Haiku BBitmap: Pixel Buffers, Row Strides, and Locking Contracts
Access Haiku BBitmap pixels safely by checking initialization, color space, row stride, buffer length, and the distinct pixel and view locks.
BBitmap is Haiku’s Interface Kit bitmap object, but it is not merely a width-by-height array of pixels. The object combines bounds, a color space, a row stride, allocated memory, optional child BView objects, and locking operations. Correct code checks that the bitmap initialized successfully, treats BytesPerRow() as authoritative, and distinguishes a lock on raw pixel bits from a lock on the off-screen window used by attached views.
This distinction matters in image importers, filters, thumbnailers, screen capture, and custom drawing. A loop that assumes tightly packed rows can skew every line after the first. A pointer obtained from Bits() is not a promise that the memory can never move. And a bitmap used by child views has an off-screen window with a separate synchronization contract.
Validate construction before touching memory
Construct a BBitmap with explicit bounds and a color space that the consumer understands. The API provides InitCheck() and IsValid() to test construction state. Do not inspect the buffer after a failed allocation, and do not infer validity solely from non-null object construction: C++ object allocation and bitmap storage allocation are separate events.
BRect bounds use inclusive coordinates, so the pixel width and height are not necessarily identical to a naive difference between right/left or bottom/top coordinates. Let the API describe the resulting bounds and use BytesPerRow() and BitsLength() for buffer traversal. If a caller supplies a custom row stride, validate that it is large enough for the chosen pixel format and dimensions before using it.
BBitmap bitmap(BRect(0, 0, 319, 199), B_RGBA32);
if (bitmap.InitCheck() != B_OK || !bitmap.IsValid())
return B_ERROR;
if (bitmap.Bits() == NULL || bitmap.BytesPerRow() <= 0
|| bitmap.BitsLength() == 0) {
return B_ERROR;
}
The constructor overloads differ in flags, view acceptance, memory requirements, and screen association. Choose one based on the needed use rather than copying flags from an unrelated example. A bitmap for CPU pixel processing does not automatically need to accept child views; a bitmap drawn through an attached view has different synchronization needs. Check the selected constructor’s documentation and InitCheck() before publishing the object to other code.
Treat row stride and color space as a data format
BytesPerRow() includes any row padding. The storage used for a row can be larger than width × bytes-per-pixel, and the number of bytes per pixel depends on the bitmap’s color space. BitsLength() bounds the allocation, not the meaning of each byte. A safe row loop starts at Bits() + y * BytesPerRow() and interprets pixels according to ColorSpace() and the exact format contract. It does not cast arbitrary storage to an assumed four-channel layout.
For interoperable code, write a format-specific reader or use a conversion API that explicitly supports the source and destination formats. Keep channel order, alpha semantics, palette behavior, and padding in one documented adapter. Do not treat an enum named “32-bit” as proof that the memory is RGBA in a particular byte order. Haiku’s API distinguishes color spaces, and import methods may support only a defined subset of conversions; unsupported combinations require an explicit conversion path.
When a BBitmap is exchanged with a file encoder, network API, or another platform, normalize the data into that boundary’s specified format. Include row stride and dimensions in the handoff rather than passing only a pointer. When validating image data from an untrusted source, compute the required buffer size with overflow-safe arithmetic and compare it to both the supplied length and the destination bitmap’s capacity before copying.
Lock raw bits while accessing their storage
The Haiku Book documents LockBits() as locking the bitmap bits so they cannot be relocated, and UnlockBits() as releasing the buffer lock. Check the returned status_t; only access the raw storage while the lock is held, and release it on every exit path. Bits() returns the address, but callers should not cache that address across unlock/relock boundaries or bitmap lifetime changes.
status_t InvertGrayPixels(BBitmap& bitmap)
{
if (bitmap.ColorSpace() != B_GRAY8)
return B_BAD_TYPE;
status_t status = bitmap.LockBits();
if (status != B_OK)
return status;
auto* bits = static_cast<uint8*>(bitmap.Bits());
const int32 stride = bitmap.BytesPerRow();
const int32 width = static_cast<int32>(bitmap.Bounds().Width()) + 1;
const int32 height = static_cast<int32>(bitmap.Bounds().Height()) + 1;
if (bits == NULL || stride < width) {
bitmap.UnlockBits();
return B_BAD_VALUE;
}
for (int32 y = 0; y < height; ++y) {
uint8* row = bits + static_cast<size_t>(y) * stride;
for (int32 x = 0; x < width; ++x)
row[x] = 255 - row[x];
}
bitmap.UnlockBits();
return B_OK;
}
The code is intentionally format-specific: it accepts only B_GRAY8, checks the row stride against the pixel width, and leaves padding bytes untouched. Production code should use a small RAII guard for UnlockBits() so exceptions or early returns cannot leave the bitmap locked. The integer width/height calculation follows inclusive BRect bounds and should be reviewed if bounds are fractional or originate from transformed geometry. A more general helper should reject non-integral bounds and perform checked size arithmetic.
The lock protects storage movement; it is not a substitute for application-level ownership. Two threads that write the same pixels still need a coherent ordering policy. Decide which component owns mutation, whether reads can run concurrently, and how a completed change is published to consumers. Keep the critical section short: decoding, disk I/O, network access, and UI callbacks should not happen while pixel bits are locked.
Do not confuse LockBits() with Lock()
BBitmap::Lock() locks the bitmap’s off-screen window, while LockBits() locks the memory backing the pixel data. If the bitmap has child BView objects, drawing through those views uses the off-screen window contract. If code directly reads or writes Bits(), it uses the bit-buffer contract. Select the operation that matches the work and always pair a successful lock with its matching unlock.
The distinction is more than naming. A program can successfully hold one lock and still violate the other path’s synchronization requirements. Keep direct pixel processing separate from view drawing where possible; if both are required, define and document a lock order, and do not call into code that may acquire the locks in reverse order. The safest implementation often performs one bounded pixel pass, unlocks, and then schedules view redraw or presentation through the normal Interface Kit path.
Import and copy with explicit conversion boundaries
SetBits() and ImportBits() are import helpers, not universal image decoders. Their supported conversions are defined by the API, and the source’s byte order, stride, offset, and color space must match the parameters supplied. If a conversion is unsupported, perform it yourself in a tested routine or use a Translation Kit path designed for the file format. Never copy a compressed file payload directly into bitmap storage and assume it becomes an image.
For partial updates, compute the source and destination rectangles carefully, clip them to the bitmap bounds, and verify the source buffer length before calling an import method. An image that is smaller than the destination is not automatically centered, scaled, or padded in the way a UI expects. Make resize, crop, and fit behavior explicit, and test each case with odd dimensions and row padding.
Bound memory and test lifecycle failures
Bitmap storage is proportional to stride times row count, plus any off-screen or driver resources. A thumbnail cache can consume substantial memory if it retains many full-size frames. Set limits on dimensions and aggregate bytes, release obsolete bitmaps promptly, and handle allocation failure rather than assuming memory is available. Avoid dimensions derived from unchecked input; multiplication can overflow before allocation checks run.
Test constructor failure, unsupported color spaces, zero or negative dimensions, padded rows, partial import, and repeated lock/unlock cycles. Exercise a bitmap with child views separately from a raw-pixel-only bitmap. Run under the target Haiku build and graphics environment because this article’s code excerpts explain API contracts but do not substitute for integration testing with the actual display and application-server path.
The reliable mental model is that BBitmap owns a format-described buffer and may also host an off-screen view tree. Validate initialization, use stride-aware format-specific code, hold the right lock for the right operation, and never let a raw pointer outlive the contract that made it valid.
Related:
- Haiku BScreen: Display Geometry, Modes, and Safe Screen Access
- How Haiku’s Interface Kit and app_server Render Native Windows
Sources: