///|
/// Encode velocity magnitude as a small RGB PNG image.
pub fn Lattice::to_png(self : Lattice) -> Bytes {
  let raw = self.png_raw_rgb()
  let idat = zlib_store(raw)
  let out : Array[Byte] = [137, 80, 78, 71, 13, 10, 26, 10]
  append_chunk(out, "IHDR", ihdr(self.size.width, self.size.height))
  append_chunk(out, "IDAT", idat)
  append_chunk(out, "IEND", [])
  Bytes::from_array(out)
}

///|
fn Lattice::png_raw_rgb(self : Lattice) -> Array[Byte] {
  let raw : Array[Byte] = Array::new(
    capacity=self.size.height * (1 + self.size.width * 3),
  )
  let stable = self.stability()
  let denom = if stable.max_speed <= 0.0 { 1.0 } else { stable.max_speed }
  for y in 0.. Array[Byte] {
  let out = Array::new(capacity=13)
  append_u32(out, width)
  append_u32(out, height)
  out.push(8)
  out.push(2)
  out.push(0)
  out.push(0)
  out.push(0)
  out
}

///|
fn zlib_store(data : Array[Byte]) -> Array[Byte] {
  let out : Array[Byte] = [0x78, 0x01]
  let mut pos = 0
  while pos < data.length() {
    let remain = data.length() - pos
    let len = remain.min(65535)
    let final_block = pos + len == data.length()
    out.push(if final_block { 1 } else { 0 })
    append_u16_le(out, len)
    append_u16_le(out, 0xffff - len)
    for i in pos..<(pos + len) {
      out.push(data[i])
    }
    pos += len
  }
  append_u32(out, adler32(data).reinterpret_as_int())
  out
}

///|
fn append_chunk(out : Array[Byte], kind : String, data : Array[Byte]) -> Unit {
  append_u32(out, data.length())
  let start = out.length()
  append_ascii(out, kind)
  out.append(data)
  let crc_data = out[start:]
  append_u32(out, crc32(crc_data).reinterpret_as_int())
}

///|
fn append_ascii(out : Array[Byte], s : String) -> Unit {
  for ch in s {
    out.push(ch.to_int().to_byte())
  }
}

///|
fn append_u16_le(out : Array[Byte], value : Int) -> Unit {
  out.push((value & 0xff).to_byte())
  out.push(((value >> 8) & 0xff).to_byte())
}

///|
fn append_u32(out : Array[Byte], value : Int) -> Unit {
  out.push(((value >> 24) & 0xff).to_byte())
  out.push(((value >> 16) & 0xff).to_byte())
  out.push(((value >> 8) & 0xff).to_byte())
  out.push((value & 0xff).to_byte())
}

///|
fn adler32(data : Array[Byte]) -> UInt {
  let mut a : UInt = 1
  let mut b : UInt = 0
  for byte in data {
    a = (a + byte.to_uint()) % 65521
    b = (b + a) % 65521
  }
  (b << 16) | a
}

///|
fn crc32(data : ArrayView[Byte]) -> UInt {
  let mut crc : UInt = 0xffffffff
  for byte in data {
    crc = crc ^ byte.to_uint()
    for _ in 0..<8 {
      let mask : UInt = if (crc & 1) == 1 { 0xedb88320 } else { 0 }
      crc = (crc >> 1) ^ mask
    }
  }
  crc ^ 0xffffffff
}