From 8be2bd6da956787bd009ce247d651d09d06dd5ab Mon Sep 17 00:00:00 2001 From: sunshineplan Date: Thu, 8 Jul 2021 13:28:48 +0800 Subject: [PATCH 1/5] Read/write support for JPEG-based compression (TIFF compression scheme 7) --- tiff/consts.go | 6 ++++ tiff/reader.go | 87 +++++++++++++++++++++++++++++++++++++++++++-- tiff/writer.go | 16 +++++++++ tiff/writer_test.go | 51 ++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 3 deletions(-) diff --git a/tiff/consts.go b/tiff/consts.go index 3e5f7f14..d569dbd8 100644 --- a/tiff/consts.go +++ b/tiff/consts.go @@ -65,6 +65,9 @@ const ( tColorMap = 320 tExtraSamples = 338 tSampleFormat = 339 + + // https://www.adobe.io/content/dam/udp/en/open/standards/tiff/TIFFphotoshop.pdf + tJPEG = 347 // page 7 ) // Compression types (defined in various places in the spec and supplements). @@ -130,6 +133,7 @@ const ( LZW CCITTGroup3 CCITTGroup4 + JPEG ) // specValue returns the compression type constant from the TIFF spec that @@ -144,6 +148,8 @@ func (c CompressionType) specValue() uint32 { return cG3 case CCITTGroup4: return cG4 + case JPEG: + return cJPEG } return cNone } diff --git a/tiff/reader.go b/tiff/reader.go index de73f4b9..be599b64 100644 --- a/tiff/reader.go +++ b/tiff/reader.go @@ -8,11 +8,14 @@ package tiff // import "golang.org/x/image/tiff" import ( + "bytes" "compress/zlib" "encoding/binary" "fmt" "image" "image/color" + "image/draw" + "image/jpeg" "io" "io/ioutil" "math" @@ -51,6 +54,8 @@ type decoder struct { off int // Current offset in buf. v uint32 // Buffer value for reading with arbitrary bit depths. nbits uint // Remaining number of bits in v. + + tmp image.Image // Store temporary image for jpeg compression } // firstVal returns the first uint of the features entry with the given tag, @@ -396,6 +401,39 @@ func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error { return nil } +// decodeJPEG decodes the jpeg data of an image. +// It reads from d.tmp and writes the strip or tile into dst. +func (d *decoder) decodeJPEG(dst image.Image, xmin, ymin, xmax, ymax int) { + rMaxX := minInt(xmax, dst.Bounds().Max.X) + rMaxY := minInt(ymax, dst.Bounds().Max.Y) + + var img draw.Image + switch d.mode { + case mGray, mGrayInvert: + if d.bpp == 16 { + img = dst.(*image.Gray16) + } else { + img = dst.(*image.Gray) + } + case mPaletted: + img = dst.(*image.Paletted) + case mRGB, mNRGBA, mRGBA: + if d.bpp == 16 { + img = dst.(*image.RGBA64) + } else { + img = dst.(*image.RGBA) + } + case mCMYK: + img = dst.(*image.CMYK) + } + + for y := 0; y+ymin < rMaxY; y++ { + for x := 0; x+xmin < rMaxX; x++ { + img.Set(x+xmin, y+ymin, d.tmp.At(x, y)) + } + } +} + func newDecoder(r io.Reader) (*decoder, error) { d := &decoder{ r: newReaderAt(r), @@ -673,6 +711,45 @@ func Decode(r io.Reader) (img image.Image, err error) { r := lzw.NewReader(io.NewSectionReader(d.r, offset, n), lzw.MSB, 8) d.buf, err = ioutil.ReadAll(r) r.Close() + case cJPEG: + var tjpeg bool + var buf bytes.Buffer + // According to the spec, JPEGTables is an optional field. The purpose of it is to + // predefine JPEG quantization and/or Huffman tables for subsequent use by JPEG image segments. + // When it is specified, these tables need not be duplicated in each segment. + // Start with SOI marker and end with EOI marker. + b := make([]byte, len(d.features[tJPEG])) + for i := range d.features[tJPEG] { + b[i] = uint8(d.features[tJPEG][i]) + } + if len(b) > 2 { + tjpeg = true + // Write to buffer without EOI marker. + buf.Write(b[:len(b)-2]) + } else if len(b) != 0 { + return nil, FormatError("bad JPEGTables field") + } + // JPEG image segment should start with SOI marker and end with EOI marker. + b, err = io.ReadAll(io.NewSectionReader(d.r, offset, n)) + if err != nil { + return nil, err + } + if len(b) < 4 { + return nil, FormatError("bad JPEG image segment") + } + if tjpeg { + // Write JPEG image segment to buffer without SOI marker. + // When this is done, buffer data will be a full JPEG format data. + buf.Write(b[2:]) + } else { + // Write full JPEG image segment to buffer. + buf.Write(b) + } + // Decode as a JPEG image. + d.tmp, err = jpeg.Decode(&buf) + if err != nil { + return nil, err + } case cDeflate, cDeflateOld: var r io.ReadCloser r, err = zlib.NewReader(io.NewSectionReader(d.r, offset, n)) @@ -694,9 +771,13 @@ func Decode(r io.Reader) (img image.Image, err error) { ymin := j * blockHeight xmax := xmin + blkW ymax := ymin + blkH - err = d.decode(img, xmin, ymin, xmax, ymax) - if err != nil { - return nil, err + if d.firstVal(tCompression) == cJPEG { + d.decodeJPEG(img, xmin, ymin, xmax, ymax) + } else { + err = d.decode(img, xmin, ymin, xmax, ymax) + if err != nil { + return nil, err + } } } } diff --git a/tiff/writer.go b/tiff/writer.go index c8a01cea..a11d1cd4 100644 --- a/tiff/writer.go +++ b/tiff/writer.go @@ -9,6 +9,7 @@ import ( "compress/zlib" "encoding/binary" "image" + "image/jpeg" "io" "sort" ) @@ -285,6 +286,15 @@ type Options struct { Predictor bool } +type discard struct{} + +func (discard) Write(p []byte) (int, error) { + return len(p), nil +} +func (discard) Close() error { + return nil +} + // Encode writes the image m to w. opt determines the options used for // encoding, such as the compression type. If opt is nil, an uncompressed // image is written. @@ -336,6 +346,12 @@ func Encode(w io.Writer, m image.Image, opt *Options) error { if err != nil { return err } + case cJPEG: + dst = discard{} + err = jpeg.Encode(&buf, m, nil) + if err != nil { + return err + } case cDeflate: dst = zlib.NewWriter(&buf) } diff --git a/tiff/writer_test.go b/tiff/writer_test.go index 0650df3d..67a349d7 100644 --- a/tiff/writer_test.go +++ b/tiff/writer_test.go @@ -75,6 +75,57 @@ func TestRoundtrip2(t *testing.T) { compare(t, m0, m1) } +func delta(u0, u1 uint32) int64 { + d := int64(u0) - int64(u1) + if d < 0 { + return -d + } + return d +} + +// averageDelta returns the average delta in RGB space. The two images must +// have the same bounds. +func averageDelta(m0, m1 image.Image) int64 { + b := m0.Bounds() + var sum, n int64 + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + c0 := m0.At(x, y) + c1 := m1.At(x, y) + r0, g0, b0, _ := c0.RGBA() + r1, g1, b1, _ := c1.RGBA() + sum += delta(r0, r1) + sum += delta(g0, g1) + sum += delta(b0, b1) + n += 3 + } + } + return sum / n +} + +// TestRoundtrip3 tests that encoding and decoding an image use JPEG compression. +func TestRoundtrip3(t *testing.T) { + img, err := openImage("video-001.tiff") + if err != nil { + t.Fatal(err) + } + + out := new(bytes.Buffer) + err = Encode(out, img, &Options{Compression: JPEG}) + if err != nil { + t.Fatal(err) + } + + img2, err := Decode(&buffer{buf: out.Bytes()}) + if err != nil { + t.Fatal(err) + } + want := int64(6 << 8) + if got := averageDelta(img, img2); got > want { + t.Errorf("average delta too high; got %d, want <= %d", got, want) + } +} + func benchmarkEncode(b *testing.B, name string, pixelSize int) { b.Helper() img, err := openImage(name) From fdb89ce5deebdb8d7fd20065b7ec69a977402715 Mon Sep 17 00:00:00 2001 From: sunshineplan Date: Thu, 8 Jul 2021 14:12:57 +0800 Subject: [PATCH 2/5] Read JPEG Tables field correctly --- tiff/consts.go | 2 ++ tiff/reader.go | 20 +++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/tiff/consts.go b/tiff/consts.go index d569dbd8..329c5656 100644 --- a/tiff/consts.go +++ b/tiff/consts.go @@ -29,6 +29,8 @@ const ( dtShort = 3 dtLong = 4 dtRational = 5 + + dtUndefined = 7 // JPEGTables field is required to have type code UNDEFINED ) // The length of one instance of each data type in bytes. diff --git a/tiff/reader.go b/tiff/reader.go index be599b64..ce22a7cb 100644 --- a/tiff/reader.go +++ b/tiff/reader.go @@ -76,16 +76,25 @@ func (d *decoder) ifdUint(p []byte) (u []uint, err error) { return nil, FormatError("bad IFD entry") } + tag := d.byteOrder.Uint16(p[0:2]) datatype := d.byteOrder.Uint16(p[2:4]) - if dt := int(datatype); dt <= 0 || dt >= len(lengths) { + if dt := int(datatype); dt <= 0 || dt >= len(lengths) && tag != tJPEG { return nil, UnsupportedError("IFD entry datatype") } + // tJPEG's type is dtUndefined which size is same as dtByte. + var length uint32 + if tag != tJPEG { + length = lengths[datatype] + } else { + length = 1 + } + count := d.byteOrder.Uint32(p[4:8]) - if count > math.MaxInt32/lengths[datatype] { + if count > math.MaxInt32/length { return nil, FormatError("IFD data too large") } - if datalen := lengths[datatype] * count; datalen > 4 { + if datalen := length * count; datalen > 4 { // The IFD contains a pointer to the real value. raw = make([]byte, datalen) _, err = d.r.ReadAt(raw, int64(d.byteOrder.Uint32(p[8:12]))) @@ -98,7 +107,7 @@ func (d *decoder) ifdUint(p []byte) (u []uint, err error) { u = make([]uint, count) switch datatype { - case dtByte: + case dtByte, dtUndefined: for i := uint32(0); i < count; i++ { u[i] = uint(raw[i]) } @@ -138,7 +147,8 @@ func (d *decoder) parseIFD(p []byte) (int, error) { tImageWidth, tFillOrder, tT4Options, - tT6Options: + tT6Options, + tJPEG: val, err := d.ifdUint(p) if err != nil { return 0, err From 54a6525e77897142c75097b505bc9741e7f4d530 Mon Sep 17 00:00:00 2001 From: sunshineplan Date: Fri, 9 Jul 2021 14:20:38 +0800 Subject: [PATCH 3/5] Remove write support for JPEG-based compression --- testdata/video-001-jpeg.tiff | Bin 0 -> 11020 bytes tiff/consts.go | 3 --- tiff/reader_test.go | 45 +++++++++++++++++++++++++++++++ tiff/writer.go | 16 ----------- tiff/writer_test.go | 51 ----------------------------------- 5 files changed, 45 insertions(+), 70 deletions(-) create mode 100644 testdata/video-001-jpeg.tiff diff --git a/testdata/video-001-jpeg.tiff b/testdata/video-001-jpeg.tiff new file mode 100644 index 0000000000000000000000000000000000000000..f8f176aab0c38be04745e273db3115d15ab94099 GIT binary patch literal 11020 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 want { + t.Errorf("average delta too high; got %d, want <= %d", got, want) + } +} + // TestDecodeTagOrder tests that a malformed image with unsorted IFD entries is // correctly rejected. func TestDecodeTagOrder(t *testing.T) { diff --git a/tiff/writer.go b/tiff/writer.go index a11d1cd4..c8a01cea 100644 --- a/tiff/writer.go +++ b/tiff/writer.go @@ -9,7 +9,6 @@ import ( "compress/zlib" "encoding/binary" "image" - "image/jpeg" "io" "sort" ) @@ -286,15 +285,6 @@ type Options struct { Predictor bool } -type discard struct{} - -func (discard) Write(p []byte) (int, error) { - return len(p), nil -} -func (discard) Close() error { - return nil -} - // Encode writes the image m to w. opt determines the options used for // encoding, such as the compression type. If opt is nil, an uncompressed // image is written. @@ -346,12 +336,6 @@ func Encode(w io.Writer, m image.Image, opt *Options) error { if err != nil { return err } - case cJPEG: - dst = discard{} - err = jpeg.Encode(&buf, m, nil) - if err != nil { - return err - } case cDeflate: dst = zlib.NewWriter(&buf) } diff --git a/tiff/writer_test.go b/tiff/writer_test.go index 67a349d7..0650df3d 100644 --- a/tiff/writer_test.go +++ b/tiff/writer_test.go @@ -75,57 +75,6 @@ func TestRoundtrip2(t *testing.T) { compare(t, m0, m1) } -func delta(u0, u1 uint32) int64 { - d := int64(u0) - int64(u1) - if d < 0 { - return -d - } - return d -} - -// averageDelta returns the average delta in RGB space. The two images must -// have the same bounds. -func averageDelta(m0, m1 image.Image) int64 { - b := m0.Bounds() - var sum, n int64 - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - c0 := m0.At(x, y) - c1 := m1.At(x, y) - r0, g0, b0, _ := c0.RGBA() - r1, g1, b1, _ := c1.RGBA() - sum += delta(r0, r1) - sum += delta(g0, g1) - sum += delta(b0, b1) - n += 3 - } - } - return sum / n -} - -// TestRoundtrip3 tests that encoding and decoding an image use JPEG compression. -func TestRoundtrip3(t *testing.T) { - img, err := openImage("video-001.tiff") - if err != nil { - t.Fatal(err) - } - - out := new(bytes.Buffer) - err = Encode(out, img, &Options{Compression: JPEG}) - if err != nil { - t.Fatal(err) - } - - img2, err := Decode(&buffer{buf: out.Bytes()}) - if err != nil { - t.Fatal(err) - } - want := int64(6 << 8) - if got := averageDelta(img, img2); got > want { - t.Errorf("average delta too high; got %d, want <= %d", got, want) - } -} - func benchmarkEncode(b *testing.B, name string, pixelSize int) { b.Helper() img, err := openImage(name) From 6dc555202991ea6ba7a6ee3287375e00a76956c6 Mon Sep 17 00:00:00 2001 From: sunshineplan Date: Mon, 12 Jul 2021 13:36:04 +0800 Subject: [PATCH 4/5] Add write support for JPEG compression, and read support for YCbCr color model in this compression mode. --- testdata/bw-jpeg.tiff | Bin 0 -> 2178 bytes testdata/video-001-jpeg.tiff | Bin 11020 -> 5180 bytes tiff/consts.go | 4 ++ tiff/reader.go | 90 ++++++++++++++++++++++------------- tiff/reader_test.go | 45 ------------------ tiff/writer.go | 28 +++++++++++ tiff/writer_test.go | 57 ++++++++++++++++++++++ 7 files changed, 145 insertions(+), 79 deletions(-) create mode 100644 testdata/bw-jpeg.tiff diff --git a/testdata/bw-jpeg.tiff b/testdata/bw-jpeg.tiff new file mode 100644 index 0000000000000000000000000000000000000000..fe6f0aa2042d071f7363fe53644e7a4b41fa5ccf GIT binary patch literal 2178 zcmb7Gc{tSD8~=`3j5S1AhOvw#A%+af#f>qOn3O@5WNV^LGZ-O}vQ^fQ$x=$8EM*-_ zja{}1jSyupqGajPgv@X1y7#&N{yxw9KIiki=X2ikoaZd>!Q(Z7_xu3Bea9UIt^#lv zj2{Mt^Yily2*3q}B}IgVgoI@zwr`V^mqjYb%OVhpC>?c0Wi1s1LIbl)OIOdpzyPUk zY-)twucL2(1_=lV2nz{Ii-<_0l@Lnk{~KHeAOr_6Ko$rj4sct6egFVLLB9cP0mKJ} zKwj8< zMCp5xuLub4*eNX|tE#51u}jlnuOY_B*yMnv71kPuKX}xE=;-8p%*ETs_ay0*pFbrq zD3}^@F*N$>wV2qr_=ME7Tj?2@x3g&Z_a78Id{kIe{H&s~s=B6@Uf0~x+ScCD`Le5j zVDK$-=-u$h1Z#3?nmzMrc5Z3;+g~fI-#I_lxFY}@1Ofmv;QN|Je!}0@v1Ati*9-V3 zivL=}Cb1!0vuw}q+_>m^1hO;L9FnNr;P8CUV$Ro^N1vZw_pzuTRHobuBY&}1SS5OD zDkr17Wo~Fb$?m$l{S~LC*~F{`39%`2?RfHm6E)_Y=Mb|N;^IUd@71$rsd-i=KGMX% z_O;zq{ze?wu*i3JpOd|{N*im%m^j`GqAIs2AC#Y}sk`2zr@g!fYPfQrhQP1WE1u;ZM{W zNAb=f(bZ}v!HfdqAal`$%7~>~BBL;tB{6nV`?Px<=bB&9DcOv4)=fhXeL}^Xc~yz1 z8P|!nCaclu%%q5+2uq(EP_MdN{!1QhnLBC7_y)W}k*8{E7t_U*kqTGwa_X$56UW__ zB2~SbG2_n2JIuZAhVmXZwdFYvEVFb`agj?HVjxPs^a*&^7jx6%7qNSHRh{29JT|7~ z7jn3Ria2pCaY6u}?3260DyR=<)QO_F-1=w1lEkoexl05)dU-=qPgPy~L>5MzE-lug zl9Eg~$llofC~4d2PA;JAP+SPwi^+u`5-uU&z+NEo(8 zsx2FSC8K*}YTq+f(%GBpgFytraPz9ome~1cA&Kh21H%+H;mmzicedsAaLZk1OOjOW znCeoqe{Y%hhmeYvP~WKx>ZW3UiO&6Y4yiE_;}(f^$i8u9+oaoz4V^MR{Cd5qW7z0J zmYi#tOi!BedST>UXgotkC5@THvD+XPMtncOIbYoC|A*sQySd#0;1xye57hwc-dsCt zf4bVeCAq`>6B!-mi<@b|uQHSD2YqTa@2f~B=%PauWA_Ki%&Ia|CdUrgMVU=#^;g!H zrB9!hM|4Lg9+vS{EG4+#%@)m0C<|I#{eE;=buj!Qt0FK(>l_oKH-c>?;}VS)(fB!h zJIro3rFnBl?fl7VlgrccLF&!T#C^QhfS(YGNzga( zC+FiF&Jj=VyTVYoD^ZMFs0?+&|GF^bP%dzFubG(tjrmD=5}|(;=4!gHJBn%FzF?%o zx=PZZyzC1Be7Qh#-s1esM(je>E3tVlaP;TnBHSi6Vzu|qh5G3}%6F>(Qacj7fjO7# zH;zmq;@xvP%k>RN0C@<7P-wy<6tZRtFJIqob>9UOvxx}FWsAfqVV~-nD~&GS@)3o% zzX$Z9hZ4`&PHu~@p*d$B;+&OlSk7-CP>WleESi4g|By!boJh9#JRqfdv!mUn-e`u_ z-Bf+VkOE%nzU&yK;lFnt{$uZzoeM~CyhhAur%Kt4L9=m3#&KB{U%s{K4YB)l1>~XszFU&^ZXS_eCi<(+ z%ldUw5d&P{T;pUR!{^vUf>qa4w$HQZ51wnH{Y7(iC5f>G@BKq_dV-n3v(Jxt-5aV)0s$>)&>I!Q9Pymc1A$ELlbR2l7N?GDFvHDAR7||L z>%KvZSQjl+=#5b75{!Ik->RfY9B!RKi7n|y3ePgBDIaj^jV}(R*FVpC80ZD3NPAan z4^Lsm31DeJBtfH$J1oqrh+u$MYk(}CgSI$^$9YxrcO1^+;4RMk1OTut{%edl2=cE+ t>Ob)vTl`m^om-kg-e6e}^tYbOODea;LwQ_dOV7vC{{oKZzw-an{trL+q96bO literal 0 HcmV?d00001 diff --git a/testdata/video-001-jpeg.tiff b/testdata/video-001-jpeg.tiff index f8f176aab0c38be04745e273db3115d15ab94099..31a0e8c450b5bc681de21c25175cc9cf898079ff 100644 GIT binary patch delta 4846 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 F{|7H&J0<`C literal 11020 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 2 { - tjpeg = true - // Write to buffer without EOI marker. - buf.Write(b[:len(b)-2]) - } else if len(b) != 0 { - return nil, FormatError("bad JPEGTables field") - } // JPEG image segment should start with SOI marker and end with EOI marker. - b, err = io.ReadAll(io.NewSectionReader(d.r, offset, n)) + b, err := io.ReadAll(io.NewSectionReader(d.r, offset, n)) if err != nil { return nil, err } if len(b) < 4 { return nil, FormatError("bad JPEG image segment") } - if tjpeg { - // Write JPEG image segment to buffer without SOI marker. - // When this is done, buffer data will be a full JPEG format data. - buf.Write(b[2:]) - } else { - // Write full JPEG image segment to buffer. - buf.Write(b) - } // Decode as a JPEG image. - d.tmp, err = jpeg.Decode(&buf) + d.tmp, err = jpeg.Decode(bytes.NewBuffer(b)) if err != nil { - return nil, err + var buf bytes.Buffer + if len(d.jpegTables) != 0 { + // Write JPEGTables data to buffer without EOI marker. + buf.Write(d.jpegTables[:len(d.jpegTables)-2]) + } else { + return nil, err + } + // Write JPEG image segment to buffer without SOI marker. + // When this is done, buffer data should be a full JPEG format data. + buf.Write(b[2:]) + d.tmp, err = jpeg.Decode(&buf) + if err != nil { + return nil, err + } } case cDeflate, cDeflateOld: var r io.ReadCloser @@ -782,12 +804,12 @@ func Decode(r io.Reader) (img image.Image, err error) { xmax := xmin + blkW ymax := ymin + blkH if d.firstVal(tCompression) == cJPEG { - d.decodeJPEG(img, xmin, ymin, xmax, ymax) + img, err = d.decodeJPEG(img, xmin, ymin, xmax, ymax) } else { err = d.decode(img, xmin, ymin, xmax, ymax) - if err != nil { - return nil, err - } + } + if err != nil { + return nil, err } } } diff --git a/tiff/reader_test.go b/tiff/reader_test.go index 7ea89033..f91fd94f 100644 --- a/tiff/reader_test.go +++ b/tiff/reader_test.go @@ -229,51 +229,6 @@ func TestDecodeCCITT(t *testing.T) { } } -func delta(u0, u1 uint32) int64 { - d := int64(u0) - int64(u1) - if d < 0 { - return -d - } - return d -} - -// averageDelta returns the average delta in RGB space. The two images must -// have the same bounds. -func averageDelta(m0, m1 image.Image) int64 { - b := m0.Bounds() - var sum, n int64 - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - c0 := m0.At(x, y) - c1 := m1.At(x, y) - r0, g0, b0, _ := c0.RGBA() - r1, g1, b1, _ := c1.RGBA() - sum += delta(r0, r1) - sum += delta(g0, g1) - sum += delta(b0, b1) - n += 3 - } - } - return sum / n -} - -// TestDecodeJPEG tests decoding an image use JPEG compression. -func TestDecodeJPEG(t *testing.T) { - img, err := openImage("video-001.tiff") - if err != nil { - t.Fatal(err) - } - - img2, err := openImage("video-001-jpeg.tiff") - if err != nil { - t.Fatal(err) - } - want := int64(6 << 8) - if got := averageDelta(img, img2); got > want { - t.Errorf("average delta too high; got %d, want <= %d", got, want) - } -} - // TestDecodeTagOrder tests that a malformed image with unsorted IFD entries is // correctly rejected. func TestDecodeTagOrder(t *testing.T) { diff --git a/tiff/writer.go b/tiff/writer.go index c8a01cea..4b5d909f 100644 --- a/tiff/writer.go +++ b/tiff/writer.go @@ -9,6 +9,7 @@ import ( "compress/zlib" "encoding/binary" "image" + "image/jpeg" "io" "sort" ) @@ -285,6 +286,15 @@ type Options struct { Predictor bool } +type discard struct{} + +func (discard) Write(p []byte) (int, error) { + return len(p), nil +} +func (discard) Close() error { + return nil +} + // Encode writes the image m to w. opt determines the options used for // encoding, such as the compression type. If opt is nil, an uncompressed // image is written. @@ -338,6 +348,12 @@ func Encode(w io.Writer, m image.Image, opt *Options) error { } case cDeflate: dst = zlib.NewWriter(&buf) + case cJPEG: + dst = discard{} + err = jpeg.Encode(&buf, m, nil) + if err != nil { + return err + } } pr := uint32(prNone) @@ -408,6 +424,18 @@ func Encode(w io.Writer, m image.Image, opt *Options) error { } } + // JPEG compression uses jpeg.Encode to encoding image which writes Gray or YCbCr image. + if compression == cJPEG { + switch m.(type) { + case *image.Gray: + default: + // Minimum Requirements for YCbCr Images. (See page 94). + photometricInterpretation = uint32(pYCbCr) + samplesPerPixel = 3 + bitsPerSample = []uint32{8, 8, 8} + } + } + ifd := []ifdEntry{ {tImageWidth, dtShort, []uint32{uint32(d.X)}}, {tImageLength, dtShort, []uint32{uint32(d.Y)}}, diff --git a/tiff/writer_test.go b/tiff/writer_test.go index 0650df3d..7347ab7c 100644 --- a/tiff/writer_test.go +++ b/tiff/writer_test.go @@ -75,6 +75,63 @@ func TestRoundtrip2(t *testing.T) { compare(t, m0, m1) } +func delta(u0, u1 uint32) int64 { + d := int64(u0) - int64(u1) + if d < 0 { + return -d + } + return d +} + +// averageDelta returns the average delta in RGB space. The two images must +// have the same bounds. +func averageDelta(m0, m1 image.Image) int64 { + b := m0.Bounds() + var sum, n int64 + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + c0 := m0.At(x, y) + c1 := m1.At(x, y) + r0, g0, b0, _ := c0.RGBA() + r1, g1, b1, _ := c1.RGBA() + sum += delta(r0, r1) + sum += delta(g0, g1) + sum += delta(b0, b1) + n += 3 + } + } + return sum / n +} + +// TestRoundtrip3 tests that encoding and decoding an image use JPEG compression. +func TestRoundtrip3(t *testing.T) { + roundtripTests := []string{ + "bw-jpeg.tiff", + "video-001-jpeg.tiff", + } + for _, rt := range roundtripTests { + img, err := openImage(rt) + if err != nil { + t.Fatal(err) + } + + out := new(bytes.Buffer) + err = Encode(out, img, &Options{Compression: JPEG}) + if err != nil { + t.Fatal(err) + } + + img2, err := Decode(&buffer{buf: out.Bytes()}) + if err != nil { + t.Fatal(err) + } + want := int64(6 << 8) + if got := averageDelta(img, img2); got > want { + t.Errorf("average delta too high; got %d, want <= %d", got, want) + } + } +} + func benchmarkEncode(b *testing.B, name string, pixelSize int) { b.Helper() img, err := openImage(name) From a2d0e4e2b74e76da049dd48845ad2f8c6243daa6 Mon Sep 17 00:00:00 2001 From: sunshineplan Date: Thu, 15 Jul 2021 10:07:34 +0800 Subject: [PATCH 5/5] Update writer.go --- tiff/writer.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tiff/writer.go b/tiff/writer.go index 4b5d909f..abbdae84 100644 --- a/tiff/writer.go +++ b/tiff/writer.go @@ -427,8 +427,7 @@ func Encode(w io.Writer, m image.Image, opt *Options) error { // JPEG compression uses jpeg.Encode to encoding image which writes Gray or YCbCr image. if compression == cJPEG { switch m.(type) { - case *image.Gray: - default: + case *image.YCbCr: // Minimum Requirements for YCbCr Images. (See page 94). photometricInterpretation = uint32(pYCbCr) samplesPerPixel = 3