All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.pawelkrol.Afterimage.Sprite.Data.scala Maven / Gradle / Ivy

package com.github.pawelkrol.Afterimage
package Sprite

import ij.ImagePlus
import ij.process.ImageProcessor

import Colour.Palette
import Mode.Data.{ Bitmap, Screen }
import Mode.{ CBM, HiRes, MultiColour }
import View.Image

/** Sprite data.
  *
  * @constructor create a new `Data` sprite data
  * @param data an array of 63 sprite data bytes
  * @param props miscellaneous sprite properties
  */
class Data(
  data: Seq[Byte],
  props: SpriteProperties
) {

  /** Returns an array of pixel colours defined as tuples of an actual colour and its display priority. */
  lazy val pixels =
    data.grouped(3).toList.map(_.map(binaryString(_)).mkString.split("")).map(props.pixels(_)).flatMap(y => {
      val expanded =
        y.flatMap(x =>
          if (props.expandX)
            Seq(x, x)
          else
            Seq(x)
          )
      if (props.expandY)
        Seq(expanded, expanded)
      else
        Seq(expanded)
    })

  /** Renders pixel data of a first sprite and combines it with a target image.
   *
   * @param x an X coordinate of a top-left corner of a next rendered sprite image
   * @param y an Y coordinate of a top-left corner of a next rendered sprite image
   * @param targetImage target `Image` object (defaults to an empty image filled with a `background` colour)
   * @param scaleFactor defines custom image scale factor to be used when rendering a picture (defaults to 1, i.e. no upscaling)
   * @param topLeftX defines custom X coordinate considered as a top-left corner of a `targetImage`
   * @param topLeftY defines custom Y coordinate considered as a top-left corner of a `targetImage`
   * @param backgroundColour default background colour used to fill screen data of an empty target image (defaults to `0xe6`)
   *
   * @return a function creating an `ImagePlus` object which is capable of generating image preview
   */
  def render(
    x: Int,
    y: Int,
    targetImage: (Byte) => Image = (backgroundColour) => emptyImage(backgroundColour),
    scaleFactor: Int = 1,
    topLeftX: Int = 0,
    topLeftY: Int = 0,
    backgroundColour: Byte = 0xe6.toByte
  ): ((ImageProcessor, CBM, Palette, Int, Int) => Unit) => ImagePlus =
    setupRenderer(x, y, scaleFactor, topLeftX, topLeftY) {
      (postProcess) =>
        targetImage(backgroundColour).create(
          scaleFactor = scaleFactor,
          scaleOf = scaleOf,
          postProcessHook = postProcess
        )
    }

  /** Renders pixel data of a next sprite and combines it with a target image.
   *
   * @param x an X coordinate of a top-left corner of a next rendered sprite image
   * @param y an Y coordinate of a top-left corner of a next rendered sprite image
   * @param targetImage a callback function creating an `ImagePlus` object which is capable of generating image preview
   * @param scaleFactor defines custom image scale factor to be used when rendering a picture (defaults to 1, i.e. no upscaling)
   * @param topLeftX defines custom X coordinate considered as a top-left corner of a `targetImage`
   * @param topLeftY defines custom Y coordinate considered as a top-left corner of a `targetImage`
   *
   * @return a function rendering an `ImagePlus` object which is capable of generating image preview
   */
  def renderNext(
    x: Int,
    y: Int,
    targetImage: ((ImageProcessor, CBM, Palette, Int, Int) => Unit) => ImagePlus,
    scaleFactor: Int = 1,
    topLeftX: Int = 0,
    topLeftY: Int = 0
  ): ((ImageProcessor, CBM, Palette, Int, Int) => Unit) => ImagePlus =
    setupRenderer(x, y, scaleFactor, topLeftX, topLeftY) {
      (postProcess) =>
        targetImage(postProcess)
    }

  private def setupRenderer(
    x: Int,
    y: Int,
    scaleFactor: Int = 1,
    topLeftX: Int = 0,
    topLeftY: Int = 0
  )(
    renderer: ((ImageProcessor, CBM, Palette, Int, Int) => Unit) => ImagePlus
  ): ((ImageProcessor, CBM, Palette, Int, Int) => Unit) => ImagePlus = {
    assert(scaleFactor > 0)

    (postProcess) => renderer(
      (ip, picture, palette, xScale, yScale) => {
        renderDelayed(x, y, scaleFactor, topLeftX, topLeftY)((_, _, _, _, _) => {
          postProcess(ip, picture, palette, xScale, yScale)
        })(ip, picture, palette, xScale, yScale)
      }
    )
  }

  private def renderDelayed(
    x: Int,
    y: Int,
    scaleFactor: Int,
    topLeftX: Int,
    topLeftY: Int
  ) = (postProcess: ((ImageProcessor, CBM, Palette, Int, Int) => Unit)) => {

    (ip: ImageProcessor, picture: CBM, palette: Palette, imageScaleX: Int, imageScaleY: Int) => {

      val (spriteScaleX, spriteScaleY) = scaleOf(picture)

      val xScale = imageScaleX / spriteScaleX
      val yScale = imageScaleY / spriteScaleY

      pixels.zipWithIndex.foreach({ case (data, dy) =>
        data.zipWithIndex.foreach({
          case (Some((colour, hasPriority)), dx) => {
            val X = x - topLeftX + dx
            val Y = y - topLeftY + dy
            val pixel = palette.pixel(colour)
            // TODO: Respect sprite to background display priority - target image's pixel combinations may be
            // determined by querying appropriate pixel data from targetImage's "picture" property and delegating
            // computations to concrete "props" classes ("putPixel" won't always be called if hasPriority == false)
            (0 until xScale).foreach(i =>
              (0 until yScale).foreach(j =>
                ip.putPixel(X * xScale + i, Y * yScale + j, pixel)
              )
            )
          }
          case (None, _) =>
        })
      })

      postProcess(ip, picture, palette, xScale, yScale)
    }
  }

  private val scaleOf = (pic: CBM) => {
    pic match {
      case multiColour: MultiColour => (2, 1)
      case hiRes: HiRes => (1, 1)
      case _ => throw new RuntimeException("Something went wrong...")
    }
  }

  private def emptyImage(backgroundColour: Byte) = Image(
    picture = HiRes(
      bitmap = Bitmap(),
      screen = Some(Screen(fill = backgroundColour)),
      border = None
    ),
    palette = Palette("default")
  )

  private def binaryString(byte: Byte) =
    String.format("%8s", Integer.toBinaryString(byte & 0xFF)).replace(' ', '0')
}

/** Factory for [[com.github.pawelkrol.Afterimage.Sprite.Data]] instances. */
object Data {

  /** Creates a new `Data` sprite data.
    *
    * @param data an array of 63 sprite data bytes
    * @param props miscellaneous sprite properties
    * @return a new `Data` sprite data instance
    */
  def apply(data: Seq[Byte], props: SpriteProperties): Data = new Data(data = data, props = props)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy