diff --git a/src/png.toit b/src/png.toit index 3921827..d0f4dcb 100644 --- a/src/png.toit +++ b/src/png.toit @@ -36,6 +36,12 @@ Only PNGs with up to 8 bits per pixel are supported. They can be */ class Png extends CustomElement: png_/png-reader.AbstractPng + last-palette_/ByteArray? := null + last-alpha-palette_/ByteArray? := null + last-transformed-palette_/ByteArray? := null + last-transformed-alpha-palette_/ByteArray? := null + palette-transformer_/PaletteTransformer? := null + palette-transformer-buffer_/ByteArray? := null /** Constructs an element that displays a PNG image, given a byte array @@ -51,7 +57,10 @@ class Png extends CustomElement: --id/string?=null --background=null --border/Border?=null - --png-file/ByteArray: + --png-file/ByteArray + --color/int?=null + --palette-transformer/PaletteTransformer?=null: + palette-transformer_ = palette-transformer info := png-reader.PngInfo png-file if info.uncompressed-random-access: png_ = png-reader.PngRandomAccess png-file @@ -69,6 +78,46 @@ class Png extends CustomElement: --id = id --background = background --border = border + if color: this.color = color + + /** + Causes the PNG to be redrawn with new values from the palette transformer. + */ + invalidate-palette-transformer -> none: + invalidate + last-transformed-palette_ = null + last-transformed-alpha-palette_ = null + + /** + Causes the palette of the PNG to be ignored, and all pixels will + be drawn with the given color. Alpha (transparency) will be + unchanged. + */ + color= value/int -> none: + invalidate-palette-transformer + palette-transformer_ = SingleColorPaletteTransformer_ value + + set-attribute_ key/string value -> none: + if key == "color": + color = value + else: + super key value + /** + The $palette-transformer is an optional PaletteTransformer that transforms + the colors in the PNG. This can be used for example if you have a PNG + that is black-and-transparent, and you want to display it as an + image that is red-and-transparent. + The palette transformer is not called eagerly, so if it is + going to return new values (eg. to change the color of the PNG) + you must call $invalidate-palette-transformer. + A simpler way to use palette transformation is to simply set the + color on this element. This will cause all PNG pixels to be + drawn with the same color, but alpha (transparency) will still + be taken from the PNG file. + */ + palette-transformer= value/PaletteTransformer?: + invalidate-palette-transformer + palette-transformer_ = value // Redraw routine. custom-draw canvas/Canvas: @@ -77,6 +126,26 @@ class Png extends CustomElement: png_.get-indexed-image-data y2 h --accept-8-bit=canvas.supports-8-bit --need-gray-palette=canvas.gray-scale: | y-from/int y-to/int bits-per-pixel/int pixels/ByteArray line-stride/int palette/ByteArray alpha-palette/ByteArray | + if palette-transformer_: + if palette != last-palette_ or alpha-palette != last-alpha-palette_: + if last-transformed-alpha-palette_ == null or + last-transformed-palette_.size != palette.size: + last-transformed-palette_ = ByteArray palette.size + if last-transformed-alpha-palette_ == null or + last-transformed-alpha-palette_.size != palette.size: + last-transformed-alpha-palette_ = ByteArray palette.size + if palette-transformer-buffer_ == null: + palette-transformer-buffer_ = ByteArray 4 + (palette.size / 3).repeat: | i | + 3.repeat: + palette-transformer-buffer_[it] = palette[i * 3 + it] + palette-transformer-buffer_[3] = alpha-palette[i] + palette-transformer_.transform palette-transformer-buffer_ + 3.repeat: + last-transformed-palette_[i * 3 + it] = palette-transformer-buffer_[it] + last-transformed-alpha-palette_[i] = palette-transformer-buffer_[3] + palette = last-transformed-palette_ + alpha-palette = last-transformed-alpha-palette_ if bits-per-pixel == 1: // Last line a little shorter because it has no stride padding. adjust := line-stride - ((round-up w 8) >> 3) @@ -98,3 +167,20 @@ class Png extends CustomElement: y2 = y-to type -> string: return "png" + +interface PaletteTransformer: + /** + Takes a 4-element byte array in rgba order and modifies the + byte array to indicate which color and transparency should + be used instead. + */ + transform rgba/ByteArray -> none + +class SingleColorPaletteTransformer_ implements PaletteTransformer: + color_/int + constructor .color_: + + transform rgba/ByteArray -> none: + rgba[0] = color_ >> 16 + rgba[1] = color_ >> 8 + rgba[2] = color_ diff --git a/tests/bitmap-2-visualized.toit b/tests/bitmap-2-visualized.toit index 3093005..71967c5 100644 --- a/tests/bitmap-2-visualized.toit +++ b/tests/bitmap-2-visualized.toit @@ -22,7 +22,7 @@ main args: basename := args[0] - driver := TrueColorPngVisualizer 524 240 basename --outline=0xffffff + driver := TrueColorPngVisualizer 800 240 basename --outline=0xffffff display := PixelDisplay.true-color driver display.background = 0xe0e080 @@ -45,6 +45,9 @@ main args: display.add (Png --x=268 --y=32 --png-file=heater-bw) display.add (Png --x=352 --y=32 --png-file=heater-white-bg) display.add (Png --x=436 --y=32 --png-file=heater-translucent) + display.add (Png --x=520 --y=32 --png-file=heater-4-bit --color=0x4080ff) + display.add (Png --x=604 --y=32 --png-file=heater-4-bit --palette-transformer=SwapRedAndBlack) + display.add (Png --x=688 --y=32 --png-file=heater-bw --color=0x20ffe0) display.add (Png --x=16 --y=120 --png-file=heater-uncompressed) display.add (Png --x=100 --y=120 --png-file=heater-4-bit-uncompressed) @@ -52,6 +55,9 @@ main args: display.add (Png --x=268 --y=120 --png-file=heater-bw-uncompressed) display.add (Png --x=352 --y=120 --png-file=heater-white-bg-uncompressed) display.add (Png --x=436 --y=120 --png-file=heater-translucent-uncompressed) + display.add (Png --x=520 --y=120 --png-file=heater-4-bit-uncompressed --color=0x4080ff) + display.add (Png --x=604 --y=120 --png-file=heater-4-bit-uncompressed --palette-transformer=SwapRedAndBlack) + display.add (Png --x=688 --y=120 --png-file=heater-bw-uncompressed --color=0x20ffe0) display.draw diff --git a/tests/gold/bitmap-2-visualized.toit.png b/tests/gold/bitmap-2-visualized.toit.png index 6cfa6ff..e1fbc7d 100644 Binary files a/tests/gold/bitmap-2-visualized.toit.png and b/tests/gold/bitmap-2-visualized.toit.png differ diff --git a/tests/gold/three-color-bitmap-2-visualized.toit.png b/tests/gold/three-color-bitmap-2-visualized.toit.png index 2085906..a40ab18 100644 Binary files a/tests/gold/three-color-bitmap-2-visualized.toit.png and b/tests/gold/three-color-bitmap-2-visualized.toit.png differ diff --git a/tests/png-visualizer.toit b/tests/png-visualizer.toit index 41b1509..df08a5d 100644 --- a/tests/png-visualizer.toit +++ b/tests/png-visualizer.toit @@ -9,6 +9,7 @@ import crypto.crc show * import host.file import monitor show Latch import pixel-display show * +import pixel-display.png import png-tools.png-writer import png-tools.png-reader show * import zlib @@ -403,3 +404,9 @@ byte-swap_ ba/ByteArray -> ByteArray: result := ba.copy byte-swap-32 result return result + +class SwapRedAndBlack implements png.PaletteTransformer: + transform palette/ByteArray -> none: + palette[0] = (palette[0] >= 0x80) ? 0 : 0xff + palette[1] = 0 + palette[2] = 0 diff --git a/tests/three-color-bitmap-2-visualized.toit b/tests/three-color-bitmap-2-visualized.toit index 700e04f..93b649b 100644 --- a/tests/three-color-bitmap-2-visualized.toit +++ b/tests/three-color-bitmap-2-visualized.toit @@ -23,7 +23,7 @@ main args: basename := args[0] - driver := ThreeColorPngVisualizer 440 240 basename --outline=BLACK + driver := ThreeColorPngVisualizer 610 240 basename --outline=BLACK display := PixelDisplay.three-color driver display.background = WHITE @@ -40,11 +40,15 @@ main args: display.add (Png --x=184 --y=32 --png-file=heater-2-bit) display.add (Png --x=268 --y=32 --png-file=heater-bw) display.add (Png --x=352 --y=32 --png-file=heater-white-bg) + display.add (Png --x=436 --y=32 --png-file=heater-bw --color=0xff0000) + display.add (Png --x=520 --y=32 --png-file=heater-2-bit --palette-transformer=SwapRedAndBlack) display.add (Png --x=100 --y=120 --png-file=heater-red-uncompressed) display.add (Png --x=184 --y=120 --png-file=heater-2-bit-uncompressed) display.add (Png --x=268 --y=120 --png-file=heater-bw-uncompressed) display.add (Png --x=352 --y=120 --png-file=heater-white-bg-uncompressed) + display.add (Png --x=436 --y=120 --png-file=heater-bw-uncompressed --color=0xff0000) + display.add (Png --x=520 --y=120 --png-file=heater-2-bit-uncompressed --palette-transformer=SwapRedAndBlack) display.draw diff --git a/tests/toit-png-tools b/tests/toit-png-tools index 8387669..ee472ac 160000 --- a/tests/toit-png-tools +++ b/tests/toit-png-tools @@ -1 +1 @@ -Subproject commit 8387669efa33a7108c746dcb19dd5ad0ac8f89d0 +Subproject commit ee472ac9d4333a138206f9d3a817d3c5432d6f87