0

I need to create an automation to join two images in a layered file (layered as in Photoshop layers) to further process the images through batching inside Photoshop. I've tried ImageMagick, psd_tools, PIL and some other things. Most of those create either a single layered file (even if it's a TIF, for example) or a multi-page file. Photoshop won't recognize this multi-page file and read only the first image, which makes the file useless to me.

I was able to create a layered file just as I need it to be with layeredimage.io but the only format it seems to support as a multi-layer is .ORA (open raster) which is NOT supported by Photoshop. Gimp can handle it. By the way, layeredimage.io seems to be able to save a layered PSD, but returns a "not implemented" error. I also could not save a layered file in any other format (xcf or tif, for example).

So, at this point I'm in kind of a pickle. I have two options:

  1. Open the file in Gimp and via Scriptfu saving it in XCF or PSD;
  2. Use the gimpfu import to operate it inside the python script I'm creating.

Each has its own problems:

Option 1: Opening the file in gimp and a Scriptfu to save it in a different format

I can't seem to understand how this scriptfu works or find more detailed information on the parameters to the save-psd command. I keep running into an error related to the "image" variable. I just need to open the file (which I can do inside the python script) and run one command (export as)

Option 2: Import gimpfu and operate gimp from inside the python script

I've seen it's a common issue, but I couldn't find an answer that works for me. The from gimpfu import * returns an error: ModuleNotFoundError: No module named 'gimpfu'. I'm currently running the script in my Fedora which has gimp and Python 2.x installed and through a quick search I could find the gimpfu.py file inside usr/lib64/gimp/2.0/python which means it exists in my computer. I have also tested in my Windows machine (which will be used for production) and it didn't work also.

Below, the script so far:

import layeredimage.io as layered
from PIL import Image
# from gimpfu import * 

# TODO: FIND A WAY TO MAKE VSCODE READ GIMPFU IN /USR/LIB64/GIMP/2.0/PYTHON/GIMPFU.PY

def png_to_tif(file_name):
    """
    Converts a PNG file to TIF and returns the TIFF file path
    """
    png = Image.open(file_name)
    tif_name = file_name.replace("png", "tif")
    tiff = png.save(tif_name)
    return tif_name


base_file = layered.openLayerImage("/home/user/Pictures/imaginer/test1.tif")
imageDimensions = base_file.dimensions

mask_png = "/home/user/Pictures/imaginer/alpha.png"
mask_tif = png_to_tif(mask_png)
mask = Image.open(mask_tif)

img_layered = base_file.insertImageAsLayer(mask,'python',1)

ora_file = "/home/user/Pictures/imaginer/pqpBR.ora"

layered.saveLayer_ORA(ora_file, base_file)

I've tried checking if I had all the packages needed, tried alternative ways to interact with gimp and so far, nothing has worked.


Some Aditional Information:

Aparently I had some misconceptions and actually by trying to simplify a bit my explanation, I guess I might have left some important information out.

I have created a GoogleDive Folder to share a real-life example.

What is this and why do I need this: This is an attempt to automate the very first step of an image retouching process which is masking out the non-important things in the image. I'm a product photographer and I do know a bit of programming and I am now studying more Python in order to make some projects become reality. I've come across a way to automate the generation of a mask that is 95% perfect, hence the need of refining it inside Photoshop. Inside photoshop I can use a tool called "refine edge" which will compare the edges of the mask with the actual image and try make the mask coincide with the edges of the product.
The first step of the image editing that I have to go through is masking out the backgrounds and isolating the product in a transparent background. In this part of the process, the generation of the mask is the most time consuming process, which now I can do semi-automatically.

About the Process

The base file (currently) is a PSD file, but it could, if needed, become another image format as long as it is an image format that retains the original information without compressing (which rules out JPG, for example) or with minimum compressing. (Later, images like this one provided go through processes like color changing, montage, etc)

The mask provided to me is a grayscale PNG but it's kind of easy to me to batch process them into RGB files. In the GoogleDive Folder I uploaded an example of the original mask and the RGB version.

