Skip to content

Commit

Permalink
Merge pull request #7 from gnoack/picons2
Browse files Browse the repository at this point in the history
  • Loading branch information
Jacalz authored Jan 21, 2024
2 parents e97186d + 124a7f8 commit c3c798e
Show file tree
Hide file tree
Showing 14 changed files with 179 additions and 34 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ jobs:
strategy:
fail-fast: false
matrix:
go-version: [1.14, 1.18]
go-version: [1.19, 1.21]

steps:
- uses: actions/checkout@v2
with:
persist-credentials: false
- uses: WillAbides/setup-go-faster@v1.7.0
- uses: WillAbides/setup-go-faster@v1.9.1
with:
go-version: ${{ matrix.go-version }}

Expand Down
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
module github.com/fyne-io/image

go 1.14
go 1.19

require (
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25
github.com/stretchr/testify v1.7.1
)

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)
32 changes: 32 additions & 0 deletions xpm/fuzz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package xpm

import (
"bytes"
"os"
"path/filepath"
"testing"
)

// FuzzParsePanic runs a fuzzer to detect possible panics in the XPM
// parser. To run, use:
//
// go test -fuzz=FuzzParsePanic
func FuzzParsePanic(f *testing.F) {
matches, err := filepath.Glob("testdata/*.xpm")
if err != nil {
f.Fatalf("filepath.Glob: %v", err)
}
for _, m := range matches {
buf, err := os.ReadFile(m)
if err != nil {
f.Fatalf("os.ReadFile(%q): %v", m, err)
}
f.Add(buf)
}
f.Fuzz(func(t *testing.T, buf []byte) {
// It is expected that this may return an error during
// fuzzing, but we can at least make sure that it does
// not panic.
_, _ = Decode(bytes.NewReader(buf))
})
}
Binary file added xpm/testdata/colors.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions xpm/testdata/colors.xpm
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* XPM */
static char * hexcolors_xpm[] = {
/* width height ncolors chars_per_pixel */
"15 17 5 1",
/* colors */
" s None c None",
". c black m black",
"h m white c orange red",
"@ c peach puff m white",
"# c #FFF m white",
/* pixels */
" . ..... ",
" ..hhhhh. ",
" .hhhh.hh. ",
" ....@@h. ",
" .@@@@@@. ",
" .@.@@.@. ",
" .@@@@@@.. ",
" .@.@@@@@@. ",
" .@..@@@@. ",
" .@@@@.@. ",
" .....@@. ",
" .@@@@@. ",
" .@@@@@. ",
" .......... ",
" ..###.####.. ",
".#..........#. ",
".#############."};
2 changes: 2 additions & 0 deletions xpm/testdata/fuzz/FuzzParsePanic/48bee47275066ef2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5 t ncolo1\", w* colors *\xb5")
2 changes: 2 additions & 0 deletions xpm/testdata/fuzz/FuzzParsePanic/49dbcf54a1a83c8b
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\"1 1 1 1000000000\n\"\n\"")
2 changes: 2 additions & 0 deletions xpm/testdata/fuzz/FuzzParsePanic/5ac08626c909a2f2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\"1 1 1 1\"00\n\".\n\".00000")
2 changes: 2 additions & 0 deletions xpm/testdata/fuzz/FuzzParsePanic/a003fd4d2de89c7a
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\"16 1 2 1\"\n\"0c #000 \n\"0c #000 \n\"0000000000000000\n\"0000000000000000")
2 changes: 2 additions & 0 deletions xpm/testdata/fuzz/FuzzParsePanic/d89382570b33e29e
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\"1 9999999911 1 13\"\b")
2 changes: 2 additions & 0 deletions xpm/testdata/fuzz/FuzzParsePanic/f275f91493d6574a
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\"1 1 1 1\"\n\"0c c")
2 changes: 2 additions & 0 deletions xpm/testdata/fuzz/FuzzParsePanic/f3c56373a6c9cfd8
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\"1 0 0 000000\n\"")
63 changes: 52 additions & 11 deletions xpm/xpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package xpm

