-
Notifications
You must be signed in to change notification settings - Fork 28
Read an Image From Memory
Chances are, your pointer is not:
- an
IStream
- an
IRandomAccessStream
- or a
pBitmap
but rather some pointer ptr
and its size
.
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:
- ptr, width, height
- ptr, stride, height
- ptr, size, width
- ptr, size, height
- ptr, size, stride
The
stride
property can be renamedpitch
.
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.
- A negative height will flip the image.
- 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:
- Case 1: (+) height (+) stride -> Use ptr or Scan0 (top-down bitmap)
- Case 2: (+) height (-) stride -> Use Scan0 only (bottom-up bitmap)
- Case 3: (-) height (+) stride -> Use ptr only (bottom-up bitmap)
- Case 4: (-) height (-) stride -> Use Scan0 only (top-down bitmap)
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.
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.
Internally,
ImagePut.BufferToBitmap(shape)
is called and creates apBitmap
from the given width, height, and stride.
ImagePutBuffer
always creates a copy of the memory:
buf := ImagePutBuffer({ptr: ptr, size: size, width: 300, height: 200, stride:300*4})
MsgBox buf.pBitmap
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 ImagePut.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:
- Define a function reference as
.free
- Define an array of function references as
.free
- Create a
.Cleanup()
function.
Note:
.free
is a property with a function reference or an array of function references..Cleanup()
is a method.
; 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.