diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4d7d236..cb222504 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: Build on: push: - branches: [master] + branches: [main] pull_request: - branches: [master] + branches: [main] release: types: [published] @@ -62,7 +62,7 @@ jobs: uses: docker/setup-qemu-action@v1 - name: Build wheels - uses: pypa/cibuildwheel@v2.3.1 + uses: pypa/cibuildwheel@v2.5.0 env: CIBW_BUILD: ${{ matrix.build }} CIBW_ARCHS_LINUX: auto aarch64 @@ -71,7 +71,7 @@ jobs: CIBW_TEST_REQUIRES: pytest numpy ipython CIBW_TEST_COMMAND: pytest {project}/tests # aggdraw 1.3.12 only supports Tuple[float, ...]: + """Return color tuple from descriptor. + + Example descriptor:: + + Descriptor(b'solidColorLayer'){ + 'Clr ': Descriptor(b'CMYC'){ + 'Cyn ': 83.04, + 'Mgnt': 74.03, + 'Ylw ': 80.99, + 'Blck': 58.3 + } + } + + Descriptor(b'solidColorLayer'){ + 'Clr ': Descriptor(b'RGBC'){ + 'Rd ': 235.90926200151443, + 'Grn ': 232.29671984910965, + 'Bl ': 25.424751117825508, + 'Bk ': 'PANTONE+® Solid Coated\x00', + 'Nm ': 'PANTONE 395 C\x00', + 'bookID': 3060, + 'bookKey': RawData(value=b'1123SC') + } + } + """ + def _get_int_color(color_desc, keys): + return tuple(float(color_desc[key]) / 255. for key in keys) + + def _get_invert_color(color_desc, keys): + return tuple((100. - float(color_desc[key])) / 100. for key in keys) + + def _get_rgb(x): + return _get_int_color(x, (Key.Red, Key.Green, Key.Blue)) + + def _get_gray(x): + return _get_invert_color(x, (Key.Gray,)) + + def _get_cmyk(x): + return _get_invert_color( + x, (Key.Cyan, Key.Magenta, Key.Yellow, Key.Black)) + + def _get_lab(x): + return _get_int_color(x, (Key.Luminance, Key.A, Key.B)) + + _COLOR_FUNC = { + Klass.RGBColor: _get_rgb, + Klass.Grayscale: _get_gray, + Klass.CMYKColor: _get_cmyk, + Klass.LabColor: _get_lab, + } + color_desc = desc.get(Key.Color) + assert color_desc, f"Could not find a color descriptor {desc}" + return _COLOR_FUNC[color_desc.classID](color_desc) + def draw_vector_mask(layer): @@ -98,8 +150,8 @@ def _draw_subpath(subpath_list, width, height, brush, pen): TODO: Replace aggdraw implementation with skimage.draw. """ - from PIL import Image import aggdraw + from PIL import Image mask = Image.new('L', (width, height), 0) draw = aggdraw.Draw(mask) pen = aggdraw.Pen(**pen) if pen else None @@ -185,9 +237,7 @@ def draw_solid_color_fill(viewport, desc): """ Create a solid color fill. """ - color_desc = desc.get(Key.Color) - color_fn = _COLOR_FUNC.get(color_desc.classID, 1.0) - fill = [color_fn(x) for x in color_desc.values()] + fill = _get_color(desc) height, width = viewport[3] - viewport[1], viewport[2] - viewport[0] color = np.full((height, width, len(fill)), fill, dtype=np.float32) return color, None @@ -196,6 +246,26 @@ def draw_solid_color_fill(viewport, desc): def draw_pattern_fill(viewport, psd, desc): """ Create a pattern fill. + + Example descriptor:: + + Descriptor(b'patternFill'){ + 'enab': True, + 'present': True, + 'showInDialog': True, + 'Md ': (b'BlnM', b'CBrn'), + 'Opct': 100.0 Percent, + 'Ptrn': Descriptor(b'Ptrn'){ + 'Nm ': 'foo\x00', + 'Idnt': '5e1713ab-e968-4c4c-8855-c8fa2cde8610\x00' + }, + 'Angl': 0.0 Angle, + 'Scl ': 87.0 Percent, + 'Algn': True, + 'phase': Descriptor(b'Pnt '){'Hrzn': 0.0, 'Vrtc': 0.0} + } + + .. todo:: Test this. """ pattern_id = desc[Enum.Pattern][Key.ID].value.rstrip('\x00') pattern = psd._get_pattern(pattern_id) @@ -318,9 +388,7 @@ def _make_linear_gradient_color(grad): X, Y = [], [] for stop in grad.get(Key.Colors, []): location = float(stop.get(Key.Location)) / 4096. - color_fn = _COLOR_FUNC.get(stop.get(Key.Color).classID) - color = np.array([color_fn(x) for x in stop.get(Key.Color).values()], - dtype=np.float32) + color = np.array(_get_color(stop), dtype=np.float32) if len(X) and X[-1] == location: logger.debug('Duplicate stop at %d' % location) X.pop(), Y.pop() diff --git a/src/psd_tools/constants.py b/src/psd_tools/constants.py index b94d929a..7f47ad0f 100644 --- a/src/psd_tools/constants.py +++ b/src/psd_tools/constants.py @@ -373,6 +373,7 @@ class Tag(bytes, Enum): VECTOR_MASK_SETTING1 = b'vmsk' VECTOR_MASK_SETTING2 = b'vsms' VECTOR_ORIGINATION_DATA = b'vogk' + VECTOR_ORIGINATION_UNKNOWN = b'vowv' VECTOR_STROKE_DATA = b'vstk' VECTOR_STROKE_CONTENT_DATA = b'vscg' VIBRANCE = b'vibA' diff --git a/src/psd_tools/psd/tagged_blocks.py b/src/psd_tools/psd/tagged_blocks.py index 52df5c05..214f7fae 100644 --- a/src/psd_tools/psd/tagged_blocks.py +++ b/src/psd_tools/psd/tagged_blocks.py @@ -93,6 +93,7 @@ Tag.VECTOR_MASK_SETTING1: VectorMaskSetting, Tag.VECTOR_MASK_SETTING2: VectorMaskSetting, Tag.VECTOR_ORIGINATION_DATA: DescriptorBlock2, + Tag.VECTOR_ORIGINATION_UNKNOWN: IntegerElement, Tag.VECTOR_STROKE_DATA: DescriptorBlock, Tag.VECTOR_STROKE_CONTENT_DATA: VectorStrokeContentSetting, }) diff --git a/src/psd_tools/version.py b/src/psd_tools/version.py index fb72a15b..36b482e1 100644 --- a/src/psd_tools/version.py +++ b/src/psd_tools/version.py @@ -1 +1 @@ -__version__ = '1.9.19' +__version__ = '1.9.20' diff --git a/tests/psd_files/descriptors/stroke-color-descriptors-gray.psd b/tests/psd_files/descriptors/stroke-color-descriptors-gray.psd new file mode 100644 index 00000000..0c8f4cbc Binary files /dev/null and b/tests/psd_files/descriptors/stroke-color-descriptors-gray.psd differ diff --git a/tests/psd_files/descriptors/stroke-color-descriptors-lab.psd b/tests/psd_files/descriptors/stroke-color-descriptors-lab.psd new file mode 100644 index 00000000..ec45c344 Binary files /dev/null and b/tests/psd_files/descriptors/stroke-color-descriptors-lab.psd differ diff --git a/tests/psd_files/descriptors/stroke-color-descriptors-rgb.psd b/tests/psd_files/descriptors/stroke-color-descriptors-rgb.psd new file mode 100644 index 00000000..1e4446f2 Binary files /dev/null and b/tests/psd_files/descriptors/stroke-color-descriptors-rgb.psd differ diff --git a/tests/psd_files/effects/double-stroke-effects.psd b/tests/psd_files/effects/double-stroke-effects.psd new file mode 100644 index 00000000..a80bbff4 Binary files /dev/null and b/tests/psd_files/effects/double-stroke-effects.psd differ diff --git a/tests/psd_tools/composite/test_effects.py b/tests/psd_tools/composite/test_effects.py index e1d8385b..9c4919bd 100644 --- a/tests/psd_tools/composite/test_effects.py +++ b/tests/psd_tools/composite/test_effects.py @@ -10,9 +10,10 @@ ('effects/stroke-effects.psd', ), ('effects/shape-fx2.psd', ), ('effects/stroke-effect-transparent-shape.psd', ), + ('effects/double-stroke-effects.psd', ), ]) @pytest.mark.xfail -def test_stroke_effects(filename): +def test_stroke_effects_xfail(filename): err = check_composite_quality(filename, threshold=0.01) diff --git a/tests/psd_tools/composite/test_vector.py b/tests/psd_tools/composite/test_vector.py index 0dbf8392..a064ca3c 100644 --- a/tests/psd_tools/composite/test_vector.py +++ b/tests/psd_tools/composite/test_vector.py @@ -96,3 +96,12 @@ def test_gradient_styles(filename): elif form == Enum.ColorNoise: # Noise gradient is not of good quality. assert _mse(reference, result) <= 0.2 + + +@pytest.mark.parametrize(("filename", ), [ + ('descriptors/stroke-color-descriptors-rgb.psd', ), + ('descriptors/stroke-color-descriptors-gray.psd', ), + ('descriptors/stroke-color-descriptors-lab.psd', ), +]) +def test_stroke_color(filename): + check_composite_quality(filename, 0.05, force=True) diff --git a/tests/psd_tools/psd/test_tagged_blocks.py b/tests/psd_tools/psd/test_tagged_blocks.py index 2c050126..eba58c19 100644 --- a/tests/psd_tools/psd/test_tagged_blocks.py +++ b/tests/psd_tools/psd/test_tagged_blocks.py @@ -50,6 +50,7 @@ def test_tagged_blocks_v2(): (Tag.LAYER_VERSION, IntegerElement(1), 2, 1), (Tag.LAYER_VERSION, IntegerElement(1), 1, 4), (Tag.LAYER_VERSION, IntegerElement(1), 2, 4), + (Tag.VECTOR_ORIGINATION_UNKNOWN, IntegerElement(2), 2, 1), ] ) def test_tagged_block(key, data, version, padding):