예제 #1
0
def _apply_opacity(layer_image, layer):
    layer_opacity = layer.opacity
    if layer.has_tag(TaggedBlock.BLEND_FILL_OPACITY):
        layer_opacity *= layer.get_tag(TaggedBlock.BLEND_FILL_OPACITY)
    if layer_opacity == 255:
        return layer_image
    return pil_support.apply_opacity(layer_image, layer_opacity)
예제 #2
0
 def final_image_as_PIL(self):
     """
     blank_pic = new_blank_pic_as_PIL(self.bbox.width, self.bbox.height)
     if not self.visible:
         return blank_pic
     try:
         final_pic = Image.blend(blank_pic, self.image, self.opacity/255)
         return final_pic
     except ValueError:
         print(self.bbox)
         return blank_pic
     """
     if not self.visible:
         blank_pic = new_blank_pic_as_PIL(self.bbox.width, self.bbox.height)
         return blank_pic
     return pil_support.apply_opacity(self._image, self.opacity)
예제 #3
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
예제 #4
0
def compose(layers, respect_visibility=True, ignore_blend_mode=True,
            skip_layer=lambda layer: False, bbox=None):
    """
    Compose layers to a single ``PIL.Image`` (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.

    Adjustment and layer effects are ignored.

    This is experimental.

    :param layers: a layer, or an iterable of layers
    :param respect_visibility: Take visibility flag into account
    :param ignore_blend_mode: Ignore blending mode
    :param skip_layer: skip composing the given layer if returns True
    :rtype: `PIL.Image`
    """

    # FIXME: this currently assumes PIL
    if isinstance(layers, psd_tools.user_api.layers._RawLayer):
        layers = [layers]

    if bbox is None:
        bbox = combined_bbox(layers)

    if bbox.is_empty():
        return None

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

    for layer in reversed(layers):
        if skip_layer(layer) or not layer.has_box() or (
                not layer.visible and respect_visibility):
            continue

        if layer.is_group():
            layer_image = layer.as_PIL(
                respect_visibility=respect_visibility,
                ignore_blend_mode=ignore_blend_mode,
                skip_layer=skip_layer)
        else:
            layer_image = layer.as_PIL()

        if not layer_image:
            continue

        if not ignore_blend_mode and layer.blend_mode != "normal":
            logger.warning("Blend mode is not implemented: %s",
                           layer.blend_mode)
            continue

        clip_image = None
        if len(layer.clip_layers):
            clip_box = combined_bbox(layer.clip_layers)
            if not clip_box.is_empty():
                intersect = clip_box.intersect(layer.bbox)
                if not intersect.is_empty():
                    clip_image = compose(
                        layer.clip_layers, respect_visibility,
                        ignore_blend_mode, skip_layer)
                    clip_image = clip_image.crop(
                        intersect.offset((clip_box.x1, clip_box.y1)))
                    clip_mask = layer_image.crop(
                        intersect.offset((layer.bbox.x1, layer.bbox.y1)))

        layer_opacity = layer.opacity
        if layer.has_tag(TaggedBlock.BLEND_FILL_OPACITY):
            layer_opacity *= layer.get_tag(TaggedBlock.BLEND_FILL_OPACITY)
        layer_image = pil_support.apply_opacity(layer_image, layer_opacity)
        layer_image = _apply_coloroverlay(layer, layer_image)

        layer_offset = layer.bbox.offset((bbox.x1, bbox.y1))
        mask = None
        if layer.has_mask():
            mask_box = layer.mask.bbox
            if not layer.mask.disabled and not mask_box.is_empty():
                mask_color = layer.mask.background_color
                mask = Image.new("L", layer_image.size, color=(mask_color,))
                mask.paste(
                    layer.mask.as_PIL(),
                    mask_box.offset((layer.bbox.x1, layer.bbox.y1))
                )

        if layer_image.mode == 'RGBA':
            tmp = Image.new("RGBA", result.size, color=(255, 255, 255, 0))
            tmp.paste(layer_image, layer_offset, mask=mask)
            result = Image.alpha_composite(result, tmp)
        elif layer_image.mode == 'RGB':
            result.paste(layer_image, layer_offset, mask=mask)
        else:
            logger.warning(
                "layer image mode is unsupported for merging: %s",
                layer_image.mode)
            continue

        if clip_image is not None:
            offset = (intersect.x1 - bbox.x1, intersect.y1 - bbox.y1)
            if clip_image.mode == 'RGBA':
                tmp = Image.new("RGBA", result.size, color=(255, 255, 255, 0))
                tmp.paste(clip_image, offset, mask=clip_mask)
                result = Image.alpha_composite(result, tmp)
            elif clip_image.mode == 'RGB':
                result.paste(clip_image, offset, mask=clip_mask)

    return result
