Haiku BRect: Inclusive Edges, Validity, and Pixel Geometry
Avoid off-by-one drawing bugs with Haiku BRect inclusive edges, width semantics, invalid sentinels, intersections, and coordinate conversions.
BRect is Haiku’s axis-aligned rectangle value used throughout the Interface Kit for frames, update regions, drawing bounds, and bitmap extents. Its coordinate convention is easy to misuse when moving between pixel rectangles and width/height calculations: the left, top, right, and bottom fields describe boundary coordinates, and the documented pixel example for a 32 by 32 area is BRect(0, 0, 31, 31). The right and bottom edges therefore participate in the rectangle’s pixel coverage rather than behaving like the exclusive upper bounds common in many modern APIs.
That difference produces familiar defects: a one-pixel gap at a shared edge, a bitmap that is one pixel short, a frame that grows by one pixel on each resize, or a rectangle that appears empty even though its coordinate fields differ. Treat BRect conversions as explicit geometry code, not incidental arithmetic. Keep the source convention visible in variable names and comments, and convert only at an API boundary.
Keep point coordinates and extents distinct
The four-float constructor takes absolute edges. It does not take an origin plus a pixel count. Given an origin (x, y) and an integer number of pixels, the last covered coordinate is one less than the count, as in right = x + width - 1 and bottom = y + height - 1. This is appropriate for integer-aligned pixel areas. For continuous view layout, use the layout API’s size and frame contracts and avoid pretending that every float coordinate is a physical pixel.
Width() and Height() describe geometric distance between edges, not necessarily the count of integer pixel coordinates included by an integer-aligned rectangle. The IntegerWidth() and IntegerHeight() APIs exist for integer sizing, but rounding and non-integral edges still require deliberate conversion. When the caller asks for a buffer dimension, do not feed Width() straight into an integer allocation if the intended convention is inclusive pixel coverage.
BRect PixelBoundsFor(int32 x, int32 y, int32 pixelWidth,
int32 pixelHeight)
{
if (pixelWidth <= 0 || pixelHeight <= 0)
return BRect();
return BRect(x, y, x + pixelWidth - 1, y + pixelHeight - 1);
}
int32 PixelCountAcross(const BRect& bounds)
{
if (!bounds.IsValid())
return 0;
return bounds.IntegerWidth() + 1;
}
The second helper is suitable only for coordinates already aligned to whole pixels and only after verifying the target SDK’s integer-width rounding behavior. It is not a universal conversion for fractional view coordinates. Check for overflow before adding a large width to an origin, and decide whether a zero-sized logical rectangle should be represented as invalid or as a separate empty-state value.
The default BRect is documented as invalid, with (0, 0, -1, -1) dimensions. Invalidity is a useful sentinel in APIs that return an empty rectangle, but it should not be used as an implicit substitute for an optional value unless every consumer understands that convention. Call IsValid() at boundaries where rectangles can be default-constructed, parsed, or computed from untrusted dimensions.
Build, inset, and offset without changing meaning
Use the BRect(BPoint leftTop, BSize size) constructor when a layout frame is naturally expressed as origin plus geometric width and height. In the current header it computes right = left + size.width and bottom = top + size.height, so it does not subtract one as a pixel-count helper would. Keep that distinction explicit at pixel-buffer boundaries. The public API exposes OffsetBy, OffsetTo, and InsetBy variants returning either a copy or a reference to the mutated object. The Self suffix signals mutation; the Copy suffix preserves the source. Prefer the copy form when the original is still needed for damage tracking or layout comparison.
Inset operations shrink or expand edge coordinates. A positive inset reduces the bounds; a negative inset expands them. Do not apply insets repeatedly to a stored canonical frame unless cumulative shrinkage is intended. Recompute a view’s content rectangle from its outer frame and its current border metrics when the theme, font, or scale changes.
Intersections and unions also return rectangle geometry, not an automatically clipped drawing operation. a & b computes a geometric intersection; callers should verify validity before using its result. a | b covers both bounds, which is useful for combining invalidated areas but can create a much larger rectangle than the actual disjoint shape. If exact disjoint coverage matters, use BRegion rather than pretending a single rectangle represents an arbitrary union.
Contains() and Intersects() are suitable for hit-testing, but hit-testing still needs the correct coordinate space. A view’s local frame, parent coordinates, screen coordinates, and transformed model coordinates are distinct. Convert the point and rectangle into the same space first. Do not compare a screen pointer with a view-local rectangle merely because both use BPoint and BRect types.
Respect validity and floating-point input
The documented valid condition is non-negative width and height: the left edge must not be greater than the right edge, and the top edge must not be greater than the bottom edge. Constructors do not prevent invalid rectangles. Operations on invalid bounds can produce unpredictable results in consumers, so validate dimensions before calling drawing or window APIs.
NaN and infinity are especially damaging because comparisons may not behave like ordinary ordered coordinates. Reject non-finite geometry at data boundaries before it becomes part of a view tree. Clamp or reject excessively large bounds based on the application’s coordinate domain; a valid rectangle can still exceed the practical dimensions of a bitmap, window, or device.
Avoid using floating point equality to decide whether a rectangle is “almost unchanged” after transformed geometry. The class comparison operators represent a value comparison, not a tolerance-based geometric equivalence. If repeated transforms introduce rounding noise, define an application tolerance and compare each edge under that explicit policy. Keep exact edge equality for caches whose key semantics require the exact rendered frame.
Convert from half-open APIs deliberately
Some external APIs describe a rectangle with an exclusive right and bottom edge. For a non-empty integer rectangle [x0, x1) × [y0, y1), conversion to inclusive pixel endpoints is (x0, y0, x1 - 1, y1 - 1). Conversion back is (left, top, right + 1, bottom + 1). Empty ranges need a separate representation because subtracting one can create an invalid BRect.
Do not perform these adjustments twice. Create named conversion helpers for image libraries, protocol formats, or file metadata rather than sprinkling +1 and -1 around painting code. Add round-trip tests for one-pixel rectangles, adjacent rectangles, empty inputs, negative origins, and the largest supported coordinate. Include fractional inputs if the external interface allows them and define the rounding policy.
For rectangle edges shared by neighboring views, define whether the boundary coordinate is intended to be included by both, one, or neither region. Inclusive pixel rectangles naturally meet at adjacent integer coordinates when one side ends at n and the next begins at n + 1. A transformed or fractional boundary may rasterize differently; use the rendering API’s clipping rules and validate actual output rather than assuming the geometry alone settles antialiasing.
Use a rectangle for bounds, not arbitrary shapes
BRect is axis-aligned. It cannot represent rotation, curves, holes, or disjoint areas. A rotated rectangle’s bounding BRect is only an enclosing extent and includes points outside the rotated shape. For coordinate transforms use BAffineTransform; for vector paths use BShape; for irregular polygon outlines use BPolygon; for exact clipped region unions use BRegion.
This distinction matters for input as well as painting. If a transformed control uses its axis-aligned bounding box for hit testing, corners outside the visible shape may still be accepted. If an invalidation rectangle encloses several distant fragments, the renderer may repaint unnecessary pixels. Use a bounding rectangle where an enclosing approximation is acceptable and a more expressive geometry type when correctness requires it.
Acceptance checks for geometry changes
Test the smallest valid rectangle, the default invalid rectangle, equal edges, adjacent edges, an intersection that only touches at a boundary, a union of distant rectangles, fractional coordinates, and transforms that reverse orientation. Confirm expected pixel coverage in a rendered bitmap or screenshot. Add round-trip tests at each boundary where your application exchanges inclusive and exclusive coordinates.
For every allocation based on a rectangle, derive the dimensions once, validate them, check integer overflow, and enforce a maximum resource size before allocating. For every drawing call, verify the rectangle and coordinate space. For every hit test, test both a point inside the intended shape and a point just outside its visible edge.
BRect is simple only when edge semantics are explicit. The reliable practice is to preserve the coordinate convention, validate rectangles before use, separate geometric distance from integer pixel count, and convert at boundaries through named and tested helpers.
Related:
- Haiku BAffineTransform: Coordinate Spaces and Composition
- Haiku BRegion: Building Exact Clipping and Damage Geometry
Sources: