Beispiel #1
0
def compose_layer(layer, force=False, **kwargs):
    """Compose a single layer with pixels."""
    from PIL import Image, ImageChops
    assert layer.bbox != (0, 0, 0, 0), 'Layer bbox is (0, 0, 0, 0)'

    image = layer.topil(**kwargs)
    if image is None or force:
        texture = create_fill(layer)
        if texture is not None:
            image = texture
    if image is None:
        return image

    # TODO: Group should have the following too.

    # Apply vector mask.
    if layer.has_vector_mask() and (force or not layer.has_pixels()):
        vector_mask = draw_vector_mask(layer)
        if image.mode.endswith('A'):
            offset = vector_mask.info['offset']
            vector_mask = ImageChops.darker(image.getchannel('A'), vector_mask)
            vector_mask.info['offset'] = offset
        image.putalpha(vector_mask)

        # Apply stroke.
        if layer.has_stroke() and layer.stroke.enabled:
            image = draw_stroke(image, layer, vector_mask)

    # Apply mask.
    image = apply_mask(layer, image)

    # Apply layer fill effects.
    effect_base = image.copy()
    apply_opacity(image,
                  layer.tagged_blocks.get_data(Tag.BLEND_FILL_OPACITY, 255))
    image = apply_effect(layer, image, effect_base)

    # Clip layers.
    if layer.has_clip_layers():
        clip_box = Group.extract_bbox(layer.clip_layers)
        offset = image.info.get('offset', layer.offset)
        bbox = offset + (offset[0] + image.width, offset[1] + image.height)
        if intersect(bbox, clip_box) != (0, 0, 0, 0):
            clip_image = compose(layer.clip_layers,
                                 bbox=bbox,
                                 context=image.copy())
            if image.mode.endswith('A'):
                mask = image.getchannel('A')
            else:
                mask = Image.new('L', image.size, 255)
            if clip_image.mode.endswith('A'):
                mask = ImageChops.darker(clip_image.getchannel('A'), mask)
            clip_image.putalpha(mask)
            image = blend(image, clip_image, (0, 0))

    # Apply opacity.
    apply_opacity(image, layer.opacity)

    return image
Beispiel #2
0
def test_compose_artboard():
    psd = PSDImage.open(full_name('artboard.psd'))
    document_image = psd.compose()
    assert document_image.size == psd.size
    artboard = psd[0]
    artboard_image = artboard.compose()
    assert artboard_image.size == artboard.size
    assert artboard.size != Group.extract_bbox(artboard)
Beispiel #3
0
    def _init(self):
        """Initialize layer structure."""
        group_stack = [self]
        clip_stack = []
        last_layer = None

        for record, channels in self._record._iter_layers():
            current_group = group_stack[-1]

            blocks = record.tagged_blocks
            end_of_group = False
            divider = blocks.get_data('SECTION_DIVIDER_SETTING', None)
            divider = blocks.get_data('NESTED_SECTION_DIVIDER_SETTING', divider)
            if divider is not None:
                if divider.kind == SectionDivider.BOUNDING_SECTION_DIVIDER:
                    layer = Group(self, None, None, current_group)
                    group_stack.append(layer)
                elif divider.kind in (SectionDivider.OPEN_FOLDER, SectionDivider.CLOSED_FOLDER):
                    layer = group_stack.pop()
                    assert layer is not self
                    layer._record = record
                    layer._channels = channels
                    end_of_group = True
            elif ('TYPE_TOOL_OBJECT_SETTING' in blocks or 'TYPE_TOOL_INFO' in blocks):
                layer = TypeLayer(self, record, channels, current_group)
            elif (record.flags.pixel_data_irrelevant
                  and ('VECTOR_ORIGINATION_DATA' in blocks or 'VECTOR_MASK_SETTING1' in blocks or 'VECTOR_MASK_SETTING2'
                       in blocks or 'VECTOR_STROKE_DATA' in blocks or 'VECTOR_STROKE_CONTENT_DATA' in blocks)):
                layer = ShapeLayer(self, record, channels, current_group)
            elif ('SMART_OBJECT_LAYER_DATA1' in blocks or 'SMART_OBJECT_LAYER_DATA2' in blocks
                  or 'PLACED_LAYER1' in blocks or 'PLACED_LAYER2' in blocks):
                layer = SmartObjectLayer(self, record, channels, current_group)
            else:
                layer = None
                for key in adjustments.TYPES.keys():
                    if key in blocks:
                        layer = adjustments.TYPES[key](self, record, channels, current_group)
                        break
                # If nothing applies, this is a pixel layer.
                if layer is None:
                    layer = PixelLayer(self, record, channels, current_group)

            if record.clipping == Clipping.NON_BASE:
                clip_stack.append(layer)
            else:
                if clip_stack:
                    last_layer._clip_layers = clip_stack
                clip_stack = []
                if not end_of_group:
                    current_group._layers.append(layer)
                last_layer = layer

        if clip_stack and last_layer:
            last_layer._clip_layers = clip_stack
