Commits

Mikhail Korobov  committed 85930f6

psd.composite_image() is renamed to psd.as_PIL(); PIL support is moved to a module.

  • Participants
  • Parent commits d66c76c

Comments (0)

Files changed (9)

 
 Export the merged image::
 
-    >>> merged_image = psd.composite_image()
+    >>> merged_image = psd.as_PIL()
     >>> merged_image.save('my_image.png')
 
 

File src/psd_tools/cli.py

 
     if args['convert']:
         psd = PSDImage.load(args['<psd_filename>'])
-        im = psd.composite_image()
+        im = psd.as_PIL()
         im.save(args['<out_filename>'])
 
     elif args['export_layer']:
         im.save(args['<out_filename>'])
         print(psd.layers)
 
-        psd.composite_image()
+        psd.as_PIL()
 
     else:
         encoding = args['--encoding']

File src/psd_tools/decoder/decoder.py

 from psd_tools.constants import TaggedBlock
 
 def parse(reader_parse_result):
+
     layer_and_mask_data = reader_parse_result.layer_and_mask_data
     layers = layer_and_mask_data.layers
 
     new_layers = decode_layers(layers)
     new_tagged_blocks = tagged_blocks.decode(layer_and_mask_data.tagged_blocks)
 
+    # 16 and 32 bit layers are stored in Lr16 and Lr32 tagged blocks
     if new_layers.layer_count == 0:
         blocks_dict = dict(new_tagged_blocks)
         if reader_parse_result.header.depth == 16:
         elif reader_parse_result.header.depth == 32:
             new_layers = blocks_dict.get(TaggedBlock.LAYER_32, new_layers)
 
