///|
fn image_attributes(
  image : Image,
  codec : Compression,
) -> Array[Attribute] raise ExrError {
  let attrs = header_attributes(
    image.data_window,
    image.display_window,
    image.channels(),
    codec,
  )
  for i = 0; i < attrs.length(); i = i + 1 {
    if attrs[i].name == "pixelAspectRatio" {
      attrs[i] = Attribute::real("pixelAspectRatio", image.pixel_aspect)
    }
    if attrs[i].name == "screenWindowWidth" {
      attrs[i] = Attribute::real("screenWindowWidth", image.screen_width)
    }
    if attrs[i].name == "screenWindowCenter" {
      let s = sink()
      s.f32(image.screen_x)
      s.f32(image.screen_y)
      attrs[i] = { name: "screenWindowCenter", kind: "v2f", value: s.finish(), }
    }
  }
  let metadata = image.metadata.copy()
  metadata.sort_by((a, b) => name_compare(a.name, b.name))
  for attr in metadata {
    attrs.push(attr)
  }
  attrs
}

///|
/// Deterministic scanline output; compression is lossless on sample bits.
pub fn encode(
  image : Image,
  compression~ : Compression = Zip,
  limits~ : Limits = Limits::default(),
) -> Bytes raise ExrError {
  encode_sources(
    [{ name: "", compression, tiles: None, images: [image], }],
    false,
    limits,
  )
}

///|
test "EXR multichannel lossless codecs crop projection and metadata roundtrip" {
  let w = Window::new(-5, -3, 7, 15)
  let count = w.pixels()
  let image = Image::new(
    w,
    [
      Plane::from_values(
        { name: "R", kind: F16, linear: false, },
        w,
        Array::makei(count, i => (i % 31).to_double() / 8.0),
      ),
      Plane::from_values(
        { name: "Z", kind: F32, linear: true, },
        w,
        Array::makei(count, i => (i - 20).to_double()),
      ),
      Plane::from_values(
        { name: "id", kind: U32, linear: true, },
        w,
        Array::makei(count, i => 4294967295.0 - i.to_double()),
      ),
    ],
    metadata=[
      Attribute::text("comments", "synthetic"),
      { name: "opaque", kind: "bytes", value: b"\xff\x00", },
    ],
    pixel_aspect=2.0,
    screen_center=(0.25, -0.5),
  )
  for codec in [None, Rle, Zips, Zip] {
    let encoded = encode(image, compression=codec)
    let file = ExrFile::open(encoded)
    let restored = file.read()
    assert_eq(restored.channels(), image.channels())
    for ch in image.channels() {
      assert_eq(
        restored.plane(ch.name).sample_bits(),
        image.plane(ch.name).sample_bits(),
      )
    }
    assert_eq(restored.metadata(), image.metadata())
    assert_eq(restored.pixel_aspect(), 2.0)
    assert_eq(restored.screen(), (0.25, -0.5, 1.0))
    let region = Window::new(-2, 0, 3, 3)
    let result = file.read_with_stats(
      region=Some(region),
      channels=Some(["id"]),
    )
    assert_eq(result.image.window(), region)
    assert_eq(result.image.channels().length(), 1)
    assert_eq(
      result.image.plane("id").bits_at(0, 2),
      image.plane("id").bits_at(0, 2),
    )
    assert_eq(result.chunks_decoded, if codec == Zip { 1 } else { 4 })
    assert_eq(encode(restored, compression=codec), encoded)
  }
}