Beispiel #4
0
def _apply_layer_ops(layer, image, force=False, bbox=None):
    """Apply layer masks, effects, and clipping."""
    from PIL import Image, ImageChops
    # Apply vector mask.
    if layer.has_vector_mask() and (force or not layer.has_pixels()):
        offset = image.info.get('offset', layer.offset)
        mask_box = offset + (offset[0] + image.width, offset[1] + image.height)
        vector_mask = draw_vector_mask(layer, mask_box)
        if image.mode.endswith('A'):
            offset = vector_mask.info['offset']
            vector_mask = ImageChops.darker(image.getchannel('A'), vector_mask)
            vector_mask.info['offset'] = offset
        image.putalpha(vector_mask)

        # Apply stroke.
        if layer.has_stroke() and layer.stroke.enabled:
            image = draw_stroke(image, layer, vector_mask)

    # Apply mask.
    image = apply_mask(layer, image, bbox=bbox)

    # Apply layer fill effects.
    apply_opacity(
        image, layer.tagged_blocks.get_data(Tag.BLEND_FILL_OPACITY, 255)
    )
    if layer.effects.enabled:
        image = apply_effect(layer, image, image.copy())

    # Clip layers.
    if layer.has_clip_layers():
        clip_box = Group.extract_bbox(layer.clip_layers)
        offset = image.info.get('offset', layer.offset)
        bbox = offset + (offset[0] + image.width, offset[1] + image.height)
        if intersect(bbox, clip_box) != (0, 0, 0, 0):
            clip_image = compose(
                layer.clip_layers,
                force=force,
                bbox=bbox,
                context=image.copy()
            )
            if image.mode.endswith('A'):
                mask = image.getchannel('A')
            else:
                mask = Image.new('L', image.size, 255)
            if clip_image.mode.endswith('A'):
                mask = ImageChops.darker(clip_image.getchannel('A'), mask)
            clip_image.putalpha(mask)
            image = blend(image, clip_image, (0, 0))

    # Apply opacity.
    apply_opacity(image, layer.opacity)

    return image
Beispiel #5
0
def test_group_extract_bbox():
    psd = PSDImage.open(full_name('hidden-groups.psd'))
    assert Group.extract_bbox(psd[1:], False) == (40, 72, 83, 134)
    assert Group.extract_bbox(psd[1:], True) == (25, 34, 83, 134)
