def _read_layer_record(fp, encoding): """ Reads single layer record. """ top, left, bottom, right, num_channels = read_fmt("4i H", fp) logger.debug(' top=%d, left=%d, bottom=%d, right=%d, num_channels=%d', top, left, bottom, right, num_channels) channel_info = [] for channel_num in range(num_channels): info = ChannelInfo(*read_fmt("hI", fp)) channel_info.append(info) sig = fp.read(4) if sig != b'8BIM': raise Error("Error parsing layer: invalid signature (%r)" % sig) blend_mode = fp.read(4) if not BlendMode.is_known(blend_mode): warnings.warn("Unknown blend mode (%s)" % blend_mode) opacity, clipping, flags, extra_length = read_fmt("BBBxI", fp) if not Clipping.is_known(clipping): warnings.warn("Unknown clipping (%s)" % clipping) logger.debug(' extra_length=%s', extra_length) flags = LayerFlags( bool(flags & 1), not bool(flags & 2), # why "not"? bool(flags & 16) if bool(flags & 8) else None ) start_pos = fp.tell() mask_data = _read_layer_mask_data(fp) blending_ranges = _read_layer_blending_ranges(fp) name = read_pascal_string(fp, encoding, 4) remaining_length = extra_length - (fp.tell() - start_pos) logger.debug(' reading layer tagged blocks...') logger.debug(' length=%d, start_pos=%d', remaining_length, fp.tell()) tagged_blocks = _read_layer_tagged_blocks(fp, remaining_length) remaining_length = extra_length - (fp.tell() - start_pos) if remaining_length > 0: fp.seek(remaining_length, 1) # skip the remainder logger.debug(' skipping %s bytes', remaining_length) return LayerRecord( top, left, bottom, right, num_channels, channel_info, blend_mode, opacity, clipping, flags, mask_data, blending_ranges, name, tagged_blocks )
def decode(data): """ Reads and decodes info about linked layers. These are embedded files (embedded smart objects). But Adobe calls them "linked layers", so we'll follow that nomenclature. Note that non-embedded smart objects are not included here. """ fp = io.BytesIO(data) layers = [] while True: start = fp.tell() length_buf = fp.read(8) if not length_buf: break # end of file length = struct.unpack(str('>Q'), length_buf)[0] liFD, version = read_fmt('4s I', fp) if liFD != b'liFD': warnings.warn('unknown layer type') break unique_id = read_pascal_string(fp, 'ascii') filename = read_unicode_string(fp) filetype, creator, filelength, have_file_open_descriptor = read_fmt('4s 4s Q B', fp) filetype = str(filetype) if have_file_open_descriptor: # Does not seem to contain any useful information undocumented_integer = read_fmt("I", fp) file_open_descriptor = decode_descriptor(None, fp) else: file_open_descriptor = None decoded = fp.read(filelength) # Undocumented extra field if version == 5: uuid = read_unicode_string(fp) else: uuid = None layers.append( LinkedLayer(version, unique_id, filename, filetype, file_open_descriptor, creator, decoded, uuid) ) # Gobble up anything that we don't know how to decode expected_position = start + 8 + length # first 8 bytes contained the length if expected_position != fp.tell(): warnings.warn('skipping over undocumented additional fields') fp.read(expected_position - fp.tell()) # Each layer is padded to start and end at 4-byte boundary pad = -fp.tell() % 4 fp.read(pad) return LinkedLayerCollection(layers)
def _read_block(fp, encoding): """ Reads single image resource block. Such blocks contain non-pixel data for the images (e.g. pen tool paths). """ sig = fp.read(4) if sig != b"8BIM": raise Error("Invalid resource signature (%r)" % sig) resource_id = read_fmt("H", fp)[0] name = read_pascal_string(fp, encoding, 2) data_size = pad(read_fmt("I", fp)[0], 2) data = fp.read(data_size) return ImageResource(resource_id, name, data)
def _read_layer_record(fp, encoding): """ Reads single layer record. """ top, left, bottom, right, num_channels = read_fmt("4i H", fp) channel_info = [] for channel_num in range(num_channels): info = ChannelInfo(*read_fmt("hI", fp)) channel_info.append(info) sig = fp.read(4) if sig != b'8BIM': raise Error("Error parsing layer: invalid signature (%r)" % sig) blend_mode = fp.read(4).decode('ascii') if not BlendMode.is_known(blend_mode): warnings.warn("Unknown blend mode (%s)" % blend_mode) opacity, clipping, flags, extra_length = read_fmt("BBBxI", fp) flags = LayerFlags(bool(flags & 1), not bool(flags & 2)) # why not? if not Clipping.is_known(clipping): warnings.warn("Unknown clipping: %s" % clipping) start = fp.tell() mask_data = _read_layer_mask_data(fp) blending_ranges = _read_layer_blending_ranges(fp) name = read_pascal_string(fp, encoding, 4) remaining_length = extra_length - (fp.tell()-start) tagged_blocks = _read_layer_tagged_blocks(fp, remaining_length) remaining_length = extra_length - (fp.tell()-start) fp.seek(remaining_length, 1) # skip the reminder return LayerRecord( top, left, bottom, right, num_channels, channel_info, blend_mode, opacity, clipping, flags, mask_data, blending_ranges, name, tagged_blocks )
def _read_block(fp, encoding): """ Reads single image resource block. Such blocks contain non-pixel data for the images (e.g. pen tool paths). """ sig = fp.read(4) if not sig in (b'8BIM', b'MeSa'): raise Error("Invalid resource signature (%r)" % sig) resource_id = read_fmt("H", fp)[0] name = read_pascal_string(fp, encoding, 2) data_size = pad(read_fmt("I", fp)[0], 2) if not data_size: logger.debug( "Found image resource with no data (%d %s). Dropping..." % ( resource_id, ImageResourceID.name_of(resource_id) )) return None data = fp.read(data_size) return ImageResource(resource_id, name, data)
def _decode_caption_pascal(data): fp = io.BytesIO(data) return read_pascal_string(fp, 'ascii')
def read(cls, fp, **kwargs): return cls(read_pascal_string(fp, 'macroman'))
def read(cls, fp, **kwargs): items = [] while is_readable(fp): items.append(read_pascal_string(fp, 'macroman', padding=1)) return cls(items)
def _read_extra(cls, fp, encoding, version): mask_data = MaskData.read(fp) blending_ranges = LayerBlendingRanges.read(fp) name = read_pascal_string(fp, encoding, padding=4) tagged_blocks = TaggedBlocks.read(fp, version=version, padding=1) return mask_data, blending_ranges, name, tagged_blocks
def _decode_clipping_path_name(data): fp = io.BytesIO(data) # TODO: flatness and fill rule decoding? return read_pascal_string(fp, 'ascii')
def pascal_string(data): return read_pascal_string(io.BytesIO(data))