Exemplo n.º 1
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
Exemplo n.º 2
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
Exemplo n.º 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
Exemplo n.º 4
0
class UnitFloat(NumericElement):
    """
    Unit float structure.

    .. py:attribute:: unit

        unit of the value in :py:class:`Unit`

    .. py:attribute:: value

        `float` value
    """
    value = attr.ib(default=0.0, type=float)
    unit = attr.ib(default=Unit._None, converter=Unit, validator=in_(Unit))

    @classmethod
    def read(cls, fp):
        unit, value = read_fmt('4sd', fp)
        return cls(unit=Unit(unit), value=value)

    def write(self, fp):
        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)
Exemplo n.º 5
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_tools.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):
        return cls(*read_fmt(('hI', 'hQ')[version - 1], fp))

    def write(self, fp, version=1):
        return write_fmt(fp, ('hI', 'hQ')[version - 1], *attr.astuple(self))
Exemplo n.º 6
0
class UnitFloats(BaseElement):
    """
    Unit floats structure.

    .. py:attribute:: unit

        unit of the value in :py:class:`Unit`

    .. py:attribute:: values

        List of `float` values
    """
    unit = attr.ib(default=Unit._None, converter=Unit, validator=in_(Unit))
    values = attr.ib(factory=list)

    @classmethod
    def read(cls, fp):
        unit, count = read_fmt('4sI', fp)
        values = list(read_fmt('%dd' % count, fp))
        return cls(unit, values)

    def write(self, fp):
        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)
Exemplo n.º 7
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
        )
Exemplo n.º 8
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)
Exemplo n.º 9
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
Exemplo n.º 10
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):
        pos = fp.tell()
        data = read_length_block(fp)  # fmt?
        logger.debug('reading global layer mask info, len=%d' % (len(data)))
        if len(data) == 0:
            return cls(overlay_color=None)
        elif len(data) < 13:
            logger.warning(
                'global layer mask info is broken, expected 13 bytes but found '
                'only %d' % (len(data)))
            fp.seek(pos)
            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):
        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
