How to create a ndrray from a ctypes.POINTER buffer with custom stride/row size in numpy?

Question:

Suppose I have a RGB image with 3 bytes per pixel stored in a raw memory buffer. In numpy I am able to create a ndarray from such raw buffer with the following code:

import ctypes
import numpy as np

# ...

shp = (image.height, image.width, 3)
ptr = ctypes.cast(image.ptr, ctypes.POINTER(ctypes.c_ubyte))
arr = np.ctypeslib.as_array(ptr, shape=shp)

Where image.ptr is the actual native pointer to the allocated buffer. This appears to work well with a trivial stride/row size, but it’s very common to find bitmap memory layouts where the size of a row may be bigger than actually required. For example a Windows GDI+ PixelFormat24bppRGB bitmap has a row size that is the closest multiple of 4 that can be computed with the formula 4 * (3 * width + 3) / 4). How I can modify the above code to create a ndarray that correctly access such custom bitmap memory layout?

Asked By: ceztko

||

Answers:

You can create a view on the raw buffer using np.lib.stride_tricks.as_strided. Here is an example to skip the alpha channel of a RGBA-based (H,W,4) image array:

# Create the example image raw array
arr = np.arange(16).astype(np.uint8).reshape(4,4)
print(arr)
print(arr.strides)

# Create a view so to skip some items (possibly to support padding)
arrView = np.lib.stride_tricks.as_strided(arr, shape=(4, 3), strides=(4, 1))
print(arrView)
print(arrView.strides)

The result is:

[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]
(4, 1)
[[ 0  1  2]
 [ 4  5  6]
 [ 8  9 10]
 [12 13 14]]
(4, 1)

As the documentation states, this function has to be used with extreme care (be prepared to a crash if not used properly).

Answered By: Jérôme Richard

@JeromeRichard answer is correct. Here it follows a snippet that simulates exactly the use case in my question, namely the 24bpp strided rgb bitmap:

import ctypes
import numpy as np

width = 50
height = 50
rowsize = 4 * int((3 * 50 + 3) / 4)
rgb_buff_size = rowsize * height

# Prepare a test byte array and access the native pointer
# https://stackoverflow.com/a/73640146/213871
test_rgb_buff = np.arange(rgb_buff_size).astype(np.uint8).reshape(1, rgb_buff_size)
buff_ptr = test_rgb_buff.ctypes.data

# Create the view from the pointer
rgb_buff_view = np.ctypeslib.as_array(ctypes.cast(buff_ptr, ctypes.POINTER(ctypes.c_ubyte)), shape=(1, rgb_buff_size))

# Create the 3 channel strided view 
rgb_strided_view = np.lib.stride_tricks.as_strided(rgb_buff_view, shape=(width, height, 3), strides=(rowsize, 3, 1))
print("type: " + str(rgb_strided_view.dtype))
print("shape: " + str(rgb_strided_view.shape))
print("strides: " + str(rgb_strided_view.strides))

This prints the following:

type: uint8
shape: (50, 50, 3)
strides: (152, 3, 1)
Answered By: ceztko