Skip to content

Commit f2e5278

Browse files
authored
image: add support for image/jpeg and image/png (#303)
* Copy from go1.17 image package * Remove unnecessary files * Reduce memory usage * Add examples/ili9341/slideshow * image: add ./image/README.md * image: change convert2bin to . /cmd * Makefile: add ./cmd to NOTEST
1 parent 259593e commit f2e5278

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+19324
-1
lines changed

Makefile

+3-1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ smoke-test:
7575
@md5sum ./build/test.hex
7676
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/ili9341/scroll
7777
@md5sum ./build/test.hex
78+
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/slideshow
79+
@md5sum ./build/test.hex
7880
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis3dh/main.go
7981
@md5sum ./build/test.hex
8082
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/lsm303agr/main.go
@@ -208,7 +210,7 @@ DRIVERS = $(wildcard */)
208210
NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \
209211
hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \
210212
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht keypad4x4 max72xx p1am tone tm1637 \
211-
pcf8563 mcp2515 servo sdcard rtl8720dn
213+
pcf8563 mcp2515 servo sdcard rtl8720dn image cmd
212214
TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS))
213215

214216
unit-test:

cmd/convert2bin/convert2bin.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"log"
7+
"os"
8+
"strings"
9+
)
10+
11+
// See ../../image/README.md for the usage.
12+
13+
func main() {
14+
err := run(os.Args)
15+
if err != nil {
16+
log.Fatal(err)
17+
}
18+
}
19+
20+
func run(args []string) error {
21+
if len(args) < 2 {
22+
return fmt.Errorf("usage: %s FILE")
23+
}
24+
25+
b, err := ioutil.ReadFile(args[1])
26+
if err != nil {
27+
return err
28+
}
29+
30+
fmt.Printf("const %s = \"\" +\n", strings.Replace(args[1], ".", "_", -1))
31+
32+
i := 0
33+
max := 32
34+
for i = 0; i < len(b); i++ {
35+
bb := b[i]
36+
if (i % max) == 0 {
37+
fmt.Printf(" \"")
38+
}
39+
fmt.Printf("\\x%02X", bb)
40+
if (i%max) == max-1 && i != len(b)-1 {
41+
fmt.Printf("\" + \n")
42+
}
43+
}
44+
if (i % max) < max-1 {
45+
fmt.Printf("\"\n")
46+
}
47+
48+
return nil
49+
}

examples/ili9341/slideshow/README.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# examples/ili9341/slideshow
2+
3+
![](./slideshow.jpg)
4+
5+
This example uses the image package for TinyGo to display png and jpeg images.
6+
7+
## How to create an image
8+
9+
The following program will output an image binary like the one in [images.go](./images.go).
10+
11+
```
12+
go run ./examples/ili9341/slideshow/convert2bin ./path/to/png_or_jpg.png
13+
```
14+
15+
## Notes
16+
17+
Displaying a 320x240 png or jpeg often requires more than 50KB of memory.
18+
The examples include samd21 settings, but if you run them as is, you will get a memory size error.
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// +build atsamd21
2+
3+
package main
4+
5+
import (
6+
"machine"
7+
8+
"tinygo.org/x/drivers/ili9341"
9+
)
10+
11+
var (
12+
display = ili9341.NewSPI(
13+
machine.SPI0,
14+
machine.D0,
15+
machine.D1,
16+
machine.D2,
17+
)
18+
19+
backlight = machine.D3
20+
)
21+
22+
func init() {
23+
machine.SPI0.Configure(machine.SPIConfig{
24+
SCK: machine.SPI0_SCK_PIN,
25+
SDO: machine.SPI0_SDO_PIN,
26+
SDI: machine.SPI0_SDI_PIN,
27+
Frequency: 24000000,
28+
})
29+
}

examples/ili9341/slideshow/images.go

+5,083
Large diffs are not rendered by default.

