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
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
def apply_effect(layer, backdrop, base_image): """Apply effect to the image. ..note: Correct effect order is the following. All the effects are first applied to the original image then blended together. * dropshadow * outerglow * (original) * patternoverlay * gradientoverlay * coloroverlay * innershadow * innerglow * bevelemboss * satin * stroke """ from PIL import ImageChops for effect in layer.effects: if effect.__class__.__name__ == 'PatternOverlay': image = draw_pattern_fill(base_image.size, layer._psd, effect.value) if base_image.mode.endswith('A'): alpha = base_image.getchannel('A') if image.mode.endswith('A'): alpha = ImageChops.darker(alpha, image.getchannel('A')) image.putalpha(alpha) backdrop = blend(backdrop, image, (0, 0), effect.blend_mode) for effect in layer.effects: if effect.__class__.__name__ == 'GradientOverlay': image = draw_gradient_fill(base_image.size, effect.value) if base_image.mode.endswith('A'): alpha = base_image.getchannel('A') if image.mode.endswith('A'): alpha = ImageChops.darker(alpha, image.getchannel('A')) image.putalpha(alpha) backdrop = blend(backdrop, image, (0, 0), effect.blend_mode) for effect in layer.effects: if effect.__class__.__name__ == 'ColorOverlay': image = draw_solid_color_fill(base_image.size, effect.value) if base_image.mode.endswith('A'): alpha = base_image.getchannel('A') if image.mode.endswith('A'): alpha = ImageChops.darker(alpha, image.getchannel('A')) image.putalpha(alpha) backdrop = blend(backdrop, image, (0, 0), effect.blend_mode) for effect in layer.effects: if effect.__class__.__name__ == 'Stroke': from PIL import ImageOps if layer.has_vector_mask(): alpha = draw_vector_mask(layer) elif base_image.mode.endswith('A'): alpha = base_image.getchannel('A') else: alpha = base_image.convert('L') alpha.info['offset'] = base_image.info['offset'] flat = alpha.getextrema()[0] < 255 # Expand the image size setting = effect.value size = int(setting.get(Key.SizeKey)) offset = backdrop.info['offset'] backdrop = ImageOps.expand(backdrop, size) backdrop.info['offset'] = tuple(x - size for x in offset) offset = alpha.info['offset'] alpha = ImageOps.expand(alpha, size) alpha.info['offset'] = tuple(x - size for x in offset) if not layer.has_vector_mask() and setting.get( Key.Style).enum == Enum.InsetFrame and flat: image = create_stroke_effect(alpha, setting, layer._psd, True) backdrop.paste(image) else: image = create_stroke_effect(alpha, setting, layer._psd) backdrop = blend(backdrop, image, (0, 0), effect.blend_mode) return backdrop