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

com.github.pawelkrol.Afterimage.Mode.Data.Screen.scala Maven / Gradle / Ivy

The newest version!
package com.github.pawelkrol.Afterimage
package Mode.Data

/** Screen colours data of an image.
  *
  * @constructor create a new screen data
  * @param data an array of bytes (must equal "cols * rows" result)
  * @param cols screen width counted as a number of 8x8 character columns
  * @param rows screen height counted as a number of 8x8 character rows
  */
case class Screen(data: Array[Byte], cols: Int, rows: Int) {

  require(
    cols >= 1 && cols <= Screen.maxCols && rows >= 1 && rows <= Screen.maxRows,
    "Invalid screen colours data size: got %s, but expected anything between 1x1 and 40x25 with row and column values counted as occurences of 8x8 character areas".format(
      "%dx%d".format(cols, rows)
    )
  )

  require(
    data.length == cols * rows,
    "Invalid screen colours data length: got %d, but expected %d bytes".format(data.length, cols * rows)
  )

  /** Returns the entire screen colours data as an array of bytes. */
  def get(): Array[Byte] = data

  def validateScreenCoordinates(x: Int, y: Int) =
    require(
      x >= 0 && x < cols && y >= 0 && y < rows,
      "Invalid pixel coordinates: got %d, but expected values within the range of %s".format(
        "[%s,%s]".format(x, y),
        "[%s,%s]".format(cols, rows)
      )
    )

  /** Returns screen colour at a given position.
    *
    * @param x X coordinate of the screen colour
    * @param y Y coordinate of the screen colour
    */
  def get(x: Int, y: Int): Byte = {
    validateScreenCoordinates(x, y)

    data(x + y * cols)
  }

  private def horizontalShift(dx: Int, fill: Byte) = {
    val screenRows = data.sliding(cols, cols)

    val newRows =
      if (dx < 0)
        screenRows.map(row => row.takeRight(cols + dx) ++ Array.fill((-dx).min(cols)){fill})
      else
        screenRows.map(row => Array.fill((dx).min(cols)){fill} ++ row.take(cols - dx))

    new Screen(newRows.reduce((a, b) => a ++ b), cols, rows)
  }

  private def verticalShift(dy: Int, fill: Byte) = {
    val screenRows = data.sliding(cols, cols).toList

    val addedRow = Array.fill(cols){fill}

    val newScreen =
      if (dy < 0) {
        // Advance the iterator past the first dy elements, and select all remaining values of the iterator:
        val init = screenRows.drop(-dy).take(rows)
        val tail = Array.fill((-dy).min(rows)){addedRow}
        (init ++ tail).reduce((a, b) => a ++ b)
      }
      else {
        // Select first rows - dy values of the iterator:
        val tail = screenRows.take(rows - dy)
        val init = Array.fill((dy).min(rows)){addedRow}
        (init ++ tail).reduce((a, b) => a ++ b)
      }

    new Screen(newScreen, cols, rows)
  }

  /** Returns the new screen colours data composed from the original image with a shifted content.
    *
    * @param dx value of horizontal data shift (positive for shifting to the right, and negative for shifting to the left)
    * @param dy value of vertical data shift (positive for shifting to the bottom, and negative for shifting to the top)
    * @param fill optional fill value of newly created empty bytes (defaults to 0x00)
    */
  def shift(dx: Int, dy: Int, fill: Byte = 0x00) =
    horizontalShift(dx, fill).verticalShift(dy, fill)

  private def getData(x: Int, y: Int, width: Int, height: Int) =
    (y to y + height.min(rows) - 1).map(row => {
      (x to x + width.min(cols) - 1).map(col => {
        get(col, row)
      })
    }).flatten.toArray

  /** Returns the new screen colours data composed from cutting out a slice of the original image.
   *
   * @param x X coordinate of the top-left corner of a rectangular selection area
   * @param y Y coordinate of the top-left corner of a rectangular selection area
   * @param newWidth total width of a rectangular selection area as a number of 8x8 character columns
   * @param newHeight total height of a rectangular selection area as a number of 8x8 character columns
   */
  def slice(x: Int, y: Int, newWidth: Int, newHeight: Int) = {
    val data = getData(x, y, newWidth, newHeight)
    new Screen(data, newWidth, newHeight)
  }

  /** Returns screen colour at a given position.
    *
    * @param x X coordinate of the screen colour
    * @param y Y coordinate of the screen colour
    */
  def apply(x: Int, y: Int): Byte = get(x, y)

  def canEqual(that: Any) = that.isInstanceOf[Screen]

  override def equals(other: Any) = other match {
    case that: Screen =>
      (that canEqual this) && (this.data.toList == that.data.toList) && (this.cols == that.cols) && (this.rows == that.rows)
    case _ =>
      false
  }
}

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

  /** Maximum possible number of columns of a Screen colours data. */
  val maxCols = 40

  /** Maximum possible number of rows of a Screen colours data. */
  val maxRows = 25

  private val maxSize = maxCols * maxRows

  /** Creates a default (empty) screen of a maximum possible size. */
  def apply(): Screen =
    Screen(Array.fill(maxSize){0x00}, maxCols, maxRows)

  /** Creates a screen of a maximum possible size filled all with a constant value. */
  def apply(fill: Byte): Screen =
    Screen(Array.fill(maxSize){fill}, maxCols, maxRows)

  /** Creates a default (empty) screen of a given size. */
  def apply(cols: Int, rows: Int): Screen =
    Screen(Array.fill(cols * rows){0x00}, cols, rows)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy