Skip to content
Permalink
Browse files

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>
  • Loading branch information...
hhrutter authored and nigeltao committed Jun 18, 2019
1 parent 92942e4 commit 7e034cad644213bc79b336b52fce73624259aeca
BIN +546 Bytes testdata/bw-gopher.png
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -42,11 +42,16 @@ const (
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 @@ const (
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
}
@@ -17,6 +17,7 @@ import (
"io/ioutil"
"math"

"golang.org/x/image/ccitt"
"golang.org/x/image/tiff/lzw"
)

@@ -129,7 +130,10 @@ func (d *decoder) parseIFD(p []byte) (int, error) {
tTileOffsets,
tTileByteCounts,
tImageLength,
tImageWidth:
tImageWidth,
tFillOrder,
tT4Options,
tT6Options:
val, err := d.ifdUint(p)
if err != nil {
return 0, err
@@ -441,7 +445,8 @@ func newDecoder(r io.Reader) (*decoder, error) {
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 @@ func DecodeConfig(r io.Reader) (image.Config, error) {
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 @@ func Decode(r io.Reader) (img image.Image, err error) {
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)
@@ -193,6 +193,32 @@ func TestDecodeLZW(t *testing.T) {
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) {
@@ -42,6 +42,7 @@ func TestRoundtrip(t *testing.T) {
if err != nil {
t.Fatal(err)
}

out := new(bytes.Buffer)
err = Encode(out, img, rt.opts)
if err != nil {

0 comments on commit 7e034ca

Please sign in to comment.
You can’t perform that action at this time.