예제 #1
0
class PlacedLayerData(BaseElement):
    """
    PlacedLayerData structure.
    """
    kind = attr.ib(default=b'plcL', type=bytes)
    version = attr.ib(default=3, type=int, validator=in_((3, )))
    uuid = attr.ib(default='', type=bytes)
    page = attr.ib(default=0, type=int)
    total_pages = attr.ib(default=0, type=int)
    anti_alias = attr.ib(default=0, type=int)
    layer_type = attr.ib(default=PlacedLayerType.UNKNOWN,
                         converter=PlacedLayerType,
                         validator=in_(PlacedLayerType))
    transform = attr.ib(default=(0., ) * 8, type=tuple)
    warp = attr.ib(default=None)

    @classmethod
    def read(cls, fp, **kwargs):
        kind, version = read_fmt('4sI', fp)
        uuid = read_pascal_string(fp, 'macroman', padding=1)
        page, total_pages, anti_alias, layer_type = read_fmt('4I', fp)
        transform = read_fmt('8d', fp)
        warp = DescriptorBlock2.read(fp, padding=1)
        return cls(kind, version, uuid, page, total_pages, anti_alias,
                   layer_type, transform, warp)

    def write(self, fp, padding=4, **kwargs):
        written = write_fmt(fp, '4sI', self.kind, self.version)
        written += write_pascal_string(fp, self.uuid, 'macroman', padding=1)
        written += write_fmt(fp, '4I', self.page, self.total_pages,
                             self.anti_alias, self.layer_type.value)
        written += write_fmt(fp, '8d', *self.transform)
        written += self.warp.write(fp, padding=1)
        written += write_padding(fp, written, padding)
        return written
예제 #2
0
class TypeToolObjectSetting(BaseElement):
    """
    TypeToolObjectSetting structure.

    .. py:attribute:: version
    .. py:attribute:: transform

        Tuple of affine transform parameters (xx, xy, yx, yy, tx, ty).

    .. py:attribute:: text_version
    .. py:attribute:: text_data
    .. py:attribute:: warp_version
    .. py:attribute:: warp
    .. py:attribute:: left
    .. py:attribute:: top
    .. py:attribute:: right
    .. py:attribute:: bottom
    """
    version = attr.ib(default=1, type=int)
    transform = attr.ib(default=(0., ) * 6, type=tuple)
    text_version = attr.ib(default=1, type=int, validator=in_((50, )))
    text_data = attr.ib(default=None, type=DescriptorBlock)
    warp_version = attr.ib(default=1, type=int, validator=in_((1, )))
    warp = attr.ib(default=None, type=DescriptorBlock)
    left = attr.ib(default=0, type=int)
    top = attr.ib(default=0, type=int)
    right = attr.ib(default=0, type=int)
    bottom = attr.ib(default=0, type=int)

    @classmethod
    def read(cls, fp, **kwargs):
        version = read_fmt('H', fp)[0]
        transform = read_fmt('6d', fp)
        text_version = read_fmt('H', fp)[0]
        text_data = DescriptorBlock.read(fp)
        # Engine data.
        if b'EngineData' in text_data:
            try:
                engine_data = text_data[b'EngineData'].value
                engine_data = EngineData.frombytes(engine_data)
                text_data[b'EngineData'].value = engine_data
            except:
                logger.warning('Failed to read engine data')
        warp_version = read_fmt('H', fp)[0]
        warp = DescriptorBlock.read(fp)
        left, top, right, bottom = read_fmt("4i", fp)
        return cls(version, transform, text_version, text_data, warp_version,
                   warp, left, top, right, bottom)

    def write(self, fp, padding=4, **kwargs):
        written = write_fmt(fp, 'H6d', self.version, *self.transform)
        written += write_fmt(fp, 'H', self.text_version)
        written += self.text_data.write(fp, padding=1)
        written += write_fmt(fp, 'H', self.warp_version)
        written += self.warp.write(fp, padding=1)
        written += write_fmt(fp, '4i', self.left, self.top, self.right,
                             self.bottom)
        written += write_padding(fp, written, padding)
        return written
예제 #3
0
class Annotation(BaseElement):
    """
    Annotation structure.

    .. py:attribute:: kind
    .. py:attribute:: is_open
    """
    kind = attr.ib(default=b'txtA',
                   type=bytes,
                   validator=in_((b'txtA', b'sndM')))
    is_open = attr.ib(default=0, type=int)
    flags = attr.ib(default=0, type=int)
    optional_blocks = attr.ib(default=1, type=int)
    icon_location = attr.ib(factory=lambda: [0, 0, 0, 0], converter=list)
    popup_location = attr.ib(factory=lambda: [0, 0, 0, 0], converter=list)
    color = attr.ib(factory=Color)
    author = attr.ib(default='', type=str)
    name = attr.ib(default='', type=str)
    mod_date = attr.ib(default='', type=str)
    marker = attr.ib(default=b'txtC',
                     type=bytes,
                     validator=in_((b'txtC', b'sndM')))
    data = attr.ib(default=b'', type=bytes)

    @classmethod
    def read(cls, fp, **kwargs):
        kind, is_open, flags, optional_blocks = read_fmt('4s2BH', fp)
        icon_location = read_fmt('4i', fp)
        popup_location = read_fmt('4i', fp)
        color = Color.read(fp)
        author = read_pascal_string(fp, 'macroman', padding=2)
        name = read_pascal_string(fp, 'macroman', padding=2)
        mod_date = read_pascal_string(fp, 'macroman', padding=2)
        length, marker = read_fmt('I4s', fp)
        data = read_length_block(fp)
        return cls(kind, is_open, flags, optional_blocks, icon_location,
                   popup_location, color, author, name, mod_date, marker, data)

    def write(self, fp, **kwargs):
        written = write_fmt(fp, '4s2BH', self.kind, self.is_open, self.flags,
                            self.optional_blocks)
        written += write_fmt(fp, '4i', *self.icon_location)
        written += write_fmt(fp, '4i', *self.popup_location)
        written += self.color.write(fp)
        written += write_pascal_string(fp, self.author, 'macroman', padding=2)
        written += write_pascal_string(fp, self.name, 'macroman', padding=2)
        written += write_pascal_string(fp,
                                       self.mod_date,
                                       'macroman',
                                       padding=2)
        written += write_fmt(fp, 'I4s', len(self.data) + 12, self.marker)
        written += write_length_block(fp, lambda f: write_bytes(f, self.data))
        return written
예제 #4
0
class UnitFloat(NumericElement):
    """
    Unit float structure.

    .. py:attribute:: unit
    .. py:attribute:: value
    """
    value = attr.ib(default=0.0, type=float)
    unit = attr.ib(default=UnitFloatType.NONE,
                   converter=UnitFloatType,
                   validator=in_(UnitFloatType))

    @classmethod
    def read(cls, fp):
        """Read the element from a file-like object.

        :param fp: file-like object
        """
        unit, value = read_fmt('4sd', fp)
        return cls(unit=UnitFloatType(unit), value=value)

    def write(self, fp):
        """Write the element to a file-like object.

        :param fp: file-like object
        """
        return write_fmt(fp, '4sd', self.unit.value, self.value)

    def _repr_pretty_(self, p, cycle):
        if cycle:
            return self.__repr__()
        p.pretty(self.value)
        p.text(' ')
        p.text(self.unit.name)
