mirror of
https://github.com/golang/go
synced 2024-11-22 03:34:40 -07:00
PNG encoder now filters.
R=r,rsc APPROVED=r DELTA=122 (102 added, 0 deleted, 20 changed) OCL=35573 CL=35587
This commit is contained in:
parent
c0e0f82e49
commit
64145109b3
@ -32,6 +32,7 @@ const (
|
||||
ftUp = 2;
|
||||
ftAverage = 3;
|
||||
ftPaeth = 4;
|
||||
nFilter = 5;
|
||||
)
|
||||
|
||||
// Decoding stage.
|
||||
|
@ -45,6 +45,14 @@ func opaque(m image.Image) bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The absolute value of a byte interpreted as a signed int8.
|
||||
func abs8(d uint8) int {
|
||||
if d < 128 {
|
||||
return int(d);
|
||||
}
|
||||
return 256-int(d);
|
||||
}
|
||||
|
||||
func (e *encoder) writeChunk(b []byte, name string) {
|
||||
if e.err != nil {
|
||||
return;
|
||||
@ -123,11 +131,97 @@ func (e *encoder) Write(b []byte) (int, os.Error) {
|
||||
}
|
||||
|
||||
// Chooses the filter to use for encoding the current row, and applies it.
|
||||
func filter(cr, pr []byte) {
|
||||
// TODO(nigeltao): For simplicity of implementation, this always picks the no-op filter.
|
||||
// To do this properly, we should use the same "minimize sum of absolute differences"
|
||||
// filter-choosing heuristic that libpng does.
|
||||
cr[0] = ftNone;
|
||||
// The return value is the index of the filter and also of the row in cr that has had it applied.
|
||||
func filter(cr [][]byte, pr []byte, bpp int) int {
|
||||
// We try all five filter types, and pick the one that minimizes the sum of absolute differences.
|
||||
// This is the same heuristic that libpng uses, although the filters are attempted in order of
|
||||
// estimated most likely to be minimal (ftUp, ftPaeth, ftNone, ftSub, ftAverage), rather than
|
||||
// in their enumeration order (ftNone, ftSub, ftUp, ftAverage, ftPaeth).
|
||||
cdat0 := cr[0][1 : len(cr[0])];
|
||||
cdat1 := cr[1][1 : len(cr[1])];
|
||||
cdat2 := cr[2][1 : len(cr[2])];
|
||||
cdat3 := cr[3][1 : len(cr[3])];
|
||||
cdat4 := cr[4][1 : len(cr[4])];
|
||||
pdat := pr[1 : len(pr)];
|
||||
n := len(cdat0);
|
||||
|
||||
// The up filter.
|
||||
sum := 0;
|
||||
for i := 0; i < n; i++ {
|
||||
cdat2[i] = cdat0[i] - pdat[i];
|
||||
sum += abs8(cdat2[i]);
|
||||
}
|
||||
best := sum;
|
||||
filter := ftUp;
|
||||
|
||||
// The Paeth filter.
|
||||
sum = 0;
|
||||
for i := 0; i < bpp; i++ {
|
||||
cdat4[i] = cdat0[i] - paeth(0, pdat[i], 0);
|
||||
sum += abs8(cdat4[i]);
|
||||
}
|
||||
for i := bpp; i < n; i++ {
|
||||
cdat4[i] = cdat0[i] - paeth(cdat0[i-bpp], pdat[i], pdat[i-bpp]);
|
||||
sum += abs8(cdat4[i]);
|
||||
if sum >= best {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if sum < best {
|
||||
best = sum;
|
||||
filter = ftPaeth;
|
||||
}
|
||||
|
||||
// The none filter.
|
||||
sum = 0;
|
||||
for i := 0; i < n; i++ {
|
||||
sum += abs8(cdat0[i]);
|
||||
if sum >= best {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if sum < best {
|
||||
best = sum;
|
||||
filter = ftNone;
|
||||
}
|
||||
|
||||
// The sub filter.
|
||||
sum = 0;
|
||||
for i := 0; i < bpp; i++ {
|
||||
cdat1[i] = cdat0[i];
|
||||
sum += abs8(cdat1[i]);
|
||||
}
|
||||
for i := bpp; i < n; i++ {
|
||||
cdat1[i] = cdat0[i] - cdat0[i-bpp];
|
||||
sum += abs8(cdat1[i]);
|
||||
if sum >= best {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if sum < best {
|
||||
best = sum;
|
||||
filter = ftSub;
|
||||
}
|
||||
|
||||
// The average filter.
|
||||
sum = 0;
|
||||
for i := 0; i < bpp; i++ {
|
||||
cdat3[i] = cdat0[i] - pdat[i] / 2;
|
||||
sum += abs8(cdat3[i]);
|
||||
}
|
||||
for i := bpp; i < n; i++ {
|
||||
cdat3[i] = cdat0[i] - uint8((int(cdat0[i-bpp]) + int(pdat[i]))/2);
|
||||
sum += abs8(cdat3[i]);
|
||||
if sum >= best {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if sum < best {
|
||||
best = sum;
|
||||
filter = ftAverage;
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
func writeImage(w io.Writer, m image.Image, ct uint8) os.Error {
|
||||
@ -148,10 +242,17 @@ func writeImage(w io.Writer, m image.Image, ct uint8) os.Error {
|
||||
case ctTrueColorAlpha:
|
||||
bpp = 4;
|
||||
}
|
||||
// cr and pr are the bytes for the current and previous row.
|
||||
// The +1 is for the per-row filter type, which is at cr[0].
|
||||
cr := make([]uint8, 1 + bpp * m.Width());
|
||||
pr := make([]uint8, 1 + bpp * m.Width());
|
||||
// cr[*] and pr are the bytes for the current and previous row.
|
||||
// cr[0] is unfiltered (or equivalently, filtered with the ftNone filter).
|
||||
// cr[ft], for non-zero filter types ft, are buffers for transforming cr[0] under the
|
||||
// other PNG filter types. These buffers are allocated once and re-used for each row.
|
||||
// The +1 is for the per-row filter type, which is at cr[*][0].
|
||||
var cr [nFilter][]uint8;
|
||||
for i := 0; i < len(cr); i++ {
|
||||
cr[i] = make([]uint8, 1 + bpp*m.Width());
|
||||
cr[i][0] = uint8(i);
|
||||
}
|
||||
pr := make([]uint8, 1 + bpp*m.Width());
|
||||
|
||||
for y := 0; y < m.Height(); y++ {
|
||||
// Convert from colors to bytes.
|
||||
@ -160,36 +261,36 @@ func writeImage(w io.Writer, m image.Image, ct uint8) os.Error {
|
||||
for x := 0; x < m.Width(); x++ {
|
||||
// We have previously verified that the alpha value is fully opaque.
|
||||
r, g, b, _ := m.At(x, y).RGBA();
|
||||
cr[3*x + 1] = uint8(r>>24);
|
||||
cr[3*x + 2] = uint8(g>>24);
|
||||
cr[3*x + 3] = uint8(b>>24);
|
||||
cr[0][3*x + 1] = uint8(r>>24);
|
||||
cr[0][3*x + 2] = uint8(g>>24);
|
||||
cr[0][3*x + 3] = uint8(b>>24);
|
||||
}
|
||||
case ctPaletted:
|
||||
for x := 0; x < m.Width(); x++ {
|
||||
cr[x+1] = paletted.ColorIndexAt(x, y);
|
||||
cr[0][x+1] = paletted.ColorIndexAt(x, y);
|
||||
}
|
||||
case ctTrueColorAlpha:
|
||||
// Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied.
|
||||
for x := 0; x < m.Width(); x++ {
|
||||
c := image.NRGBAColorModel.Convert(m.At(x, y)).(image.NRGBAColor);
|
||||
cr[4*x + 1] = c.R;
|
||||
cr[4*x + 2] = c.G;
|
||||
cr[4*x + 3] = c.B;
|
||||
cr[4*x + 4] = c.A;
|
||||
cr[0][4*x + 1] = c.R;
|
||||
cr[0][4*x + 2] = c.G;
|
||||
cr[0][4*x + 3] = c.B;
|
||||
cr[0][4*x + 4] = c.A;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the filter.
|
||||
filter(cr, pr);
|
||||
f := filter(cr[0:nFilter], pr, bpp);
|
||||
|
||||
// Write the compressed bytes.
|
||||
_, err = zw.Write(cr);
|
||||
_, err = zw.Write(cr[f]);
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
|
||||
// The current row for y is the previous row for y+1.
|
||||
pr, cr = cr, pr;
|
||||
pr, cr[0] = cr[0], pr;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user