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>
This commit is contained in:
Horst Rutter 2019-06-18 22:33:55 +02:00 коммит произвёл Nigel Tao
Родитель 92942e4437
Коммит 7e034cad64
7 изменённых файлов: 67 добавлений и 2 удалений

Двоичные данные
testdata/bw-gopher.png поставляемый Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 546 B

Двоичные данные
testdata/bw-gopher_ccittGroup3.tiff поставляемый Normal file

Двоичный файл не отображается.

Двоичные данные
testdata/bw-gopher_ccittGroup4.tiff поставляемый Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -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 {