예제 #5
0
class ChannelMixer(BaseElement):
    """
    ChannelMixer structure.

    .. py:attribute:: version
    .. py:attribute:: monochrome
    .. py:attribute:: data
    """
    version = attr.ib(default=1, type=int, validator=in_((1, )))
    monochrome = attr.ib(default=0, type=int)
    data = attr.ib(factory=list, converter=list)
    unknown = attr.ib(default=b'', type=bytes, repr=False)

    @classmethod
    def read(cls, fp, **kwargs):
        version, monochrome = read_fmt('2H', fp)
        data = list(read_fmt('5h', fp))
        unknown = fp.read()
        return cls(version, monochrome, data, unknown)

    def write(self, fp, **kwargs):
        written = write_fmt(fp, '2H', self.version, self.monochrome)
        written += write_fmt(fp, '5h', *self.data)
        written += write_bytes(fp, self.unknown)
        return written
예제 #6
0
class SectionDividerSetting(BaseElement):
    """
    SectionDividerSetting structure.

    .. py:attribute:: kind
    .. py:attribute:: key
    .. py:attribute:: sub_type
    """
    kind = attr.ib(default=SectionDivider.OTHER,
                   converter=SectionDivider,
                   validator=in_(SectionDivider))
    signature = attr.ib(default=None, repr=False)
    key = attr.ib(default=None)
    sub_type = attr.ib(default=None)

    @classmethod
    def read(cls, fp, **kwargs):
        kind = SectionDivider(read_fmt('I', fp)[0])
        signature, key = None, None
        if is_readable(fp, 8):
            signature = read_fmt('4s', fp)[0]
            assert signature == b'8BIM', 'Invalid signature %r' % signature
            key = BlendMode(read_fmt('4s', fp)[0])
        sub_type = None
        if is_readable(fp, 4):
            sub_type = read_fmt('I', fp)[0]
        return cls(kind, signature=signature, key=key, sub_type=sub_type)

    def write(self, fp, **kwargs):
        written = write_fmt(fp, 'I', self.kind.value)
        if self.signature and self.key:
            written += write_fmt(fp, '4s4s', self.signature, self.key.value)
            if self.sub_type is not None:
                written += write_fmt(fp, 'I', self.sub_type)
        return written
예제 #7
0
class ShadowInfo(BaseElement):
    """
    Effects layer shadow info.

    .. py:attribute:: version
    .. py:attribute:: blur
    .. py:attribute:: intensity
    .. py:attribute:: angle
    .. py:attribute:: distance
    .. py:attribute:: color
    .. py:attribute:: blend_mode
    .. py:attribute:: enabled
    .. py:attribute:: use_global_angle
    .. py:attribute:: opacity
    .. py:attribute:: native_color
    """
    version = attr.ib(default=0, type=int)
    blur = attr.ib(default=0, type=int)
    intensity = attr.ib(default=0, type=int)
    angle = attr.ib(default=0, type=int)
    distance = attr.ib(default=0, type=int)
    color = attr.ib(factory=Color)
    blend_mode = attr.ib(default=BlendMode.NORMAL, converter=BlendMode,
                         validator=in_(BlendMode))
    enabled = attr.ib(default=0, type=int)
    use_global_angle = attr.ib(default=0, type=int)
    opacity = attr.ib(default=0, type=int)
    native_color = attr.ib(factory=Color)

    @classmethod
    def read(cls, fp):
        # TODO: Check 4-byte = 2-byte int + 2-byte fraction?
        version, blur, intensity, angle, distance = read_fmt(
            'IIIiI', fp
        )
        color = Color.read(fp)
        signature = read_fmt('4s', fp)[0]
        assert signature == b'8BIM', 'Invalid signature %r' % (signature)
        blend_mode = BlendMode(read_fmt('4s', fp)[0])
        enabled, use_global_angle, opacity = read_fmt('3B', fp)
        native_color = Color.read(fp)
        return cls(
            version, blur, intensity, angle, distance, color, blend_mode,
            enabled, use_global_angle, opacity, native_color
        )

    def write(self, fp):
        written = write_fmt(
            fp, 'IIIiI', self.version, self.blur, self.intensity,
            self.angle, self.distance
        )
        written += self.color.write(fp)
        written += write_fmt(
            fp, '4s4s3B', b'8BIM', self.blend_mode.value, self.enabled,
            self.use_global_angle, self.opacity
        )
        written += self.native_color.write(fp)
        return written
예제 #8
0
class Pattern(BaseElement):
    """
    Pattern structure.

    .. py:attribute:: version
    .. py:attribute:: image_mode
    .. py:attribute:: point
    .. py:attribute:: name
    .. py:attribute:: pattern_id
    .. py:attribute:: color_table
    .. py:attribute:: data
    """
    version = attr.ib(default=1, type=int)
    image_mode = attr.ib(default=ColorMode,
                         converter=ColorMode,
                         validator=in_(ColorMode))
    point = attr.ib(default=None)
    name = attr.ib(default='', type=str)
    pattern_id = attr.ib(default='', type=str)
    color_table = attr.ib(default=None)
    data = attr.ib(default=None)

    @classmethod
    def read(cls, fp, **kwargs):
        version = read_fmt('I', fp)[0]
        assert version == 1, 'Invalid version %d' % (version)
        image_mode = ColorMode(read_fmt('I', fp)[0])
        point = read_fmt('2h', fp)
        name = read_unicode_string(fp)
        pattern_id = read_pascal_string(fp, encoding='ascii', padding=1)
        color_table = None
        if image_mode == ColorMode.INDEXED:
            color_table = [read_fmt("3B", fp) for i in range(256)]
            read_fmt('4x', fp)

        data = VirtualMemoryArrayList.read(fp)
        return cls(version, image_mode, point, name, pattern_id, color_table,
                   data)

    def write(self, fp, **kwargs):
        written = write_fmt(fp, '2I', self.version, self.image_mode.value)
        written += write_fmt(fp, '2h', *self.point)
        written += write_unicode_string(fp, self.name)
        written += write_pascal_string(fp,
                                       self.pattern_id,
                                       encoding='ascii',
                                       padding=1)
        if self.color_table:
            for row in self.color_table:
                written += write_fmt(fp, '3B', *row)
            written += write_fmt(fp, '4x')
        written += self.data.write(fp)
        return written
