def create_fill(layer): from PIL import Image mode = get_pil_mode(layer._psd.color_mode, True) size = (layer.width, layer.height) fill_image = None stroke = layer.tagged_blocks.get_data(Tag.VECTOR_STROKE_DATA) # Apply fill. if Tag.VECTOR_STROKE_CONTENT_DATA in layer.tagged_blocks: setting = layer.tagged_blocks.get_data(Tag.VECTOR_STROKE_CONTENT_DATA) if stroke and bool(stroke.get('fillEnabled', True)) is False: fill_image = Image.new(mode, size) elif Enum.Pattern in setting: fill_image = draw_pattern_fill(size, layer._psd, setting) elif Key.Gradient in setting: fill_image = draw_gradient_fill(size, setting) else: fill_image = draw_solid_color_fill(size, setting) elif Tag.SOLID_COLOR_SHEET_SETTING in layer.tagged_blocks: setting = layer.tagged_blocks.get_data(Tag.SOLID_COLOR_SHEET_SETTING) fill_image = draw_solid_color_fill(size, setting) elif Tag.PATTERN_FILL_SETTING in layer.tagged_blocks: setting = layer.tagged_blocks.get_data(Tag.PATTERN_FILL_SETTING) fill_image = draw_pattern_fill(size, layer._psd, setting) elif Tag.GRADIENT_FILL_SETTING in layer.tagged_blocks: setting = layer.tagged_blocks.get_data(Tag.GRADIENT_FILL_SETTING) fill_image = draw_gradient_fill(size, setting) return fill_image
def create_fill(layer): from PIL import Image mode = get_pil_mode(layer._psd.color_mode, True) image = Image.new(mode, (layer.width, layer.height)) if 'SOLID_COLOR_SHEET_SETTING' in layer.tagged_blocks: setting = layer.tagged_blocks.get_data('SOLID_COLOR_SHEET_SETTING') draw_solid_color_fill(image, setting) elif 'PATTERN_FILL_SETTING' in layer.tagged_blocks: setting = layer.tagged_blocks.get_data('PATTERN_FILL_SETTING') draw_pattern_fill(image, layer._psd, setting) elif 'GRADIENT_FILL_SETTING' in layer.tagged_blocks: setting = layer.tagged_blocks.get_data('GRADIENT_FILL_SETTING') draw_gradient_fill(image, setting, blend=False) return image
def composite_pil(layer, color, alpha, viewport, layer_filter, force, as_layer=False): from PIL import Image from psd_tools.api.pil_io import get_pil_mode from psd_tools.api.numpy_io import has_transparency UNSUPPORTED_MODES = { ColorMode.DUOTONE, ColorMode.LAB, } color_mode = getattr(layer, '_psd', layer).color_mode if color_mode in UNSUPPORTED_MODES: logger.warning('Unsupported blending color space: %s' % (color_mode)) color, _, alpha = composite(layer, color=color, alpha=alpha, viewport=viewport, layer_filter=layer_filter, force=force, as_layer=as_layer) mode = get_pil_mode(color_mode) if mode == 'P': mode = 'RGB' # Skip only when there is a preview image and it has no alpha. skip_alpha = not force and ( color_mode not in (ColorMode.GRAYSCALE, ColorMode.RGB) or (layer.kind == 'psdimage' and layer.has_preview() and not has_transparency(layer))) logger.debug('Skipping alpha: %g' % skip_alpha) if not skip_alpha: color = np.concatenate((color, alpha), 2) mode += 'A' if mode in ('1', 'L'): color = color[:, :, 0] if color.shape[0] == 0 or color.shape[1] == 0: return None return Image.fromarray((255 * color).astype(np.uint8), mode)
def test_get_pil_mode(mode, alpha, expected): assert pil_io.get_pil_mode(mode, alpha) == expected
def compose(layers, bbox=None, layer_filter=None, color=None): """ 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 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 None if bbox is None: bbox = extract_bbox(valid_layers) if bbox == (0, 0, 0, 0): return None # Alpha must be forced to correctly blend. mode = get_pil_mode(valid_layers[0]._psd.color_mode, True) result = Image.new( mode, (bbox[2] - bbox[0], bbox[3] - bbox[1]), color=color, ) initial_layer = True for layer in valid_layers: if intersect(layer.bbox, bbox) == (0, 0, 0, 0): continue image = layer.compose() if image is None: continue logger.debug('Composing %s' % layer) offset = (layer.left - bbox[0], layer.top - bbox[1]) if initial_layer: result.paste(image, offset) initial_layer = False else: result = _blend(result, image, offset) return result
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