ccitt: implement NewReader and DecodeIntoGray

The testdata/bw-gopher.png image was derived from
testdata/bw-uncompressed.tiff

The testdata/*ccitt* data was generated from a custom CCITT encoder:
https://go-review.googlesource.com/c/image/+/174139/10/ccitt/writer.go

Updates golang/go#19443

Change-Id: I53b7acd807aa9a627517ede5bc3316d6f1fe4724
Reviewed-on: https://go-review.googlesource.com/c/image/+/182219
Reviewed-by: Benny Siegert <bsiegert@gmail.com>
diff --git a/ccitt/reader.go b/ccitt/reader.go
index ef353dc..04d0302 100644
--- a/ccitt/reader.go
+++ b/ccitt/reader.go
@@ -10,12 +10,22 @@
 import (
 	"encoding/binary"
 	"errors"
+	"image"
 	"io"
 	"math/bits"
 )
 
 var (
-	errInvalidCode = errors.New("ccitt: invalid code")
+	errInvalidBounds           = errors.New("ccitt: invalid bounds")
+	errInvalidCode             = errors.New("ccitt: invalid code")
+	errInvalidMode             = errors.New("ccitt: invalid mode")
+	errInvalidOffset           = errors.New("ccitt: invalid offset")
+	errMissingEOL              = errors.New("ccitt: missing End-of-Line")
+	errRunLengthOverflowsWidth = errors.New("ccitt: run length overflows width")
+	errRunLengthTooLong        = errors.New("ccitt: run length too long")
+	errUnsupportedMode         = errors.New("ccitt: unsupported mode")
+	errUnsupportedSubFormat    = errors.New("ccitt: unsupported sub-format")
+	errUnsupportedWidth        = errors.New("ccitt: unsupported width")
 )
 
 // Order specifies the bit ordering in a CCITT data stream.
@@ -28,6 +38,35 @@
 	MSB
 )
 
+// SubFormat represents that the CCITT format consists of a number of
+// sub-formats. Decoding or encoding a CCITT data stream requires knowing the
+// sub-format context. It is not represented in the data stream per se.
+type SubFormat uint32
+
+const (
+	Group3 SubFormat = iota
+	Group4
+)
+
+// Options are optional parameters.
+type Options struct {
+	// Align means that some variable-bit-width codes are byte-aligned.
+	Align bool
+	// Invert means that black is the 1 bit or 0xFF byte, and white is 0.
+	Invert bool
+}
+
+// maxWidth is the maximum (inclusive) supported width. This is a limitation of
+// this implementation, to guard against integer overflow, and not anything
+// inherent to the CCITT format.
+const maxWidth = 1 << 20
+
+func invertBytes(b []byte) {
+	for i, c := range b {
+		b[i] = ^c
+	}
+}
+
 type bitReader struct {
 	r io.Reader
 
@@ -36,6 +75,7 @@
 	// process the n > 0 bytes returned before considering the error err".
 	readErr error
 
+	// order is whether to process r's bytes LSB first or MSB first.
 	order Order
 
 	// The low nBits bits of the bits field hold upcoming bits in LSB order.
@@ -128,3 +168,493 @@
 		}
 	}
 }
+
+type reader struct {
+	br        bitReader
+	subFormat SubFormat
+
+	// width is the image width in pixels.
+	width int
+
+	// rowsRemaining starts at the image height in pixels, when the reader is
+	// driven through the io.Reader interface, and decrements to zero as rows
+	// are decoded. When driven through DecodeIntoGray, this field is unused.
+	rowsRemaining int
+
+	// curr and prev hold the current and previous rows. Each element is either
+	// 0x00 (black) or 0xFF (white).
+	//
+	// prev may be nil, when processing the first row.
+	curr []byte
+	prev []byte
+
+	// ri is the read index. curr[:ri] are those bytes of curr that have been
+	// passed along via the Read method.
+	//
+	// When the reader is driven through DecodeIntoGray, instead of through the
+	// io.Reader interface, this field is unused.
+	ri int
+
+	// wi is the write index. curr[:wi] are those bytes of curr that have
+	// already been decoded via the decodeRow method.
+	//
+	// What this implementation calls wi is roughly equivalent to what the spec
+	// calls the a0 index.
+	wi int
+
+	// These fields are copied from the *Options (which may be nil).
+	align  bool
+	invert bool
+
+	// atStartOfRow is whether we have just started the row. Some parts of the
+	// spec say to treat this situation as if "wi = -1".
+	atStartOfRow bool
+
+	// penColorIsWhite is whether the next run is black or white.
+	penColorIsWhite bool
+
+	// seenStartOfImage is whether we've called the startDecode method.
+	seenStartOfImage bool
+
+	// readErr is a sticky error for the Read method.
+	readErr error
+}
+
+func (z *reader) Read(p []byte) (int, error) {
+	if z.readErr != nil {
+		return 0, z.readErr
+	}
+	originalP := p
+
+	for len(p) > 0 {
+		// Allocate buffers (and decode any start-of-image codes), if
+		// processing the first or second row.
+		if z.curr == nil {
+			if !z.seenStartOfImage {
+				if z.readErr = z.startDecode(); z.readErr != nil {
+					break
+				}
+				z.atStartOfRow = true
+			}
+			z.curr = make([]byte, z.width)
+		}
+
+		// Decode the next row, if necessary.
+		if z.atStartOfRow {
+			if z.rowsRemaining <= 0 {
+				if z.readErr = z.finishDecode(); z.readErr != nil {
+					break
+				}
+				z.readErr = io.EOF
+				break
+			}
+			if z.readErr = z.decodeRow(); z.readErr != nil {
+				break
+			}
+			z.rowsRemaining--
+		}
+
+		// Pack from z.curr (1 byte per pixel) to p (1 bit per pixel), up to 8
+		// elements per iteration.
+		i := 0
+		for ; i < len(p); i++ {
+			numToPack := len(z.curr) - z.ri
+			if numToPack <= 0 {
+				break
+			} else if numToPack > 8 {
+				numToPack = 8
+			}
+
+			byteValue := byte(0)
+			for j := 0; j < numToPack; j++ {
+				byteValue |= (z.curr[z.ri] & 0x80) >> uint(j)
+				z.ri++
+			}
+			p[i] = byteValue
+		}
+		p = p[i:]
+
+		// Prepare to decode the next row, if necessary.
+		if z.ri == len(z.curr) {
+			z.ri, z.curr, z.prev = 0, z.prev, z.curr
+			z.atStartOfRow = true
+		}
+	}
+
+	n := len(originalP) - len(p)
+	// TODO: when invert is true, should the end-of-row padding bits be 0 or 1?
+	if z.invert {
+		invertBytes(originalP[:n])
+	}
+	return n, z.readErr
+}
+
+func (z *reader) penColor() byte {
+	if z.penColorIsWhite {
+		return 0xFF
+	}
+	return 0x00
+}
+
+func (z *reader) startDecode() error {
+	switch z.subFormat {
+	case Group3:
+		if err := z.decodeEOL(); err != nil {
+			return err
+		}
+
+	case Group4:
+		// No-op.
+
+	default:
+		return errUnsupportedSubFormat
+	}
+
+	z.seenStartOfImage = true
+	return nil
+}
+
+func (z *reader) finishDecode() error {
+	numberOfEOLs := 0
+	switch z.subFormat {
+	case Group3:
+		// The stream ends with a RTC (Return To Control) of 6 consecutive
+		// EOL's, but we should have already just seen an EOL, either in
+		// z.startDecode (for a zero-height image) or in z.decodeRow.
+		numberOfEOLs = 5
+
+	case Group4:
+		// The stream ends with two EOL's, the first of which is possibly
+		// byte-aligned.
+		numberOfEOLs = 2
+		if err := z.decodeEOL(); err == nil {
+			numberOfEOLs--
+		} else if err == errInvalidCode {
+			// Try again, this time starting from a byte boundary.
+			z.br.alignToByteBoundary()
+		} else {
+			return err
+		}
+
+	default:
+		return errUnsupportedSubFormat
+	}
+
+	for ; numberOfEOLs > 0; numberOfEOLs-- {
+		if err := z.decodeEOL(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (z *reader) decodeEOL() error {
+	// TODO: EOL doesn't have to be in the modeDecodeTable. It could be in its
+	// own table, or we could just hard-code it, especially if we might need to
+	// cater for optional byte-alignment, or an arbitrary number (potentially
+	// more than 8) of 0-valued padding bits.
+	if mode, err := decode(&z.br, modeDecodeTable[:]); err != nil {
+		return err
+	} else if mode != modeEOL {
+		return errMissingEOL
+	}
+	return nil
+}
+
+func (z *reader) decodeRow() error {
+	z.wi = 0
+	z.atStartOfRow = true
+	z.penColorIsWhite = true
+
+	switch z.subFormat {
+	case Group3:
+		for ; z.wi < len(z.curr); z.atStartOfRow = false {
+			if err := z.decodeRun(); err != nil {
+				return err
+			}
+		}
+		return z.decodeEOL()
+
+	case Group4:
+		if z.align {
+			z.br.alignToByteBoundary()
+		}
+
+		for ; z.wi < len(z.curr); z.atStartOfRow = false {
+			mode, err := decode(&z.br, modeDecodeTable[:])
+			if err != nil {
+				return err
+			}
+			rm := readerMode{}
+			if mode < uint32(len(readerModes)) {
+				rm = readerModes[mode]
+			}
+			if rm.function == nil {
+				return errInvalidMode
+			}
+			if err := rm.function(z, rm.arg); err != nil {
+				return err
+			}
+		}
+		return nil
+	}
+
+	return errUnsupportedSubFormat
+}
+
+func (z *reader) decodeRun() error {
+	table := blackDecodeTable[:]
+	if z.penColorIsWhite {
+		table = whiteDecodeTable[:]
+	}
+
+	total := 0
+	for {
+		n, err := decode(&z.br, table)
+		if err != nil {
+			return err
+		}
+		if n > maxWidth {
+			panic("unreachable")
+		}
+		total += int(n)
+		if total > maxWidth {
+			return errRunLengthTooLong
+		}
+		// Anything 0x3F or below is a terminal code.
+		if n <= 0x3F {
+			break
+		}
+	}
+
+	if total > (len(z.curr) - z.wi) {
+		return errRunLengthOverflowsWidth
+	}
+	dst := z.curr[z.wi : z.wi+total]
+	penColor := z.penColor()
+	for i := range dst {
+		dst[i] = penColor
+	}
+	z.wi += total
+	z.penColorIsWhite = !z.penColorIsWhite
+
+	return nil
+}
+
+// The various modes' semantics are based on determining a row of pixels'
+// "changing elements": those pixels whose color differs from the one on its
+// immediate left.
+//
+// The row above the first row is implicitly all white. Similarly, the column
+// to the left of the first column is implicitly all white.
+//
+// For example, here's Figure 1 in "ITU-T Recommendation T.6", where the
+// current and previous rows contain black (B) and white (w) pixels. The a?
+// indexes point into curr, the b? indexes point into prev.
+//
+//                 b1 b2
+//                 v  v
+// prev: BBBBBwwwwwBBBwwwww
+// curr: BBBwwwwwBBBBBBwwww
+//          ^    ^     ^
+//          a0   a1    a2
+//
+// a0 is the "reference element" or current decoder position, roughly
+// equivalent to what this implementation calls reader.wi.
+//
+// a1 is the next changing element to the right of a0, on the "coding line"
+// (the current row).
+//
+// a2 is the next changing element to the right of a1, again on curr.
+//
+// b1 is the first changing element on the "reference line" (the previous row)
+// to the right of a0 and of opposite color to a0.
+//
+// b2 is the next changing element to the right of b1, again on prev.
+//
+// The various modes calculate a1 (and a2, for modeH):
+//  - modePass calculates that a1 is at or to the right of b2.
+//  - modeH    calculates a1 and a2 without considering b1 or b2.
+//  - modeV*   calculates a1 to be b1 plus an adjustment (between -3 and +3).
+
+const (
+	findB1 = false
+	findB2 = true
+)
+
+// findB finds either the b1 or b2 value.
+func (z *reader) findB(whichB bool) int {
+	// The initial row is a special case. The previous row is implicitly all
+	// white, so that there are no changing pixel elements. We return b1 or b2
+	// to be at the end of the row.
+	if len(z.prev) != len(z.curr) {
+		return len(z.curr)
+	}
+
+	i := z.wi
+
+	if z.atStartOfRow {
+		// a0 is implicitly at -1, on a white pixel. b1 is the first black
+		// pixel in the previous row. b2 is the first white pixel after that.
+		for ; (i < len(z.prev)) && (z.prev[i] == 0xFF); i++ {
+		}
+		if whichB == findB2 {
+			for ; (i < len(z.prev)) && (z.prev[i] == 0x00); i++ {
+			}
+		}
+		return i
+	}
+
+	// As per figure 1 above, assume that the current pen color is white.
+	// First, walk past every contiguous black pixel in prev, starting at a0.
+	oppositeColor := ^z.penColor()
+	for ; (i < len(z.prev)) && (z.prev[i] == oppositeColor); i++ {
+	}
+
+	// Then walk past every contiguous white pixel.
+	penColor := ^oppositeColor
+	for ; (i < len(z.prev)) && (z.prev[i] == penColor); i++ {
+	}
+
+	// We're now at a black pixel (or at the end of the row). That's b1.
+	if whichB == findB2 {
+		// If we're looking for b2, walk past every contiguous black pixel
+		// again.
+		oppositeColor := ^penColor
+		for ; (i < len(z.prev)) && (z.prev[i] == oppositeColor); i++ {
+		}
+	}
+
+	return i
+}
+
+type readerMode struct {
+	function func(z *reader, arg int) error
+	arg      int
+}
+
+var readerModes = [...]readerMode{
+	modePass: {function: readerModePass},
+	modeH:    {function: readerModeH},
+	modeV0:   {function: readerModeV, arg: +0},
+	modeVR1:  {function: readerModeV, arg: +1},
+	modeVR2:  {function: readerModeV, arg: +2},
+	modeVR3:  {function: readerModeV, arg: +3},
+	modeVL1:  {function: readerModeV, arg: -1},
+	modeVL2:  {function: readerModeV, arg: -2},
+	modeVL3:  {function: readerModeV, arg: -3},
+	modeExt:  {function: readerModeExt},
+}
+
+func readerModePass(z *reader, arg int) error {
+	b2 := z.findB(findB2)
+	if (b2 < z.wi) || (len(z.curr) < b2) {
+		return errInvalidOffset
+	}
+	dst := z.curr[z.wi:b2]
+	penColor := z.penColor()
+	for i := range dst {
+		dst[i] = penColor
+	}
+	z.wi = b2
+	return nil
+}
+
+func readerModeH(z *reader, arg int) error {
+	// The first iteration finds a1. The second finds a2.
+	for i := 0; i < 2; i++ {
+		if err := z.decodeRun(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func readerModeV(z *reader, arg int) error {
+	a1 := z.findB(findB1) + arg
+	if (a1 < z.wi) || (len(z.curr) < a1) {
+		return errInvalidOffset
+	}
+	dst := z.curr[z.wi:a1]
+	penColor := z.penColor()
+	for i := range dst {
+		dst[i] = penColor
+	}
+	z.wi = a1
+	z.penColorIsWhite = !z.penColorIsWhite
+	return nil
+}
+
+func readerModeExt(z *reader, arg int) error {
+	return errUnsupportedMode
+}
+
+// DecodeIntoGray decodes the CCITT-formatted data in r into dst.
+//
+// It returns an error if dst's width and height don't match the implied width
+// and height of CCITT-formatted data.
+func DecodeIntoGray(dst *image.Gray, r io.Reader, order Order, sf SubFormat, opts *Options) error {
+	bounds := dst.Bounds()
+	if (bounds.Dx() < 0) || (bounds.Dy() < 0) {
+		return errInvalidBounds
+	}
+	if bounds.Dx() > maxWidth {
+		return errUnsupportedWidth
+	}
+
+	z := reader{
+		br:        bitReader{r: r, order: order},
+		subFormat: sf,
+		align:     (opts != nil) && opts.Align,
+		invert:    (opts != nil) && opts.Invert,
+		width:     bounds.Dx(),
+	}
+	if err := z.startDecode(); err != nil {
+		return err
+	}
+
+	width := bounds.Dx()
+	for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
+		p := (y - bounds.Min.Y) * dst.Stride
+		z.curr = dst.Pix[p : p+width]
+		if err := z.decodeRow(); err != nil {
+			return err
+		}
+		z.curr, z.prev = nil, z.curr
+	}
+
+	if err := z.finishDecode(); err != nil {
+		return err
+	}
+
+	if z.invert {
+		for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
+			p := (y - bounds.Min.Y) * dst.Stride
+			invertBytes(dst.Pix[p : p+width])
+		}
+	}
+
+	return nil
+}
+
+// NewReader returns an io.Reader that decodes the CCITT-formatted data in r.
+// The resultant byte stream is one bit per pixel (MSB first), with 1 meaning
+// white and 0 meaning black. Each row in the result is byte-aligned.
+func NewReader(r io.Reader, order Order, sf SubFormat, width int, height int, opts *Options) io.Reader {
+	readErr := error(nil)
+	if (width < 0) || (height < 0) {
+		readErr = errInvalidBounds
+	} else if width > maxWidth {
+		readErr = errUnsupportedWidth
+	}
+
+	return &reader{
+		br:            bitReader{r: r, order: order},
+		subFormat:     sf,
+		align:         (opts != nil) && opts.Align,
+		invert:        (opts != nil) && opts.Invert,
+		width:         width,
+		rowsRemaining: height,
+		readErr:       readErr,
+	}
+}
diff --git a/ccitt/reader_test.go b/ccitt/reader_test.go
index a1b9451..c582f16 100644
--- a/ccitt/reader_test.go
+++ b/ccitt/reader_test.go
@@ -6,12 +6,47 @@
 
 import (
 	"bytes"
+	"fmt"
+	"image"
+	"image/png"
 	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
 	"reflect"
 	"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)
+}
+
 func TestMaxCodeLength(t *testing.T) {
 	br := bitReader{}
 	size := unsafe.Sizeof(br.bits)
@@ -44,7 +79,7 @@
 		m[code.val] = code.str
 	}
 
-	// Build the encoded form of those values.
+	// Build the encoded form of those values in LSB order.
 	enc := []byte(nil)
 	bits := uint8(0)
 	nBits := uint32(0)
@@ -163,4 +198,134 @@
 	}
 }
 
-// TODO: more tests.
+func TestReadRegular(t *testing.T) { testRead(t, false) }
+func TestReadInvert(t *testing.T)  { testRead(t, true) }
+
+func testRead(t *testing.T, invert bool) {
+	t.Helper()
+
+	const width, height = 153, 55
+	opts := &Options{
+		Invert: invert,
+	}
+
+	got := ""
+	{
+		f, err := os.Open("testdata/bw-gopher.ccitt_group3")
+		if err != nil {
+			t.Fatalf("Open: %v", err)
+		}
+		defer f.Close()
+		gotBytes, err := ioutil.ReadAll(NewReader(f, MSB, Group3, 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
+				}
+			}
+		}
+		if invert {
+			invertBytes(wantBytes)
+		}
+		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))
+	}
+}
+
+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},
+	} {
+		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)
+	}
+
+	baseName := fileName[:len(fileName)-len(filepath.Ext(fileName))]
+	want, err := decodePNG(baseName + ".png")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	compareImages(t, got, want)
+}
diff --git a/ccitt/testdata/bw-gopher.ccitt_group3 b/ccitt/testdata/bw-gopher.ccitt_group3
new file mode 100644
index 0000000..a461904
--- /dev/null
+++ b/ccitt/testdata/bw-gopher.ccitt_group3
Binary files differ
diff --git a/ccitt/testdata/bw-gopher.ccitt_group4 b/ccitt/testdata/bw-gopher.ccitt_group4
new file mode 100644
index 0000000..05757a7
--- /dev/null
+++ b/ccitt/testdata/bw-gopher.ccitt_group4
Binary files differ
diff --git a/ccitt/testdata/bw-gopher.png b/ccitt/testdata/bw-gopher.png
new file mode 100644
index 0000000..6e8e957
--- /dev/null
+++ b/ccitt/testdata/bw-gopher.png
Binary files differ