Skip to content

Commit

Permalink
image/png: implement grayscale transparency.
Browse files Browse the repository at this point in the history
Change-Id: Ib9309ee499fc51be2662d778430ee30089822e57
Reviewed-on: https://go-review.googlesource.com/32143
Reviewed-by: Rob Pike <[email protected]>
  • Loading branch information
nigeltao committed Oct 28, 2016
1 parent bba1ac4 commit caba0bd
Show file tree
Hide file tree
Showing 7 changed files with 350 additions and 43 deletions.
136 changes: 112 additions & 24 deletions src/image/png/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,25 @@ func (d *decoder) parsePLTE(length uint32) error {
func (d *decoder) parsetRNS(length uint32) error {
switch d.cb {
case cbG1, cbG2, cbG4, cbG8, cbG16:
return UnsupportedError("grayscale transparency")
if length != 2 {
return FormatError("bad tRNS length")
}
n, err := io.ReadFull(d.r, d.tmp[:length])
if err != nil {
return err
}
d.crc.Write(d.tmp[:n])

copy(d.transparent[:], d.tmp[:length])
switch d.cb {
case cbG1:
d.transparent[1] *= 0xff
case cbG2:
d.transparent[1] *= 0x55
case cbG4:
d.transparent[1] *= 0x11
}
d.useTransparent = true

case cbTC8, cbTC16:
if length != 6 {
Expand Down Expand Up @@ -413,8 +431,13 @@ func (d *decoder) readImagePass(r io.Reader, pass int, allocateOnly bool) (image
switch d.cb {
case cbG1, cbG2, cbG4, cbG8:
bitsPerPixel = d.depth
gray = image.NewGray(image.Rect(0, 0, width, height))
img = gray
if d.useTransparent {
nrgba = image.NewNRGBA(image.Rect(0, 0, width, height))
img = nrgba
} else {
gray = image.NewGray(image.Rect(0, 0, width, height))
img = gray
}
case cbGA8:
bitsPerPixel = 16
nrgba = image.NewNRGBA(image.Rect(0, 0, width, height))
Expand All @@ -438,8 +461,13 @@ func (d *decoder) readImagePass(r io.Reader, pass int, allocateOnly bool) (image
img = nrgba
case cbG16:
bitsPerPixel = 16
gray16 = image.NewGray16(image.Rect(0, 0, width, height))
img = gray16
if d.useTransparent {
nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height))
img = nrgba64
} else {
gray16 = image.NewGray16(image.Rect(0, 0, width, height))
img = gray16
}
case cbGA16:
bitsPerPixel = 32
nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height))
Expand Down Expand Up @@ -512,27 +540,75 @@ func (d *decoder) readImagePass(r io.Reader, pass int, allocateOnly bool) (image
// Convert from bytes to colors.
switch d.cb {
case cbG1:
for x := 0; x < width; x += 8 {
b := cdat[x/8]
for x2 := 0; x2 < 8 && x+x2 < width; x2++ {
gray.SetGray(x+x2, y, color.Gray{(b >> 7) * 0xff})
b <<= 1
if d.useTransparent {
ty := d.transparent[1]
for x := 0; x < width; x += 8 {
b := cdat[x/8]
for x2 := 0; x2 < 8 && x+x2 < width; x2++ {
ycol := (b >> 7) * 0xff
acol := uint8(0xff)
if ycol == ty {
acol = 0x00
}
nrgba.SetNRGBA(x+x2, y, color.NRGBA{ycol, ycol, ycol, acol})
b <<= 1
}
}
} else {
for x := 0; x < width; x += 8 {
b := cdat[x/8]
for x2 := 0; x2 < 8 && x+x2 < width; x2++ {
gray.SetGray(x+x2, y, color.Gray{(b >> 7) * 0xff})
b <<= 1
}
}
}
case cbG2:
for x := 0; x < width; x += 4 {
b := cdat[x/4]
for x2 := 0; x2 < 4 && x+x2 < width; x2++ {
gray.SetGray(x+x2, y, color.Gray{(b >> 6) * 0x55})
b <<= 2
if d.useTransparent {
ty := d.transparent[1]
for x := 0; x < width; x += 4 {
b := cdat[x/4]
for x2 := 0; x2 < 4 && x+x2 < width; x2++ {
ycol := (b >> 6) * 0x55
acol := uint8(0xff)
if ycol == ty {
acol = 0x00
}
nrgba.SetNRGBA(x+x2, y, color.NRGBA{ycol, ycol, ycol, acol})
b <<= 2
}
}
} else {
for x := 0; x < width; x += 4 {
b := cdat[x/4]
for x2 := 0; x2 < 4 && x+x2 < width; x2++ {
gray.SetGray(x+x2, y, color.Gray{(b >> 6) * 0x55})
b <<= 2
}
}
}
case cbG4:
for x := 0; x < width; x += 2 {
b := cdat[x/2]
for x2 := 0; x2 < 2 && x+x2 < width; x2++ {
gray.SetGray(x+x2, y, color.Gray{(b >> 4) * 0x11})
b <<= 4
if d.useTransparent {
ty := d.transparent[1]
for x := 0; x < width; x += 2 {
b := cdat[x/2]
for x2 := 0; x2 < 2 && x+x2 < width; x2++ {
ycol := (b >> 4) * 0x11
acol := uint8(0xff)
if ycol == ty {
acol = 0x00
}
nrgba.SetNRGBA(x+x2, y, color.NRGBA{ycol, ycol, ycol, acol})
b <<= 4
}
}
} else {
for x := 0; x < width; x += 2 {
b := cdat[x/2]
for x2 := 0; x2 < 2 && x+x2 < width; x2++ {
gray.SetGray(x+x2, y, color.Gray{(b >> 4) * 0x11})
b <<= 4
}
}
}
case cbG8:
Expand All @@ -551,7 +627,7 @@ func (d *decoder) readImagePass(r io.Reader, pass int, allocateOnly bool) (image
r := cdat[j+0]
g := cdat[j+1]
b := cdat[j+2]
a := byte(0xff)
a := uint8(0xff)
if r == tr && g == tg && b == tb {
a = 0x00
}
Expand Down Expand Up @@ -625,9 +701,21 @@ func (d *decoder) readImagePass(r io.Reader, pass int, allocateOnly bool) (image
copy(nrgba.Pix[pixOffset:], cdat)
pixOffset += nrgba.Stride
case cbG16:
for x := 0; x < width; x++ {
ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1])
gray16.SetGray16(x, y, color.Gray16{ycol})
if d.useTransparent {
ty := uint16(d.transparent[0])<<8 | uint16(d.transparent[1])
for x := 0; x < width; x++ {
ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1])
acol := uint16(0xffff)
if ycol == ty {
acol = 0x0000
}
nrgba64.SetNRGBA64(x, y, color.NRGBA64{ycol, ycol, ycol, acol})
}
} else {
for x := 0; x < width; x++ {
ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1])
gray16.SetGray16(x, y, color.Gray16{ycol})
}
}
case cbGA16:
for x := 0; x < width; x++ {
Expand Down
70 changes: 55 additions & 15 deletions src/image/png/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ var filenames = []string{
"basn4a16",
"basn6a08",
"basn6a16",
//"ftbbn0g01", // TODO: grayscale transparency.
//"ftbbn0g02", // TODO: grayscale transparency.
//"ftbbn0g04", // TODO: grayscale transparency.
"ftbbn0g01",
"ftbbn0g02",
"ftbbn0g04",
"ftbbn2c16",
"ftbbn3p08",
"ftbgn2c16",
"ftbgn3p08",
"ftbrn2c08",
//"ftbwn0g16", // TODO: grayscale transparency.
"ftbwn0g16",
"ftbwn3p08",
"ftbyn3p08",
"ftp0n0g08",
Expand Down Expand Up @@ -96,6 +96,14 @@ var fakebKGDs = map[string]string{
"ftbyn3p08": "bKGD {index: 245}\n",
}

// fakegAMAs maps from filenames to fake gAMA chunks for our approximation to
// the sng command-line tool. Package png doesn't keep that metadata when
// png.Decode returns an image.Image.
var fakegAMAs = map[string]string{
"ftbbn0g01": "",
"ftbbn0g02": "gAMA {0.45455}\n",
}

// fakeIHDRUsings maps from filenames to fake IHDR "using" lines for our
// approximation to the sng command-line tool. The PNG model is that
// transparency (in the tRNS chunk) is separate to the color/grayscale/palette
Expand All @@ -106,9 +114,13 @@ var fakebKGDs = map[string]string{
// can't otherwise discriminate PNG's "IHDR says color (with no alpha) but tRNS
// says alpha" and "IHDR says color with alpha".
var fakeIHDRUsings = map[string]string{
"ftbbn0g01": " using grayscale;\n",
"ftbbn0g02": " using grayscale;\n",
"ftbbn0g04": " using grayscale;\n",
"ftbbn2c16": " using color;\n",
"ftbgn2c16": " using color;\n",
"ftbrn2c08": " using color;\n",
"ftbwn0g16": " using grayscale;\n",
}

// An approximation of the sng command-line tool.
Expand Down Expand Up @@ -163,7 +175,11 @@ func sng(w io.WriteCloser, filename string, png image.Image) {
// We fake a gAMA chunk. The test files have a gAMA chunk but the go PNG
// parser ignores it (the PNG spec section 11.3 says "Ancillary chunks may
// be ignored by a decoder").
io.WriteString(w, "gAMA {1.0000}\n")
if s, ok := fakegAMAs[filename]; ok {
io.WriteString(w, s)
} else {
io.WriteString(w, "gAMA {1.0000}\n")
}

// Write the PLTE and tRNS (if applicable).
useTransparent := false
Expand Down Expand Up @@ -209,14 +225,28 @@ func sng(w io.WriteCloser, filename string, png image.Image) {
if c.A == 0 {
useTransparent = true
io.WriteString(w, "tRNS {\n")
fmt.Fprintf(w, " red: %d; green: %d; blue: %d;\n", c.R, c.G, c.B)
switch filename {
case "ftbbn0g01", "ftbbn0g02", "ftbbn0g04":
// The standard image package doesn't have a "gray with
// alpha" type. Instead, we use an image.NRGBA.
fmt.Fprintf(w, " gray: %d;\n", c.R)
default:
fmt.Fprintf(w, " red: %d; green: %d; blue: %d;\n", c.R, c.G, c.B)
}
io.WriteString(w, "}\n")
}
case color.NRGBA64:
if c.A == 0 {
useTransparent = true
io.WriteString(w, "tRNS {\n")
fmt.Fprintf(w, " red: %d; green: %d; blue: %d;\n", c.R, c.G, c.B)
switch filename {
case "ftbwn0g16":
// The standard image package doesn't have a "gray16 with
// alpha" type. Instead, we use an image.NRGBA64.
fmt.Fprintf(w, " gray: %d;\n", c.R)
default:
fmt.Fprintf(w, " red: %d; green: %d; blue: %d;\n", c.R, c.G, c.B)
}
io.WriteString(w, "}\n")
}
}
Expand Down Expand Up @@ -249,19 +279,29 @@ func sng(w io.WriteCloser, filename string, png image.Image) {
case cm == color.NRGBAModel:
for x := bounds.Min.X; x < bounds.Max.X; x++ {
nrgba := png.At(x, y).(color.NRGBA)
if useTransparent {
fmt.Fprintf(w, "%02x%02x%02x ", nrgba.R, nrgba.G, nrgba.B)
} else {
fmt.Fprintf(w, "%02x%02x%02x%02x ", nrgba.R, nrgba.G, nrgba.B, nrgba.A)
switch filename {
case "ftbbn0g01", "ftbbn0g02", "ftbbn0g04":
fmt.Fprintf(w, "%02x", nrgba.R)
default:
if useTransparent {
fmt.Fprintf(w, "%02x%02x%02x ", nrgba.R, nrgba.G, nrgba.B)
} else {
fmt.Fprintf(w, "%02x%02x%02x%02x ", nrgba.R, nrgba.G, nrgba.B, nrgba.A)
}
}
}
case cm == color.NRGBA64Model:
for x := bounds.Min.X; x < bounds.Max.X; x++ {
nrgba64 := png.At(x, y).(color.NRGBA64)
if useTransparent {
fmt.Fprintf(w, "%04x%04x%04x ", nrgba64.R, nrgba64.G, nrgba64.B)
} else {
fmt.Fprintf(w, "%04x%04x%04x%04x ", nrgba64.R, nrgba64.G, nrgba64.B, nrgba64.A)
switch filename {
case "ftbwn0g16":
fmt.Fprintf(w, "%04x ", nrgba64.R)
default:
if useTransparent {
fmt.Fprintf(w, "%04x%04x%04x ", nrgba64.R, nrgba64.G, nrgba64.B)
} else {
fmt.Fprintf(w, "%04x%04x%04x%04x ", nrgba64.R, nrgba64.G, nrgba64.B, nrgba64.A)
}
}
}
case cpm != nil:
Expand Down
8 changes: 4 additions & 4 deletions src/image/png/testdata/pngsuite/README
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ basn3a08.png was generated from basn6a08.png using the pngnq tool, which
converted it to the 8-bit paletted image with alpha values in tRNS chunk.

The *.sng files in this directory were generated from the *.png files by the
sng command-line tool and some hand editing. The files basn0g0{1,2,4}.sng were
actually generated by first converting the PNG to a bitdepth of 8 and then
running sng on them. basn4a08.sng was generated from a 16-bit rgba version of
basn4a08.png rather than the original gray + alpha.
sng command-line tool and some hand editing. The files basn0g0{1,2,4}.sng and
ftbbn0g0{1,2,4}.sng were actually generated by first converting the PNG to a
bitdepth of 8 and then running sng on them. basn4a08.sng was generated from a
16-bit rgba version of basn4a08.png rather than the original gray + alpha.
44 changes: 44 additions & 0 deletions src/image/png/testdata/pngsuite/ftbbn0g01.sng
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#SNG: from ftbbn0g01.png
IHDR {
width: 32; height: 32; bitdepth: 8;
using grayscale;
}
bKGD {gray: 0;}
tRNS {
gray: 0;
}
IMAGE {
pixels hex
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
00ffffffffffffff000000000000000000000000000000000000000000000000
00ffffffffffffffff0000000000000000000000000000000000000000000000
00ffffffffffffffffff00000000000000000000000000000000000000000000
00ffffff0000ffffffff00000000000000000000000000000000000000000000
00ffffff000000ffffff00000000000000000000000000000000000000000000
00ffffff000000ffffff00ffffff000000ffffff000000000000000000000000
00ffffff00ffffffffff00ffffff000000ffffff000000000000000000000000
00ffffffffffffffffff00ffffffff0000ffffff000000000000000000000000
00ffffffffffffffff0000ffffffff0000ffffff000000000000000000000000
00ffffffffff0000000000ffffffffff00ffffff000000000000000000000000
00ffffff00000000000000ffffffffff00ffffff0000000000ffffffffff0000
00ffffff00000000000000ffffffffffffffffff000000ffffffffffffffff00
00ffffff00000000000000ffffffffffffffffff000000ffffffffffffffff00
00ffffff00000000000000ffffffffffffffffff0000ffffffffff00ffffff00
0000000000000000000000ffffff00ffffffffff0000ffffffff000000000000
0000000000000000000000ffffff00ffffffffff0000ffffff00000000000000
0000000000000000000000ffffff0000ffffffff0000ffffff0000ffffff0000
0000000000000000000000ffffff000000ffffff0000ffffff00ffffffffff00
0000000000000000000000ffffff000000ffffff0000ffffff0000ffffffff00
00000000000000000000000000000000000000000000ffffff00000000ffff00
00000000000000000000000000000000000000000000ffffffff0000ffffff00
00000000000000000000000000000000000000000000ffffffffffffffffff00
0000000000000000000000000000000000000000000000ffffffffffffffff00
000000000000000000000000000000000000000000000000ffffffffffff0000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
}
Loading

0 comments on commit caba0bd

Please sign in to comment.