mdboom / pytoshop

Library for reading and writing Photoshop PSD and PSB files
Other
119 stars 22 forks source link

Can't add image data to layer #16

Closed TravisEvashkevich closed 5 years ago

TravisEvashkevich commented 5 years ago

Description

Trying to make a texture atlassing script using Pillow and pytoshop. The idea would be that I can give the script a bunch of images, read them in with Pillow and then write them to a PSD with pytoshop.

I'm trying to achive -Groups (named) -Named layers in groups with image data.

What I Did

import os
import pip
try:
    print("Log: Trying to Import Pillow.")
    #it might be that we don't have Pillow installed on the system so we should check first
    from PIL import Image
    import numpy
    print ("Log: Imported successfully.")
except ImportError:
    print("Log: Pillow not installed, Installing via Pip")
    pip.main(['install',"Pillow"])
    from PIL import Image
    print("\n\n")

try:
    print("Log: Trying to Import pytoshop.")
    #it might be that we don't have Pillow installed on the system so we should check first
    import pytoshop
    from pytoshop.user import nested_layers
    from pytoshop import enums
    from pytoshop.image_data import ImageData
    print ("Log: Imported successfully.")
except ImportError:
    print("Log: Pytoshop not installed, Installing via Pip")
    pip.main(['install',"six"])
    pip.main(['install',"pytoshop"])
    import pytoshop
    from pytoshop.user import nested_layers
    print("\n\n")

cwd = os.getcwd()
tgas = [x for x in os.listdir(cwd) if x.lower().endswith(".tga")]

with open('AtlasBase-2048.psd', 'rb') as fd:
    psd = pytoshop.PsdFile.read(fd)
    layers = []
    for tga in tgas:
        #print(tga)
        im = Image.open(tga)
        data = ImageData(channels=numpy.array(im))
        newLayer = nested_layers.Image(name=tga.replace(".tga", ""), visible=True, opacity=255, group_id=0,
                                        blend_mode=enums.BlendMode.normal, top=0,
                                        left=0, bottom=im.height, right=im.width, channels=data.channels,
                                        metadata=None, layer_color=0, color_mode=None)
        layers.append(newLayer)

    output = nested_layers.nested_layers_to_psd(layers, color_mode=3, size=(1024, 1024))

    with open('updated.psd', 'wb') as fd:
        output.write(fd)
        psd.write(fd)

Getting an error of

ValueError: Channels in image have different shapes or do not match layer

have tried: -just putting the pillow image into the channels -feeding just specific parts of the image array to channels -not setting bottom/right -not setting size in nested_layers_to_psd call If I don't put an image(data or array) in channels when making a layer it will complete and make the PSD with layers named correctly but not with the needed image data in it.

Current code is just trying to throw the image data into the layer but I'd also like to be able to "position" it as well (if I have 4 x 1024 square images, I'd prefer to be able to have them laid out in a square of the 4 images (top left, top right, bottom left, bottom right). Not sure if this would have to be done via pillow (position then pass data to pytoshop) or if is possible in pytoshop directly

mdboom commented 5 years ago

Thanks for the report.

The main issue here is that the input image data is in the wrong shape. Here's the docs for nested_layers.Image.channels:

        The channel image data. May be one of the following:
        - A dictionary from `enums.ChannelId` to 2-D numpy arrays.
        - A 3-D numpy array of the shape (num_channels, height, width)
        - A list of numpy arrays where each is a channel.

The data that Pillow gives is (height, width, num_channels). You can convert as follows: (It's probably also possible to do using some Numpy striding magic, but it's just as convenient to make a list of channels here):

    im = Image.open(tga)
    arr = numpy.array(im)
    channels = [arr[:,:,0], arr[:,:,1], arr[:,:,2]]
    data = ImageData(channels=numpy.array(im))
    newLayer = nested_layers.Image(name=tga.replace(".tga", ""), visible=True, opacity=255, group_id=0,
                                   blend_mode=enums.BlendMode.normal, top=0,
                                   left=0, channels=channels,
                                   metadata=None, layer_color=0, color_mode=None)

This finishes for me, but I haven't tested the result -- I don't actually have a license to Photoshop right now, if you can believe it... :) (Adobe, are you listening? Want to gift a humble open source developer supporting your product? )

Also: leaving out bottom and right is probably most convenient here, so you don't have to do the math yourself. You should be able to set left and top to whatever you want in order to lay out the layers how you'd like.

Lastly, I see that you are reading in a psd file, and then writing it out after the new layers you've created. This probably creates an invalid PSD file (you are basically putting the literal contents of two PSD Files into one). Are you trying to add the layers to an existing file? In that case, you'll want to read the PSD, convert it to nested layers using nested_layers.psd_to_nested_layers, add your new layers to that structure, and then convert back using nested_layers.nested_layers_to_psd and then write that out.

TravisEvashkevich commented 5 years ago

Works, awesome. Yeah I was trying to figure out how to create a new PSD from scratch with the dimensions and stuff I want (which I still haven't figured out) but other than that it's all working great now! Thanks. I might try to get a PR together for adding some more examples and such (as I had a few other experienced TA's looking at this and we were scratching our heads trying to get things going...)

mdboom commented 5 years ago

Great! A PR would be very welcome.

TravisEvashkevich commented 5 years ago

is there a way to (or need to even) specify PSD size or does it just go by the biggest layers shape? as in if I have 4 1024 sized images being put into the psd but I want to lay them out so it's a 2x2, do I have make a psd explicitly 2048 or will just doing the output.write(fd) line just determine that automatically? Edit: Just got it working without having to specify. Answered my own question. Thanks!

TravisEvashkevich commented 5 years ago

I never got around to a PR, but here is a gist of how I got some example and helpful functions to get things running.

magnanta commented 5 years ago

what is the color mode in nested_layers.nested_layers_to_psd(layers, color_mode=3,) if we want to save RGBA in psd .if i used color mode as 3 it writting only RGB in psd .