I wasn't mentioning this mess with file formats because I though it wouldn't be a huge deal. But apparently from a programming side of things it is.

What do I need to figure out

I have automated the generation of the mask. I have automated the refinement and application of the mask (inside Photoshop). I now need a way to join the two files into one layered file (the order of the layers does not matter since I can rewrite/adapt the Photoshop automation easily). This middle step cost me hours weekly (although it's already better than manually making the masks) and when the number of files increase too much, it becomes impossíble for me to do by myself.


About Mr. fmw42's answer

If you are reading this in the future or if you got to this thread by googling something or whatever: Frustrated with the fact that I couldn't get the results, I decided to download the example images he sent and try to reproduce the results: It works! His answer is actually very accurate and produce the desired results. However, when I apply the same commands in my own files, it does not work. I guess I'll have to dig deeper into file specifications. For now, these are the specifications of the files:

PSD/TIF(Layer1):

  • Software used to generate the file: Capture One Pro 20
  • 16bit
  • Color space: AdobeRGB
  • Size: 3151x4758px

PNG(Layer2):

  • 8bit
  • Color space: L
  • Size: 3151x4758px

I did try converting the PNG to 16bit RGB (so that both images would be equal in specifications) and applying the command, with no good results so far.

Most recent command being used: magick _AUG1017.psd -delete 0 _AUG1017_1_maskRGB.png \( -clone 0--1 -flatten \) -reverse magick_layered.psd

augustoL
  • 3
  • 3
  • Imagemagick can create layered PSD images that may work better than TIFF, though not fully layered as in Photoshop. Imagemagick cannot create true layered TIFFs only multipage TIFFs. – fmw42 Aug 22 '23 at 23:52
  • layered PSD? I have only been able to compose two images into a single layered PSD with magick so far. do you have any resource on that? that might actually solve my problem. – augustoL Aug 23 '23 at 00:28
  • [psdtags](https://pypi.org/project/psdtags/) and tifffile are able to write layered TIFF. Create the type of layered TIFF of interest in Photoshop first, then inspect the structures of the TIFF and tags with psdtags/tifffile, and use them as templates to insert your layers... The structures can get very complicated and not all details are documented. See the [layered_tiff.py](https://github.com/cgohlke/psdtags/blob/master/examples/layered_tiff.py) example. – cgohlke Aug 23 '23 at 03:16
  • I do not understand what you are masking and where in your Photoshop process. If you can explain that better, I can perhaps show how to do that in Imagemagick. – fmw42 Aug 23 '23 at 18:10

2 Answers2

0

In Imagemagick, to create a simple layered PSD, you must create the PSD flattened layer. You also must reverse the order of images. So for

Input:

enter image description here

enter image description here

enter image description here

enter image description here

Unix IM 6 Syntax:

convert lena.jpg mandril3.jpg zelda1.jpg redhat.jpg \( -clone 0-3 -flatten \) -reverse layered.psd

Resulting Layers PSD (zip file):

For Windows, remove the \s from the parentheses. For IM 7, change convert to magick

ADDITION

Let's add the following image to the resulting PSD file above. Note that one has to remove the previous psd flattened layer, add the new file, then recreate the new flattened layer.

New layer image:

enter image description here

convert layered.psd -delete 0 barn.jpg \( -clone 0--1 -flatten \) -reverse layered2.psd

The result is (zip file):

Note that 0--1 means 0 to -1 where -1 means the last image and 0 means the first image in the command line (after we have deleted the previous first image).

fmw42
  • 46,825
  • 10
  • 62
  • 80
  • Ok, so... I've tried it and got some... strange results. I created a [GoogleDrive Folder](https://drive.google.com/drive/folders/1p4rXNDAV80-iBZY4F-w7C1XnDdD-YfA6?usp=drive_link) to share the results. The comand `magick _AUG0932.psd _AUG0932_0_maskRGB.png \( -clone 0-1 -flatten \) -reverse layered.psd` resulted in a psd file that has its first layer doubled (there is a print of the file opened in gimp in the folder) and photoshop can't read this file. both images have the same size. the original mask is in grayscale, but I can easily convert it to rgb. the order of layers is not important. – augustoL Aug 23 '23 at 11:43
  • Your first image is a psd file. So Imagemagick will extract the flattened image as the first image, then all the actual layers. Looks like you are just trying to add png to the psd file. Is that correct? Your question was about creating a psd from Individual images. So you need two different commands. Your command to just add the png is not correct if my assumption is correct, because you need to update the flattened layer (actual delete the original and recreate from the all the layers including the new png) – fmw42 Aug 23 '23 at 15:06
  • See my ADDITION in my answer above for how to add a new layer image to an existing PSD file. – fmw42 Aug 23 '23 at 16:32
  • I have also added some aditional information in my original post about the process and what it is. I have quickly read through your adition and I definatly will try it later today. Thanks a lot. I'll be back with the results. – augustoL Aug 23 '23 at 16:46
  • No success. I created a subfolder in the Google Drive Linked above with the original files and the results. I used the PSD for this test and the RGB version of the mask. The resulting PSD is not readable by Gimp and Photoshop can only see the L1 layer. There might be something with the way Adobe and Phase One encode their PSDs and TIFFs. I tryed plugging a Adobe TiFF to the script above (that was working before) and it couldn't even open it. – augustoL Aug 24 '23 at 00:11
  • Could this be because I'm using ImageMagick 7.x? – augustoL Aug 24 '23 at 00:13
  • With Imagemagick 7, just use magick in place of convert. There should be no changes needed. If you want further help, provide more information about your masking requirements and your image formats and your Imagemagick command – fmw42 Aug 24 '23 at 01:11
  • `magick _AUG1017.psd -delete 0 _AUG1017_1_maskRGB.png \( -clone 0--1 -flatten \) -reverse magick_layered.psd` was the command I used. right now there is no need to mask it. I just need to render a usable file with two layers: 1- the original PSD and 2- the mask provided which is a PNG file. – augustoL Aug 24 '23 at 01:28
  • So what is wrong with that command? I do not understand what you mean by two layers where the first is the original PSD. That PSD has multiple layers in it? Perhaps you need to show me your two images -- the original PSD and the mask image. – fmw42 Aug 24 '23 at 16:11
  • I added a "HowItShouldBe.psd" to the Google Drive/Attempt2 (link in original post) so that I can exemplify what it is that I need. I addded some more details about the files in the post and an answer explaining a bit more. I actually tried the command in the files you used as an example and it worked. I'm now suspecting this is not working because my files are 16bit. You understand Magick in depth, perhaps you know if this might be the reason why. – augustoL Aug 24 '23 at 17:24
  • 16 bit files should be fine in Imagemagick. But I don't know about PSD files. Let me look at your google drive data. – fmw42 Aug 24 '23 at 17:44
  • Please explain which two files you are trying to combine and what you expect for the result. If you can do what you want in Photoshop, then provide the resulting PSD file. – fmw42 Aug 24 '23 at 17:47
  • I'm trying to combine `_AUG1017.psd` and `_AUG1017_1_mask.png`. the expected result should be `HowItShouldBe.psd` All files inside GoogleDrive/Attempt2 – augustoL Aug 24 '23 at 18:34
  • Let me know if this is what you want. There are two issues. First, your mask is binary with values 0 and 1. Second, I think you need to convert your images to 8-bit per channel. So you must multiply the mask by 255 and set the -depth to 8. `convert \( _AUG0932_0_mask.png -evaluate multiply 255 \) _AUG0932.psd -depth 8 \( -clone 0--1 -flatten \) -reverse x.psd` – fmw42 Aug 24 '23 at 18:58
  • I can change the PNG to a RGB or a grayscale file. I cannot bring the PSD down to 8bit. This will cause damage to later processes that these images go through. – augustoL Aug 24 '23 at 19:38
  • This works for me as 16-bit. `convert \( _AUG0932_0_mask.png -evaluate multiply 65535 \) _AUG0932.psd -depth 16 \( -clone 0--1 -flatten \) -reverse y.psd` – fmw42 Aug 24 '23 at 19:52
  • I was testing also cgohlke's script and I found that my Fedora machine was returning weird results also with his/her script. I tried it in my Windows machine and it worked. This last command you sent was better, but the image rendered was in grayscale. I'm now suspecting my computer is the issue. I wont mbe able to install magick there today, though. But I'll definitely test this weekend. There might be something weird happening around here. I'll be back with the results. But if it's working there for you, well, it's working. Thanks a lot for all the help!!! – augustoL Aug 24 '23 at 21:31
  • That was a Unix syntax command. For Window syntax remove the backslashed from before the opening and closing parentheses. – fmw42 Aug 24 '23 at 21:55
0

This Python script creates a layered TIFF file from product and mask images using the psdtags (>=2023.8.24), tifffile, and imagecodecs libraries:

from __future__ import annotations

import numpy
import imagecodecs
import tifffile

from psdtags import (
    PsdBlendMode,
    PsdChannel,
    PsdChannelId,
    PsdColorSpaceType,
    PsdCompressionType,
    PsdFormat,
    PsdKey,
    PsdLayer,
    PsdLayers,
    PsdRectangle,
    PsdUserMask,
    TiffImageSourceData,
)

# read individual layer images from files
product: numpy.ndarray = imagecodecs.imread('product.png')
mask: numpy.ndarray = imagecodecs.imread('mask.png')

assert product.shape[2] == 3
assert product.dtype == numpy.uint16
assert mask.shape == product.shape[:2]
assert mask.dtype == numpy.uint8

mask = mask.astype(numpy.uint16)
mask *= 257

# create the ImageSourceData structure for the layered TIFF
image_source_data = TiffImageSourceData(
    name='Masked product',
    psdformat=PsdFormat.LE32BIT,
    layers=PsdLayers(
        key=PsdKey.LAYER_16,
        has_transparency=False,
        layers=[
            PsdLayer(
                name='Product',
                channels=[
                    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],
                    ),
                ],
                rectangle=PsdRectangle(0, 0, *product.shape[:2]),
                blendmode=PsdBlendMode.NORMAL,
            ),
            PsdLayer(
                name='Mask',
                channels=[
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL0,
                        compression=PsdCompressionType.ZIP_PREDICTED,
                        data=mask,
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL1,
                        compression=PsdCompressionType.ZIP_PREDICTED,
                        data=mask,
                    ),
                    PsdChannel(
                        channelid=PsdChannelId.CHANNEL2,
                        compression=PsdCompressionType.ZIP_PREDICTED,
                        data=mask,
                    ),
                ],
                rectangle=PsdRectangle(0, 0, *mask.shape),
                blendmode=PsdBlendMode.MULTIPLY,
            ),
        ],
    ),
    usermask=PsdUserMask(
        colorspace=PsdColorSpaceType.RGB,
        components=(65535, 0, 0, 0),
        opacity=50,
    ),
)


# merge layers
image = product * mask.astype('?')[..., numpy.newaxis]
# alternative RGBA image:
# image = numpy.empty(shape=product.shape[:2] + (4,), dtype=product.dtype)
# image[..., :3] = product
# image[..., 3] = mask

# write a layered TIFF file
tifffile.imwrite(
    'masked_product.tif',
    image,
    photometric='rgb',
    compression='adobe_deflate',
    predictor='horizontal',
    resolution=((3000000, 10000), (3000000, 10000)),
    resolutionunit='inch',
    metadata=None,
    extratags=[image_source_data.tifftag()],
)

enter image description here

cgohlke
  • 9,142
  • 2
  • 33
  • 36
  • For some reason in my Fedora machine it was returning some weird results. I don't if there is a conflict with anything or what. The fact is that is works wonders on mi windows machine which is where I need it to work anyway. Thanks a lot! – augustoL Aug 24 '23 at 21:26