예제 #1
0
def _decode_section_divider(data):
    data_length = len(data)
    blend_mode = None
    sub_type = None

    fp = io.BytesIO(data)
    tp = read_fmt("I", fp)[0]
    if not SectionDivider.is_known(tp):
        warnings.warn("Unknown section divider type (%s)" % tp)

    if data_length >= 12:
        sig = fp.read(4)
        if sig != b"8BIM":
            raise Error("Invalid signature in section divider block (%r)" % sig)

        blend_mode = fp.read(4)
        if not BlendMode.is_known(blend_mode):
            warnings.warn("Unknown section divider blend mode (%s)" % blend_mode)

        if data_length >= 16:
            sub_type = read_fmt("I", fp)[0]
            if not SectionDividerSub.is_known(sub_type):
                warnings.warn("Unknown section divider sub-type (%s)" % sub_type)

    return Divider(tp, blend_mode, sub_type)
예제 #2
0
 def blend_mode(self, value):
     if isinstance(value, BlendMode):
         self._record.blend_mode = value
     elif hasattr(BlendMode, value.upper()):
         self._record.blend_mode = getattr(BlendMode, value.upper())
     else:
         self._record.blend_mode = BlendMode(value)
예제 #3
0
    def _repr_pretty_(self, p, cycle):
        if cycle:
            p.text(repr(self))
        else:
            blend_mode = self.blend_mode
            if blend_mode is not None:
                blend_mode = BlendMode.name_of(blend_mode)

            sub_type = self.sub_type
            if sub_type is not None:
                sub_type = SectionDividerSub.name_of(sub_type)

            p.begin_group(2, "Divider(")
            p.begin_group(0)

            p.break_()
            p.text("type = %s," % SectionDivider.name_of(self.type))
            p.break_()
            p.text("blend_mode = %s," % blend_mode)
            p.break_()
            p.text("sub_type = %s" % sub_type)

            p.end_group(2)
            p.break_()
            p.end_group(0, ")")
예제 #4
0
파일: layers.py 프로젝트: thkien/psd-tools2
    def blend_mode(self):
        """
        Blend mode of this layer. See
        :py:class:`~psd_tools.constants.BlendMode`

        :rtype: str
        """
        return BlendMode.human_name_of(self._record.blend_mode)
 def _read_body(cls, fp):
     # TODO: Check 4-byte = 2-byte int + 2-byte fraction?
     version, blur, intensity = read_fmt('III', fp)
     color = Color.read(fp)
     signature = read_fmt('4s', fp)[0]
     assert signature == b'8BIM', 'Invalid signature %r' % (signature)
     blend_mode = BlendMode(read_fmt('4s', fp)[0])
     enabled, opacity = read_fmt('2B', fp)
     return version, blur, intensity, color, blend_mode, enabled, opacity
예제 #6
0
def _read_layer_record(fp, encoding):
    """
    Reads single layer record.
    """
    top, left, bottom, right, num_channels = read_fmt("4i H", fp)
    logger.debug('  top=%d, left=%d, bottom=%d, right=%d, num_channels=%d',
                 top, left, bottom, right, num_channels)

    channel_info = []
    for channel_num in range(num_channels):
        info = ChannelInfo(*read_fmt("hI", fp))
        channel_info.append(info)

    sig = fp.read(4)
    if sig != b'8BIM':
        raise Error("Error parsing layer: invalid signature (%r)" % sig)

    blend_mode = fp.read(4)
    if not BlendMode.is_known(blend_mode):
        warnings.warn("Unknown blend mode (%s)" % blend_mode)

    opacity, clipping, flags, extra_length = read_fmt("BBBxI", fp)

    if not Clipping.is_known(clipping):
        warnings.warn("Unknown clipping (%s)" % clipping)
    logger.debug('  extra_length=%s', extra_length)

    flags = LayerFlags(
        bool(flags & 1), not bool(flags & 2),           # why "not"?
        bool(flags & 16) if bool(flags & 8) else None
    )

    start_pos = fp.tell()
    mask_data = _read_layer_mask_data(fp)
    blending_ranges = _read_layer_blending_ranges(fp)

    name = read_pascal_string(fp, encoding, 4)

    remaining_length = extra_length - (fp.tell() - start_pos)

    logger.debug('  reading layer tagged blocks...')
    logger.debug('    length=%d, start_pos=%d', remaining_length, fp.tell())

    tagged_blocks = _read_layer_tagged_blocks(fp, remaining_length)

    remaining_length = extra_length - (fp.tell() - start_pos)
    if remaining_length > 0:
        fp.seek(remaining_length, 1) # skip the remainder
        logger.debug('  skipping %s bytes', remaining_length)

    return LayerRecord(
        top, left, bottom, right,
        num_channels, channel_info,
        blend_mode, opacity, clipping, flags,
        mask_data, blending_ranges, name,
        tagged_blocks
    )
