Basic implementation for new color system (6x8x5 for 8 bit, 16 grayscale palette, pure 16 color palette for 4 bit).

This commit is contained in:
Florian Nücke 2014-04-28 01:05:38 +02:00
parent f36835511a
commit e0b35d4729
9 changed files with 147 additions and 82 deletions

View File

@ -270,7 +270,7 @@ class PacketHandler extends CommonPacketHandler {
case Some(t: Rack) => t.terminals(p.readInt()).buffer
case _ => return // Invalid packet.
}
buffer.depth = PackedColor.Depth(p.readInt())
buffer.format = PackedColor.Depth.format(PackedColor.Depth(p.readInt()))
}
def onScreenFill(p: PacketParser) {

View File

@ -54,7 +54,7 @@ trait Buffer extends GuiScreen {
currentWidth = w
currentHeight = h
scale = changeSize(currentWidth, currentHeight)
BufferRenderer.compileText(scale, buffer.lines, buffer.color, buffer.depth)
BufferRenderer.compileText(scale, buffer.lines, buffer.color, buffer.format)
}
GL11.glPushMatrix()
RenderState.disableLighting()

View File

@ -19,9 +19,9 @@ object MonospaceFontRenderer {
val fontWidth = 5
val fontHeight = 9
def drawString(x: Int, y: Int, value: Array[Char], color: Array[Short], depth: PackedColor.Depth.Value) = this.synchronized(instance match {
def drawString(x: Int, y: Int, value: Array[Char], color: Array[Short], format: PackedColor.ColorFormat) = this.synchronized(instance match {
case None => OpenComputers.log.warning("Trying to render string with uninitialized MonospaceFontRenderer.")
case Some(renderer) => renderer.drawString(x, y, value, color, depth)
case Some(renderer) => renderer.drawString(x, y, value, color, format)
})
private class Renderer(private val textureManager: TextureManager) {
@ -71,7 +71,7 @@ object MonospaceFontRenderer {
GL11.glEndList()
}
def drawString(x: Int, y: Int, value: Array[Char], color: Array[Short], depth: PackedColor.Depth.Value) = {
def drawString(x: Int, y: Int, value: Array[Char], color: Array[Short], format: PackedColor.ColorFormat) = {
if (color.length != value.length) throw new IllegalArgumentException("Color count must match char count.")
if (Settings.get.textAntiAlias)
@ -90,7 +90,7 @@ object MonospaceFontRenderer {
var cbg = 0x000000
var offset = 0
var width = 0
for (col <- color.map(PackedColor.unpackBackground(_, depth))) {
for (col <- color.map(PackedColor.unpackBackground(_, format))) {
if (col != cbg) {
draw(cbg, offset, width)
cbg = col
@ -108,7 +108,7 @@ object MonospaceFontRenderer {
// Foreground second. We only have to flush when the color changes, so
// unless every char has a different color this should be quite efficient.
var cfg = -1
for ((ch, col) <- value.zip(color.map(PackedColor.unpackForeground(_, depth)))) {
for ((ch, col) <- value.zip(color.map(PackedColor.unpackForeground(_, format)))) {
val index = 1 + (chars.indexOf(ch) match {
case -1 => chars.indexOf('?')
case i => i

View File

@ -67,7 +67,7 @@ object BufferRenderer {
GL11.glEndList()
}
def compileText(scale: Double, lines: Array[Array[Char]], colors: Array[Array[Short]], depth: PackedColor.Depth.Value) =
def compileText(scale: Double, lines: Array[Array[Char]], colors: Array[Array[Short]], format: PackedColor.ColorFormat) =
if (textureManager.isDefined) {
GL11.glNewList(displayLists + 1, GL11.GL_COMPILE)
GL11.glPushAttrib(GL11.GL_DEPTH_BUFFER_BIT)
@ -75,7 +75,7 @@ object BufferRenderer {
GL11.glScaled(scale, scale, 1)
lines.zip(colors).zipWithIndex.foreach {
case ((line, color), i) => MonospaceFontRenderer.drawString(0, i * MonospaceFontRenderer.fontHeight, line, color, depth)
case ((line, color), i) => MonospaceFontRenderer.drawString(0, i * MonospaceFontRenderer.fontHeight, line, color, format)
}
GL11.glPopAttrib()

View File

@ -175,7 +175,7 @@ object ScreenRenderer extends TileEntitySpecialRenderer with Callable[Int] with
GL11.glTranslatef(0, 0, 0.01f)
for (((line, color), i) <- screen.buffer.lines.zip(screen.buffer.color).zipWithIndex) {
MonospaceFontRenderer.drawString(0, i * MonospaceFontRenderer.fontHeight, line, color, screen.buffer.depth)
MonospaceFontRenderer.drawString(0, i * MonospaceFontRenderer.fontHeight, line, color, screen.buffer.format)
}
if (doCompile) {

View File

@ -14,7 +14,7 @@ class Buffer(val owner: Buffer.Owner) extends api.network.Environment {
withConnector().
create()
val buffer = new TextBuffer(maxResolution, maxDepth)
val buffer = new TextBuffer(maxResolution, PackedColor.Depth.format(maxDepth))
def maxResolution = Settings.screenResolutionsByTier(owner.tier)
@ -30,13 +30,13 @@ class Buffer(val owner: Buffer.Owner) extends api.network.Environment {
// ----------------------------------------------------------------------- //
def depth = buffer.depth
def format = buffer.format
def depth_=(value: PackedColor.Depth.Value) = {
if (value > maxDepth)
def format_=(value: PackedColor.ColorFormat) = {
if (value.depth > maxDepth)
throw new IllegalArgumentException("unsupported depth")
if (buffer.depth = value) {
owner.onScreenDepthChange(value)
if (buffer.format = value) {
owner.onScreenDepthChange(value.depth)
true
}
else false

View File

@ -61,7 +61,7 @@ abstract class GraphicsCard extends ManagedComponent {
val (gmw, gmh) = maxResolution
val (smw, smh) = s.maxResolution
s.resolution = (math.min(gmw, smw), math.min(gmh, smh))
s.depth = PackedColor.Depth(math.min(maxDepth.id, s.maxDepth.id))
s.format = PackedColor.Depth.format(PackedColor.Depth(math.min(maxDepth.id, s.maxDepth.id)))
s.foreground = 0xFFFFFF
s.background = 0x000000
result(true)
@ -90,15 +90,15 @@ abstract class GraphicsCard extends ManagedComponent {
@Callback(direct = true)
def getDepth(context: Context, args: Arguments): Array[AnyRef] =
screen(s => result(PackedColor.Depth.bits(s.depth)))
screen(s => result(PackedColor.Depth.bits(s.format.depth)))
@Callback
def setDepth(context: Context, args: Arguments): Array[AnyRef] = {
val depth = args.checkInteger(0)
screen(s => result(s.depth = depth match {
case 1 => PackedColor.Depth.OneBit
case 4 if maxDepth >= PackedColor.Depth.FourBit => PackedColor.Depth.FourBit
case 8 if maxDepth >= PackedColor.Depth.EightBit => PackedColor.Depth.EightBit
screen(s => result(s.format = depth match {
case 1 => PackedColor.Depth.format(PackedColor.Depth.OneBit)
case 4 if maxDepth >= PackedColor.Depth.FourBit => PackedColor.Depth.format(PackedColor.Depth.FourBit)
case 8 if maxDepth >= PackedColor.Depth.EightBit => PackedColor.Depth.format(PackedColor.Depth.EightBit)
case _ => throw new IllegalArgumentException("unsupported depth")
}))
}
@ -152,9 +152,9 @@ abstract class GraphicsCard extends ManagedComponent {
screen(s => {
val char = s.get(x, y)
val color = s.color(y)(x)
val depth = s.depth
val foreground = PackedColor.unpackForeground(color, depth)
val background = PackedColor.unpackBackground(color, depth)
val format = s.format
val foreground = PackedColor.unpackForeground(color, format)
val background = PackedColor.unpackBackground(color, format)
result(char, foreground, background)
})
}
@ -222,7 +222,7 @@ abstract class GraphicsCard extends ManagedComponent {
buffer.foreground = 0xFFFFFF
message.source.host match {
case machine: machine.Machine if machine.lastError != null =>
if (buffer.depth > PackedColor.Depth.OneBit) buffer.background = 0x0000FF
if (buffer.format.depth > PackedColor.Depth.OneBit) buffer.background = 0x0000FF
else buffer.background = 0x000000
if (buffer.buffer.fill(0, 0, w, h, ' ')) {
buffer.owner.onScreenFill(0, 0, w, h, ' ')

View File

@ -1,5 +1,8 @@
package li.cil.oc.util
import li.cil.oc.api.Persistable
import net.minecraft.nbt.NBTTagCompound
object PackedColor {
object Depth extends Enumeration {
@ -10,74 +13,133 @@ object PackedColor {
case FourBit => 4
case EightBit => 8
}
def format(depth: Depth.Value) = depth match {
case OneBit => SingleBitFormat
case FourBit => new MutablePaletteFormat
case EightBit => new HybridFormat
}
}
private val rMask32 = 0xFF0000
private val gMask32 = 0x00FF00
private val bMask32 = 0x0000FF
private val rShift32 = 16
private val gShift32 = 8
private val bShift32 = 0
private abstract class ColorFormat {
private def extract(value: Int) = {
val r = (value >>> rShift32) & 0xFF
val g = (value >>> gShift32) & 0xFF
val b = (value >>> bShift32) & 0xFF
(r, g, b)
}
trait ColorFormat extends Persistable {
def depth: Depth.Value
def inflate(value: Int): Int
def deflate(value: Int): Int
override def load(nbt: NBTTagCompound) {}
override def save(nbt: NBTTagCompound) {}
}
private class SingleBitFormat extends ColorFormat {
def inflate(value: Int) = if (value == 0) 0x000000 else 0xFFFFFF
object SingleBitFormat extends ColorFormat {
override def depth = Depth.OneBit
def deflate(value: Int) = if (value == 0) 0 else 1
override def inflate(value: Int) = if (value == 0) 0x000000 else 0xFFFFFF
override def deflate(value: Int) = if (value == 0) 0 else 1
}
private class MultiBitFormat(rBits: Int, gBits: Int, bBits: Int) extends ColorFormat {
def mask(nBits: Int) = 0xFFFFFFFF >>> (32 - nBits)
abstract class PaletteFormat extends ColorFormat {
override def inflate(value: Int) = palette(math.max(0, math.min(palette.length - 1, value)))
private val bShift = 0
private val gShift = bBits
private val rShift = gShift + gBits
override def deflate(value: Int) = palette.map(delta(value, _)).zipWithIndex.minBy(_._1)._2
private val bMask = mask(bBits) << bShift
private val gMask = mask(gBits) << gShift
private val rMask = mask(rBits) << rShift
protected def palette: Array[Int]
private val bScale = 255.0 / ((1 << bBits) - 1)
private val gScale = 255.0 / ((1 << gBits) - 1)
private val rScale = 255.0 / ((1 << rBits) - 1)
def inflate(value: Int) = {
val r = ((((value & rMask) >>> rShift) * rScale + 0.5).toInt << rShift32) & rMask32
val g = ((((value & gMask) >>> gShift) * gScale + 0.5).toInt << gShift32) & gMask32
val b = ((((value & bMask) >>> bShift) * bScale + 0.5).toInt << bShift32) & bMask32
r | g | b
}
def deflate(value: Int) = {
val r = ((((value & rMask32) >>> rShift32) / rScale + 0.5).toInt << rShift) & rMask
val g = ((((value & gMask32) >>> gShift32) / gScale + 0.5).toInt << gShift) & gMask
val b = ((((value & bMask32) >>> bShift32) / bScale + 0.5).toInt << bShift) & bMask
r | g | b
protected def delta(colorA: Int, colorB: Int) = {
val (rA, gA, bA) = extract(colorA)
val (rB, gB, bB) = extract(colorB)
val dr = rA - rB
val dg = gA - gB
val db = bA - bB
0.2126 * dr * dr + 0.7152 * dg * dg + 0.0722 * db * db
}
}
private val formats = Map(
Depth.OneBit -> new SingleBitFormat(),
Depth.FourBit -> new MultiBitFormat(1, 2, 1),
Depth.EightBit -> new MultiBitFormat(3, 3, 2))
class MutablePaletteFormat extends PaletteFormat {
override def depth = Depth.FourBit
def apply(index: Int) = palette(index)
def update(index: Int, value: Int) = palette(index) = value
protected val palette = Array(
0x000000, 0xFF3333, 0x336600, 0x663300,
0x333399, 0x9933CC, 0x336699, 0xCCCCCC,
0x333333, 0xFF6699, 0x33CC33, 0xFFFF33,
0x6699FF, 0xCC66CC, 0xFFCC33, 0xFFFFFF)
override def load(nbt: NBTTagCompound) {
val loaded = nbt.getIntArray("palette")
Array.copy(loaded, 0, palette, 0, math.min(loaded.length, palette.length))
}
override def save(nbt: NBTTagCompound) {
nbt.setIntArray("palette", palette)
}
}
class HybridFormat extends MutablePaletteFormat {
private val reds = 6
private val greens = 8
private val blues = 5
// Initialize palette to grayscale, excluding black and white, because
// those are already contained in the normal color cube.
for (i <- 0 until palette.length) {
val shade = 0xFF * (i + 1) / (palette.length + 1)
this(i) = (shade << rShift32) | (shade << gShift32) | (shade << bShift32)
}
override def depth = Depth.EightBit
override def inflate(value: Int) = {
if (value < palette.length) super.inflate(value)
else {
val index = value - palette.length
val idxB = index % blues
val idxG = (index / blues) % greens
val idxR = (index / blues / greens) % reds
val r = (idxR * 0xFF / (reds - 1.0) + 0.5).toInt
val g = (idxG * 0xFF / (greens - 1.0) + 0.5).toInt
val b = (idxB * 0xFF / (blues - 1.0) + 0.5).toInt
(r << rShift32) | (g << gShift32) | (b << bShift32)
}
}
override def deflate(value: Int) = {
val (r, g, b) = extract(value)
val idxR = (r * (reds - 1.0) / 0xFF + 0.5).toInt
val idxG = (g * (greens - 1.0) / 0xFF + 0.5).toInt
val idxB = (b * (blues - 1.0) / 0xFF + 0.5).toInt
palette.length + idxR * greens * blues + idxG * blues + idxB
}
}
// Colors are packed: 0xFFBB (F = foreground, B = background)
private val fgShift = 8
private val bgMask = 0x000000FF
def pack(foreground: Int, background: Int, depth: Depth.Value) = {
val format = formats(depth)
def pack(foreground: Int, background: Int, format: ColorFormat) = {
((format.deflate(foreground) << fgShift) | format.deflate(background)).toShort
}
def unpackForeground(color: Short, depth: Depth.Value) =
formats(depth).inflate((color & 0xFFFF) >>> fgShift)
def unpackForeground(color: Short, format: ColorFormat) =
format.inflate((color & 0xFFFF) >>> fgShift)
def unpackBackground(color: Short, depth: Depth.Value) =
formats(depth).inflate(color & bgMask)
def unpackBackground(color: Short, format: ColorFormat) =
format.inflate(color & bgMask)
}

View File

@ -11,46 +11,46 @@ import net.minecraft.nbt._
* relatively fast updates, given a smart algorithm (using copy()/fill()
* instead of set()ing everything).
*/
class TextBuffer(var width: Int, var height: Int, initialDepth: PackedColor.Depth.Value) {
def this(size: (Int, Int), depth: PackedColor.Depth.Value) = this(size._1, size._2, depth)
class TextBuffer(var width: Int, var height: Int, initialFormat: PackedColor.ColorFormat) {
def this(size: (Int, Int), format: PackedColor.ColorFormat) = this(size._1, size._2, format)
private var _depth = initialDepth
private var _format = initialFormat
private var _foreground = 0xFFFFFF
private var _background = 0x000000
private var packed = PackedColor.pack(_foreground, _background, _depth)
private var packed = PackedColor.pack(_foreground, _background, _format)
def foreground = _foreground
def foreground_=(value: Int) = {
_foreground = value
packed = PackedColor.pack(_foreground, _background, _depth)
packed = PackedColor.pack(_foreground, _background, _format)
}
def background = _background
def background_=(value: Int) = {
_background = value
packed = PackedColor.pack(_foreground, _background, _depth)
packed = PackedColor.pack(_foreground, _background, _format)
}
def depth = _depth
def format = _format
def depth_=(value: PackedColor.Depth.Value) = {
if (depth != value) {
def format_=(value: PackedColor.ColorFormat) = {
if (format.depth != value.depth) {
for (row <- 0 until height) {
val rowColor = color(row)
for (col <- 0 until width) {
val packed = rowColor(col)
val fg = PackedColor.unpackForeground(packed, _depth)
val bg = PackedColor.unpackBackground(packed, _depth)
val fg = PackedColor.unpackForeground(packed, _format)
val bg = PackedColor.unpackBackground(packed, _format)
rowColor(col) = PackedColor.pack(fg, bg, value)
}
}
_depth = value
packed = PackedColor.pack(_foreground, _background, _depth)
_format = value
packed = PackedColor.pack(_foreground, _background, _format)
true
}
else false
@ -178,7 +178,9 @@ class TextBuffer(var width: Int, var height: Int, initialDepth: PackedColor.Dept
}
}
_depth = PackedColor.Depth(nbt.getInteger("depth") max 0 min PackedColor.Depth.maxId)
val depth = PackedColor.Depth(nbt.getInteger("depth") max 0 min PackedColor.Depth.maxId)
_format = PackedColor.Depth.format(depth)
_format.load(nbt)
foreground = nbt.getInteger("foreground")
background = nbt.getInteger("background")
@ -207,7 +209,8 @@ class TextBuffer(var width: Int, var height: Int, initialDepth: PackedColor.Dept
}
nbt.setTag("buffer", b)
nbt.setInteger("depth", _depth.id)
nbt.setInteger("depth", _format.depth.id)
_format.save(nbt)
nbt.setInteger("foreground", _foreground)
nbt.setInteger("background", _background)