-
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
.
- ptr, pitch, height
- ptr, pitch, 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.
- 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)
All parameters are optional.
buf := ImagePut.BitmapBuffer(ptr?, size?, width?, height?) ; stride calculated automatically
buf := ImagePut.BitmapBuffer()
buf.ptr := obj.ptr
buf.size := obj.size
buf.width := 300
buf.height := 200
The stride is always the number of bytes and never the number of bits.
buf.stride := 4 * width ; set stride manually if needed
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
buf := ImagePut.BitmapBuffer(ptr, size, 300, 200) ; stride is calculated automatically
Obviously nothing prevents writing to the buffer such as
buf[0, 100] := 0xFF
. It's just good practice.
Smart pointer:
; A white rectangle allocated by the user:
obj := Buffer(240000, 0xFF) ; 4 * width * height ➡️ 4 × 300 × 200 = 240000
; Constructor - ImagePut.BitmapBuffer(ptr, size, width, height)
buf := ImagePut.BitmapBuffer(ptr, size, 300, 200)
buf.ref := obj ; Doesn't matter what this property name is!
obj := "" ; Delete the original buffer
buf.show(1)
Normal pointer:
; Allocate global memory.
size := 4 * 300 * 200
ptr := DllCall("GlobalAlloc", "uint", 0, "uptr", size, "ptr")
; Constructor - ImagePut.BitmapBuffer(ptr, size, width, height)
buf := ImagePut.BitmapBuffer(ptr, size, 300, 200)
buf.free := DllCall.Bind("GlobalFree", "ptr", ptr)
buf.show(1)
Different ways to attach a cleanup function to ImagePut.BitmapBuffer:
- Define a function reference as
.free
shown above. - Define an array of function references as
.free
such asbuf.free := [callback1, callback2]
- Create a
.Cleanup()
function.
.Cleanup()
is a method..free
is a value: (1) a function reference or (2) an array of function references.
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")
When in doubt, use ImagePutBuffer() as it will always copy the memory into itself.