import (
"bufio"
"errors"
"fmt"
"image"
"image/color"
Expand All @@ -10,6 +11,15 @@ import (
"strings"
)

// maxPixels is the maximum number of pixels that the parser supports,
// to protect against prohibitively large memory allocations. XPM
// pixmaps tend to be small, it should not be possible to run into
// this limit with real-life data.
const maxPixels = 1024 * 1024 * 1024

// ErrInvalidFormat indicates that the input image was malformatted.
var ErrInvalidFormat = errors.New("invalid format")

func parseXPM(data io.Reader) (image.Image, error) {
// Specification: https://www.xfree86.org/current/xpm.pdf
var colCount, charSize int
Expand Down Expand Up @@ -43,18 +53,19 @@ func parseXPM(data io.Reader) (image.Image, error) {
colors[id] = c
}
} else {
parsePixels(row, charSize, rowNum-colCount-1, colors, img)
err := parsePixels(row, charSize, rowNum-colCount-1, colors, img)
if err != nil {
return nil, err
}
}
rowNum++
}
return img, scan.Err()
}

func parseColor(data string, charSize int) (id string, c color.Color, err error) {
// TODO: parseColor should return a non-nil err in all error cases,
// instead of just returning and implicitly leaving err as nil.
if len(data) < charSize {
return
return "", nil, fmt.Errorf("%w: missing color specification", ErrInvalidFormat)
}

id = data[:charSize]
Expand All @@ -67,6 +78,10 @@ func parseColor(data string, charSize int) (id string, c color.Color, err error)
color := strings.Join(parts[:nki], " ")
parts = parts[nki:]

if color == "" {
return "", nil, fmt.Errorf("%w: missing color specification", ErrInvalidFormat)
}

switch key {
case "c":
c, err := stringToColor(color)
Expand All @@ -79,7 +94,7 @@ func parseColor(data string, charSize int) (id string, c color.Color, err error)
return "", nil, fmt.Errorf("unknown visual %q", key)
}
}
return
return "", nil, fmt.Errorf("%w: missing color specification", ErrInvalidFormat)
}

// nextKeyIndex returns the index of the next "c", "m", s", "g4", or
Expand All @@ -94,7 +109,7 @@ func nextKeyIndex(parts []string) int {
return len(parts)
}