예제 #7
0
파일: layers.py 프로젝트: zective/psd-tools
 def blend_mode(self, value):
     _value = BlendMode(value)
     if _value == BlendMode.PASS_THROUGH:
         self._record.blend_mode = BlendMode.NORMAL
     else:
         self._record.blend_mode = _value
     setting = self._setting
     if setting:
         setting.blend_mode = _value
예제 #8
0
def _read_layer_record(fp, encoding, version):
    """
    Reads single layer record.
    """
    top, left, bottom, right, num_channels = read_fmt("4i H", fp)
    logger.debug('  top=%d, left=%d, bottom=%d, right=%d, num_channels=%d',
                 top, left, bottom, right, num_channels)

    channel_info = []
    for channel_num in range(num_channels):
        if version == 1:
            info = ChannelInfo(*read_fmt("hI", fp))
        elif version == 2:
            info = ChannelInfo(*read_fmt("hQ", fp))
        channel_info.append(info)

    sig = fp.read(4)
    if sig != b'8BIM':
        raise Error("Error parsing layer: invalid signature (%r)" % sig)

    blend_mode = fp.read(4)
    if not BlendMode.is_known(blend_mode):
        warnings.warn("Unknown blend mode (%s)" % blend_mode)

    opacity, clipping, flags, extra_length = read_fmt("BBBxI", fp)

    if not Clipping.is_known(clipping):
        warnings.warn("Unknown clipping (%s)" % clipping)
    logger.debug('  extra_length=%s', extra_length)

    flags = LayerFlags(
        bool(flags & 1),
        not bool(flags & 2),  # why "not"?
        bool(flags & 16) if bool(flags & 8) else None)

    start_pos = fp.tell()
    mask_data = _read_layer_mask_data(fp)
    blending_ranges = _read_layer_blending_ranges(fp)

    name = read_pascal_string(fp, encoding, 4)

    remaining_length = extra_length - (fp.tell() - start_pos)

    logger.debug('  reading layer tagged blocks...')
    logger.debug('    length=%d, start_pos=%d', remaining_length, fp.tell())

    tagged_blocks = _read_layer_tagged_blocks(fp, remaining_length, version)

    remaining_length = extra_length - (fp.tell() - start_pos)
    if remaining_length > 0:
        fp.seek(remaining_length, 1)  # skip the remainder
        logger.debug('  skipping %s bytes', remaining_length)

    return LayerRecord(top, left, bottom, right, num_channels, channel_info,
                       blend_mode, opacity, clipping, flags, mask_data,
                       blending_ranges, name, tagged_blocks)
예제 #9
0
def _read_blend_mode(fp):
    sig = fp.read(4)
    if sig != b'8BIM':
        raise Error("Error parsing layer effect: invalid signature (%r)" % sig)

    blend_mode = fp.read(4)
    if not BlendMode.is_known(blend_mode):
        warnings.warn("Unknown blend mode (%s)" % blend_mode)

    return blend_mode
예제 #10
0
def _read_blend_mode(fp):
    sig = fp.read(4)
    if sig != b'8BIM':
        raise Error("Error parsing layer effect: invalid signature (%r)" % sig)

    blend_mode = fp.read(4)
    if not BlendMode.is_known(blend_mode):
        warnings.warn("Unknown blend mode (%s)" % blend_mode)

    return blend_mode
예제 #11
0
 def read(cls, fp, **kwargs):
     kind = SectionDivider(read_fmt('I', fp)[0])
     signature, key = None, None
     if is_readable(fp, 8):
         signature = read_fmt('4s', fp)[0]
         assert signature == b'8BIM', 'Invalid signature %r' % signature
         key = BlendMode(read_fmt('4s', fp)[0])
     sub_type = None
     if is_readable(fp, 4):
         sub_type = read_fmt('I', fp)[0]
     return cls(kind, signature=signature, key=key, sub_type=sub_type)
 def read(cls, fp):
     # TODO: Check 4-byte = 2-byte int + 2-byte fraction?
     version, blur, intensity, angle, distance = read_fmt('IIIiI', fp)
     color = Color.read(fp)
     signature = read_fmt('4s', fp)[0]
     assert signature == b'8BIM', 'Invalid signature %r' % (signature)
     blend_mode = BlendMode(read_fmt('4s', fp)[0])
     enabled, use_global_angle, opacity = read_fmt('3B', fp)
     native_color = Color.read(fp)
     return cls(version, blur, intensity, angle, distance, color,
                blend_mode, enabled, use_global_angle, opacity,
                native_color)
예제 #13
0
    def __repr__(self):
        blend_mode = self.blend_mode
        if blend_mode is not None:
            blend_mode = BlendMode.name_of(blend_mode)

        sub_type = self.sub_type
        if sub_type is not None:
            sub_type = SectionDividerSub.name_of(sub_type)

        return "Divider(type=%s, blend_mode=%s, sub_type=%s)" % (
            SectionDivider.name_of(self.type),
            blend_mode,
            sub_type,
        )
예제 #14
0
def _read_layer_record(fp, encoding):
    """
    Reads single layer record.
    """
    top, left, bottom, right, num_channels = read_fmt("4i H", fp)

    channel_info = []
    for channel_num in range(num_channels):
        info = ChannelInfo(*read_fmt("hI", fp))
        channel_info.append(info)

    sig = fp.read(4)
    if sig != b'8BIM':
        raise Error("Error parsing layer: invalid signature (%r)" % sig)

    blend_mode = fp.read(4).decode('ascii')
    if not BlendMode.is_known(blend_mode):
        warnings.warn("Unknown blend mode (%s)" % blend_mode)

    opacity, clipping, flags, extra_length = read_fmt("BBBxI", fp)

    flags = LayerFlags(bool(flags & 1), not bool(flags & 2)) # why not?

    if not Clipping.is_known(clipping):
        warnings.warn("Unknown clipping: %s" % clipping)

    start = fp.tell()
    mask_data = _read_layer_mask_data(fp)
    blending_ranges = _read_layer_blending_ranges(fp)

    name = read_pascal_string(fp, encoding, 4)

    remaining_length = extra_length - (fp.tell()-start)
    tagged_blocks = _read_layer_tagged_blocks(fp, remaining_length)

    remaining_length = extra_length - (fp.tell()-start)
    fp.seek(remaining_length, 1) # skip the reminder

    return LayerRecord(
        top, left, bottom, right,
        num_channels, channel_info,
        blend_mode, opacity, clipping, flags,
        mask_data, blending_ranges, name,
        tagged_blocks
    )
