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 {