Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.8.12 #104

Merged
merged 4 commits into from
Mar 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
1.8.12 (2019-03-25)
-------------------

- add apply_icc option in pil io.

1.8.11 (2019-03-14)
-------------------

Expand Down
6 changes: 3 additions & 3 deletions docs/reference/psd_tools.psd.descriptor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ Double
.. autoclass:: psd_tools.psd.descriptor.Double
:members:

Enum
----
Enumerated
----------

.. autoclass:: psd_tools.psd.descriptor.Enum
.. autoclass:: psd_tools.psd.descriptor.Enumerated
:members:

EnumeratedReference
Expand Down
8 changes: 4 additions & 4 deletions src/psd_tools/api/composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def _blend(target, image, offset, mask=None):
return target


def compose(layers, bbox=None, layer_filter=None, color=None):
def compose(layers, bbox=None, layer_filter=None, color=None, **kwargs):
"""
Compose layers to a single :py:class:`PIL.Image`.
If the layers do not have visible pixels, the function returns `None`.
Expand Down Expand Up @@ -129,7 +129,7 @@ def _default_filter(layer):
if intersect(layer.bbox, bbox) == (0, 0, 0, 0):
continue

image = layer.compose()
image = layer.compose(**kwargs)
if image is None:
continue

Expand All @@ -144,12 +144,12 @@ def _default_filter(layer):
return result


def compose_layer(layer, force=False):
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()
image = layer.topil(**kwargs)
if image is None or force:
image = create_fill(layer)

Expand Down
10 changes: 5 additions & 5 deletions src/psd_tools/api/layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,14 +329,14 @@ def origination(self):
]
return self._origination

def topil(self):
def topil(self, **kwargs):
"""
Get PIL Image of the layer.

:return: :py:class:`PIL.Image`, or `None` if the layer has no pixels.
"""
if self.has_pixels():
return convert_layer_to_pil(self)
return convert_layer_to_pil(self, **kwargs)
return None

def compose(self, *args, **kwargs):
Expand Down Expand Up @@ -482,14 +482,14 @@ def __setitem__(self, key, value):
def __delitem__(self, key):
return self._layers.__delitem__(key)

def compose(self):
def compose(self, **kwargs):
"""
Compose layer and masks (mask, vector mask, and clipping layers).

:return: PIL Image object, or None if the layer has no pixels.
"""
from psd_tools.api.composer import compose
return compose(self)
return compose(self, **kwargs)

def descendants(self, include_clip=True):
"""
Expand Down Expand Up @@ -858,7 +858,7 @@ def __init__(self, *args):
if hasattr(self.__class__, '_KEY'):
self._data = self.tagged_blocks.get_data(self.__class__._KEY)

def compose(self):
def compose(self, **kwargs):
"""
Adjustment layer is never composed.

Expand Down
8 changes: 4 additions & 4 deletions src/psd_tools/api/pil_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def extract_pil_mode(psd):
return get_pil_mode(psd.header.color_mode, alpha)


def convert_image_data_to_pil(psd):
def convert_image_data_to_pil(psd, apply_icc=True, **kwargs):
"""Convert ImageData to PIL Image."""
from PIL import Image, ImageOps
header = psd.header
Expand All @@ -57,14 +57,14 @@ def convert_image_data_to_pil(psd):
image = Image.merge(mode, channels[:(len(channels) - alpha)])
if mode == 'CMYK':
image = image.point(lambda x: 255 - x)
if 'ICC_PROFILE' in psd.image_resources:
if apply_icc and 'ICC_PROFILE' in psd.image_resources:
image = _apply_icc(image, psd.image_resources.get_data('ICC_PROFILE'))
if alpha and mode in ('L', 'RGB'):
image.putalpha(channels[-1])
return _remove_white_background(image)


def convert_layer_to_pil(layer):
def convert_layer_to_pil(layer, apply_icc=True, **kwargs):
"""Convert Layer to PIL Image."""
from PIL import Image
header = layer._psd._record.header
Expand Down Expand Up @@ -95,7 +95,7 @@ def convert_layer_to_pil(layer):
else:
logger.debug('Alpha channel is not supported in %s' % (mode))

if 'ICC_PROFILE' in layer._psd.image_resources:
if apply_icc and 'ICC_PROFILE' in layer._psd.image_resources:
image = _apply_icc(
image, layer._psd.image_resources.get_data('ICC_PROFILE')
)
Expand Down
15 changes: 12 additions & 3 deletions src/psd_tools/api/psd_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,15 @@ def save(self, fp, mode='wb'):
with open(fp, mode) as f:
self._record.write(f)

def topil(self):
def topil(self, **kwargs):
"""
Get PIL Image.

:return: :py:class:`PIL.Image`, or `None` if the composed image is not
available.
"""
if self.has_preview():
return pil_io.convert_image_data_to_pil(self._record)
return pil_io.convert_image_data_to_pil(self._record, **kwargs)
return None

def compose(self, force=False, bbox=None, **kwargs):
Expand All @@ -135,7 +135,9 @@ def compose(self, force=False, bbox=None, **kwargs):
:return: :py:class:`PIL.Image`, or `None` if there is no pixel.
"""
from psd_tools.api.composer import compose
image = self.topil() if (not force or len(self) == 0) else None
image = None
if not force or len(self) == 0:
image = self.topil(**kwargs)
if image is None:
image = compose(self, bbox=bbox or self.viewbox, **kwargs)
return image
Expand Down Expand Up @@ -348,6 +350,13 @@ def image_resources(self):

version_info = psd.image_resources.get_data('VERSION_INFO')
slices = psd.image_resources.get_data('SLICES')

Image resources contain an ICC profile. The following shows how to
export a PNG file with embedded ICC profile::

icc_profile = psd.image_resources.get_data('ICC_PROFILE')
image = psd.compose(apply_icc=False)
image.save('output.png', icc_profile=icc_profile)
"""
return self._record.image_resources

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.8.11'
__version__ = '1.8.12'
Binary file not shown.
8 changes: 8 additions & 0 deletions tests/psd_tools/api/test_composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,11 @@ def test_compose_artboard():
artboard_image = artboard.compose()
assert artboard_image.size == artboard.size
assert artboard.size != extract_bbox(artboard)


def test_apply_icc_profile():
filepath = full_name('colorprofiles/north_america_newspaper.psd')
psd = PSDImage.open(filepath)
no_icc = psd.compose(apply_icc=False)
with_icc = psd.compose(apply_icc=True)
assert no_icc.getextrema() != with_icc.getextrema()