Skip to content

Read an Image From Memory

Edison Hua edited this page Nov 26, 2024 · 39 revisions

Chances are, your pointer is not:

  • an IStream
  • an IRandomAccessStream
  • or a pBitmap

but rather some pointer ptr and its size.

Pointer to Pixel Data

Given a pointer to pixel data, create a 2-D shape by adding width or height. All pixels are assumed to be 32-bit ARGB by default in the format of 0xAARRGGBB.

; Only width or height is needed to infer a 2-D shape.
shape := {ptr: ptr, size: size, width: 300}
shape := {ptr: ptr, size: size, height: 200}
buf := ImagePutBuffer(shape)

The following minimal combinations are permissible:

  1. ptr, width, height
  2. ptr, stride, height
  3. ptr, size, width
  4. ptr, size, height
  5. ptr, size, stride 

Full definition:

; Shapes the buffer into an 300x200 image. The user defines ptr and size.
shape := {ptr: ptr, size: size, width: 300, height: 200, stride:300*4}
buf := ImagePutBuffer(shape)

Bottom-up bitmaps, Negative Height, Negative Stride, and Scan0 - Skip this section if you don't understand.

  1. A negative height will flip the image.
  2. The stride could be negative if you are passing a Scan0 instead of a ptr.

A Scan0 refers to the first scanline of the image. In a top-down bitmap, the ptr = Scan0. In a bottom-up bitmap, the ptr refers to the beginning of the allocated memory, while the Scan0 points to the last row of the bitmap. Because the Scan0 points to the last row of the allocated memory, the Scan0 should be negative here, to allow the image to be read from the bottom to the top. Hence the name bottom-up bitmap.

Whether to pass the ptr or the Scan0 depends on the signs of the height and stride:

  1. Case 1: (+) height (+) stride -> Use ptr or Scan0 (top-down bitmap)
  2. Case 2: (+) height (-) stride -> Use Scan0 only (bottom-up bitmap)
  3. Case 3: (-) height (+) stride -> Use ptr only (bottom-up bitmap)
  4. Case 4: (-) height (-) stride -> Use Scan0 only (top-down bitmap)

Changing width, height or stride after creation

It is possible to alter the width and height after the fact. This may lead to read errors if the allocated memory is not big enough.

; Reduces the height of the image to 200.
buf := ImagePutBuffer("cats.jpg")
buf.height := 200
buf.show()

Use buf.crop(0, 0, 1000, 200) instead.

Ownership

pBitmap := ImagePutBitmap(shape) is the only special function where the user owns the pointer. In other words, if the user writes to the pointer, all changes are reflected on the pBitmap. Likewise, if the pointer is deleted the pBitmap becomes a dangling pointer referencing freed memory, also known as a bug.

Note: Internally, ImagePut.BufferToBitmap(shape) is called and creates a pBitmap from the given width, height, and stride.

Giving pointer ownership to ImagePut

ImagePutBuffer always creates a copy of the memory:

buf := ImagePutBuffer({ptr: ptr, size: size, width: 300, height: 200, stride:300*4})

To directly give ownership of the pointer to ImagePut without copying:

; A white rectangle allocated by the user:
obj := Buffer(240000, 0xFF) ; 4 * width * height = 4 × 300 × 200 = 240000

; Method #1 - Prototype
buf := ImagePut.BitmapBuffer()
buf.ptr := obj.ptr
buf.size := obj.size
buf.width := 300
buf.height := 200
buf.reference := obj ; Doesn't matter what you name this property.
obj := ""            ; Delete the original object.
buf.show(1)

Another example using the constructor of BitmapBuffer(ptr?, size?, width?, height?):

; Allocate global memory.
size := 4 * 300 * 200
ptr := DllCall("GlobalAlloc", "uint", 0, "uptr", size, "ptr")

; Method #2 - Constructor
buf := ImagePut.BitmapBuffer(ptr, size, 300, 200)
buf.free := DllCall.Bind("GlobalFree", "ptr", ptr)
buf.show(1)

To add a function to free the underlying memory, use the following ways:

  1. Define a function reference as .free
  2. Define an array of function references as .free
  3. Create a .Cleanup() function.

Note: .free is a property with a function reference or an array of function references. .Cleanup() is a method.

Demonstrating what happens when the pointer is freed before pBitmap.

; Example showing how the pBitmap created via ImagePutBitmap is backed by user owned memory.
test := ImagePutBuffer("https://picsum.photos/1000")
shape := {ptr: test.ptr, size: test.size, width: test.width, height: test.height}
pBitmap := ImagePutBitmap(shape) ; Backed by user owned memory
buf := ImagePutBuffer(shape)     ; Copies data into ImagePut managed memory
test := ""                       ; Frees the backing pointer

ImagePutWindow(buf.pBitmap, "The clone has its memory managed by ImagePut")
MsgBox "Danger! Use after free. Will throw as the backing memory is freed."
ImagePutWindow(pBitmap, "should not be shown")

So, when in doubt make sure to use ImagePutBuffer() as it will always copy the memory into itself.