tiff: Add support for CCITT group 3/4 compression The algorithm is described at https://www.itu.int/rec/T-REC-T.6/en Fixes golang/go#19443 Change-Id: Ib8a078ab43c78d1f58d2ac849ed455b05dc209e9 Reviewed-on: https://go-review.googlesource.com/c/image/+/174139 Reviewed-by: Benny Siegert <bsiegert@gmail.com> Reviewed-by: Nigel Tao <nigeltao@golang.org> Run-TryBot: Benny Siegert <bsiegert@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/testdata/bw-gopher.png b/testdata/bw-gopher.png new file mode 100644 index 0000000..6e8e957 --- /dev/null +++ b/testdata/bw-gopher.png Binary files differ
diff --git a/testdata/bw-gopher_ccittGroup3.tiff b/testdata/bw-gopher_ccittGroup3.tiff new file mode 100644 index 0000000..1b969b4 --- /dev/null +++ b/testdata/bw-gopher_ccittGroup3.tiff Binary files differ
diff --git a/testdata/bw-gopher_ccittGroup4.tiff b/testdata/bw-gopher_ccittGroup4.tiff new file mode 100644 index 0000000..aeeef8a --- /dev/null +++ b/testdata/bw-gopher_ccittGroup4.tiff Binary files differ
diff --git a/tiff/consts.go b/tiff/consts.go index 3c51a70..3e5f7f1 100644 --- a/tiff/consts.go +++ b/tiff/consts.go
@@ -42,11 +42,16 @@ tCompression = 259 tPhotometricInterpretation = 262 + tFillOrder = 266 + tStripOffsets = 273 tSamplesPerPixel = 277 tRowsPerStrip = 278 tStripByteCounts = 279 + tT4Options = 292 // CCITT Group 3 options, a set of 32 flag bits. + tT6Options = 293 // CCITT Group 4 options, a set of 32 flag bits. + tTileWidth = 322 tTileLength = 323 tTileOffsets = 324 @@ -112,22 +117,33 @@ mRGB mRGBA mNRGBA + mCMYK ) // CompressionType describes the type of compression used in Options. type CompressionType int +// Constants for supported compression types. const ( Uncompressed CompressionType = iota Deflate + LZW + CCITTGroup3 + CCITTGroup4 ) // specValue returns the compression type constant from the TIFF spec that // is equivalent to c. func (c CompressionType) specValue() uint32 { switch c { + case LZW: + return cLZW case Deflate: return cDeflate + case CCITTGroup3: + return cG3 + case CCITTGroup4: + return cG4 } return cNone }
diff --git a/tiff/reader.go b/tiff/reader.go index ce2ef71..c26ec36 100644 --- a/tiff/reader.go +++ b/tiff/reader.go
@@ -17,6 +17,7 @@ "io/ioutil" "math" + "golang.org/x/image/ccitt" "golang.org/x/image/tiff/lzw" ) @@ -129,7 +130,10 @@ tTileOffsets, tTileByteCounts, tImageLength, - tImageWidth: + tImageWidth, + tFillOrder, + tT4Options, + tT6Options: val, err := d.ifdUint(p) if err != nil { return 0, err @@ -441,7 +445,8 @@ d.config.Height = int(d.firstVal(tImageLength)) if _, ok := d.features[tBitsPerSample]; !ok { - return nil, FormatError("BitsPerSample tag missing") + // Default is 1 per specification. + d.features[tBitsPerSample] = []uint{1} } d.bpp = d.firstVal(tBitsPerSample) switch d.bpp { @@ -539,6 +544,13 @@ return d.config, nil } +func ccittFillOrder(tiffFillOrder uint) ccitt.Order { + if tiffFillOrder == 2 { + return ccitt.LSB + } + return ccitt.MSB +} + // Decode reads a TIFF image from r and returns it as an image.Image. // The type of Image returned depends on the contents of the TIFF. func Decode(r io.Reader) (img image.Image, err error) { @@ -644,6 +656,16 @@ d.buf = make([]byte, n) _, err = d.r.ReadAt(d.buf, offset) } + case cG3: + inv := d.firstVal(tPhotometricInterpretation) == pWhiteIsZero + order := ccittFillOrder(d.firstVal(tFillOrder)) + r := ccitt.NewReader(io.NewSectionReader(d.r, offset, n), order, ccitt.Group3, blkW, blkH, &ccitt.Options{Invert: inv, Align: false}) + d.buf, err = ioutil.ReadAll(r) + case cG4: + inv := d.firstVal(tPhotometricInterpretation) == pWhiteIsZero + order := ccittFillOrder(d.firstVal(tFillOrder)) + r := ccitt.NewReader(io.NewSectionReader(d.r, offset, n), order, ccitt.Group4, blkW, blkH, &ccitt.Options{Invert: inv, Align: false}) + d.buf, err = ioutil.ReadAll(r) case cLZW: r := lzw.NewReader(io.NewSectionReader(d.r, offset, n), lzw.MSB, 8) d.buf, err = ioutil.ReadAll(r)
diff --git a/tiff/reader_test.go b/tiff/reader_test.go index 53d2b3c..82134c4 100644 --- a/tiff/reader_test.go +++ b/tiff/reader_test.go
@@ -193,6 +193,32 @@ compare(t, img0, img1) } +// TestDecodeCCITT tests that decoding a PNG image and a CCITT compressed TIFF +// image result in the same pixel data. +func TestDecodeCCITT(t *testing.T) { + // TODO Add more tests. + for _, fn := range []string{ + "bw-gopher", + } { + img0, err := load(fn + ".png") + if err != nil { + t.Fatal(err) + } + + img1, err := load(fn + "_ccittGroup3.tiff") + if err != nil { + t.Fatal(err) + } + compare(t, img0, img1) + + img2, err := load(fn + "_ccittGroup4.tiff") + if err != nil { + t.Fatal(err) + } + compare(t, img0, img2) + } +} + // TestDecodeTagOrder tests that a malformed image with unsorted IFD entries is // correctly rejected. func TestDecodeTagOrder(t *testing.T) {
diff --git a/tiff/writer_test.go b/tiff/writer_test.go index 05e27d2..0650df3 100644 --- a/tiff/writer_test.go +++ b/tiff/writer_test.go
@@ -42,6 +42,7 @@ if err != nil { t.Fatal(err) } + out := new(bytes.Buffer) err = Encode(out, img, rt.opts) if err != nil {