예제 #9
0
class SmartObjectLayerData(BaseElement):
    """
    VersionedDescriptorBlock structure.

    .. py:attribute:: kind
    .. py:attribute:: version
    .. py:attribute:: data
    """
    kind = attr.ib(default=b'soLD', type=bytes, validator=in_((b'soLD', )))
    version = attr.ib(default=5, type=int, validator=in_((4, 5)))
    data = attr.ib(default=None, type=DescriptorBlock)

    @classmethod
    def read(cls, fp, **kwargs):
        kind, version = read_fmt('4sI', fp)
        data = DescriptorBlock.read(fp)
        return cls(kind, version, data)

    def write(self, fp, padding=4, **kwargs):
        written = write_fmt(fp, '4sI', self.kind, self.version)
        written += self.data.write(fp, padding=1)
        written += write_padding(fp, written, padding)
        return written
예제 #10
0
class Levels(ListElement):
    """
    List of level records. See :py:class:
    `~psd_tools2.psd.adjustments.LevelRecord`.

    .. py:attribute:: version

        Version.

    .. py:attribute:: extra_version

        Version of the extra field.
    """
    version = attr.ib(default=0, type=int, validator=in_((2, )))
    extra_version = attr.ib(default=None)

    @classmethod
    def read(cls, fp, **kwargs):
        version = read_fmt('H', fp)[0]
        assert version == 2, 'Invalid version %d' % (version)
        items = [LevelRecord.read(fp) for _ in range(29)]

        extra_version = None
        if is_readable(fp, 6):
            signature, extra_version = read_fmt('4sH', fp)
            assert signature == b'Lvls', 'Invalid signature %r' % (signature)
            assert extra_version == 3, 'Invalid extra version %d' % (
                extra_version)
            count = read_fmt('H', fp)[0]
            items += [LevelRecord.read(fp) for _ in range(count - 29)]

        return cls(version=version, extra_version=extra_version, items=items)

    def write(self, fp, **kwargs):
        written = write_fmt(fp, 'H', self.version)
        for index in range(29):
            written += self[index].write(fp)

        if self.extra_version is not None:
            written += write_fmt(fp, '4sH', b'Lvls', self.extra_version)
            written += write_fmt(fp, 'H', len(self))
            for index in range(29, len(self)):
                written += self[index].write(fp)

        written += write_padding(fp, written, 4)
        return written
예제 #11
0
class InnerGlowInfo(BaseElement, _GlowInfo):
    """
    Effects layer inner glow info.

    .. py:attribute:: version
    .. py:attribute:: blur
    .. py:attribute:: intensity
    .. py:attribute:: color
    .. py:attribute:: blend_mode
    .. py:attribute:: enabled
    .. py:attribute:: opacity
    .. py:attribute:: invert
    .. py:attribute:: native_color
    """
    version = attr.ib(default=0, type=int)
    blur = attr.ib(default=0, type=int)
    intensity = attr.ib(default=0, type=int)
    color = attr.ib(factory=Color)
    blend_mode = attr.ib(default=BlendMode.NORMAL, converter=BlendMode,
                         validator=in_(BlendMode))
    enabled = attr.ib(default=0, type=int)
    opacity = attr.ib(default=0, type=int)
    invert = attr.ib(default=None)
    native_color = attr.ib(default=None)

    @classmethod
    def read(cls, fp):
        version, blur, intensity, color, blend_mode, enabled, opacity = (
            cls._read_body(fp)
        )
        invert, native_color = None, None
        if version >= 2:
            invert = read_fmt('B', fp)[0]
            native_color = Color.read(fp)
        return cls(
            version, blur, intensity, color, blend_mode, enabled, opacity,
            invert, native_color
        )

    def write(self, fp):
        written = self._write_body(fp)
        if self.version >= 2:
            written += write_fmt(fp, 'B', self.invert)
            written += self.native_color.write(fp)
        return written
예제 #12
0
class PhotoFilter(BaseElement):
    """
    PhotoFilter structure.

    .. py:attribute:: version
    .. py:attribute:: xyz
    .. py:attribute:: color_space
    .. py:attribute:: color_components
    .. py:attribute:: density
    .. py:attribute:: luminosity
    """
    version = attr.ib(default=0, type=int, validator=in_((2, 3)))
    xyz = attr.ib(default=(0, 0, 0), type=tuple)
    color_space = attr.ib(default=None)
    color_components = attr.ib(default=None)
    density = attr.ib(default=None)
    luminosity = attr.ib(default=None)

    @classmethod
    def read(cls, fp, **kwargs):
        version = read_fmt('H', fp)[0]
        assert version in (2, 3), 'Invalid version %d' % (version)
        if version == 3:
            xyz = read_fmt('3I', fp)
            color_space = None
            color_components = None
        else:
            xyz = None
            color_space = read_fmt('H', fp)[0]
            color_components = read_fmt('4H', fp)
        density, luminosity = read_fmt('IB', fp)
        return cls(version, xyz, color_space, color_components, density,
                   luminosity)

    def write(self, fp, **kwargs):
        written = write_fmt(fp, 'H', self.version)
        if self.version == 3:
            written += write_fmt(fp, '3I', *self.xyz)
        else:
            written += write_fmt(fp, 'H4H', self.color_space,
                                 *self.color_components)
        written += write_fmt(fp, 'IB', self.density, self.luminosity)
        written += write_padding(fp, written, 4)
        return written
예제 #13
0
class DescriptorBlock(Descriptor):
    """
    Dict-like Descriptor-based structure. See
    :py:class:`~psd_tools2.psd.descriptor.Descriptor`.

    .. py:attribute:: version
    """
    version = attr.ib(default=16, type=int, validator=in_((16, )))

    @classmethod
    def read(cls, fp, **kwargs):
        version = read_fmt('I', fp)[0]
        return cls(version=version, **cls._read_body(fp))

    def write(self, fp, padding=4, **kwargs):
        written = write_fmt(fp, 'I', self.version)
        written += self._write_body(fp)
        written += write_padding(fp, written, padding)
        return written
예제 #14
0
class MetadataSetting(BaseElement):
    """
    MetadataSetting structure.
    """
    signature = attr.ib(default=b'8BIM',
                        type=bytes,
                        repr=False,
                        validator=in_((b'8BIM', )))
    key = attr.ib(default=b'', type=bytes)
    copy_on_sheet = attr.ib(default=False, type=bool)
    data = attr.ib(default=b'', type=bytes)

    @classmethod
    def read(cls, fp, **kwargs):
        signature = read_fmt('4s', fp)[0]
        assert signature == b'8BIM', 'Invalid signature %r' % signature
        key, copy_on_sheet = read_fmt("4s?3x", fp)
        data = read_length_block(fp)
        if key == b'mdyn':
            with io.BytesIO(data) as f:
                data = read_fmt('I', f)[0]
        elif key in (b'cust', b'cmls', b'extn'):
            data = DescriptorBlock.frombytes(data, padding=4)
        else:
            message = 'Unknown metadata key %r' % (key)
            logger.warning(message)
            warn(message)
            data = data
        return cls(signature, key, copy_on_sheet, data)

    def write(self, fp, **kwargs):
        written = write_fmt(fp, '4s4s?3x', self.signature, self.key,
                            self.copy_on_sheet)

        def writer(f):
            if hasattr(self.data, 'write'):
                return self.data.write(f, padding=4)
            elif isinstance(self.data, int):
                return write_fmt(fp, 'I', self.data)
            return write_bytes(f, self.data)

        written += write_length_block(fp, writer)
        return written
