0

I'm trying to make a python program that can run Photoshop Plugins by using this DLL library from Spetric - https://github.com/spetric/Photoshop-Plugin-Host

I need to send an image loaded by opencv to the dll (while I can read c++ I cannot program in it and have never been able to get the dll to compile for edits such as loading the image directly from a filename)

The description of the function is

pspiSetImage(TImgType type, int width, int height, void *imageBuff, int imageStride, void *alphaBuff = 0, int alphaStride = 0);

I have tried several suggestions and solutions found here on stackoverflow but they all result in the same thing "OSError: exception: access violation writing 0x00000000"

Which is weird because I thought the idea was to pass in a pointer to a long buffer of data and that number wouldn't be 0. I've checked the value/output and it never is zero that I pass in.

I have tried accessing the __array_interface++['data'][0], using ctypes.data_as built into the numpy object, various versions of POINTER and c_void_p. My fundamental misunderstanding of what's needed and how to get it and pass it is my problem.

Are there any suggestions or helpful hints to point me in the right direction?

EDIT: Here is the code I'm currently working with

import ctypes
import cv2

plugins = {}

def main(args=None): 
    plugin_host = ctypes.CDLL('.\\pspiHost.dll')

    plugin_host.pspiSetPath(".\\8bf filters\\")

    # Any image will do, I used a PNG to test alpha transparency
    im = cv2.imread('.\\fish.png', cv2.IMREAD_UNCHANGED)

    array = im.ctypes.data_as(ctypes.POINTER(ctypes.c_void_p))
    width = im.shape[0]
    height = im.shape[1]

    # 1 in first parameter is for RGBA format, I'm using a png with transparency
    plugin_host.pspiSetImage(ctypes.c_int(1), 
                             ctypes.c_int(width), 
                             ctypes.c_int(height), 
                             array, 
                             ctypes.c_int(0))

if __name__=='__main__':
    main()
Christoph Rackwitz
  • 11,317
  • 4
  • 27
  • 36
  • got a [mre]? looks like a purely ctypes+numpy question. – Christoph Rackwitz Oct 01 '22 at 19:04
  • @ChristophRackwitz - Sorry, yes I've just edited the question with a copy of my current test code. It's already very minimal so I just posted the whole thing. – LeviFiction Oct 01 '22 at 20:26
  • https://stackoverflow.com/questions/3195660/how-to-use-numpy-array-with-ctypes – Christoph Rackwitz Oct 01 '22 at 23:37
  • @ChristophRackwitz - Thank you for your reply. Yeah the stride 0 is supposedly "Auto" which is the default. Or literally width * size of elements. I read the stride is supposed to include any buffer data, but I couldn't find stride information in the Python CV2 image information. As far as I know PNG doesn't use buffers to fit a "divisible by 4" row size like BMP does. One of many reasons I chose that format to test with until I can learn more. I originally used byref, then tried the data_as and they didn't work. My current code is just me playing randomly. I'll check out the link. – LeviFiction Oct 02 '22 at 02:30
  • 1
    Specify all parameters when calling pspiSetImage() and the stride should not be zero. Use im.strides[0] for the stride. Also cast like ```im.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8))```. – relent95 Oct 02 '22 at 03:52
  • @relent95 - Thank you very much. Not sure why I couldn't find strides previously, must have done something stupid to have missed it. And that worked, looks like it was the leaving out of the last two parameters. I assumed since they had default values I didn't need them. It's always the simple things I overlook or assumptions I make. My bad. But it also means that all solutions I'd tried previously now work. Now to try the next steps and confuse myself even more. Thanks again. – LeviFiction Oct 02 '22 at 04:41
  • @relent95 - Since your solution worked. Would you post it as the answer so I can accept it? Thanks. – LeviFiction Oct 02 '22 at 04:44
  • I don't want to setup the testing environment. Why not answering your own question? – relent95 Oct 02 '22 at 08:12
  • you entirely misunderstood "buffer" in the context of *strides*. that has nothing at all to do with the compressed image. you aren't dealing with that anyway, but with an uncompressed format in memory. you wouldn't find anything useful in the OpenCV docs either because you are dealing with numpy arrays instead. – Christoph Rackwitz Oct 02 '22 at 11:07
  • @relent95 - why would you need to set up a test environment to add the exact same text as an answer? Does stack require proof you tested before you answered? – LeviFiction Oct 02 '22 at 14:43

1 Answers1

0

This is a duplicate of [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer), but I'm going to detail.

When working with CTypes, the .dll must export functions using C compatible interface (extern "C"), while pspiSetImage seems to be C++ (default arguments).

Also check [SO]: How to write a function in C which takes as input two matrices as numpy array (@CristiFati's answer) for details converting NumPy arrays.

Here's a snippet that should fix things.

import ctypes as cts
import cv2
import numpy as np


plugin_host = ctypes.CDLL(r".\pspiHost.dll")

pspiSetPath = plugin_host.pspiSetPath
pspiSetPath.argtypes = (cts.c_wchar_p,)  # Got this from the passed argument
pspiSetPath.restype = None  # void return?

# ...

img = cv2.imread(r".\fish.png", cv2.IMREAD_UNCHANGED)

# ...

pspiSetImage = plugin_host.pspiSetImage
pspiSetImage.argtypes = (
    cts.c_int, cts.c_int, cts.c_int,
    np.ctypeslib.ndpointer(dtype=img.dtype, shape=img.shape, flags="C"), #cts.c_void_p,  # Extra checks for NumPy array
    cts.c_int,
    cts.c_void_p,  # Not sure what the array properties are
    cts.c_int,
)
pspiSetImage.restype = None  # void return?

# ...

pspiSetPath(".\\8bf filters\\")

img_stride = img.strides[0]

pspiSetImage(
    1, *img.shape[:2],
    img,  #np.ctypeslib.as_ctypes(img),
    img_stride, None, 0
)

Notes:

  • Not sure what the function does internally, but bear in mind that the array has 3 dimensions (and you're only passing 2), also its dtype

  • Also, after taking a (shallow) look at Photoshop-Plugin-Host sources, imageStride argument should (most likely) be img.strides[0]

  • Function argument imageBuff is tailored on this specific array. If you need to call it multiple times, with different arrays (shapes and dtypes), you should take the generic approach (have a cts.c_void_p argument and convert it via np.ctypeslib.as_ctypes (or img.ctypes.data_as))

CristiFati
  • 38,250
  • 9
  • 50
  • 87