Create a multi layer tiff without visible modification in wand/imagemagic/python/js

Question:

I need to create a multi layer tif and when I open in photoshop in need to be as separated layers like in the picture:

photoshop with tif

I need to do it without photoshop so I though so that I can do with Wand in python:

from wand.image import Image

with Image(filename="./GPD_TEST_Product.png") as img:
    img.read(filename="./GPD_TEST_Reflection.png")
    img.read(filename="./GPD_TEST_Shadow.png")
    img.read(filename="./GPD_TEST_Background.png")  
    img.save(filename="final.tif")

but when I open it with photoshop I got:

tif without laters

but when I open it with preview on mac I can see the layers…

layers in preview

My other idea is to create a psd with layers and try to convert it to .tif with imagemagic, but still when I open it with photoshop, is merged.

here is the file which I’m working on:
https://drive.google.com/drive/folders/1ioWG2rJ32FVstUYJK8ITuhmGSUr7I_Ps?fbclid=IwAR1HtNsChsr6befuUTPHklqKREtwxCMLk39cRhbeshio_MU0fGU-P35kxlU

Any help will be appreciate.

Asked By: konradns

||

Answers:

The layer and thumbnail image and metadata of layered TIFF files are stored in the ImageResources (#34377) and ImageSourceData (#37724) TIFF tags. The psdtags Python library can read and write those tags but creating the tags from scratch requires knowledge of the format. Something like this should work (I don’t have Photoshop to verify):

from psdtags import *
from tifffile import imwrite
from imagecodecs import imread

background = imread('GPD_TEST_Background.png')
product = imread('GPD_TEST_Product.png')
reflection = imread('GPD_TEST_Reflection.png')
shadow = imread('GPD_TEST_Shadow.png')

image_source_data = TiffImageSourceData(
    name='Layered TIFF Test',
    psdformat=PsdFormat.LE32BIT,
    layers=PsdLayers(
        key=PsdKey.LAYER,
        has_transparency=False,
        layers=[
            PsdLayer(
                name='Background',
                rectangle=PsdRectangle(0, 0, *background.shape[:2]),
                channels=[
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL0,
                        compression=PsdCompressionType.RLE,
                        data=background[..., 0],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL1,
                        compression=PsdCompressionType.RLE,
                        data=background[..., 1],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL2,
                        compression=PsdCompressionType.RLE,
                        data=background[..., 2],
                    ),
                ],
                mask=PsdLayerMask(),
                opacity=255,
                blendmode=PsdBlendMode.NORMAL,
                blending_ranges=(),
                clipping=PsdClippingType.BASE,
                flags=PsdLayerFlag.PHOTOSHOP5
                | PsdLayerFlag.TRANSPARENCY_PROTECTED,
                info=[
                    PsdString(PsdKey.UNICODE_LAYER_NAME, 'Background'),
                ],
            ),
            PsdLayer(
                name='Reflection',
                rectangle=PsdRectangle(0, 0, *reflection.shape[:2]),
                channels=[
                    PsdChannel(
                        channelid=PsdChannelId.TRANSPARENCY_MASK,
                        compression=PsdCompressionType.RLE,
                        data=reflection[..., 3],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL0,
                        compression=PsdCompressionType.RLE,
                        data=reflection[..., 0],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL1,
                        compression=PsdCompressionType.RLE,
                        data=reflection[..., 1],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL2,
                        compression=PsdCompressionType.RLE,
                        data=reflection[..., 2],
                    ),
                ],
                mask=PsdLayerMask(),
                opacity=255,
                blendmode=PsdBlendMode.NORMAL,
                blending_ranges=(),
                clipping=PsdClippingType.BASE,
                flags=PsdLayerFlag.PHOTOSHOP5,
                info=[
                    PsdString(PsdKey.UNICODE_LAYER_NAME, 'Reflection'),
                ],
            ),
            PsdLayer(
                name='Shadow',
                rectangle=PsdRectangle(0, 0, *shadow.shape[:2]),
                channels=[
                    PsdChannel(
                        channelid=PsdChannelId.TRANSPARENCY_MASK,
                        compression=PsdCompressionType.RLE,
                        data=shadow[..., 3],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL0,
                        compression=PsdCompressionType.RLE,
                        data=shadow[..., 0],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL1,
                        compression=PsdCompressionType.RLE,
                        data=shadow[..., 1],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL2,
                        compression=PsdCompressionType.RLE,
                        data=shadow[..., 2],
                    ),
                ],
                mask=PsdLayerMask(),
                opacity=255,
                blendmode=PsdBlendMode.NORMAL,
                blending_ranges=(),
                clipping=PsdClippingType.BASE,
                flags=PsdLayerFlag.PHOTOSHOP5,
                info=[
                    PsdString(PsdKey.UNICODE_LAYER_NAME, 'Shadow'),
                ],
            ),
            PsdLayer(
                name='Product',
                rectangle=PsdRectangle(0, 0, *product.shape[:2]),
                channels=[
                    PsdChannel(
                        channelid=PsdChannelId.TRANSPARENCY_MASK,
                        compression=PsdCompressionType.RLE,
                        data=product[..., 3],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL0,
                        compression=PsdCompressionType.RLE,
                        data=product[..., 0],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL1,
                        compression=PsdCompressionType.RLE,
                        data=product[..., 1],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL2,
                        compression=PsdCompressionType.RLE,
                        data=product[..., 2],
                    ),
                ],
                mask=PsdLayerMask(),
                opacity=255,
                blendmode=PsdBlendMode.NORMAL,
                blending_ranges=(),
                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,
        ),
    ],
)

imwrite(
    'LayeredTiffTest.tif',
    background,
    photometric='rgb',
    metadata=None,
    extratags=[image_source_data.tifftag()],
)

print(TiffImageSourceData.fromtiff('LayeredTiffTest.tif'))
Answered By: cgohlke

If anyone need generating tiff with layers and thumbnail is not white background but composite image here is my solution:

from psdtags import *
from tifffile import imwrite
from imagecodecs import imread
import numpy as np
from PIL import Image


# backgroundImage = Image.open('GPD_TEST_Background.png')
productImage = Image.open('GPD_TEST_Product.png')
reflectionImage = Image.open('GPD_TEST_Reflection.png')
shadowImage = Image.open('GPD_TEST_Shadow.png')

w, h = productImage.size

# create background with defined color
backgroundImage = Image.new(mode="RGBA", size=(w,h), color=(255, 255, 255,255))

background = np.array(backgroundImage)
product = np.array(productImage)
reflection = np.array(reflectionImage)
shadow = np.array(shadowImage)

#thumbnail
thumbnail = Image.alpha_composite(backgroundImage, reflectionImage)
thumbnail = Image.alpha_composite(thumbnail, shadowImage)
thumbnail = Image.alpha_composite(thumbnail, productImage)
thumbnail = np.array(thumbnail)

image_source_data = TiffImageSourceData(
    name='Layered TIFF Test',
    psdformat=PsdFormat.LE32BIT,
    layers=PsdLayers(
        key=PsdKey.LAYER,
        has_transparency=False,
        layers=[
            PsdLayer(
                name='Background',
                rectangle=PsdRectangle(0, 0, *background.shape[:2]),
                channels=[
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL0,
                        compression=PsdCompressionType.RLE,
                        data=background[..., 0],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL1,
                        compression=PsdCompressionType.RLE,
                        data=background[..., 1],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL2,
                        compression=PsdCompressionType.RLE,
                        data=background[..., 2],
                    ),
                ],
                mask=PsdLayerMask(),
                opacity=255,
                blendmode=PsdBlendMode.NORMAL,
                blending_ranges=(),
                clipping=PsdClippingType.BASE,
                flags=PsdLayerFlag.PHOTOSHOP5
                | PsdLayerFlag.TRANSPARENCY_PROTECTED,
                info=[
                    PsdString(PsdKey.UNICODE_LAYER_NAME, 'Background'),
                ],
            ),
            PsdLayer(
                name='Reflection',
                rectangle=PsdRectangle(0, 0, *reflection.shape[:2]),
                channels=[
                    PsdChannel(
                        channelid=PsdChannelId.TRANSPARENCY_MASK,
                        compression=PsdCompressionType.RLE,
                        data=reflection[..., 3],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL0,
                        compression=PsdCompressionType.RLE,
                        data=reflection[..., 0],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL1,
                        compression=PsdCompressionType.RLE,
                        data=reflection[..., 1],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL2,
                        compression=PsdCompressionType.RLE,
                        data=reflection[..., 2],
                    ),
                ],
                mask=PsdLayerMask(),
                opacity=255,
                blendmode=PsdBlendMode.NORMAL,
                blending_ranges=(),
                clipping=PsdClippingType.BASE,
                flags=PsdLayerFlag.PHOTOSHOP5,
                info=[
                    PsdString(PsdKey.UNICODE_LAYER_NAME, 'Reflection'),
                ],
            ),
            PsdLayer(
                name='Shadow',
                rectangle=PsdRectangle(0, 0, *shadow.shape[:2]),
                channels=[
                    PsdChannel(
                        channelid=PsdChannelId.TRANSPARENCY_MASK,
                        compression=PsdCompressionType.RLE,
                        data=shadow[..., 3],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL0,
                        compression=PsdCompressionType.RLE,
                        data=shadow[..., 0],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL1,
                        compression=PsdCompressionType.RLE,
                        data=shadow[..., 1],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL2,
                        compression=PsdCompressionType.RLE,
                        data=shadow[..., 2],
                    ),
                ],
                mask=PsdLayerMask(),
                opacity=255,
                blendmode=PsdBlendMode.NORMAL,
                blending_ranges=(),
                clipping=PsdClippingType.BASE,
                flags=PsdLayerFlag.PHOTOSHOP5,
                info=[
                    PsdString(PsdKey.UNICODE_LAYER_NAME, 'Shadow'),
                ],
            ),
            PsdLayer(
                name='Product',
                rectangle=PsdRectangle(0, 0, *product.shape[:2]),
                channels=[
                    PsdChannel(
                        channelid=PsdChannelId.TRANSPARENCY_MASK,
                        compression=PsdCompressionType.RLE,
                        data=product[..., 3],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL0,
                        compression=PsdCompressionType.RLE,
                        data=product[..., 0],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL1,
                        compression=PsdCompressionType.RLE,
                        data=product[..., 1],
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL2,
                        compression=PsdCompressionType.RLE,
                        data=product[..., 2],
                    ),
                ],
                mask=PsdLayerMask(),
                opacity=255,
                blendmode=PsdBlendMode.NORMAL,
                blending_ranges=(),
                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,
        ),
    ],
)


imwrite(
    'LayeredTiffTest.tif',
    thumbnail,
    photometric='rgb',
    metadata=None,
    extratags=[image_source_data.tifftag()],
)

print(TiffImageSourceData.fromtiff('LayeredTiffTest.tif'))

What do you think about it?

Answered By: konradns
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.