# -*- 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