Every pixel buffer across the library is represented as a `Bitmap`. You use bitmaps to read CPU-rendered views, pass images to the page, supply textures to a GPU driver, and save screenshots.

Bitmaps are reference-counted, so you hold them in ref-pointers.

## Creating a Bitmap

To allocate a new bitmap, call `Bitmap::Create()` with the width, height, and pixel format.

```cpp
#include <Ultralight/Ultralight.h>

using namespace ultralight;

RefPtr<Bitmap> bitmap;

void CreateBitmap() {
  ///
  /// Allocate a 256 by 256 BGRA bitmap. The pixels are allocated but not
  /// initialized, so fill them before you read them.
  ///
  bitmap = Bitmap::Create(256, 256, BitmapFormat::BGRA8_UNORM_SRGB);
}
```

The allocated pixels are uninitialized— call `Bitmap::Erase()` to clear them to zero before reading or drawing.

Neither dimension can exceed `Bitmap::MaxDimension` (16,384 pixels). If either dimension exceeds this limit, `Bitmap::Create()` returns `nullptr`.

### Wrapping Existing Memory

To wrap an existing pixel buffer without copying, pass the pointer, row pitch, byte size, and `false` for `should_copy` to `Bitmap::Create()`.

```cpp
RefPtr<Bitmap> WrapEngineBuffer(void* pixels, uint32_t width, uint32_t height,
                                uint32_t row_bytes) {
  ///
  /// Use the engine's buffer in place (no copy). We still own the memory.
  ///
  return Bitmap::Create(width, height, BitmapFormat::BGRA8_UNORM_SRGB,
                        row_bytes, pixels, row_bytes * height, false);
}
```

By default, `Bitmap::Create()` copies pixel data into new storage. When `should_copy` is `false`, the bitmap uses the buffer in place.

Alternatively, you can provide a `DestroyBitmapCallback` and user data pointer so the library calls back when the bitmap is destroyed.

> 🚧 Lifetime of Wrapped Buffers
>
> When wrapping memory with `should_copy = false`, the buffer must remain valid for the entire lifetime of the `Bitmap`. You retain ownership and must free the memory after the bitmap is destroyed.

## Choosing a Pixel Format

You select a pixel format from the `BitmapFormat` enum when creating or wrapping a bitmap.

| Format | Details |
| :--- | :--- |
| `BitmapFormat::BGRA8_UNORM_SRGB` | 32-bit color with 8 bits per channel in sRGB space and premultiplied linear alpha. This is the primary format used across the library— every `Surface` and screenshot uses it. |
| `BitmapFormat::A8_UNORM` | 8-bit alpha channel with no color data. Used for mask textures received by the GPU driver. |
| `BitmapFormat::BC1_UNORM`, `BitmapFormat::BC2_UNORM`, `BitmapFormat::BC3_UNORM`, `BitmapFormat::BC7_UNORM` | Block-compressed GPU texture formats, available in the Pro edition or higher. These formats store pixels in 4x4 blocks and do not support per-pixel operations. See [Compressed Textures](/docs/2.0/compressed-textures). |

## Reading and Writing Pixels

To inspect or modify raw pixel data, lock the buffer using `Bitmap::LockPixelsSafe()`.

```cpp
void FillWithPurple() {
  ///
  /// Lock the pixels. The guard unlocks them when it goes out of scope.
  ///
  auto pixels = bitmap->LockPixelsSafe();
  if (!pixels || !pixels.data())
    return;

  ///
  /// Walk the rows. Rows are row_bytes() apart, which may be more than
  /// width() * bpp(), so never assume they are packed.
  ///
  uint8_t* row = static_cast<uint8_t*>(pixels.data());
  for (uint32_t y = 0; y < bitmap->height(); ++y) {
    uint8_t* pixel = row;
    for (uint32_t x = 0; x < bitmap->width(); ++x) {
      pixel[0] = 128; // blue
      pixel[1] = 0;   // green
      pixel[2] = 128; // red
      pixel[3] = 255; // alpha
      pixel += bitmap->bpp();
    }
    row += bitmap->row_bytes();
  }
}
```

Rows may contain padding due to memory alignment rules. Always advance across rows using `Bitmap::row_bytes()` and step pixels using `Bitmap::bpp()` rather than assuming rows are tightly packed.

If a lock must outlive the current scope, call `Bitmap::LockPixels()` and `Bitmap::UnlockPixels()` manually.

> 🚧 Thread Safety with Wrapped Memory
>
> The library takes an internal lock only when the `Bitmap` owns its pixel buffer. If the bitmap wraps external memory through `should_copy = false` or a destruction callback, it performs no locking— you must synchronize access across threads.

## Copying, Resizing, and Converting

The library provides operations to blit rectangles between bitmaps, scale image dimensions, and convert color channels or alpha formats.

### Copying Between Bitmaps

To copy a rectangular region from one bitmap into another, call `Bitmap::DrawBitmap()`.

```cpp
void CopyIntoAtlas(RefPtr<Bitmap> atlas, RefPtr<Bitmap> sprite) {
  ///
  /// Draw the whole sprite into the top-left corner of the atlas.
  ///
  IntRect dest = { 0, 0, static_cast<int>(sprite->width()),
                   static_cast<int>(sprite->height()) };
  atlas->DrawBitmap(sprite->bounds(), dest, sprite, false);
}
```

The source and destination formats do not need to match— `Bitmap::DrawBitmap()` converts between formats automatically.

### Resizing a Bitmap

To resize a bitmap, call `Bitmap::Resample()` with a destination bitmap allocated at the target size.

```cpp
RefPtr<Bitmap> MakeThumbnail(RefPtr<Bitmap> source) {
  ///
  /// Resample() fills a destination we allocate at the target size.
  ///
  RefPtr<Bitmap> thumbnail =
      Bitmap::Create(64, 64, BitmapFormat::BGRA8_UNORM_SRGB);
  if (!source->Resample(thumbnail, true))
    return nullptr;

  return thumbnail;
}
```

Pass `true` for `high_quality` to use smooth filtering, or `false` for fast nearest-neighbor sampling.

Both bitmaps must use `BitmapFormat::BGRA8_UNORM_SRGB`— `Bitmap::Resample()` returns `false` if either bitmap uses another format.

### Converting Channel Order and Alpha

These operations modify pixels in place and require `BitmapFormat::BGRA8_UNORM_SRGB`.

- `Bitmap::SwapRedBlueChannels()` — Swaps the red and blue channels between BGRA and RGBA.
- `Bitmap::ConvertToStraightAlpha()` — Converts premultiplied alpha values to straight alpha.
- `Bitmap::ConvertToPremultipliedAlpha()` — Converts straight alpha values to premultiplied alpha.

## Saving to PNG

To write a bitmap to disk or encode it as a memory buffer, call `Bitmap::WritePNG()` or `Bitmap::EncodePNG()`.

```cpp
void SavePurple() {
  ///
  /// Write to disk. The defaults convert BGRA premultiplied to RGBA straight.
  ///
  bitmap->WritePNG("purple.png");

  ///
  /// Or keep the encoded bytes in memory instead.
  ///
  RefPtr<Buffer> png = bitmap->EncodePNG();
  if (png) {
    // png->data() holds png->size() bytes of PNG.
  }
}
```

The PNG standard expects straight-alpha RGBA pixels. By default, both methods automatically convert from premultiplied BGRA.

Both functions accept optional flags to toggle channel swapping and alpha conversion. Keep the default `true` values unless the bitmap already contains straight-alpha RGBA data.
