Commits

Mikhail Korobov committed 9d6daa0

pymaging support

  • Participants
  • Parent commits ab099e8

Comments (0)

Files changed (5)

 
       pip install docopt
 
-* and PIL_ (or Pillow_) for accessing PSD layer data as PIL images::
+* PIL_ (or Pillow_) for accessing PSD image and layer data as PIL images::
 
       pip install Pillow
 
+* pymaging_ and packbits_ for accessing PSD image and layer
+  data as ``pymaging.Image``::
+
+      pip install packbits
+
+  (`pymaging installation instructions`_ are available in pymaging docs).
+
 .. note::
 
     In order to extract images from 32bit PSD files PIL/Pillow must be built
 .. _docopt: https://github.com/docopt/docopt
 .. _PIL: http://www.pythonware.com/products/pil/
 .. _Pillow: https://github.com/python-imaging/Pillow
-
+.. _packbits: http://pypi.python.org/pypi/packbits/
+.. _pymaging: https://github.com/ojii/pymaging
+.. _pymaging installation instructions: http://pymaging.readthedocs.org/en/latest/usr/installation.html
 
 Usage
 -----
     >>> layer.as_PIL()
     <PIL.Image.Image image mode=RGBA size=43x62 at ...>
 
+
 Export a single layer::
 
     >>> layer_image = layer.as_PIL()
     >>> merged_image = psd.as_PIL()
     >>> merged_image.save('my_image.png')
 
+The same using pymaging_::
+
+    >>> merged_image = psd.as_pymaging()
+    >>> merged_image.save_to_path('my_image.png')
+    >>> layer_image = layer.as_pymaging()
+    >>> layer_image.save_to_path('layer.png')
+
 
 Why yet another PSD reader?
 ---------------------------
 The process of handling a PSD file is split into 3 stages:
 
 1) "Reading": the file is read and parsed to low-level data
-   structures that closely match the specification. No PIL images
-   are constructed; image resources blocks and additional layer
+   structures that closely match the specification. No user-accessible
+   images are constructed; image resources blocks and additional layer
    information are extracted but not parsed (they remain just keys
    with a binary data). The goal is to extract all information
    from a PSD file.
 resembles PSD file; it should be fairly complete but very low-level
 and not easy to use. So there is a third stage:
 
-3) "User-facing API": PIL images of the PSD layers are created and
-   combined to a user-friendly data structure.
+3) "User-facing API": PSD image is converted to an user-friendly object
+   that supports layer groups, exporting data as ``PIL.Image`` or
+   ``pymaging.Image``, etc.
 
 Stage separation also means user-facing API may be opinionated:
 if somebody doesn't like it then it should possible to build an
-another API (e.g. without PIL) based on lower-level decoded PSD file.
+another API based on lower-level decoded PSD file.
 
 ``psd-tools`` tries not to throw away information from the original
 PSD file; even if the library can't parse some info, this info
   a single layer and to export a final image, but it is not possible to
   render e.g. layer group;
 * the decoding of Descriptor structures is very basic;
-* the writing of PSD images.
+* the writing of PSD images is not implemented;
+* only 8bit images can be converted to ``pymaging.Image``.
 
 If you need some of unimplemented features then please fire an issue
 or implement it yourself (pull requests are welcome in this case).

File src/psd_tools/user_api/pymaging_support.py

 import array
 
 try:
+    import packbits
     from pymaging import Image
     from pymaging.colors import RGB, RGBA
     from pymaging.pixelarray import get_pixel_array
 except ImportError:
     Image = None
+    packbits = None
 
 from psd_tools.constants import ColorMode, Compression
 
     Converts a composite (merged) image from the ``decoded_data``
     to a pymaging.Image.
     """
-    if Image is None:
-        raise Exception("This module requires `pymaging` library installed.")
+    if Image is None or packbits is None:
+        raise Exception("This module requires `pymaging` and `packbits` packages.")
 
     header = decoded_data.header
     size = header.width, header.height
     assert len(channels_data) == num_channels
 
     total_size = size[0]*size[1]*num_channels
-
     image_bytes = array.array(str("B"), [0]*total_size)
 
     for index, channel in enumerate(channels_data):
+
+        data = channel.data # zip and zip-with-prediction data is already decoded
         if channel.compression == Compression.PACK_BITS:
-            raise NotImplementedError("PackBits decompression is not implemented for pymaging")
+            data = packbits.decode(data)
 
-        image_bytes[index::num_channels] = array.array(str("B"), channel.data)
+        image_bytes[index::num_channels] = array.array(str("B"), data)
 
     pixels = get_pixel_array(image_bytes, size[0], size[1], mode.length)
 
     return Image(pixels, mode)
+

File tests/test_images.py

     except AttributeError:
         return pil_image.tostring()
 
-SINGLE_LAYER_FILES = [['1layer.psd'], ['transparentbg-gimp.psd']]
+SINGLE_LAYER_FILES = [
+    ['1layer.psd'],
+    ['transparentbg-gimp.psd']
+]
 
 
 @pytest.mark.parametrize(["filename"], SINGLE_LAYER_FILES)

File tests/test_pixels.py

     ('32bit5x5.psd', 1, (1, 3), (46, 196, 104, 255)),
 )
 
-
-def _assert_image_pixel(filename, point, color):
+def _assert_PIL_image_pixel(filename, point, color):
     psd = PSDImage(decode_psd(filename))
     image = psd.as_PIL()
     assert image.getpixel(point) == color
 
+def _assert_pymaging_image_pixel(filename, point, color):
+    psd = PSDImage(decode_psd(filename))
+    image = psd.as_pymaging()
+    assert list(image.get_pixel(*point)) == list(color)
+
+BACKENDS = [[_assert_PIL_image_pixel], [_assert_pymaging_image_pixel]]
+
+
+@pytest.mark.parametrize(["assert_function"], BACKENDS)
 @pytest.mark.parametrize(["filename", "point", "color"], PIXEL_COLORS)
-def test_composite_image_pixels(filename, point, color):
-    _assert_image_pixel(filename, point, color)
+def test_composite(filename, point, color, assert_function):
+    assert_function(filename, point, color)
 
 @pytest.mark.parametrize(["filename", "point", "color"], PIXEL_COLORS_32BIT)
-def test_composite_image_pixels_32bit(filename, point, color):
-    _assert_image_pixel(filename, point, color)
+def test_composite_32bit(filename, point, color):
+    _assert_PIL_image_pixel(filename, point, color)
 
 @pytest.mark.parametrize(["filename", "point", "color"], PIXEL_COLORS_16BIT)
 def test_composite_16bit(filename, point, color):
-    _assert_image_pixel(filename, point, color)
+    _assert_PIL_image_pixel(filename, point, color)
 
 @pytest.mark.parametrize(["filename", "layer_num", "point", "color"], LAYER_COLORS)
 def test_layer_colors(filename, layer_num, point, color):
     pytest-cov
     coverage
     git+git://github.com/ojii/pymaging.git@0b79cf0b33ca81bdc79ac62d1377eebc3b7469bc#egg=pymaging
+    packbits
 
 [testenv]
 deps=