예제 #15
0
def merge_layers(layers, respect_visibility=True, skip_layer=lambda layer: False, background=None, bbox=None):
    """
    Merges layers together (the first layer is on top).

    By default hidden layers are not rendered;
    pass ``respect_visibility=False`` to render them.

    In order to skip some layers pass ``skip_layer`` function which
    should take ``layer` as an argument and return True or False.

    If ``bbox`` is not None, it should be an instance of ``BBox`` class
    with coordinates; returned image will be restricted to this rectangle.

    If ``background`` is not None, ``bbox`` should be passed as well.
    It should be an image such as background.size == (bbox.width, bbox.height)

    This is highly experimental.
    """

    if _blend_modes is not None:
        blend_functions = {
            BlendMode.NORMAL:           None,
            BlendMode.DISSOLVE:         _blend_modes.dissolve,

            BlendMode.DARKEN:           _blend_modes.darken,
            BlendMode.MULTIPLY:         _blend_modes.multiply,
            BlendMode.COLOR_BURN:       _blend_modes.color_burn,
            BlendMode.LINEAR_BURN:      _blend_modes.linear_burn,
            BlendMode.DARKER_COLOR:     _blend_modes.darker_color,      #                                       Photoshop bug

            BlendMode.LIGHTEN:          _blend_modes.lighten,
            BlendMode.SCREEN:           _blend_modes.screen,
            BlendMode.COLOR_DODGE:      _blend_modes.color_dodge,
            BlendMode.LINEAR_DODGE:     _blend_modes.linear_dodge,
            BlendMode.LIGHTER_COLOR:    _blend_modes.lighter_color,     #                                       Photoshop bug

            BlendMode.OVERLAY:          _blend_modes.overlay,
            BlendMode.SOFT_LIGHT:       _blend_modes.soft_light,        # max deviation - +/-1 tone
            BlendMode.HARD_LIGHT:       _blend_modes.hard_light,        #                                       Photoshop bug
            BlendMode.VIVID_LIGHT:      _blend_modes.vivid_light,       # max deviation - +2 tone               Photoshop bug
            BlendMode.LINEAR_LIGHT:     _blend_modes.linear_light,      #                                       Photoshop bug
            BlendMode.PIN_LIGHT:        _blend_modes.pin_light,         # max deviation - +1 tone
            BlendMode.HARD_MIX:         _blend_modes.hard_mix,

            BlendMode.DIFFERENCE:       _blend_modes.difference,
            BlendMode.EXCLUSION:        _blend_modes.exclusion,         # max deviation - +/-1 tone
            BlendMode.SUBTRACT:         _blend_modes.subtract,
            BlendMode.DIVIDE:           _blend_modes.divide,

            BlendMode.HUE:              _blend_modes.hue,               # max deviation - +/-2 luminance level  Photoshop bug
            BlendMode.SATURATION:       _blend_modes.saturation,        # max deviation - +/-2 luminance level  Photoshop bug
            BlendMode.COLOR:            _blend_modes.color,             # max deviation - +/-2 luminance level  Photoshop bug
            BlendMode.LUMINOSITY:       _blend_modes.luminosity         # max deviation - +/-2 luminance level  Photoshop bug
        }
    else:
        logger.warning(
            '"_blend_modes" C extension is not found. ' \
            'Blend modes are unavailable for merging!'
        )

    if background is None:
        if bbox is None:
            bbox = combined_bbox(layers)
            if bbox is None:
                return None

        # creating a base image...
        result = Image.new(
            "RGBA",
            (bbox.width, bbox.height),
            color = (255, 255, 255, 0)
        )
    else:
        if bbox is None or background.size != (bbox.width, bbox.height):
            return None

        # using existing image as a base...
        result = background

    for layer in reversed(layers):
        if layer is None:
            continue
        if respect_visibility and not layer.visible:
            continue

        layer_bbox = layer.bbox

        if layer_bbox.width == 0 or layer_bbox.height == 0:
            continue
        if layer_bbox.x2 < bbox.x1 or layer_bbox.y2 < bbox.y1 \
        or layer_bbox.x1 > bbox.x2 or layer_bbox.y1 > bbox.y2:
            logger.debug("Layer outside of bbox. Skipping...")
            continue
        if skip_layer(layer):
            continue

        use_dissolve = (layer.blend_mode == BlendMode.DISSOLVE)

        if isinstance(layer, psd_tools.Group):
            # if group's blend mode is PASS_THROUGH,
            # then its layers should be merged as if they aren't in group
            if layer.blend_mode == BlendMode.PASS_THROUGH:
                layer_image = merge_layers(
                    layer.layers, respect_visibility, skip_layer,
                    result.copy(), bbox
                )
            else:
                layer_image = merge_layers(
                    layer.layers, respect_visibility, skip_layer, None, bbox
                )

            x = y = 0
            background = result
        else:
            layer_image = layer.as_PIL()

            x, y = layer_bbox.x1 - bbox.x1, layer_bbox.y1 - bbox.y1
            w, h = layer_image.size
            # checking if layer is inside the area of rendering...
            if x < 0 or y < 0 or x + w > bbox.width or y + h > bbox.height:
                # layer doesn't fit the bbox
                crop_bbox = (
                    max(-x, 0),             max(-y, 0),
                    min(w, bbox.width - x), min(h, bbox.height - y)
                )

                logger.debug("Cropping layer to (%s, %s, %s, %s)...", *crop_bbox)

                layer_image = layer_image.crop(crop_bbox)
                x += crop_bbox[0]
                y += crop_bbox[1]
                w, h = layer_image.size

            if result.size != layer_image.size:
                background = result.crop((x, y, x + w, y + h))
            else:
                background = result

            # layer_image = pil_support.apply_opacity(layer_image, layer.fill)
            # layer_image = _blend(background, layer_image, None, use_dissolve)

        if _blend_modes is not None:
            # getting a blending function based on layers' blend mode...
            # if the given mode is not implemented, Normal mode will be used instead
            func = blend_functions.get(layer.blend_mode)
            if func is None and layer.blend_mode not in (BlendMode.NORMAL,
                                                         BlendMode.PASS_THROUGH):
                logger.warning(
                    "Blend mode is not implemented: %s. Using NORMAL mode...",
                    BlendMode.name_of(layer.blend_mode)
                )
            else:
                logger.debug(
                    "Blending using %s mode...", BlendMode.name_of(layer.blend_mode)
                )
        else:
            func = None
            use_dissolve = False

        layer_image = pil_support.apply_opacity(layer_image, layer.opacity)
        layer_image = _blend(background, layer_image, func, use_dissolve)

        if layer_image.size == result.size:
            result = layer_image
        else:
            result.paste(layer_image, (x, y))

    return result
