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

com.github.pawelkrol.Afterimage.File.Import.Piece.scala Maven / Gradle / Ivy

The newest version!
package com.github.pawelkrol.Afterimage
package File.Import

import scala.math.Ordering.Double.TotalOrdering

import Colour.{Colour, Palette}

/** Small image piece (8x8 pixels) abstraction.
  *
  * @constructor create an image data representation equivalent to a single CBM 8x8 char area
  * @param pixels a two-dimensional 8x8 array of pixel colours
  */
class Piece(
  pixels: Seq[Seq[Colour]]
) {

  /** Returns a two-dimensional 8x8 array of pixel colours that was used to initialize this object. */
  def getPixels = pixels

  private def getEverySecondPixels = pixels.grouped(2).toList.map(_.head)

  private def findTopNColours(palette: Palette, n: Int, backgroundColour: Option[Byte] = None): List[Byte] = {

    // For multicolour pictures (n == 4) fetch every second pixel from piece data:
    val analysedPixels =
      if (n == 4)
        getEverySecondPixels
      else
        getPixels

    // Find number of occurrences of each colour within this piece:
    val occurrences = analysedPixels.flatten.foldLeft[Map[Int,Int]](Map[Int,Int]())((ranking, colour) => {

      // Find the closest colour from the provided palette, as only they matter:
      val colourC64 = palette.get(colour)

      if (ranking.contains(colourC64)) {
        val count = ranking(colourC64) + 1
        ranking - (colourC64) + ((colourC64, count))
      }
      else
        ranking + ((colourC64, 1))
    })

    // Sort calculated occurrences of each colour within this piece:
    val sortedOccurrences = occurrences.toList.sortWith((a, b) => a._2 > b._2).map(_._1.toByte)

    // If background colour has been provided, put it into the front of a sorted list:
    val allSortedOccurrences =
      backgroundColour match {
        case Some(bckgrd) => {
          bckgrd +: sortedOccurrences.filter(colour => colour != bckgrd)
        }
        case None =>
          sortedOccurrences
      }

    // Return "n" most common colours from the ordered list (do it by duplicating the last
    // element "n" times in order to make sure that list contains at least "n" elements):
    (allSortedOccurrences ++ List.fill[Byte](n - 1)(allSortedOccurrences.last)).slice(0, n)
  }

  /** Converts piece data into HiRes image using provided colour palette and optional background colour.
    *
    * @param palette colour palette to be used during conversion process
    * @param backgroundColour enforce usage of a constant background colour to a target image data (0x00..0x0f)
    * @return a tuple of 8 bitmap and 1 screen data bytes
    */
  def toHiRes(palette: Palette, backgroundColour: Option[Byte] = None): Tuple2[Seq[Byte], Byte] = {

    val top2Colours = findTopNColours(palette, 2, backgroundColour)
    val (bckgrdC64, colourC64) = (top2Colours(0), top2Colours(1))

    val colour = palette(colourC64)
    val bckgrd = palette(bckgrdC64)

    // Map each colour from the 8x8 "pixels" array into the closest match from
    // the list of two top colours:
    val pixelsC64 = getPixels.map(column => {
      column.map(pixel => {
        if (colour.delta_to(pixel) <= bckgrd.delta_to(pixel))
          colourC64
        else
          bckgrdC64
      })
    })

    // Convert each row of pixels into an actual bitmap data of an 8x8 char:
    val bitmap = (0x00 until 0x08).map(x => {
      pixelsC64.foldLeft[Tuple2[Int, Int]](Tuple2[Int, Int](0x00, 0x80))((result, column) => {
        val (byte, bitMask) = result

        val newByte =
          if (column(x) == bckgrdC64)
            byte
          else
            byte | bitMask

        (newByte, bitMask >> 1)
      })._1.toByte
    })

    val screen = (colourC64 << 4) | bckgrdC64

    (bitmap, screen.toByte)
  }

  /** Converts piece data into MultiColour image using provided colour palette and background colour.
    *
    * @param palette colour palette to be used during conversion process
    * @param backgroundColour background colour of a target image data (0x00..0x0f)
    * @return a tuple of 8 bitmap, 1 screen and 1 colour data bytes
    */
  def toMultiColour(palette: Palette, backgroundColour: Byte): Tuple3[Seq[Byte], Byte, Byte] = {

    val top4Colours = findTopNColours(palette, 4, Some(backgroundColour))
    val (bckgrdC64, screenC64lo, screenC64hi, colourC64) = (top4Colours(0).toInt, top4Colours(1).toInt, top4Colours(2).toInt, top4Colours(3).toInt)

    val colour = palette(colourC64)
    val bckgrd = palette(bckgrdC64)
    val screenLo = palette(screenC64lo)
    val screenHi = palette(screenC64hi)

    // Image colours contain the list of piece colours, where each tuple consists of:
    // 1. C64 colour
    // 2. Colour object
    // 3. Bit values inside an entire byte (e.g., "01010101")
    val imageColours = List[Tuple3[Int, Colour, Int]](
      (bckgrdC64, bckgrd, 0x00),
      (screenC64hi, screenHi, 0x55),
      (screenC64lo, screenLo, 0xaa),
      (colourC64, colour, 0xff)
    )

    // Map each colour from the 8x8 "pixels" array into the closest match from
    // the list of four top colours:
    val pixelsColours = getEverySecondPixels.map(column => {
      column.map(pixel => {
        // Find which of the most popular colours (colour, bckgrd, screenLo,
        // screenHi) is the closest one to this currently analysed pixel:
        imageColours.minBy(_._2.delta_to(pixel))(TotalOrdering)
      })
    })

    // Convert each row of pixel colours into an actual bitmap data of an 8x8 char:
    val bitmap = (0x00 until 0x08).map(x => {
      pixelsColours.foldLeft[Tuple2[Int, Int]](Tuple2[Int, Int](0x00, 0xc0))((result, column) => {
        val (byte, bitMask) = result
        val pixelColour = column(x)
        val (colourC64, colour, bits) = pixelColour

        val bitsToSet = bits & bitMask

        val newByte = byte | bitsToSet

        (newByte, bitMask >> 2)
      })._1.toByte
    })

    val screen = (screenC64hi << 4) | screenC64lo
    val colors = colourC64

    (bitmap, screen.toByte, colors.toByte)
  }

  override def toString = {

    val colours = (0 until 8).map(x => {
      (0 until 8).map(y => {
        "[x=%d,y=%d] => %s".format(x, y, pixels(x)(y))
      }).mkString("\n  ")
    }).mkString("\n  ")

    "Piece(pixels = \n  %s\n)".format(colours)
  }

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

  override def equals(other: Any) = other match {
    case that: Piece =>
      (that canEqual this) && (this.getPixels == that.getPixels)
    case _ =>
      false
  }
}

/** Factory for [[com.github.pawelkrol.Afterimage.File.Import.Piece]] instances. */
object Piece {

  val emptyPixelArray =
    Seq.fill[Seq[Colour]](0x08)(
      Seq.fill[Colour](0x08)(
        Colour(0x00, 0x00, 0x00, None)
      )
    )

  /** Creates an empty `Piece` of 8x8 pixels image data. */
  def apply() = new Piece(emptyPixelArray)

  /** Creates a new `Piece` of 8x8 pixels image data provided a two-dimensional 8x8 colour array. */
  def apply(pixels: Seq[Seq[Colour]]) = new Piece(pixels)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy