github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/image/jpeg/writer_test.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package jpeg 6 7 import ( 8 "bytes" 9 "fmt" 10 "image" 11 "image/color" 12 "image/png" 13 "io" 14 "math/rand" 15 "os" 16 "strings" 17 "testing" 18 ) 19 20 // zigzag maps from the natural ordering to the zig-zag ordering. For example, 21 // zigzag[0*8 + 3] is the zig-zag sequence number of the element in the fourth 22 // column and first row. 23 var zigzag = [blockSize]int{ 24 0, 1, 5, 6, 14, 15, 27, 28, 25 2, 4, 7, 13, 16, 26, 29, 42, 26 3, 8, 12, 17, 25, 30, 41, 43, 27 9, 11, 18, 24, 31, 40, 44, 53, 28 10, 19, 23, 32, 39, 45, 52, 54, 29 20, 22, 33, 38, 46, 51, 55, 60, 30 21, 34, 37, 47, 50, 56, 59, 61, 31 35, 36, 48, 49, 57, 58, 62, 63, 32 } 33 34 func TestZigUnzig(t *testing.T) { 35 for i := 0; i < blockSize; i++ { 36 if unzig[zigzag[i]] != i { 37 t.Errorf("unzig[zigzag[%d]] == %d", i, unzig[zigzag[i]]) 38 } 39 if zigzag[unzig[i]] != i { 40 t.Errorf("zigzag[unzig[%d]] == %d", i, zigzag[unzig[i]]) 41 } 42 } 43 } 44 45 // unscaledQuantInNaturalOrder are the unscaled quantization tables in 46 // natural (not zig-zag) order, as specified in section K.1. 47 var unscaledQuantInNaturalOrder = [nQuantIndex][blockSize]byte{ 48 // Luminance. 49 { 50 16, 11, 10, 16, 24, 40, 51, 61, 51 12, 12, 14, 19, 26, 58, 60, 55, 52 14, 13, 16, 24, 40, 57, 69, 56, 53 14, 17, 22, 29, 51, 87, 80, 62, 54 18, 22, 37, 56, 68, 109, 103, 77, 55 24, 35, 55, 64, 81, 104, 113, 92, 56 49, 64, 78, 87, 103, 121, 120, 101, 57 72, 92, 95, 98, 112, 100, 103, 99, 58 }, 59 // Chrominance. 60 { 61 17, 18, 24, 47, 99, 99, 99, 99, 62 18, 21, 26, 66, 99, 99, 99, 99, 63 24, 26, 56, 99, 99, 99, 99, 99, 64 47, 66, 99, 99, 99, 99, 99, 99, 65 99, 99, 99, 99, 99, 99, 99, 99, 66 99, 99, 99, 99, 99, 99, 99, 99, 67 99, 99, 99, 99, 99, 99, 99, 99, 68 99, 99, 99, 99, 99, 99, 99, 99, 69 }, 70 } 71 72 func TestUnscaledQuant(t *testing.T) { 73 bad := false 74 for i := quantIndex(0); i < nQuantIndex; i++ { 75 for zig := 0; zig < blockSize; zig++ { 76 got := unscaledQuant[i][zig] 77 want := unscaledQuantInNaturalOrder[i][unzig[zig]] 78 if got != want { 79 t.Errorf("i=%d, zig=%d: got %d, want %d", i, zig, got, want) 80 bad = true 81 } 82 } 83 } 84 if bad { 85 names := [nQuantIndex]string{"Luminance", "Chrominance"} 86 buf := &strings.Builder{} 87 for i, name := range names { 88 fmt.Fprintf(buf, "// %s.\n{\n", name) 89 for zig := 0; zig < blockSize; zig++ { 90 fmt.Fprintf(buf, "%d, ", unscaledQuantInNaturalOrder[i][unzig[zig]]) 91 if zig%8 == 7 { 92 buf.WriteString("\n") 93 } 94 } 95 buf.WriteString("},\n") 96 } 97 t.Logf("expected unscaledQuant values:\n%s", buf.String()) 98 } 99 } 100 101 var testCase = []struct { 102 filename string 103 quality int 104 tolerance int64 105 }{ 106 {"../testdata/video-001.png", 1, 24 << 8}, 107 {"../testdata/video-001.png", 20, 12 << 8}, 108 {"../testdata/video-001.png", 60, 8 << 8}, 109 {"../testdata/video-001.png", 80, 6 << 8}, 110 {"../testdata/video-001.png", 90, 4 << 8}, 111 {"../testdata/video-001.png", 100, 2 << 8}, 112 } 113 114 func delta(u0, u1 uint32) int64 { 115 d := int64(u0) - int64(u1) 116 if d < 0 { 117 return -d 118 } 119 return d 120 } 121 122 func readPng(filename string) (image.Image, error) { 123 f, err := os.Open(filename) 124 if err != nil { 125 return nil, err 126 } 127 defer f.Close() 128 return png.Decode(f) 129 } 130 131 func TestWriter(t *testing.T) { 132 for _, tc := range testCase { 133 // Read the image. 134 m0, err := readPng(tc.filename) 135 if err != nil { 136 t.Error(tc.filename, err) 137 continue 138 } 139 // Encode that image as JPEG. 140 var buf bytes.Buffer 141 err = Encode(&buf, m0, &Options{Quality: tc.quality}) 142 if err != nil { 143 t.Error(tc.filename, err) 144 continue 145 } 146 // Decode that JPEG. 147 m1, err := Decode(&buf) 148 if err != nil { 149 t.Error(tc.filename, err) 150 continue 151 } 152 if m0.Bounds() != m1.Bounds() { 153 t.Errorf("%s, bounds differ: %v and %v", tc.filename, m0.Bounds(), m1.Bounds()) 154 continue 155 } 156 // Compare the average delta to the tolerance level. 157 if averageDelta(m0, m1) > tc.tolerance { 158 t.Errorf("%s, quality=%d: average delta is too high", tc.filename, tc.quality) 159 continue 160 } 161 } 162 } 163 164 // TestWriteGrayscale tests that a grayscale images survives a round-trip 165 // through encode/decode cycle. 166 func TestWriteGrayscale(t *testing.T) { 167 m0 := image.NewGray(image.Rect(0, 0, 32, 32)) 168 for i := range m0.Pix { 169 m0.Pix[i] = uint8(i) 170 } 171 var buf bytes.Buffer 172 if err := Encode(&buf, m0, nil); err != nil { 173 t.Fatal(err) 174 } 175 m1, err := Decode(&buf) 176 if err != nil { 177 t.Fatal(err) 178 } 179 if m0.Bounds() != m1.Bounds() { 180 t.Fatalf("bounds differ: %v and %v", m0.Bounds(), m1.Bounds()) 181 } 182 if _, ok := m1.(*image.Gray); !ok { 183 t.Errorf("got %T, want *image.Gray", m1) 184 } 185 // Compare the average delta to the tolerance level. 186 want := int64(2 << 8) 187 if got := averageDelta(m0, m1); got > want { 188 t.Errorf("average delta too high; got %d, want <= %d", got, want) 189 } 190 } 191 192 // averageDelta returns the average delta in RGB space. The two images must 193 // have the same bounds. 194 func averageDelta(m0, m1 image.Image) int64 { 195 b := m0.Bounds() 196 var sum, n int64 197 for y := b.Min.Y; y < b.Max.Y; y++ { 198 for x := b.Min.X; x < b.Max.X; x++ { 199 c0 := m0.At(x, y) 200 c1 := m1.At(x, y) 201 r0, g0, b0, _ := c0.RGBA() 202 r1, g1, b1, _ := c1.RGBA() 203 sum += delta(r0, r1) 204 sum += delta(g0, g1) 205 sum += delta(b0, b1) 206 n += 3 207 } 208 } 209 return sum / n 210 } 211 212 func TestEncodeYCbCr(t *testing.T) { 213 bo := image.Rect(0, 0, 640, 480) 214 imgRGBA := image.NewRGBA(bo) 215 // Must use 444 subsampling to avoid lossy RGBA to YCbCr conversion. 216 imgYCbCr := image.NewYCbCr(bo, image.YCbCrSubsampleRatio444) 217 rnd := rand.New(rand.NewSource(123)) 218 // Create identical rgba and ycbcr images. 219 for y := bo.Min.Y; y < bo.Max.Y; y++ { 220 for x := bo.Min.X; x < bo.Max.X; x++ { 221 col := color.RGBA{ 222 uint8(rnd.Intn(256)), 223 uint8(rnd.Intn(256)), 224 uint8(rnd.Intn(256)), 225 255, 226 } 227 imgRGBA.SetRGBA(x, y, col) 228 yo := imgYCbCr.YOffset(x, y) 229 co := imgYCbCr.COffset(x, y) 230 cy, ccr, ccb := color.RGBToYCbCr(col.R, col.G, col.B) 231 imgYCbCr.Y[yo] = cy 232 imgYCbCr.Cb[co] = ccr 233 imgYCbCr.Cr[co] = ccb 234 } 235 } 236 237 // Now check that both images are identical after an encode. 238 var bufRGBA, bufYCbCr bytes.Buffer 239 Encode(&bufRGBA, imgRGBA, nil) 240 Encode(&bufYCbCr, imgYCbCr, nil) 241 if !bytes.Equal(bufRGBA.Bytes(), bufYCbCr.Bytes()) { 242 t.Errorf("RGBA and YCbCr encoded bytes differ") 243 } 244 } 245 246 func BenchmarkEncodeRGBA(b *testing.B) { 247 img := image.NewRGBA(image.Rect(0, 0, 640, 480)) 248 bo := img.Bounds() 249 rnd := rand.New(rand.NewSource(123)) 250 for y := bo.Min.Y; y < bo.Max.Y; y++ { 251 for x := bo.Min.X; x < bo.Max.X; x++ { 252 img.SetRGBA(x, y, color.RGBA{ 253 uint8(rnd.Intn(256)), 254 uint8(rnd.Intn(256)), 255 uint8(rnd.Intn(256)), 256 255, 257 }) 258 } 259 } 260 b.SetBytes(640 * 480 * 4) 261 b.ReportAllocs() 262 b.ResetTimer() 263 options := &Options{Quality: 90} 264 for i := 0; i < b.N; i++ { 265 Encode(io.Discard, img, options) 266 } 267 } 268 269 func BenchmarkEncodeYCbCr(b *testing.B) { 270 img := image.NewYCbCr(image.Rect(0, 0, 640, 480), image.YCbCrSubsampleRatio420) 271 bo := img.Bounds() 272 rnd := rand.New(rand.NewSource(123)) 273 for y := bo.Min.Y; y < bo.Max.Y; y++ { 274 for x := bo.Min.X; x < bo.Max.X; x++ { 275 cy := img.YOffset(x, y) 276 ci := img.COffset(x, y) 277 img.Y[cy] = uint8(rnd.Intn(256)) 278 img.Cb[ci] = uint8(rnd.Intn(256)) 279 img.Cr[ci] = uint8(rnd.Intn(256)) 280 } 281 } 282 b.SetBytes(640 * 480 * 3) 283 b.ReportAllocs() 284 b.ResetTimer() 285 options := &Options{Quality: 90} 286 for i := 0; i < b.N; i++ { 287 Encode(io.Discard, img, options) 288 } 289 }