예제 #5
0
def merge_layers(layers,
                 respect_visibility=True,
                 ignore_blend_mode=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 experimental.
    """

    # FIXME: this currently assumes PIL
    from PIL import Image

    if bbox is None:
        bbox = combined_bbox(layers)

    if bbox.is_empty():
        return None

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

    for layer in reversed(layers):
        if skip_layer(layer) or not layer.has_box() or (not layer.visible and
                                                        respect_visibility):
            continue

        if layer.is_group():
            layer_image = layer.as_PIL(respect_visibility=respect_visibility,
                                       ignore_blend_mode=ignore_blend_mode,
                                       skip_layer=skip_layer)
        else:
            layer_image = layer.as_PIL()

        if not layer_image:
            continue

        if not ignore_blend_mode and layer.blend_mode != "normal":
            logger.warning("Blend mode is not implemented: %s",
                           layer.blend_mode)
            continue

        clip_image = None
        if len(layer.clip_layers):
            clip_box = combined_bbox(layer.clip_layers)
            if not clip_box.is_empty():
                intersect = clip_box.intersect(layer.bbox)
                if not intersect.is_empty():
                    clip_image = merge_layers(layer.clip_layers,
                                              respect_visibility,
                                              ignore_blend_mode, skip_layer)
                    clip_image = clip_image.crop(
                        intersect.offset((clip_box.x1, clip_box.y1)))
                    clip_mask = layer_image.crop(
                        intersect.offset((layer.bbox.x1, layer.bbox.y1)))

        layer_image = pil_support.apply_opacity(layer_image, layer.opacity)
        layer_offset = layer.bbox.offset((bbox.x1, bbox.y1))
        mask = None
        if layer.has_mask():
            mask_box = layer.mask.bbox
            if not layer.mask.disabled and not mask_box.is_empty():
                mask = Image.new("L", layer_image.size, color=(0, ))
                mask.paste(layer.mask.as_PIL(),
                           mask_box.offset((layer.bbox.x1, layer.bbox.y1)))

        if layer_image.mode == 'RGBA':
            tmp = Image.new("RGBA", result.size, color=(255, 255, 255, 0))
            tmp.paste(layer_image, layer_offset, mask=mask)
            result = Image.alpha_composite(result, tmp)
        elif layer_image.mode == 'RGB':
            result.paste(layer_image, layer_offset, mask=mask)
        else:
            logger.warning("layer image mode is unsupported for merging: %s",
                           layer_image.mode)
            continue

        if clip_image is not None:
            offset = (intersect.x1 - bbox.x1, intersect.y1 - bbox.y1)
            if clip_image.mode == 'RGBA':
                tmp = Image.new("RGBA", result.size, color=(255, 255, 255, 0))
                tmp.paste(clip_image, offset, mask=clip_mask)
                result = Image.alpha_composite(result, tmp)
            elif clip_image.mode == 'RGB':
                result.paste(clip_image, offset, mask=clip_mask)

    return result
예제 #6
0
def merge_layers(layers,
                 respect_visibility=True,
                 ignore_blend_mode=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 experimental.
    """

    # FIXME: this currently assumes PIL
    from PIL import Image

    if bbox is None:
        bbox = combined_bbox(layers)

    if bbox.is_empty():
        return None

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

    for layer in reversed(layers):
        if skip_layer(layer) or not layer.has_box() or (not layer.visible and
                                                        respect_visibility):
            continue

        if layer.is_group():
            layer_image = layer.as_PIL(respect_visibility=respect_visibility,
                                       ignore_blend_mode=ignore_blend_mode,
                                       skip_layer=skip_layer)
        else:
            layer_image = layer.as_PIL()

        if not layer_image:
            continue

        if not ignore_blend_mode and layer.blend_mode != "normal":
            logger.warning("Blend mode is not implemented: %s",
                           layer.blend_mode)
            continue

        clip_mask_exists = False
        if len(layer.clip_layers):
            clip_box = combined_bbox(layer.clip_layers)
            if not clip_box.is_empty():
                intersect = clip_box.intersect(layer.bbox)
                if not intersect.is_empty():
                    clip_image = merge_layers(layer.clip_layers,
                                              respect_visibility,
                                              ignore_blend_mode, skip_layer)
                    clip_image = clip_image.crop(
                        intersect.offset((clip_box.x1, clip_box.y1)))
                    clip_mask = layer_image.crop(
                        intersect.offset((layer.bbox.x1, layer.bbox.y1)))
                    clip_mask_exists = True

        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_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

        if clip_mask_exists:
            location = (intersect.x1 - bbox.x1, intersect.y1 - bbox.y1)
            if clip_image.mode == 'RGBA':
                tmp = Image.new("RGBA", result.size, color=(255, 255, 255, 0))
                tmp.paste(clip_image, location, mask=clip_mask)
                result = Image.alpha_composite(result, tmp)
            elif clip_image.mode == 'RGB':
                result.paste(clip_image, location, mask=clip_mask)

    return result
예제 #7
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
예제 #8
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
예제 #9
0
 def final_image_as_PIL(self):
     if not self.visible:
         blank_pic = new_blank_pic_as_PIL(self.bbox.width, self.bbox.height)
         return blank_pic
     return pil_support.apply_opacity(self.image, self.opacity)