Beispiel #6
0
    def _init(self):
        """Initialize layer structure."""
        group_stack = [self]
        clip_stack = []
        last_layer = None

        for record, channels in self._record._iter_layers():
            current_group = group_stack[-1]

            blocks = record.tagged_blocks
            end_of_group = False
            layer = None
            divider = blocks.get_data(Tag.SECTION_DIVIDER_SETTING, None)
            divider = blocks.get_data(Tag.NESTED_SECTION_DIVIDER_SETTING,
                                      divider)
            if divider is not None:
                if divider.kind == SectionDivider.BOUNDING_SECTION_DIVIDER:
                    layer = Group(self, None, None, current_group)
                    group_stack.append(layer)
                elif divider.kind in (SectionDivider.OPEN_FOLDER,
                                      SectionDivider.CLOSED_FOLDER):
                    layer = group_stack.pop()
                    assert layer is not self

                    layer._record = record
                    layer._channels = channels
                    for key in (Tag.ARTBOARD_DATA1, Tag.ARTBOARD_DATA2,
                                Tag.ARTBOARD_DATA3):
                        if key in blocks:
                            layer = Artboard._move(layer)
                    end_of_group = True
                else:
                    logger.warning('Divider %s found.' % divider.kind)
            elif (Tag.TYPE_TOOL_OBJECT_SETTING in blocks
                  or Tag.TYPE_TOOL_INFO in blocks):
                layer = TypeLayer(self, record, channels, current_group)
            elif (Tag.SMART_OBJECT_LAYER_DATA1 in blocks
                  or Tag.SMART_OBJECT_LAYER_DATA2 in blocks
                  or Tag.PLACED_LAYER1 in blocks
                  or Tag.PLACED_LAYER2 in blocks):
                layer = SmartObjectLayer(self, record, channels, current_group)
            else:
                for key in adjustments.TYPES.keys():
                    if key in blocks:
                        layer = adjustments.TYPES[key](self, record, channels,
                                                       current_group)
                        break

            # If nothing applies, this is either a shape or pixel layer.
            if layer is None:
                if (record.flags.pixel_data_irrelevant
                        and (Tag.VECTOR_ORIGINATION_DATA in blocks
                             or Tag.VECTOR_MASK_SETTING1 in blocks
                             or Tag.VECTOR_MASK_SETTING2 in blocks
                             or Tag.VECTOR_STROKE_DATA in blocks
                             or Tag.VECTOR_STROKE_CONTENT_DATA in blocks)):
                    layer = ShapeLayer(self, record, channels, current_group)
                else:
                    layer = PixelLayer(self, record, channels, current_group)

            assert layer is not None

            if record.clipping == Clipping.NON_BASE:
                clip_stack.append(layer)
            else:
                if clip_stack:
                    last_layer._clip_layers = clip_stack
                clip_stack = []
                if not end_of_group:
                    current_group._layers.append(layer)
                last_layer = layer

        if clip_stack and last_layer:
            last_layer._clip_layers = clip_stack
Beispiel #7
0
def compose(layers,
            bbox=None,
            context=None,
            layer_filter=None,
            color=None,
            **kwargs):
    """
    Compose layers to a single :py:class:`PIL.Image`.
    If the layers do not have visible pixels, the function returns `None`.

    Example::

        image = compose([layer1, layer2])

    In order to skip some layers, pass `layer_filter` function which
    should take `layer` as an argument and return `True` to keep the layer
    or return `False` to skip::

        image = compose(
            layers,
            layer_filter=lambda x: x.is_visible() and x.kind == 'type'
        )

    By default, visible layers are composed.

    .. note:: This function is experimental and does not guarantee
        Photoshop-quality rendering.

        Currently the following are ignored:

         - Adjustments layers
         - Layer effects
         - Blending mode (all blending modes become normal)

        Shape drawing is inaccurate if the PSD file is not saved with
        maximum compatibility.

    :param layers: a layer, or an iterable of layers.
    :param bbox: (left, top, bottom, right) tuple that specifies a region to
        compose. By default, all the visible area is composed. The origin
        is at the top-left corner of the PSD document.
    :param context: `PIL.Image` object for the backdrop rendering context. Must
        be used with the correct `bbox` size.
    :param layer_filter: a callable that takes a layer and returns `bool`.
    :param color: background color in `int` or `tuple`.
    :return: :py:class:`PIL.Image` or `None`.
    """
    from PIL import Image

    if not hasattr(layers, '__iter__'):
        layers = [layers]

    def _default_filter(layer):
        return layer.is_visible()

    layer_filter = layer_filter or _default_filter
    valid_layers = [x for x in layers if layer_filter(x)]
    if len(valid_layers) == 0:
        return context

    if bbox is None:
        bbox = Group.extract_bbox(valid_layers)
        if bbox == (0, 0, 0, 0):
            return context

    if context is None:
        mode = get_pil_mode(valid_layers[0]._psd.color_mode, True)
        context = Image.new(
            mode,
            (bbox[2] - bbox[0], bbox[3] - bbox[1]),
            color=color if color is not None else 'white',
        )
        context.putalpha(0)  # Alpha must be forced to correctly blend.
        context.info['offset'] = (bbox[0], bbox[1])

    for layer in valid_layers:
        if intersect(layer.bbox, bbox) == (0, 0, 0, 0):
            continue

        if layer.is_group():
            if layer.blend_mode == BlendMode.PASS_THROUGH:
                context = layer.compose(context=context, bbox=bbox, **kwargs)
                continue
            else:
                image = layer.compose(**kwargs)
        else:
            image = compose_layer(layer, **kwargs)
        if image is None:
            continue

        logger.debug('Composing %s' % layer)
        offset = image.info.get('offset', layer.offset)
        offset = (offset[0] - bbox[0], offset[1] - bbox[1])

        context = blend(context, image, offset, layer.blend_mode)

    return context