# `Hakushi.Kits.Images`
[🔗](https://github.com/hattoriai/hakushi/blob/v0.6.0/lib/hakushi/kits/images.ex#L2)

The kit of images on the paper: for a moodboard, a screenshot to mark
up, references beside a plan.

    <.canvas id="board" graph={@graph} kits={[Hakushi.Kits.Images]} on_change={&save/2} />

People drop image files on the paper, paste them (or paste an image's
address, from the hosts the app allows), or pick them with the kit's
tool (I). Each is a node where it lands, at its own aspect ratio, sized
to fit the view. While a file uploads, the canvas shows a placeholder
with its progress (to the others too, on a shared canvas); the node is
made only once the server has the file and has checked it
(`Hakushi.Images`).

An image resizes from its corners and sides with its aspect ratio kept
(Shift frees it), is cropped in place (the panel's Crop, then Enter),
takes a caption on a double click, and alt text from the panel. Its
edges, groups, copy and paste and export are those of any node.

## The node

A node of type `"image"`, whose data is the stored file and how it is
shown:

    %{
      "url" => "/uploads/2026/10/…-board.png",
      "key" => "2026/10/…-board.png",   # in the store (Storehouse)
      "thumb" => nil,                     # a smaller copy's URL, for small zooms
      "name" => "board.png",
      "pixels" => [1600, 1000],           # the file's width and height
      "crop" => [0, 0, 1, 1],             # x, y, width, height: fractions of the file
      "alt" => "The palette of the spring campaign",
      "caption" => "Spring"
    }

## The files

The files go to the app's store with
[Storehouse](https://hexdocs.pm/storehouse): the local disk, S3, or a
store of the app's (`config :storehouse, adapter: ...`). It is an
optional dependency of Hakushi: the kit is there when the app has
`{:storehouse, "~> 0.2"}` in its deps. The canvas takes the files with
a LiveView upload of its own: the canvas must not be in a form of the
app's. The limits are the kit's config:

    config :hakushi, Hakushi.Kits.Images,
      max_file_size: 10_000_000,     # bytes, the default
      max_entries: 10,               # files at once, the default
      max_pixels: 50_000_000,        # width × height, the default
      fetch: ["images.example.com", "*.cdn.example.com"],
      thumbnail: &MyApp.Images.thumbnail/2,
      files: &MyApp.Images.path/1

  * `:fetch` - the hosts whose images people paste by their address,
    and agents add by theirs: the server downloads them
    (`Hakushi.Images.fetch/2`) and stores them as uploads. None by
    default.
  * `:thumbnail` - a function of the stored image and its file that
    gives the URL of a smaller copy, which the canvas shows when it is
    zoomed out (see `Hakushi.Images.store/2`).
  * `:files` - a function of a name an agent gives (`add_image`'s
    `file`) that gives `{:ok, path}` of one of the app's files, or
    `:error`.
  * `:adapter` - the store, for this kit only. The default is
    `Storehouse.adapter/0`.

# `config`

```elixir
@spec config() :: keyword()
```

The kit's config, with its defaults.

# `fetchable?`

```elixir
@spec fetchable?(String.t()) :: boolean()
```

Whether the kit takes images from the address `url`: its host is one of
the config's `:fetch`.

# `image_node`

```elixir
@spec image_node(Hakushi.Images.image(), [number()] | nil, keyword()) :: map()
```

An image's node, for a stored image (`Hakushi.Images.store/2`), in a box
of the canvas: at its `x` and `y`, as wide as it (at most the image's
own width, and 480 with no width), and as high as its
aspect ratio says.

    {:ok, image} = Hakushi.Images.store("priv/seeds/palette.png")
    Hakushi.Kits.Images.image_node(image, [0, 0, 320, 0], alt: "The palette")

## Options

  * `:alt` and `:caption` - the image's.

---

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