Code Monkey home page Code Monkey logo

psdtags's Introduction

Read and write layered TIFF ImageSourceData and ImageResources tags

Psdtags is a Python library to read and write the Adobe Photoshop(r) specific ImageResources (#34377) and ImageSourceData (#37724) TIFF tags, which contain image resource blocks, layer and mask information found in a typical layered TIFF file created by Photoshop.

The format is specified in the Adobe Photoshop TIFF Technical Notes (March 22, 2002) and Adobe Photoshop File Formats Specification (November 2019).

Adobe Photoshop is a registered trademark of Adobe Systems Inc.

Author:Christoph Gohlke
License:BSD 3-Clause
Version:2024.5.24
DOI:10.5281/zenodo.7879187

Quickstart

Install the psdtags package and all dependencies from the Python Package Index:

python -m pip install -U psdtags[all]

View the layer image and metadata stored in a layered TIFF file:

python -m psdtags file.tif

See Examples for using the programming interface.

Source code, examples, and support are available on GitHub.

Requirements

This revision was tested with the following requirements and dependencies (other versions may work):

  • CPython 3.9.13, 3.10.11, 3.11.9, 3.12.3
  • NumPy 1.26.4
  • Imagecodecs 2024.1.1 (required for compressing/decompressing image data)
  • Tifffile 2024.5.22 (required for reading/writing tags from/to TIFF files)
  • Matplotlib 3.8.4 (required for plotting)

Revisions

2024.5.24

  • Fix GitHub not correctly rendering docstring examples.

2024.2.22

  • Fix reading PsdBoolean (#10).
  • Fix order of PsdReferencePoint coordinates (breaking).
  • Allow reading unaligned PsdLayer blending_ranges.

2024.1.15

  • Fix multi-threading.

2024.1.8

  • Add option to compress layer channels in multiple threads.
  • Improve logging.
  • Drop support for Python 3.9 and numpy < 1.23 (NEP29).

2023.8.24

  • Fix channel data in layer and pattern blocks must be in big-endian order.

2023.6.15

  • Use PsdThumbnailFormat enum for PsdThumbnailBlock.format.

2023.4.30

  • Few API changes (breaking).
  • Improve object repr.
  • Drop support for Python 3.8 and numpy < 1.21 (NEP29).

2023.2.18

  • Allow unknown PsdKeys (#5).

2023.2.8

  • Change PsdPoint and PsdReferencePoint signatures (breaking).
  • Add helper function to create composite from layers.

2022.8.25

  • Update metadata.

2022.2.11

  • Fix struct padding.
  • Support TiffImageResources.

2022.2.2

  • Various API changes (breaking).
  • Handle additional layer information.
  • Preserve structures of unknown format as opaque bytes.
  • Add options to skip tag structures of unknown format.
  • Add abstract base class for tag structures.
  • Add classes for many structures.

2022.1.18

  • Various API changes (breaking).
  • Various fixes for writing TiffImageSourceData.
  • Support filter masks.
  • Add option to change channel compression on write.
  • Warn when skipping ResourceKey sections.

2022.1.14

  • Initial release.

Notes

The API is not stable yet and might change between revisions.

This library has been tested with a limited number of files only.

Additional layer information is not yet supported.

Consider psd-tools and pytoshop for working with Adobe Photoshop PSD files.

Layered TIFF files can be read or written by Photoshop, Affinity Photo, and Krita.

See also Reading and writing a Photoshop TIFF.

Examples

Read the ImageSourceData tag value from a layered TIFF file and iterate over all the channels:

>>> isd = TiffImageSourceData.fromtiff('layered.tif')
>>> for layer in isd.layers:
...     layer.name
...     for channel in layer.channels:
...         ch = channel.data  # a numpy array
...
'Background'
'Reflect1'
'Reflect2'
'image'
'Layer 1'
'ORight'
'I'
'IShadow'
'O'

Read the ImageResources tag value from the TIFF file, iterate over the blocks, and get the thumbnail image:

>>> res = TiffImageResources.fromtiff('layered.tif')
>>> for block in res.blocks:
...     blockname = block.name
...
>>> res.thumbnail().shape
(90, 160, 3)

Write the image, ImageSourceData and ImageResources to a new layered TIFF file:

>>> from tifffile import imread, imwrite
>>> image = imread('layered.tif')
>>> imwrite(
...     '_layered.tif',
...     image,
...     byteorder=isd.byteorder,  # must match ImageSourceData
...     photometric='rgb',  # must match ImageSourceData
...     metadata=None,  # do not write any tifffile specific metadata
...     extratags=[isd.tifftag(maxworkers=4), res.tifftag()],
... )

Verify that the new layered TIFF file contains readable ImageSourceData:

>>> assert isd == TiffImageSourceData.fromtiff('_layered.tif')
>>> assert res == TiffImageResources.fromtiff('_layered.tif')

View the layer and mask information as well as the image resource blocks in a layered TIFF file from a command line:

python -m psdtags layered.tif

Refer to the layered_tiff.py example in the source distribution for creating a layered TIFF file from individual layer images.

psdtags's People

Contributors

cgohlke avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

psdtags's Issues

can output tif keep layers ?

some small tweak to the example on pypi page,
tif saved successful, but the layer were flattern.

Is is possible to keep the layers on ouput tif image ?

from tifffile import imread, imwrite
import psdtags

isd = psdtags.TiffImageResources.fromtiff('input_2layer.tif')
res = psdtags.TiffImageResources.fromtiff('input_2layer.tif')

image = imread('input_2layer.tif')
imwrite(
    'output_layered_tiff.tif',
    image,
    byteorder=isd.psdformat.byteorder,  # must match ImageSourceData
    photometric='rgb',  # must match ImageSourceData
    metadata=None,  # do not write any tifffile specific metadata
    extratags=[isd.tifftag(), res.tifftag()],
)

All PsdBooleans are True

Hi there Christoph,

psdtags is great! But I just found a bug while doing an overly[*] paranoid assert(originalbytes == frombytes(originalbytes).tobytes() at the PsdLayer level). It seemed to me that a layer's infx value should have been 0000, but it was being written out as 1000. The same thing was true of knko. But I noticed that there wasn't an unexpected diff in the layer's clbl. They're all PsdBooleans. Then it occurred to me.. it's because they were all hardcoded by psdtags to 1000 (True), and only the clbl was originally true. Lo and behold:

In https://github.com/cgohlke/psdtags/blob/v2024.1.15/psdtags/psdtags.py#L2490 we see:

        value = bool(fh.read(1))

However:

% python3
>>> bool(b'\x01')
True
>>> bool(b'\x00')
True

I believe this is is making all PsdBoolean's read()/frombytes() True, regardless of input value. This also isn't the only place I see a bool(fh.read(1)), which I think is generally a bug for the above reason - unless the read literally returns False or None, you're always going to be True.

I tried patching this in my local checkout to check for fh.read(1) != '\x00', but it had really bizarre side effects - namely that write()/tobytes() output of my PsdLayer object ballooned in size. If I just hardcoded value = True I had identical broken output, but if I got it to parse correctly, I think some other routine is conditioning on some PsdBoolean somewhere, and since it's never been False before (or at least recently - I didn't check blame history) I'm guessing said codepath wasn't exercised.

Hope this helps,
-Andrew

[*] turns out maybe this wasn't overly paranoid, but "just right paranoid" ;)

Create multi-layered tiff from png images

Hi @cgohlke. First of all, awesome work!
I got your library from stackoverflow that it might be helpful for me.
What I need is to create a multi-layered tiff. Just the same like you exported tiff from your photoshop.

Here is an example of files: google drive
where you have photoshop file and tif created from it and also images which was created from.

This I want to accomplish, when you open generated tif, photoshop needs to see as layers.
e4542

I tried multiple approach:

  • generate psd and then with imagemagic convert to tif but without a success.
  • generate a tif with wand library
  • generate psd and convert with online tools
    ... and I'm complete stuck.

here is the stackoverflow

FR: add tests that assert equality of bytestreams that take a roundtrip through psdtags with unknown=False

I think it would be beneficial to add tests that check for non-determinism and/or accidental misparsing/mutation. If not at the highest/largest levels, at least for smaller primitives like PsdLayer etc, using existing test inputs.

I did this manually and fell down a rabbit hole unearthing what seems to be issue #10. I think such a test feature would likely catch small and large regressions across the codebase, either retroactively or in the future.

That being said, I don't have a sufficient command of the Adobe spec to know as to whether there are situations where non-determinism is encouraged (!!), nor of your codebase to know if determinism and repeatability are inherit goals. So please forgive me if I'm quite offbase here.

Thank you!
-Andrew

How to write a TIFF or PSD with Layer Mask ?

Fantastic work ! Thank you !

I'm trying to save a TIFF or PSD file with Layer Mask so i can edit it later if needed.

I would like to know how to do that if possible.

here are the files (original, mask)

original
mask

here how i am expecting to open the final file in photoshop :

photoshop

Appreciate your help !

How can I edit psd file

Thanks for providing this tool, one question is how to edit the psd file, for example, layer creation and replacement

ValueError: operands could not be broadcast together with shapes

I'm trying to create two layer tiff from two png with the script below:

import numpy
import imagecodecs
import tifffile

from psdtags import (
    __version__,
    PsdBlendMode,
    PsdChannel,
    PsdChannelId,
    PsdClippingType,
    PsdColorSpaceType,
    PsdCompressionType,
    PsdEmpty,
    PsdFilterMask,
    PsdFormat,
    PsdKey,
    PsdLayer,
    PsdLayerFlag,
    PsdLayerMask,
    PsdLayers,
    PsdRectangle,
    PsdString,
    PsdUserMask,
    TiffImageSourceData,
    overlay,
)

import numpy as np

# read individual layer images from files
background: numpy.ndarray = imagecodecs.imread('background.png')
product: numpy.ndarray = imagecodecs.imread(r'C:\Users\cical\Desktop\Università\Progetto per Giuseppe\ComputerVision\pexels-arianna-jadé-4754648_mask.png')




# positions of layers in canvas
background_offset = (0, 0)
product_offset = (79, 83)

# create the ImageSourceData structure for the layered TIFF
image_source_data = TiffImageSourceData(
    name='Layered TIFF',
    psdformat=PsdFormat.LE32BIT,
    layers=PsdLayers(
        key=PsdKey.LAYER,
        has_transparency=False,
        layers=[
            PsdLayer(
                name='Background',
                rectangle=PsdRectangle(
                    background_offset[0],
                    background_offset[1],
                    background_offset[0] + background.shape[0],
                    background_offset[1] + background.shape[1],
                ),
                channels=[
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL0,
                        compression=PsdCompressionType.ZIP_PREDICTED,
                        data=background[..., 0],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL1,
                        compression=PsdCompressionType.ZIP_PREDICTED,
                        data=background[..., 1],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL2,
                        compression=PsdCompressionType.ZIP_PREDICTED,
                        data=background[..., 2],
                    ),
                ],
                mask=PsdLayerMask(),
                opacity=255,
                blendmode=PsdBlendMode.NORMAL,
                clipping=PsdClippingType.BASE,
                flags=PsdLayerFlag.PHOTOSHOP5 | PsdLayerFlag.TRANSPARENCY_PROTECTED,
                info=[
                    PsdString(PsdKey.UNICODE_LAYER_NAME, 'Background'),
                ],
            ),
            PsdLayer(
                name='Product',
                rectangle=PsdRectangle(
                    product_offset[0],
                    product_offset[1],
                    product_offset[0] + product.shape[0],
                    product_offset[1] + product.shape[1],
                ),
                channels=[
                    PsdChannel(
                        channelid=PsdChannelId.TRANSPARENCY_MASK,
                        compression=PsdCompressionType.ZIP_PREDICTED,
                        data=product[..., 3],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL0,
                        compression=PsdCompressionType.ZIP_PREDICTED,
                        data=product[..., 0],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL1,
                        compression=PsdCompressionType.ZIP_PREDICTED,
                        data=product[..., 1],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL2,
                        compression=PsdCompressionType.ZIP_PREDICTED,
                        data=product[..., 2],
                    ),
                ],
                mask=PsdLayerMask(),
                opacity=255,
                blendmode=PsdBlendMode.NORMAL,
                clipping=PsdClippingType.BASE,
                flags=PsdLayerFlag.PHOTOSHOP5,
                info=[
                    PsdString(PsdKey.UNICODE_LAYER_NAME, 'Product'),
                ],
            ),
        ],
    ),
    usermask=PsdUserMask(
        colorspace=PsdColorSpaceType.RGB,
        components=(65535, 0, 0, 0),
        opacity=50,
    ),
    info=[
        PsdEmpty(PsdKey.PATTERNS),
        PsdFilterMask(
            colorspace=PsdColorSpaceType.RGB,
            components=(65535, 0, 0, 0),
            opacity=50,
        ),
    ],
)


# create a composite of the layers
composite = overlay(
    (background, background_offset),
    (product, product_offset),
    shape=background.shape
)
# write a layered TIFF file
tifffile.imwrite(
    'layered.tif',
    # write composite as main TIFF image, accessible to regular TIFF readers
    composite,
    photometric='rgb',
    compression='adobe_deflate',
    # 72 dpi resolution
    resolution=((720000, 10000), (720000, 10000)),
    resolutionunit='inch',
    # do not write tifffile specific metadata
    metadata=None,
    # write layers and sRGB profile as extra tags
    extratags=[
        # ImageSourceData tag
        image_source_data.tifftag(),
        # InterColorProfile tag
        (34675, 7, None, imagecodecs.cms_profile('srgb'), True),
    ],
)

# read the ImageSourceData structure from the TIFF file
isd = TiffImageSourceData.fromtiff('layered.tif')
print(isd)
print(f'psdtags {__version__}')

# plot the layer and composite images in the TIFF file
for layer in isd.layers:
    tifffile.imshow(layer.asarray(), title=layer.name)
tifffile.imshow(tifffile.imread('layered.tif'), title='Composite', show=True)

But i recive error:

Traceback (most recent call last):
  File "C:\Users\cical\Desktop\Università\Progetto per Giuseppe\ComputerVision\prova.py", line 142, in <module>
    composite = overlay(
                ^^^^^^^^
  File "C:\Users\cical\anaconda3\Lib\site-packages\psdtags\psdtags.py", line 3775, in overlay
    over(composite, layer[0] / vmax, layer[1])
  File "C:\Users\cical\anaconda3\Lib\site-packages\psdtags\psdtags.py", line 3767, in over
    x = b[..., 3:] * (1.0 - a[..., 3:])
        ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~
ValueError: operands could not be broadcast together with shapes (2738,1825,3,1) (2738,1825,0) 

Please, someone can help me? I'm a newbie

write .tif to be read with photoshop

Is it possible to put multiple images, as layers, into a tif file and have them be read by photoshop.

I tried the following,

with tifffile.TiffWriter('temp.tif') as tiff:
    for img in data:
        data_block = bytes('Adobe Photoshop Document Data Block', 'ascii') + b'\x00'
        bim = bytes('8BIM', 'ascii')
        layer = bytes('Layr', 'ascii')
        img_bytes = img.tobytes()
        img_len = len(img_bytes)
        img_len_bytes = bytes(str(img_len), 'ascii')
        all_bytes = data_block + bim + layer + img_len_bytes
        byte_length = len(all_bytes) - len(data_block)
        bytes_bytes = bytes(byte_length)
        allll = all_bytes + bytes_bytes
        metad = {"37724": str(allll)}
        
        tiff.save(img, metadata=metad)
tif.close()

which works with preview (on mac) but not photoshop. Photoshop only sees the first layer...

Some tif files failed to show layers info

Hi,
With some (rare) tif files I have an issue with getting layers info:

Traceback (most recent call last):
  File "C:\Users\yuriy.l\AppData\Local\Autodesk\3dsMax\2021 - 64bit\ENU\scripts\fraglab_asset_checker_libs\psdtags\psdtags.py", line 263, in __call__
    c = enum.EnumMeta.__call__(cls, *args, **kwds)
  File "C:\Program Files\Autodesk\3ds Max 2021\Python37\lib\enum.py", line 310, in __call__
    return cls.__new__(cls, value)
  File "C:\Program Files\Autodesk\3ds Max 2021\Python37\lib\enum.py", line 564, in __new__
    raise exc
  File "C:\Program Files\Autodesk\3ds Max 2021\Python37\lib\enum.py", line 548, in __new__
    result = cls._missing_(value)
  File "C:\Program Files\Autodesk\3ds Max 2021\Python37\lib\enum.py", line 577, in _missing_
    raise ValueError("%r is not a valid %s" % (value, cls.__name__))
ValueError: b'iOpa' is not a valid PsdKey

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\yuriy.l\AppData\Local\Autodesk\3dsMax\2021 - 64bit\ENU\scripts\fraglab_asset_checker_libs\psdtags\psdtags.py", line 2967, in fromtiff
    data, name=os.path.split(filename)[-1], unknown=unknown
  File "C:\Users\yuriy.l\AppData\Local\Autodesk\3dsMax\2021 - 64bit\ENU\scripts\fraglab_asset_checker_libs\psdtags\psdtags.py", line 2951, in frombytes
    self = cls.read(fh, name=name, unknown=unknown)
  File "C:\Users\yuriy.l\AppData\Local\Autodesk\3dsMax\2021 - 64bit\ENU\scripts\fraglab_asset_checker_libs\psdtags\psdtags.py", line 2912, in read
    fh, psdformat, key, length=size, unknown=unknown
  File "C:\Users\yuriy.l\AppData\Local\Autodesk\3dsMax\2021 - 64bit\ENU\scripts\fraglab_asset_checker_libs\psdtags\psdtags.py", line 945, in read
    layers.append(PsdLayer.read(fh, psdformat, unknown=unknown))
  File "C:\Users\yuriy.l\AppData\Local\Autodesk\3dsMax\2021 - 64bit\ENU\scripts\fraglab_asset_checker_libs\psdtags\psdtags.py", line 1122, in read
    fh, psdformat, length=end - fh.tell(), unknown=unknown, align=2
  File "C:\Users\yuriy.l\AppData\Local\Autodesk\3dsMax\2021 - 64bit\ENU\scripts\fraglab_asset_checker_libs\psdtags\psdtags.py", line 3333, in read_psdtags
    key = PsdKey(fh.read(4))
  File "C:\Users\yuriy.l\AppData\Local\Autodesk\3dsMax\2021 - 64bit\ENU\scripts\fraglab_asset_checker_libs\psdtags\psdtags.py", line 265, in __call__
    raise exc
  File "C:\Users\yuriy.l\AppData\Local\Autodesk\3dsMax\2021 - 64bit\ENU\scripts\fraglab_asset_checker_libs\psdtags\psdtags.py", line 257, in __call__
    c = enum.EnumMeta.__call__(cls, *args, **kwds)
  File "C:\Program Files\Autodesk\3ds Max 2021\Python37\lib\enum.py", line 310, in __call__
    return cls.__new__(cls, value)
  File "C:\Program Files\Autodesk\3ds Max 2021\Python37\lib\enum.py", line 564, in __new__
    raise exc
  File "C:\Program Files\Autodesk\3ds Max 2021\Python37\lib\enum.py", line 548, in __new__
    result = cls._missing_(value)
  File "C:\Program Files\Autodesk\3ds Max 2021\Python37\lib\enum.py", line 577, in _missing_
    raise ValueError("%r is not a valid %s" % (value, cls.__name__))
ValueError: b'apOi' is not a valid PsdKey

Please check the attached tif file:
power_stone_core_d_ddna.zip

I'm executing Python 3.7.6 from MaxScript inside 3ds Max 2021
The next line raises the exception:
imageSourceData = psdTagsLibrary.TiffImageSourceData.fromtiff(texturePath)

psdtags ver. 2022.8.25

How to create an empty ImageResources

My tif file is generated from tifffile,
use
TiffImageResources.fromtiff('out.tif')
hint
TIFF file contains no ImageResources tag

How to create an empty ImageResources and assign it?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.