Skip to content

Commit c7b050b

Browse files
committed
Add read support for JPEG compression and multi page tiffs, bump go.mod and version
1 parent d35ae4c commit c7b050b

6 files changed

Lines changed: 190 additions & 22 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ This package is an improved version of [x/image/tiff](https://github.com/golang/
55
* Read support for CCITT Group3/4 compressed images using [x/image/ccitt](https://github.com/golang/image/tree/master/ccitt)
66
* Read/write support for LZW compressed images using [github.com/hhrutter/lzw](https://github.com/hhrutter/lzw)
77
* Read/write support for the CMYK color model.
8+
* Read support for JPEG compressed images.
9+
* Read support for multi page TIFF files.
810

911

1012
## Background
@@ -20,4 +22,4 @@ Working on [pdfcpu](https://github.com/pdfcpu/pdfcpu) (a PDF processor) created
2022

2123
## Goal
2224

23-
An improved version of [x/image/tiff](https://github.com/golang/image/tree/master/tiff) with full read/write support for CCITT, LZW compression and the CMYK color model.
25+
An improved version of [x/image/tiff](https://github.com/golang/image/tree/master/tiff) with full read/write support for CCITT, LZW, JPEG compression and the CMYK color model.

consts.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ const (
6565
tColorMap = 320
6666
tExtraSamples = 338
6767
tSampleFormat = 339
68+
69+
tJPEGTables = 347
6870
)
6971

7072
// Compression types (defined in various places in the spec and supplements).
@@ -130,6 +132,8 @@ const (
130132
LZW
131133
CCITTGroup3
132134
CCITTGroup4
135+
JPEGOld
136+
JPEG
133137
)
134138

135139
// specValue returns the compression type constant from the TIFF spec that
@@ -144,6 +148,10 @@ func (c CompressionType) specValue() uint32 {
144148
return cG3
145149
case CCITTGroup4:
146150
return cG4
151+
case JPEGOld:
152+
return cJPEGOld
153+
case JPEG:
154+
return cJPEG
147155
}
148156
return cNone
149157
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ go 1.19
44

55
require (
66
github.com/hhrutter/lzw v1.0.0
7-
golang.org/x/image v0.11.0
7+
golang.org/x/image v0.24.0
88
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
99
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
1010
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
1111
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
12+
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
13+
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
1214
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
1315
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
1416
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=

reader.go

Lines changed: 162 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
package tiff
1111

1212
import (
13+
"bytes"
1314
"compress/zlib"
1415
"encoding/binary"
1516
"fmt"
1617
"image"
1718
"image/color"
19+
"image/draw"
1820
"io"
19-
"io/ioutil"
2021
"math"
2122

2223
"github.com/hhrutter/lzw"
@@ -40,6 +41,17 @@ func (e UnsupportedError) Error() string {
4041

4142
var errNoPixels = FormatError("not enough pixel data")
4243

44+
var app14Marker = []byte{
45+
0xFF, 0xEE,
46+
0x00, 0x0E,
47+
'A', 'd', 'o', 'b', 'e',
48+
0x00,
49+
0x64, 0x00,
50+
0x00, 0x00,
51+
0x00, 0x00,
52+
0x00, // RGB
53+
}
54+
4355
type decoder struct {
4456
r io.ReaderAt
4557
byteOrder binary.ByteOrder
@@ -48,6 +60,7 @@ type decoder struct {
4860
bpp uint
4961
features map[int][]uint
5062
palette []color.Color
63+
tables []byte
5164

5265
buf []byte
5366
off int // Current offset in buf.
@@ -173,7 +186,19 @@ func (d *decoder) parseIFD(p []byte) (int, error) {
173186
return 0, UnsupportedError("sample format")
174187
}
175188
}
189+
case tJPEGTables:
190+
// See Adobe Photoshop® TIFF Technical Notes.
191+
datatype := d.byteOrder.Uint16(p[2:4])
192+
if dt := int(datatype); dt != 7 {
193+
return 0, UnsupportedError("IFD entry datatype")
194+
}
195+
size := d.byteOrder.Uint32(p[4:8])
196+
d.tables = make([]byte, size-4)
197+
if _, err := d.r.ReadAt(d.tables, int64(d.byteOrder.Uint32(p[8:12]))+2); err != nil {
198+
return 0, err
199+
}
176200
}
201+
177202
return int(tag), nil
178203
}
179204

@@ -411,7 +436,7 @@ func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
411436
return nil
412437
}
413438

414-
func newDecoder(r io.Reader) (*decoder, error) {
439+
func newDecoderAt(r io.Reader, ifdOffset int64) (*decoder, error) {
415440
d := &decoder{
416441
r: newReaderAt(r),
417442
features: make(map[int][]uint),
@@ -430,7 +455,9 @@ func newDecoder(r io.Reader) (*decoder, error) {
430455
return nil, FormatError("malformed header")
431456
}
432457

433-
ifdOffset := int64(d.byteOrder.Uint32(p[4:8]))
458+
if ifdOffset == 0 {
459+
ifdOffset = int64(d.byteOrder.Uint32(p[4:8]))
460+
}
434461

435462
// The first two bytes contain the number of entries (12 bytes each).
436463
if _, err := d.r.ReadAt(p[0:2], ifdOffset); err != nil {
@@ -559,7 +586,17 @@ func newDecoder(r io.Reader) (*decoder, error) {
559586
// DecodeConfig returns the color model and dimensions of a TIFF image without
560587
// decoding the entire image.
561588
func DecodeConfig(r io.Reader) (image.Config, error) {
562-
d, err := newDecoder(r)
589+
d, err := newDecoderAt(r, 0)
590+
if err != nil {
591+
return image.Config{}, err
592+
}
593+
return d.config, nil
594+
}
595+
596+
// DecodeConfig returns the color model and dimensions of a TIFF image at ifdOffset without
597+
// decoding the entire image.
598+
func DecodeConfigAt(r io.Reader, ifdOffset int64) (image.Config, error) {
599+
d, err := newDecoderAt(r, ifdOffset)
563600
if err != nil {
564601
return image.Config{}, err
565602
}
@@ -573,14 +610,83 @@ func ccittFillOrder(tiffFillOrder uint) ccitt.Order {
573610
return ccitt.MSB
574611
}
575612

576-
// Decode reads a TIFF image from r and returns it as an image.Image.
577-
// The type of Image returned depends on the contents of the TIFF.
578-
func Decode(r io.Reader) (img image.Image, err error) {
579-
d, err := newDecoder(r)
613+
func (d *decoder) copyStrip(dst image.Image, strip image.Image, x, y int) {
614+
switch d.mode {
615+
case mGray, mGrayInvert:
616+
if d.bpp == 16 {
617+
draw.Draw(dst.(*image.Gray16), image.Rect(x, y, x+strip.Bounds().Dx(), y+strip.Bounds().Dy()), strip, image.Point{}, draw.Over)
618+
} else {
619+
draw.Draw(dst.(*image.Gray), image.Rect(x, y, x+strip.Bounds().Dx(), y+strip.Bounds().Dy()), strip, image.Point{}, draw.Over)
620+
}
621+
case mPaletted:
622+
draw.Draw(dst.(*image.Paletted), image.Rect(x, y, x+strip.Bounds().Dx(), y+strip.Bounds().Dy()), strip, image.Point{}, draw.Over)
623+
case mNRGBA:
624+
if d.bpp == 16 {
625+
draw.Draw(dst.(*image.NRGBA64), image.Rect(x, y, x+strip.Bounds().Dx(), y+strip.Bounds().Dy()), strip, image.Point{}, draw.Over)
626+
} else {
627+
draw.Draw(dst.(*image.NRGBA), image.Rect(x, y, x+strip.Bounds().Dx(), y+strip.Bounds().Dy()), strip, image.Point{}, draw.Over)
628+
}
629+
case mRGB, mRGBA:
630+
if d.bpp == 16 {
631+
draw.Draw(dst.(*image.RGBA64), image.Rect(x, y, x+strip.Bounds().Dx(), y+strip.Bounds().Dy()), strip, image.Point{}, draw.Over)
632+
} else {
633+
draw.Draw(dst.(*image.RGBA), image.Rect(x, y, x+strip.Bounds().Dx(), y+strip.Bounds().Dy()), strip, image.Point{}, draw.Over)
634+
}
635+
case mCMYK:
636+
draw.Draw(dst.(*image.CMYK), image.Rect(x, y, x+strip.Bounds().Dx(), y+strip.Bounds().Dy()), strip, image.Point{}, draw.Over)
637+
}
638+
}
639+
640+
func (d *decoder) prepJPEG(offset, n int64, needAPP14, needTables bool) (io.Reader, error) {
641+
bb, err := io.ReadAll(io.NewSectionReader(d.r, offset, n))
580642
if err != nil {
581-
return
643+
return nil, err
644+
}
645+
if len(bb) < 2 || bb[0] != 0xFF || bb[1] != 0xD8 {
646+
return nil, FormatError("corrupt JPG stream")
582647
}
583648

649+
if needAPP14 {
650+
var buf bytes.Buffer
651+
buf.Write(bb[:2])
652+
buf.Write(app14Marker)
653+
buf.Write(bb[2:])
654+
bb = buf.Bytes()
655+
}
656+
657+
if needTables {
658+
sosIndex := bytes.Index(bb, []byte{0xFF, 0xDA})
659+
if sosIndex == -1 {
660+
return nil, FormatError("corrupt JPG stream")
661+
}
662+
var buf bytes.Buffer
663+
buf.Write(bb[:sosIndex])
664+
buf.Write(d.tables)
665+
buf.Write(bb[sosIndex:])
666+
bb = buf.Bytes()
667+
}
668+
669+
return bytes.NewReader(bb), nil
670+
}
671+
672+
func (d *decoder) decodeJPGOld(img image.Image, offset, n int64, i, j, blockWidth, blockHeight int) error {
673+
var rd io.Reader = io.NewSectionReader(d.r, offset, n)
674+
if len(d.features[tBitsPerSample]) == 4 && d.firstVal(tExtraSamples) > 0 {
675+
var err error
676+
rd, err = d.prepJPEG(offset, n, true, false)
677+
if err != nil {
678+
return err
679+
}
680+
}
681+
img0, _, err := image.Decode(rd)
682+
if err != nil {
683+
return err
684+
}
685+
d.copyStrip(img, img0, i*blockWidth, j*blockHeight)
686+
return nil
687+
}
688+
689+
func decode(d *decoder) (img image.Image, err error) {
584690
blockPadding := false
585691
blockWidth := d.config.Width
586692
blockHeight := d.config.Height
@@ -689,24 +795,47 @@ func Decode(r io.Reader) (img image.Image, err error) {
689795
inv := d.firstVal(tPhotometricInterpretation) == pWhiteIsZero
690796
order := ccittFillOrder(d.firstVal(tFillOrder))
691797
r := ccitt.NewReader(io.NewSectionReader(d.r, offset, n), order, ccitt.Group3, blkW, blkH, &ccitt.Options{Invert: inv, Align: false})
692-
d.buf, err = ioutil.ReadAll(r)
798+
d.buf, err = io.ReadAll(r)
693799
case cG4:
694800
inv := d.firstVal(tPhotometricInterpretation) == pWhiteIsZero
695801
order := ccittFillOrder(d.firstVal(tFillOrder))
696802
r := ccitt.NewReader(io.NewSectionReader(d.r, offset, n), order, ccitt.Group4, blkW, blkH, &ccitt.Options{Invert: inv, Align: false})
697-
d.buf, err = ioutil.ReadAll(r)
803+
d.buf, err = io.ReadAll(r)
698804
case cLZW:
699805
r := lzw.NewReader(io.NewSectionReader(d.r, offset, n), true)
700-
d.buf, err = ioutil.ReadAll(r)
806+
d.buf, err = io.ReadAll(r)
701807
r.Close()
702808
case cDeflate, cDeflateOld:
703809
var r io.ReadCloser
704810
r, err = zlib.NewReader(io.NewSectionReader(d.r, offset, n))
705811
if err != nil {
706812
return nil, err
707813
}
708-
d.buf, err = ioutil.ReadAll(r)
814+
d.buf, err = io.ReadAll(r)
709815
r.Close()
816+
case cJPEGOld:
817+
if err := d.decodeJPGOld(img, offset, n, i, j, blockWidth, blockHeight); err != nil {
818+
return nil, err
819+
}
820+
continue
821+
case cJPEG:
822+
if len(d.tables) == 0 {
823+
if err := d.decodeJPGOld(img, offset, n, i, j, blockWidth, blockHeight); err != nil {
824+
return nil, err
825+
}
826+
continue
827+
}
828+
needAPP14 := len(d.features[tBitsPerSample]) == 4 && d.firstVal(tExtraSamples) > 0
829+
rd, err := d.prepJPEG(offset, n, needAPP14, true)
830+
if err != nil {
831+
return nil, err
832+
}
833+
img0, _, err := image.Decode(rd)
834+
if err != nil {
835+
return nil, err
836+
}
837+
d.copyStrip(img, img0, i*blockWidth, j*blockHeight)
838+
continue
710839
case cPackBits:
711840
d.buf, err = unpackBits(io.NewSectionReader(d.r, offset, n))
712841
default:
@@ -729,6 +858,26 @@ func Decode(r io.Reader) (img image.Image, err error) {
729858
return
730859
}
731860

861+
// Decode reads a TIFF image from r and returns it as an image.Image.
862+
// The type of Image returned depends on the contents of the TIFF.
863+
func Decode(r io.Reader) (img image.Image, err error) {
864+
d, err := newDecoderAt(r, 0)
865+
if err != nil {
866+
return
867+
}
868+
return decode(d)
869+
}
870+
871+
// DecodeAt reads a TIFF image from r at ifdOffset and returns it as an image.Image.
872+
// The type of Image returned depends on the contents of the TIFF.
873+
func DecodeAt(r io.Reader, ifdOffset int64) (img image.Image, err error) {
874+
d, err := newDecoderAt(r, ifdOffset)
875+
if err != nil {
876+
return
877+
}
878+
return decode(d)
879+
}
880+
732881
func init() {
733882
image.RegisterFormat("tiff", leHeader, Decode, DecodeConfig)
734883
image.RegisterFormat("tiff", beHeader, Decode, DecodeConfig)

0 commit comments

Comments
 (0)