Source code for psd_tools.api.effects

"""
Effects module.
"""
from __future__ import absolute_import, unicode_literals

import logging

from psd_tools.constants import Resource, Tag
from psd_tools.psd.descriptor import Descriptor, List
from psd_tools.terminology import Key, Klass
from psd_tools.utils import new_registry

logger = logging.getLogger(__name__)

_TYPES, register = new_registry()


[docs] class Effects(object): """ List-like effects. """ def __init__(self, layer): self._data = None for tag in ( Tag.OBJECT_BASED_EFFECTS_LAYER_INFO, Tag.OBJECT_BASED_EFFECTS_LAYER_INFO_V0, Tag.OBJECT_BASED_EFFECTS_LAYER_INFO_V1, ): if tag in layer.tagged_blocks: self._data = layer.tagged_blocks.get_data(tag) break self._items = [] for key in self._data or []: value = self._data[key] if not isinstance(value, List): value = [value] for item in value: if not (isinstance(item, Descriptor) and item.get(Key.Enabled)): continue kls = _TYPES.get(item.classID) assert kls is not None, "kls not found for %r" % item.classID self._items.append(kls(item, layer._psd.image_resources)) @property def scale(self): """Scale value.""" return self._data.get(Key.Scale).value if self._data else None @property def enabled(self): """Whether if all the effects are enabled. :rtype: bool """ return bool(self._data.get(b"masterFXSwitch")) if self._data else False @property def items(self): return self._items
[docs] def find(self, name): """Iterate effect items by name.""" if not self.enabled: return KLASS = {kls.__name__.lower(): kls for kls in _TYPES.values()} for item in self: if isinstance(item, KLASS.get(name.lower(), None)): yield item
def __len__(self): return self._items.__len__() def __iter__(self): return self._items.__iter__() def __getitem__(self, key): return self._items.__getitem__(key) # def __setitem__(self, key, value): # return self._items.__setitem__(key, value) # def __delitem__(self, key): # return self._items.__delitem__(key) def __repr__(self): return "%s(%s)" % ( self.__class__.__name__, " ".join(x.__class__.__name__.lower() for x in self) if self._data else "", )
class _Effect(object): """Base Effect class.""" def __init__(self, value, image_resources): self.value = value self._image_resources = image_resources @property def enabled(self): """Whether if the effect is enabled.""" return bool(self.value.get(Key.Enabled)) @property def present(self): """Whether if the effect is present in Photoshop UI.""" return bool(self.value.get(b"present")) @property def shown(self): """Whether if the effect is shown in dialog.""" return bool(self.value.get(b"showInDialog")) @property def opacity(self): """Layer effect opacity in percentage.""" return self.value.get(Key.Opacity).value def __repr__(self): return self.__class__.__name__ def _repr_pretty_(self, p, cycle): if cycle: return self.__repr__() p.text(self.__repr__()) class _ColorMixin(object): @property def color(self): """Color.""" return self.value.get(Key.Color) @property def blend_mode(self): """Effect blending mode.""" return self.value.get(Key.Mode).enum class _ChokeNoiseMixin(_ColorMixin): @property def choke(self): """Choke level.""" return self.value.get(Key.ChokeMatte).value @property def size(self): """Size in pixels.""" return self.value.get(Key.Blur).value @property def noise(self): """Noise level.""" return self.value.get(Key.Noise).value @property def anti_aliased(self): """Angi-aliased.""" return bool(self.value.get(Key.AntiAlias)) @property def contour(self): """Contour configuration.""" return self.value.get(Key.TransferSpec) class _AngleMixin(object): @property def use_global_light(self): """Using global light.""" return bool(self.value.get(Key.UseGlobalAngle)) @property def angle(self): """Angle value.""" if self.use_global_light: return self._image_resources.get_data(Resource.GLOBAL_ANGLE, 30.0) return self.value.get(Key.LocalLightingAngle).value class _GradientMixin(object): @property def gradient(self): """Gradient configuration.""" return self.value.get(Key.Gradient) @property def angle(self): """Angle value.""" return self.value.get(Key.Angle).value @property def type(self): """ Gradient type, one of `linear`, `radial`, `angle`, `reflected`, or `diamond`. """ return self.value.get(Key.Type).enum @property def reversed(self): """Reverse flag.""" return bool(self.value.get(Key.Reverse)) @property def dithered(self): """Dither flag.""" return bool(self.value.get(Key.Dither)) @property def offset(self): """Offset value.""" return self.value.get(Key.Offset) class _PatternMixin(object): @property def pattern(self): """Pattern config.""" # TODO: Expose nested property. return self.value.get(b"Ptrn") # Enum.Pattern. Seems a bug. @property def linked(self): """Linked.""" return self.value.get(b"Lnkd") # Enum.Linked. Seems a bug. @property def angle(self): """Angle value.""" return self.value.get(Key.Angle).value @property def phase(self): """Phase value in Point.""" return self.value.get(b"phase") class _ShadowEffect(_Effect, _ChokeNoiseMixin, _AngleMixin): """Base class for shadow effect.""" @property def distance(self): """Distance.""" return self.value.get(Key.Distance).value class _GlowEffect(_Effect, _ChokeNoiseMixin, _GradientMixin): """Base class for glow effect.""" @property def glow_type(self): """Glow type.""" return self.value.get(Key.GlowTechnique).enum @property def quality_range(self): """Quality range.""" return self.value.get(Key.InputRange).value @property def quality_jitter(self): """Quality jitter""" return self.value.get(Key.ShadingNoise).value class _OverlayEffect(_Effect): pass class _AlignScaleMixin(object): @property def blend_mode(self): """Effect blending mode.""" return self.value.get(Key.Mode).enum @property def scale(self): """Scale value.""" return self.value.get(Key.Scale).value @property def aligned(self): """Aligned.""" return bool(self.value.get(Key.Alignment))
[docs] @register(Klass.DropShadow.value) class DropShadow(_ShadowEffect): @property def layer_knocks_out(self): """Layers are knocking out.""" return bool(self.value.get(b"layerConceals"))
[docs] @register(Klass.InnerShadow.value) class InnerShadow(_ShadowEffect): pass
[docs] @register(Klass.OuterGlow.value) class OuterGlow(_GlowEffect): @property def spread(self): return self.value.get(Key.ShadingNoise).value
[docs] @register(Klass.InnerGlow.value) class InnerGlow(_GlowEffect): @property def glow_source(self): """Elements source.""" return self.value.get(Key.InnerGlowSource).enum
[docs] @register(Klass.SolidFill.value) class ColorOverlay(_OverlayEffect, _ColorMixin): pass
[docs] @register(b"GrFl") # Equal to Enum.GradientFill. This seems a bug. class GradientOverlay(_OverlayEffect, _AlignScaleMixin, _GradientMixin): pass
[docs] @register(b"patternFill") class PatternOverlay(_OverlayEffect, _AlignScaleMixin, _PatternMixin): pass
[docs] @register(Klass.FrameFX.value) class Stroke(_Effect, _ColorMixin, _PatternMixin, _GradientMixin): @property def position(self): """ Position of the stroke, InsetFrame, OutsetFrame, or CenteredFrame. """ return self.value.get(Key.Style).enum @property def fill_type(self): """Fill type, SolidColor, Gradient, or Pattern.""" return self.value.get(Key.PaintType).enum @property def size(self): """Size value.""" return self.value.get(Key.SizeKey).value @property def overprint(self): """Overprint flag.""" return bool(self.value.get(b"overprint"))
[docs] @register(Klass.BevelEmboss.value) class BevelEmboss(_Effect, _AngleMixin): @property def highlight_mode(self): """Highlight blending mode.""" return self.value.get(Key.HighlightMode).enum @property def highlight_color(self): """Highlight color value.""" return self.value.get(Key.HighlightColor) @property def highlight_opacity(self): """Highlight opacity value.""" return self.value.get(Key.HighlightOpacity).value @property def shadow_mode(self): """Shadow blending mode.""" return self.value.get(Key.ShadowMode).enum @property def shadow_color(self): """Shadow color value.""" return self.value.get(Key.ShadowColor) @property def shadow_opacity(self): """Shadow opacity value.""" return self.value.get(Key.ShadowOpacity).value @property def bevel_type(self): """Bevel type, one of `SoftMatte`, `HardLight`, `SoftLight`.""" return self.value.get(Key.BevelTechnique).enum @property def bevel_style(self): """ Bevel style, one of `OuterBevel`, `InnerBevel`, `Emboss`, `PillowEmboss`, or `StrokeEmboss`. """ return self.value.get(Key.BevelStyle).enum @property def altitude(self): """Altitude value.""" return self.value.get(Key.LocalLightingAltitude).value @property def depth(self): """Depth value.""" return self.value.get(Key.StrengthRatio).value @property def size(self): """Size value in pixel.""" return self.value.get(Key.Blur).value @property def direction(self): """Direction, either `StampIn` or `StampOut`.""" return self.value.get(Key.BevelDirection).enum @property def contour(self): """Contour configuration.""" return self.value.get(Key.TransferSpec) @property def anti_aliased(self): """Anti-aliased.""" return bool(self.value.get(b"antialiasGloss")) @property def soften(self): """Soften value.""" return self.value.get(Key.Softness).value @property def use_shape(self): """Using shape.""" return bool(self.value.get(b"useShape")) @property def use_texture(self): """Using texture.""" return bool(self.value.get(b"useTexture"))
[docs] @register(Klass.ChromeFX.value) class Satin(_Effect, _ColorMixin): """Satin effect""" @property def anti_aliased(self): """Anti-aliased.""" return bool(self.value.get(Key.AntiAlias)) @property def inverted(self): """Inverted.""" return bool(self.value.get(Key.Invert)) @property def angle(self): """Angle value.""" return self.value.get(Key.LocalLightingAngle).value @property def distance(self): """Distance value.""" return self.value.get(Key.Distance).value @property def size(self): """Size value in pixel.""" return self.value.get(Key.Blur).value @property def contour(self): """Contour configuration.""" return self.value.get(Key.MappingShape)