Exemplo n.º 11
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
Exemplo n.º 12
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
Exemplo n.º 13
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
Exemplo n.º 14
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
Exemplo n.º 15
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
Exemplo n.º 16
0
class Levels(ListElement):
    """
    List of level records. See :py:class:
    `~psd_tools.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
Exemplo n.º 17
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
Exemplo n.º 18
0
class MetadataSetting(BaseElement):
    """
    MetadataSetting structure.
    """
    _KNOWN_KEYS = {b'cust', b'cmls', b'extn', b'mlst', b'tmln'}
    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 cls._KNOWN_KEYS:
            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
Exemplo n.º 19
0
class DescriptorBlock(Descriptor):
    """
    Dict-like Descriptor-based structure that has `version` field. See
    :py:class:`~psd_tools.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
Exemplo n.º 20
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
Exemplo n.º 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
Exemplo n.º 22
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)
Exemplo n.º 23
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
Exemplo n.º 24
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
Exemplo n.º 25
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
Exemplo n.º 26
0
class VirtualMemoryArray(BaseElement):
    """
    VirtualMemoryArrayList structure, corresponding to each channel.

    .. py:attribute:: is_written
    .. py:attribute:: depth
    .. py:attribute:: rectangle
    .. py:attribute:: pixel_depth
    .. py:attribute:: compression
    .. py:attribute:: data
    """
    is_written = attr.ib(default=0)
    depth = attr.ib(default=None)
    rectangle = attr.ib(default=None)
    pixel_depth = attr.ib(default=None)
    compression = attr.ib(default=Compression.RAW,
                          converter=Compression,
                          validator=in_(Compression))
    data = attr.ib(default=b'')

    @classmethod
    def read(cls, fp, **kwargs):
        is_written = read_fmt('I', fp)[0]
        if is_written == 0:
            return cls(is_written=is_written)
        length = read_fmt('I', fp)[0]
        if length == 0:
            return cls(is_written=is_written)
        depth = read_fmt('I', fp)[0]
        rectangle = read_fmt('4I', fp)
        pixel_depth, compression = read_fmt('HB', fp)
        data = fp.read(length - 23)
        return cls(is_written, depth, rectangle, pixel_depth, compression,
                   data)

    def write(self, fp, **kwargs):
        written = write_fmt(fp, 'I', self.is_written)
        if self.is_written == 0:
            return written
        if self.depth is None:
            written += write_fmt(fp, 'I', 0)
            return written

        return written + write_length_block(fp, lambda f: self._write_body(f))

    def _write_body(self, fp):
        written = write_fmt(fp, 'I', self.depth)
        written += write_fmt(fp, '4I', *self.rectangle)
        written += write_fmt(fp, 'HB', self.pixel_depth,
                             self.compression.value)
        written += write_bytes(fp, self.data)
        return written

    def get_data(self):
        """Get decompressed bytes."""
        if not self.is_written:
            return None
        width, height = self.rectangle[3], self.rectangle[2]
        return decompress(self.data,
                          self.compression,
                          width,
                          height,
                          self.depth,
                          version=1)

    def set_data(self, size, data, depth, compression=0):
        """Set bytes."""
        self.data = compress(data,
                             compression,
                             size[0],
                             size[1],
                             depth,
                             version=1)
        self.depth = int(depth)
        self.pixel_depth = int(depth)
        self.rectangle = (0, 0, int(size[1]), int(size[0]))
        self.compression = Compression(compression)
        self.is_written = True
Exemplo n.º 27
0
class TaggedBlock(BaseElement):
    """
    Layer tagged block with extra info.

    .. py:attribute:: key

        4-character code. See :py:class:`~psd_tools.constants.TaggedBlockID`

    .. py:attribute:: data

        Data.
    """
    _SIGNATURES = (b'8BIM', b'8B64')
    _BIG_KEYS = {
        TaggedBlockID.USER_MASK,
        TaggedBlockID.LAYER_16,
        TaggedBlockID.LAYER_32,
        TaggedBlockID.LAYER,
        TaggedBlockID.SAVING_MERGED_TRANSPARENCY16,
        TaggedBlockID.SAVING_MERGED_TRANSPARENCY32,
        TaggedBlockID.SAVING_MERGED_TRANSPARENCY,
        TaggedBlockID.SAVING_MERGED_TRANSPARENCY16,
        TaggedBlockID.ALPHA,
        TaggedBlockID.FILTER_MASK,
        TaggedBlockID.LINKED_LAYER2,
        TaggedBlockID.LINKED_LAYER_EXTERNAL,
        TaggedBlockID.FILTER_EFFECTS1,
        TaggedBlockID.FILTER_EFFECTS2,
        TaggedBlockID.PIXEL_SOURCE_DATA2,
        TaggedBlockID.UNICODE_PATH_NAME,
        TaggedBlockID.EXPORT_SETTING1,
        TaggedBlockID.EXPORT_SETTING2,
        TaggedBlockID.COMPUTER_INFO,
    }

    signature = attr.ib(default=b'8BIM',
                        repr=False,
                        validator=in_(_SIGNATURES))
    key = attr.ib(default=b'')
    data = attr.ib(default=b'', repr=True)

    @classmethod
    def read(cls, fp, version=1, padding=1):
        signature = read_fmt('4s', fp)[0]
        if signature not in cls._SIGNATURES:
            logger.warning('Invalid signature (%r)' % (signature))
            fp.seek(-4, 1)
            return None

        key = read_fmt('4s', fp)[0]
        try:
            key = TaggedBlockID(key)
        except ValueError:
            message = 'Unknown key: %r' % (key)
            warn(message)
            logger.warning(message)

        fmt = cls._length_format(key, version)
        raw_data = read_length_block(fp, fmt=fmt, padding=padding)
        kls = TYPES.get(key)
        if kls:
            data = kls.frombytes(raw_data, version=version)
            # _raw_data = data.tobytes(version=version,
            #                          padding=1 if padding == 4 else 4)
            # assert raw_data == _raw_data, '%r: %s vs %s' % (
            #     kls, trimmed_repr(raw_data), trimmed_repr(_raw_data)
            # )
        else:
            message = 'Unknown tagged block: %r, %s' % (key,
                                                        trimmed_repr(raw_data))
            warn(message)
            logger.warning(message)
            data = raw_data
        return cls(signature, key, data)

    def write(self, fp, version=1, padding=1):
        key = self.key if isinstance(self.key, bytes) else self.key.value
        written = write_fmt(fp, '4s4s', self.signature, key)

        def writer(f):
            if hasattr(self.data, 'write'):
                # It seems padding size applies at the block level here.
                inner_padding = 1 if padding == 4 else 4
                return self.data.write(f,
                                       padding=inner_padding,
                                       version=version)
            return write_bytes(f, self.data)

        fmt = self._length_format(self.key, version)
        written += write_length_block(fp, writer, fmt=fmt, padding=padding)
        return written

    @classmethod
    def _length_format(cls, key, version):
        return ('I', 'Q')[int(version == 2 and key in cls._BIG_KEYS)]
Exemplo n.º 28
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_tools.constants.BlendMode`.

    .. py:attribute:: opacity

        Opacity, 0 = transparent, 255 = opaque.

    .. py:attribute:: clipping

        Clipping, 0 = base, 1 = non-base. See
        :py:class:`~psd_tools.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_tools.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
Exemplo n.º 29
0
class ChannelData(BaseElement):
    """
    Channel data.

    .. py:attribute:: compression

        Compression type. See :py:class:`~psd_tools.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_tools.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)
Exemplo n.º 30
0
class FileHeader(BaseElement):
    """
    Header section of the PSD file.

    Example::

        from psd_tools.psd.header import FileHeader
        from psd_tools.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_tools.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))