From 1018be505f830bf19687d9256a3f55016d947b19 Mon Sep 17 00:00:00 2001 From: Paul Friederichsen Date: Sat, 16 Sep 2023 13:14:03 -0500 Subject: [PATCH] Split psd-tools code into separate file to fix issue with using types (#1436) --- hydrus/core/HydrusPSDHandling.py | 84 ++-------------------------- hydrus/core/HydrusPSDTools.py | 96 ++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 78 deletions(-) create mode 100644 hydrus/core/HydrusPSDTools.py diff --git a/hydrus/core/HydrusPSDHandling.py b/hydrus/core/HydrusPSDHandling.py index 49c573e0..18cd6e71 100644 --- a/hydrus/core/HydrusPSDHandling.py +++ b/hydrus/core/HydrusPSDHandling.py @@ -7,11 +7,8 @@ from hydrus.core import HydrusExceptions, HydrusImageHandling try: - from psd_tools import PSDImage - from psd_tools.constants import Resource, ColorMode, Resource - from psd_tools.api.numpy_io import has_transparency, get_transparency_index - from psd_tools.api.pil_io import get_pil_mode, get_pil_channels, _create_image - + from hydrus.core import HydrusPSDTools + PSD_TOOLS_OK = True except: @@ -26,9 +23,7 @@ def PSDHasICCProfile( path: str ): raise HydrusExceptions.UnsupportedFileException( 'psd_tools unavailable' ) - psd = PSDImage.open( path ) - - return Resource.ICC_PROFILE in psd.image_resources + return HydrusPSDTools.PSDHasICCProfile( path ) def MergedPILImageFromPSD( path: str ) -> PILImage: @@ -38,27 +33,7 @@ def MergedPILImageFromPSD( path: str ) -> PILImage: raise HydrusExceptions.UnsupportedFileException( 'psd_tools unavailable' ) - psd = PSDImage.open( path ) - - #pil_image = psd.topil( apply_icc = False ) - - if psd.has_preview(): - - pil_image = convert_image_data_to_pil(psd) - - else: - - raise HydrusExceptions.UnsupportedFileException('PSD file has no embedded preview!') - - - if Resource.ICC_PROFILE in psd.image_resources: - - icc = psd.image_resources.get_data( Resource.ICC_PROFILE ) - - pil_image.info[ 'icc_profile' ] = icc - - - return pil_image + return HydrusPSDTools.MergedPILImageFromPSD( path ) def GenerateThumbnailBytesFromPSDPath( path: str, target_resolution: typing.Tuple[int, int], clip_rect = None ) -> bytes: @@ -83,9 +58,7 @@ def GetPSDResolution( path: str ): raise HydrusExceptions.UnsupportedFileException( 'psd_tools unavailable' ) - psd = PSDImage.open( path ) - - return ( psd.width, psd.height ) + return HydrusPSDTools.GetPSDResolution( path ) def GetPSDResolutionFallback( path: str ): @@ -102,49 +75,4 @@ def GetPSDResolutionFallback( path: str ): width: int = struct.unpack( '>L', width_bytes )[0] return ( width, height ) - - -# modified from psd-tools source: -# https://github.com/psd-tools/psd-tools/blob/main/src/psd_tools/api/pil_io.py - -def convert_image_data_to_pil(psd: PSDImage): - alpha = None - - channel_data = psd._record.image_data.get_data(psd._record.header) - size = (psd.width, psd.height) - - channels = [_create_image(size, c, psd.depth) for c in channel_data] - - # has_transparency not quite correct - # see https://github.com/psd-tools/psd-tools/issues/369 - # and https://github.com/psd-tools/psd-tools/pull/370 - no_alpha = psd._record.layer_and_mask_information.layer_info is not None and psd._record.layer_and_mask_information.layer_info.layer_count > 0 - - if has_transparency(psd) and not no_alpha: - alpha = channels[get_transparency_index(psd)] - - if psd.color_mode == ColorMode.INDEXED: - image = channels[0] - image.putpalette(psd._record.color_mode_data.interleave()) - elif psd.color_mode == ColorMode.MULTICHANNEL: - image = channels[0] # Multi-channel mode is a collection of alpha. - else: - mode = get_pil_mode(psd.color_mode) - image = PILImage.merge(mode, channels[:get_pil_channels(mode)]) - - if not image: - return None - - return post_process(image, alpha) - - -def post_process(image, alpha): - # Fix inverted CMYK. - if image.mode == 'CMYK': - from PIL import ImageChops - image = ImageChops.invert(image) - - # In Pillow, alpha channel is only available in RGB or L. - if alpha and image.mode in ('RGB', 'L'): - image.putalpha(alpha) - return image + \ No newline at end of file diff --git a/hydrus/core/HydrusPSDTools.py b/hydrus/core/HydrusPSDTools.py new file mode 100644 index 00000000..2c51ad19 --- /dev/null +++ b/hydrus/core/HydrusPSDTools.py @@ -0,0 +1,96 @@ +from PIL import Image as PILImage + +from hydrus.core import HydrusExceptions + + +from psd_tools import PSDImage +from psd_tools.constants import Resource, ColorMode, Resource +from psd_tools.api.numpy_io import has_transparency, get_transparency_index +from psd_tools.api.pil_io import get_pil_mode, get_pil_channels, _create_image + + + +def PSDHasICCProfile( path: str ): + + psd = PSDImage.open( path ) + + return Resource.ICC_PROFILE in psd.image_resources + + +def MergedPILImageFromPSD( path: str ) -> PILImage: + + psd = PSDImage.open( path ) + + #pil_image = psd.topil( apply_icc = False ) + + if psd.has_preview(): + + pil_image = convert_image_data_to_pil(psd) + + else: + + raise HydrusExceptions.UnsupportedFileException('PSD file has no embedded preview!') + + + if Resource.ICC_PROFILE in psd.image_resources: + + icc = psd.image_resources.get_data( Resource.ICC_PROFILE ) + + pil_image.info[ 'icc_profile' ] = icc + + + return pil_image + + + +def GetPSDResolution( path: str ): + + psd = PSDImage.open( path ) + + return ( psd.width, psd.height ) + + +# modified from psd-tools source: +# https://github.com/psd-tools/psd-tools/blob/main/src/psd_tools/api/pil_io.py + +def convert_image_data_to_pil(psd: PSDImage): + alpha = None + + channel_data = psd._record.image_data.get_data(psd._record.header) + size = (psd.width, psd.height) + + channels = [_create_image(size, c, psd.depth) for c in channel_data] + + # has_transparency not quite correct + # see https://github.com/psd-tools/psd-tools/issues/369 + # and https://github.com/psd-tools/psd-tools/pull/370 + no_alpha = psd._record.layer_and_mask_information.layer_info is not None and psd._record.layer_and_mask_information.layer_info.layer_count > 0 + + if has_transparency(psd) and not no_alpha: + alpha = channels[get_transparency_index(psd)] + + if psd.color_mode == ColorMode.INDEXED: + image = channels[0] + image.putpalette(psd._record.color_mode_data.interleave()) + elif psd.color_mode == ColorMode.MULTICHANNEL: + image = channels[0] # Multi-channel mode is a collection of alpha. + else: + mode = get_pil_mode(psd.color_mode) + image = PILImage.merge(mode, channels[:get_pil_channels(mode)]) + + if not image: + return None + + return post_process(image, alpha) + + +def post_process(image, alpha): + # Fix inverted CMYK. + if image.mode == 'CMYK': + from PIL import ImageChops + image = ImageChops.invert(image) + + # In Pillow, alpha channel is only available in RGB or L. + if alpha and image.mode in ('RGB', 'L'): + image.putalpha(alpha) + return image