cgohlke / psdtags Goto Github PK
View Code? Open in Web Editor NEWRead and write layered TIFF ImageSourceData and ImageResources tags
Home Page: https://pypi.org/project/psdtags
License: BSD 3-Clause "New" or "Revised" License
Read and write layered TIFF ImageSourceData and ImageResources tags
Home Page: https://pypi.org/project/psdtags
License: BSD 3-Clause "New" or "Revised" License
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
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
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...
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.
I tried multiple approach:
here is the stackoverflow
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
Is is possible to add new layer and import data from another image file ?
or change the image data of existing layer?
Thanks for providing this tool, one question is how to edit the psd file, for example, layer creation and replacement
Can you please provide any example for TIFF layers to png convertion
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()],
)
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?
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 PsdBoolean
s. 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" ;)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.