-
+    # XXX: this code is complicated because of the namedtuple abuse
     new_layer_and_mask_data = layer_and_mask_data._replace(
         layers = new_layers,
         tagged_blocks = new_tagged_blocks

File src/psd_tools/reader/layers.py

 import logging
 import warnings
 import zlib
-import array
 
 from psd_tools.utils import (read_fmt, read_pascal_string,
                              read_be_array, trimmed_repr, pad, synchronize,
         return self.bottom - self.top
 
 
-
 Layers = collections.namedtuple('Layers', 'length, layer_count, layer_records, channel_image_data')
 LayerFlags = collections.namedtuple('LayerFlags', 'transparency_protected visible')
 LayerAndMaskData = collections.namedtuple('LayerAndMaskData', 'layers global_mask_info tagged_blocks')
     def height(self):
         return self.bottom - self.top
 
+
 class ChannelData(_ChannelData):
     def __repr__(self):
         return "ChannelData(compression=%r %s, len(data)=%r)" % (
             data_size = sum(byte_counts) * bytes_per_pixel
             data = fp.read(data_size)
 
+        # are there any ZIP-encoded composite images in a wild?
         elif compress_type == Compression.ZIP:
             warnings.warn("ZIP compression of composite image is not supported.")
 

File src/psd_tools/user_api/layers.py

 from __future__ import absolute_import, unicode_literals, print_function, division
 import warnings
 
-import array
-from psd_tools.utils import be_array_from_bytes
-from psd_tools.constants import (Compression, ChannelID, ColorMode,
-                                 TaggedBlock, SectionDivider, ImageResourceID)
-
-try:
-    from PIL import Image, ImageCms
-    if hasattr(Image, 'frombytes'):
-        frombytes = Image.frombytes
-    else:
-        frombytes = Image.fromstring
-
-except ImportError:
-    Image = None
+from psd_tools.constants import TaggedBlock, SectionDivider
 
 def group_layers(decoded_data):
     """
             ))
 
     return root['layers']
-
-
-def get_icc_profile(decoded_data):
-    icc_profiles = [res.data for res in decoded_data.image_resource_blocks
-                   if res.resource_id == ImageResourceID.ICC_PROFILE]
-
-    if not icc_profiles:
-        return None
-
-    icc_profile = icc_profiles[0]
-
-    if isinstance(icc_profile, bytes): # profile was not decoded
-        return None
-
-    return icc_profile
-
-
-def _get_mode(band_keys):
-    for mode in ['RGBA', 'RGB']:
-        if set(band_keys) == set(list(mode)):
-            return mode
-
-def _from_8bit_raw(data, size):
-    return frombytes('L', size, data, "raw", 'L')
-
-def _from_16bit_raw(data, size):
-    im = frombytes('I', size, data, "raw", 'I;16B')
-    return im.point(lambda i: i * (1/(256.0)))
-
-def _from_32bit_raw(data, size):
-    pixels = be_array_from_bytes("f", data)
-    im = Image.new("F", size)
-    im.putdata(pixels, 255, 0)
-    return im
-
-def _channels_data_to_PIL(channels_data, channel_types, size, depth, icc_profile):
-    if Image is None:
-        raise Exception("This module requires PIL (or Pillow) installed.")
-
-    if size == (0, 0):
-        return
-
-    bands = {}
-
-    for channel, channel_type in zip(channels_data, channel_types):
-
-        pil_band = ChannelID.to_PIL(channel_type)
-        if pil_band is None:
-            warnings.warn("Unsupported channel type (%d)" % channel_type)
-            continue
-
-        if channel.compression in [Compression.RAW, Compression.ZIP, Compression.ZIP_WITH_PREDICTION]:
-            if depth == 8:
-                im = _from_8bit_raw(channel.data, size)
-            elif depth == 16:
-                im = _from_16bit_raw(channel.data, size)
-            elif depth == 32:
-                im = _from_32bit_raw(channel.data, size)
-            else:
-                warnings.warn("Unsupported depth (%s)" % depth)
-                continue
-
-        elif channel.compression == Compression.PACK_BITS:
-            if depth != 8:
-                warnings.warn("Depth %s is unsupported for PackBits compression" % depth)
-                continue
-            im = frombytes('L', size, channel.data, "packbits", 'L')
-        else:
-            if Compression.is_known(channel.compression):
-                warnings.warn("Compression method is not implemented (%s)" % channel.compression)
-            else:
-                warnings.warn("Unknown compression method (%s)" % channel.compression)
-            continue
-
-        bands[pil_band] = im.convert('L')
-
-    mode = _get_mode(bands.keys())
-    merged_image = Image.merge(mode, [bands[band] for band in mode])
-
-    if icc_profile is not None:
-        display_profile = ImageCms.createProfile('sRGB') # XXX: ImageCms.get_display_profile()?
-        ImageCms.profileToProfile(merged_image, icc_profile, display_profile, inPlace=True)
-
-    return merged_image
-
-
-def layer_to_PIL(decoded_data, layer_index):
-    layers = decoded_data.layer_and_mask_data.layers
-    layer = layers.layer_records[layer_index]
-
-    channels_data = layers.channel_image_data[layer_index]
-    size = layer.width(), layer.height()
-    channel_types = [info.id for info in layer.channels]
-
-    return _channels_data_to_PIL(
-        channels_data, channel_types, size,
-        decoded_data.header.depth, get_icc_profile(decoded_data))
-
-def composite_image_to_PIL(decoded_data):
-    header = decoded_data.header
-    size = header.width, header.height
-
-    if header.color_mode == ColorMode.RGB:
-
-        if header.number_of_channels == 3:
-            channel_types = [0, 1, 2]
-        elif header.number_of_channels == 4:
-            channel_types = [0, 1, 2, -1]
-        else:
-            warnings.warn("This number of channels (%d) is unsupported for this color mode (%s)" % (
-                         header.number_of_channels, header.color_mode))
-            return
-
-    else:
-        warnings.warn("Unsupported color mode (%s)" % header.color_mode)
-        return
-
-    return _channels_data_to_PIL(
-        decoded_data.image_data,
-        channel_types,
-        size,
-        header.depth,
-        get_icc_profile(decoded_data),
-    )

File src/psd_tools/user_api/pil_support.py

+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+import warnings
+from psd_tools.utils import be_array_from_bytes
+from psd_tools.constants import Compression, ChannelID, ColorMode, ImageResourceID
+
+try:
+    from PIL import Image, ImageCms
+    if hasattr(Image, 'frombytes'):
+        frombytes = Image.frombytes
+    else:
+        frombytes = Image.fromstring
+
+except ImportError:
+    Image = None
+
+
+def layer_to_PIL(decoded_data, layer_index):
+    """
+    Converts a layer from the ``decoded_data`` to a PIL image.
+    """
+    layers = decoded_data.layer_and_mask_data.layers
+    layer = layers.layer_records[layer_index]
+
+    channels_data = layers.channel_image_data[layer_index]
+    size = layer.width(), layer.height()
+    channel_types = [info.id for info in layer.channels]
+
+    return _channels_data_to_PIL(
+        channels_data, channel_types, size,
+        decoded_data.header.depth, get_icc_profile(decoded_data))
+
+def composite_image_to_PIL(decoded_data):
+    """
+    Converts a composite (merged) images from the ``decoded_data``
+    to a PIL image.
+    """
+    header = decoded_data.header
+    size = header.width, header.height
+
+    if header.color_mode == ColorMode.RGB:
+
+        if header.number_of_channels == 3:
+            channel_types = [0, 1, 2]
+        elif header.number_of_channels == 4:
+            channel_types = [0, 1, 2, -1]
+        else:
+            warnings.warn("This number of channels (%d) is unsupported for this color mode (%s)" % (
+                         header.number_of_channels, header.color_mode))
+            return
+
+    else:
+        warnings.warn("Unsupported color mode (%s)" % header.color_mode)
+        return
+
+    return _channels_data_to_PIL(
+        decoded_data.image_data,
+        channel_types,
+        size,
+        header.depth,
+        get_icc_profile(decoded_data),
+    )
+
+
+def _channels_data_to_PIL(channels_data, channel_types, size, depth, icc_profile):
+    if Image is None:
+        raise Exception("This module requires PIL (or Pillow) installed.")
+
+    if size == (0, 0):
+        return
+
+    bands = {}
+
+    for channel, channel_type in zip(channels_data, channel_types):
+
+        pil_band = ChannelID.to_PIL(channel_type)
+        if pil_band is None:
+            warnings.warn("Unsupported channel type (%d)" % channel_type)
+            continue
+
+        if channel.compression in [Compression.RAW, Compression.ZIP, Compression.ZIP_WITH_PREDICTION]:
+            if depth == 8:
+                im = _from_8bit_raw(channel.data, size)
+            elif depth == 16:
+                im = _from_16bit_raw(channel.data, size)
+            elif depth == 32:
+                im = _from_32bit_raw(channel.data, size)
+            else:
+                warnings.warn("Unsupported depth (%s)" % depth)
+                continue
+
+        elif channel.compression == Compression.PACK_BITS:
+            if depth != 8:
+                warnings.warn("Depth %s is unsupported for PackBits compression" % depth)
+                continue
+            im = frombytes('L', size, channel.data, "packbits", 'L')
+        else:
+            if Compression.is_known(channel.compression):
+                warnings.warn("Compression method is not implemented (%s)" % channel.compression)
+            else:
+                warnings.warn("Unknown compression method (%s)" % channel.compression)
+            continue
+
+        bands[pil_band] = im.convert('L')
+
+    mode = _get_mode(bands.keys())
+    merged_image = Image.merge(mode, [bands[band] for band in mode])
+
+    if icc_profile is not None:
+        display_profile = ImageCms.createProfile('sRGB') # XXX: ImageCms.get_display_profile()?
+        ImageCms.profileToProfile(merged_image, icc_profile, display_profile, inPlace=True)
+
+    return merged_image
+
+def _get_mode(band_keys):
+    for mode in ['RGBA', 'RGB']:
+        if set(band_keys) == set(list(mode)):
+            return mode
+
+def _from_8bit_raw(data, size):
+    return frombytes('L', size, data, "raw", 'L')
+
+def _from_16bit_raw(data, size):
+    im = frombytes('I', size, data, "raw", 'I;16B')
+    return im.point(lambda i: i * (1/(256.0)))
+
+def _from_32bit_raw(data, size):
+    pixels = be_array_from_bytes("f", data)
+    im = Image.new("F", size)
+    im.putdata(pixels, 255, 0)
+    return im
+
+def get_icc_profile(decoded_data):
+    """
+    Returns ICC image profile (if it exists and was correctly decoded)
+    """
+    # fixme: move this function somewhere?
+    icc_profiles = [res.data for res in decoded_data.image_resource_blocks
+                   if res.resource_id == ImageResourceID.ICC_PROFILE]
+
+    if not icc_profiles:
+        return None
+
+    icc_profile = icc_profiles[0]
+
+    if isinstance(icc_profile, bytes): # profile was not decoded
+        return None
+
+    return icc_profile

File src/psd_tools/user_api/psd_image.py

 from __future__ import absolute_import, unicode_literals
 
 import collections
-import weakref
+import weakref              # FIXME: there should be weakrefs in this module
 import psd_tools.reader
 import psd_tools.decoder
 from psd_tools.constants import TaggedBlock, SectionDivider
-from psd_tools.user_api.layers import (group_layers, composite_image_to_PIL,
-                                       layer_to_PIL)
+from psd_tools.user_api.layers import group_layers
+from psd_tools.user_api.pil_support import composite_image_to_PIL, layer_to_PIL
 
 BBox = collections.namedtuple('BBox', 'x1, y1, x2, y2')
 
 
     @property
     def _info(self):
-        return self._psd.layer_info(self._index)
+        return self._psd._layer_info(self._index)
 
     @property
     def _tagged_blocks(self):
 
     def as_PIL(self):
         """ Returns a PIL image for this layer. """
-        return self._psd.layer_as_PIL(self._index)
+        return self._psd._layer_as_PIL(self._index)
 
     @property
     def bbox(self):
                     # regular layer
                     group._add_layer(Layer(group, index))
 
-
         self._psd = self
         fake_root_data = {'layers': group_layers(decoded_data), 'index': None}
         root = _RootGroup(self, None, [])
         return cls(decoded_data)
 
 
-    def layer_info(self, index):
-        layers = self.decoded_data.layer_and_mask_data.layers.layer_records
-        return layers[index]
-
-    def layer_as_PIL(self, index):
-        return layer_to_PIL(self.decoded_data, index)
-
-    def composite_image(self):
+    def as_PIL(self):
         """
         Returns a pre-rendered image for this PSD file.
         """
         """
         return _combined_bbox(self.layers)
 
+    def _layer_info(self, index):
+        layers = self.decoded_data.layer_and_mask_data.layers.layer_records
+        return layers[index]
+
+    def _layer_as_PIL(self, index):
+        return layer_to_PIL(self.decoded_data, index)
+
 
 def _combined_bbox(layers):
     """

File tests/test_images.py

 import pytest
 
 from psd_tools import PSDImage
-from psd_tools.user_api.layers import composite_image_to_PIL, layer_to_PIL
+from psd_tools.user_api.pil_support import composite_image_to_PIL, layer_to_PIL
 from psd_tools.constants import BlendMode
 
 from .utils import decode_psd

File tests/test_pixels.py

 
 def _assert_image_pixel(filename, point, color):
     psd = PSDImage(decode_psd(filename))
-    image = psd.composite_image()
+    image = psd.as_PIL()
     assert image.getpixel(point) == color
 
 @pytest.mark.parametrize(["filename", "point", "color"], PIXEL_COLORS)