# HG changeset patch # User Laurens Holst # Date 1588017959 -7200 # Mon Apr 27 22:05:59 2020 +0200 # Node ID 2b0fe71d13ff730a57b5bb7df2389519c7fda9b5 # Parent 8d6ecfdee53b0813230776c84aa0532040759800 images: Store RGB colours as homogeneous coordinates. So that the exact value is preserved and they’re easier to convert to different bit-depths. diff --git a/tools/images.js b/tools/images.js --- a/tools/images.js +++ b/tools/images.js @@ -36,7 +36,7 @@ } else if (screenMode == 8) { for (let y = 0; y < this.height; y++) { for (let x = 0; x < this.width; x++) { - const pixel = this.getPixel(x, y); + const pixel = this.getPixel(x, y).project(7).round(); pixelData.push(pixel.g << 5 | pixel.r << 2 | pixel.b >> 1); } } @@ -134,17 +134,52 @@ } } -class PaletteColor { - constructor(index, r, g, b) { +class Color { + constructor(r, g, b, w) { checkTypes(arguments, "number", "number", "number", "number"); - this.index = index; this.r = r; this.g = g; this.b = b; + this.w = w; + } + + multiply(other) { + return new Color(this.r * other.r, this.g * other.g, this.b * other.b, this.w * other.w); + } + + power(exponent) { + return new Color(Math.pow(this.r, exponent.r), Math.pow(this.g, exponent.g), Math.pow(this.b, exponent.b), Math.pow(this.w, exponent.w)); + } + + dot(other) { + return this.r * other.r + this.g * other.g + this.b * other.b + this.w * other.w; + } + + project(scale) { + return new Color(this.r * scale / this.w, this.g * scale / this.w, this.b * scale / this.w, scale); + } + + round() { + return new Color(Math.round(this.r), Math.round(this.g), Math.round(this.b), Math.round(this.w)); + } +} + +class ScalarColor extends Color { + constructor(scalar) { + super(scalar, scalar, scalar, scalar); + } +} + +class PaletteColor extends Color { + constructor(index, r, g, b, w) { + checkTypes(arguments, "number", "number", "number", "number", "number"); + super(r, g, b, w); + this.index = index; } getWord() { - return this.r << 8 | this.g << 4 | this.b; + const color = this.project(7).round(); + return color.r << 8 | color.g << 4 | color.b; } toAsm() { @@ -156,10 +191,7 @@ constructor(path, gamma) { checkTypes(arguments, "string"); this.path = path; - this.valueMapping3Bit = new Array(256); - for (let i = 0; i < 256; i++) { - this.valueMapping3Bit[i] = Math.round(Math.pow(i / 255, gamma / 2.2) * 7); - } + this.gamma = gamma; } async loadImage() { @@ -182,12 +214,10 @@ toPalette(palette8bit) { checkTypes(arguments, Array); const palette = new Palette(); - const palette3bit = palette8bit.map(c => this.valueMapping3Bit[c]); - for (let i = 0; i < palette3bit.length / 3; i++) { - const r = palette3bit[i * 3] || 0; - const g = palette3bit[i * 3 + 1] || 0; - const b = palette3bit[i * 3 + 2] || 0; - palette.addColor(new PaletteColor(i, r, g, b)); + for (let i = 0; i < palette8bit.length / 3; i++) { + const color = new Color(palette8bit[i * 3] || 0, palette8bit[i * 3 + 1] || 0, palette8bit[i * 3 + 2] || 0, 255); + const gammaColor = color.power(new ScalarColor(this.gamma / 2.2)); + palette.addColor(new PaletteColor(i, gammaColor.r, gammaColor.g, gammaColor.b, gammaColor.w)); } return palette; }