зеркало из https://github.com/golang/image.git
463 строки
11 KiB
Go
463 строки
11 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package ccitt
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"image"
|
|
"image/png"
|
|
"io"
|
|
"io/ioutil"
|
|
"math/rand"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"unsafe"
|
|
)
|
|
|
|
func compareImages(t *testing.T, img0 image.Image, img1 image.Image) {
|
|
t.Helper()
|
|
|
|
b0 := img0.Bounds()
|
|
b1 := img1.Bounds()
|
|
if b0 != b1 {
|
|
t.Fatalf("bounds differ: %v vs %v", b0, b1)
|
|
}
|
|
|
|
for y := b0.Min.Y; y < b0.Max.Y; y++ {
|
|
for x := b0.Min.X; x < b0.Max.X; x++ {
|
|
c0 := img0.At(x, y)
|
|
c1 := img1.At(x, y)
|
|
if c0 != c1 {
|
|
t.Fatalf("pixel at (%d, %d) differs: %v vs %v", x, y, c0, c1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func decodePNG(fileName string) (image.Image, error) {
|
|
f, err := os.Open(fileName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
return png.Decode(f)
|
|
}
|
|
|
|
// simpleHB is a simple implementation of highBits.
|
|
func simpleHB(dst []byte, src []byte, invert bool) (d int, s int) {
|
|
for d < len(dst) {
|
|
numToPack := len(src) - s
|
|
if numToPack <= 0 {
|
|
break
|
|
} else if numToPack > 8 {
|
|
numToPack = 8
|
|
}
|
|
|
|
byteValue := byte(0)
|
|
if invert {
|
|
byteValue = 0xFF >> uint(numToPack)
|
|
}
|
|
for n := 0; n < numToPack; n++ {
|
|
byteValue |= (src[s] & 0x80) >> uint(n)
|
|
s++
|
|
}
|
|
dst[d] = byteValue
|
|
d++
|
|
}
|
|
return d, s
|
|
}
|
|
|
|
func TestHighBits(t *testing.T) {
|
|
rng := rand.New(rand.NewSource(1))
|
|
dst0 := make([]byte, 3)
|
|
dst1 := make([]byte, 3)
|
|
src := make([]byte, 20)
|
|
|
|
for r := 0; r < 1000; r++ {
|
|
numDst := rng.Intn(len(dst0) + 1)
|
|
randomByte := byte(rng.Intn(256))
|
|
for i := 0; i < numDst; i++ {
|
|
dst0[i] = randomByte
|
|
dst1[i] = randomByte
|
|
}
|
|
|
|
numSrc := rng.Intn(len(src) + 1)
|
|
for i := 0; i < numSrc; i++ {
|
|
src[i] = byte(rng.Intn(256))
|
|
}
|
|
|
|
invert := rng.Intn(2) == 0
|
|
|
|
d0, s0 := highBits(dst0[:numDst], src[:numSrc], invert)
|
|
d1, s1 := simpleHB(dst1[:numDst], src[:numSrc], invert)
|
|
|
|
if (d0 != d1) || (s0 != s1) || !bytes.Equal(dst0[:numDst], dst1[:numDst]) {
|
|
srcHighBits := make([]byte, numSrc)
|
|
for i := range srcHighBits {
|
|
srcHighBits[i] = src[i] >> 7
|
|
}
|
|
|
|
t.Fatalf("r=%d, numDst=%d, numSrc=%d, invert=%t:\nsrcHighBits=%d\n"+
|
|
"got d=%d, s=%d, bytes=[% 02X]\n"+
|
|
"want d=%d, s=%d, bytes=[% 02X]",
|
|
r, numDst, numSrc, invert, srcHighBits,
|
|
d0, s0, dst0[:numDst],
|
|
d1, s1, dst1[:numDst],
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkHighBits(b *testing.B) {
|
|
rng := rand.New(rand.NewSource(1))
|
|
dst := make([]byte, 1024)
|
|
src := make([]byte, 7777)
|
|
for i := range src {
|
|
src[i] = uint8(rng.Intn(256))
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for n := 0; n < b.N; n++ {
|
|
highBits(dst, src, false)
|
|
highBits(dst, src, true)
|
|
}
|
|
}
|
|
|
|
func TestMaxCodeLength(t *testing.T) {
|
|
br := bitReader{}
|
|
size := unsafe.Sizeof(br.bits)
|
|
size *= 8 // Convert from bytes to bits.
|
|
|
|
// Check that the size of the bitReader.bits field is large enough to hold
|
|
// nextBitMaxNBits bits.
|
|
if size < nextBitMaxNBits {
|
|
t.Fatalf("size: got %d, want >= %d", size, nextBitMaxNBits)
|
|
}
|
|
|
|
// Check that bitReader.nextBit will always leave enough spare bits in the
|
|
// bitReader.bits field such that the decode function can unread up to
|
|
// maxCodeLength bits.
|
|
if want := size - nextBitMaxNBits; maxCodeLength > want {
|
|
t.Fatalf("maxCodeLength: got %d, want <= %d", maxCodeLength, want)
|
|
}
|
|
|
|
// The decode function also assumes that, when saving bits to possibly
|
|
// unread later, those bits fit inside a uint32.
|
|
if maxCodeLength > 32 {
|
|
t.Fatalf("maxCodeLength: got %d, want <= %d", maxCodeLength, 32)
|
|
}
|
|
}
|
|
|
|
func testDecodeTable(t *testing.T, decodeTable [][2]int16, codes []code, values []uint32) {
|
|
// Build a map from values to codes.
|
|
m := map[uint32]string{}
|
|
for _, code := range codes {
|
|
m[code.val] = code.str
|
|
}
|
|
|
|
// Build the encoded form of those values in MSB order.
|
|
enc := []byte(nil)
|
|
bits := uint8(0)
|
|
nBits := uint32(0)
|
|
for _, v := range values {
|
|
code := m[v]
|
|
if code == "" {
|
|
panic("unmapped code")
|
|
}
|
|
for _, c := range code {
|
|
bits |= uint8(c&1) << (7 - nBits)
|
|
nBits++
|
|
if nBits == 8 {
|
|
enc = append(enc, bits)
|
|
bits = 0
|
|
nBits = 0
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
if nBits > 0 {
|
|
enc = append(enc, bits)
|
|
}
|
|
|
|
// Decode that encoded form.
|
|
got := []uint32(nil)
|
|
r := &bitReader{
|
|
r: bytes.NewReader(enc),
|
|
order: MSB,
|
|
}
|
|
finalValue := values[len(values)-1]
|
|
for {
|
|
v, err := decode(r, decodeTable)
|
|
if err != nil {
|
|
t.Fatalf("after got=%d: %v", got, err)
|
|
}
|
|
got = append(got, v)
|
|
if v == finalValue {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Check that the round-tripped values were unchanged.
|
|
if !reflect.DeepEqual(got, values) {
|
|
t.Fatalf("\ngot: %v\nwant: %v", got, values)
|
|
}
|
|
}
|
|
|
|
func TestModeDecodeTable(t *testing.T) {
|
|
testDecodeTable(t, modeDecodeTable[:], modeCodes, []uint32{
|
|
modePass,
|
|
modeV0,
|
|
modeV0,
|
|
modeVL1,
|
|
modeVR3,
|
|
modeVL2,
|
|
modeExt,
|
|
modeVL1,
|
|
modeH,
|
|
modeVL1,
|
|
modeVL1,
|
|
// The exact value of this final slice element doesn't really matter,
|
|
// except that testDecodeTable assumes that it (the finalValue) is
|
|
// different from every previous element.
|
|
modeVL3,
|
|
})
|
|
}
|
|
|
|
func TestWhiteDecodeTable(t *testing.T) {
|
|
testDecodeTable(t, whiteDecodeTable[:], whiteCodes, []uint32{
|
|
0, 1, 256, 7, 128, 3, 2560,
|
|
})
|
|
}
|
|
|
|
func TestBlackDecodeTable(t *testing.T) {
|
|
testDecodeTable(t, blackDecodeTable[:], blackCodes, []uint32{
|
|
63, 64, 63, 64, 64, 63, 22, 1088, 2048, 7, 6, 5, 4, 3, 2, 1, 0,
|
|
})
|
|
}
|
|
|
|
func TestDecodeInvalidCode(t *testing.T) {
|
|
// The bit stream is:
|
|
// 1 010 000000011011
|
|
// Packing that LSB-first gives:
|
|
// 0b_1101_1000_0000_0101
|
|
src := []byte{0x05, 0xD8}
|
|
|
|
decodeTable := modeDecodeTable[:]
|
|
r := &bitReader{
|
|
r: bytes.NewReader(src),
|
|
}
|
|
|
|
// "1" decodes to the value 2.
|
|
if v, err := decode(r, decodeTable); v != 2 || err != nil {
|
|
t.Fatalf("decode #0: got (%v, %v), want (2, nil)", v, err)
|
|
}
|
|
|
|
// "010" decodes to the value 6.
|
|
if v, err := decode(r, decodeTable); v != 6 || err != nil {
|
|
t.Fatalf("decode #0: got (%v, %v), want (6, nil)", v, err)
|
|
}
|
|
|
|
// "00000001" is an invalid code.
|
|
if v, err := decode(r, decodeTable); v != 0 || err != errInvalidCode {
|
|
t.Fatalf("decode #0: got (%v, %v), want (0, %v)", v, err, errInvalidCode)
|
|
}
|
|
|
|
// The bitReader should not have advanced after encountering an invalid
|
|
// code. The remaining bits should be "000000011011".
|
|
remaining := []byte(nil)
|
|
for {
|
|
bit, err := r.nextBit()
|
|
if err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
t.Fatalf("nextBit: %v", err)
|
|
}
|
|
remaining = append(remaining, uint8('0'+bit))
|
|
}
|
|
if got, want := string(remaining), "000000011011"; got != want {
|
|
t.Fatalf("remaining bits: got %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func testRead(t *testing.T, fileName string, sf SubFormat, align, invert, truncated bool) {
|
|
t.Helper()
|
|
|
|
const width, height = 153, 55
|
|
opts := &Options{
|
|
Align: align,
|
|
Invert: invert,
|
|
}
|
|
|
|
got := ""
|
|
{
|
|
f, err := os.Open(fileName)
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
defer f.Close()
|
|
gotBytes, err := ioutil.ReadAll(NewReader(f, MSB, sf, width, height, opts))
|
|
if err != nil {
|
|
t.Fatalf("ReadAll: %v", err)
|
|
}
|
|
got = string(gotBytes)
|
|
}
|
|
|
|
want := ""
|
|
{
|
|
img, err := decodePNG("testdata/bw-gopher.png")
|
|
if err != nil {
|
|
t.Fatalf("decodePNG: %v", err)
|
|
}
|
|
gray, ok := img.(*image.Gray)
|
|
if !ok {
|
|
t.Fatalf("decodePNG: got %T, want *image.Gray", img)
|
|
}
|
|
bounds := gray.Bounds()
|
|
if w := bounds.Dx(); w != width {
|
|
t.Fatalf("width: got %d, want %d", w, width)
|
|
}
|
|
if h := bounds.Dy(); h != height {
|
|
t.Fatalf("height: got %d, want %d", h, height)
|
|
}
|
|
|
|
// Prepare to extend each row's width to a multiple of 8, to simplify
|
|
// packing from 1 byte per pixel to 1 bit per pixel.
|
|
extended := make([]byte, (width+7)&^7)
|
|
|
|
wantBytes := []byte(nil)
|
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
|
rowPix := gray.Pix[(y-bounds.Min.Y)*gray.Stride:]
|
|
rowPix = rowPix[:width]
|
|
copy(extended, rowPix)
|
|
|
|
// Pack from 1 byte per pixel to 1 bit per pixel, MSB first.
|
|
byteValue := uint8(0)
|
|
for x, pixel := range extended {
|
|
byteValue |= (pixel & 0x80) >> uint(x&7)
|
|
if (x & 7) == 7 {
|
|
wantBytes = append(wantBytes, byteValue)
|
|
byteValue = 0
|
|
}
|
|
}
|
|
}
|
|
want = string(wantBytes)
|
|
}
|
|
|
|
// We expect a width of 153 pixels, which is 20 bytes per row (at 1 bit per
|
|
// pixel, plus 7 final bits of padding). Check that want is 20 * height
|
|
// bytes long, and if got != want, format them to split at every 20 bytes.
|
|
|
|
if n := len(want); n != 20*height {
|
|
t.Fatalf("len(want): got %d, want %d", n, 20*height)
|
|
}
|
|
|
|
format := func(s string) string {
|
|
b := []byte(nil)
|
|
for row := 0; len(s) >= 20; row++ {
|
|
b = append(b, fmt.Sprintf("row%02d: %02X\n", row, s[:20])...)
|
|
s = s[20:]
|
|
}
|
|
if len(s) > 0 {
|
|
b = append(b, fmt.Sprintf("%02X\n", s)...)
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
if got != want {
|
|
t.Fatalf("got:\n%s\nwant:\n%s", format(got), format(want))
|
|
}
|
|
|
|
// Passing AutoDetectHeight should produce the same output, provided that
|
|
// the input hasn't truncated the trailing sequence of consecutive EOL's
|
|
// that marks the end of the image.
|
|
if !truncated {
|
|
f, err := os.Open(fileName)
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
defer f.Close()
|
|
adhBytes, err := ioutil.ReadAll(NewReader(f, MSB, sf, width, AutoDetectHeight, opts))
|
|
if err != nil {
|
|
t.Fatalf("ReadAll: %v", err)
|
|
}
|
|
if s := string(adhBytes); s != want {
|
|
t.Fatalf("AutoDetectHeight produced different output.\n"+
|
|
"height=%d:\n%s\nheight=%d:\n%s",
|
|
AutoDetectHeight, format(s), height, format(want))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRead(t *testing.T) {
|
|
for _, fileName := range []string{
|
|
"testdata/bw-gopher.ccitt_group3",
|
|
"testdata/bw-gopher-aligned.ccitt_group3",
|
|
"testdata/bw-gopher-inverted.ccitt_group3",
|
|
"testdata/bw-gopher-inverted-aligned.ccitt_group3",
|
|
"testdata/bw-gopher.ccitt_group4",
|
|
"testdata/bw-gopher-aligned.ccitt_group4",
|
|
"testdata/bw-gopher-inverted.ccitt_group4",
|
|
"testdata/bw-gopher-inverted-aligned.ccitt_group4",
|
|
"testdata/bw-gopher-truncated0.ccitt_group3",
|
|
"testdata/bw-gopher-truncated0.ccitt_group4",
|
|
"testdata/bw-gopher-truncated1.ccitt_group3",
|
|
"testdata/bw-gopher-truncated1.ccitt_group4",
|
|
} {
|
|
subFormat := Group3
|
|
if strings.HasSuffix(fileName, "group4") {
|
|
subFormat = Group4
|
|
}
|
|
align := strings.Contains(fileName, "aligned")
|
|
invert := strings.Contains(fileName, "inverted")
|
|
truncated := strings.Contains(fileName, "truncated")
|
|
testRead(t, fileName, subFormat, align, invert, truncated)
|
|
}
|
|
}
|
|
|
|
func TestDecodeIntoGray(t *testing.T) {
|
|
for _, tt := range []struct {
|
|
fileName string
|
|
sf SubFormat
|
|
w, h int
|
|
}{
|
|
{"testdata/bw-gopher.ccitt_group3", Group3, 153, 55},
|
|
{"testdata/bw-gopher.ccitt_group4", Group4, 153, 55},
|
|
{"testdata/bw-gopher-truncated0.ccitt_group3", Group3, 153, 55},
|
|
{"testdata/bw-gopher-truncated0.ccitt_group4", Group4, 153, 55},
|
|
{"testdata/bw-gopher-truncated1.ccitt_group3", Group3, 153, 55},
|
|
{"testdata/bw-gopher-truncated1.ccitt_group4", Group4, 153, 55},
|
|
} {
|
|
t.Run(tt.fileName, func(t *testing.T) {
|
|
testDecodeIntoGray(t, tt.fileName, MSB, tt.sf, tt.w, tt.h, nil)
|
|
})
|
|
}
|
|
}
|
|
|
|
func testDecodeIntoGray(t *testing.T, fileName string, order Order, sf SubFormat, width int, height int, opts *Options) {
|
|
t.Helper()
|
|
|
|
f, err := os.Open(filepath.FromSlash(fileName))
|
|
if err != nil {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
got := image.NewGray(image.Rect(0, 0, width, height))
|
|
if err := DecodeIntoGray(got, f, order, sf, opts); err != nil {
|
|
t.Fatalf("DecodeIntoGray: %v", err)
|
|
}
|
|
|
|
want, err := decodePNG("testdata/bw-gopher.png")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
compareImages(t, got, want)
|
|
}
|