1
2
3
4
5 package ccitt
6
7 import (
8 "bytes"
9 "fmt"
10 "image"
11 "image/png"
12 "io"
13 "io/ioutil"
14 "math/rand"
15 "os"
16 "path/filepath"
17 "reflect"
18 "strings"
19 "testing"
20 "unsafe"
21 )
22
23 func compareImages(t *testing.T, img0 image.Image, img1 image.Image) {
24 t.Helper()
25
26 b0 := img0.Bounds()
27 b1 := img1.Bounds()
28 if b0 != b1 {
29 t.Fatalf("bounds differ: %v vs %v", b0, b1)
30 }
31
32 for y := b0.Min.Y; y < b0.Max.Y; y++ {
33 for x := b0.Min.X; x < b0.Max.X; x++ {
34 c0 := img0.At(x, y)
35 c1 := img1.At(x, y)
36 if c0 != c1 {
37 t.Fatalf("pixel at (%d, %d) differs: %v vs %v", x, y, c0, c1)
38 }
39 }
40 }
41 }
42
43 func decodePNG(fileName string) (image.Image, error) {
44 f, err := os.Open(fileName)
45 if err != nil {
46 return nil, err
47 }
48 defer f.Close()
49 return png.Decode(f)
50 }
51
52
53 func simpleHB(dst []byte, src []byte, invert bool) (d int, s int) {
54 for d < len(dst) {
55 numToPack := len(src) - s
56 if numToPack <= 0 {
57 break
58 } else if numToPack > 8 {
59 numToPack = 8
60 }
61
62 byteValue := byte(0)
63 if invert {
64 byteValue = 0xFF >> uint(numToPack)
65 }
66 for n := 0; n < numToPack; n++ {
67 byteValue |= (src[s] & 0x80) >> uint(n)
68 s++
69 }
70 dst[d] = byteValue
71 d++
72 }
73 return d, s
74 }
75
76 func TestHighBits(t *testing.T) {
77 rng := rand.New(rand.NewSource(1))
78 dst0 := make([]byte, 3)
79 dst1 := make([]byte, 3)
80 src := make([]byte, 20)
81
82 for r := 0; r < 1000; r++ {
83 numDst := rng.Intn(len(dst0) + 1)
84 randomByte := byte(rng.Intn(256))
85 for i := 0; i < numDst; i++ {
86 dst0[i] = randomByte
87 dst1[i] = randomByte
88 }
89
90 numSrc := rng.Intn(len(src) + 1)
91 for i := 0; i < numSrc; i++ {
92 src[i] = byte(rng.Intn(256))
93 }
94
95 invert := rng.Intn(2) == 0
96
97 d0, s0 := highBits(dst0[:numDst], src[:numSrc], invert)
98 d1, s1 := simpleHB(dst1[:numDst], src[:numSrc], invert)
99
100 if (d0 != d1) || (s0 != s1) || !bytes.Equal(dst0[:numDst], dst1[:numDst]) {
101 srcHighBits := make([]byte, numSrc)
102 for i := range srcHighBits {
103 srcHighBits[i] = src[i] >> 7
104 }
105
106 t.Fatalf("r=%d, numDst=%d, numSrc=%d, invert=%t:\nsrcHighBits=%d\n"+
107 "got d=%d, s=%d, bytes=[% 02X]\n"+
108 "want d=%d, s=%d, bytes=[% 02X]",
109 r, numDst, numSrc, invert, srcHighBits,
110 d0, s0, dst0[:numDst],
111 d1, s1, dst1[:numDst],
112 )
113 }
114 }
115 }
116
117 func BenchmarkHighBits(b *testing.B) {
118 rng := rand.New(rand.NewSource(1))
119 dst := make([]byte, 1024)
120 src := make([]byte, 7777)
121 for i := range src {
122 src[i] = uint8(rng.Intn(256))
123 }
124
125 b.ResetTimer()
126 for n := 0; n < b.N; n++ {
127 highBits(dst, src, false)
128 highBits(dst, src, true)
129 }
130 }
131
132 func TestMaxCodeLength(t *testing.T) {
133 br := bitReader{}
134 size := unsafe.Sizeof(br.bits)
135 size *= 8
136
137
138
139 if size < nextBitMaxNBits {
140 t.Fatalf("size: got %d, want >= %d", size, nextBitMaxNBits)
141 }
142
143
144
145
146 if want := size - nextBitMaxNBits; maxCodeLength > want {
147 t.Fatalf("maxCodeLength: got %d, want <= %d", maxCodeLength, want)
148 }
149
150
151
152 if maxCodeLength > 32 {
153 t.Fatalf("maxCodeLength: got %d, want <= %d", maxCodeLength, 32)
154 }
155 }
156
157 func testDecodeTable(t *testing.T, decodeTable [][2]int16, codes []code, values []uint32) {
158
159 m := map[uint32]string{}
160 for _, code := range codes {
161 m[code.val] = code.str
162 }
163
164
165 enc := []byte(nil)
166 bits := uint8(0)
167 nBits := uint32(0)
168 for _, v := range values {
169 code := m[v]
170 if code == "" {
171 panic("unmapped code")
172 }
173 for _, c := range code {
174 bits |= uint8(c&1) << (7 - nBits)
175 nBits++
176 if nBits == 8 {
177 enc = append(enc, bits)
178 bits = 0
179 nBits = 0
180 continue
181 }
182 }
183 }
184 if nBits > 0 {
185 enc = append(enc, bits)
186 }
187
188
189 got := []uint32(nil)
190 r := &bitReader{
191 r: bytes.NewReader(enc),
192 order: MSB,
193 }
194 finalValue := values[len(values)-1]
195 for {
196 v, err := decode(r, decodeTable)
197 if err != nil {
198 t.Fatalf("after got=%d: %v", got, err)
199 }
200 got = append(got, v)
201 if v == finalValue {
202 break
203 }
204 }
205
206
207 if !reflect.DeepEqual(got, values) {
208 t.Fatalf("\ngot: %v\nwant: %v", got, values)
209 }
210 }
211
212 func TestModeDecodeTable(t *testing.T) {
213 testDecodeTable(t, modeDecodeTable[:], modeCodes, []uint32{
214 modePass,
215 modeV0,
216 modeV0,
217 modeVL1,
218 modeVR3,
219 modeVL2,
220 modeExt,
221 modeVL1,
222 modeH,
223 modeVL1,
224 modeVL1,
225
226
227
228 modeVL3,
229 })
230 }
231
232 func TestWhiteDecodeTable(t *testing.T) {
233 testDecodeTable(t, whiteDecodeTable[:], whiteCodes, []uint32{
234 0, 1, 256, 7, 128, 3, 2560,
235 })
236 }
237
238 func TestBlackDecodeTable(t *testing.T) {
239 testDecodeTable(t, blackDecodeTable[:], blackCodes, []uint32{
240 63, 64, 63, 64, 64, 63, 22, 1088, 2048, 7, 6, 5, 4, 3, 2, 1, 0,
241 })
242 }
243
244 func TestDecodeInvalidCode(t *testing.T) {
245
246
247
248
249 src := []byte{0x05, 0xD8}
250
251 decodeTable := modeDecodeTable[:]
252 r := &bitReader{
253 r: bytes.NewReader(src),
254 }
255
256
257 if v, err := decode(r, decodeTable); v != 2 || err != nil {
258 t.Fatalf("decode #0: got (%v, %v), want (2, nil)", v, err)
259 }
260
261
262 if v, err := decode(r, decodeTable); v != 6 || err != nil {
263 t.Fatalf("decode #0: got (%v, %v), want (6, nil)", v, err)
264 }
265
266
267 if v, err := decode(r, decodeTable); v != 0 || err != errInvalidCode {
268 t.Fatalf("decode #0: got (%v, %v), want (0, %v)", v, err, errInvalidCode)
269 }
270
271
272
273 remaining := []byte(nil)
274 for {
275 bit, err := r.nextBit()
276 if err == io.EOF {
277 break
278 } else if err != nil {
279 t.Fatalf("nextBit: %v", err)
280 }
281 remaining = append(remaining, uint8('0'+bit))
282 }
283 if got, want := string(remaining), "000000011011"; got != want {
284 t.Fatalf("remaining bits: got %q, want %q", got, want)
285 }
286 }
287
288 func testRead(t *testing.T, fileName string, sf SubFormat, align, invert, truncated bool) {
289 t.Helper()
290
291 const width, height = 153, 55
292 opts := &Options{
293 Align: align,
294 Invert: invert,
295 }
296
297 got := ""
298 {
299 f, err := os.Open(fileName)
300 if err != nil {
301 t.Fatalf("Open: %v", err)
302 }
303 defer f.Close()
304 gotBytes, err := ioutil.ReadAll(NewReader(f, MSB, sf, width, height, opts))
305 if err != nil {
306 t.Fatalf("ReadAll: %v", err)
307 }
308 got = string(gotBytes)
309 }
310
311 want := ""
312 {
313 img, err := decodePNG("testdata/bw-gopher.png")
314 if err != nil {
315 t.Fatalf("decodePNG: %v", err)
316 }
317 gray, ok := img.(*image.Gray)
318 if !ok {
319 t.Fatalf("decodePNG: got %T, want *image.Gray", img)
320 }
321 bounds := gray.Bounds()
322 if w := bounds.Dx(); w != width {
323 t.Fatalf("width: got %d, want %d", w, width)
324 }
325 if h := bounds.Dy(); h != height {
326 t.Fatalf("height: got %d, want %d", h, height)
327 }
328
329
330
331 extended := make([]byte, (width+7)&^7)
332
333 wantBytes := []byte(nil)
334 for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
335 rowPix := gray.Pix[(y-bounds.Min.Y)*gray.Stride:]
336 rowPix = rowPix[:width]
337 copy(extended, rowPix)
338
339
340 byteValue := uint8(0)
341 for x, pixel := range extended {
342 byteValue |= (pixel & 0x80) >> uint(x&7)
343 if (x & 7) == 7 {
344 wantBytes = append(wantBytes, byteValue)
345 byteValue = 0
346 }
347 }
348 }
349 want = string(wantBytes)
350 }
351
352
353
354
355
356 if n := len(want); n != 20*height {
357 t.Fatalf("len(want): got %d, want %d", n, 20*height)
358 }
359
360 format := func(s string) string {
361 b := []byte(nil)
362 for row := 0; len(s) >= 20; row++ {
363 b = append(b, fmt.Sprintf("row%02d: %02X\n", row, s[:20])...)
364 s = s[20:]
365 }
366 if len(s) > 0 {
367 b = append(b, fmt.Sprintf("%02X\n", s)...)
368 }
369 return string(b)
370 }
371
372 if got != want {
373 t.Fatalf("got:\n%s\nwant:\n%s", format(got), format(want))
374 }
375
376
377
378
379 if !truncated {
380 f, err := os.Open(fileName)
381 if err != nil {
382 t.Fatalf("Open: %v", err)
383 }
384 defer f.Close()
385 adhBytes, err := ioutil.ReadAll(NewReader(f, MSB, sf, width, AutoDetectHeight, opts))
386 if err != nil {
387 t.Fatalf("ReadAll: %v", err)
388 }
389 if s := string(adhBytes); s != want {
390 t.Fatalf("AutoDetectHeight produced different output.\n"+
391 "height=%d:\n%s\nheight=%d:\n%s",
392 AutoDetectHeight, format(s), height, format(want))
393 }
394 }
395 }
396
397 func TestRead(t *testing.T) {
398 for _, fileName := range []string{
399 "testdata/bw-gopher.ccitt_group3",
400 "testdata/bw-gopher-aligned.ccitt_group3",
401 "testdata/bw-gopher-inverted.ccitt_group3",
402 "testdata/bw-gopher-inverted-aligned.ccitt_group3",
403 "testdata/bw-gopher.ccitt_group4",
404 "testdata/bw-gopher-aligned.ccitt_group4",
405 "testdata/bw-gopher-inverted.ccitt_group4",
406 "testdata/bw-gopher-inverted-aligned.ccitt_group4",
407 "testdata/bw-gopher-truncated0.ccitt_group3",
408 "testdata/bw-gopher-truncated0.ccitt_group4",
409 "testdata/bw-gopher-truncated1.ccitt_group3",
410 "testdata/bw-gopher-truncated1.ccitt_group4",
411 } {
412 subFormat := Group3
413 if strings.HasSuffix(fileName, "group4") {
414 subFormat = Group4
415 }
416 align := strings.Contains(fileName, "aligned")
417 invert := strings.Contains(fileName, "inverted")
418 truncated := strings.Contains(fileName, "truncated")
419 testRead(t, fileName, subFormat, align, invert, truncated)
420 }
421 }
422
423 func TestDecodeIntoGray(t *testing.T) {
424 for _, tt := range []struct {
425 fileName string
426 sf SubFormat
427 w, h int
428 }{
429 {"testdata/bw-gopher.ccitt_group3", Group3, 153, 55},
430 {"testdata/bw-gopher.ccitt_group4", Group4, 153, 55},
431 {"testdata/bw-gopher-truncated0.ccitt_group3", Group3, 153, 55},
432 {"testdata/bw-gopher-truncated0.ccitt_group4", Group4, 153, 55},
433 {"testdata/bw-gopher-truncated1.ccitt_group3", Group3, 153, 55},
434 {"testdata/bw-gopher-truncated1.ccitt_group4", Group4, 153, 55},
435 } {
436 t.Run(tt.fileName, func(t *testing.T) {
437 testDecodeIntoGray(t, tt.fileName, MSB, tt.sf, tt.w, tt.h, nil)
438 })
439 }
440 }
441
442 func testDecodeIntoGray(t *testing.T, fileName string, order Order, sf SubFormat, width int, height int, opts *Options) {
443 t.Helper()
444
445 f, err := os.Open(filepath.FromSlash(fileName))
446 if err != nil {
447 t.Fatalf("Open: %v", err)
448 }
449 defer f.Close()
450
451 got := image.NewGray(image.Rect(0, 0, width, height))
452 if err := DecodeIntoGray(got, f, order, sf, opts); err != nil {
453 t.Fatalf("DecodeIntoGray: %v", err)
454 }
455
456 want, err := decodePNG("testdata/bw-gopher.png")
457 if err != nil {
458 t.Fatal(err)
459 }
460
461 compareImages(t, got, want)
462 }
463
View as plain text