예제 #15
0
class CurvesExtraMarker(ListElement):
    """
    Curves extra marker structure.

    .. py:attribute:: version
    """
    version = attr.ib(default=4, type=int, validator=in_((3, 4)))

    @classmethod
    def read(cls, fp, **kwargs):
        signature, version, count = read_fmt('4sHI', fp)
        assert signature == b'Crv ', 'Invalid signature %r' % (signature)
        items = []
        for i in range(count):
            items.append(CurvesExtraItem.read(fp, **kwargs))
        return cls(version=version, items=items)

    def write(self, fp, **kwargs):
        written = write_fmt(fp, '4sHI', b'Crv ', self.version, len(self))
        written += sum(item.write(fp) for item in self)
        return written
예제 #16
0
class Slices(BaseElement):
    """
    Slices resource.

    .. py:attribute:: version
    .. py:attribute:: data
    """
    version = attr.ib(default=0, type=int, validator=in_((6, 7, 8)))
    data = attr.ib(default=None)

    @classmethod
    def read(cls, fp, **kwargs):
        version = read_fmt('I', fp)[0]
        assert version in (6, 7, 8), 'Invalid version %d' % (version)
        if version == 6:
            return cls(version=version, data=SlicesV6.read(fp))
        return cls(version=version, data=DescriptorBlock.read(fp))

    def write(self, fp, **kwargs):
        written = write_fmt(fp, 'I', self.version)
        written += self.data.write(fp, padding=1)
        return written
예제 #17
0
class UnitFloats(BaseElement):
    """
    Unit floats structure.

    .. py:attribute:: unit
    .. py:attribute:: values
    """
    unit = attr.ib(default=UnitFloatType.NONE,
                   converter=UnitFloatType,
                   validator=in_(UnitFloatType))
    values = attr.ib(factory=list)

    @classmethod
    def read(cls, fp):
        """Read the element from a file-like object.

        :param fp: file-like object
        """
        unit, count = read_fmt('4sI', fp)
        values = list(read_fmt('%dd' % count, fp))
        return cls(unit, values)

    def write(self, fp):
        """Write the element to a file-like object.

        :param fp: file-like object
        """
        return write_fmt(fp, '4sI%dd' % len(self.values), self.unit.value,
                         len(self.values), *self.values)

    def __iter__(self):
        for value in self.values:
            yield value

    def __getitem__(self, index):
        return self.values[index]

    def __len__(self):
        return len(self.values)
예제 #18
0
class PrintScale(BaseElement):
    """
    Print scale structure.

    .. py:attribute:: style
    .. py:attribute:: x
    .. py:attribute:: y
    .. py:attribute:: scale
    """
    style = attr.ib(default=PrintScaleStyle.CENTERED,
                    converter=PrintScaleStyle,
                    validator=in_(PrintScaleStyle))
    x = attr.ib(default=0., type=float)
    y = attr.ib(default=0., type=float)
    scale = attr.ib(default=0., type=float)

    @classmethod
    def read(cls, fp, **kwargs):
        return cls(*read_fmt('H3f', fp))

    def write(self, fp, **kwargs):
        return write_fmt(fp, 'H3f', self.style.value, self.x, self.y,
                         self.scale)
예제 #19
0
class SelectiveColor(BaseElement):
    """
    SelectiveColor structure.

    .. py:attribute:: version
    .. py:attribute:: method
    .. py:attribute:: data
    """
    version = attr.ib(default=1, type=int, validator=in_((1, )))
    method = attr.ib(default=0, type=int)
    data = attr.ib(factory=list, converter=list)

    @classmethod
    def read(cls, fp, **kwargs):
        version, method = read_fmt('2H', fp)
        data = [read_fmt('4h', fp) for i in range(10)]
        return cls(version, method, data)

    def write(self, fp, **kwargs):
        written = write_fmt(fp, '2H', self.version, self.method)
        for plate in self.data:
            written += write_fmt(fp, '4h', *plate)
        return written
예제 #20
0
class ChannelInfo(BaseElement):
    """
    Channel information.

    .. py:attribute:: id

        Channel ID: 0 = red, 1 = green, etc.; -1 = transparency mask; -2 =
        user supplied layer mask, -3 real user supplied layer mask (when both
        a user mask and a vector mask are present). See
        :py:class:`~psd_tools2.constants.ChannelID`.

    .. py:attribute:: length

        Length of the corresponding channel data.
    """
    id = attr.ib(default=ChannelID.CHANNEL_0,
                 converter=ChannelID,
                 validator=in_(ChannelID))
    length = attr.ib(default=0, type=int)

    @classmethod
    def read(cls, fp, version=1):
        """Read the element from a file-like object.

        :param fp: file-like object
        :param version: psd file version
        :rtype: :py:class:`.ChannelInfo`
        """
        return cls(*read_fmt(('hI', 'hQ')[version - 1], fp))

    def write(self, fp, version=1):
        """Write the element to a file-like object.

        :param fp: file-like object
        :param version: psd file version
        """
        return write_fmt(fp, ('hI', 'hQ')[version - 1], *attr.astuple(self))
예제 #21
0
class SolidFillInfo(BaseElement):
    """
    Effects layer inner glow info.

    .. py:attribute:: version
    .. py:attribute:: blend_mode
    .. py:attribute:: color
    .. py:attribute:: opacity
    .. py:attribute:: enabled
    .. py:attribute:: native_color
    """
    version = attr.ib(default=2, type=int)
    blend_mode = attr.ib(default=BlendMode.NORMAL, converter=BlendMode,
                         validator=in_(BlendMode))
    color = attr.ib(factory=Color)
    opacity = attr.ib(default=0, type=int)
    enabled = attr.ib(default=0, type=int)
    native_color = attr.ib(factory=Color)

    @classmethod
    def read(cls, fp):
        version = read_fmt('I', fp)[0]
        signature, blend_mode = read_fmt('4s4s', fp)
        assert signature == b'8BIM', 'Invalid signature %r' % (signature)
        color = Color.read(fp)
        opacity, enabled = read_fmt('2B', fp)
        native_color = Color.read(fp)
        return cls(version, blend_mode, color, opacity, enabled, native_color)

    def write(self, fp):
        written = write_fmt(fp, 'I4s4s', self.version, b'8BIM',
                            self.blend_mode.value)
        written += self.color.write(fp)
        written += write_fmt(fp, '2B', self.opacity, self.enabled)
        written += self.native_color.write(fp)
        return written
