// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

// Putting one picture on top of another.
//
// Compositing is one formula — source over destination, in straight alpha —
// and it is the same formula whether the two pictures are layers of a
// composition, a watermark on a photograph, or a frame of an animation on the
// canvas it was drawn for. The animation code calls this one rather than
// keeping a second copy of it.
//
// The result keeps the format of the destination, because that is the picture
// the caller is holding; the source is converted on the way in, which is the
// only place in the library where a conversion happens without being asked for
// and is worth it, since the alternative is refusing to composite two pictures
// that are both perfectly good pictures.

///|
/// This image with `source` laid over it, its top-left corner at (`x`, `y`).
///
/// A source larger than the destination is clipped rather than refused: a
/// watermark is usually placed by a corner rule, and a corner rule that lands
/// one pixel outside is not an error.
///
/// `opacity` scales the source's alpha before it is laid down, which is how a
/// watermark is made faint without being re-encoded.
pub fn Image::composite(
  self : Image,
  source : Image,
  x? : Int32,
  y? : Int32,
  opacity? : Double,
) -> Image {
  let x = x.unwrap_or(0)
  let y = y.unwrap_or(0)
  let opacity = clamp_unit(opacity.unwrap_or(1.0))
  if opacity <= 0.0 || source.width == 0 || source.height == 0 {
    return self
  }
  let bytes = self.format.bytes()
  let stride = self.width * bytes
  let buffer : FixedArray[Byte] = FixedArray::make(
    if self.height == 0 {
      0
    } else {
      stride * self.height
    },
    b'\x00',
  )
  buffer.blit_from_bytesview(0, self.pixels)
  let top = picture_over(self, source, opacity)
  let left = within(x, 0, self.width)
  let right = within(x + top.width, 0, self.width)
  let upper = within(y, 0, self.height)
  let lower = within(y + top.height, 0, self.height)
  if right <= left || lower <= upper {
    return self
  }
  let source_bytes = top.format.bytes()
  for row in upper.. abort("the rectangle is inside the image")
      }
      let above = read_pixel(top.pixels, from, top.format)
      write_pixel(
        buffer,
        row * stride + column * bytes,
        self.format,
        over(above, under),
      )
    }
  }
  assemble(
    self.width,
    self.height,
    self.format,
    buffer,
    self.premultiplied,
    self.metadata,
  )
}

///|
/// The source in the destination's format and straight alpha, with the opacity
/// already folded in.
///
/// Both matter: `over` reads straight alpha, and the destination's format is
/// what the caller is going to keep.
fn picture_over(destination : Image, source : Image, opacity : Double) -> Image {
  let straight = if source.premultiplied {
    source.to_straight()
  } else {
    source
  }
  let matching = straight.convert(destination.format)
  if opacity >= 1.0 {
    return matching
  }
  matching.map_pixels(pixel => { ..pixel, a: pixel.a * opacity, })
}

///|
fn within(value : Int32, low : Int32, high : Int32) -> Int32 {
  if value < low {
    low
  } else if value > high {
    high
  } else {
    value
  }
}