Skip to content

Commit

Permalink
Merge pull request #249 from psd-tools/v1.9.16
Browse files Browse the repository at this point in the history
v1.9.16
  • Loading branch information
kyamagu committed Sep 24, 2020
2 parents 8fcfb4e + edb7682 commit 6a9b156
Show file tree
Hide file tree
Showing 20 changed files with 88 additions and 59 deletions.
13 changes: 1 addition & 12 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,9 @@ jobs:
- quay.io/pypa/manylinux2010_x86_64
- quay.io/pypa/manylinux2014_x86_64
python-version:
- cp27-cp27m
- cp27-cp27mu
- cp35-cp35m
- cp36-cp36m
- cp37-cp37m
- cp38-cp38
exclude:
- container: quay.io/pypa/manylinux2014_x86_64
python-version: cp27-cp27m
- container: quay.io/pypa/manylinux2014_x86_64
python-version: cp27-cp27mu
container: ${{ matrix.container }}
steps:
- uses: actions/checkout@v1
Expand All @@ -65,10 +57,7 @@ jobs:
strategy:
matrix:
os: [macos-latest, windows-latest]
python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
exclude:
- os: windows-latest
python-version: 2.7
python-version: [3.6, 3.7, 3.8]
steps:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1
Expand Down
8 changes: 1 addition & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ jobs:
- quay.io/pypa/manylinux2010_x86_64
- quay.io/pypa/manylinux2014_x86_64
python-version:
- cp27-cp27m
- cp27-cp27mu
- cp35-cp35m
- cp36-cp36m
- cp37-cp37m
- cp38-cp38
Expand Down Expand Up @@ -50,10 +47,7 @@ jobs:
strategy:
matrix:
os: [macos-latest, windows-latest]
python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
exclude:
- os: windows-latest
python-version: 2.7
python-version: [3.6, 3.7, 3.8]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v1
Expand Down
6 changes: 3 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox:tox]
envlist = clean,py27,py35,py36,py37,py38,report
envlist = clean,py36,py37,py38,report

[testenv]
commands = py.test --cov=psd_tools --cov-append --cov-report=term []
Expand All @@ -9,8 +9,8 @@ deps=
ipython
cython
depends =
{py27,py35,py36,py37,py38}: clean
report: py27,py35,py36,py37,py38
{py36,py37,py38}: clean
report: py36,py37,py38

[testenv:report]
deps = coverage
Expand Down
3 changes: 0 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@ def get_version():
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
Expand Down
4 changes: 2 additions & 2 deletions src/psd_tools/api/layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ def compose(self, force=False, bbox=None, layer_filter=None):
return compose_layer(self, force=force)
return compose(self, force=force, bbox=bbox, layer_filter=layer_filter)

def numpy(self, channel=None):
def numpy(self, channel=None, real_mask=True):
"""
Get NumPy array of the layer.
Expand All @@ -389,7 +389,7 @@ def numpy(self, channel=None):
:return: :py:class:`numpy.ndarray` or None if there is no pixel.
"""
from .numpy_io import get_array
return get_array(self, channel)
return get_array(self, channel, real_mask=real_mask)

def composite(
self,
Expand Down
8 changes: 4 additions & 4 deletions src/psd_tools/api/numpy_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
}


def get_array(layer, channel):
def get_array(layer, channel, **kwargs):
if layer.kind == 'psdimage':
return get_image_data(layer, channel)
else:
return get_layer_data(layer, channel)
return get_layer_data(layer, channel, **kwargs)
return None


Expand Down Expand Up @@ -53,7 +53,7 @@ def get_image_data(psd, channel):
return data


