Mikhail Korobov avatar Mikhail Korobov committed dba85cb

'global' tagged blocks are back; 32bit channels support is improved (but is still incorrect)

Comments (0)

Files changed (5)

             )
 
         print(decoded.header)
-        pprint.pprint(decoded.image_resource_blocks)
+        pprint.pprint(len(decoded.image_resource_blocks))
+        pprint.pprint(decoded.layer_and_mask_data)
+        pprint.pprint(decoded.image_data)
         pprint.pprint(group_layers(decoded))
+

psd_tools/constants.py

     FILTER_EFFECTS1 = 'FXid'
     FILTER_EFFECTS2 = 'FEid'
 
+    LAYER_16 = "Lr16"
+    LAYER_32 = "Lr32"
+    LAYER = "Layr"
+
 
 class OSType(Enum):
     """

psd_tools/reader/layers.py

 import logging
 import warnings
 
-from psd_tools.utils import read_fmt, read_pascal_string, read_be_array, trimmed_repr
+from psd_tools.utils import (read_fmt, read_pascal_string, read_be_array,
+                             trimmed_repr, debug_view, pad, syncronize)
 from psd_tools.exceptions import Error
 from psd_tools.constants import (Compression, Clipping, BlendMode,
                                  ChannelID, TaggedBlock)
 
 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')
+LayerAndMaskData = collections.namedtuple('LayerAndMaskData', 'layers global_mask_info tagged_blocks')
 ChannelInfo = collections.namedtuple('ChannelInfo', 'id length')
 _MaskData = collections.namedtuple('MaskData', 'top left bottom right default_color flags real_flags real_background')
 LayerBlendingRanges = collections.namedtuple('LayerBlendingRanges', 'composite_ranges channel_ranges')
     global_mask_info = _read_global_mask_info(fp)
 
     consumed_bytes = fp.tell() - start_position
+    syncronize(fp) # hack hack hack
+    tagged_blocks = _read_layer_tagged_blocks(fp, length - consumed_bytes)
+
+    consumed_bytes = fp.tell() - start_position
     fp.seek(length-consumed_bytes, 1)
 
-    return LayerAndMaskData(layers, global_mask_info)
+    return LayerAndMaskData(layers, global_mask_info, tagged_blocks)
 
 def _read_layers(fp, encoding, depth):
     """
         return
 
     key = fp.read(4)
-    length = read_fmt("I", fp)[0]
+    length = pad(read_fmt("I", fp)[0], 4)
+
     data = fp.read(length)
     return Block(key, data)
 
     """
     # XXX: Does it really work properly? What is it for?
     start_pos = fp.tell()
-    length, overlay_color_space, c1, c2, c3, c4, opacity, kind = read_fmt("IH 4H HB", fp)
-    filler_length = length - (fp.tell()-start_pos)
-    if filler_length > 0:
-        fp.seek(filler_length, 1)
+    length = read_fmt("H", fp)[0]
 
-    return GlobalMaskInfo(overlay_color_space, (c1, c2, c3, c4), opacity, kind)
+    if length:
+        overlay_color_space, c1, c2, c3, c4, opacity, kind = read_fmt("H 4H HB", fp)
+        filler_length = length - (fp.tell()-start_pos)
+        if filler_length > 0:
+            fp.seek(filler_length, 1)
+        return GlobalMaskInfo(overlay_color_space, (c1, c2, c3, c4), opacity, kind)
+    else:
+        return None
 
 def read_image_data(fp, header):
     """
     w, h = header.width, header.height
     compression = read_fmt("H", fp)[0]
 
+    bytes_per_pixel = header.depth // 8
+
     channel_byte_counts = []
     if compression == Compression.PACK_BITS:
         for ch in range(header.number_of_channels):
     for channel_id in range(header.number_of_channels):
 
         if compression == Compression.RAW:
-            data = fp.read(w*h)
+            data = fp.read(w*h*bytes_per_pixel)
             channel_data.append(ChannelData(compression, data))
 
         elif compression == Compression.PACK_BITS:
             byte_counts = channel_byte_counts[channel_id]
-            data = fp.read(sum(byte_counts))
+            data = fp.read(sum(byte_counts)*bytes_per_pixel)
             channel_data.append(ChannelData(compression, data))
 
         elif Compression.is_known(compression):

psd_tools/user_api/layers.py

 
     bands = {}
     if depth == 8:
-        pil_depth = 'L'
+        mode, raw_mode = 'L', 'L'
     elif depth == 32:
-        pil_depth = 'I'
+        mode, raw_mode = 'I', 'I;32B'
     else:
         warnings.warn("Unsupported depth (%s)" % depth)
         return
         if pil_band is None:
             warnings.warn("Unsupported channel type (%d)" % channel_type)
             continue
+
         if channel.compression == Compression.RAW:
-            bands[pil_band] = frombytes(pil_depth, size, channel.data, "raw", pil_depth)
+            im = frombytes(mode, size, channel.data, "raw", raw_mode)
         elif channel.compression == Compression.PACK_BITS:
-            bands[pil_band] = frombytes(pil_depth, size, channel.data, "packbits", pil_depth)
-        elif Compression.is_known(channel.compression):
-            warnings.warn("Compression method is not implemented (%s)" % channel.compression)
+            im = frombytes(mode, size, channel.data, "packbits", raw_mode)
         else:
-            warnings.warn("Unknown compression method (%s)" % channel.compression)
+            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())
     return Image.merge(mode, [bands[band] for band in mode])
     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:

psd_tools/utils.py

 # -*- coding: utf-8 -*-
-from __future__ import absolute_import, division, unicode_literals
+from __future__ import absolute_import, division, unicode_literals, print_function
 
 import sys
 import struct
         if len(data) > trim_length:
             return repr(data[:trim_length] + b' ... =' + str(len(data)).encode('ascii'))
     return repr(data)
+
+def syncronize(fp, signature=b'8BIM', limit=8):
+    # This is a hack for the cases where I gave up understanding PSD format.
+    start = fp.tell()
+    data = fp.read(limit)
+    pos = data.find(signature)
+    if pos != -1:
+        fp.seek(start+pos)
+        return True
+    else:
+        fp.seek(start)
+        return False
+
+def debug_view(fp, txt=""):
+    fp.seek(-20, 1)
+    pre = fp.read(20)
+    post = fp.read(100)
+    fp.seek(-100, 1)
+    print(txt, repr(pre), "--->.<---", repr(post))
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.