Skip to content

Commit

Permalink
Merge pull request #208 from psd-tools/v1.9.7
Browse files Browse the repository at this point in the history
v1.9.7
  • Loading branch information
kyamagu committed Mar 17, 2020
2 parents 489c3a7 + c2ba114 commit e5537dc
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 25 deletions.
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
1.9.7 (2020-03-17)
------------------

- [composite] Fix path operation for merged components.
- [composite] Fix vector mask compositing condition.

1.9.6 (2020-03-16)
------------------

Expand Down
24 changes: 15 additions & 9 deletions src/psd_tools/composite/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,14 +294,7 @@ def _get_group(self, layer, knockout):
def _get_object(self, layer):
"""Get object attributes."""
color, shape = layer.numpy('color'), layer.numpy('shape')
FILL_TAGS = (
Tag.SOLID_COLOR_SHEET_SETTING,
Tag.PATTERN_FILL_SETTING,
Tag.GRADIENT_FILL_SETTING,
Tag.VECTOR_STROKE_CONTENT_DATA,
)
has_fill = any(tag in layer.tagged_blocks for tag in FILL_TAGS)
if (self._force or not layer.has_pixels()) and has_fill:
if (self._force or not layer.has_pixels()) and has_fill(layer):
color, shape = create_fill(layer, layer.bbox)
if shape is None:
shape = np.ones((layer.height, layer.width, 1),
Expand Down Expand Up @@ -372,7 +365,10 @@ def _get_mask(self, layer):
opacity = float(density) / 255.

if layer.has_vector_mask() and (
self._force or not (layer.has_mask() and layer.mask._has_real())
self._force or not layer.has_pixels() or (
not has_fill(layer) and layer.has_mask() and
not layer.mask._has_real()
)
):
shape_v = draw_vector_mask(layer)
shape_v = paste(self._viewport, layer._psd.viewbox, shape_v)
Expand Down Expand Up @@ -478,6 +474,16 @@ def _intersect(a, b):
return inter


def has_fill(layer):
FILL_TAGS = (
Tag.SOLID_COLOR_SHEET_SETTING,
Tag.PATTERN_FILL_SETTING,
Tag.GRADIENT_FILL_SETTING,
Tag.VECTOR_STROKE_CONTENT_DATA,
)
return any(tag in layer.tagged_blocks for tag in FILL_TAGS)


def _union(backdrop, source):
"""Generalized union of shape."""
return backdrop + source - (backdrop * source)
Expand Down
43 changes: 28 additions & 15 deletions src/psd_tools/composite/vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,28 +55,40 @@ def _draw_path(layer, brush=None, pen=None):
height, width = layer._psd.height, layer._psd.width
color = layer.vector_mask.initial_fill_rule
mask = np.full((height, width, 1), color, dtype=np.float32)
first = True

# Group merged path components.
paths = []
for subpath in layer.vector_mask.paths:
plane = _draw_subpath(subpath, width, height, brush, pen)
if subpath.operation == -1:
paths[-1].append(subpath)
else:
paths.append([subpath])

# Apply shape operation.
first = True
for subpath_list in paths:
plane = _draw_subpath(subpath_list, width, height, brush, pen)
assert mask.shape == (height, width, 1)
assert plane.shape == mask.shape
if subpath.operation == 0: # Exclude = Union - Intersect.

op = subpath_list[0].operation
if op == 0: # Exclude = Union - Intersect.
mask = mask + plane - 2 * mask * plane
elif subpath.operation == 1: # Union (Combine).
elif op == 1: # Union (Combine).
mask = mask + plane - mask * plane
elif subpath.operation in (2, -1): # Subtract.
elif op == 2: # Subtract.
if first and brush:
mask = 1 - mask
mask = np.maximum(0, mask - plane)
elif subpath.operation == 3: # Intersect.
elif op == 3: # Intersect.
if first and brush:
mask = 1 - mask
mask = mask * plane
first = False
return np.minimum(1, np.maximum(0, mask))


def _draw_subpath(subpath, width, height, brush, pen):
def _draw_subpath(subpath_list, width, height, brush, pen):
"""
Rasterize Bezier curves.
Expand All @@ -85,17 +97,18 @@ def _draw_subpath(subpath, width, height, brush, pen):
from PIL import Image
import aggdraw
mask = Image.new('L', (width, height), 0)
if len(subpath) <= 1:
logger.warning('not enough knots: %d' % len(subpath))
else:
draw = aggdraw.Draw(mask)
pen = aggdraw.Pen(**pen) if pen else None
brush = aggdraw.Brush(**brush) if brush else None
for subpath in subpath_list:
if len(subpath) <= 1:
logger.warning('not enough knots: %d' % len(subpath))
continue
path = ' '.join(map(str, _generate_symbol(subpath, width, height)))
draw = aggdraw.Draw(mask)
pen = aggdraw.Pen(**pen) if pen else None
brush = aggdraw.Brush(**brush) if brush else None
symbol = aggdraw.Symbol(path)
draw.symbol((0, 0), symbol, pen, brush)
draw.flush()
del draw
draw.flush()
del draw
return np.expand_dims(np.array(mask).astype(np.float32) / 255., 2)


Expand Down
2 changes: 1 addition & 1 deletion src/psd_tools/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.9.6'
__version__ = '1.9.7'
Binary file not shown.
Binary file added tests/psd_files/path-operations/exclude-group.psd
Binary file not shown.
Binary file not shown.
Binary file not shown.
4 changes: 4 additions & 0 deletions tests/psd_tools/composite/test_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@

@pytest.mark.parametrize(("filename", ), [
('path-operations/combine.psd', ),
('path-operations/combine-group.psd', ),
('path-operations/exclude-first.psd', ),
('path-operations/exclude.psd', ),
('path-operations/exclude-group.psd', ),
('path-operations/intersect-all.psd', ),
('path-operations/intersect-first.psd', ),
('path-operations/intersect-group.psd', ),
('path-operations/subtract-all.psd', ),
('path-operations/subtract-first.psd', ),
('path-operations/subtract-second.psd', ),
('path-operations/subtract-group.psd', ),
])
def test_path_operations(filename):
check_composite_quality(filename, 0.02)
Expand Down

0 comments on commit e5537dc

Please sign in to comment.