예제 #22
0
class LinkedLayer(BaseElement):
    """
    LinkedLayer structure.

    .. py:attribute:: kind
    .. py:attribute:: version
    .. py:attribute:: uuid
    .. py:attribute:: filename
    .. py:attribute:: filetype
    .. py:attribute:: creator
    .. py:attribute:: filesize
    .. py:attribute:: open_file
    .. py:attribute:: linked_file
    .. py:attribute:: timestamp
    .. py:attribute:: data
    .. py:attribute:: child_id
    .. py:attribute:: mod_time
    .. py:attribute:: lock_state
    """
    kind = attr.ib(default=LinkedLayerType.ALIAS,
                   validator=in_(LinkedLayerType))
    version = attr.ib(default=1, validator=range_(1, 7))
    uuid = attr.ib(default='', type=str)
    filename = attr.ib(default='', type=str)
    filetype = attr.ib(default=b'\x00\x00\x00\x00', type=bytes)
    creator = attr.ib(default=b'\x00\x00\x00\x00', type=bytes)
    filesize = attr.ib(default=None)
    open_file = attr.ib(default=None)
    linked_file = attr.ib(default=None)
    timestamp = attr.ib(default=None)
    data = attr.ib(default=None)
    child_id = attr.ib(default=None)
    mod_time = attr.ib(default=None)
    lock_state = attr.ib(default=None)

    @classmethod
    def read(cls, fp, **kwargs):
        kind = LinkedLayerType(read_fmt('4s', fp)[0])
        version = read_fmt('I', fp)[0]
        assert 1 <= version and version <= 7, 'Invalid version %d' % (version)
        uuid = read_pascal_string(fp, 'macroman', padding=1)
        filename = read_unicode_string(fp)
        filetype, creator, datasize, open_file = read_fmt('4s4sQB', fp)
        if open_file:
            open_file = DescriptorBlock.read(fp, padding=1)
        else:
            open_file = None

        linked_file = None
        timestamp = None
        data = None
        filesize = None
        child_id = None
        mod_time = None
        lock_state = None

        if kind == LinkedLayerType.EXTERNAL:
            linked_file = DescriptorBlock.read(fp, padding=1)
            if version > 3:
                timestamp = read_fmt('I4Bd', fp)
            filesize = read_fmt('Q', fp)[0]  # External file size.
            if version > 2:
                data = fp.read(datasize)
        elif kind == LinkedLayerType.ALIAS:
            read_fmt('8x', fp)
        if kind == LinkedLayerType.DATA:
            data = fp.read(datasize)
            assert len(data) == datasize, '(%d vs %d)' % (len(data), datasize)

        # The followings are not well documented...
        if version >= 5:
            child_id = read_unicode_string(fp)
        if version >= 6:
            mod_time = read_fmt('d', fp)[0]
        if version >= 7:
            lock_state = read_fmt('B', fp)[0]
        if kind == LinkedLayerType.EXTERNAL and version == 2:
            data = fp.read(datasize)

        return cls(kind, version, uuid, filename, filetype, creator, filesize,
                   open_file, linked_file, timestamp, data, child_id, mod_time,
                   lock_state)

    def write(self, fp, padding=1, **kwargs):
        written = write_fmt(fp, '4sI', self.kind.value, self.version)
        written += write_pascal_string(fp, self.uuid, 'macroman', padding=1)
        written += write_unicode_string(fp, self.filename)
        written += write_fmt(fp, '4s4sQB', self.filetype, self.creator,
                             len(self.data) if self.data is not None else 0,
                             self.open_file is not None)
        if self.open_file is not None:
            written += self.open_file.write(fp, padding=1)

        if self.kind == LinkedLayerType.EXTERNAL:
            written += self.linked_file.write(fp, padding=1)
            if self.version > 3:
                written += write_fmt(fp, 'I4Bd', *self.timestamp)
            written += write_fmt(fp, 'Q', self.filesize)
            if self.version > 2:
                written += write_bytes(fp, self.data)
        elif self.kind == LinkedLayerType.ALIAS:
            written += write_fmt(fp, '8x')
        if self.kind == LinkedLayerType.DATA:
            written += write_bytes(fp, self.data)

        if self.child_id is not None:
            written += write_unicode_string(fp, self.child_id)
        if self.mod_time is not None:
            written += write_fmt(fp, 'd', self.mod_time)
        if self.lock_state is not None:
            written += write_fmt(fp, 'B', self.lock_state)

        if self.kind == LinkedLayerType.EXTERNAL and self.version == 2:
            written += write_bytes(fp, self.data)

        written += write_padding(fp, written, padding)
        return written
예제 #23
0
class ImageResource(BaseElement):
    """
    Image resource block.

    .. py:attribute:: signature

        Binary signature, always ``b'8BIM'``.

    .. py:attribute:: key

        Unique identifier for the resource. See
        :py:class:`~psd_tools2.constants.ImageResourceID`.

    .. py:attribute:: name
    .. py:attribute:: data

        The resource data.
    """
    signature = attr.ib(default=b'8BIM',
                        type=bytes,
                        repr=False,
                        validator=in_(
                            {b'8BIM', b'MeSa', b'AgHg', b'PHUT', b'DCSR'}))
    key = attr.ib(default=1000, type=int)
    name = attr.ib(default='', type=str)
    data = attr.ib(default=b'', type=bytes, repr=False)

    @classmethod
    def read(cls, fp, encoding='macroman'):
        """Read the element from a file-like object.

        :param fp: file-like object
        :rtype: :py:class:`.ImageResource`
        """
        signature, key = read_fmt('4sH', fp)
        try:
            key = ImageResourceID(key)
        except ValueError:
            logger.warning('Unknown image resource %d' % (key))
        name = read_pascal_string(fp, encoding, padding=2)
        raw_data = read_length_block(fp, padding=2)
        if key in TYPES:
            data = TYPES[key].frombytes(raw_data)
            # try:
            #     _raw_data = data.tobytes(padding=1)
            #     assert _raw_data == raw_data, '%r vs %r' % (
            #         _raw_data, raw_data
            #     )
            # except AssertionError as e:
            #     logger.error(e)
            #     raise
        else:
            data = raw_data
        return cls(signature, key, name, data)

    def write(self, fp, encoding='macroman'):
        """Write the element to a file-like object.

        :param fp: file-like object
        :rtype: int
        """
        written = write_fmt(fp, '4sH', self.signature,
                            getattr(self.key, 'value', self.key))
        written += write_pascal_string(fp, self.name, encoding, 2)

        def writer(f):
            if hasattr(self.data, 'write'):
                return self.data.write(f, padding=1)
            return write_bytes(f, self.data)

        written += write_length_block(fp, writer, padding=2)
        return written
