Learn · 09 of 09

Images.

Image accepts three source shapes: named resource paths, local file URLs, and embedded bytes. The runtime resolves image metadata before layout, then the host presents the raster attachment using the best protocol available for that terminal.

Supported input formats are PNG, baseline JPEG, and GIF. When you pass bytes, the decoder chooses the format from the leading magic bytes; authors do not need separate PNG/JPEG/GIF view types.

Three source paths. One view type.

Named resources resolve through explicit imageResourceRoots. File URLs are useful for drops and user-picked files. Embedded bytes make small self-contained demos and generated assets easy to ship.

ImageBrowser.swift swift
import Foundation
import TerminalUI
import TerminalUICLI

@main
struct ImageBrowserApp: App {
  var body: some Scene {
    WindowGroup("Images") {
      ImageBrowser()
    }
  }
}

struct ImageBrowser: View {
  let droppedFile = URL(fileURLWithPath: "/tmp/logo.png")

  var body: some View {
    VStack(alignment: .leading, spacing: 1) {
      Text("Asset preview").bold()

      HStack(alignment: .top, spacing: 2) {
        VStack(alignment: .leading, spacing: 0) {
          Text("Named resource").foregroundStyle(.muted)
          Image(path: "icons/logo.png")
            .environment(\.imageResourceRoots, ["./Resources"])
            .border(.separator)
        }

        VStack(alignment: .leading, spacing: 0) {
          Text("Local file URL").foregroundStyle(.muted)
          Image(fileURL: droppedFile.absoluteString)
            .scaledToFit()
            .frame(width: 24, height: 10)
            .border(.separator)
        }

        VStack(alignment: .leading, spacing: 0) {
          Text("Embedded bytes").foregroundStyle(.muted)
          Image(data: samplePNGBytes)
            .resizable()
            .frame(width: 16, height: 8)
            .border(.separator)
        }
      }
    }
    .padding(.init(horizontal: 1, vertical: 0))
  }

  // PNG, baseline JPEG, and GIF all enter through Image(data:).
  // The decoder picks the format from magic bytes.
  var samplePNGBytes: [UInt8] {
    Array(Data(base64Encoded: Self.tinyPNGBase64) ?? Data())
  }

  static let tinyPNGBase64 =
    "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMB/az8W7cAAAAASUVORK5CYII="
}

Source choices

  • Image(path:) resolves a named resource against EnvironmentValues.imageResourceRoots. Prefer it for app-owned assets.
  • Image(fileURL:) points at a local file URL string. Prefer it for user-picked or dropped files.
  • Image(data:) embeds bytes directly in the view value. Prefer it for generated, downloaded-then-cached, or tiny demo assets.

Sizing modes

Without modifiers, an image reports an intrinsic cell size derived from its pixel size and the current cell-pixel metrics. Use .resizable() to stretch into the proposed frame, .scaledToFit() to preserve aspect while fitting inside the frame, and .scaledToFill() to preserve aspect while covering the frame.

.scaledToFill() can overflow the frame by design. Pair it with .clipped() when you want a cropped thumbnail.

Host presentation

The rasterizer emits an image attachment with source, destination bounds, visible bounds, pixel size, and scaling mode. The terminal host can present it through Kitty graphics or Sixel when available; otherwise it composites a text fallback into the cell buffer. The authored view does not need to branch on host capability.

What to read after Learn