Skip to content

Commit

Permalink
tiff: Add support for CCITT group 3/4 compression
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Nigel Tao <[email protected]>
Run-TryBot: Benny Siegert <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
  • Loading branch information
MiderWong5ddop committed Dec 2, 2023
1 parent de94201 commit 2b88c0e
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 2 deletions.
Binary file added testdata/bw-gopher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added testdata/bw-gopher_ccittGroup3.tiff
Binary file not shown.
Binary file added testdata/bw-gopher_ccittGroup4.tiff
Binary file not shown.
16 changes: 16 additions & 0 deletions tiff/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
26 changes: 24 additions & 2 deletions tiff/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"io/ioutil"
"math"

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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down
26 changes: 26 additions & 0 deletions tiff/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions tiff/writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 2b88c0e

Please sign in to comment.