예제 #24
0
class GlobalLayerMaskInfo(BaseElement):
    """
    Global mask information.

    .. py:attribute:: overlay_color

        Overlay color space (undocumented) and color components.

    .. py:attribute:: opacity

        Opacity. 0 = transparent, 100 = opaque.

    .. py:attribute:: kind

        Kind.
        0 = Color selected--i.e. inverted;
        1 = Color protected;
        128 = use value stored per layer. This value is preferred. The others
        are for backward compatibility with beta versions.
    """
    overlay_color = attr.ib(default=None)
    opacity = attr.ib(default=0, type=int)
    kind = attr.ib(default=GlobalLayerMaskKind.PER_LAYER,
                   converter=GlobalLayerMaskKind,
                   validator=in_(GlobalLayerMaskKind))

    @classmethod
    def read(cls, fp):
        """Read the element from a file-like object.

        :param fp: file-like object
        :rtype: :py:class:`.GlobalLayerMaskInfo`
        """
        data = read_length_block(fp)
        logger.debug('reading global layer mask info, len=%d' % (len(data)))
        if len(data) == 0:
            return cls(overlay_color=None)

        with io.BytesIO(data) as f:
            return cls._read_body(f)

    @classmethod
    def _read_body(cls, fp):
        overlay_color = list(read_fmt('5H', fp))
        opacity, kind = read_fmt('HB', fp)
        return cls(overlay_color, opacity, kind)

    def write(self, fp):
        """Write the element to a file-like object.

        :param fp: file-like object
        """
        return write_length_block(fp, lambda f: self._write_body(f))

    def _write_body(self, fp):
        written = 0
        if self.overlay_color is not None:
            written = write_fmt(fp, '5H', *self.overlay_color)
            written += write_fmt(fp, 'HB', self.opacity, self.kind.value)
            written += write_padding(fp, written, 4)
        logger.debug('writing global layer mask info, len=%d' % (written))
        return written
예제 #25
0
class ChannelData(BaseElement):
    """
    Channel data.

    .. py:attribute:: compression

        Compression type. See :py:class:`~psd_tools2.constants.Compression`.

    .. py:attribute:: data

        Data.
    """
    compression = attr.ib(default=Compression.RAW,
                          converter=Compression,
                          validator=in_(Compression))
    data = attr.ib(default=b'', type=bytes, repr=False)

    @classmethod
    def read(cls, fp):
        """Read the element from a file-like object.

        :param fp: file-like object
        :rtype: :py:class:`.ChannelData`
        """
        compression = Compression(read_fmt('H', fp)[0])
        data = fp.read()
        return cls(compression, data)

    def write(self, fp, **kwargs):
        """Write the element to a file-like object.

        :param fp: file-like object
        """
        written = write_fmt(fp, 'H', self.compression.value)
        written += write_bytes(fp, self.data)
        # written += write_padding(fp, written, 2)  # Seems no padding here.
        return written

    def get_data(self, width, height, depth, version=1):
        """Get decompressed channel data.

        :param width: width.
        :param height: height.
        :param depth: bit depth of the pixel.
        :param version: psd file version.
        :rtype: bytes
        """
        return decompress(self.data, self.compression, width, height, depth,
                          version)

    def set_data(self, data, width, height, depth, version=1):
        """Set raw channel data and compress to store.

        :param data: raw data bytes to write.
        :param compression: compression type,
            see :py:class:`~psd_tools2.constants.Compression`.
        :param width: width.
        :param height: height.
        :param depth: bit depth of the pixel.
        :param version: psd file version.
        """
        self.data = compress(data, self.compression, width, height, depth,
                             version)
        return len(self.data)

    @property
    def _length(self):
        """Length of channel data block.
        """
        return 2 + len(self.data)
예제 #26
0
class LayerRecord(BaseElement):
    """
    Layer record.

    .. py:attribute:: top

        Top position.

    .. py:attribute:: left

        Left position.

    .. py:attribute:: bottom

        Bottom position.

    .. py:attribute:: right

        Right position.

    .. py:attribute:: channel_info

        List of :py:class:`.ChannelInfo`.

    .. py:attribute:: signature

        Blend mode signature ``b'8BIM'``.

    .. py:attribute:: blend_mode

        Blend mode key. See :py:class:`~psd_tools2.constants.BlendMode`.

    .. py:attribute:: opacity

        Opacity, 0 = transparent, 255 = opaque.

    .. py:attribute:: clipping

        Clipping, 0 = base, 1 = non-base. See
        :py:class:`~psd_tools2.constants.Clipping`.

    .. py:attribute:: flags

        See :py:class:`.LayerFlags`.

    .. py:attribute:: mask_data

        :py:class:`.MaskData` or None.

    .. py:attribute:: blending_ranges

        See :py:class:`~psd_tools2.constants.LayerBlendingRanges`.

    .. py:attribute:: name

        Layer name.

    .. py:attribute:: tagged_blocks

        See :py:class:`.TaggedBlocks`.
    """
    top = attr.ib(default=0, type=int)
    left = attr.ib(default=0, type=int)
    bottom = attr.ib(default=0, type=int)
    right = attr.ib(default=0, type=int)
    channel_info = attr.ib(factory=list)
    signature = attr.ib(default=b'8BIM',
                        repr=False,
                        type=bytes,
                        validator=in_((b'8BIM', )))
    blend_mode = attr.ib(default=BlendMode.NORMAL,
                         converter=BlendMode,
                         validator=in_(BlendMode))
    opacity = attr.ib(default=255, type=int, validator=range_(0, 255))
    clipping = attr.ib(default=Clipping.BASE,
                       converter=Clipping,
                       validator=in_(Clipping))
    flags = attr.ib(factory=LayerFlags)
    mask_data = attr.ib(default=None)
    blending_ranges = attr.ib(factory=LayerBlendingRanges)
    name = attr.ib(default='', type=str)
    tagged_blocks = attr.ib(factory=TaggedBlocks)

    @classmethod
    def read(cls, fp, encoding='macroman', version=1):
        """Read the element from a file-like object.

        :param fp: file-like object
        :param encoding: encoding of the string
        :param version: psd file version
        :rtype: :py:class:`.LayerRecord`
        """
        start_pos = fp.tell()
        top, left, bottom, right, num_channels = read_fmt('4iH', fp)
        channel_info = [
            ChannelInfo.read(fp, version) for i in range(num_channels)
        ]
        signature, blend_mode, opacity, clipping = read_fmt('4s4sBB', fp)
        flags = LayerFlags.read(fp)

        data = read_length_block(fp, fmt='xI')
        logger.debug('  read layer record, len=%d' % (fp.tell() - start_pos))
        with io.BytesIO(data) as f:
            self = cls(top, left, bottom, right, channel_info, signature,
                       blend_mode, opacity, clipping, flags,
                       *cls._read_extra(f, encoding, version))

        # with io.BytesIO() as f:
        #     self._write_extra(f, encoding, version)
        #     assert data == f.getvalue()

        return self

    @classmethod
    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 write(self, fp, encoding='macroman', version=1):
        """Write the element to a file-like object.

        :param fp: file-like object
        :param encoding: encoding of the string
        :param version: psd file version
        """
        start_pos = fp.tell()
        written = write_fmt(fp, '4iH', self.top, self.left, self.bottom,
                            self.right, len(self.channel_info))
        written += sum(c.write(fp, version) for c in self.channel_info)
        written += write_fmt(fp, '4s4sBB', self.signature,
                             self.blend_mode.value, self.opacity,
                             self.clipping.value)
        written += self.flags.write(fp)

        def writer(f):
            written = self._write_extra(f, encoding, version)
            logger.debug('  wrote layer record, len=%d' %
                         (fp.tell() - start_pos))
            return written

        written += write_length_block(fp, writer, fmt='xI')
        return written

    def _write_extra(self, fp, encoding, version):
        written = 0
        if self.mask_data:
            written += self.mask_data.write(fp)
        else:
            written += write_fmt(fp, 'I', 0)

        written += self.blending_ranges.write(fp)
        written += write_pascal_string(fp, self.name, encoding, padding=4)
        written += self.tagged_blocks.write(fp, version, padding=1)
        written += write_padding(fp, written, 2)
        return written

    @property
    def width(self):
        """Width of the layer."""
        return max(self.right - self.left, 0)

    @property
    def height(self):
        """Height of the layer."""
        return max(self.bottom - self.top, 0)

    @property
    def channel_sizes(self):
        """List of channel sizes: [(width, height)].
        """
        sizes = []
        for channel in self.channel_info:
            if channel.id == ChannelID.USER_LAYER_MASK:
                sizes.append((self.mask_data.width, self.mask_data.height))
            elif channel.id == ChannelID.REAL_USER_LAYER_MASK:
                sizes.append(
                    (self.mask_data.real_width, self.mask_data.real_height))
            else:
                sizes.append((self.width, self.height))
        return sizes
