Source code for pytoshop.image_resources

# -*- coding: utf-8 -*-


"""
The `ImageResources` section.

Image resource blocks are the basic building unit of several file
formats, including Photoshop's native file format, JPEG, and
TIFF. Image resources are used to store non-pixel data associated with
images, such as pen tool paths.
"""


from __future__ import unicode_literals, absolute_import


import six


import numpy as np


from . import docs
from . import enums
from . import util


from typing import Any, BinaryIO, Dict, List, Optional, Type, TYPE_CHECKING, Union  # NOQA
if TYPE_CHECKING:
    from . import core  # NOQA


class _ImageResourceBlockMeta(type):
    """
    A metaclass that builds a mapping of subclasses.
    """
    mapping = {}  # type: Dict[int, Type[ImageResourceBlock]]

    def __new__(cls, name, parents, dct):
        new_cls = type.__new__(cls, name, parents, dct)

        if '_resource_id' in dct and isinstance(dct['_resource_id'], int):
            resource_id = dct['_resource_id']
            if resource_id in cls.mapping:
                raise ValueError(
                    "Duplicate resource_id '{}'".format(
                        resource_id))
            cls.mapping[resource_id] = new_cls

        return new_cls


[docs]@six.add_metaclass(_ImageResourceBlockMeta) class ImageResourceBlock(object): """ Stores a single image resource block. ``pytoshop`` currently doesn't deeply parse image resource blocks. The raw data is merely retained for round-tripping. """ _resource_id = -1 @property def name(self): # type: (...) -> unicode "Name of image resource." return self._name @name.setter def name(self, value): # type: (Union[bytes, unicode]) -> None if isinstance(value, bytes): value = value.decode('ascii') if (not isinstance(value, six.text_type) or len(value) > 255): raise ValueError("name must be unicode string of length < 255") self._name = value @property def resource_id(self): # type: (...) -> int "Type of image resource." return self._resource_id
[docs] def length(self, header): # type: (core.Header) -> int data_length = self.data_length(header) length = ( 4 + 2 + util.pascal_string_length(self.name, 2) + 4 + data_length ) if data_length % 2 != 0: length += 1 return length
length.__doc__ = docs.length # type: ignore
[docs] def total_length(self, header): # type: (core.Header) -> int return self.length(header)
total_length.__doc__ = docs.total_length # type: ignore
[docs] def data_length(self, header): # type: (core.Header) -> int raise NotImplementedError()
[docs] @classmethod @util.trace_read def read(cls, fd, header): # type: (BinaryIO, core.Header) -> ImageResourceBlock signature = fd.read(4) if signature != b'8BIM': raise ValueError('Invalid image resource block signature') resource_id = util.read_value(fd, 'H') name = util.read_pascal_string(fd, 2) data_length = util.read_value(fd, 'I') util.log( "resource_id: {}, name: {}, data_length: {}", resource_id, name, data_length ) new_cls = _ImageResourceBlockMeta.mapping.get( resource_id, GenericImageResourceBlock) start = fd.tell() result = new_cls.read_data(fd, resource_id, name, data_length, header) end = fd.tell() if end - start != data_length: raise ValueError("{} read the wrong amount".format(new_cls)) if data_length % 2 != 0: fd.read(1) return result
read.__func__.__doc__ = docs.read # type: ignore
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode data_length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock raise NotImplementedError()
[docs] @util.trace_write def write(self, fd, header): # type: (BinaryIO, core.Header) -> None fd.write(b'8BIM') util.write_value(fd, 'H', self.resource_id) util.write_pascal_string(fd, self.name, 2) length = self.data_length(header) util.write_value(fd, 'I', length) start = fd.tell() self.write_data(fd, header) end = fd.tell() if end - start != length: raise ValueError( "{} wrote the wrong amount".format(self.__class__)) if length % 2 != 0: fd.write(b'\0')
write.__doc__ = docs.write # test: ignore
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None raise NotImplementedError()
[docs]class GenericImageResourceBlock(ImageResourceBlock): def __init__(self, name='', resource_id=0, data=b''): self.name = name self.resource_id = resource_id self.data = data @property def resource_id(self): # type: (...) -> int "Type of image resource." return self._resource_id @resource_id.setter def resource_id(self, value): # type: (int) -> None if (not isinstance(value, int) or value < 0 or value > (1 << 16)): raise ValueError( "resource_id must be a 16-bit positive integer" ) self._resource_id = value @property def data(self): # type: (...) -> bytes "Raw data of image resource." return self._data @data.setter def data(self, value): # type: (bytes) -> None if (not isinstance(value, bytes) or len(value) > (1 << 32)): raise ValueError("data must be a byte string") self._data = value
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock data = fd.read(length) return cls(resource_id=resource_id, name=name, data=data)
[docs] def data_length(self, header): # type: (core.Header) -> int return len(self.data)
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None fd.write(self.data)
[docs]class ImageResourceUnicodeString(ImageResourceBlock): def __init__(self, name='', # type: unicode value='' # type: unicode ): # type: (...) -> None self.name = name self.value = value @property def value(self): # type: (...) -> unicode return self._value @value.setter def value(self, value): # type: (unicode) -> None if (not isinstance(value, six.text_type) or len(value) > (1 << 32)): raise TypeError("value must be a unicode string") self._value = value
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock data = fd.read(length) value = util.decode_unicode_string(data) return cls( name=name, value=value )
[docs] def data_length(self, header): # type: (core.Header) -> int return util.unicode_string_length(self.value)
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None fd.write(util.encode_unicode_string(self.value))
[docs]class LayersGroupInfo(ImageResourceBlock): """ Layers group information. Indicates which layers are locked together. """ def __init__(self, name='', # type: unicode group_ids=[] # type: List[int] ): # type: (...) -> None self.name = name self.group_ids = group_ids _resource_id = enums.ImageResourceID.layers_group_info @property def group_ids(self): # type: (...) -> List[int] return self._group_ids @group_ids.setter def group_ids(self, value): # type: (List[int]) -> None util.assert_is_list_of(value, int, min=0, max=65535) self._group_ids = value
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock data = fd.read(length) group_ids = np.frombuffer(data, '>u2').tolist() return cls(name=name, group_ids=group_ids)
[docs] def data_length(self, header): # type: (core.Header) -> int return len(self.group_ids * 2)
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None data = np.array(self.group_ids, '>u2').tobytes() fd.write(data)
[docs]class BorderInfo(ImageResourceBlock): """ Border information. """ def __init__(self, name='', # type: unicode border_width_num=0, # type: int border_width_den=1, # type: int unit=enums.Units.inches # type: int ): # type: (...) -> None self.name = name self.border_width_num = border_width_num self.border_width_den = border_width_den self.unit = unit _resource_id = enums.ImageResourceID.border_info @property def border_width_num(self): # type: (...) -> int "Border width numerator" return self._border_width_num @border_width_num.setter def border_width_num(self, value): # type: (int) -> None if (not isinstance(value, int) or value < 0 or value > 65535): raise ValueError( "border_width_num must be integer in range 0-65535" ) self._border_width_num = value @property def border_width_den(self): # type: (...) -> int "Border width denominator" return self._border_width_den @border_width_den.setter def border_width_den(self, value): # type: (int) -> None if (not isinstance(value, int) or value < 1 or value > 65535): raise ValueError( "border_width_den must be integer in range 1-65535" ) self._border_width_den = value @property def unit(self): # type: (...) -> int "Unit. See `enums.Units`." return self._unit @unit.setter def unit(self, value): # type: (int) -> None if value not in list(enums.Units): # type: ignore raise ValueError("Invalid unit.") self._unit = value
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock num, den, unit = util.read_value(fd, 'HHH') return cls( name=name, border_width_num=num, border_width_den=den, unit=unit)
[docs] def data_length(self, header): # type: (core.Header) -> int return 6
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None util.write_value( fd, 'HHH', self.border_width_num, self.border_width_den, self.unit )
[docs]class BackgroundColor(ImageResourceBlock): """ Background color. """ def __init__(self, name='', # type: unicode color_space=enums.ColorSpace.rgb, # type: int color=[] # type: List[int] ): # type: (...) -> None self.name = name self.color_space = color_space self.color = color _resource_id = enums.ImageResourceID.background_color @property def color_space(self): # type: (...) -> int "The color space. See `enums.ColorSpace`" return self._color_space @color_space.setter def color_space(self, value): # type: (int) -> None if value not in list(enums.ColorSpace): # type: ignore raise ValueError("Invalid color space.") self._color_space = value @property def color(self): # type: (...) -> List[int] """ The color data. If the color data does not require 4 values, the extra values are undefined and should be included as zeros. """ return self._color @color.setter def color(self, value): # type: (List[int]) -> None util.assert_is_list_of(value, int, -32767, 65536) if len(value) < 1 or len(value) > 4: raise ValueError("Color must be of length 1-4") self._color = value
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock space_id, a, b, c, d = util.read_value(fd, 'HHHHH') if space_id == enums.ColorSpace.lab: b -= 32767 c -= 32767 return cls( name=name, color_space=space_id, color=[a, b, c, d] )
[docs] def data_length(self, header): # type: (core.Header) -> int return 10
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None color = self.color[:] color.extend([0] * (4 - len(color))) a, b, c, d = color if self.color_space == enums.ColorSpace.lab: b += 32767 c += 32767 util.write_value( fd, 'HHHHH', self.color_space, a, b, c, d )
[docs]class PrintFlags(ImageResourceBlock): """ Print flags. """ def __init__(self, name='', # type: unicode labels=False, # type: bool crop_marks=False, # type: bool color_bars=False, # type: bool registration_marks=False, # type: bool negative=False, # type: bool flip=False, # type: bool interpolate=False, # type: bool caption=False, # type: bool print_flags=False # type: bool ): # type: (...) -> None self.name = name self.labels = labels self.crop_marks = crop_marks self.color_bars = color_bars self.registration_marks = registration_marks self.negative = negative self.flip = flip self.interpolate = interpolate self.caption = caption self.print_flags = print_flags _resource_id = enums.ImageResourceID.print_flags @property def labels(self): # type: (...) -> bool "labels" return self._labels @labels.setter def labels(self, value): # type: (Any) -> None self._labels = bool(value) @property def crop_marks(self): # type: (...) -> bool "crop marks" return self._crop_marks @crop_marks.setter def crop_marks(self, value): # type: (Any) -> None self._crop_marks = bool(value) @property def color_bars(self): # type: (...) -> bool "color bars" return self._color_bars @color_bars.setter def color_bars(self, value): # type: (Any) -> None self._color_bars = bool(value) @property def registration_marks(self): # type: (...) -> bool "registration marks" return self._registration_marks @registration_marks.setter def registration_marks(self, value): # type: (Any) -> None self._registration_marks = bool(value) @property def negative(self): # type: (...) -> bool "negative" return self._negative @negative.setter def negative(self, value): # type: (Any) -> None self._negative = bool(value) @property def flip(self): # type: (...) -> bool "flip" return self._flip @flip.setter def flip(self, value): # type: (Any) -> None self._flip = bool(value) @property def interpolate(self): # type: (...) -> bool "interpolate" return self._interpolate @interpolate.setter def interpolate(self, value): # type: (Any) -> None self._interpolate = bool(value) @property def caption(self): # type: (...) -> bool "caption" return self._caption @caption.setter def caption(self, value): # type: (Any) -> None self._caption = bool(value) @property def print_flags(self): # type: (...) -> bool "print flags" return self._print_flags @print_flags.setter def print_flags(self, value): # type: (Any) -> None self._print_flags = bool(value)
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock vals = util.read_value(fd, 'BBBBBBBBB') vals = [bool(x) for x in vals] return cls( name=name, labels=vals[0], crop_marks=vals[1], color_bars=vals[2], registration_marks=vals[3], negative=vals[4], flip=vals[5], interpolate=vals[6], caption=vals[7], print_flags=vals[8] )
[docs] def data_length(self, header): # type: (core.Header) -> int return 9
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None vals = [ self.labels, self.crop_marks, self.color_bars, self.registration_marks, self.negative, self.flip, self.interpolate, self.caption, self.print_flags ] int_vals = [(x and 255 or 0) for x in vals] util.write_value(fd, 'BBBBBBBBB', *int_vals)
[docs]class GuideResourceBlock(object): def __init__(self, location=0, # type: int direction=enums.GuideDirection.vertical # type: int ): # type: (...) -> None self.location = location self.direction = direction @property def location(self): # type: (...) -> int "Location of guide in document coordinates." return self._location @location.setter def location(self, value): # type: (int) -> None if (not isinstance(value, int) or value < 0 or value > (1 << 32)): raise ValueError("location must be a 32-bit unsigned int") self._location = value @property def direction(self): # type: (...) -> int "Guide direction. See `enums.GuideDirection`." return self._direction @direction.setter def direction(self, value): # type: (int) -> None if value not in list(enums.GuideDirection): # type: ignore raise ValueError("Invalid guide direction") self._direction = value
[docs] @classmethod @util.trace_read def read(cls, fd, header): # type: (BinaryIO, core.Header) -> GuideResourceBlock location, direction = util.read_value(fd, 'IB') return cls(location=location, direction=direction)
[docs] @util.trace_write def write(self, fd, header): # type: (BinaryIO, core.Header) -> None util.write_value(fd, 'IB', self.location, self.direction)
[docs] def data_length(self, header): # type: (core.Header) -> int return 5
[docs]class GridAndGuidesInfo(ImageResourceBlock): """ Grid and guides resource. """ def __init__(self, name='', # type: unicode grid_hori=0, # type: int grid_vert=0, # type: int guides=[] # type: List[GuideResourceBlock] ): # type: (...) -> None self.name = name self.grid_hori = grid_hori self.grid_vert = grid_vert self.guides = guides _resource_id = enums.ImageResourceID.grid_and_guides_info @property def version(self): # type: (...) -> int return 1 @property def grid_hori(self): # type: (...) -> int "Document-specific grid (horizontal). In 1/32 pt." return self._grid_hori @grid_hori.setter def grid_hori(self, value): # type: (int) -> None if (not isinstance(value, int) or value < 0 or value > (1 << 32)): raise ValueError("grid_hori must be a 32-bit unsigned int") self._grid_hori = value @property def grid_vert(self): # type: (...) -> int "Document-specific grid (vertical). In 1/32 pt." return self._grid_vert @grid_vert.setter def grid_vert(self, value): # type: (int) -> None if (not isinstance(value, int) or value < 0 or value > (1 << 32)): raise ValueError("grid_vert must be a 32-bit unsigned int") self._grid_vert = value @property def guides(self): # type: (...) -> List[GuideResourceBlock] "Guides. See `GuideResourceBlock`." return self._guides @guides.setter def guides(self, value): # type: (List[GuideResourceBlock]) -> None util.assert_is_list_of(value, GuideResourceBlock) self._guides = value
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock version, grid_hori, grid_vert, nguides = util.read_value( fd, 'IIII') if version != 1: raise ValueError( "Unknown version {} in grid and guides info block.".format( version)) guides = [] for i in range(nguides): guides.append(GuideResourceBlock.read(fd, header)) return cls( name=name, grid_hori=grid_hori, grid_vert=grid_vert, guides=guides )
[docs] def data_length(self, header): # type: (core.Header) -> int return 16 + (5 * len(self.guides))
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None util.write_value( fd, 'IIII', self.version, self.grid_hori, self.grid_vert, len(self.guides) ) for guide in self.guides: guide.write(fd, header)
[docs]class CopyrightFlag(ImageResourceBlock): def __init__(self, name='', # type: unicode copyright=False # type: bool ): # type: (...) -> None self.name = name self.copyright = copyright _resource_id = enums.ImageResourceID.copyright_flag @property def copyright(self): # type: (...) -> bool "Is copyrighted?" return self._copyright @copyright.setter def copyright(self, value): # type: (Any) -> None self._copyright = bool(value)
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock copyright = bool(util.read_value(fd, 'B')) return cls( name=name, copyright=copyright )
[docs] def data_length(self, header): # type: (core.Header) -> int return 1
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None util.write_value(fd, 'B', self.copyright and 255 or 0)
[docs]class Url(ImageResourceBlock): def __init__(self, name='', # type: unicode url=b'' # type: bytes ): # type: (...) -> None self.name = name self.url = url _resource_id = enums.ImageResourceID.url @property def url(self): # type: (...) -> bytes "URL" return self._url @url.setter def url(self, value): # type: (bytes) -> None if not isinstance(value, bytes): raise TypeError("url must be bytes string") self._url = value
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock url = fd.read(length) return cls( name=name, url=url )
[docs] def data_length(self, header): # type: (core.Header) -> int return len(self.url)
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None fd.write(self.url)
[docs]class GlobalAngle(ImageResourceBlock): def __init__(self, name='', # type: unicode angle=0 # type: int ): # type: (...) -> None self.name = name self.angle = angle _resource_id = enums.ImageResourceID.global_angle @property def angle(self): # type: (...) -> int "Global light angle for the effect layer" return self._angle @angle.setter def angle(self, value): # type: (int) -> None if (not isinstance(value, int) or value < -360 or value > 360): raise ValueError( "angle must be an int in range -360 to 360" ) self._angle = value
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock angle = util.read_value(fd, 'i') return cls( name=name, angle=angle )
[docs] def data_length(self, header): # type: (core.Header) -> int return 4
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None util.write_value(fd, 'i', self.angle)
[docs]class EffectsVisible(ImageResourceBlock): def __init__(self, name='', # type: unicode visible=False # type: bool ): # type: (...) -> None self.name = name self.visible = visible _resource_id = enums.ImageResourceID.effects_visible @property def visible(self): # type: (...) -> bool "Are effects visible?" return self._visible @visible.setter def visible(self, value): # type: (Any) -> None self._visible = bool(value)
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock visible = bool(util.read_value(fd, 'B')) return cls( name=name, visible=visible )
[docs] def data_length(self, header): # type: (core.Header) -> int return 1
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None util.write_value(fd, 'B', self.visible and 255 or 0)
[docs]class DocumentSpecificIdsSeedNumber(ImageResourceBlock): def __init__(self, name='', # type: unicode base_value=0 # type: int ): # type: (...) -> None self.name = name self.base_value = base_value _resource_id = enums.ImageResourceID.document_specific_ids_seed_number @property def base_value(self): # type: (...) -> int "Base value" return self._base_value @base_value.setter def base_value(self, value): # type: (int) -> None if (not isinstance(value, int) or value < 0 or value > (1 << 32)): raise ValueError("base_value must be a 32-bit integer") self._base_value = value
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock base_value = bool(util.read_value(fd, 'I')) return cls( name=name, base_value=base_value )
[docs] def data_length(self, header): # type: (core.Header) -> int return 4
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None util.write_value(fd, 'I', self.base_value)
[docs]class UnicodeAlphaNames(ImageResourceUnicodeString): _resource_id = enums.ImageResourceID.unicode_alpha_names
[docs]class GlobalAltitude(ImageResourceBlock): def __init__(self, name='', # type: unicode altitude=0 # type: int ): # type: (...) -> None self.name = name self.altitude = altitude _resource_id = enums.ImageResourceID.global_altitude @property def altitude(self): # type: (...) -> int "Global altitude" return self._altitude @altitude.setter def altitude(self, value): # type: (int) -> None if (not isinstance(value, int) or value < 0 or value > (1 << 32)): raise ValueError("altitude must be a 32-bit integer") self._altitude = value
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock altitude = util.read_value(fd, 'I') return cls( name=name, altitude=altitude )
[docs] def data_length(self, header): # type: (core.Header) -> int return 4
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None util.write_value(fd, 'I', self.altitude)
[docs]class WorkflowUrl(ImageResourceUnicodeString): _resource_id = enums.ImageResourceID.workflow_url
[docs]class AlphaIdentifiers(ImageResourceBlock): def __init__(self, name='', # type: unicode identifiers=[] # type: List[int] ): # type: (...) -> None self.name = name self.identifiers = identifiers _resource_id = enums.ImageResourceID.alpha_identifiers @property def identifiers(self): # type: (...) -> List[int] "Alpha indentifiers" return self._identifiers @identifiers.setter def identifiers(self, value): # type: (List[int]) -> None util.assert_is_list_of(value, int, min=0, max=(1 << 32)) self._identifiers = value
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock length = util.read_value(fd, 'I') buf = fd.read(4 * length) identifiers = list(np.frombuffer(buf, np.uint32)) return cls( name=name, identifiers=identifiers )
[docs] def data_length(self, header): # type: (core.Header) -> int return 4 + (len(self.identifiers) * 4)
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None util.write_value(fd, 'I', len(self.identifiers)) for identifier in self.identifiers: util.write_value(fd, 'I', identifier)
[docs]class VersionInfo(ImageResourceBlock): def __init__(self, name='', # type: unicode version=0, # type: int has_real_merged_data=False, # type: bool writer='', # type: unicode reader='', # type: unicode file_version=0 # type: int ): # type: (...) -> None self.name = name self.version = version self.has_real_merged_data = has_real_merged_data self.writer = writer self.reader = reader self.file_version = file_version _resource_id = enums.ImageResourceID.version_info @property def version(self): # type: (...) -> int "version" return self._version @version.setter def version(self, value): # type: (int) -> None if (not isinstance(value, int) or value < 0 or value > (1 << 32)): raise ValueError("version must be a 32-bit integer") self._version = value @property def has_real_merged_data(self): # type: (...) -> bool "has real merged data?" return self._has_real_merged_data @has_real_merged_data.setter def has_real_merged_data(self, value): # type: (Any) -> None self._has_real_merged_data = bool(value) @property def writer(self): # type: (...) -> unicode "writer name" return self._writer @writer.setter def writer(self, value): # type: (unicode) -> None if not isinstance(value, six.text_type): raise TypeError("writer must be a Unicode string") self._writer = value @property def reader(self): # type: (...) -> unicode "reader name" return self._reader @reader.setter def reader(self, value): # type: (unicode) -> None if not isinstance(value, six.text_type): raise TypeError("reader must be a Unicode string") self._reader = value @property def file_version(self): # type: (...) -> int "file version" return self._file_version @file_version.setter def file_version(self, value): # type: (int) -> None if (not isinstance(value, int) or value < 0 or value > (1 << 32)): raise ValueError("file_version must be a 32-bit integer") self._file_version = value
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock version, has_real_merged_data = util.read_value(fd, 'IB') has_real_merged_data = bool(has_real_merged_data) writer = util.read_unicode_string(fd) reader = util.read_unicode_string(fd) file_version = util.read_value(fd, 'I') return cls( name=name, version=version, has_real_merged_data=has_real_merged_data, writer=writer, reader=reader, file_version=file_version )
[docs] def data_length(self, header): # type: (core.Header) -> int return ( 4 + 1 + util.unicode_string_length(self.writer) + util.unicode_string_length(self.reader) + 4)
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None util.write_value(fd, 'IB', self.version, self.has_real_merged_data) util.write_unicode_string(fd, self.writer) util.write_unicode_string(fd, self.reader) util.write_value(fd, 'I', self.file_version)
[docs]class PrintScale(ImageResourceBlock): def __init__(self, name='', # type: unicode style=enums.PrintScaleStyle.centered, # type: int x=0.0, # type: float y=0.0, # type: float scale=0.0 # type: float ): # type: (...) -> None self.name = name self.style = style self.x = x self.y = y self.scale = scale _resource_id = enums.ImageResourceID.print_scale @property def style(self): # type: (...) -> int "Style. See `enums.PrintScaleStyle`." return self._style @style.setter def style(self, value): # type: (int) -> None if value not in list(enums.PrintScaleStyle): # type: ignore raise ValueError("Invalid print scale style") self._style = value @property def x(self): # type: (...) -> float "x location" return self._x @x.setter def x(self, value): # type: (float) -> None if not isinstance(value, float): raise TypeError("x must be a float") self._x = value @property def y(self): # type: (...) -> float "y location" return self._y @y.setter def y(self, value): # type: (float) -> None if not isinstance(value, float): raise TypeError("y must be a float") self._y = value @property def scale(self): # type: (...) -> float "scale" return self._scale @scale.setter def scale(self, value): # type: (float) -> None if not isinstance(value, float): raise TypeError("scale must be a float") self._scale = value
[docs] @classmethod def read_data(cls, fd, # type: BinaryIO resource_id, # type: int name, # type: unicode length, # type: int header # type: core.Header ): # type: (...) -> ImageResourceBlock style, x, y, scale = util.read_value(fd, 'Hfff') return cls( name=name, style=style, x=x, y=y, scale=scale )
[docs] def data_length(self, header): # type: (core.Header) -> int return (2 + 4 + 4 + 4)
[docs] def write_data(self, fd, header): # type: (BinaryIO, core.Header) -> None util.write_value(fd, 'Hfff', self.style, self.x, self.y, self.scale)
[docs]class ImageResources(object): """ The image resource block section. """ def __init__(self, blocks=[] # type: List[ImageResourceBlock] ): # type: (...) -> None self.blocks = blocks @property def blocks(self): # type: (...) -> List[ImageResourceBlock] "List of all `ImageResourceBlock` items." return self._blocks @blocks.setter def blocks(self, value): # type: (List[ImageResourceBlock]) -> None util.assert_is_list_of(value, ImageResourceBlock) self._blocks = value
[docs] def length(self, header): # type: (core.Header) -> int return sum(block.total_length(header) for block in self.blocks)
length.__doc__ = docs.length # type: ignore
[docs] def total_length(self, header): # type: (core.Header) -> int return 4 + self.length(header)
total_length.__doc__ = docs.total_length # type: ignore
[docs] def get_block(self, resource_id): # type: (int) -> Optional[ImageResourceBlock] """ Get the first block with the given resource id. """ for block in self.blocks: if block.resource_id == resource_id: return block return None
[docs] @classmethod @util.trace_read def read(cls, fd, header): # type: (BinaryIO, core.Header) -> ImageResources length = util.read_value(fd, 'I') end = fd.tell() + length util.log("length: {}, end: {}", length, end) blocks = [] while fd.tell() < end: blocks.append(ImageResourceBlock.read(fd, header)) if fd.tell() != end: raise ValueError( "read the wrong amount reading image resource blocks") return cls(blocks=blocks)
read.__func__.__doc__ = docs.read
[docs] @util.trace_write def write(self, fd, header): # type: (BinaryIO, core.Header) -> None util.write_value(fd, 'I', self.length(header)) for block in self.blocks: block.write(fd, header)
write.__doc__ = docs.write