# `RasterExRatatui.Framebuffer`
[🔗](https://github.com/mcass19/raster_ex_ratatui/blob/v0.4.0/lib/raster_ex_ratatui/framebuffer.ex#L1)

Writes packed pixels to a Linux framebuffer device (`/dev/fbN`).

fbdev needs no driver code on the Elixir side: the kernel exposes the display as a file, `/sys/class/graphics/fbN` describes its geometry, and writing bytes at the right offset lights the pixels.

    {:ok, fb} = Framebuffer.open("fb0")
    {:ok, format} = Framebuffer.format_for(fb.info)
    raster = Raster.new(size: {fb.info.width, fb.info.height}, format: format, scale: 3)
    # … on every push:
    :ok = Framebuffer.write(fb, patches)

## Stride

A framebuffer line may be longer than `width * bytes_per_pixel` (the driver pads it to an alignment). Patches carry no padding, so `write/2` places every patch row at `y * stride + x * bytes_per_pixel`, and a full frame is written row by row when the stride differs from the visible line.

## Consoles

The kernel's framebuffer console (fbcon) draws on the same device, so text and a blinking cursor can appear over the app. `unbind_console/2` detaches it; moving the system console off the display (a serial `ctty` on Nerves) keeps the boot log away too.

## Options

Every function that touches the filesystem accepts `:root` (default `"/"`), prepended to `/sys/...` and `/dev/...`. It exists for tests and for devices that mount sysfs elsewhere.

# `info`

```elixir
@type info() :: %{
  width: pos_integer(),
  height: pos_integer(),
  bits_per_pixel: pos_integer(),
  stride: pos_integer()
}
```

Framebuffer geometry, from sysfs.

# `t`

```elixir
@type t() :: %RasterExRatatui.Framebuffer{
  device: :file.io_device(),
  info: info(),
  name: String.t()
}
```

# `auto_scale`

```elixir
@spec auto_scale({pos_integer(), pos_integer()}, keyword()) :: pos_integer()
```

The largest integer font scale that keeps at least `columns:` (default `100`) cells of the font (default `RasterExRatatui.Font.Default6x8`) along the panel's long side, and never less than 1.

The long side is the same whichever way the panel is rotated, so this holds for the app's logical image too.

## Examples

The Touch Display 2, 720×1280, gets 12×16 pixel cells: 60×80 in portrait, 106×45 turned; a 1080p monitor gets 18×24 pixel cells, 106×45.

    iex> RasterExRatatui.Framebuffer.auto_scale({720, 1280})
    2

    iex> RasterExRatatui.Framebuffer.auto_scale({1920, 1080})
    3

    iex> RasterExRatatui.Framebuffer.auto_scale({3840, 2160}, columns: 200)
    3

    iex> RasterExRatatui.Framebuffer.auto_scale({480, 320})
    1

# `blank`

```elixir
@spec blank(String.t(), boolean(), keyword()) :: :ok | {:error, term()}
```

Blanks (`true`) or unblanks (`false`) the display through `/sys/class/graphics/<name>/blank`.

# `close`

```elixir
@spec close(t()) :: :ok | {:error, term()}
```

Closes the device.

# `format_for`

```elixir
@spec format_for(%{bits_per_pixel: pos_integer()}) ::
  {:ok, module()} | {:error, :unsupported}
```

The pixel format for a framebuffer's depth: `RGB565` at 16 bits, `XRGB8888` at 32.

## Examples

    iex> RasterExRatatui.Framebuffer.format_for(%{bits_per_pixel: 32})
    {:ok, RasterExRatatui.PixelFormat.XRGB8888}

    iex> RasterExRatatui.Framebuffer.format_for(%{bits_per_pixel: 16})
    {:ok, RasterExRatatui.PixelFormat.RGB565}

    iex> RasterExRatatui.Framebuffer.format_for(%{bits_per_pixel: 24})
    {:error, :unsupported}

# `info`

```elixir
@spec info(String.t(), keyword()) :: {:ok, info()} | {:error, term()}
```

Reads the geometry of framebuffer `name` (`"fb0"`) from `/sys/class/graphics/<name>`: `virtual_size`, `bits_per_pixel`, and `stride`.

Returns `{:error, reason}` when a file is missing or does not hold what fbdev writes there.

# `open`

```elixir
@spec open(String.t(), keyword()) :: {:ok, t()} | {:error, term()}
```

Reads the geometry of framebuffer `name` and opens `/dev/<name>` for writing.

# `unbind_console`

```elixir
@spec unbind_console(String.t(), keyword()) :: :ok | {:error, term()}
```

Detaches the kernel's framebuffer console so it stops drawing text and a cursor over the display.

Writes `0` to `/sys/class/vtconsole/<console>/bind`. The framebuffer console is usually `"vtcon1"` (`"vtcon0"` is the dummy console); `/sys/class/vtconsole/*/name` tells them apart. Best effort: returns `{:error, reason}` when the kernel has no such console.

# `write`

```elixir
@spec write(t(), [RasterExRatatui.Patch.t()] | {:frame, binary()}) ::
  :ok | {:error, term()}
```

Writes a list of patches, or a `{:frame, binary}` covering the whole display, in one `:file.pwrite/2` call.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