예제 #27
0
class FileHeader(BaseElement):
    """
    Header section of the PSD file.

    Example::

        from psd_tools2.psd.header import FileHeader
        from psd_tools2.constants import ColorMode

        header = FileHeader(channels=2, height=359, width=400, depth=8,
                            color_mode=ColorMode.GRAYSCALE)

    .. py:attribute:: signature

        Signature: always equal to ``b'8BPS'``.

    .. py:attribute:: version

        Version number. PSD is 1, and PSB is 2.

    .. py:attribute:: channels

        The number of channels in the image, including any alpha channels.

    .. py:attribute:: height

        The height of the image in pixels.

    .. py:attribute:: width

        The width of the image in pixels.

    .. py:attribute:: depth

        The number of bits per channel.

    .. py:attribute:: color_mode

        The color mode of the file. See
        :py:class:`~psd_tools2.constants.ColorMode`
    """
    _FORMAT = '4sH6xHIIHH'

    signature = attr.ib(default=b'8BPS', type=bytes, repr=False)
    version = attr.ib(default=1, type=int, validator=in_((1, 2)))
    channels = attr.ib(default=4, type=int, validator=range_(1, 57))
    height = attr.ib(default=64, type=int, validator=range_(1, 300001))
    width = attr.ib(default=64, type=int, validator=range_(1, 300001))
    depth = attr.ib(default=8, type=int, validator=in_((1, 8, 16, 32)))
    color_mode = attr.ib(default=ColorMode.RGB,
                         converter=ColorMode,
                         validator=in_(ColorMode))

    @signature.validator
    def _validate_signature(self, attribute, value):
        if value != b'8BPS':
            raise ValueError('This is not a PSD or PSB file')

    @classmethod
    def read(cls, fp):
        """Read the element from a file-like object.

        :param fp: file-like object
        :rtype: FileHeader
        """
        return cls(*read_fmt(cls._FORMAT, fp))

    def write(self, fp):
        """Write the element to a file-like object.

        :param fp: file-like object
        """
        return write_fmt(fp, self._FORMAT, *attr.astuple(self))
예제 #28
0
class BevelInfo(BaseElement):
    """
    Effects layer bevel info.

    .. py:attribute:: version
    .. py:attribute:: angle
    .. py:attribute:: depth
    .. py:attribute:: blur
    .. py:attribute:: highlight_blend_mode
    .. py:attribute:: shadow_blend_mode
    .. py:attribute:: highlight_color
    .. py:attribute:: shadow_color
    .. py:attribute:: highlight_opacity
    .. py:attribute:: shadow_opacity
    .. py:attribute:: enabled
    .. py:attribute:: use_global_angle
    .. py:attribute:: direction
    .. py:attribute:: real_hightlight_color
    .. py:attribute:: real_shadow_color
    """
    version = attr.ib(default=0, type=int)
    angle = attr.ib(default=0, type=int)
    depth = attr.ib(default=0, type=int)
    blur = attr.ib(default=0, type=int)
    highlight_blend_mode = attr.ib(default=BlendMode.NORMAL,
                                   converter=BlendMode,
                                   validator=in_(BlendMode))
    shadow_blend_mode = attr.ib(default=BlendMode.NORMAL, converter=BlendMode,
                                validator=in_(BlendMode))
    highlight_color = attr.ib(factory=Color)
    shadow_color = attr.ib(factory=Color)
    bevel_style = attr.ib(default=0, type=int)
    highlight_opacity = attr.ib(default=0, type=int)
    shadow_opacity = attr.ib(default=0, type=int)
    enabled = attr.ib(default=0, type=int)
    use_global_angle = attr.ib(default=0, type=int)
    direction = attr.ib(default=0, type=int)
    real_highlight_color = attr.ib(default=None)
    real_shadow_color = attr.ib(default=None)

    @classmethod
    def read(cls, fp):
        # TODO: Check 4-byte = 2-byte int + 2-byte fraction?
        version, angle, depth, blur = read_fmt('Ii2I', fp)
        signature, highlight_blend_mode = read_fmt('4s4s', fp)
        assert signature == b'8BIM', 'Invalid signature %r' % (signature)
        signature, shadow_blend_mode = read_fmt('4s4s', fp)
        assert signature == b'8BIM', 'Invalid signature %r' % (signature)
        highlight_color = Color.read(fp)
        shadow_color = Color.read(fp)
        bevel_style, highlight_opacity, shadow_opacity = read_fmt('3B', fp)
        enabled, use_global_angle, direction = read_fmt('3B', fp)
        real_highlight_color, real_shadow_color = None, None
        if version == 2:
            real_highlight_color = Color.read(fp)
            real_shadow_color = Color.read(fp)
        return cls(
            version, angle, depth, blur, highlight_blend_mode,
            shadow_blend_mode, highlight_color, shadow_color, bevel_style,
            highlight_opacity, shadow_opacity, enabled, use_global_angle,
            direction, real_highlight_color, real_shadow_color
        )

    def write(self, fp):
        written = write_fmt(
            fp, 'Ii2I', self.version, self.angle, self.depth, self.blur
        )
        written += write_fmt(
            fp, '4s4s4s4s', b'8BIM', self.highlight_blend_mode.value, b'8BIM',
            self.shadow_blend_mode.value
        )
        written += self.highlight_color.write(fp)
        written += self.shadow_color.write(fp)
        written += write_fmt(
            fp, '6B', self.bevel_style, self.highlight_opacity,
            self.shadow_opacity, self.enabled, self.use_global_angle,
            self.direction
        )
        if self.version >= 2:
            written += self.highlight_color.write(fp)
            written += self.shadow_color.write(fp)
        return written
예제 #29
0
class ImageData(BaseElement):
    """
    Merged channel image data.

    .. py:attribute:: compression

        See :py:class:`~psd_tools2.constants.Compression`.

    .. py:attribute:: data
    """
    compression = attr.ib(default=Compression.RAW,
                          converter=Compression,
                          validator=in_(Compression))
    data = attr.ib(default=b'', type=bytes, repr=False)

    @classmethod
    def read(cls, fp):
        """Read the element from a file-like object.

        :param fp: file-like object
        :rtype: :py:class:`.ImageData`
        """
        start_pos = fp.tell()
        compression = Compression(read_fmt('H', fp)[0])
        data = fp.read()  # TODO: Parse data here. Need header.
        logger.debug('  read image data, len=%d' % (fp.tell() - start_pos))
        return cls(compression, data)

    def write(self, fp):
        """Write the element to a file-like object.

        :param fp: file-like object
        :rtype: int
        """
        start_pos = fp.tell()
        written = write_fmt(fp, 'H', self.compression.value)
        written += write_bytes(fp, self.data)
        logger.debug('  wrote image data, len=%d' % (fp.tell() - start_pos))
        return written

    def get_data(self, header):
        """Get decompressed data.

        :param header: See :py:class:`~psd_tools2.psd.header.FileHeader`.
        :return: list of bytes corresponding each channel.
        :rtype: list
        """
        data = decompress(self.data, self.compression, header.width,
                          header.height * header.channels, header.depth,
                          header.version)
        plane_size = len(data) // header.channels
        with io.BytesIO(data) as f:
            return [f.read(plane_size) for _ in range(header.channels)]

    def set_data(self, data, header):
        """Set raw data and compress.

        :param data: list of raw data bytes corresponding channels.
        :param compression: compression type,
            see :py:class:`~psd_tools2.constants.Compression`.
        :param header: See :py:class:`~psd_tools2.psd.header.FileHeader`.
        :return: length of compressed data.
        """
        self.data = compress(b''.join(data), self.compression, header.width,
                             header.height * header.channels, header.depth,
                             header.version)
        return len(self.data)

    @classmethod
    def new(cls, header, color=0, compression=Compression.RAW):
        """Create a new image data object.

        :param header: FileHeader.
        :param compression: compression type.
        :param color: default color. int or iterable for channel length.
        """
        plane_size = header.width * header.height
        if isinstance(color, (bool, int, float)):
            color = (color, ) * header.channels
        if len(color) != header.channels:
            raise ValueError('Invalid color %s for channel size %d' %
                             (color, header.channels))
        # Bitmap is not supported here.
        fmt = {8: 'B', 16: 'H', 32: 'I'}.get(header.depth)
        data = []
        for i in range(header.channels):
            data.append(pack(fmt, color[i]) * plane_size)
        self = cls(compression=compression)
        self.set_data(data, header)
        return self
예제 #30
0
class GradientMap(BaseElement):
    """
    GradientMap structure.

    .. py:attribute:: version
    .. py:attribute:: is_reversed
    .. py:attribute:: is_dithered
    .. py:attribute:: name
    .. py:attribute:: color_stops
    .. py:attribute:: transparency_stops
    .. py:attribute:: expansion
    .. py:attribute:: interpolation
    .. py:attribute:: length
    .. py:attribute:: mode
    .. py:attribute:: random_seed
    .. py:attribute:: show_transparency
    .. py:attribute:: use_vector_color
    .. py:attribute:: roughness
    .. py:attribute:: color_model
    .. py:attribute:: minimum_color
    .. py:attribute:: maximum_color
    """
    version = attr.ib(default=1, type=int, validator=in_((1, )))
    is_reversed = attr.ib(default=0, type=int)
    is_dithered = attr.ib(default=0, type=int)
    name = attr.ib(default='', type=str)
    color_stops = attr.ib(factory=list, converter=list)
    transparency_stops = attr.ib(factory=list, converter=list)
    expansion = attr.ib(default=2, type=int, validator=in_((2, )))
    interpolation = attr.ib(default=0, type=int)
    length = attr.ib(default=32, type=int, validator=in_((32, )))
    mode = attr.ib(default=0, type=int)
    random_seed = attr.ib(default=0, type=int)
    show_transparency = attr.ib(default=0, type=int)
    use_vector_color = attr.ib(default=0, type=int)
    roughness = attr.ib(default=0, type=int)
    color_model = attr.ib(default=0, type=int)
    minimum_color = attr.ib(factory=list, converter=list)
    maximum_color = attr.ib(factory=list, converter=list)

    @classmethod
    def read(cls, fp, **kwargs):
        version, is_reversed, is_dithered = read_fmt('H2B', fp)
        assert version == 1, 'Invalid version %s' % (version)
        name = read_unicode_string(fp)
        count = read_fmt('H', fp)[0]
        color_stops = [ColorStop.read(fp) for _ in range(count)]
        count = read_fmt('H', fp)[0]
        transparency_stops = [TransparencyStop.read(fp) for _ in range(count)]
        expansion, interpolation, length, mode = read_fmt('4H', fp)
        assert expansion == 2, 'Invalid expansion %d' % (expansion)
        random_seed, show_transparency, use_vector_color = read_fmt('I2H', fp)
        roughness, color_model = read_fmt('IH', fp)
        minimum_color = read_fmt('4H', fp)
        maximum_color = read_fmt('4H', fp)
        read_fmt('2x', fp)  # Dummy?
        return cls(version, is_reversed, is_dithered, name, color_stops,
                   transparency_stops, expansion, interpolation, length, mode,
                   random_seed, show_transparency, use_vector_color, roughness,
                   color_model, minimum_color, maximum_color)

    def write(self, fp, **kwargs):
        written = write_fmt(fp, 'H2B', self.version, self.is_reversed,
                            self.is_dithered)
        written += write_unicode_string(fp, self.name)
        written += write_fmt(fp, 'H', len(self.color_stops))
        written += sum(stop.write(fp) for stop in self.color_stops)
        written += write_fmt(fp, 'H', len(self.transparency_stops))
        written += sum(stop.write(fp) for stop in self.transparency_stops)
        written += write_fmt(fp, '4HI2HIH', self.expansion, self.interpolation,
                             self.length, self.mode, self.random_seed,
                             self.show_transparency, self.use_vector_color,
                             self.roughness, self.color_model)
        written += write_fmt(fp, '4H', *self.minimum_color)
        written += write_fmt(fp, '4H', *self.maximum_color)
        written += write_fmt(fp, '2x')
        written += write_padding(fp, written, 4)
        return written