pythonimagemagickwand

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


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.


Solution

  • 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'))