examples/ili9341/slideshow/main.go

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"image/color"
6+
"machine"
7+
"strings"
8+
"time"
9+
10+
"tinygo.org/x/drivers/ili9341"
11+
"tinygo.org/x/drivers/image/jpeg"
12+
"tinygo.org/x/drivers/image/png"
13+
)
14+
15+
var (
16+
black = color.RGBA{0, 0, 0, 255}
17+
white = color.RGBA{255, 255, 255, 255}
18+
red = color.RGBA{255, 0, 0, 255}
19+
blue = color.RGBA{0, 0, 255, 255}
20+
green = color.RGBA{0, 255, 0, 255}
21+
)
22+
23+
func main() {
24+
err := run()
25+
for err != nil {
26+
errorMessage(err)
27+
}
28+
}
29+
30+
func run() error {
31+
backlight.Configure(machine.PinConfig{machine.PinOutput})
32+
33+
display.Configure(ili9341.Config{})
34+
35+
width, height := display.Size()
36+
if width < 320 || height < 240 {
37+
display.SetRotation(ili9341.Rotation270)
38+
}
39+
40+
display.FillScreen(black)
41+
backlight.High()
42+
43+
for {
44+
err := drawJpeg(display)
45+
if err != nil {
46+
return err
47+
}
48+
time.Sleep(time.Second)
49+
50+
err = drawPng(display)
51+
if err != nil {
52+
return err
53+
}
54+
time.Sleep(time.Second)
55+
}
56+
57+
return nil
58+
}
59+
60+
// Define the buffer required for the callback. In most cases, this setting
61+
// should be sufficient. For jpeg, the callback will always be called every
62+
// 3*8*8*4 pix. png will be called every line, i.e. every width pix.
63+
var buffer [3 * 8 * 8 * 4]uint16
64+
65+
func drawPng(display *ili9341.Device) error {
66+
p := strings.NewReader(pngImage)
67+
png.SetCallback(buffer[:], func(data []uint16, x, y, w, h, width, height int16) {
68+
err := display.DrawRGBBitmap(x, y, data[:w*h], w, h)
69+
if err != nil {
70+
errorMessage(fmt.Errorf("error drawPng: %s", err))
71+
}
72+
})
73+
74+
return png.Decode(p)
75+
}
76+
77+
func drawJpeg(display *ili9341.Device) error {
78+
p := strings.NewReader(jpegImage)
79+
jpeg.SetCallback(buffer[:], func(data []uint16, x, y, w, h, width, height int16) {
80+
err := display.DrawRGBBitmap(x, y, data[:w*h], w, h)
81+
if err != nil {
82+
errorMessage(fmt.Errorf("error drawJpeg: %s", err))
83+
}
84+
})
85+
86+
return jpeg.Decode(p)
87+
}
88+
89+
func errorMessage(err error) {
90+
for {
91+
fmt.Printf("%s\r\n", err.Error())
92+
time.Sleep(5 * time.Second)
93+
}
94+
}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// +build pyportal
2+
3+
package main
4+
5+
import (
6+
"machine"
7+
8+
"tinygo.org/x/drivers/ili9341"
9+
)
10+
11+
var (
12+
display = ili9341.NewParallel(
13+
machine.LCD_DATA0,
14+
machine.TFT_WR,
15+
machine.TFT_DC,
16+
machine.TFT_CS,
17+
machine.TFT_RESET,
18+
machine.TFT_RD,
19+
)
20+
21+
backlight = machine.TFT_BACKLIGHT
22+
)
30.4 KB
Loading
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// +build wioterminal
2+
3+
package main
4+
5+
import (
6+
"machine"
7+
8+
"tinygo.org/x/drivers/ili9341"
9+
)
10+
11+
var (
12+
display = ili9341.NewSPI(
13+
machine.SPI3,
14+
machine.LCD_DC,
15+
machine.LCD_SS_PIN,
16+
machine.LCD_RESET,
17+
)
18+
19+
backlight = machine.LCD_BACKLIGHT
20+
)
21+
22+
func init() {
23+
machine.SPI3.Configure(machine.SPIConfig{
24+
SCK: machine.LCD_SCK_PIN,
25+
SDO: machine.LCD_SDO_PIN,
26+
SDI: machine.LCD_SDI_PIN,
27+
Frequency: 40000000,
28+
})
29+
}

image/README.md

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# tinygo.org/x/drivers/image
2+
3+
This is an image package that uses less RAM to run on a microcontroller.
4+
Unlike Go's original image package, `image.Decode()` does not return `image.Image`.
5+
6+
Instead, a callback can be set to process the data corresponding to the image.
7+
8+
## How to use
9+
10+
First, use `SetCallback()` to set the callback.
11+
Then call `png.Decode()` or `jpeg.Decode()`.
12+
The callback will be called as many times as necessary to load the image.
13+
14+
`SetCallback()` needs to be given a Buffer to handle the callback and the actual function to be called.
15+
The `data []uint16` in the callback is in RGB565 format.
16+
17+
The `io.Reader` to pass to `Decode()` specifies the binary data of the image.
18+
19+
```go
20+
func drawPng(display *ili9341.Device) error {
21+
p := strings.NewReader(pngImage)
22+
png.SetCallback(buffer[:], func(data []uint16, x, y, w, h, width, height int16) {
23+
err := display.DrawRGBBitmap(x, y, data[:w*h], w, h)
24+
if err != nil {
25+
errorMessage(fmt.Errorf("error drawPng: %s", err))
26+
}
27+
})
28+
29+
return png.Decode(p)
30+
}
31+
```
32+
33+
```go
34+
func drawJpeg(display *ili9341.Device) error {
35+
p := strings.NewReader(jpegImage)
36+
jpeg.SetCallback(buffer[:], func(data []uint16, x, y, w, h, width, height int16) {
37+
err := display.DrawRGBBitmap(x, y, data[:w*h], w, h)
38+
if err != nil {
39+
errorMessage(fmt.Errorf("error drawJpeg: %s", err))
40+
}
41+
})
42+
43+
return jpeg.Decode(p)
44+
}
45+
```
46+
47+
## How to create an image
48+
49+
The following program will output an image binary like the one in [images.go](./examples/ili9341/slideshow/images.go).
50+
51+
```
52+
go run ./cmd/convert2bin ./path/to/png_or_jpg.png
53+
```
54+
55+
## Examples
56+
57+
An example can be found below.
58+
Processing jpegs requires a minimum of 32KB of RAM.
59+
60+
* [./examples/ili9341/slideshow](./examples/ili9341/slideshow)

0 commit comments

Comments
 (0)