def get_layer_data(layer, channel):
def get_layer_data(layer, channel, real_mask=True):
def _find_channel(layer, width, height, condition):
depth, version = layer._psd.depth, layer._psd.version
iterator = zip(layer._record.channel_info, layer._channels)
Expand Down Expand Up @@ -81,7 +81,7 @@ def _find_channel(layer, width, height, condition):
lambda x: x.id == ChannelID.TRANSPARENCY_MASK
)
elif channel == 'mask':
if layer.mask._has_real():
if layer.mask._has_real() and real_mask:
channel_id = ChannelID.REAL_USER_LAYER_MASK
else:
channel_id = ChannelID.USER_LAYER_MASK
Expand Down
34 changes: 22 additions & 12 deletions src/psd_tools/api/psd_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,10 +497,17 @@ def _pretty(layer, p):
def _make_header(cls, mode, size, depth=8):
from .pil_io import get_color_mode
assert depth in (8, 16, 32), 'Invalid depth: %d' % (depth)
assert size[0] <= 300000, 'Width too large > 300,000'
assert size[1] <= 300000, 'Height too large > 300,000'
version = 1
if size[0] > 30000 or size[1] > 30000:
logger.debug('Width or height larger than 30,000 pixels')
version = 2
color_mode = get_color_mode(mode)
alpha = int(mode.upper().endswith('A'))
channels = ColorMode.channels(color_mode, alpha)
return FileHeader(
version=version,
width=size[0],
height=size[1],
depth=depth,
Expand Down Expand Up @@ -560,16 +567,6 @@ def _init(self):
Tag.TYPE_TOOL_INFO in blocks
):
layer = TypeLayer(self, record, channels, current_group)
elif (
record.flags.pixel_data_irrelevant and (
Tag.VECTOR_ORIGINATION_DATA in blocks or
Tag.VECTOR_MASK_SETTING1 in blocks or
Tag.VECTOR_MASK_SETTING2 in blocks or
Tag.VECTOR_STROKE_DATA in blocks or
Tag.VECTOR_STROKE_CONTENT_DATA in blocks
)
):
layer = ShapeLayer(self, record, channels, current_group)
elif (
Tag.SMART_OBJECT_LAYER_DATA1 in blocks or
Tag.SMART_OBJECT_LAYER_DATA2 in blocks or
Expand All @@ -584,9 +581,22 @@ def _init(self):
)
break

# If nothing applies, this is a pixel layer.
# If nothing applies, this is either a shape or pixel layer.
if layer is None:
layer = PixelLayer(self, record, channels, current_group)
if (
record.flags.pixel_data_irrelevant and (
Tag.VECTOR_ORIGINATION_DATA in blocks or
Tag.VECTOR_MASK_SETTING1 in blocks or
Tag.VECTOR_MASK_SETTING2 in blocks or
Tag.VECTOR_STROKE_DATA in blocks or
Tag.VECTOR_STROKE_CONTENT_DATA in blocks
)
):
layer = ShapeLayer(self, record, channels, current_group)
else:
layer = PixelLayer(self, record, channels, current_group)

assert layer is not None