예제 #16
0
def merge_layers(layers,
                 respect_visibility=True,
                 skip_layer=lambda layer: False,
                 bbox=None):
    """
    Merges layers together (the first layer is on top).

    By default hidden layers are not rendered;
    pass ``respect_visibility=False`` to render them.

    In order to skip some layers pass ``skip_layer`` function which
    should take ``layer` as an argument and return True or False.

    If ``bbox`` is not None, it should be a 4-tuple with coordinates;
    returned image will be restricted to this rectangle.

    This is highly experimental.
    """

    # FIXME: this currently assumes PIL
    from PIL import Image

    if bbox is None:
        bbox = combined_bbox(layers)

    if bbox is None:
        return None

    result = Image.new(
        "RGBA",
        (bbox.width, bbox.height),
        color=(255, 255, 255, 0
               )  # fixme: transparency calculation is incorrect
    )

    for layer in reversed(layers):

        if layer is None:
            continue

        if layer.bbox.width == 0 and layer.bbox.height == 0:
            continue

        if skip_layer(layer):
            continue

        if not layer.visible and respect_visibility:
            continue

        if isinstance(layer, psd_tools.Group):
            layer_image = merge_layers(layer.layers, respect_visibility,
                                       skip_layer)
        else:
            layer_image = layer.as_PIL()

        layer_image = pil_support.apply_opacity(layer_image, layer.opacity)

        x, y = layer.bbox.x1 - bbox.x1, layer.bbox.y1 - bbox.y1
        w, h = layer_image.size

        if x < 0 or y < 0:  # image doesn't fit the bbox
            x_overflow = -min(x, 0)
            y_overflow = -min(y, 0)
            logger.debug("cropping.. (%s, %s)", x_overflow, y_overflow)
            layer_image = layer_image.crop((x_overflow, y_overflow, w, h))
            x += x_overflow
            y += y_overflow

        if w + x > bbox.width or h + y > bbox.height:
            # FIXME
            logger.debug("cropping..")

        if layer.blend_mode == BlendMode.NORMAL:
            if layer_image.mode == 'RGBA':
                tmp = Image.new("RGBA", result.size, color=(255, 255, 255, 0))
                tmp.paste(layer_image, (x, y))
                result = Image.alpha_composite(result, tmp)
            elif layer_image.mode == 'RGB':
                result.paste(layer_image, (x, y))
            else:
                logger.warning(
                    "layer image mode is unsupported for merging: %s",
                    layer_image.mode)
                continue
        else:
            logger.warning("Blend mode is not implemented: %s",
                           BlendMode.name_of(layer.blend_mode))
            continue

    return result
