Search code examples
pythonphotoshopwin32com

Exception occured: Illegal Argument - Argument 2, Object Expected (Making a Photoshop contact sheet using win32com)


I'm trying to make a Photoshop contact sheet with the MakeContactSheet function included in Photoshop's API. It can be accessed using 'win32com.client'.

My first line: psApp = Dispatch("Photoshop.Application")

Creates a win32com.gen_py.E891EE9A-D0AE-4CB4-8871-F92C0109F18Ex0x1x0._Application._Application object.

The class of this object seems to have all the available functions listed in the documentation .

I then proceed to make a list of strings with os.walk.

CSInputFiles = [path.join(r, f) for r, sd, fs in walk('C:\\Users\\chris\\Desktop\\1A') for f in fs]

Then a mixed array of options:

CSoptions = [True, psApp, False, False, 6, True, None, 0, 7, 4, 3, 300, 3, None, True]

Finally I pass these arguments: psApp.MakeContactSheet(CSInpuFiles, CSoptions)

Which seems to be right considering the function definition in _Application:

def MakeContactSheet(self, InputFiles=defaultNamedNotOptArg, Options=defaultNamedOptArg):
    'create a contact sheet from multiple files'
    # Result is a Unicode object
    return self._oleobj_.InvokeTypes(1129599816, LCID, 1, (8, 0), ((12, 1), (12, 17)),InputFiles
        , Options)

Alas, I get the following error:

Traceback (most recent call last):
  File "C:\Users\chris\Desktop\test.py", line 17, in <module>
    psApp.MakeContactSheet(CSInputFiles, CSoptions)
  File "C:\Users\chris\AppData\Local\Temp\gen_py\3.9\E891EE9A-D0AE-4CB4-8871-F92C0109F18Ex0x1x0\_Application.py", line 97, in MakeContactSheet
    return self._oleobj_.InvokeTypes(1129599816, LCID, 1, (8, 0), ((12, 1), (12, 17)),InputFiles
pywintypes.com_error: (-2147352567, 'Exception occurred.', (0, 'Adobe Photoshop', 'Illegal argument - argument 2\n- Object expected', None, 0, -2147220261), None)

My initial instinct was to convert all the string in my csInputFiles array to path objects with pathlib.

from pathlib import Path

CSInputFiles = [Path(path.join(r, f)) for r, sd, fs in walk('C:\\Users\\chris\\Desktop\\1A') for f in fs]

Which yielded this obscure piece of garbage when I passed the array to the function:

psApp.MakeContactSheet(CSInputFiles, CSoptions)

#RUN!

File "C:\Users\chris\Desktop\test.py", line 17, in <module>
    psApp.MakeContactSheet(CSInputFiles, CSoptions)
  File "C:\Users\chris\AppData\Local\Temp\gen_py\3.9\E891EE9A-D0AE-4CB4-8871-F92C0109F18Ex0x1x0\_Application.py", line 97, in MakeContactSheet
    return self._oleobj_.InvokeTypes(1129599816, LCID, 1, (8, 0), ((12, 1), (12, 17)),InputFiles
TypeError: must be real number, not WindowsPath

Which doesn't make sense at all! How could it be expecting a real number? This is meant to be an array of input files!


Solution

  • The error specified in the question was caused by the fact that I made the options argument a list as opposed to an object. For information about the structure of the object you can look here.

    However after some asking around on GitHub I found that MakeContactSheet is a deprecated function as stated on page 19 of this document.

    This lead me develop a replacement of sorts using the pillow library.

    The solution is to simply create an empty png and then paste all the images you want to put on your contact sheet onto that png.

    I will paste a simplified version of this program, for the sake of solving the problem here.

    from PIL import Image
    from os import walk, path
    
    #rows = amount of rows (int)
    #columns = amount of clomns (int)
    #sheetWidth = the width of the contact sheet in inch (int)
    #sheetHeight = the height of the contact sheet in inch (int)
    #aspectRatio = the simplified height to width ratio of the images on the contact sheet (height/width) (float)
    #ppi = pixels per inch
    #path = the folder containing all the images you wish to make a contact sheet of (string)
    
    def make_contact_sheet(rows, columns, sheetWidth, sheetHeight, aspectRatio, ppi, filePath):
    
        #convert inches to pixels
        sheetWidth *= ppi
        sheetHeight *= ppi
    
        #Calculate the size that images will be on the sheet
        imageHeight = sheetHeight // columns
        imageWidth = int(imageHeight * aspectRatio)
    
        #Calculate how far apart the images will be spaced (I call this padding)
        xRemSpace = sheetWidth - (imageWidth * columns)
        xPad = xRemSpace // (columns-1)
    
        yRemSpace = sheetHeight - (imageHeight * rows)
        yPad = yRemSpace // (rows+1)
    
        #Now make the empty png
        sheet = Image.new("RGBA", (sheetWidth, sheetHeight), color=(0,0,0,255))
    
        #Loop through paths of the images and paste the images one by one
        x, y = 0, yPad
        images = [Image.open(path.join(r,f)) for r,sd,fs in walk(filePath) for f in fs]
    
        for i in range(len(images)):
            #Resize the image
            resized = images[i].resize((imageWidth, imageHeight))
    
            #Paste the images
            if i == 0:
                sheet.paste(resized, (x,y))
            elif i % columns == 0:
                x = 0
                y += imageHeight + yPad
                sheet.paste(resized, (x,y))
            else:
                x += imageWidth + xPad
                sheet.paste(resized, (x,y))
    
        return sheet
    
    if __name__ == "__main__":
        sheet = make_contact_sheet(3, 5, 8, 5, (2/3), 300, r"Some path")
        sheet.show()
    

    As stated above this is a very crude example, and it is based off a more elaborate version of the final program I wrote. But I've already overstepped the bounds of relevance.