func parseDimensions(data string) (w, h, i, j int, err error) {
func parseDimensions(data string) (w, h, ncolors, cpp int, err error) {
if len(data) == 0 {
return
}
Expand All @@ -111,16 +126,41 @@ func parseDimensions(data string) (w, h, i, j int, err error) {
if err != nil {
return
}
i, err = strconv.Atoi(parts[2])
if w*h <= 0 {
err = fmt.Errorf("%w: empty or negative-sized image (%v x %v)", ErrInvalidFormat, w, h)
return
}
if w*h >= maxPixels {
err = fmt.Errorf("%w: too many pixels (%v x %v), want < %v", ErrInvalidFormat, w, h, maxPixels)
return
}
ncolors, err = strconv.Atoi(parts[2])
if err != nil {
return
}
if ncolors <= 0 {
err = fmt.Errorf("%w: ncolors <= 0: missing color palette", ErrInvalidFormat)
return
}
cpp, err = strconv.Atoi(parts[3])
if err != nil {
return
}
j, err = strconv.Atoi(parts[3])
if cpp <= 0 {
err = fmt.Errorf("%w: characters per pixel <= 0", ErrInvalidFormat)
return
}
return
}

func parsePixels(row string, charSize int, pixRow int, colors map[string]color.Color, img *image.NRGBA) {
func parsePixels(row string, charSize int, pixRow int, colors map[string]color.Color, img *image.NRGBA) error {
if len(row) < charSize*(img.Stride/4) {
return fmt.Errorf("%w: missing pixel data", ErrInvalidFormat)
}
off := pixRow * img.Stride
if len(img.Pix) < off+img.Stride {
return fmt.Errorf("%w: too much pixel data", ErrInvalidFormat)
}
chPos := 0
for i := 0; i < img.Stride/4; i++ {
id := row[chPos : chPos+charSize]
Expand All @@ -137,6 +177,7 @@ func parsePixels(row string, charSize int, pixRow int, colors map[string]color.C
img.Pix[pos+3] = uint8(a)
chPos += charSize
}
return nil
}

func stringToColor(data string) (color.Color, error) {
Expand All @@ -159,13 +200,13 @@ func stringToColor(data string) (color.Color, error) {
// See https://gitlab.freedesktop.org/xorg/lib/libxpm/-/issues/7
r, g, b = 0x10*r, 0x10*g, 0x10*b
default:
return nil, fmt.Errorf("invalid hex color %q", data)
return nil, fmt.Errorf("%w: invalid hex color %q", ErrInvalidFormat, data)
}
return color.NRGBA{r, g, b, 0xff}, err
default:
c, ok := x11colors[data]
if !ok {
return nil, fmt.Errorf("invalid X11 color %q", data)
return nil, fmt.Errorf("%w: invalid X11 color %q", ErrInvalidFormat, data)
}
return c, nil
}
Expand Down
64 changes: 44 additions & 20 deletions xpm/xpm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,60 @@ import (
"image/color"
_ "image/png"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
)

func TestParse(t *testing.T) {
r, err := os.Open("testdata/blarg.xpm")
assert.Nil(t, err)
defer r.Close()
img, err := parseXPM(r)
assert.Nil(t, err)
assert.Equal(t, 0, img.Bounds().Min.X)
assert.Equal(t, 0, img.Bounds().Min.Y)
assert.Equal(t, 16, img.Bounds().Max.X)
assert.Equal(t, 7, img.Bounds().Max.Y)

r, err = os.Open("testdata/blarg.png")
matches, err := filepath.Glob("testdata/*.png")
if err != nil {
t.Error(err)
t.Fatalf("filepath.Glob: %v", err)
}

golden, _, err := image.Decode(r)
if err != nil {
t.Error(err)
if len(matches) == 0 {
t.Fatalf("Missing examples and golden files")
}
for _, pngName := range matches {
var (
pngName = pngName
xpmName = pngName[:len(pngName)-4] + ".xpm"
testName = filepath.Base(pngName[:len(pngName)-4])
)
t.Run(testName, func(t *testing.T) {
r, err := os.Open(xpmName)
if err != nil {
t.Fatalf("os.Open(%q): %v", xpmName, err)
}
defer r.Close()

img, err := parseXPM(r)
assert.Nil(t, err)

r, err = os.Open(pngName)
if err != nil {
t.Fatalf("os.Open(%q): %v", pngName, err)
}
defer r.Close()

golden, _, err := image.Decode(r)
if err != nil {
t.Fatalf("image.Decode() for %q: %v", pngName, err)
}
assert.Equal(t, golden.Bounds(), img.Bounds())

pixCount := len(golden.(*image.RGBA).Pix)
assert.Equal(t, pixCount, len(img.(*image.NRGBA).Pix))
for i := 0; i < pixCount; i++ {
assert.Equal(t, golden.(*image.RGBA).Pix[i], img.(*image.NRGBA).Pix[i])
b := golden.Bounds()
for x := b.Min.X; x < b.Max.X; x++ {
for y := b.Min.Y; y < b.Max.Y; y++ {
iR, iG, iB, iA := img.At(x, y).RGBA()
gR, gG, gB, gA := golden.At(x, y).RGBA()
assert.Equal(t, iR, gR, "red at (%v, %v)", x, y)
assert.Equal(t, iG, gG, "green at (%v, %v)", x, y)
assert.Equal(t, iB, gB, "blue at (%v, %v)", x, y)
assert.Equal(t, iA, gA, "alpha at (%v, %v)", x, y)
}
}
})
}
}

Expand Down

0 comments on commit c3c798e

Please sign in to comment.