예제 #17
0
def merge_layers(layers, respect_visibility=True, skip_layer=lambda layer: False, bbox=None):
    """
    Merges layers together (the first layer is on top).

    By default hidden layers are not rendered;
    pass ``respect_visibility=False`` to render them.

    In order to skip some layers pass ``skip_layer`` function which
    should take ``layer` as an argument and return True or False.

    If ``bbox`` is not None, it should be a 4-tuple with coordinates;
    returned image will be restricted to this rectangle.

    This is highly experimental.
    """

    # FIXME: this currently assumes PIL
    from PIL import Image

    if bbox is None:
        bbox = combined_bbox(layers)

    if bbox is None:
        return None

    result = Image.new(
        "RGBA",
        (bbox.width, bbox.height),
        color=(255, 255, 255, 0)  # fixme: transparency calculation is incorrect
    )

    for layer in reversed(layers):

        if layer is None:
            continue

        if layer.bbox.width == 0 and layer.bbox.height == 0:
            continue

        if skip_layer(layer):
            continue

        if not layer.visible and respect_visibility:
            continue

        if isinstance(layer, psd_tools.Group):
            layer_image = merge_layers(layer.layers, respect_visibility, skip_layer)
        else:
            layer_image = layer.as_PIL()

        layer_image = pil_support.apply_opacity(layer_image, layer.opacity)

        x, y = layer.bbox.x1 - bbox.x1, layer.bbox.y1 - bbox.y1
        w, h = layer_image.size

        if x < 0 or y < 0:  # image doesn't fit the bbox
            x_overflow = - min(x, 0)
            y_overflow = - min(y, 0)
            logger.debug("cropping.. (%s, %s)", x_overflow, y_overflow)
            layer_image = layer_image.crop((x_overflow, y_overflow, w, h))
            x += x_overflow
            y += y_overflow

        if w+x > bbox.width or h+y > bbox.height:
            # FIXME
            logger.debug("cropping..")

        if layer.blend_mode == BlendMode.NORMAL:
            if layer_image.mode == 'RGBA':
                tmp = Image.new("RGBA", result.size, color=(255, 255, 255, 0))
                tmp.paste(layer_image, (x, y))
                result = Image.alpha_composite(result, tmp)
            elif layer_image.mode == 'RGB':
                result.paste(layer_image, (x,y))
            else:
                logger.warning("layer image mode is unsupported for merging: %s", layer_image.mode)
                continue
        else:
            logger.warning("Blend mode is not implemented: %s", BlendMode.name_of(layer.blend_mode))
            continue

    return result
예제 #18
0
파일: layers.py 프로젝트: zective/psd-tools
 def blend_mode(self, value):
     self._record.blend_mode = BlendMode(value)