Skip to content

Commit

Permalink
Color transform pngs (#92)
Browse files Browse the repository at this point in the history
* Allow --text on Labels instead of --label.

* feedback

* Add ability to color-transform PNG images

* feedback

* feedback
  • Loading branch information
Erik Corry authored Feb 29, 2024
1 parent 4a6f7c4 commit 17ad4d4
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 4 deletions.
88 changes: 87 additions & 1 deletion src/png.toit
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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)
Expand All @@ -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_
8 changes: 7 additions & 1 deletion tests/bitmap-2-visualized.toit
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -45,13 +45,19 @@ 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)
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-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

Expand Down
Binary file modified tests/gold/bitmap-2-visualized.toit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/gold/three-color-bitmap-2-visualized.toit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions tests/png-visualizer.toit
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
6 changes: 5 additions & 1 deletion tests/three-color-bitmap-2-visualized.toit
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion tests/toit-png-tools

0 comments on commit 17ad4d4

Please sign in to comment.