if record.clipping == Clipping.NON_BASE:
clip_stack.append(layer)
Expand Down
9 changes: 5 additions & 4 deletions src/psd_tools/composite/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@ def composite_pil(
if mode == 'P':
mode = 'RGB'
# Skip only when there is a preview image and it has no alpha.
skip_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'
Expand Down Expand Up @@ -363,9 +364,9 @@ def _get_mask(self, layer):
"""Get mask attributes."""
shape = 1.
opacity = 1.
if layer.has_mask():
if layer.has_mask() and not layer.mask.disabled:
# TODO: When force, ignore real mask.
mask = layer.numpy('mask')
mask = layer.numpy('mask', real_mask=not self._force)
if mask is not None:
shape = paste(
self._viewport, layer.mask.bbox, mask,
Expand All @@ -379,7 +380,7 @@ def _get_mask(self, layer):
density = 255
opacity = float(density) / 255.

if layer.has_vector_mask() and (
if layer.has_vector_mask() and not layer.vector_mask.disabled and (
self._force or not layer.has_pixels() or (
not has_fill(layer) and layer.has_mask() and
not layer.mask._has_real()
Expand Down
6 changes: 5 additions & 1 deletion src/psd_tools/composite/vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ def draw_stroke(layer):

def _draw_path(layer, brush=None, pen=None):
height, width = layer._psd.height, layer._psd.width
color = layer.vector_mask.initial_fill_rule
color = 0
if layer.vector_mask.initial_fill_rule and \
len(layer.vector_mask.paths) == 0:
color = 1
mask = np.full((height, width, 1), color, dtype=np.float32)

# Group merged path components.
Expand Down Expand Up @@ -85,6 +88,7 @@ def _draw_path(layer, brush=None, pen=None):
mask = 1 - mask
mask = mask * plane
first = False

return np.minimum(1, np.maximum(0, mask))


Expand Down
22 changes: 16 additions & 6 deletions src/psd_tools/psd/descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,19 +278,24 @@ class UnitFloat(NumericElement):
.. py:attribute:: unit
unit of the value in :py:class:`Unit`
unit of the value in :py:class:`Unit` or :py:class:`Enum`
.. py:attribute:: value
`float` value
"""
value = attr.ib(default=0.0, type=float)
unit = attr.ib(default=Unit._None, converter=Unit, validator=in_(Unit))
unit = attr.ib(default=Unit._None)

@classmethod
def read(cls, fp):
unit, value = read_fmt('4sd', fp)
return cls(unit=Unit(unit), value=value)
try:
unit = Unit(unit)
except ValueError:
logger.warning('Using Enum for Unit field')
unit = Enum(unit)
return cls(unit=unit, value=value)

def write(self, fp):
return write_fmt(fp, '4sd', self.unit.value, self.value)
Expand All @@ -311,20 +316,25 @@ class UnitFloats(BaseElement):
.. py:attribute:: unit
unit of the value in :py:class:`Unit`
unit of the value in :py:class:`Unit` or :py:class:`Enum`
.. py:attribute:: values
List of `float` values
"""
unit = attr.ib(default=Unit._None, converter=Unit, validator=in_(Unit))
unit = attr.ib(default=Unit._None)
values = attr.ib(factory=list)

@classmethod
def read(cls, fp):
unit, count = read_fmt('4sI', fp)
try:
unit = Unit(unit)
except ValueError:
logger.warning('Using Enum for Unit field')
unit = Enum(unit)
values = list(read_fmt('%dd' % count, fp))
return cls(unit, values)
return cls(unit=unit, values=values)

def write(self, fp):
return write_fmt(
Expand Down
6 changes: 3 additions & 3 deletions src/psd_tools/psd/vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,17 +291,17 @@ def write(self, fp, **kwargs):
@property
def invert(self):
"""Flag to indicate that the vector mask is inverted."""
return self.flags & 1
return bool(self.flags & 1)

@property
def not_link(self):
"""Flag to indicate that the vector mask is not linked."""
return self.flags & 2
return bool(self.flags & 2)

@property
def disable(self):
"""Flag to indicate that the vector mask is disabled."""
return self.flags & 4
return bool(self.flags & 4)


@attr.s(repr=False, slots=True)
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.15'
__version__ = '1.9.16'
Binary file added tests/psd_files/layers/curves-with-vectormask.psd
Binary file not shown.
Binary file added tests/psd_files/mask-disabled.psd
Binary file not shown.
Binary file added tests/psd_files/vector-mask-disabled.psd
Binary file not shown.
Binary file modified tests/psd_files/vector-mask.psd
Binary file not shown.
5 changes: 5 additions & 0 deletions tests/psd_tools/api/test_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ def test_layer_kind(kind_args):
assert layer.kind == expected


def test_curves_with_vectormask():
layer = PSDImage.open(full_name('layers/curves-with-vectormask.psd'))[0]
assert layer.kind == 'curves'


@pytest.fixture(params=ALL_FIXTURES)
def topil_args(request):
is_image = request.param in {
Expand Down
7 changes: 7 additions & 0 deletions tests/psd_tools/api/test_psd_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ def test_new(args):
PSDImage.new(*args)


def test_frompil_psb():
from PIL import Image
image = Image.new('RGB', (30001, 24))
psb = PSDImage.frompil(image)
assert psb.version == 2


@pytest.mark.parametrize('filename', [
'colormodes/4x4_8bit_rgb.psd',
])
Expand Down
6 changes: 5 additions & 1 deletion tests/psd_tools/composite/test_composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ def check_composite_quality(filename, threshold=0.1, force=False):
('background-red-opacity-80.psd', ),
('32bit.psd', ),
('clipping-mask2.psd', ),
('vector-mask3.psd', ),
('clipping-mask.psd', ),
('clipping-mask2.psd', ),
('clipping-mask3.psd', ),
Expand All @@ -44,6 +43,11 @@ def check_composite_quality(filename, threshold=0.1, force=False):
('transparency/knockout-isolated-groups.psd', ),
('transparency/clip-opacity.psd', ),
('transparency/fill-opacity.psd', ),
('mask.psd', ),
('mask-disabled.psd', ),
# ('vector-mask.psd', ), # 32-bit blending not working.
('vector-mask-disabled.psd', ),
('vector-mask3.psd', ),
])
def test_composite_quality(filename):
check_composite_quality(filename, 0.01, False)
Expand Down
8 changes: 8 additions & 0 deletions tests/psd_tools/psd/test_descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,11 @@ def test_unit_float(unit, value):
assert fixture == value
assert fixture + 1.0
assert isinstance(float(fixture), float)


@pytest.mark.parametrize('fixture', [
b'RrCm\x00\x00\x00\x00\x00\x00\x00\x00',
b'#Pxl\x00\x00\x00\x00\x00\x00\x00\x00',
])
def test_unit_float_enum(fixture):
unitfloat = UnitFloat.frombytes(fixture)

0 comments on commit 6a9b156

Please sign in to comment.