All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
eu.joaocosta.minart.graphics.image.qoi.QoiImageWriter.scala Maven / Gradle / Ivy
package eu.joaocosta.minart.graphics.image.qoi
import java.io.OutputStream
import scala.annotation.tailrec
import eu.joaocosta.minart.graphics.*
import eu.joaocosta.minart.graphics.image.*
import eu.joaocosta.minart.internal.*
/** Image writer for QOI files.
*/
trait QoiImageWriter extends ImageWriter {
import ByteWriter.*
private def storeHeader(surface: Surface): ByteStreamState[String] = {
(for {
_ <- writeString("qoif")
_ <- writeBENumber(surface.width, 4)
_ <- writeBENumber(surface.height, 4)
_ <- writeByte(3) // channels
_ <- writeByte(0) // linear colorspace
} yield ())
}
private def toOps(surface: Surface): List[Op] =
surface
.getPixels()
.iterator
.flatten
.foldLeft(QoiImageWriter.QoiState()) { case (state, color) => state.addMinartColor(color) }
.opAcc
.reverse
private def writeOp(op: Op): ByteStreamState[String] = op match {
case Op.OpRgb(r, g, b) => writeBytes(Vector(254, r, g, b))
case Op.OpRgba(r, g, b, a) => writeBytes(Vector(255, r, g, b, a))
case Op.OpIndex(index) =>
if (index < 0 || index > 63) State.error(s"Invalid index: $index")
else writeByte(index)
case Op.OpRun(run) =>
if (run < 1 || run > 62) State.error(s"Invalid run size: $run")
else writeByte((run - 1) | 0xc0)
case _ =>
State.error("Unsupported Op")
}
@tailrec
private def storeOps(ops: List[Op], acc: ByteStreamState[String] = emptyStream): ByteStreamState[String] = ops match {
case Nil => acc
case op :: xs =>
val nextStream = (for {
_ <- acc
_ <- writeOp(op)
} yield ())
storeOps(ops.tail, nextStream)
}
private val storeTrail: ByteStreamState[String] = writeBytes(Vector(0, 0, 0, 0, 0, 0, 0, 1))
final def storeImage(surface: Surface, os: OutputStream): Either[String, Unit] = {
val state = for {
_ <- storeHeader(surface)
_ <- storeOps(toOps(surface))
_ <- storeTrail
} yield ()
toOutputStream(state, os)
}
}
object QoiImageWriter {
private final case class QoiState(
opAcc: List[Op] = Nil,
previousColor: Option[QoiColor] = None,
colorMap: Vector[QoiColor] = Vector.fill(64)(QoiColor(0, 0, 0, 0))
) {
def addMinartColor(color: Color): QoiState =
addColor(QoiColor.fromMinartColor(color))
def addColor(color: QoiColor): QoiState = {
lazy val hash = color.hash
val nextAcc =
if (previousColor.contains(color)) opAcc match {
case Op.OpRun(run) :: xs if run < 62 => Op.OpRun(run + 1) :: xs
case Op.OpRun(run) :: _ => Op.OpRgb(color.r, color.g, color.b) :: opAcc
case _ => Op.OpRun(1) :: opAcc
}
else if (colorMap(hash) == color) Op.OpIndex(hash) :: opAcc
else Op.OpRgb(color.r, color.g, color.b) :: opAcc
QoiState(nextAcc, Some(color), colorMap.updated(hash, color))
}
}
}