From 91c8d5d20d9abee8113614ef49b1a626c76c16b4 Mon Sep 17 00:00:00 2001 From: tobi <31960611+tsmethurst@users.noreply.github.com> Date: Wed, 10 Aug 2022 14:05:14 +0200 Subject: [PATCH] [bugfix] Fix thumbnails not taking exif rotation into account (#746) * use disintegration/imaging instead of nfnt/resize * update tests * use disintegration lib for thumbing (if necessary) --- README.md | 2 +- go.mod | 3 +- go.sum | 6 +- internal/api/client/media/mediacreate_test.go | 4 +- internal/media/image.go | 41 +- internal/media/manager_test.go | 14 +- internal/media/test/test-jpeg-thumbnail.jpg | Bin 24571 -> 22858 bytes .../disintegration/imaging/.travis.yml | 12 + .../github.com/disintegration/imaging/LICENSE | 21 + .../disintegration/imaging/README.md | 226 ++++ .../disintegration/imaging/adjust.go | 253 +++++ .../disintegration/imaging/convolution.go | 148 +++ .../github.com/disintegration/imaging/doc.go | 7 + .../disintegration/imaging/effects.go | 169 +++ .../disintegration/imaging/histogram.go | 52 + .../github.com/disintegration/imaging/io.go | 444 ++++++++ .../disintegration/imaging/resize.go | 595 +++++++++++ .../disintegration/imaging/scanner.go | 285 +++++ .../disintegration/imaging/tools.go | 249 +++++ .../disintegration/imaging/transform.go | 268 +++++ .../disintegration/imaging/utils.go | 167 +++ vendor/github.com/nfnt/resize/.travis.yml | 7 - vendor/github.com/nfnt/resize/LICENSE | 13 - vendor/github.com/nfnt/resize/README.md | 151 --- vendor/github.com/nfnt/resize/converter.go | 438 -------- vendor/github.com/nfnt/resize/filters.go | 143 --- vendor/github.com/nfnt/resize/nearest.go | 318 ------ vendor/github.com/nfnt/resize/resize.go | 620 ----------- vendor/github.com/nfnt/resize/thumbnail.go | 55 - vendor/github.com/nfnt/resize/ycc.go | 387 ------- vendor/golang.org/x/image/AUTHORS | 3 + vendor/golang.org/x/image/CONTRIBUTORS | 3 + vendor/golang.org/x/image/LICENSE | 27 + vendor/golang.org/x/image/PATENTS | 22 + vendor/golang.org/x/image/bmp/reader.go | 213 ++++ vendor/golang.org/x/image/bmp/writer.go | 262 +++++ vendor/golang.org/x/image/ccitt/reader.go | 697 ++++++++++++ vendor/golang.org/x/image/ccitt/table.go | 989 ++++++++++++++++++ vendor/golang.org/x/image/ccitt/writer.go | 102 ++ vendor/golang.org/x/image/tiff/buffer.go | 69 ++ vendor/golang.org/x/image/tiff/compress.go | 58 + vendor/golang.org/x/image/tiff/consts.go | 149 +++ vendor/golang.org/x/image/tiff/fuzz.go | 29 + vendor/golang.org/x/image/tiff/lzw/reader.go | 272 +++++ vendor/golang.org/x/image/tiff/reader.go | 706 +++++++++++++ vendor/golang.org/x/image/tiff/writer.go | 438 ++++++++ vendor/modules.txt | 10 +- 47 files changed, 6984 insertions(+), 2163 deletions(-) create mode 100644 vendor/github.com/disintegration/imaging/.travis.yml create mode 100644 vendor/github.com/disintegration/imaging/LICENSE create mode 100644 vendor/github.com/disintegration/imaging/README.md create mode 100644 vendor/github.com/disintegration/imaging/adjust.go create mode 100644 vendor/github.com/disintegration/imaging/convolution.go create mode 100644 vendor/github.com/disintegration/imaging/doc.go create mode 100644 vendor/github.com/disintegration/imaging/effects.go create mode 100644 vendor/github.com/disintegration/imaging/histogram.go create mode 100644 vendor/github.com/disintegration/imaging/io.go create mode 100644 vendor/github.com/disintegration/imaging/resize.go create mode 100644 vendor/github.com/disintegration/imaging/scanner.go create mode 100644 vendor/github.com/disintegration/imaging/tools.go create mode 100644 vendor/github.com/disintegration/imaging/transform.go create mode 100644 vendor/github.com/disintegration/imaging/utils.go delete mode 100644 vendor/github.com/nfnt/resize/.travis.yml delete mode 100644 vendor/github.com/nfnt/resize/LICENSE delete mode 100644 vendor/github.com/nfnt/resize/README.md delete mode 100644 vendor/github.com/nfnt/resize/converter.go delete mode 100644 vendor/github.com/nfnt/resize/filters.go delete mode 100644 vendor/github.com/nfnt/resize/nearest.go delete mode 100644 vendor/github.com/nfnt/resize/resize.go delete mode 100644 vendor/github.com/nfnt/resize/thumbnail.go delete mode 100644 vendor/github.com/nfnt/resize/ycc.go create mode 100644 vendor/golang.org/x/image/AUTHORS create mode 100644 vendor/golang.org/x/image/CONTRIBUTORS create mode 100644 vendor/golang.org/x/image/LICENSE create mode 100644 vendor/golang.org/x/image/PATENTS create mode 100644 vendor/golang.org/x/image/bmp/reader.go create mode 100644 vendor/golang.org/x/image/bmp/writer.go create mode 100644 vendor/golang.org/x/image/ccitt/reader.go create mode 100644 vendor/golang.org/x/image/ccitt/table.go create mode 100644 vendor/golang.org/x/image/ccitt/writer.go create mode 100644 vendor/golang.org/x/image/tiff/buffer.go create mode 100644 vendor/golang.org/x/image/tiff/compress.go create mode 100644 vendor/golang.org/x/image/tiff/consts.go create mode 100644 vendor/golang.org/x/image/tiff/fuzz.go create mode 100644 vendor/golang.org/x/image/tiff/lzw/reader.go create mode 100644 vendor/golang.org/x/image/tiff/reader.go create mode 100644 vendor/golang.org/x/image/tiff/writer.go diff --git a/README.md b/README.md index ba99069ad..6583b49a7 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,7 @@ The following libraries and frameworks are used by GoToSocial, with gratitude - [buckket/go-blurhash](https://github.com/buckket/go-blurhash); used for generating image blurhashes. [GPL-3.0 License](https://spdx.org/licenses/GPL-3.0-only.html). - [coreos/go-oidc](https://github.com/coreos/go-oidc); OIDC client library. [Apache-2.0 License](https://spdx.org/licenses/Apache-2.0.html). +- [disintegration/imaging](https://github.com/disintegration/imaging); image resizing. [MIT License](https://spdx.org/licenses/MIT.html). - [gin-gonic/gin](https://github.com/gin-gonic/gin); speedy router engine. [MIT License](https://spdx.org/licenses/MIT.html). - [gin-contrib/cors](https://github.com/gin-contrib/cors); Gin CORS middleware. [MIT License](https://spdx.org/licenses/MIT.html). - [gin-contrib/gzip](https://github.com/gin-contrib/gzip); Gin gzip middleware. [MIT License](https://spdx.org/licenses/MIT.html). @@ -232,7 +233,6 @@ The following libraries and frameworks are used by GoToSocial, with gratitude - [modernc.org/ccgo](https://gitlab.com/cznic/ccgo); c99 AST -> Go translater. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). - [modernc.org/libc](https://gitlab.com/cznic/libc); C-runtime services. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). - [mvdan/xurls](https://github.com/mvdan/xurls); URL parsing regular expressions. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). -- [nfnt/resize](https://github.com/nfnt/resize); convenient image resizing. [ISC License](https://spdx.org/licenses/ISC.html). - [oklog/ulid](https://github.com/oklog/ulid); sequential, database-friendly ID generation. [Apache-2.0 License](https://spdx.org/licenses/Apache-2.0.html). - [ReneKroon/ttlcache](https://github.com/ReneKroon/ttlcache); in-memory caching. [MIT License](https://spdx.org/licenses/MIT.html). - [robfig/cron](https://github.com/robfig/cron); cron job scheduling. [MIT License](https://spdx.org/licenses/MIT.html). diff --git a/go.mod b/go.mod index 80def8a23..2ffeec757 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( codeberg.org/gruf/go-store v1.3.8 github.com/buckket/go-blurhash v1.1.0 github.com/coreos/go-oidc/v3 v3.1.0 + github.com/disintegration/imaging v1.6.2 github.com/gin-contrib/cors v1.3.1 github.com/gin-contrib/gzip v0.0.5 github.com/gin-contrib/sessions v0.0.5 @@ -31,7 +32,6 @@ require ( github.com/miekg/dns v1.1.49 github.com/minio/minio-go/v7 v7.0.29 github.com/mitchellh/mapstructure v1.5.0 - github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/oklog/ulid v1.3.1 github.com/robfig/cron/v3 v3.0.1 github.com/russross/blackfriday/v2 v2.1.0 @@ -126,6 +126,7 @@ require ( github.com/ugorji/go/codec v1.2.7 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32 // indirect golang.org/x/tools v0.1.10 // indirect diff --git a/go.sum b/go.sum index b8ff5295b..44c569902 100644 --- a/go.sum +++ b/go.sum @@ -122,6 +122,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE= github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E= github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8= @@ -432,8 +434,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= -github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= -github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -635,6 +635,8 @@ golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv2 golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/internal/api/client/media/mediacreate_test.go b/internal/api/client/media/mediacreate_test.go index 8651fd982..be3bf300e 100644 --- a/internal/api/client/media/mediacreate_test.go +++ b/internal/api/client/media/mediacreate_test.go @@ -212,7 +212,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() { Y: 0.5, }, }, attachmentReply.Meta) - suite.Equal("LjBzUo#6RQR._NvzRjWF?urqV@a$", attachmentReply.Blurhash) + suite.Equal("LiBzRk#6V[WF_NvzV@WY_3rqV@a$", attachmentReply.Blurhash) suite.NotEmpty(attachmentReply.ID) suite.NotEmpty(attachmentReply.URL) suite.NotEmpty(attachmentReply.PreviewURL) @@ -306,7 +306,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessfulV2() { Y: 0.5, }, }, attachmentReply.Meta) - suite.Equal("LjBzUo#6RQR._NvzRjWF?urqV@a$", attachmentReply.Blurhash) + suite.Equal("LiBzRk#6V[WF_NvzV@WY_3rqV@a$", attachmentReply.Blurhash) suite.NotEmpty(attachmentReply.ID) suite.Nil(attachmentReply.URL) suite.NotEmpty(attachmentReply.PreviewURL) diff --git a/internal/media/image.go b/internal/media/image.go index cb26ad539..9dd699dc6 100644 --- a/internal/media/image.go +++ b/internal/media/image.go @@ -29,7 +29,7 @@ import ( "io" "github.com/buckket/go-blurhash" - "github.com/nfnt/resize" + "github.com/disintegration/imaging" ) const ( @@ -114,33 +114,40 @@ func deriveThumbnail(r io.Reader, contentType string, createBlurhash bool) (*ima var err error switch contentType { - case mimeImageJpeg: - i, err = jpeg.Decode(r) + case mimeImageJpeg, mimeImageGif: + i, err = imaging.Decode(r, imaging.AutoOrientation(true)) case mimeImagePng: - i, err = StrippedPngDecode(r) - case mimeImageGif: - i, err = gif.Decode(r) + strippedPngReader := io.Reader(&PNGAncillaryChunkStripper{ + Reader: r, + }) + i, err = imaging.Decode(strippedPngReader, imaging.AutoOrientation(true)) default: err = fmt.Errorf("content type %s can't be thumbnailed", contentType) } if err != nil { - return nil, fmt.Errorf("error decoding image as %s: %s", contentType, err) + return nil, fmt.Errorf("error decoding %s: %s", contentType, err) } - if i == nil { - return nil, errors.New("processed image was nil") + originalX := i.Bounds().Size().X + originalY := i.Bounds().Size().Y + + var thumb image.Image + if originalX <= thumbnailMaxWidth && originalY <= thumbnailMaxHeight { + // it's already small, no need to resize + thumb = i + } else { + thumb = imaging.Fit(i, thumbnailMaxWidth, thumbnailMaxHeight, imaging.Linear) } - thumb := resize.Thumbnail(thumbnailMaxWidth, thumbnailMaxHeight, i, resize.NearestNeighbor) - width := thumb.Bounds().Size().X - height := thumb.Bounds().Size().Y - size := width * height - aspect := float64(width) / float64(height) + thumbX := thumb.Bounds().Size().X + thumbY := thumb.Bounds().Size().Y + size := thumbX * thumbY + aspect := float64(thumbX) / float64(thumbY) im := &imageMeta{ - width: width, - height: height, + width: thumbX, + height: thumbY, size: size, aspect: aspect, } @@ -148,7 +155,7 @@ func deriveThumbnail(r io.Reader, contentType string, createBlurhash bool) (*ima if createBlurhash { // for generating blurhashes, it's more cost effective to lose detail rather than // pass a big image into the blurhash algorithm, so make a teeny tiny version - tiny := resize.Thumbnail(32, 32, thumb, resize.NearestNeighbor) + tiny := imaging.Resize(thumb, 32, 0, imaging.NearestNeighbor) bh, err := blurhash.Encode(4, 3, tiny) if err != nil { return nil, fmt.Errorf("error creating blurhash: %s", err) diff --git a/internal/media/manager_test.go b/internal/media/manager_test.go index f55763439..396df1523 100644 --- a/internal/media/manager_test.go +++ b/internal/media/manager_test.go @@ -80,7 +80,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessBlocking() { suite.Equal("image/jpeg", attachment.File.ContentType) suite.Equal("image/jpeg", attachment.Thumbnail.ContentType) suite.Equal(269739, attachment.File.FileSize) - suite.Equal("LjBzUo#6RQR._NvzRjWF?urqV@a$", attachment.Blurhash) + suite.Equal("LiBzRk#6V[WF_NvzV@WY_3rqV@a$", attachment.Blurhash) // now make sure the attachment is in the database dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachmentID) @@ -152,7 +152,7 @@ func (suite *ManagerTestSuite) TestPngNoAlphaChannelProcessBlocking() { suite.Equal("image/png", attachment.File.ContentType) suite.Equal("image/jpeg", attachment.Thumbnail.ContentType) suite.Equal(17471, attachment.File.FileSize) - suite.Equal("LFP?{^.A-?xd.9o#RVRQ~oj:_0xW", attachment.Blurhash) + suite.Equal("LFQT7e.A%O%4?co$M}M{_1W9~TxV", attachment.Blurhash) // now make sure the attachment is in the database dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachmentID) @@ -224,7 +224,7 @@ func (suite *ManagerTestSuite) TestPngAlphaChannelProcessBlocking() { suite.Equal("image/png", attachment.File.ContentType) suite.Equal("image/jpeg", attachment.Thumbnail.ContentType) suite.Equal(18904, attachment.File.FileSize) - suite.Equal("LFP?{^.A-?xd.9o#RVRQ~oj:_0xW", attachment.Blurhash) + suite.Equal("LFQT7e.A%O%4?co$M}M{_1W9~TxV", attachment.Blurhash) // now make sure the attachment is in the database dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachmentID) @@ -307,7 +307,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessBlockingWithCallback() { suite.Equal("image/jpeg", attachment.File.ContentType) suite.Equal("image/jpeg", attachment.Thumbnail.ContentType) suite.Equal(269739, attachment.File.FileSize) - suite.Equal("LjBzUo#6RQR._NvzRjWF?urqV@a$", attachment.Blurhash) + suite.Equal("LiBzRk#6V[WF_NvzV@WY_3rqV@a$", attachment.Blurhash) // now make sure the attachment is in the database dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachmentID) @@ -386,7 +386,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessAsync() { suite.Equal("image/jpeg", attachment.File.ContentType) suite.Equal("image/jpeg", attachment.Thumbnail.ContentType) suite.Equal(269739, attachment.File.FileSize) - suite.Equal("LjBzUo#6RQR._NvzRjWF?urqV@a$", attachment.Blurhash) + suite.Equal("LiBzRk#6V[WF_NvzV@WY_3rqV@a$", attachment.Blurhash) // now make sure the attachment is in the database dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachmentID) @@ -467,7 +467,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegQueueSpamming() { suite.Equal("image/jpeg", attachment.File.ContentType) suite.Equal("image/jpeg", attachment.Thumbnail.ContentType) suite.Equal(269739, attachment.File.FileSize) - suite.Equal("LjBzUo#6RQR._NvzRjWF?urqV@a$", attachment.Blurhash) + suite.Equal("LiBzRk#6V[WF_NvzV@WY_3rqV@a$", attachment.Blurhash) // now make sure the attachment is in the database dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachmentID) @@ -556,7 +556,7 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessBlockingWithDiskStorage() { suite.Equal("image/jpeg", attachment.File.ContentType) suite.Equal("image/jpeg", attachment.Thumbnail.ContentType) suite.Equal(269739, attachment.File.FileSize) - suite.Equal("LjBzUo#6RQR._NvzRjWF?urqV@a$", attachment.Blurhash) + suite.Equal("LiBzRk#6V[WF_NvzV@WY_3rqV@a$", attachment.Blurhash) // now make sure the attachment is in the database dbAttachment, err := suite.db.GetAttachmentByID(ctx, attachmentID) diff --git a/internal/media/test/test-jpeg-thumbnail.jpg b/internal/media/test/test-jpeg-thumbnail.jpg index eeb947ed158696ae082115648d3e295b9b0badf1..80170e7c846fb6a8e72186fb078ccd593abf4542 100644 GIT binary patch literal 22858 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

h6E-skie{EW+At4cFlUk+rdyF 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 Go&N{pmo@ 1 { + s = 1 + } + r, g, b := hslToRGB(h, s, l) + return color.NRGBA{r, g, b, c.A} + }) +} + +// AdjustContrast changes the contrast of the image using the percentage parameter and returns the adjusted image. +// The percentage must be in range (-100, 100). The percentage = 0 gives the original image. +// The percentage = -100 gives solid gray image. +// +// Examples: +// +// dstImage = imaging.AdjustContrast(srcImage, -10) // Decrease image contrast by 10%. +// dstImage = imaging.AdjustContrast(srcImage, 20) // Increase image contrast by 20%. +// +func AdjustContrast(img image.Image, percentage float64) *image.NRGBA { + percentage = math.Min(math.Max(percentage, -100.0), 100.0) + lut := make([]uint8, 256) + + v := (100.0 + percentage) / 100.0 + for i := 0; i < 256; i++ { + switch { + case 0 <= v && v <= 1: + lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*v) * 255.0) + case 1 < v && v < 2: + lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*(1/(2.0-v))) * 255.0) + default: + lut[i] = uint8(float64(i)/255.0+0.5) * 255 + } + } + + return adjustLUT(img, lut) +} + +// AdjustBrightness changes the brightness of the image using the percentage parameter and returns the adjusted image. +// The percentage must be in range (-100, 100). The percentage = 0 gives the original image. +// The percentage = -100 gives solid black image. The percentage = 100 gives solid white image. +// +// Examples: +// +// dstImage = imaging.AdjustBrightness(srcImage, -15) // Decrease image brightness by 15%. +// dstImage = imaging.AdjustBrightness(srcImage, 10) // Increase image brightness by 10%. +// +func AdjustBrightness(img image.Image, percentage float64) *image.NRGBA { + percentage = math.Min(math.Max(percentage, -100.0), 100.0) + lut := make([]uint8, 256) + + shift := 255.0 * percentage / 100.0 + for i := 0; i < 256; i++ { + lut[i] = clamp(float64(i) + shift) + } + + return adjustLUT(img, lut) +} + +// AdjustGamma performs a gamma correction on the image and returns the adjusted image. +// Gamma parameter must be positive. Gamma = 1.0 gives the original image. +// Gamma less than 1.0 darkens the image and gamma greater than 1.0 lightens it. +// +// Example: +// +// dstImage = imaging.AdjustGamma(srcImage, 0.7) +// +func AdjustGamma(img image.Image, gamma float64) *image.NRGBA { + e := 1.0 / math.Max(gamma, 0.0001) + lut := make([]uint8, 256) + + for i := 0; i < 256; i++ { + lut[i] = clamp(math.Pow(float64(i)/255.0, e) * 255.0) + } + + return adjustLUT(img, lut) +} + +// AdjustSigmoid changes the contrast of the image using a sigmoidal function and returns the adjusted image. +// It's a non-linear contrast change useful for photo adjustments as it preserves highlight and shadow detail. +// The midpoint parameter is the midpoint of contrast that must be between 0 and 1, typically 0.5. +// The factor parameter indicates how much to increase or decrease the contrast, typically in range (-10, 10). +// If the factor parameter is positive the image contrast is increased otherwise the contrast is decreased. +// +// Examples: +// +// dstImage = imaging.AdjustSigmoid(srcImage, 0.5, 3.0) // Increase the contrast. +// dstImage = imaging.AdjustSigmoid(srcImage, 0.5, -3.0) // Decrease the contrast. +// +func AdjustSigmoid(img image.Image, midpoint, factor float64) *image.NRGBA { + if factor == 0 { + return Clone(img) + } + + lut := make([]uint8, 256) + a := math.Min(math.Max(midpoint, 0.0), 1.0) + b := math.Abs(factor) + sig0 := sigmoid(a, b, 0) + sig1 := sigmoid(a, b, 1) + e := 1.0e-6 + + if factor > 0 { + for i := 0; i < 256; i++ { + x := float64(i) / 255.0 + sigX := sigmoid(a, b, x) + f := (sigX - sig0) / (sig1 - sig0) + lut[i] = clamp(f * 255.0) + } + } else { + for i := 0; i < 256; i++ { + x := float64(i) / 255.0 + arg := math.Min(math.Max((sig1-sig0)*x+sig0, e), 1.0-e) + f := a - math.Log(1.0/arg-1.0)/b + lut[i] = clamp(f * 255.0) + } + } + + return adjustLUT(img, lut) +} + +func sigmoid(a, b, x float64) float64 { + return 1 / (1 + math.Exp(b*(a-x))) +} + +// adjustLUT applies the given lookup table to the colors of the image. +func adjustLUT(img image.Image, lut []uint8) *image.NRGBA { + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h)) + lut = lut[0:256] + parallel(0, src.h, func(ys <-chan int) { + for y := range ys { + i := y * dst.Stride + src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4]) + for x := 0; x < src.w; x++ { + d := dst.Pix[i : i+3 : i+3] + d[0] = lut[d[0]] + d[1] = lut[d[1]] + d[2] = lut[d[2]] + i += 4 + } + } + }) + return dst +} + +// AdjustFunc applies the fn function to each pixel of the img image and returns the adjusted image. +// +// Example: +// +// dstImage = imaging.AdjustFunc( +// srcImage, +// func(c color.NRGBA) color.NRGBA { +// // Shift the red channel by 16. +// r := int(c.R) + 16 +// if r > 255 { +// r = 255 +// } +// return color.NRGBA{uint8(r), c.G, c.B, c.A} +// } +// ) +// +func AdjustFunc(img image.Image, fn func(c color.NRGBA) color.NRGBA) *image.NRGBA { + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h)) + parallel(0, src.h, func(ys <-chan int) { + for y := range ys { + i := y * dst.Stride + src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4]) + for x := 0; x < src.w; x++ { + d := dst.Pix[i : i+4 : i+4] + r := d[0] + g := d[1] + b := d[2] + a := d[3] + c := fn(color.NRGBA{r, g, b, a}) + d[0] = c.R + d[1] = c.G + d[2] = c.B + d[3] = c.A + i += 4 + } + } + }) + return dst +} diff --git a/vendor/github.com/disintegration/imaging/convolution.go b/vendor/github.com/disintegration/imaging/convolution.go new file mode 100644 index 000000000..11eddc162 --- /dev/null +++ b/vendor/github.com/disintegration/imaging/convolution.go @@ -0,0 +1,148 @@ +package imaging + +import ( + "image" +) + +// ConvolveOptions are convolution parameters. +type ConvolveOptions struct { + // If Normalize is true the kernel is normalized before convolution. + Normalize bool + + // If Abs is true the absolute value of each color channel is taken after convolution. + Abs bool + + // Bias is added to each color channel value after convolution. + Bias int +} + +// Convolve3x3 convolves the image with the specified 3x3 convolution kernel. +// Default parameters are used if a nil *ConvolveOptions is passed. +func Convolve3x3(img image.Image, kernel [9]float64, options *ConvolveOptions) *image.NRGBA { + return convolve(img, kernel[:], options) +} + +// Convolve5x5 convolves the image with the specified 5x5 convolution kernel. +// Default parameters are used if a nil *ConvolveOptions is passed. +func Convolve5x5(img image.Image, kernel [25]float64, options *ConvolveOptions) *image.NRGBA { + return convolve(img, kernel[:], options) +} + +func convolve(img image.Image, kernel []float64, options *ConvolveOptions) *image.NRGBA { + src := toNRGBA(img) + w := src.Bounds().Max.X + h := src.Bounds().Max.Y + dst := image.NewNRGBA(image.Rect(0, 0, w, h)) + + if w < 1 || h < 1 { + return dst + } + + if options == nil { + options = &ConvolveOptions{} + } + + if options.Normalize { + normalizeKernel(kernel) + } + + type coef struct { + x, y int + k float64 + } + var coefs []coef + var m int + + switch len(kernel) { + case 9: + m = 1 + case 25: + m = 2 + } + + i := 0 + for y := -m; y <= m; y++ { + for x := -m; x <= m; x++ { + if kernel[i] != 0 { + coefs = append(coefs, coef{x: x, y: y, k: kernel[i]}) + } + i++ + } + } + + parallel(0, h, func(ys <-chan int) { + for y := range ys { + for x := 0; x < w; x++ { + var r, g, b float64 + for _, c := range coefs { + ix := x + c.x + if ix < 0 { + ix = 0 + } else if ix >= w { + ix = w - 1 + } + + iy := y + c.y + if iy < 0 { + iy = 0 + } else if iy >= h { + iy = h - 1 + } + + off := iy*src.Stride + ix*4 + s := src.Pix[off : off+3 : off+3] + r += float64(s[0]) * c.k + g += float64(s[1]) * c.k + b += float64(s[2]) * c.k + } + + if options.Abs { + if r < 0 { + r = -r + } + if g < 0 { + g = -g + } + if b < 0 { + b = -b + } + } + + if options.Bias != 0 { + r += float64(options.Bias) + g += float64(options.Bias) + b += float64(options.Bias) + } + + srcOff := y*src.Stride + x*4 + dstOff := y*dst.Stride + x*4 + d := dst.Pix[dstOff : dstOff+4 : dstOff+4] + d[0] = clamp(r) + d[1] = clamp(g) + d[2] = clamp(b) + d[3] = src.Pix[srcOff+3] + } + } + }) + + return dst +} + +func normalizeKernel(kernel []float64) { + var sum, sumpos float64 + for i := range kernel { + sum += kernel[i] + if kernel[i] > 0 { + sumpos += kernel[i] + } + } + if sum != 0 { + for i := range kernel { + kernel[i] /= sum + } + } else if sumpos != 0 { + for i := range kernel { + kernel[i] /= sumpos + } + } +} diff --git a/vendor/github.com/disintegration/imaging/doc.go b/vendor/github.com/disintegration/imaging/doc.go new file mode 100644 index 000000000..c98c91250 --- /dev/null +++ b/vendor/github.com/disintegration/imaging/doc.go @@ -0,0 +1,7 @@ +/* +Package imaging provides basic image processing functions (resize, rotate, crop, brightness/contrast adjustments, etc.). + +All the image processing functions provided by the package accept any image type that implements image.Image interface +as an input, and return a new image of *image.NRGBA type (32bit RGBA colors, non-premultiplied alpha). +*/ +package imaging diff --git a/vendor/github.com/disintegration/imaging/effects.go b/vendor/github.com/disintegration/imaging/effects.go new file mode 100644 index 000000000..47316b701 --- /dev/null +++ b/vendor/github.com/disintegration/imaging/effects.go @@ -0,0 +1,169 @@ +package imaging + +import ( + "image" + "math" +) + +func gaussianBlurKernel(x, sigma float64) float64 { + return math.Exp(-(x*x)/(2*sigma*sigma)) / (sigma * math.Sqrt(2*math.Pi)) +} + +// Blur produces a blurred version of the image using a Gaussian function. +// Sigma parameter must be positive and indicates how much the image will be blurred. +// +// Example: +// +// dstImage := imaging.Blur(srcImage, 3.5) +// +func Blur(img image.Image, sigma float64) *image.NRGBA { + if sigma <= 0 { + return Clone(img) + } + + radius := int(math.Ceil(sigma * 3.0)) + kernel := make([]float64, radius+1) + + for i := 0; i <= radius; i++ { + kernel[i] = gaussianBlurKernel(float64(i), sigma) + } + + return blurVertical(blurHorizontal(img, kernel), kernel) +} + +func blurHorizontal(img image.Image, kernel []float64) *image.NRGBA { + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h)) + radius := len(kernel) - 1 + + parallel(0, src.h, func(ys <-chan int) { + scanLine := make([]uint8, src.w*4) + scanLineF := make([]float64, len(scanLine)) + for y := range ys { + src.scan(0, y, src.w, y+1, scanLine) + for i, v := range scanLine { + scanLineF[i] = float64(v) + } + for x := 0; x < src.w; x++ { + min := x - radius + if min < 0 { + min = 0 + } + max := x + radius + if max > src.w-1 { + max = src.w - 1 + } + var r, g, b, a, wsum float64 + for ix := min; ix <= max; ix++ { + i := ix * 4 + weight := kernel[absint(x-ix)] + wsum += weight + s := scanLineF[i : i+4 : i+4] + wa := s[3] * weight + r += s[0] * wa + g += s[1] * wa + b += s[2] * wa + a += wa + } + if a != 0 { + aInv := 1 / a + j := y*dst.Stride + x*4 + d := dst.Pix[j : j+4 : j+4] + d[0] = clamp(r * aInv) + d[1] = clamp(g * aInv) + d[2] = clamp(b * aInv) + d[3] = clamp(a / wsum) + } + } + } + }) + + return dst +} + +func blurVertical(img image.Image, kernel []float64) *image.NRGBA { + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h)) + radius := len(kernel) - 1 + + parallel(0, src.w, func(xs <-chan int) { + scanLine := make([]uint8, src.h*4) + scanLineF := make([]float64, len(scanLine)) + for x := range xs { + src.scan(x, 0, x+1, src.h, scanLine) + for i, v := range scanLine { + scanLineF[i] = float64(v) + } + for y := 0; y < src.h; y++ { + min := y - radius + if min < 0 { + min = 0 + } + max := y + radius + if max > src.h-1 { + max = src.h - 1 + } + var r, g, b, a, wsum float64 + for iy := min; iy <= max; iy++ { + i := iy * 4 + weight := kernel[absint(y-iy)] + wsum += weight + s := scanLineF[i : i+4 : i+4] + wa := s[3] * weight + r += s[0] * wa + g += s[1] * wa + b += s[2] * wa + a += wa + } + if a != 0 { + aInv := 1 / a + j := y*dst.Stride + x*4 + d := dst.Pix[j : j+4 : j+4] + d[0] = clamp(r * aInv) + d[1] = clamp(g * aInv) + d[2] = clamp(b * aInv) + d[3] = clamp(a / wsum) + } + } + } + }) + + return dst +} + +// Sharpen produces a sharpened version of the image. +// Sigma parameter must be positive and indicates how much the image will be sharpened. +// +// Example: +// +// dstImage := imaging.Sharpen(srcImage, 3.5) +// +func Sharpen(img image.Image, sigma float64) *image.NRGBA { + if sigma <= 0 { + return Clone(img) + } + + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h)) + blurred := Blur(img, sigma) + + parallel(0, src.h, func(ys <-chan int) { + scanLine := make([]uint8, src.w*4) + for y := range ys { + src.scan(0, y, src.w, y+1, scanLine) + j := y * dst.Stride + for i := 0; i < src.w*4; i++ { + val := int(scanLine[i])<<1 - int(blurred.Pix[j]) + if val < 0 { + val = 0 + } else if val > 0xff { + val = 0xff + } + dst.Pix[j] = uint8(val) + j++ + } + } + }) + + return dst +} diff --git a/vendor/github.com/disintegration/imaging/histogram.go b/vendor/github.com/disintegration/imaging/histogram.go new file mode 100644 index 000000000..c547fe822 --- /dev/null +++ b/vendor/github.com/disintegration/imaging/histogram.go @@ -0,0 +1,52 @@ +package imaging + +import ( + "image" + "sync" +) + +// Histogram returns a normalized histogram of an image. +// +// Resulting histogram is represented as an array of 256 floats, where +// histogram[i] is a probability of a pixel being of a particular luminance i. +func Histogram(img image.Image) [256]float64 { + var mu sync.Mutex + var histogram [256]float64 + var total float64 + + src := newScanner(img) + if src.w == 0 || src.h == 0 { + return histogram + } + + parallel(0, src.h, func(ys <-chan int) { + var tmpHistogram [256]float64 + var tmpTotal float64 + scanLine := make([]uint8, src.w*4) + for y := range ys { + src.scan(0, y, src.w, y+1, scanLine) + i := 0 + for x := 0; x < src.w; x++ { + s := scanLine[i : i+3 : i+3] + r := s[0] + g := s[1] + b := s[2] + y := 0.299*float32(r) + 0.587*float32(g) + 0.114*float32(b) + tmpHistogram[int(y+0.5)]++ + tmpTotal++ + i += 4 + } + } + mu.Lock() + for i := 0; i < 256; i++ { + histogram[i] += tmpHistogram[i] + } + total += tmpTotal + mu.Unlock() + }) + + for i := 0; i < 256; i++ { + histogram[i] = histogram[i] / total + } + return histogram +} diff --git a/vendor/github.com/disintegration/imaging/io.go b/vendor/github.com/disintegration/imaging/io.go new file mode 100644 index 000000000..f6c6da86b --- /dev/null +++ b/vendor/github.com/disintegration/imaging/io.go @@ -0,0 +1,444 @@ +package imaging + +import ( + "encoding/binary" + "errors" + "image" + "image/draw" + "image/gif" + "image/jpeg" + "image/png" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "golang.org/x/image/bmp" + "golang.org/x/image/tiff" +) + +type fileSystem interface { + Create(string) (io.WriteCloser, error) + Open(string) (io.ReadCloser, error) +} + +type localFS struct{} + +func (localFS) Create(name string) (io.WriteCloser, error) { return os.Create(name) } +func (localFS) Open(name string) (io.ReadCloser, error) { return os.Open(name) } + +var fs fileSystem = localFS{} + +type decodeConfig struct { + autoOrientation bool +} + +var defaultDecodeConfig = decodeConfig{ + autoOrientation: false, +} + +// DecodeOption sets an optional parameter for the Decode and Open functions. +type DecodeOption func(*decodeConfig) + +// AutoOrientation returns a DecodeOption that sets the auto-orientation mode. +// If auto-orientation is enabled, the image will be transformed after decoding +// according to the EXIF orientation tag (if present). By default it's disabled. +func AutoOrientation(enabled bool) DecodeOption { + return func(c *decodeConfig) { + c.autoOrientation = enabled + } +} + +// Decode reads an image from r. +func Decode(r io.Reader, opts ...DecodeOption) (image.Image, error) { + cfg := defaultDecodeConfig + for _, option := range opts { + option(&cfg) + } + + if !cfg.autoOrientation { + img, _, err := image.Decode(r) + return img, err + } + + var orient orientation + pr, pw := io.Pipe() + r = io.TeeReader(r, pw) + done := make(chan struct{}) + go func() { + defer close(done) + orient = readOrientation(pr) + io.Copy(ioutil.Discard, pr) + }() + + img, _, err := image.Decode(r) + pw.Close() + <-done + if err != nil { + return nil, err + } + + return fixOrientation(img, orient), nil +} + +// Open loads an image from file. +// +// Examples: +// +// // Load an image from file. +// img, err := imaging.Open("test.jpg") +// +// // Load an image and transform it depending on the EXIF orientation tag (if present). +// img, err := imaging.Open("test.jpg", imaging.AutoOrientation(true)) +// +func Open(filename string, opts ...DecodeOption) (image.Image, error) { + file, err := fs.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + return Decode(file, opts...) +} + +// Format is an image file format. +type Format int + +// Image file formats. +const ( + JPEG Format = iota + PNG + GIF + TIFF + BMP +) + +var formatExts = map[string]Format{ + "jpg": JPEG, + "jpeg": JPEG, + "png": PNG, + "gif": GIF, + "tif": TIFF, + "tiff": TIFF, + "bmp": BMP, +} + +var formatNames = map[Format]string{ + JPEG: "JPEG", + PNG: "PNG", + GIF: "GIF", + TIFF: "TIFF", + BMP: "BMP", +} + +func (f Format) String() string { + return formatNames[f] +} + +// ErrUnsupportedFormat means the given image format is not supported. +var ErrUnsupportedFormat = errors.New("imaging: unsupported image format") + +// FormatFromExtension parses image format from filename extension: +// "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported. +func FormatFromExtension(ext string) (Format, error) { + if f, ok := formatExts[strings.ToLower(strings.TrimPrefix(ext, "."))]; ok { + return f, nil + } + return -1, ErrUnsupportedFormat +} + +// FormatFromFilename parses image format from filename: +// "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported. +func FormatFromFilename(filename string) (Format, error) { + ext := filepath.Ext(filename) + return FormatFromExtension(ext) +} + +type encodeConfig struct { + jpegQuality int + gifNumColors int + gifQuantizer draw.Quantizer + gifDrawer draw.Drawer + pngCompressionLevel png.CompressionLevel +} + +var defaultEncodeConfig = encodeConfig{ + jpegQuality: 95, + gifNumColors: 256, + gifQuantizer: nil, + gifDrawer: nil, + pngCompressionLevel: png.DefaultCompression, +} + +// EncodeOption sets an optional parameter for the Encode and Save functions. +type EncodeOption func(*encodeConfig) + +// JPEGQuality returns an EncodeOption that sets the output JPEG quality. +// Quality ranges from 1 to 100 inclusive, higher is better. Default is 95. +func JPEGQuality(quality int) EncodeOption { + return func(c *encodeConfig) { + c.jpegQuality = quality + } +} + +// GIFNumColors returns an EncodeOption that sets the maximum number of colors +// used in the GIF-encoded image. It ranges from 1 to 256. Default is 256. +func GIFNumColors(numColors int) EncodeOption { + return func(c *encodeConfig) { + c.gifNumColors = numColors + } +} + +// GIFQuantizer returns an EncodeOption that sets the quantizer that is used to produce +// a palette of the GIF-encoded image. +func GIFQuantizer(quantizer draw.Quantizer) EncodeOption { + return func(c *encodeConfig) { + c.gifQuantizer = quantizer + } +} + +// GIFDrawer returns an EncodeOption that sets the drawer that is used to convert +// the source image to the desired palette of the GIF-encoded image. +func GIFDrawer(drawer draw.Drawer) EncodeOption { + return func(c *encodeConfig) { + c.gifDrawer = drawer + } +} + +// PNGCompressionLevel returns an EncodeOption that sets the compression level +// of the PNG-encoded image. Default is png.DefaultCompression. +func PNGCompressionLevel(level png.CompressionLevel) EncodeOption { + return func(c *encodeConfig) { + c.pngCompressionLevel = level + } +} + +// Encode writes the image img to w in the specified format (JPEG, PNG, GIF, TIFF or BMP). +func Encode(w io.Writer, img image.Image, format Format, opts ...EncodeOption) error { + cfg := defaultEncodeConfig + for _, option := range opts { + option(&cfg) + } + + switch format { + case JPEG: + if nrgba, ok := img.(*image.NRGBA); ok && nrgba.Opaque() { + rgba := &image.RGBA{ + Pix: nrgba.Pix, + Stride: nrgba.Stride, + Rect: nrgba.Rect, + } + return jpeg.Encode(w, rgba, &jpeg.Options{Quality: cfg.jpegQuality}) + } + return jpeg.Encode(w, img, &jpeg.Options{Quality: cfg.jpegQuality}) + + case PNG: + encoder := png.Encoder{CompressionLevel: cfg.pngCompressionLevel} + return encoder.Encode(w, img) + + case GIF: + return gif.Encode(w, img, &gif.Options{ + NumColors: cfg.gifNumColors, + Quantizer: cfg.gifQuantizer, + Drawer: cfg.gifDrawer, + }) + + case TIFF: + return tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true}) + + case BMP: + return bmp.Encode(w, img) + } + + return ErrUnsupportedFormat +} + +// Save saves the image to file with the specified filename. +// The format is determined from the filename extension: +// "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported. +// +// Examples: +// +// // Save the image as PNG. +// err := imaging.Save(img, "out.png") +// +// // Save the image as JPEG with optional quality parameter set to 80. +// err := imaging.Save(img, "out.jpg", imaging.JPEGQuality(80)) +// +func Save(img image.Image, filename string, opts ...EncodeOption) (err error) { + f, err := FormatFromFilename(filename) + if err != nil { + return err + } + file, err := fs.Create(filename) + if err != nil { + return err + } + err = Encode(file, img, f, opts...) + errc := file.Close() + if err == nil { + err = errc + } + return err +} + +// orientation is an EXIF flag that specifies the transformation +// that should be applied to image to display it correctly. +type orientation int + +const ( + orientationUnspecified = 0 + orientationNormal = 1 + orientationFlipH = 2 + orientationRotate180 = 3 + orientationFlipV = 4 + orientationTranspose = 5 + orientationRotate270 = 6 + orientationTransverse = 7 + orientationRotate90 = 8 +) + +// readOrientation tries to read the orientation EXIF flag from image data in r. +// If the EXIF data block is not found or the orientation flag is not found +// or any other error occures while reading the data, it returns the +// orientationUnspecified (0) value. +func readOrientation(r io.Reader) orientation { + const ( + markerSOI = 0xffd8 + markerAPP1 = 0xffe1 + exifHeader = 0x45786966 + byteOrderBE = 0x4d4d + byteOrderLE = 0x4949 + orientationTag = 0x0112 + ) + + // Check if JPEG SOI marker is present. + var soi uint16 + if err := binary.Read(r, binary.BigEndian, &soi); err != nil { + return orientationUnspecified + } + if soi != markerSOI { + return orientationUnspecified // Missing JPEG SOI marker. + } + + // Find JPEG APP1 marker. + for { + var marker, size uint16 + if err := binary.Read(r, binary.BigEndian, &marker); err != nil { + return orientationUnspecified + } + if err := binary.Read(r, binary.BigEndian, &size); err != nil { + return orientationUnspecified + } + if marker>>8 != 0xff { + return orientationUnspecified // Invalid JPEG marker. + } + if marker == markerAPP1 { + break + } + if size < 2 { + return orientationUnspecified // Invalid block size. + } + if _, err := io.CopyN(ioutil.Discard, r, int64(size-2)); err != nil { + return orientationUnspecified + } + } + + // Check if EXIF header is present. + var header uint32 + if err := binary.Read(r, binary.BigEndian, &header); err != nil { + return orientationUnspecified + } + if header != exifHeader { + return orientationUnspecified + } + if _, err := io.CopyN(ioutil.Discard, r, 2); err != nil { + return orientationUnspecified + } + + // Read byte order information. + var ( + byteOrderTag uint16 + byteOrder binary.ByteOrder + ) + if err := binary.Read(r, binary.BigEndian, &byteOrderTag); err != nil { + return orientationUnspecified + } + switch byteOrderTag { + case byteOrderBE: + byteOrder = binary.BigEndian + case byteOrderLE: + byteOrder = binary.LittleEndian + default: + return orientationUnspecified // Invalid byte order flag. + } + if _, err := io.CopyN(ioutil.Discard, r, 2); err != nil { + return orientationUnspecified + } + + // Skip the EXIF offset. + var offset uint32 + if err := binary.Read(r, byteOrder, &offset); err != nil { + return orientationUnspecified + } + if offset < 8 { + return orientationUnspecified // Invalid offset value. + } + if _, err := io.CopyN(ioutil.Discard, r, int64(offset-8)); err != nil { + return orientationUnspecified + } + + // Read the number of tags. + var numTags uint16 + if err := binary.Read(r, byteOrder, &numTags); err != nil { + return orientationUnspecified + } + + // Find the orientation tag. + for i := 0; i < int(numTags); i++ { + var tag uint16 + if err := binary.Read(r, byteOrder, &tag); err != nil { + return orientationUnspecified + } + if tag != orientationTag { + if _, err := io.CopyN(ioutil.Discard, r, 10); err != nil { + return orientationUnspecified + } + continue + } + if _, err := io.CopyN(ioutil.Discard, r, 6); err != nil { + return orientationUnspecified + } + var val uint16 + if err := binary.Read(r, byteOrder, &val); err != nil { + return orientationUnspecified + } + if val < 1 || val > 8 { + return orientationUnspecified // Invalid tag value. + } + return orientation(val) + } + return orientationUnspecified // Missing orientation tag. +} + +// fixOrientation applies a transform to img corresponding to the given orientation flag. +func fixOrientation(img image.Image, o orientation) image.Image { + switch o { + case orientationNormal: + case orientationFlipH: + img = FlipH(img) + case orientationFlipV: + img = FlipV(img) + case orientationRotate90: + img = Rotate90(img) + case orientationRotate180: + img = Rotate180(img) + case orientationRotate270: + img = Rotate270(img) + case orientationTranspose: + img = Transpose(img) + case orientationTransverse: + img = Transverse(img) + } + return img +} diff --git a/vendor/github.com/disintegration/imaging/resize.go b/vendor/github.com/disintegration/imaging/resize.go new file mode 100644 index 000000000..706435e3d --- /dev/null +++ b/vendor/github.com/disintegration/imaging/resize.go @@ -0,0 +1,595 @@ +package imaging + +import ( + "image" + "math" +) + +type indexWeight struct { + index int + weight float64 +} + +func precomputeWeights(dstSize, srcSize int, filter ResampleFilter) [][]indexWeight { + du := float64(srcSize) / float64(dstSize) + scale := du + if scale < 1.0 { + scale = 1.0 + } + ru := math.Ceil(scale * filter.Support) + + out := make([][]indexWeight, dstSize) + tmp := make([]indexWeight, 0, dstSize*int(ru+2)*2) + + for v := 0; v < dstSize; v++ { + fu := (float64(v)+0.5)*du - 0.5 + + begin := int(math.Ceil(fu - ru)) + if begin < 0 { + begin = 0 + } + end := int(math.Floor(fu + ru)) + if end > srcSize-1 { + end = srcSize - 1 + } + + var sum float64 + for u := begin; u <= end; u++ { + w := filter.Kernel((float64(u) - fu) / scale) + if w != 0 { + sum += w + tmp = append(tmp, indexWeight{index: u, weight: w}) + } + } + if sum != 0 { + for i := range tmp { + tmp[i].weight /= sum + } + } + + out[v] = tmp + tmp = tmp[len(tmp):] + } + + return out +} + +// Resize resizes the image to the specified width and height using the specified resampling +// filter and returns the transformed image. If one of width or height is 0, the image aspect +// ratio is preserved. +// +// Example: +// +// dstImage := imaging.Resize(srcImage, 800, 600, imaging.Lanczos) +// +func Resize(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA { + dstW, dstH := width, height + if dstW < 0 || dstH < 0 { + return &image.NRGBA{} + } + if dstW == 0 && dstH == 0 { + return &image.NRGBA{} + } + + srcW := img.Bounds().Dx() + srcH := img.Bounds().Dy() + if srcW <= 0 || srcH <= 0 { + return &image.NRGBA{} + } + + // If new width or height is 0 then preserve aspect ratio, minimum 1px. + if dstW == 0 { + tmpW := float64(dstH) * float64(srcW) / float64(srcH) + dstW = int(math.Max(1.0, math.Floor(tmpW+0.5))) + } + if dstH == 0 { + tmpH := float64(dstW) * float64(srcH) / float64(srcW) + dstH = int(math.Max(1.0, math.Floor(tmpH+0.5))) + } + + if filter.Support <= 0 { + // Nearest-neighbor special case. + return resizeNearest(img, dstW, dstH) + } + + if srcW != dstW && srcH != dstH { + return resizeVertical(resizeHorizontal(img, dstW, filter), dstH, filter) + } + if srcW != dstW { + return resizeHorizontal(img, dstW, filter) + } + if srcH != dstH { + return resizeVertical(img, dstH, filter) + } + return Clone(img) +} + +func resizeHorizontal(img image.Image, width int, filter ResampleFilter) *image.NRGBA { + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, width, src.h)) + weights := precomputeWeights(width, src.w, filter) + parallel(0, src.h, func(ys <-chan int) { + scanLine := make([]uint8, src.w*4) + for y := range ys { + src.scan(0, y, src.w, y+1, scanLine) + j0 := y * dst.Stride + for x := range weights { + var r, g, b, a float64 + for _, w := range weights[x] { + i := w.index * 4 + s := scanLine[i : i+4 : i+4] + aw := float64(s[3]) * w.weight + r += float64(s[0]) * aw + g += float64(s[1]) * aw + b += float64(s[2]) * aw + a += aw + } + if a != 0 { + aInv := 1 / a + j := j0 + x*4 + d := dst.Pix[j : j+4 : j+4] + d[0] = clamp(r * aInv) + d[1] = clamp(g * aInv) + d[2] = clamp(b * aInv) + d[3] = clamp(a) + } + } + } + }) + return dst +} + +func resizeVertical(img image.Image, height int, filter ResampleFilter) *image.NRGBA { + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, height)) + weights := precomputeWeights(height, src.h, filter) + parallel(0, src.w, func(xs <-chan int) { + scanLine := make([]uint8, src.h*4) + for x := range xs { + src.scan(x, 0, x+1, src.h, scanLine) + for y := range weights { + var r, g, b, a float64 + for _, w := range weights[y] { + i := w.index * 4 + s := scanLine[i : i+4 : i+4] + aw := float64(s[3]) * w.weight + r += float64(s[0]) * aw + g += float64(s[1]) * aw + b += float64(s[2]) * aw + a += aw + } + if a != 0 { + aInv := 1 / a + j := y*dst.Stride + x*4 + d := dst.Pix[j : j+4 : j+4] + d[0] = clamp(r * aInv) + d[1] = clamp(g * aInv) + d[2] = clamp(b * aInv) + d[3] = clamp(a) + } + } + } + }) + return dst +} + +// resizeNearest is a fast nearest-neighbor resize, no filtering. +func resizeNearest(img image.Image, width, height int) *image.NRGBA { + dst := image.NewNRGBA(image.Rect(0, 0, width, height)) + dx := float64(img.Bounds().Dx()) / float64(width) + dy := float64(img.Bounds().Dy()) / float64(height) + + if dx > 1 && dy > 1 { + src := newScanner(img) + parallel(0, height, func(ys <-chan int) { + for y := range ys { + srcY := int((float64(y) + 0.5) * dy) + dstOff := y * dst.Stride + for x := 0; x < width; x++ { + srcX := int((float64(x) + 0.5) * dx) + src.scan(srcX, srcY, srcX+1, srcY+1, dst.Pix[dstOff:dstOff+4]) + dstOff += 4 + } + } + }) + } else { + src := toNRGBA(img) + parallel(0, height, func(ys <-chan int) { + for y := range ys { + srcY := int((float64(y) + 0.5) * dy) + srcOff0 := srcY * src.Stride + dstOff := y * dst.Stride + for x := 0; x < width; x++ { + srcX := int((float64(x) + 0.5) * dx) + srcOff := srcOff0 + srcX*4 + copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4]) + dstOff += 4 + } + } + }) + } + + return dst +} + +// Fit scales down the image using the specified resample filter to fit the specified +// maximum width and height and returns the transformed image. +// +// Example: +// +// dstImage := imaging.Fit(srcImage, 800, 600, imaging.Lanczos) +// +func Fit(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA { + maxW, maxH := width, height + + if maxW <= 0 || maxH <= 0 { + return &image.NRGBA{} + } + + srcBounds := img.Bounds() + srcW := srcBounds.Dx() + srcH := srcBounds.Dy() + + if srcW <= 0 || srcH <= 0 { + return &image.NRGBA{} + } + + if srcW <= maxW && srcH <= maxH { + return Clone(img) + } + + srcAspectRatio := float64(srcW) / float64(srcH) + maxAspectRatio := float64(maxW) / float64(maxH) + + var newW, newH int + if srcAspectRatio > maxAspectRatio { + newW = maxW + newH = int(float64(newW) / srcAspectRatio) + } else { + newH = maxH + newW = int(float64(newH) * srcAspectRatio) + } + + return Resize(img, newW, newH, filter) +} + +// Fill creates an image with the specified dimensions and fills it with the scaled source image. +// To achieve the correct aspect ratio without stretching, the source image will be cropped. +// +// Example: +// +// dstImage := imaging.Fill(srcImage, 800, 600, imaging.Center, imaging.Lanczos) +// +func Fill(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA { + dstW, dstH := width, height + + if dstW <= 0 || dstH <= 0 { + return &image.NRGBA{} + } + + srcBounds := img.Bounds() + srcW := srcBounds.Dx() + srcH := srcBounds.Dy() + + if srcW <= 0 || srcH <= 0 { + return &image.NRGBA{} + } + + if srcW == dstW && srcH == dstH { + return Clone(img) + } + + if srcW >= 100 && srcH >= 100 { + return cropAndResize(img, dstW, dstH, anchor, filter) + } + return resizeAndCrop(img, dstW, dstH, anchor, filter) +} + +// cropAndResize crops the image to the smallest possible size that has the required aspect ratio using +// the given anchor point, then scales it to the specified dimensions and returns the transformed image. +// +// This is generally faster than resizing first, but may result in inaccuracies when used on small source images. +func cropAndResize(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA { + dstW, dstH := width, height + + srcBounds := img.Bounds() + srcW := srcBounds.Dx() + srcH := srcBounds.Dy() + srcAspectRatio := float64(srcW) / float64(srcH) + dstAspectRatio := float64(dstW) / float64(dstH) + + var tmp *image.NRGBA + if srcAspectRatio < dstAspectRatio { + cropH := float64(srcW) * float64(dstH) / float64(dstW) + tmp = CropAnchor(img, srcW, int(math.Max(1, cropH)+0.5), anchor) + } else { + cropW := float64(srcH) * float64(dstW) / float64(dstH) + tmp = CropAnchor(img, int(math.Max(1, cropW)+0.5), srcH, anchor) + } + + return Resize(tmp, dstW, dstH, filter) +} + +// resizeAndCrop resizes the image to the smallest possible size that will cover the specified dimensions, +// crops the resized image to the specified dimensions using the given anchor point and returns +// the transformed image. +func resizeAndCrop(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA { + dstW, dstH := width, height + + srcBounds := img.Bounds() + srcW := srcBounds.Dx() + srcH := srcBounds.Dy() + srcAspectRatio := float64(srcW) / float64(srcH) + dstAspectRatio := float64(dstW) / float64(dstH) + + var tmp *image.NRGBA + if srcAspectRatio < dstAspectRatio { + tmp = Resize(img, dstW, 0, filter) + } else { + tmp = Resize(img, 0, dstH, filter) + } + + return CropAnchor(tmp, dstW, dstH, anchor) +} + +// Thumbnail scales the image up or down using the specified resample filter, crops it +// to the specified width and hight and returns the transformed image. +// +// Example: +// +// dstImage := imaging.Thumbnail(srcImage, 100, 100, imaging.Lanczos) +// +func Thumbnail(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA { + return Fill(img, width, height, Center, filter) +} + +// ResampleFilter specifies a resampling filter to be used for image resizing. +// +// General filter recommendations: +// +// - Lanczos +// A high-quality resampling filter for photographic images yielding sharp results. +// +// - CatmullRom +// A sharp cubic filter that is faster than Lanczos filter while providing similar results. +// +// - MitchellNetravali +// A cubic filter that produces smoother results with less ringing artifacts than CatmullRom. +// +// - Linear +// Bilinear resampling filter, produces a smooth output. Faster than cubic filters. +// +// - Box +// Simple and fast averaging filter appropriate for downscaling. +// When upscaling it's similar to NearestNeighbor. +// +// - NearestNeighbor +// Fastest resampling filter, no antialiasing. +// +type ResampleFilter struct { + Support float64 + Kernel func(float64) float64 +} + +// NearestNeighbor is a nearest-neighbor filter (no anti-aliasing). +var NearestNeighbor ResampleFilter + +// Box filter (averaging pixels). +var Box ResampleFilter + +// Linear filter. +var Linear ResampleFilter + +// Hermite cubic spline filter (BC-spline; B=0; C=0). +var Hermite ResampleFilter + +// MitchellNetravali is Mitchell-Netravali cubic filter (BC-spline; B=1/3; C=1/3). +var MitchellNetravali ResampleFilter + +// CatmullRom is a Catmull-Rom - sharp cubic filter (BC-spline; B=0; C=0.5). +var CatmullRom ResampleFilter + +// BSpline is a smooth cubic filter (BC-spline; B=1; C=0). +var BSpline ResampleFilter + +// Gaussian is a Gaussian blurring filter. +var Gaussian ResampleFilter + +// Bartlett is a Bartlett-windowed sinc filter (3 lobes). +var Bartlett ResampleFilter + +// Lanczos filter (3 lobes). +var Lanczos ResampleFilter + +// Hann is a Hann-windowed sinc filter (3 lobes). +var Hann ResampleFilter + +// Hamming is a Hamming-windowed sinc filter (3 lobes). +var Hamming ResampleFilter + +// Blackman is a Blackman-windowed sinc filter (3 lobes). +var Blackman ResampleFilter + +// Welch is a Welch-windowed sinc filter (parabolic window, 3 lobes). +var Welch ResampleFilter + +// Cosine is a Cosine-windowed sinc filter (3 lobes). +var Cosine ResampleFilter + +func bcspline(x, b, c float64) float64 { + var y float64 + x = math.Abs(x) + if x < 1.0 { + y = ((12-9*b-6*c)*x*x*x + (-18+12*b+6*c)*x*x + (6 - 2*b)) / 6 + } else if x < 2.0 { + y = ((-b-6*c)*x*x*x + (6*b+30*c)*x*x + (-12*b-48*c)*x + (8*b + 24*c)) / 6 + } + return y +} + +func sinc(x float64) float64 { + if x == 0 { + return 1 + } + return math.Sin(math.Pi*x) / (math.Pi * x) +} + +func init() { + NearestNeighbor = ResampleFilter{ + Support: 0.0, // special case - not applying the filter + } + + Box = ResampleFilter{ + Support: 0.5, + Kernel: func(x float64) float64 { + x = math.Abs(x) + if x <= 0.5 { + return 1.0 + } + return 0 + }, + } + + Linear = ResampleFilter{ + Support: 1.0, + Kernel: func(x float64) float64 { + x = math.Abs(x) + if x < 1.0 { + return 1.0 - x + } + return 0 + }, + } + + Hermite = ResampleFilter{ + Support: 1.0, + Kernel: func(x float64) float64 { + x = math.Abs(x) + if x < 1.0 { + return bcspline(x, 0.0, 0.0) + } + return 0 + }, + } + + MitchellNetravali = ResampleFilter{ + Support: 2.0, + Kernel: func(x float64) float64 { + x = math.Abs(x) + if x < 2.0 { + return bcspline(x, 1.0/3.0, 1.0/3.0) + } + return 0 + }, + } + + CatmullRom = ResampleFilter{ + Support: 2.0, + Kernel: func(x float64) float64 { + x = math.Abs(x) + if x < 2.0 { + return bcspline(x, 0.0, 0.5) + } + return 0 + }, + } + + BSpline = ResampleFilter{ + Support: 2.0, + Kernel: func(x float64) float64 { + x = math.Abs(x) + if x < 2.0 { + return bcspline(x, 1.0, 0.0) + } + return 0 + }, + } + + Gaussian = ResampleFilter{ + Support: 2.0, + Kernel: func(x float64) float64 { + x = math.Abs(x) + if x < 2.0 { + return math.Exp(-2 * x * x) + } + return 0 + }, + } + + Bartlett = ResampleFilter{ + Support: 3.0, + Kernel: func(x float64) float64 { + x = math.Abs(x) + if x < 3.0 { + return sinc(x) * (3.0 - x) / 3.0 + } + return 0 + }, + } + + Lanczos = ResampleFilter{ + Support: 3.0, + Kernel: func(x float64) float64 { + x = math.Abs(x) + if x < 3.0 { + return sinc(x) * sinc(x/3.0) + } + return 0 + }, + } + + Hann = ResampleFilter{ + Support: 3.0, + Kernel: func(x float64) float64 { + x = math.Abs(x) + if x < 3.0 { + return sinc(x) * (0.5 + 0.5*math.Cos(math.Pi*x/3.0)) + } + return 0 + }, + } + + Hamming = ResampleFilter{ + Support: 3.0, + Kernel: func(x float64) float64 { + x = math.Abs(x) + if x < 3.0 { + return sinc(x) * (0.54 + 0.46*math.Cos(math.Pi*x/3.0)) + } + return 0 + }, + } + + Blackman = ResampleFilter{ + Support: 3.0, + Kernel: func(x float64) float64 { + x = math.Abs(x) + if x < 3.0 { + return sinc(x) * (0.42 - 0.5*math.Cos(math.Pi*x/3.0+math.Pi) + 0.08*math.Cos(2.0*math.Pi*x/3.0)) + } + return 0 + }, + } + + Welch = ResampleFilter{ + Support: 3.0, + Kernel: func(x float64) float64 { + x = math.Abs(x) + if x < 3.0 { + return sinc(x) * (1.0 - (x * x / 9.0)) + } + return 0 + }, + } + + Cosine = ResampleFilter{ + Support: 3.0, + Kernel: func(x float64) float64 { + x = math.Abs(x) + if x < 3.0 { + return sinc(x) * math.Cos((math.Pi/2.0)*(x/3.0)) + } + return 0 + }, + } +} diff --git a/vendor/github.com/disintegration/imaging/scanner.go b/vendor/github.com/disintegration/imaging/scanner.go new file mode 100644 index 000000000..37d92cef8 --- /dev/null +++ b/vendor/github.com/disintegration/imaging/scanner.go @@ -0,0 +1,285 @@ +package imaging + +import ( + "image" + "image/color" +) + +type scanner struct { + image image.Image + w, h int + palette []color.NRGBA +} + +func newScanner(img image.Image) *scanner { + s := &scanner{ + image: img, + w: img.Bounds().Dx(), + h: img.Bounds().Dy(), + } + if img, ok := img.(*image.Paletted); ok { + s.palette = make([]color.NRGBA, len(img.Palette)) + for i := 0; i < len(img.Palette); i++ { + s.palette[i] = color.NRGBAModel.Convert(img.Palette[i]).(color.NRGBA) + } + } + return s +} + +// scan scans the given rectangular region of the image into dst. +func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) { + switch img := s.image.(type) { + case *image.NRGBA: + size := (x2 - x1) * 4 + j := 0 + i := y1*img.Stride + x1*4 + if size == 4 { + for y := y1; y < y2; y++ { + d := dst[j : j+4 : j+4] + s := img.Pix[i : i+4 : i+4] + d[0] = s[0] + d[1] = s[1] + d[2] = s[2] + d[3] = s[3] + j += size + i += img.Stride + } + } else { + for y := y1; y < y2; y++ { + copy(dst[j:j+size], img.Pix[i:i+size]) + j += size + i += img.Stride + } + } + + case *image.NRGBA64: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1*8 + for x := x1; x < x2; x++ { + s := img.Pix[i : i+8 : i+8] + d := dst[j : j+4 : j+4] + d[0] = s[0] + d[1] = s[2] + d[2] = s[4] + d[3] = s[6] + j += 4 + i += 8 + } + } + + case *image.RGBA: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1*4 + for x := x1; x < x2; x++ { + d := dst[j : j+4 : j+4] + a := img.Pix[i+3] + switch a { + case 0: + d[0] = 0 + d[1] = 0 + d[2] = 0 + d[3] = a + case 0xff: + s := img.Pix[i : i+4 : i+4] + d[0] = s[0] + d[1] = s[1] + d[2] = s[2] + d[3] = a + default: + s := img.Pix[i : i+4 : i+4] + r16 := uint16(s[0]) + g16 := uint16(s[1]) + b16 := uint16(s[2]) + a16 := uint16(a) + d[0] = uint8(r16 * 0xff / a16) + d[1] = uint8(g16 * 0xff / a16) + d[2] = uint8(b16 * 0xff / a16) + d[3] = a + } + j += 4 + i += 4 + } + } + + case *image.RGBA64: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1*8 + for x := x1; x < x2; x++ { + s := img.Pix[i : i+8 : i+8] + d := dst[j : j+4 : j+4] + a := s[6] + switch a { + case 0: + d[0] = 0 + d[1] = 0 + d[2] = 0 + case 0xff: + d[0] = s[0] + d[1] = s[2] + d[2] = s[4] + default: + r32 := uint32(s[0])<<8 | uint32(s[1]) + g32 := uint32(s[2])<<8 | uint32(s[3]) + b32 := uint32(s[4])<<8 | uint32(s[5]) + a32 := uint32(s[6])<<8 | uint32(s[7]) + d[0] = uint8((r32 * 0xffff / a32) >> 8) + d[1] = uint8((g32 * 0xffff / a32) >> 8) + d[2] = uint8((b32 * 0xffff / a32) >> 8) + } + d[3] = a + j += 4 + i += 8 + } + } + + case *image.Gray: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1 + for x := x1; x < x2; x++ { + c := img.Pix[i] + d := dst[j : j+4 : j+4] + d[0] = c + d[1] = c + d[2] = c + d[3] = 0xff + j += 4 + i++ + } + } + + case *image.Gray16: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1*2 + for x := x1; x < x2; x++ { + c := img.Pix[i] + d := dst[j : j+4 : j+4] + d[0] = c + d[1] = c + d[2] = c + d[3] = 0xff + j += 4 + i += 2 + } + } + + case *image.YCbCr: + j := 0 + x1 += img.Rect.Min.X + x2 += img.Rect.Min.X + y1 += img.Rect.Min.Y + y2 += img.Rect.Min.Y + + hy := img.Rect.Min.Y / 2 + hx := img.Rect.Min.X / 2 + for y := y1; y < y2; y++ { + iy := (y-img.Rect.Min.Y)*img.YStride + (x1 - img.Rect.Min.X) + + var yBase int + switch img.SubsampleRatio { + case image.YCbCrSubsampleRatio444, image.YCbCrSubsampleRatio422: + yBase = (y - img.Rect.Min.Y) * img.CStride + case image.YCbCrSubsampleRatio420, image.YCbCrSubsampleRatio440: + yBase = (y/2 - hy) * img.CStride + } + + for x := x1; x < x2; x++ { + var ic int + switch img.SubsampleRatio { + case image.YCbCrSubsampleRatio444, image.YCbCrSubsampleRatio440: + ic = yBase + (x - img.Rect.Min.X) + case image.YCbCrSubsampleRatio422, image.YCbCrSubsampleRatio420: + ic = yBase + (x/2 - hx) + default: + ic = img.COffset(x, y) + } + + yy1 := int32(img.Y[iy]) * 0x10101 + cb1 := int32(img.Cb[ic]) - 128 + cr1 := int32(img.Cr[ic]) - 128 + + r := yy1 + 91881*cr1 + if uint32(r)&0xff000000 == 0 { + r >>= 16 + } else { + r = ^(r >> 31) + } + + g := yy1 - 22554*cb1 - 46802*cr1 + if uint32(g)&0xff000000 == 0 { + g >>= 16 + } else { + g = ^(g >> 31) + } + + b := yy1 + 116130*cb1 + if uint32(b)&0xff000000 == 0 { + b >>= 16 + } else { + b = ^(b >> 31) + } + + d := dst[j : j+4 : j+4] + d[0] = uint8(r) + d[1] = uint8(g) + d[2] = uint8(b) + d[3] = 0xff + + iy++ + j += 4 + } + } + + case *image.Paletted: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1 + for x := x1; x < x2; x++ { + c := s.palette[img.Pix[i]] + d := dst[j : j+4 : j+4] + d[0] = c.R + d[1] = c.G + d[2] = c.B + d[3] = c.A + j += 4 + i++ + } + } + + default: + j := 0 + b := s.image.Bounds() + x1 += b.Min.X + x2 += b.Min.X + y1 += b.Min.Y + y2 += b.Min.Y + for y := y1; y < y2; y++ { + for x := x1; x < x2; x++ { + r16, g16, b16, a16 := s.image.At(x, y).RGBA() + d := dst[j : j+4 : j+4] + switch a16 { + case 0xffff: + d[0] = uint8(r16 >> 8) + d[1] = uint8(g16 >> 8) + d[2] = uint8(b16 >> 8) + d[3] = 0xff + case 0: + d[0] = 0 + d[1] = 0 + d[2] = 0 + d[3] = 0 + default: + d[0] = uint8(((r16 * 0xffff) / a16) >> 8) + d[1] = uint8(((g16 * 0xffff) / a16) >> 8) + d[2] = uint8(((b16 * 0xffff) / a16) >> 8) + d[3] = uint8(a16 >> 8) + } + j += 4 + } + } + } +} diff --git a/vendor/github.com/disintegration/imaging/tools.go b/vendor/github.com/disintegration/imaging/tools.go new file mode 100644 index 000000000..0ec19a039 --- /dev/null +++ b/vendor/github.com/disintegration/imaging/tools.go @@ -0,0 +1,249 @@ +package imaging + +import ( + "bytes" + "image" + "image/color" + "math" +) + +// New creates a new image with the specified width and height, and fills it with the specified color. +func New(width, height int, fillColor color.Color) *image.NRGBA { + if width <= 0 || height <= 0 { + return &image.NRGBA{} + } + + c := color.NRGBAModel.Convert(fillColor).(color.NRGBA) + if (c == color.NRGBA{0, 0, 0, 0}) { + return image.NewNRGBA(image.Rect(0, 0, width, height)) + } + + return &image.NRGBA{ + Pix: bytes.Repeat([]byte{c.R, c.G, c.B, c.A}, width*height), + Stride: 4 * width, + Rect: image.Rect(0, 0, width, height), + } +} + +// Clone returns a copy of the given image. +func Clone(img image.Image) *image.NRGBA { + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h)) + size := src.w * 4 + parallel(0, src.h, func(ys <-chan int) { + for y := range ys { + i := y * dst.Stride + src.scan(0, y, src.w, y+1, dst.Pix[i:i+size]) + } + }) + return dst +} + +// Anchor is the anchor point for image alignment. +type Anchor int + +// Anchor point positions. +const ( + Center Anchor = iota + TopLeft + Top + TopRight + Left + Right + BottomLeft + Bottom + BottomRight +) + +func anchorPt(b image.Rectangle, w, h int, anchor Anchor) image.Point { + var x, y int + switch anchor { + case TopLeft: + x = b.Min.X + y = b.Min.Y + case Top: + x = b.Min.X + (b.Dx()-w)/2 + y = b.Min.Y + case TopRight: + x = b.Max.X - w + y = b.Min.Y + case Left: + x = b.Min.X + y = b.Min.Y + (b.Dy()-h)/2 + case Right: + x = b.Max.X - w + y = b.Min.Y + (b.Dy()-h)/2 + case BottomLeft: + x = b.Min.X + y = b.Max.Y - h + case Bottom: + x = b.Min.X + (b.Dx()-w)/2 + y = b.Max.Y - h + case BottomRight: + x = b.Max.X - w + y = b.Max.Y - h + default: + x = b.Min.X + (b.Dx()-w)/2 + y = b.Min.Y + (b.Dy()-h)/2 + } + return image.Pt(x, y) +} + +// Crop cuts out a rectangular region with the specified bounds +// from the image and returns the cropped image. +func Crop(img image.Image, rect image.Rectangle) *image.NRGBA { + r := rect.Intersect(img.Bounds()).Sub(img.Bounds().Min) + if r.Empty() { + return &image.NRGBA{} + } + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, r.Dx(), r.Dy())) + rowSize := r.Dx() * 4 + parallel(r.Min.Y, r.Max.Y, func(ys <-chan int) { + for y := range ys { + i := (y - r.Min.Y) * dst.Stride + src.scan(r.Min.X, y, r.Max.X, y+1, dst.Pix[i:i+rowSize]) + } + }) + return dst +} + +// CropAnchor cuts out a rectangular region with the specified size +// from the image using the specified anchor point and returns the cropped image. +func CropAnchor(img image.Image, width, height int, anchor Anchor) *image.NRGBA { + srcBounds := img.Bounds() + pt := anchorPt(srcBounds, width, height, anchor) + r := image.Rect(0, 0, width, height).Add(pt) + b := srcBounds.Intersect(r) + return Crop(img, b) +} + +// CropCenter cuts out a rectangular region with the specified size +// from the center of the image and returns the cropped image. +func CropCenter(img image.Image, width, height int) *image.NRGBA { + return CropAnchor(img, width, height, Center) +} + +// Paste pastes the img image to the background image at the specified position and returns the combined image. +func Paste(background, img image.Image, pos image.Point) *image.NRGBA { + dst := Clone(background) + pos = pos.Sub(background.Bounds().Min) + pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())} + interRect := pasteRect.Intersect(dst.Bounds()) + if interRect.Empty() { + return dst + } + src := newScanner(img) + parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) { + for y := range ys { + x1 := interRect.Min.X - pasteRect.Min.X + x2 := interRect.Max.X - pasteRect.Min.X + y1 := y - pasteRect.Min.Y + y2 := y1 + 1 + i1 := y*dst.Stride + interRect.Min.X*4 + i2 := i1 + interRect.Dx()*4 + src.scan(x1, y1, x2, y2, dst.Pix[i1:i2]) + } + }) + return dst +} + +// PasteCenter pastes the img image to the center of the background image and returns the combined image. +func PasteCenter(background, img image.Image) *image.NRGBA { + bgBounds := background.Bounds() + bgW := bgBounds.Dx() + bgH := bgBounds.Dy() + bgMinX := bgBounds.Min.X + bgMinY := bgBounds.Min.Y + + centerX := bgMinX + bgW/2 + centerY := bgMinY + bgH/2 + + x0 := centerX - img.Bounds().Dx()/2 + y0 := centerY - img.Bounds().Dy()/2 + + return Paste(background, img, image.Pt(x0, y0)) +} + +// Overlay draws the img image over the background image at given position +// and returns the combined image. Opacity parameter is the opacity of the img +// image layer, used to compose the images, it must be from 0.0 to 1.0. +// +// Examples: +// +// // Draw spriteImage over backgroundImage at the given position (x=50, y=50). +// dstImage := imaging.Overlay(backgroundImage, spriteImage, image.Pt(50, 50), 1.0) +// +// // Blend two opaque images of the same size. +// dstImage := imaging.Overlay(imageOne, imageTwo, image.Pt(0, 0), 0.5) +// +func Overlay(background, img image.Image, pos image.Point, opacity float64) *image.NRGBA { + opacity = math.Min(math.Max(opacity, 0.0), 1.0) // Ensure 0.0 <= opacity <= 1.0. + dst := Clone(background) + pos = pos.Sub(background.Bounds().Min) + pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())} + interRect := pasteRect.Intersect(dst.Bounds()) + if interRect.Empty() { + return dst + } + src := newScanner(img) + parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) { + scanLine := make([]uint8, interRect.Dx()*4) + for y := range ys { + x1 := interRect.Min.X - pasteRect.Min.X + x2 := interRect.Max.X - pasteRect.Min.X + y1 := y - pasteRect.Min.Y + y2 := y1 + 1 + src.scan(x1, y1, x2, y2, scanLine) + i := y*dst.Stride + interRect.Min.X*4 + j := 0 + for x := interRect.Min.X; x < interRect.Max.X; x++ { + d := dst.Pix[i : i+4 : i+4] + r1 := float64(d[0]) + g1 := float64(d[1]) + b1 := float64(d[2]) + a1 := float64(d[3]) + + s := scanLine[j : j+4 : j+4] + r2 := float64(s[0]) + g2 := float64(s[1]) + b2 := float64(s[2]) + a2 := float64(s[3]) + + coef2 := opacity * a2 / 255 + coef1 := (1 - coef2) * a1 / 255 + coefSum := coef1 + coef2 + coef1 /= coefSum + coef2 /= coefSum + + d[0] = uint8(r1*coef1 + r2*coef2) + d[1] = uint8(g1*coef1 + g2*coef2) + d[2] = uint8(b1*coef1 + b2*coef2) + d[3] = uint8(math.Min(a1+a2*opacity*(255-a1)/255, 255)) + + i += 4 + j += 4 + } + } + }) + return dst +} + +// OverlayCenter overlays the img image to the center of the background image and +// returns the combined image. Opacity parameter is the opacity of the img +// image layer, used to compose the images, it must be from 0.0 to 1.0. +func OverlayCenter(background, img image.Image, opacity float64) *image.NRGBA { + bgBounds := background.Bounds() + bgW := bgBounds.Dx() + bgH := bgBounds.Dy() + bgMinX := bgBounds.Min.X + bgMinY := bgBounds.Min.Y + + centerX := bgMinX + bgW/2 + centerY := bgMinY + bgH/2 + + x0 := centerX - img.Bounds().Dx()/2 + y0 := centerY - img.Bounds().Dy()/2 + + return Overlay(background, img, image.Point{x0, y0}, opacity) +} diff --git a/vendor/github.com/disintegration/imaging/transform.go b/vendor/github.com/disintegration/imaging/transform.go new file mode 100644 index 000000000..fe4a92f9d --- /dev/null +++ b/vendor/github.com/disintegration/imaging/transform.go @@ -0,0 +1,268 @@ +package imaging + +import ( + "image" + "image/color" + "math" +) + +// FlipH flips the image horizontally (from left to right) and returns the transformed image. +func FlipH(img image.Image) *image.NRGBA { + src := newScanner(img) + dstW := src.w + dstH := src.h + rowSize := dstW * 4 + dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) + parallel(0, dstH, func(ys <-chan int) { + for dstY := range ys { + i := dstY * dst.Stride + srcY := dstY + src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize]) + reverse(dst.Pix[i : i+rowSize]) + } + }) + return dst +} + +// FlipV flips the image vertically (from top to bottom) and returns the transformed image. +func FlipV(img image.Image) *image.NRGBA { + src := newScanner(img) + dstW := src.w + dstH := src.h + rowSize := dstW * 4 + dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) + parallel(0, dstH, func(ys <-chan int) { + for dstY := range ys { + i := dstY * dst.Stride + srcY := dstH - dstY - 1 + src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize]) + } + }) + return dst +} + +// Transpose flips the image horizontally and rotates 90 degrees counter-clockwise. +func Transpose(img image.Image) *image.NRGBA { + src := newScanner(img) + dstW := src.h + dstH := src.w + rowSize := dstW * 4 + dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) + parallel(0, dstH, func(ys <-chan int) { + for dstY := range ys { + i := dstY * dst.Stride + srcX := dstY + src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize]) + } + }) + return dst +} + +// Transverse flips the image vertically and rotates 90 degrees counter-clockwise. +func Transverse(img image.Image) *image.NRGBA { + src := newScanner(img) + dstW := src.h + dstH := src.w + rowSize := dstW * 4 + dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) + parallel(0, dstH, func(ys <-chan int) { + for dstY := range ys { + i := dstY * dst.Stride + srcX := dstH - dstY - 1 + src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize]) + reverse(dst.Pix[i : i+rowSize]) + } + }) + return dst +} + +// Rotate90 rotates the image 90 degrees counter-clockwise and returns the transformed image. +func Rotate90(img image.Image) *image.NRGBA { + src := newScanner(img) + dstW := src.h + dstH := src.w + rowSize := dstW * 4 + dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) + parallel(0, dstH, func(ys <-chan int) { + for dstY := range ys { + i := dstY * dst.Stride + srcX := dstH - dstY - 1 + src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize]) + } + }) + return dst +} + +// Rotate180 rotates the image 180 degrees counter-clockwise and returns the transformed image. +func Rotate180(img image.Image) *image.NRGBA { + src := newScanner(img) + dstW := src.w + dstH := src.h + rowSize := dstW * 4 + dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) + parallel(0, dstH, func(ys <-chan int) { + for dstY := range ys { + i := dstY * dst.Stride + srcY := dstH - dstY - 1 + src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize]) + reverse(dst.Pix[i : i+rowSize]) + } + }) + return dst +} + +// Rotate270 rotates the image 270 degrees counter-clockwise and returns the transformed image. +func Rotate270(img image.Image) *image.NRGBA { + src := newScanner(img) + dstW := src.h + dstH := src.w + rowSize := dstW * 4 + dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) + parallel(0, dstH, func(ys <-chan int) { + for dstY := range ys { + i := dstY * dst.Stride + srcX := dstY + src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize]) + reverse(dst.Pix[i : i+rowSize]) + } + }) + return dst +} + +// Rotate rotates an image by the given angle counter-clockwise . +// The angle parameter is the rotation angle in degrees. +// The bgColor parameter specifies the color of the uncovered zone after the rotation. +func Rotate(img image.Image, angle float64, bgColor color.Color) *image.NRGBA { + angle = angle - math.Floor(angle/360)*360 + + switch angle { + case 0: + return Clone(img) + case 90: + return Rotate90(img) + case 180: + return Rotate180(img) + case 270: + return Rotate270(img) + } + + src := toNRGBA(img) + srcW := src.Bounds().Max.X + srcH := src.Bounds().Max.Y + dstW, dstH := rotatedSize(srcW, srcH, angle) + dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) + + if dstW <= 0 || dstH <= 0 { + return dst + } + + srcXOff := float64(srcW)/2 - 0.5 + srcYOff := float64(srcH)/2 - 0.5 + dstXOff := float64(dstW)/2 - 0.5 + dstYOff := float64(dstH)/2 - 0.5 + + bgColorNRGBA := color.NRGBAModel.Convert(bgColor).(color.NRGBA) + sin, cos := math.Sincos(math.Pi * angle / 180) + + parallel(0, dstH, func(ys <-chan int) { + for dstY := range ys { + for dstX := 0; dstX < dstW; dstX++ { + xf, yf := rotatePoint(float64(dstX)-dstXOff, float64(dstY)-dstYOff, sin, cos) + xf, yf = xf+srcXOff, yf+srcYOff + interpolatePoint(dst, dstX, dstY, src, xf, yf, bgColorNRGBA) + } + } + }) + + return dst +} + +func rotatePoint(x, y, sin, cos float64) (float64, float64) { + return x*cos - y*sin, x*sin + y*cos +} + +func rotatedSize(w, h int, angle float64) (int, int) { + if w <= 0 || h <= 0 { + return 0, 0 + } + + sin, cos := math.Sincos(math.Pi * angle / 180) + x1, y1 := rotatePoint(float64(w-1), 0, sin, cos) + x2, y2 := rotatePoint(float64(w-1), float64(h-1), sin, cos) + x3, y3 := rotatePoint(0, float64(h-1), sin, cos) + + minx := math.Min(x1, math.Min(x2, math.Min(x3, 0))) + maxx := math.Max(x1, math.Max(x2, math.Max(x3, 0))) + miny := math.Min(y1, math.Min(y2, math.Min(y3, 0))) + maxy := math.Max(y1, math.Max(y2, math.Max(y3, 0))) + + neww := maxx - minx + 1 + if neww-math.Floor(neww) > 0.1 { + neww++ + } + newh := maxy - miny + 1 + if newh-math.Floor(newh) > 0.1 { + newh++ + } + + return int(neww), int(newh) +} + +func interpolatePoint(dst *image.NRGBA, dstX, dstY int, src *image.NRGBA, xf, yf float64, bgColor color.NRGBA) { + j := dstY*dst.Stride + dstX*4 + d := dst.Pix[j : j+4 : j+4] + + x0 := int(math.Floor(xf)) + y0 := int(math.Floor(yf)) + bounds := src.Bounds() + if !image.Pt(x0, y0).In(image.Rect(bounds.Min.X-1, bounds.Min.Y-1, bounds.Max.X, bounds.Max.Y)) { + d[0] = bgColor.R + d[1] = bgColor.G + d[2] = bgColor.B + d[3] = bgColor.A + return + } + + xq := xf - float64(x0) + yq := yf - float64(y0) + points := [4]image.Point{ + {x0, y0}, + {x0 + 1, y0}, + {x0, y0 + 1}, + {x0 + 1, y0 + 1}, + } + weights := [4]float64{ + (1 - xq) * (1 - yq), + xq * (1 - yq), + (1 - xq) * yq, + xq * yq, + } + + var r, g, b, a float64 + for i := 0; i < 4; i++ { + p := points[i] + w := weights[i] + if p.In(bounds) { + i := p.Y*src.Stride + p.X*4 + s := src.Pix[i : i+4 : i+4] + wa := float64(s[3]) * w + r += float64(s[0]) * wa + g += float64(s[1]) * wa + b += float64(s[2]) * wa + a += wa + } else { + wa := float64(bgColor.A) * w + r += float64(bgColor.R) * wa + g += float64(bgColor.G) * wa + b += float64(bgColor.B) * wa + a += wa + } + } + if a != 0 { + aInv := 1 / a + d[0] = clamp(r * aInv) + d[1] = clamp(g * aInv) + d[2] = clamp(b * aInv) + d[3] = clamp(a) + } +} diff --git a/vendor/github.com/disintegration/imaging/utils.go b/vendor/github.com/disintegration/imaging/utils.go new file mode 100644 index 000000000..6c7af1a51 --- /dev/null +++ b/vendor/github.com/disintegration/imaging/utils.go @@ -0,0 +1,167 @@ +package imaging + +import ( + "image" + "math" + "runtime" + "sync" +) + +// parallel processes the data in separate goroutines. +func parallel(start, stop int, fn func(<-chan int)) { + count := stop - start + if count < 1 { + return + } + + procs := runtime.GOMAXPROCS(0) + if procs > count { + procs = count + } + + c := make(chan int, count) + for i := start; i < stop; i++ { + c <- i + } + close(c) + + var wg sync.WaitGroup + for i := 0; i < procs; i++ { + wg.Add(1) + go func() { + defer wg.Done() + fn(c) + }() + } + wg.Wait() +} + +// absint returns the absolute value of i. +func absint(i int) int { + if i < 0 { + return -i + } + return i +} + +// clamp rounds and clamps float64 value to fit into uint8. +func clamp(x float64) uint8 { + v := int64(x + 0.5) + if v > 255 { + return 255 + } + if v > 0 { + return uint8(v) + } + return 0 +} + +func reverse(pix []uint8) { + if len(pix) <= 4 { + return + } + i := 0 + j := len(pix) - 4 + for i < j { + pi := pix[i : i+4 : i+4] + pj := pix[j : j+4 : j+4] + pi[0], pj[0] = pj[0], pi[0] + pi[1], pj[1] = pj[1], pi[1] + pi[2], pj[2] = pj[2], pi[2] + pi[3], pj[3] = pj[3], pi[3] + i += 4 + j -= 4 + } +} + +func toNRGBA(img image.Image) *image.NRGBA { + if img, ok := img.(*image.NRGBA); ok { + return &image.NRGBA{ + Pix: img.Pix, + Stride: img.Stride, + Rect: img.Rect.Sub(img.Rect.Min), + } + } + return Clone(img) +} + +// rgbToHSL converts a color from RGB to HSL. +func rgbToHSL(r, g, b uint8) (float64, float64, float64) { + rr := float64(r) / 255 + gg := float64(g) / 255 + bb := float64(b) / 255 + + max := math.Max(rr, math.Max(gg, bb)) + min := math.Min(rr, math.Min(gg, bb)) + + l := (max + min) / 2 + + if max == min { + return 0, 0, l + } + + var h, s float64 + d := max - min + if l > 0.5 { + s = d / (2 - max - min) + } else { + s = d / (max + min) + } + + switch max { + case rr: + h = (gg - bb) / d + if g < b { + h += 6 + } + case gg: + h = (bb-rr)/d + 2 + case bb: + h = (rr-gg)/d + 4 + } + h /= 6 + + return h, s, l +} + +// hslToRGB converts a color from HSL to RGB. +func hslToRGB(h, s, l float64) (uint8, uint8, uint8) { + var r, g, b float64 + if s == 0 { + v := clamp(l * 255) + return v, v, v + } + + var q float64 + if l < 0.5 { + q = l * (1 + s) + } else { + q = l + s - l*s + } + p := 2*l - q + + r = hueToRGB(p, q, h+1/3.0) + g = hueToRGB(p, q, h) + b = hueToRGB(p, q, h-1/3.0) + + return clamp(r * 255), clamp(g * 255), clamp(b * 255) +} + +func hueToRGB(p, q, t float64) float64 { + if t < 0 { + t++ + } + if t > 1 { + t-- + } + if t < 1/6.0 { + return p + (q-p)*6*t + } + if t < 1/2.0 { + return q + } + if t < 2/3.0 { + return p + (q-p)*(2/3.0-t)*6 + } + return p +} diff --git a/vendor/github.com/nfnt/resize/.travis.yml b/vendor/github.com/nfnt/resize/.travis.yml deleted file mode 100644 index 5ff08e7e4..000000000 --- a/vendor/github.com/nfnt/resize/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: go - -go: - - "1.x" - - "1.1" - - "1.4" - - "1.10" diff --git a/vendor/github.com/nfnt/resize/LICENSE b/vendor/github.com/nfnt/resize/LICENSE deleted file mode 100644 index 7836cad5f..000000000 --- a/vendor/github.com/nfnt/resize/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright (c) 2012, Jan Schlicht - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. diff --git a/vendor/github.com/nfnt/resize/README.md b/vendor/github.com/nfnt/resize/README.md deleted file mode 100644 index 372777d2e..000000000 --- a/vendor/github.com/nfnt/resize/README.md +++ /dev/null @@ -1,151 +0,0 @@ -# This package is no longer being updated! Please look for alternatives if that bothers you. - -Resize -====== - -Image resizing for the [Go programming language](http://golang.org) with common interpolation methods. - -[![Build Status](https://travis-ci.org/nfnt/resize.svg)](https://travis-ci.org/nfnt/resize) - -Installation ------------- - -```bash -$ go get github.com/nfnt/resize -``` - -It's that easy! - -Usage ------ - -This package needs at least Go 1.1. Import package with - -```go -import "github.com/nfnt/resize" -``` - -The resize package provides 2 functions: - -* `resize.Resize` creates a scaled image with new dimensions (`width`, `height`) using the interpolation function `interp`. - If either `width` or `height` is set to 0, it will be set to an aspect ratio preserving value. -* `resize.Thumbnail` downscales an image preserving its aspect ratio to the maximum dimensions (`maxWidth`, `maxHeight`). - It will return the original image if original sizes are smaller than the provided dimensions. - -```go -resize.Resize(width, height uint, img image.Image, interp resize.InterpolationFunction) image.Image -resize.Thumbnail(maxWidth, maxHeight uint, img image.Image, interp resize.InterpolationFunction) image.Image -``` - -The provided interpolation functions are (from fast to slow execution time) - -- `NearestNeighbor`: [Nearest-neighbor interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation) -- `Bilinear`: [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation) -- `Bicubic`: [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) -- `MitchellNetravali`: [Mitchell-Netravali interpolation](http://dl.acm.org/citation.cfm?id=378514) -- `Lanczos2`: [Lanczos resampling](http://en.wikipedia.org/wiki/Lanczos_resampling) with a=2 -- `Lanczos3`: [Lanczos resampling](http://en.wikipedia.org/wiki/Lanczos_resampling) with a=3 - -Which of these methods gives the best results depends on your use case. - -Sample usage: - -```go -package main - -import ( - "github.com/nfnt/resize" - "image/jpeg" - "log" - "os" -) - -func main() { - // open "test.jpg" - file, err := os.Open("test.jpg") - if err != nil { - log.Fatal(err) - } - - // decode jpeg into image.Image - img, err := jpeg.Decode(file) - if err != nil { - log.Fatal(err) - } - file.Close() - - // resize to width 1000 using Lanczos resampling - // and preserve aspect ratio - m := resize.Resize(1000, 0, img, resize.Lanczos3) - - out, err := os.Create("test_resized.jpg") - if err != nil { - log.Fatal(err) - } - defer out.Close() - - // write new image to file - jpeg.Encode(out, m, nil) -} -``` - -Caveats -------- - -* Optimized access routines are used for `image.RGBA`, `image.NRGBA`, `image.RGBA64`, `image.NRGBA64`, `image.YCbCr`, `image.Gray`, and `image.Gray16` types. All other image types are accessed in a generic way that will result in slow processing speed. -* JPEG images are stored in `image.YCbCr`. This image format stores data in a way that will decrease processing speed. A resize may be up to 2 times slower than with `image.RGBA`. - - -Downsizing Samples -------- - -Downsizing is not as simple as it might look like. Images have to be filtered before they are scaled down, otherwise aliasing might occur. -Filtering is highly subjective: Applying too much will blur the whole image, too little will make aliasing become apparent. -Resize tries to provide sane defaults that should suffice in most cases. - -### Artificial sample - -Original image -![Rings](http://nfnt.github.com/img/rings_lg_orig.png) - - - - - - - - - - - - - - -

Nearest-Neighbor

Bilinear

Bicubic

Mitchell-Netravali

Lanczos2

Lanczos3
- -### Real-Life sample - -Original image -![Original](http://nfnt.github.com/img/IMG_3694_720.jpg) - - - - - - - - - - - - - - -

Nearest-Neighbor

Bilinear

Bicubic

Mitchell-Netravali

Lanczos2

Lanczos3
- - -License -------- - -Copyright (c) 2012 Jan Schlicht -Resize is released under a MIT style license. diff --git a/vendor/github.com/nfnt/resize/converter.go b/vendor/github.com/nfnt/resize/converter.go deleted file mode 100644 index f9c520d09..000000000 --- a/vendor/github.com/nfnt/resize/converter.go +++ /dev/null @@ -1,438 +0,0 @@ -/* -Copyright (c) 2012, Jan Schlicht - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. -*/ - -package resize - -import "image" - -// Keep value in [0,255] range. -func clampUint8(in int32) uint8 { - // casting a negative int to an uint will result in an overflown - // large uint. this behavior will be exploited here and in other functions - // to achieve a higher performance. - if uint32(in) < 256 { - return uint8(in) - } - if in > 255 { - return 255 - } - return 0 -} - -// Keep value in [0,65535] range. -func clampUint16(in int64) uint16 { - if uint64(in) < 65536 { - return uint16(in) - } - if in > 65535 { - return 65535 - } - return 0 -} - -func resizeGeneric(in image.Image, out *image.RGBA64, scale float64, coeffs []int32, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]int64 - var sum int64 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - coeff := coeffs[ci+i] - if coeff != 0 { - xi := start + i - switch { - case xi < 0: - xi = 0 - case xi >= maxX: - xi = maxX - } - - r, g, b, a := in.At(xi+in.Bounds().Min.X, x+in.Bounds().Min.Y).RGBA() - - rgba[0] += int64(coeff) * int64(r) - rgba[1] += int64(coeff) * int64(g) - rgba[2] += int64(coeff) * int64(b) - rgba[3] += int64(coeff) * int64(a) - sum += int64(coeff) - } - } - - offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 - - value := clampUint16(rgba[0] / sum) - out.Pix[offset+0] = uint8(value >> 8) - out.Pix[offset+1] = uint8(value) - value = clampUint16(rgba[1] / sum) - out.Pix[offset+2] = uint8(value >> 8) - out.Pix[offset+3] = uint8(value) - value = clampUint16(rgba[2] / sum) - out.Pix[offset+4] = uint8(value >> 8) - out.Pix[offset+5] = uint8(value) - value = clampUint16(rgba[3] / sum) - out.Pix[offset+6] = uint8(value >> 8) - out.Pix[offset+7] = uint8(value) - } - } -} - -func resizeRGBA(in *image.RGBA, out *image.RGBA, scale float64, coeffs []int16, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]int32 - var sum int32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - coeff := coeffs[ci+i] - if coeff != 0 { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 4 - case xi >= maxX: - xi = 4 * maxX - default: - xi = 0 - } - - rgba[0] += int32(coeff) * int32(row[xi+0]) - rgba[1] += int32(coeff) * int32(row[xi+1]) - rgba[2] += int32(coeff) * int32(row[xi+2]) - rgba[3] += int32(coeff) * int32(row[xi+3]) - sum += int32(coeff) - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4 - - out.Pix[xo+0] = clampUint8(rgba[0] / sum) - out.Pix[xo+1] = clampUint8(rgba[1] / sum) - out.Pix[xo+2] = clampUint8(rgba[2] / sum) - out.Pix[xo+3] = clampUint8(rgba[3] / sum) - } - } -} - -func resizeNRGBA(in *image.NRGBA, out *image.RGBA, scale float64, coeffs []int16, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]int32 - var sum int32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - coeff := coeffs[ci+i] - if coeff != 0 { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 4 - case xi >= maxX: - xi = 4 * maxX - default: - xi = 0 - } - - // Forward alpha-premultiplication - a := int32(row[xi+3]) - r := int32(row[xi+0]) * a - r /= 0xff - g := int32(row[xi+1]) * a - g /= 0xff - b := int32(row[xi+2]) * a - b /= 0xff - - rgba[0] += int32(coeff) * r - rgba[1] += int32(coeff) * g - rgba[2] += int32(coeff) * b - rgba[3] += int32(coeff) * a - sum += int32(coeff) - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4 - - out.Pix[xo+0] = clampUint8(rgba[0] / sum) - out.Pix[xo+1] = clampUint8(rgba[1] / sum) - out.Pix[xo+2] = clampUint8(rgba[2] / sum) - out.Pix[xo+3] = clampUint8(rgba[3] / sum) - } - } -} - -func resizeRGBA64(in *image.RGBA64, out *image.RGBA64, scale float64, coeffs []int32, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]int64 - var sum int64 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - coeff := coeffs[ci+i] - if coeff != 0 { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 8 - case xi >= maxX: - xi = 8 * maxX - default: - xi = 0 - } - - rgba[0] += int64(coeff) * (int64(row[xi+0])<<8 | int64(row[xi+1])) - rgba[1] += int64(coeff) * (int64(row[xi+2])<<8 | int64(row[xi+3])) - rgba[2] += int64(coeff) * (int64(row[xi+4])<<8 | int64(row[xi+5])) - rgba[3] += int64(coeff) * (int64(row[xi+6])<<8 | int64(row[xi+7])) - sum += int64(coeff) - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 - - value := clampUint16(rgba[0] / sum) - out.Pix[xo+0] = uint8(value >> 8) - out.Pix[xo+1] = uint8(value) - value = clampUint16(rgba[1] / sum) - out.Pix[xo+2] = uint8(value >> 8) - out.Pix[xo+3] = uint8(value) - value = clampUint16(rgba[2] / sum) - out.Pix[xo+4] = uint8(value >> 8) - out.Pix[xo+5] = uint8(value) - value = clampUint16(rgba[3] / sum) - out.Pix[xo+6] = uint8(value >> 8) - out.Pix[xo+7] = uint8(value) - } - } -} - -func resizeNRGBA64(in *image.NRGBA64, out *image.RGBA64, scale float64, coeffs []int32, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]int64 - var sum int64 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - coeff := coeffs[ci+i] - if coeff != 0 { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 8 - case xi >= maxX: - xi = 8 * maxX - default: - xi = 0 - } - - // Forward alpha-premultiplication - a := int64(uint16(row[xi+6])<<8 | uint16(row[xi+7])) - r := int64(uint16(row[xi+0])<<8|uint16(row[xi+1])) * a - r /= 0xffff - g := int64(uint16(row[xi+2])<<8|uint16(row[xi+3])) * a - g /= 0xffff - b := int64(uint16(row[xi+4])<<8|uint16(row[xi+5])) * a - b /= 0xffff - - rgba[0] += int64(coeff) * r - rgba[1] += int64(coeff) * g - rgba[2] += int64(coeff) * b - rgba[3] += int64(coeff) * a - sum += int64(coeff) - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 - - value := clampUint16(rgba[0] / sum) - out.Pix[xo+0] = uint8(value >> 8) - out.Pix[xo+1] = uint8(value) - value = clampUint16(rgba[1] / sum) - out.Pix[xo+2] = uint8(value >> 8) - out.Pix[xo+3] = uint8(value) - value = clampUint16(rgba[2] / sum) - out.Pix[xo+4] = uint8(value >> 8) - out.Pix[xo+5] = uint8(value) - value = clampUint16(rgba[3] / sum) - out.Pix[xo+6] = uint8(value >> 8) - out.Pix[xo+7] = uint8(value) - } - } -} - -func resizeGray(in *image.Gray, out *image.Gray, scale float64, coeffs []int16, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[(x-newBounds.Min.X)*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var gray int32 - var sum int32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - coeff := coeffs[ci+i] - if coeff != 0 { - xi := start + i - switch { - case xi < 0: - xi = 0 - case xi >= maxX: - xi = maxX - } - gray += int32(coeff) * int32(row[xi]) - sum += int32(coeff) - } - } - - offset := (y-newBounds.Min.Y)*out.Stride + (x - newBounds.Min.X) - out.Pix[offset] = clampUint8(gray / sum) - } - } -} - -func resizeGray16(in *image.Gray16, out *image.Gray16, scale float64, coeffs []int32, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var gray int64 - var sum int64 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - coeff := coeffs[ci+i] - if coeff != 0 { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 2 - case xi >= maxX: - xi = 2 * maxX - default: - xi = 0 - } - gray += int64(coeff) * int64(uint16(row[xi+0])<<8|uint16(row[xi+1])) - sum += int64(coeff) - } - } - - offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*2 - value := clampUint16(gray / sum) - out.Pix[offset+0] = uint8(value >> 8) - out.Pix[offset+1] = uint8(value) - } - } -} - -func resizeYCbCr(in *ycc, out *ycc, scale float64, coeffs []int16, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var p [3]int32 - var sum int32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - coeff := coeffs[ci+i] - if coeff != 0 { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 3 - case xi >= maxX: - xi = 3 * maxX - default: - xi = 0 - } - p[0] += int32(coeff) * int32(row[xi+0]) - p[1] += int32(coeff) * int32(row[xi+1]) - p[2] += int32(coeff) * int32(row[xi+2]) - sum += int32(coeff) - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*3 - out.Pix[xo+0] = clampUint8(p[0] / sum) - out.Pix[xo+1] = clampUint8(p[1] / sum) - out.Pix[xo+2] = clampUint8(p[2] / sum) - } - } -} - -func nearestYCbCr(in *ycc, out *ycc, scale float64, coeffs []bool, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var p [3]float32 - var sum float32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - if coeffs[ci+i] { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 3 - case xi >= maxX: - xi = 3 * maxX - default: - xi = 0 - } - p[0] += float32(row[xi+0]) - p[1] += float32(row[xi+1]) - p[2] += float32(row[xi+2]) - sum++ - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*3 - out.Pix[xo+0] = floatToUint8(p[0] / sum) - out.Pix[xo+1] = floatToUint8(p[1] / sum) - out.Pix[xo+2] = floatToUint8(p[2] / sum) - } - } -} diff --git a/vendor/github.com/nfnt/resize/filters.go b/vendor/github.com/nfnt/resize/filters.go deleted file mode 100644 index 4ce04e389..000000000 --- a/vendor/github.com/nfnt/resize/filters.go +++ /dev/null @@ -1,143 +0,0 @@ -/* -Copyright (c) 2012, Jan Schlicht - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. -*/ - -package resize - -import ( - "math" -) - -func nearest(in float64) float64 { - if in >= -0.5 && in < 0.5 { - return 1 - } - return 0 -} - -func linear(in float64) float64 { - in = math.Abs(in) - if in <= 1 { - return 1 - in - } - return 0 -} - -func cubic(in float64) float64 { - in = math.Abs(in) - if in <= 1 { - return in*in*(1.5*in-2.5) + 1.0 - } - if in <= 2 { - return in*(in*(2.5-0.5*in)-4.0) + 2.0 - } - return 0 -} - -func mitchellnetravali(in float64) float64 { - in = math.Abs(in) - if in <= 1 { - return (7.0*in*in*in - 12.0*in*in + 5.33333333333) * 0.16666666666 - } - if in <= 2 { - return (-2.33333333333*in*in*in + 12.0*in*in - 20.0*in + 10.6666666667) * 0.16666666666 - } - return 0 -} - -func sinc(x float64) float64 { - x = math.Abs(x) * math.Pi - if x >= 1.220703e-4 { - return math.Sin(x) / x - } - return 1 -} - -func lanczos2(in float64) float64 { - if in > -2 && in < 2 { - return sinc(in) * sinc(in*0.5) - } - return 0 -} - -func lanczos3(in float64) float64 { - if in > -3 && in < 3 { - return sinc(in) * sinc(in*0.3333333333333333) - } - return 0 -} - -// range [-256,256] -func createWeights8(dy, filterLength int, blur, scale float64, kernel func(float64) float64) ([]int16, []int, int) { - filterLength = filterLength * int(math.Max(math.Ceil(blur*scale), 1)) - filterFactor := math.Min(1./(blur*scale), 1) - - coeffs := make([]int16, dy*filterLength) - start := make([]int, dy) - for y := 0; y < dy; y++ { - interpX := scale*(float64(y)+0.5) - 0.5 - start[y] = int(interpX) - filterLength/2 + 1 - interpX -= float64(start[y]) - for i := 0; i < filterLength; i++ { - in := (interpX - float64(i)) * filterFactor - coeffs[y*filterLength+i] = int16(kernel(in) * 256) - } - } - - return coeffs, start, filterLength -} - -// range [-65536,65536] -func createWeights16(dy, filterLength int, blur, scale float64, kernel func(float64) float64) ([]int32, []int, int) { - filterLength = filterLength * int(math.Max(math.Ceil(blur*scale), 1)) - filterFactor := math.Min(1./(blur*scale), 1) - - coeffs := make([]int32, dy*filterLength) - start := make([]int, dy) - for y := 0; y < dy; y++ { - interpX := scale*(float64(y)+0.5) - 0.5 - start[y] = int(interpX) - filterLength/2 + 1 - interpX -= float64(start[y]) - for i := 0; i < filterLength; i++ { - in := (interpX - float64(i)) * filterFactor - coeffs[y*filterLength+i] = int32(kernel(in) * 65536) - } - } - - return coeffs, start, filterLength -} - -func createWeightsNearest(dy, filterLength int, blur, scale float64) ([]bool, []int, int) { - filterLength = filterLength * int(math.Max(math.Ceil(blur*scale), 1)) - filterFactor := math.Min(1./(blur*scale), 1) - - coeffs := make([]bool, dy*filterLength) - start := make([]int, dy) - for y := 0; y < dy; y++ { - interpX := scale*(float64(y)+0.5) - 0.5 - start[y] = int(interpX) - filterLength/2 + 1 - interpX -= float64(start[y]) - for i := 0; i < filterLength; i++ { - in := (interpX - float64(i)) * filterFactor - if in >= -0.5 && in < 0.5 { - coeffs[y*filterLength+i] = true - } else { - coeffs[y*filterLength+i] = false - } - } - } - - return coeffs, start, filterLength -} diff --git a/vendor/github.com/nfnt/resize/nearest.go b/vendor/github.com/nfnt/resize/nearest.go deleted file mode 100644 index 888039d85..000000000 --- a/vendor/github.com/nfnt/resize/nearest.go +++ /dev/null @@ -1,318 +0,0 @@ -/* -Copyright (c) 2014, Charlie Vieth - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. -*/ - -package resize - -import "image" - -func floatToUint8(x float32) uint8 { - // Nearest-neighbor values are always - // positive no need to check lower-bound. - if x > 0xfe { - return 0xff - } - return uint8(x) -} - -func floatToUint16(x float32) uint16 { - if x > 0xfffe { - return 0xffff - } - return uint16(x) -} - -func nearestGeneric(in image.Image, out *image.RGBA64, scale float64, coeffs []bool, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]float32 - var sum float32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - if coeffs[ci+i] { - xi := start + i - switch { - case xi < 0: - xi = 0 - case xi >= maxX: - xi = maxX - } - r, g, b, a := in.At(xi+in.Bounds().Min.X, x+in.Bounds().Min.Y).RGBA() - rgba[0] += float32(r) - rgba[1] += float32(g) - rgba[2] += float32(b) - rgba[3] += float32(a) - sum++ - } - } - - offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 - value := floatToUint16(rgba[0] / sum) - out.Pix[offset+0] = uint8(value >> 8) - out.Pix[offset+1] = uint8(value) - value = floatToUint16(rgba[1] / sum) - out.Pix[offset+2] = uint8(value >> 8) - out.Pix[offset+3] = uint8(value) - value = floatToUint16(rgba[2] / sum) - out.Pix[offset+4] = uint8(value >> 8) - out.Pix[offset+5] = uint8(value) - value = floatToUint16(rgba[3] / sum) - out.Pix[offset+6] = uint8(value >> 8) - out.Pix[offset+7] = uint8(value) - } - } -} - -func nearestRGBA(in *image.RGBA, out *image.RGBA, scale float64, coeffs []bool, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]float32 - var sum float32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - if coeffs[ci+i] { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 4 - case xi >= maxX: - xi = 4 * maxX - default: - xi = 0 - } - rgba[0] += float32(row[xi+0]) - rgba[1] += float32(row[xi+1]) - rgba[2] += float32(row[xi+2]) - rgba[3] += float32(row[xi+3]) - sum++ - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4 - out.Pix[xo+0] = floatToUint8(rgba[0] / sum) - out.Pix[xo+1] = floatToUint8(rgba[1] / sum) - out.Pix[xo+2] = floatToUint8(rgba[2] / sum) - out.Pix[xo+3] = floatToUint8(rgba[3] / sum) - } - } -} - -func nearestNRGBA(in *image.NRGBA, out *image.NRGBA, scale float64, coeffs []bool, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]float32 - var sum float32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - if coeffs[ci+i] { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 4 - case xi >= maxX: - xi = 4 * maxX - default: - xi = 0 - } - rgba[0] += float32(row[xi+0]) - rgba[1] += float32(row[xi+1]) - rgba[2] += float32(row[xi+2]) - rgba[3] += float32(row[xi+3]) - sum++ - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4 - out.Pix[xo+0] = floatToUint8(rgba[0] / sum) - out.Pix[xo+1] = floatToUint8(rgba[1] / sum) - out.Pix[xo+2] = floatToUint8(rgba[2] / sum) - out.Pix[xo+3] = floatToUint8(rgba[3] / sum) - } - } -} - -func nearestRGBA64(in *image.RGBA64, out *image.RGBA64, scale float64, coeffs []bool, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]float32 - var sum float32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - if coeffs[ci+i] { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 8 - case xi >= maxX: - xi = 8 * maxX - default: - xi = 0 - } - rgba[0] += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1])) - rgba[1] += float32(uint16(row[xi+2])<<8 | uint16(row[xi+3])) - rgba[2] += float32(uint16(row[xi+4])<<8 | uint16(row[xi+5])) - rgba[3] += float32(uint16(row[xi+6])<<8 | uint16(row[xi+7])) - sum++ - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 - value := floatToUint16(rgba[0] / sum) - out.Pix[xo+0] = uint8(value >> 8) - out.Pix[xo+1] = uint8(value) - value = floatToUint16(rgba[1] / sum) - out.Pix[xo+2] = uint8(value >> 8) - out.Pix[xo+3] = uint8(value) - value = floatToUint16(rgba[2] / sum) - out.Pix[xo+4] = uint8(value >> 8) - out.Pix[xo+5] = uint8(value) - value = floatToUint16(rgba[3] / sum) - out.Pix[xo+6] = uint8(value >> 8) - out.Pix[xo+7] = uint8(value) - } - } -} - -func nearestNRGBA64(in *image.NRGBA64, out *image.NRGBA64, scale float64, coeffs []bool, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var rgba [4]float32 - var sum float32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - if coeffs[ci+i] { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 8 - case xi >= maxX: - xi = 8 * maxX - default: - xi = 0 - } - rgba[0] += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1])) - rgba[1] += float32(uint16(row[xi+2])<<8 | uint16(row[xi+3])) - rgba[2] += float32(uint16(row[xi+4])<<8 | uint16(row[xi+5])) - rgba[3] += float32(uint16(row[xi+6])<<8 | uint16(row[xi+7])) - sum++ - } - } - - xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 - value := floatToUint16(rgba[0] / sum) - out.Pix[xo+0] = uint8(value >> 8) - out.Pix[xo+1] = uint8(value) - value = floatToUint16(rgba[1] / sum) - out.Pix[xo+2] = uint8(value >> 8) - out.Pix[xo+3] = uint8(value) - value = floatToUint16(rgba[2] / sum) - out.Pix[xo+4] = uint8(value >> 8) - out.Pix[xo+5] = uint8(value) - value = floatToUint16(rgba[3] / sum) - out.Pix[xo+6] = uint8(value >> 8) - out.Pix[xo+7] = uint8(value) - } - } -} - -func nearestGray(in *image.Gray, out *image.Gray, scale float64, coeffs []bool, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var gray float32 - var sum float32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - if coeffs[ci+i] { - xi := start + i - switch { - case xi < 0: - xi = 0 - case xi >= maxX: - xi = maxX - } - gray += float32(row[xi]) - sum++ - } - } - - offset := (y-newBounds.Min.Y)*out.Stride + (x - newBounds.Min.X) - out.Pix[offset] = floatToUint8(gray / sum) - } - } -} - -func nearestGray16(in *image.Gray16, out *image.Gray16, scale float64, coeffs []bool, offset []int, filterLength int) { - newBounds := out.Bounds() - maxX := in.Bounds().Dx() - 1 - - for x := newBounds.Min.X; x < newBounds.Max.X; x++ { - row := in.Pix[x*in.Stride:] - for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { - var gray float32 - var sum float32 - start := offset[y] - ci := y * filterLength - for i := 0; i < filterLength; i++ { - if coeffs[ci+i] { - xi := start + i - switch { - case uint(xi) < uint(maxX): - xi *= 2 - case xi >= maxX: - xi = 2 * maxX - default: - xi = 0 - } - gray += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1])) - sum++ - } - } - - offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*2 - value := floatToUint16(gray / sum) - out.Pix[offset+0] = uint8(value >> 8) - out.Pix[offset+1] = uint8(value) - } - } -} diff --git a/vendor/github.com/nfnt/resize/resize.go b/vendor/github.com/nfnt/resize/resize.go deleted file mode 100644 index 0d7fbf69a..000000000 --- a/vendor/github.com/nfnt/resize/resize.go +++ /dev/null @@ -1,620 +0,0 @@ -/* -Copyright (c) 2012, Jan Schlicht - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. -*/ - -// Package resize implements various image resizing methods. -// -// The package works with the Image interface described in the image package. -// Various interpolation methods are provided and multiple processors may be -// utilized in the computations. -// -// Example: -// imgResized := resize.Resize(1000, 0, imgOld, resize.MitchellNetravali) -package resize - -import ( - "image" - "runtime" - "sync" -) - -// An InterpolationFunction provides the parameters that describe an -// interpolation kernel. It returns the number of samples to take -// and the kernel function to use for sampling. -type InterpolationFunction int - -// InterpolationFunction constants -const ( - // Nearest-neighbor interpolation - NearestNeighbor InterpolationFunction = iota - // Bilinear interpolation - Bilinear - // Bicubic interpolation (with cubic hermite spline) - Bicubic - // Mitchell-Netravali interpolation - MitchellNetravali - // Lanczos interpolation (a=2) - Lanczos2 - // Lanczos interpolation (a=3) - Lanczos3 -) - -// kernal, returns an InterpolationFunctions taps and kernel. -func (i InterpolationFunction) kernel() (int, func(float64) float64) { - switch i { - case Bilinear: - return 2, linear - case Bicubic: - return 4, cubic - case MitchellNetravali: - return 4, mitchellnetravali - case Lanczos2: - return 4, lanczos2 - case Lanczos3: - return 6, lanczos3 - default: - // Default to NearestNeighbor. - return 2, nearest - } -} - -// values <1 will sharpen the image -var blur = 1.0 - -// Resize scales an image to new width and height using the interpolation function interp. -// A new image with the given dimensions will be returned. -// If one of the parameters width or height is set to 0, its size will be calculated so that -// the aspect ratio is that of the originating image. -// The resizing algorithm uses channels for parallel computation. -// If the input image has width or height of 0, it is returned unchanged. -func Resize(width, height uint, img image.Image, interp InterpolationFunction) image.Image { - scaleX, scaleY := calcFactors(width, height, float64(img.Bounds().Dx()), float64(img.Bounds().Dy())) - if width == 0 { - width = uint(0.7 + float64(img.Bounds().Dx())/scaleX) - } - if height == 0 { - height = uint(0.7 + float64(img.Bounds().Dy())/scaleY) - } - - // Trivial case: return input image - if int(width) == img.Bounds().Dx() && int(height) == img.Bounds().Dy() { - return img - } - - // Input image has no pixels - if img.Bounds().Dx() <= 0 || img.Bounds().Dy() <= 0 { - return img - } - - if interp == NearestNeighbor { - return resizeNearest(width, height, scaleX, scaleY, img, interp) - } - - taps, kernel := interp.kernel() - cpus := runtime.GOMAXPROCS(0) - wg := sync.WaitGroup{} - - // Generic access to image.Image is slow in tight loops. - // The optimal access has to be determined from the concrete image type. - switch input := img.(type) { - case *image.RGBA: - // 8-bit precision - temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewRGBA(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.RGBA) - go func() { - defer wg.Done() - resizeRGBA(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.RGBA) - go func() { - defer wg.Done() - resizeRGBA(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.NRGBA: - // 8-bit precision - temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewRGBA(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.RGBA) - go func() { - defer wg.Done() - resizeNRGBA(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.RGBA) - go func() { - defer wg.Done() - resizeRGBA(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - - case *image.YCbCr: - // 8-bit precision - // accessing the YCbCr arrays in a tight loop is slow. - // converting the image to ycc increases performance by 2x. - temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio) - result := newYCC(image.Rect(0, 0, int(width), int(height)), image.YCbCrSubsampleRatio444) - - coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) - in := imageYCbCrToYCC(input) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*ycc) - go func() { - defer wg.Done() - resizeYCbCr(in, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*ycc) - go func() { - defer wg.Done() - resizeYCbCr(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result.YCbCr() - case *image.RGBA64: - // 16-bit precision - temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.RGBA64) - go func() { - defer wg.Done() - resizeRGBA64(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.RGBA64) - go func() { - defer wg.Done() - resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.NRGBA64: - // 16-bit precision - temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.RGBA64) - go func() { - defer wg.Done() - resizeNRGBA64(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.RGBA64) - go func() { - defer wg.Done() - resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.Gray: - // 8-bit precision - temp := image.NewGray(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewGray(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.Gray) - go func() { - defer wg.Done() - resizeGray(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.Gray) - go func() { - defer wg.Done() - resizeGray(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.Gray16: - // 16-bit precision - temp := image.NewGray16(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewGray16(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.Gray16) - go func() { - defer wg.Done() - resizeGray16(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.Gray16) - go func() { - defer wg.Done() - resizeGray16(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - default: - // 16-bit precision - temp := image.NewRGBA64(image.Rect(0, 0, img.Bounds().Dy(), int(width))) - result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.RGBA64) - go func() { - defer wg.Done() - resizeGeneric(img, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.RGBA64) - go func() { - defer wg.Done() - resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - } -} - -func resizeNearest(width, height uint, scaleX, scaleY float64, img image.Image, interp InterpolationFunction) image.Image { - taps, _ := interp.kernel() - cpus := runtime.GOMAXPROCS(0) - wg := sync.WaitGroup{} - - switch input := img.(type) { - case *image.RGBA: - // 8-bit precision - temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewRGBA(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.RGBA) - go func() { - defer wg.Done() - nearestRGBA(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.RGBA) - go func() { - defer wg.Done() - nearestRGBA(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.NRGBA: - // 8-bit precision - temp := image.NewNRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewNRGBA(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.NRGBA) - go func() { - defer wg.Done() - nearestNRGBA(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.NRGBA) - go func() { - defer wg.Done() - nearestNRGBA(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.YCbCr: - // 8-bit precision - // accessing the YCbCr arrays in a tight loop is slow. - // converting the image to ycc increases performance by 2x. - temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio) - result := newYCC(image.Rect(0, 0, int(width), int(height)), image.YCbCrSubsampleRatio444) - - coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) - in := imageYCbCrToYCC(input) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*ycc) - go func() { - defer wg.Done() - nearestYCbCr(in, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*ycc) - go func() { - defer wg.Done() - nearestYCbCr(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result.YCbCr() - case *image.RGBA64: - // 16-bit precision - temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.RGBA64) - go func() { - defer wg.Done() - nearestRGBA64(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.RGBA64) - go func() { - defer wg.Done() - nearestRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.NRGBA64: - // 16-bit precision - temp := image.NewNRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewNRGBA64(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.NRGBA64) - go func() { - defer wg.Done() - nearestNRGBA64(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.NRGBA64) - go func() { - defer wg.Done() - nearestNRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.Gray: - // 8-bit precision - temp := image.NewGray(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewGray(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.Gray) - go func() { - defer wg.Done() - nearestGray(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.Gray) - go func() { - defer wg.Done() - nearestGray(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - case *image.Gray16: - // 16-bit precision - temp := image.NewGray16(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewGray16(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.Gray16) - go func() { - defer wg.Done() - nearestGray16(input, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.Gray16) - go func() { - defer wg.Done() - nearestGray16(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - default: - // 16-bit precision - temp := image.NewRGBA64(image.Rect(0, 0, img.Bounds().Dy(), int(width))) - result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) - - // horizontal filter, results in transposed temporary image - coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.RGBA64) - go func() { - defer wg.Done() - nearestGeneric(img, slice, scaleX, coeffs, offset, filterLength) - }() - } - wg.Wait() - - // horizontal filter on transposed image, result is not transposed - coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) - wg.Add(cpus) - for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.RGBA64) - go func() { - defer wg.Done() - nearestRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) - }() - } - wg.Wait() - return result - } - -} - -// Calculates scaling factors using old and new image dimensions. -func calcFactors(width, height uint, oldWidth, oldHeight float64) (scaleX, scaleY float64) { - if width == 0 { - if height == 0 { - scaleX = 1.0 - scaleY = 1.0 - } else { - scaleY = oldHeight / float64(height) - scaleX = scaleY - } - } else { - scaleX = oldWidth / float64(width) - if height == 0 { - scaleY = scaleX - } else { - scaleY = oldHeight / float64(height) - } - } - return -} - -type imageWithSubImage interface { - image.Image - SubImage(image.Rectangle) image.Image -} - -func makeSlice(img imageWithSubImage, i, n int) image.Image { - return img.SubImage(image.Rect(img.Bounds().Min.X, img.Bounds().Min.Y+i*img.Bounds().Dy()/n, img.Bounds().Max.X, img.Bounds().Min.Y+(i+1)*img.Bounds().Dy()/n)) -} diff --git a/vendor/github.com/nfnt/resize/thumbnail.go b/vendor/github.com/nfnt/resize/thumbnail.go deleted file mode 100644 index 9efc246be..000000000 --- a/vendor/github.com/nfnt/resize/thumbnail.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright (c) 2012, Jan Schlicht - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. -*/ - -package resize - -import ( - "image" -) - -// Thumbnail will downscale provided image to max width and height preserving -// original aspect ratio and using the interpolation function interp. -// It will return original image, without processing it, if original sizes -// are already smaller than provided constraints. -func Thumbnail(maxWidth, maxHeight uint, img image.Image, interp InterpolationFunction) image.Image { - origBounds := img.Bounds() - origWidth := uint(origBounds.Dx()) - origHeight := uint(origBounds.Dy()) - newWidth, newHeight := origWidth, origHeight - - // Return original image if it have same or smaller size as constraints - if maxWidth >= origWidth && maxHeight >= origHeight { - return img - } - - // Preserve aspect ratio - if origWidth > maxWidth { - newHeight = uint(origHeight * maxWidth / origWidth) - if newHeight < 1 { - newHeight = 1 - } - newWidth = maxWidth - } - - if newHeight > maxHeight { - newWidth = uint(newWidth * maxHeight / newHeight) - if newWidth < 1 { - newWidth = 1 - } - newHeight = maxHeight - } - return Resize(newWidth, newHeight, img, interp) -} diff --git a/vendor/github.com/nfnt/resize/ycc.go b/vendor/github.com/nfnt/resize/ycc.go deleted file mode 100644 index 143e4d06a..000000000 --- a/vendor/github.com/nfnt/resize/ycc.go +++ /dev/null @@ -1,387 +0,0 @@ -/* -Copyright (c) 2014, Charlie Vieth - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. -*/ - -package resize - -import ( - "image" - "image/color" -) - -// ycc is an in memory YCbCr image. The Y, Cb and Cr samples are held in a -// single slice to increase resizing performance. -type ycc struct { - // Pix holds the image's pixels, in Y, Cb, Cr order. The pixel at - // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*3]. - Pix []uint8 - // Stride is the Pix stride (in bytes) between vertically adjacent pixels. - Stride int - // Rect is the image's bounds. - Rect image.Rectangle - // SubsampleRatio is the subsample ratio of the original YCbCr image. - SubsampleRatio image.YCbCrSubsampleRatio -} - -// PixOffset returns the index of the first element of Pix that corresponds to -// the pixel at (x, y). -func (p *ycc) PixOffset(x, y int) int { - return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*3 -} - -func (p *ycc) Bounds() image.Rectangle { - return p.Rect -} - -func (p *ycc) ColorModel() color.Model { - return color.YCbCrModel -} - -func (p *ycc) At(x, y int) color.Color { - if !(image.Point{x, y}.In(p.Rect)) { - return color.YCbCr{} - } - i := p.PixOffset(x, y) - return color.YCbCr{ - p.Pix[i+0], - p.Pix[i+1], - p.Pix[i+2], - } -} - -func (p *ycc) Opaque() bool { - return true -} - -// SubImage returns an image representing the portion of the image p visible -// through r. The returned value shares pixels with the original image. -func (p *ycc) SubImage(r image.Rectangle) image.Image { - r = r.Intersect(p.Rect) - if r.Empty() { - return &ycc{SubsampleRatio: p.SubsampleRatio} - } - i := p.PixOffset(r.Min.X, r.Min.Y) - return &ycc{ - Pix: p.Pix[i:], - Stride: p.Stride, - Rect: r, - SubsampleRatio: p.SubsampleRatio, - } -} - -// newYCC returns a new ycc with the given bounds and subsample ratio. -func newYCC(r image.Rectangle, s image.YCbCrSubsampleRatio) *ycc { - w, h := r.Dx(), r.Dy() - buf := make([]uint8, 3*w*h) - return &ycc{Pix: buf, Stride: 3 * w, Rect: r, SubsampleRatio: s} -} - -// Copy of image.YCbCrSubsampleRatio constants - this allows us to support -// older versions of Go where these constants are not defined (i.e. Go 1.4) -const ( - ycbcrSubsampleRatio444 image.YCbCrSubsampleRatio = iota - ycbcrSubsampleRatio422 - ycbcrSubsampleRatio420 - ycbcrSubsampleRatio440 - ycbcrSubsampleRatio411 - ycbcrSubsampleRatio410 -) - -// YCbCr converts ycc to a YCbCr image with the same subsample ratio -// as the YCbCr image that ycc was generated from. -func (p *ycc) YCbCr() *image.YCbCr { - ycbcr := image.NewYCbCr(p.Rect, p.SubsampleRatio) - switch ycbcr.SubsampleRatio { - case ycbcrSubsampleRatio422: - return p.ycbcr422(ycbcr) - case ycbcrSubsampleRatio420: - return p.ycbcr420(ycbcr) - case ycbcrSubsampleRatio440: - return p.ycbcr440(ycbcr) - case ycbcrSubsampleRatio444: - return p.ycbcr444(ycbcr) - case ycbcrSubsampleRatio411: - return p.ycbcr411(ycbcr) - case ycbcrSubsampleRatio410: - return p.ycbcr410(ycbcr) - } - return ycbcr -} - -// imageYCbCrToYCC converts a YCbCr image to a ycc image for resizing. -func imageYCbCrToYCC(in *image.YCbCr) *ycc { - w, h := in.Rect.Dx(), in.Rect.Dy() - p := ycc{ - Pix: make([]uint8, 3*w*h), - Stride: 3 * w, - Rect: image.Rect(0, 0, w, h), - SubsampleRatio: in.SubsampleRatio, - } - switch in.SubsampleRatio { - case ycbcrSubsampleRatio422: - return convertToYCC422(in, &p) - case ycbcrSubsampleRatio420: - return convertToYCC420(in, &p) - case ycbcrSubsampleRatio440: - return convertToYCC440(in, &p) - case ycbcrSubsampleRatio444: - return convertToYCC444(in, &p) - case ycbcrSubsampleRatio411: - return convertToYCC411(in, &p) - case ycbcrSubsampleRatio410: - return convertToYCC410(in, &p) - } - return &p -} - -func (p *ycc) ycbcr422(ycbcr *image.YCbCr) *image.YCbCr { - var off int - Pix := p.Pix - Y := ycbcr.Y - Cb := ycbcr.Cb - Cr := ycbcr.Cr - for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ { - yy := y * ycbcr.YStride - cy := y * ycbcr.CStride - for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ { - ci := cy + x/2 - Y[yy+x] = Pix[off+0] - Cb[ci] = Pix[off+1] - Cr[ci] = Pix[off+2] - off += 3 - } - } - return ycbcr -} - -func (p *ycc) ycbcr420(ycbcr *image.YCbCr) *image.YCbCr { - var off int - Pix := p.Pix - Y := ycbcr.Y - Cb := ycbcr.Cb - Cr := ycbcr.Cr - for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ { - yy := y * ycbcr.YStride - cy := (y / 2) * ycbcr.CStride - for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ { - ci := cy + x/2 - Y[yy+x] = Pix[off+0] - Cb[ci] = Pix[off+1] - Cr[ci] = Pix[off+2] - off += 3 - } - } - return ycbcr -} - -func (p *ycc) ycbcr440(ycbcr *image.YCbCr) *image.YCbCr { - var off int - Pix := p.Pix - Y := ycbcr.Y - Cb := ycbcr.Cb - Cr := ycbcr.Cr - for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ { - yy := y * ycbcr.YStride - cy := (y / 2) * ycbcr.CStride - for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ { - ci := cy + x - Y[yy+x] = Pix[off+0] - Cb[ci] = Pix[off+1] - Cr[ci] = Pix[off+2] - off += 3 - } - } - return ycbcr -} - -func (p *ycc) ycbcr444(ycbcr *image.YCbCr) *image.YCbCr { - var off int - Pix := p.Pix - Y := ycbcr.Y - Cb := ycbcr.Cb - Cr := ycbcr.Cr - for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ { - yy := y * ycbcr.YStride - cy := y * ycbcr.CStride - for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ { - ci := cy + x - Y[yy+x] = Pix[off+0] - Cb[ci] = Pix[off+1] - Cr[ci] = Pix[off+2] - off += 3 - } - } - return ycbcr -} - -func (p *ycc) ycbcr411(ycbcr *image.YCbCr) *image.YCbCr { - var off int - Pix := p.Pix - Y := ycbcr.Y - Cb := ycbcr.Cb - Cr := ycbcr.Cr - for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ { - yy := y * ycbcr.YStride - cy := y * ycbcr.CStride - for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ { - ci := cy + x/4 - Y[yy+x] = Pix[off+0] - Cb[ci] = Pix[off+1] - Cr[ci] = Pix[off+2] - off += 3 - } - } - return ycbcr -} - -func (p *ycc) ycbcr410(ycbcr *image.YCbCr) *image.YCbCr { - var off int - Pix := p.Pix - Y := ycbcr.Y - Cb := ycbcr.Cb - Cr := ycbcr.Cr - for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ { - yy := y * ycbcr.YStride - cy := (y / 2) * ycbcr.CStride - for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ { - ci := cy + x/4 - Y[yy+x] = Pix[off+0] - Cb[ci] = Pix[off+1] - Cr[ci] = Pix[off+2] - off += 3 - } - } - return ycbcr -} - -func convertToYCC422(in *image.YCbCr, p *ycc) *ycc { - var off int - Pix := p.Pix - Y := in.Y - Cb := in.Cb - Cr := in.Cr - for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ { - yy := y * in.YStride - cy := y * in.CStride - for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ { - ci := cy + x/2 - Pix[off+0] = Y[yy+x] - Pix[off+1] = Cb[ci] - Pix[off+2] = Cr[ci] - off += 3 - } - } - return p -} - -func convertToYCC420(in *image.YCbCr, p *ycc) *ycc { - var off int - Pix := p.Pix - Y := in.Y - Cb := in.Cb - Cr := in.Cr - for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ { - yy := y * in.YStride - cy := (y / 2) * in.CStride - for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ { - ci := cy + x/2 - Pix[off+0] = Y[yy+x] - Pix[off+1] = Cb[ci] - Pix[off+2] = Cr[ci] - off += 3 - } - } - return p -} - -func convertToYCC440(in *image.YCbCr, p *ycc) *ycc { - var off int - Pix := p.Pix - Y := in.Y - Cb := in.Cb - Cr := in.Cr - for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ { - yy := y * in.YStride - cy := (y / 2) * in.CStride - for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ { - ci := cy + x - Pix[off+0] = Y[yy+x] - Pix[off+1] = Cb[ci] - Pix[off+2] = Cr[ci] - off += 3 - } - } - return p -} - -func convertToYCC444(in *image.YCbCr, p *ycc) *ycc { - var off int - Pix := p.Pix - Y := in.Y - Cb := in.Cb - Cr := in.Cr - for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ { - yy := y * in.YStride - cy := y * in.CStride - for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ { - ci := cy + x - Pix[off+0] = Y[yy+x] - Pix[off+1] = Cb[ci] - Pix[off+2] = Cr[ci] - off += 3 - } - } - return p -} - -func convertToYCC411(in *image.YCbCr, p *ycc) *ycc { - var off int - Pix := p.Pix - Y := in.Y - Cb := in.Cb - Cr := in.Cr - for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ { - yy := y * in.YStride - cy := y * in.CStride - for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ { - ci := cy + x/4 - Pix[off+0] = Y[yy+x] - Pix[off+1] = Cb[ci] - Pix[off+2] = Cr[ci] - off += 3 - } - } - return p -} - -func convertToYCC410(in *image.YCbCr, p *ycc) *ycc { - var off int - Pix := p.Pix - Y := in.Y - Cb := in.Cb - Cr := in.Cr - for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ { - yy := y * in.YStride - cy := (y / 2) * in.CStride - for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ { - ci := cy + x/4 - Pix[off+0] = Y[yy+x] - Pix[off+1] = Cb[ci] - Pix[off+2] = Cr[ci] - off += 3 - } - } - return p -} diff --git a/vendor/golang.org/x/image/AUTHORS b/vendor/golang.org/x/image/AUTHORS new file mode 100644 index 000000000..15167cd74 --- /dev/null +++ b/vendor/golang.org/x/image/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/image/CONTRIBUTORS b/vendor/golang.org/x/image/CONTRIBUTORS new file mode 100644 index 000000000..1c4577e96 --- /dev/null +++ b/vendor/golang.org/x/image/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/image/LICENSE b/vendor/golang.org/x/image/LICENSE new file mode 100644 index 000000000..6a66aea5e --- /dev/null +++ b/vendor/golang.org/x/image/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/image/PATENTS b/vendor/golang.org/x/image/PATENTS new file mode 100644 index 000000000..733099041 --- /dev/null +++ b/vendor/golang.org/x/image/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/image/bmp/reader.go b/vendor/golang.org/x/image/bmp/reader.go new file mode 100644 index 000000000..c10a022f6 --- /dev/null +++ b/vendor/golang.org/x/image/bmp/reader.go @@ -0,0 +1,213 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package bmp implements a BMP image decoder and encoder. +// +// The BMP specification is at http://www.digicamsoft.com/bmp/bmp.html. +package bmp // import "golang.org/x/image/bmp" + +import ( + "errors" + "image" + "image/color" + "io" +) + +// ErrUnsupported means that the input BMP image uses a valid but unsupported +// feature. +var ErrUnsupported = errors.New("bmp: unsupported BMP image") + +func readUint16(b []byte) uint16 { + return uint16(b[0]) | uint16(b[1])<<8 +} + +func readUint32(b []byte) uint32 { + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 +} + +// decodePaletted reads an 8 bit-per-pixel BMP image from r. +// If topDown is false, the image rows will be read bottom-up. +func decodePaletted(r io.Reader, c image.Config, topDown bool) (image.Image, error) { + paletted := image.NewPaletted(image.Rect(0, 0, c.Width, c.Height), c.ColorModel.(color.Palette)) + if c.Width == 0 || c.Height == 0 { + return paletted, nil + } + var tmp [4]byte + y0, y1, yDelta := c.Height-1, -1, -1 + if topDown { + y0, y1, yDelta = 0, c.Height, +1 + } + for y := y0; y != y1; y += yDelta { + p := paletted.Pix[y*paletted.Stride : y*paletted.Stride+c.Width] + if _, err := io.ReadFull(r, p); err != nil { + return nil, err + } + // Each row is 4-byte aligned. + if c.Width%4 != 0 { + _, err := io.ReadFull(r, tmp[:4-c.Width%4]) + if err != nil { + return nil, err + } + } + } + return paletted, nil +} + +// decodeRGB reads a 24 bit-per-pixel BMP image from r. +// If topDown is false, the image rows will be read bottom-up. +func decodeRGB(r io.Reader, c image.Config, topDown bool) (image.Image, error) { + rgba := image.NewRGBA(image.Rect(0, 0, c.Width, c.Height)) + if c.Width == 0 || c.Height == 0 { + return rgba, nil + } + // There are 3 bytes per pixel, and each row is 4-byte aligned. + b := make([]byte, (3*c.Width+3)&^3) + y0, y1, yDelta := c.Height-1, -1, -1 + if topDown { + y0, y1, yDelta = 0, c.Height, +1 + } + for y := y0; y != y1; y += yDelta { + if _, err := io.ReadFull(r, b); err != nil { + return nil, err + } + p := rgba.Pix[y*rgba.Stride : y*rgba.Stride+c.Width*4] + for i, j := 0, 0; i < len(p); i, j = i+4, j+3 { + // BMP images are stored in BGR order rather than RGB order. + p[i+0] = b[j+2] + p[i+1] = b[j+1] + p[i+2] = b[j+0] + p[i+3] = 0xFF + } + } + return rgba, nil +} + +// decodeNRGBA reads a 32 bit-per-pixel BMP image from r. +// If topDown is false, the image rows will be read bottom-up. +func decodeNRGBA(r io.Reader, c image.Config, topDown bool) (image.Image, error) { + rgba := image.NewNRGBA(image.Rect(0, 0, c.Width, c.Height)) + if c.Width == 0 || c.Height == 0 { + return rgba, nil + } + y0, y1, yDelta := c.Height-1, -1, -1 + if topDown { + y0, y1, yDelta = 0, c.Height, +1 + } + for y := y0; y != y1; y += yDelta { + p := rgba.Pix[y*rgba.Stride : y*rgba.Stride+c.Width*4] + if _, err := io.ReadFull(r, p); err != nil { + return nil, err + } + for i := 0; i < len(p); i += 4 { + // BMP images are stored in BGRA order rather than RGBA order. + p[i+0], p[i+2] = p[i+2], p[i+0] + } + } + return rgba, nil +} + +// Decode reads a BMP image from r and returns it as an image.Image. +// Limitation: The file must be 8, 24 or 32 bits per pixel. +func Decode(r io.Reader) (image.Image, error) { + c, bpp, topDown, err := decodeConfig(r) + if err != nil { + return nil, err + } + switch bpp { + case 8: + return decodePaletted(r, c, topDown) + case 24: + return decodeRGB(r, c, topDown) + case 32: + return decodeNRGBA(r, c, topDown) + } + panic("unreachable") +} + +// DecodeConfig returns the color model and dimensions of a BMP image without +// decoding the entire image. +// Limitation: The file must be 8, 24 or 32 bits per pixel. +func DecodeConfig(r io.Reader) (image.Config, error) { + config, _, _, err := decodeConfig(r) + return config, err +} + +func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown bool, err error) { + // We only support those BMP images that are a BITMAPFILEHEADER + // immediately followed by a BITMAPINFOHEADER. + const ( + fileHeaderLen = 14 + infoHeaderLen = 40 + v4InfoHeaderLen = 108 + v5InfoHeaderLen = 124 + ) + var b [1024]byte + if _, err := io.ReadFull(r, b[:fileHeaderLen+4]); err != nil { + return image.Config{}, 0, false, err + } + if string(b[:2]) != "BM" { + return image.Config{}, 0, false, errors.New("bmp: invalid format") + } + offset := readUint32(b[10:14]) + infoLen := readUint32(b[14:18]) + if infoLen != infoHeaderLen && infoLen != v4InfoHeaderLen && infoLen != v5InfoHeaderLen { + return image.Config{}, 0, false, ErrUnsupported + } + if _, err := io.ReadFull(r, b[fileHeaderLen+4:fileHeaderLen+infoLen]); err != nil { + return image.Config{}, 0, false, err + } + width := int(int32(readUint32(b[18:22]))) + height := int(int32(readUint32(b[22:26]))) + if height < 0 { + height, topDown = -height, true + } + if width < 0 || height < 0 { + return image.Config{}, 0, false, ErrUnsupported + } + // We only support 1 plane and 8, 24 or 32 bits per pixel and no + // compression. + planes, bpp, compression := readUint16(b[26:28]), readUint16(b[28:30]), readUint32(b[30:34]) + // if compression is set to BITFIELDS, but the bitmask is set to the default bitmask + // that would be used if compression was set to 0, we can continue as if compression was 0 + if compression == 3 && infoLen > infoHeaderLen && + readUint32(b[54:58]) == 0xff0000 && readUint32(b[58:62]) == 0xff00 && + readUint32(b[62:66]) == 0xff && readUint32(b[66:70]) == 0xff000000 { + compression = 0 + } + if planes != 1 || compression != 0 { + return image.Config{}, 0, false, ErrUnsupported + } + switch bpp { + case 8: + if offset != fileHeaderLen+infoLen+256*4 { + return image.Config{}, 0, false, ErrUnsupported + } + _, err = io.ReadFull(r, b[:256*4]) + if err != nil { + return image.Config{}, 0, false, err + } + pcm := make(color.Palette, 256) + for i := range pcm { + // BMP images are stored in BGR order rather than RGB order. + // Every 4th byte is padding. + pcm[i] = color.RGBA{b[4*i+2], b[4*i+1], b[4*i+0], 0xFF} + } + return image.Config{ColorModel: pcm, Width: width, Height: height}, 8, topDown, nil + case 24: + if offset != fileHeaderLen+infoLen { + return image.Config{}, 0, false, ErrUnsupported + } + return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 24, topDown, nil + case 32: + if offset != fileHeaderLen+infoLen { + return image.Config{}, 0, false, ErrUnsupported + } + return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 32, topDown, nil + } + return image.Config{}, 0, false, ErrUnsupported +} + +func init() { + image.RegisterFormat("bmp", "BM????\x00\x00\x00\x00", Decode, DecodeConfig) +} diff --git a/vendor/golang.org/x/image/bmp/writer.go b/vendor/golang.org/x/image/bmp/writer.go new file mode 100644 index 000000000..f07b39dba --- /dev/null +++ b/vendor/golang.org/x/image/bmp/writer.go @@ -0,0 +1,262 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bmp + +import ( + "encoding/binary" + "errors" + "image" + "io" +) + +type header struct { + sigBM [2]byte + fileSize uint32 + resverved [2]uint16 + pixOffset uint32 + dibHeaderSize uint32 + width uint32 + height uint32 + colorPlane uint16 + bpp uint16 + compression uint32 + imageSize uint32 + xPixelsPerMeter uint32 + yPixelsPerMeter uint32 + colorUse uint32 + colorImportant uint32 +} + +func encodePaletted(w io.Writer, pix []uint8, dx, dy, stride, step int) error { + var padding []byte + if dx < step { + padding = make([]byte, step-dx) + } + for y := dy - 1; y >= 0; y-- { + min := y*stride + 0 + max := y*stride + dx + if _, err := w.Write(pix[min:max]); err != nil { + return err + } + if padding != nil { + if _, err := w.Write(padding); err != nil { + return err + } + } + } + return nil +} + +func encodeRGBA(w io.Writer, pix []uint8, dx, dy, stride, step int, opaque bool) error { + buf := make([]byte, step) + if opaque { + for y := dy - 1; y >= 0; y-- { + min := y*stride + 0 + max := y*stride + dx*4 + off := 0 + for i := min; i < max; i += 4 { + buf[off+2] = pix[i+0] + buf[off+1] = pix[i+1] + buf[off+0] = pix[i+2] + off += 3 + } + if _, err := w.Write(buf); err != nil { + return err + } + } + } else { + for y := dy - 1; y >= 0; y-- { + min := y*stride + 0 + max := y*stride + dx*4 + off := 0 + for i := min; i < max; i += 4 { + a := uint32(pix[i+3]) + if a == 0 { + buf[off+2] = 0 + buf[off+1] = 0 + buf[off+0] = 0 + buf[off+3] = 0 + off += 4 + continue + } else if a == 0xff { + buf[off+2] = pix[i+0] + buf[off+1] = pix[i+1] + buf[off+0] = pix[i+2] + buf[off+3] = 0xff + off += 4 + continue + } + buf[off+2] = uint8(((uint32(pix[i+0]) * 0xffff) / a) >> 8) + buf[off+1] = uint8(((uint32(pix[i+1]) * 0xffff) / a) >> 8) + buf[off+0] = uint8(((uint32(pix[i+2]) * 0xffff) / a) >> 8) + buf[off+3] = uint8(a) + off += 4 + } + if _, err := w.Write(buf); err != nil { + return err + } + } + } + return nil +} + +func encodeNRGBA(w io.Writer, pix []uint8, dx, dy, stride, step int, opaque bool) error { + buf := make([]byte, step) + if opaque { + for y := dy - 1; y >= 0; y-- { + min := y*stride + 0 + max := y*stride + dx*4 + off := 0 + for i := min; i < max; i += 4 { + buf[off+2] = pix[i+0] + buf[off+1] = pix[i+1] + buf[off+0] = pix[i+2] + off += 3 + } + if _, err := w.Write(buf); err != nil { + return err + } + } + } else { + for y := dy - 1; y >= 0; y-- { + min := y*stride + 0 + max := y*stride + dx*4 + off := 0 + for i := min; i < max; i += 4 { + buf[off+2] = pix[i+0] + buf[off+1] = pix[i+1] + buf[off+0] = pix[i+2] + buf[off+3] = pix[i+3] + off += 4 + } + if _, err := w.Write(buf); err != nil { + return err + } + } + } + return nil +} + +func encode(w io.Writer, m image.Image, step int) error { + b := m.Bounds() + buf := make([]byte, step) + for y := b.Max.Y - 1; y >= b.Min.Y; y-- { + off := 0 + for x := b.Min.X; x < b.Max.X; x++ { + r, g, b, _ := m.At(x, y).RGBA() + buf[off+2] = byte(r >> 8) + buf[off+1] = byte(g >> 8) + buf[off+0] = byte(b >> 8) + off += 3 + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +// Encode writes the image m to w in BMP format. +func Encode(w io.Writer, m image.Image) error { + d := m.Bounds().Size() + if d.X < 0 || d.Y < 0 { + return errors.New("bmp: negative bounds") + } + h := &header{ + sigBM: [2]byte{'B', 'M'}, + fileSize: 14 + 40, + pixOffset: 14 + 40, + dibHeaderSize: 40, + width: uint32(d.X), + height: uint32(d.Y), + colorPlane: 1, + } + + var step int + var palette []byte + var opaque bool + switch m := m.(type) { + case *image.Gray: + step = (d.X + 3) &^ 3 + palette = make([]byte, 1024) + for i := 0; i < 256; i++ { + palette[i*4+0] = uint8(i) + palette[i*4+1] = uint8(i) + palette[i*4+2] = uint8(i) + palette[i*4+3] = 0xFF + } + h.imageSize = uint32(d.Y * step) + h.fileSize += uint32(len(palette)) + h.imageSize + h.pixOffset += uint32(len(palette)) + h.bpp = 8 + + case *image.Paletted: + step = (d.X + 3) &^ 3 + palette = make([]byte, 1024) + for i := 0; i < len(m.Palette) && i < 256; i++ { + r, g, b, _ := m.Palette[i].RGBA() + palette[i*4+0] = uint8(b >> 8) + palette[i*4+1] = uint8(g >> 8) + palette[i*4+2] = uint8(r >> 8) + palette[i*4+3] = 0xFF + } + h.imageSize = uint32(d.Y * step) + h.fileSize += uint32(len(palette)) + h.imageSize + h.pixOffset += uint32(len(palette)) + h.bpp = 8 + case *image.RGBA: + opaque = m.Opaque() + if opaque { + step = (3*d.X + 3) &^ 3 + h.bpp = 24 + } else { + step = 4 * d.X + h.bpp = 32 + } + h.imageSize = uint32(d.Y * step) + h.fileSize += h.imageSize + case *image.NRGBA: + opaque = m.Opaque() + if opaque { + step = (3*d.X + 3) &^ 3 + h.bpp = 24 + } else { + step = 4 * d.X + h.bpp = 32 + } + h.imageSize = uint32(d.Y * step) + h.fileSize += h.imageSize + default: + step = (3*d.X + 3) &^ 3 + h.imageSize = uint32(d.Y * step) + h.fileSize += h.imageSize + h.bpp = 24 + } + + if err := binary.Write(w, binary.LittleEndian, h); err != nil { + return err + } + if palette != nil { + if err := binary.Write(w, binary.LittleEndian, palette); err != nil { + return err + } + } + + if d.X == 0 || d.Y == 0 { + return nil + } + + switch m := m.(type) { + case *image.Gray: + return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step) + case *image.Paletted: + return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step) + case *image.RGBA: + return encodeRGBA(w, m.Pix, d.X, d.Y, m.Stride, step, opaque) + case *image.NRGBA: + return encodeNRGBA(w, m.Pix, d.X, d.Y, m.Stride, step, opaque) + } + return encode(w, m, step) +} diff --git a/vendor/golang.org/x/image/ccitt/reader.go b/vendor/golang.org/x/image/ccitt/reader.go new file mode 100644 index 000000000..16bd495d5 --- /dev/null +++ b/vendor/golang.org/x/image/ccitt/reader.go @@ -0,0 +1,697 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:generate go run gen.go + +// Package ccitt implements a CCITT (fax) image decoder. +package ccitt + +import ( + "encoding/binary" + "errors" + "image" + "io" + "math/bits" +) + +var ( + errInvalidBounds = errors.New("ccitt: invalid bounds") + errInvalidCode = errors.New("ccitt: invalid code") + errInvalidMode = errors.New("ccitt: invalid mode") + errInvalidOffset = errors.New("ccitt: invalid offset") + errMissingEOL = errors.New("ccitt: missing End-of-Line") + errRunLengthOverflowsWidth = errors.New("ccitt: run length overflows width") + errRunLengthTooLong = errors.New("ccitt: run length too long") + errUnsupportedMode = errors.New("ccitt: unsupported mode") + errUnsupportedSubFormat = errors.New("ccitt: unsupported sub-format") + errUnsupportedWidth = errors.New("ccitt: unsupported width") +) + +// Order specifies the bit ordering in a CCITT data stream. +type Order uint32 + +const ( + // LSB means Least Significant Bits first. + LSB Order = iota + // MSB means Most Significant Bits first. + MSB +) + +// SubFormat represents that the CCITT format consists of a number of +// sub-formats. Decoding or encoding a CCITT data stream requires knowing the +// sub-format context. It is not represented in the data stream per se. +type SubFormat uint32 + +const ( + Group3 SubFormat = iota + Group4 +) + +// Options are optional parameters. +type Options struct { + // Align means that some variable-bit-width codes are byte-aligned. + Align bool + // Invert means that black is the 1 bit or 0xFF byte, and white is 0. + Invert bool +} + +// maxWidth is the maximum (inclusive) supported width. This is a limitation of +// this implementation, to guard against integer overflow, and not anything +// inherent to the CCITT format. +const maxWidth = 1 << 20 + +func invertBytes(b []byte) { + for i, c := range b { + b[i] = ^c + } +} + +func reverseBitsWithinBytes(b []byte) { + for i, c := range b { + b[i] = bits.Reverse8(c) + } +} + +// highBits writes to dst (1 bit per pixel, most significant bit first) the +// high (0x80) bits from src (1 byte per pixel). It returns the number of bytes +// written and read such that dst[:d] is the packed form of src[:s]. +// +// For example, if src starts with the 8 bytes [0x7D, 0x7E, 0x7F, 0x80, 0x81, +// 0x82, 0x00, 0xFF] then 0x1D will be written to dst[0]. +// +// If src has (8 * len(dst)) or more bytes then only len(dst) bytes are +// written, (8 * len(dst)) bytes are read, and invert is ignored. +// +// Otherwise, if len(src) is not a multiple of 8 then the final byte written to +// dst is padded with 1 bits (if invert is true) or 0 bits. If inverted, the 1s +// are typically temporary, e.g. they will be flipped back to 0s by an +// invertBytes call in the highBits caller, reader.Read. +func highBits(dst []byte, src []byte, invert bool) (d int, s int) { + // Pack as many complete groups of 8 src bytes as we can. + n := len(src) / 8 + if n > len(dst) { + n = len(dst) + } + dstN := dst[:n] + for i := range dstN { + src8 := src[i*8 : i*8+8] + dstN[i] = ((src8[0] & 0x80) >> 0) | + ((src8[1] & 0x80) >> 1) | + ((src8[2] & 0x80) >> 2) | + ((src8[3] & 0x80) >> 3) | + ((src8[4] & 0x80) >> 4) | + ((src8[5] & 0x80) >> 5) | + ((src8[6] & 0x80) >> 6) | + ((src8[7] & 0x80) >> 7) + } + d, s = n, 8*n + dst, src = dst[d:], src[s:] + + // Pack up to 7 remaining src bytes, if there's room in dst. + if (len(dst) > 0) && (len(src) > 0) { + dstByte := byte(0) + if invert { + dstByte = 0xFF >> uint(len(src)) + } + for n, srcByte := range src { + dstByte |= (srcByte & 0x80) >> uint(n) + } + dst[0] = dstByte + d, s = d+1, s+len(src) + } + return d, s +} + +type bitReader struct { + r io.Reader + + // readErr is the error returned from the most recent r.Read call. As the + // io.Reader documentation says, when r.Read returns (n, err), "always + // process the n > 0 bytes returned before considering the error err". + readErr error + + // order is whether to process r's bytes LSB first or MSB first. + order Order + + // The high nBits bits of the bits field hold upcoming bits in MSB order. + bits uint64 + nBits uint32 + + // bytes[br:bw] holds bytes read from r but not yet loaded into bits. + br uint32 + bw uint32 + bytes [1024]uint8 +} + +func (b *bitReader) alignToByteBoundary() { + n := b.nBits & 7 + b.bits <<= n + b.nBits -= n +} + +// nextBitMaxNBits is the maximum possible value of bitReader.nBits after a +// bitReader.nextBit call, provided that bitReader.nBits was not more than this +// value before that call. +// +// Note that the decode function can unread bits, which can temporarily set the +// bitReader.nBits value above nextBitMaxNBits. +const nextBitMaxNBits = 31 + +func (b *bitReader) nextBit() (uint64, error) { + for { + if b.nBits > 0 { + bit := b.bits >> 63 + b.bits <<= 1 + b.nBits-- + return bit, nil + } + + if available := b.bw - b.br; available >= 4 { + // Read 32 bits, even though b.bits is a uint64, since the decode + // function may need to unread up to maxCodeLength bits, putting + // them back in the remaining (64 - 32) bits. TestMaxCodeLength + // checks that the generated maxCodeLength constant fits. + // + // If changing the Uint32 call, also change nextBitMaxNBits. + b.bits = uint64(binary.BigEndian.Uint32(b.bytes[b.br:])) << 32 + b.br += 4 + b.nBits = 32 + continue + } else if available > 0 { + b.bits = uint64(b.bytes[b.br]) << (7 * 8) + b.br++ + b.nBits = 8 + continue + } + + if b.readErr != nil { + return 0, b.readErr + } + + n, err := b.r.Read(b.bytes[:]) + b.br = 0 + b.bw = uint32(n) + b.readErr = err + + if b.order != MSB { + reverseBitsWithinBytes(b.bytes[:b.bw]) + } + } +} + +func decode(b *bitReader, decodeTable [][2]int16) (uint32, error) { + nBitsRead, bitsRead, state := uint32(0), uint64(0), int32(1) + for { + bit, err := b.nextBit() + if err != nil { + return 0, err + } + bitsRead |= bit << (63 - nBitsRead) + nBitsRead++ + // The "&1" is redundant, but can eliminate a bounds check. + state = int32(decodeTable[state][bit&1]) + if state < 0 { + return uint32(^state), nil + } else if state == 0 { + // Unread the bits we've read, then return errInvalidCode. + b.bits = (b.bits >> nBitsRead) | bitsRead + b.nBits += nBitsRead + return 0, errInvalidCode + } + } +} + +type reader struct { + br bitReader + subFormat SubFormat + + // width is the image width in pixels. + width int + + // rowsRemaining starts at the image height in pixels, when the reader is + // driven through the io.Reader interface, and decrements to zero as rows + // are decoded. When driven through DecodeIntoGray, this field is unused. + rowsRemaining int + + // curr and prev hold the current and previous rows. Each element is either + // 0x00 (black) or 0xFF (white). + // + // prev may be nil, when processing the first row. + curr []byte + prev []byte + + // ri is the read index. curr[:ri] are those bytes of curr that have been + // passed along via the Read method. + // + // When the reader is driven through DecodeIntoGray, instead of through the + // io.Reader interface, this field is unused. + ri int + + // wi is the write index. curr[:wi] are those bytes of curr that have + // already been decoded via the decodeRow method. + // + // What this implementation calls wi is roughly equivalent to what the spec + // calls the a0 index. + wi int + + // These fields are copied from the *Options (which may be nil). + align bool + invert bool + + // atStartOfRow is whether we have just started the row. Some parts of the + // spec say to treat this situation as if "wi = -1". + atStartOfRow bool + + // penColorIsWhite is whether the next run is black or white. + penColorIsWhite bool + + // seenStartOfImage is whether we've called the startDecode method. + seenStartOfImage bool + + // readErr is a sticky error for the Read method. + readErr error +} + +func (z *reader) Read(p []byte) (int, error) { + if z.readErr != nil { + return 0, z.readErr + } + originalP := p + + for len(p) > 0 { + // Allocate buffers (and decode any start-of-image codes), if + // processing the first or second row. + if z.curr == nil { + if !z.seenStartOfImage { + if z.readErr = z.startDecode(); z.readErr != nil { + break + } + z.atStartOfRow = true + } + z.curr = make([]byte, z.width) + } + + // Decode the next row, if necessary. + if z.atStartOfRow { + if z.rowsRemaining <= 0 { + if z.readErr = z.finishDecode(); z.readErr != nil { + break + } + z.readErr = io.EOF + break + } + if z.readErr = z.decodeRow(); z.readErr != nil { + break + } + z.rowsRemaining-- + } + + // Pack from z.curr (1 byte per pixel) to p (1 bit per pixel). + packD, packS := highBits(p, z.curr[z.ri:], z.invert) + p = p[packD:] + z.ri += packS + + // Prepare to decode the next row, if necessary. + if z.ri == len(z.curr) { + z.ri, z.curr, z.prev = 0, z.prev, z.curr + z.atStartOfRow = true + } + } + + n := len(originalP) - len(p) + if z.invert { + invertBytes(originalP[:n]) + } + return n, z.readErr +} + +func (z *reader) penColor() byte { + if z.penColorIsWhite { + return 0xFF + } + return 0x00 +} + +func (z *reader) startDecode() error { + switch z.subFormat { + case Group3: + if err := z.decodeEOL(); err != nil { + return err + } + + case Group4: + // No-op. + + default: + return errUnsupportedSubFormat + } + + z.seenStartOfImage = true + return nil +} + +func (z *reader) finishDecode() error { + numberOfEOLs := 0 + switch z.subFormat { + case Group3: + // The stream ends with a RTC (Return To Control) of 6 consecutive + // EOL's, but we should have already just seen an EOL, either in + // z.startDecode (for a zero-height image) or in z.decodeRow. + numberOfEOLs = 5 + + case Group4: + // The stream ends with two EOL's, the first of which is possibly + // byte-aligned. + numberOfEOLs = 2 + if err := z.decodeEOL(); err == nil { + numberOfEOLs-- + } else if err == errInvalidCode { + // Try again, this time starting from a byte boundary. + z.br.alignToByteBoundary() + } else { + return err + } + + default: + return errUnsupportedSubFormat + } + + for ; numberOfEOLs > 0; numberOfEOLs-- { + if err := z.decodeEOL(); err != nil { + return err + } + } + return nil +} + +func (z *reader) decodeEOL() error { + // TODO: EOL doesn't have to be in the modeDecodeTable. It could be in its + // own table, or we could just hard-code it, especially if we might need to + // cater for optional byte-alignment, or an arbitrary number (potentially + // more than 8) of 0-valued padding bits. + if mode, err := decode(&z.br, modeDecodeTable[:]); err != nil { + return err + } else if mode != modeEOL { + return errMissingEOL + } + return nil +} + +func (z *reader) decodeRow() error { + z.wi = 0 + z.atStartOfRow = true + z.penColorIsWhite = true + + if z.align { + z.br.alignToByteBoundary() + } + + switch z.subFormat { + case Group3: + for ; z.wi < len(z.curr); z.atStartOfRow = false { + if err := z.decodeRun(); err != nil { + return err + } + } + return z.decodeEOL() + + case Group4: + for ; z.wi < len(z.curr); z.atStartOfRow = false { + mode, err := decode(&z.br, modeDecodeTable[:]) + if err != nil { + return err + } + rm := readerMode{} + if mode < uint32(len(readerModes)) { + rm = readerModes[mode] + } + if rm.function == nil { + return errInvalidMode + } + if err := rm.function(z, rm.arg); err != nil { + return err + } + } + return nil + } + + return errUnsupportedSubFormat +} + +func (z *reader) decodeRun() error { + table := blackDecodeTable[:] + if z.penColorIsWhite { + table = whiteDecodeTable[:] + } + + total := 0 + for { + n, err := decode(&z.br, table) + if err != nil { + return err + } + if n > maxWidth { + panic("unreachable") + } + total += int(n) + if total > maxWidth { + return errRunLengthTooLong + } + // Anything 0x3F or below is a terminal code. + if n <= 0x3F { + break + } + } + + if total > (len(z.curr) - z.wi) { + return errRunLengthOverflowsWidth + } + dst := z.curr[z.wi : z.wi+total] + penColor := z.penColor() + for i := range dst { + dst[i] = penColor + } + z.wi += total + z.penColorIsWhite = !z.penColorIsWhite + + return nil +} + +// The various modes' semantics are based on determining a row of pixels' +// "changing elements": those pixels whose color differs from the one on its +// immediate left. +// +// The row above the first row is implicitly all white. Similarly, the column +// to the left of the first column is implicitly all white. +// +// For example, here's Figure 1 in "ITU-T Recommendation T.6", where the +// current and previous rows contain black (B) and white (w) pixels. The a? +// indexes point into curr, the b? indexes point into prev. +// +// b1 b2 +// v v +// prev: BBBBBwwwwwBBBwwwww +// curr: BBBwwwwwBBBBBBwwww +// ^ ^ ^ +// a0 a1 a2 +// +// a0 is the "reference element" or current decoder position, roughly +// equivalent to what this implementation calls reader.wi. +// +// a1 is the next changing element to the right of a0, on the "coding line" +// (the current row). +// +// a2 is the next changing element to the right of a1, again on curr. +// +// b1 is the first changing element on the "reference line" (the previous row) +// to the right of a0 and of opposite color to a0. +// +// b2 is the next changing element to the right of b1, again on prev. +// +// The various modes calculate a1 (and a2, for modeH): +// - modePass calculates that a1 is at or to the right of b2. +// - modeH calculates a1 and a2 without considering b1 or b2. +// - modeV* calculates a1 to be b1 plus an adjustment (between -3 and +3). + +const ( + findB1 = false + findB2 = true +) + +// findB finds either the b1 or b2 value. +func (z *reader) findB(whichB bool) int { + // The initial row is a special case. The previous row is implicitly all + // white, so that there are no changing pixel elements. We return b1 or b2 + // to be at the end of the row. + if len(z.prev) != len(z.curr) { + return len(z.curr) + } + + i := z.wi + + if z.atStartOfRow { + // a0 is implicitly at -1, on a white pixel. b1 is the first black + // pixel in the previous row. b2 is the first white pixel after that. + for ; (i < len(z.prev)) && (z.prev[i] == 0xFF); i++ { + } + if whichB == findB2 { + for ; (i < len(z.prev)) && (z.prev[i] == 0x00); i++ { + } + } + return i + } + + // As per figure 1 above, assume that the current pen color is white. + // First, walk past every contiguous black pixel in prev, starting at a0. + oppositeColor := ^z.penColor() + for ; (i < len(z.prev)) && (z.prev[i] == oppositeColor); i++ { + } + + // Then walk past every contiguous white pixel. + penColor := ^oppositeColor + for ; (i < len(z.prev)) && (z.prev[i] == penColor); i++ { + } + + // We're now at a black pixel (or at the end of the row). That's b1. + if whichB == findB2 { + // If we're looking for b2, walk past every contiguous black pixel + // again. + oppositeColor := ^penColor + for ; (i < len(z.prev)) && (z.prev[i] == oppositeColor); i++ { + } + } + + return i +} + +type readerMode struct { + function func(z *reader, arg int) error + arg int +} + +var readerModes = [...]readerMode{ + modePass: {function: readerModePass}, + modeH: {function: readerModeH}, + modeV0: {function: readerModeV, arg: +0}, + modeVR1: {function: readerModeV, arg: +1}, + modeVR2: {function: readerModeV, arg: +2}, + modeVR3: {function: readerModeV, arg: +3}, + modeVL1: {function: readerModeV, arg: -1}, + modeVL2: {function: readerModeV, arg: -2}, + modeVL3: {function: readerModeV, arg: -3}, + modeExt: {function: readerModeExt}, +} + +func readerModePass(z *reader, arg int) error { + b2 := z.findB(findB2) + if (b2 < z.wi) || (len(z.curr) < b2) { + return errInvalidOffset + } + dst := z.curr[z.wi:b2] + penColor := z.penColor() + for i := range dst { + dst[i] = penColor + } + z.wi = b2 + return nil +} + +func readerModeH(z *reader, arg int) error { + // The first iteration finds a1. The second finds a2. + for i := 0; i < 2; i++ { + if err := z.decodeRun(); err != nil { + return err + } + } + return nil +} + +func readerModeV(z *reader, arg int) error { + a1 := z.findB(findB1) + arg + if (a1 < z.wi) || (len(z.curr) < a1) { + return errInvalidOffset + } + dst := z.curr[z.wi:a1] + penColor := z.penColor() + for i := range dst { + dst[i] = penColor + } + z.wi = a1 + z.penColorIsWhite = !z.penColorIsWhite + return nil +} + +func readerModeExt(z *reader, arg int) error { + return errUnsupportedMode +} + +// DecodeIntoGray decodes the CCITT-formatted data in r into dst. +// +// It returns an error if dst's width and height don't match the implied width +// and height of CCITT-formatted data. +func DecodeIntoGray(dst *image.Gray, r io.Reader, order Order, sf SubFormat, opts *Options) error { + bounds := dst.Bounds() + if (bounds.Dx() < 0) || (bounds.Dy() < 0) { + return errInvalidBounds + } + if bounds.Dx() > maxWidth { + return errUnsupportedWidth + } + + z := reader{ + br: bitReader{r: r, order: order}, + subFormat: sf, + align: (opts != nil) && opts.Align, + invert: (opts != nil) && opts.Invert, + width: bounds.Dx(), + } + if err := z.startDecode(); err != nil { + return err + } + + width := bounds.Dx() + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + p := (y - bounds.Min.Y) * dst.Stride + z.curr = dst.Pix[p : p+width] + if err := z.decodeRow(); err != nil { + return err + } + z.curr, z.prev = nil, z.curr + } + + if err := z.finishDecode(); err != nil { + return err + } + + if z.invert { + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + p := (y - bounds.Min.Y) * dst.Stride + invertBytes(dst.Pix[p : p+width]) + } + } + + return nil +} + +// NewReader returns an io.Reader that decodes the CCITT-formatted data in r. +// The resultant byte stream is one bit per pixel (MSB first), with 1 meaning +// white and 0 meaning black. Each row in the result is byte-aligned. +func NewReader(r io.Reader, order Order, sf SubFormat, width int, height int, opts *Options) io.Reader { + readErr := error(nil) + if (width < 0) || (height < 0) { + readErr = errInvalidBounds + } else if width > maxWidth { + readErr = errUnsupportedWidth + } + + return &reader{ + br: bitReader{r: r, order: order}, + subFormat: sf, + align: (opts != nil) && opts.Align, + invert: (opts != nil) && opts.Invert, + width: width, + rowsRemaining: height, + readErr: readErr, + } +} diff --git a/vendor/golang.org/x/image/ccitt/table.go b/vendor/golang.org/x/image/ccitt/table.go new file mode 100644 index 000000000..f01cc12b5 --- /dev/null +++ b/vendor/golang.org/x/image/ccitt/table.go @@ -0,0 +1,989 @@ +// generated by "go run gen.go". DO NOT EDIT. + +package ccitt + +// Each decodeTable is represented by an array of [2]int16's: a binary tree. +// Each array element (other than element 0, which means invalid) is a branch +// node in that tree. The root node is always element 1 (the second element). +// +// To walk the tree, look at the next bit in the bit stream, using it to select +// the first or second element of the [2]int16. If that int16 is 0, we have an +// invalid code. If it is positive, go to that branch node. If it is negative, +// then we have a leaf node, whose value is the bitwise complement (the ^ +// operator) of that int16. +// +// Comments above each decodeTable also show the same structure visually. The +// "b123" lines show the 123'rd branch node. The "=XXXXX" lines show an invalid +// code. The "=v1234" lines show a leaf node with value 1234. When reading the +// bit stream, a 0 or 1 bit means to go up or down, as you move left to right. +// +// For example, in modeDecodeTable, branch node b005 is three steps up from the +// root node, meaning that we have already seen "000". If the next bit is "0" +// then we move to branch node b006. Otherwise, the next bit is "1", and we +// move to the leaf node v0000 (also known as the modePass constant). Indeed, +// the bits that encode modePass are "0001". +// +// Tables 1, 2 and 3 come from the "ITU-T Recommendation T.6: FACSIMILE CODING +// SCHEMES AND CODING CONTROL FUNCTIONS FOR GROUP 4 FACSIMILE APPARATUS" +// specification: +// +// https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-T.6-198811-I!!PDF-E&type=items + +// modeDecodeTable represents Table 1 and the End-of-Line code. +// +// +=XXXXX +// b015 +-+ +// | +=v0010 +// b014 +-+ +// | +=XXXXX +// b013 +-+ +// | +=XXXXX +// b012 +-+ +// | +=XXXXX +// b011 +-+ +// | +=XXXXX +// b009 +-+ +// | +=v0009 +// b007 +-+ +// | | +=v0008 +// b010 | +-+ +// | +=v0005 +// b006 +-+ +// | | +=v0007 +// b008 | +-+ +// | +=v0004 +// b005 +-+ +// | +=v0000 +// b003 +-+ +// | +=v0001 +// b002 +-+ +// | | +=v0006 +// b004 | +-+ +// | +=v0003 +// b001 +-+ +// +=v0002 +var modeDecodeTable = [...][2]int16{ + 0: {0, 0}, + 1: {2, ^2}, + 2: {3, 4}, + 3: {5, ^1}, + 4: {^6, ^3}, + 5: {6, ^0}, + 6: {7, 8}, + 7: {9, 10}, + 8: {^7, ^4}, + 9: {11, ^9}, + 10: {^8, ^5}, + 11: {12, 0}, + 12: {13, 0}, + 13: {14, 0}, + 14: {15, 0}, + 15: {0, ^10}, +} + +// whiteDecodeTable represents Tables 2 and 3 for a white run. +// +// +=XXXXX +// b059 +-+ +// | | +=v1792 +// b096 | | +-+ +// | | | | +=v1984 +// b100 | | | +-+ +// | | | +=v2048 +// b094 | | +-+ +// | | | | +=v2112 +// b101 | | | | +-+ +// | | | | | +=v2176 +// b097 | | | +-+ +// | | | | +=v2240 +// b102 | | | +-+ +// | | | +=v2304 +// b085 | +-+ +// | | +=v1856 +// b098 | | +-+ +// | | | +=v1920 +// b095 | +-+ +// | | +=v2368 +// b103 | | +-+ +// | | | +=v2432 +// b099 | +-+ +// | | +=v2496 +// b104 | +-+ +// | +=v2560 +// b040 +-+ +// | | +=v0029 +// b060 | +-+ +// | +=v0030 +// b026 +-+ +// | | +=v0045 +// b061 | | +-+ +// | | | +=v0046 +// b041 | +-+ +// | +=v0022 +// b016 +-+ +// | | +=v0023 +// b042 | | +-+ +// | | | | +=v0047 +// b062 | | | +-+ +// | | | +=v0048 +// b027 | +-+ +// | +=v0013 +// b008 +-+ +// | | +=v0020 +// b043 | | +-+ +// | | | | +=v0033 +// b063 | | | +-+ +// | | | +=v0034 +// b028 | | +-+ +// | | | | +=v0035 +// b064 | | | | +-+ +// | | | | | +=v0036 +// b044 | | | +-+ +// | | | | +=v0037 +// b065 | | | +-+ +// | | | +=v0038 +// b017 | +-+ +// | | +=v0019 +// b045 | | +-+ +// | | | | +=v0031 +// b066 | | | +-+ +// | | | +=v0032 +// b029 | +-+ +// | +=v0001 +// b004 +-+ +// | | +=v0012 +// b030 | | +-+ +// | | | | +=v0053 +// b067 | | | | +-+ +// | | | | | +=v0054 +// b046 | | | +-+ +// | | | +=v0026 +// b018 | | +-+ +// | | | | +=v0039 +// b068 | | | | +-+ +// | | | | | +=v0040 +// b047 | | | | +-+ +// | | | | | | +=v0041 +// b069 | | | | | +-+ +// | | | | | +=v0042 +// b031 | | | +-+ +// | | | | +=v0043 +// b070 | | | | +-+ +// | | | | | +=v0044 +// b048 | | | +-+ +// | | | +=v0021 +// b009 | +-+ +// | | +=v0028 +// b049 | | +-+ +// | | | | +=v0061 +// b071 | | | +-+ +// | | | +=v0062 +// b032 | | +-+ +// | | | | +=v0063 +// b072 | | | | +-+ +// | | | | | +=v0000 +// b050 | | | +-+ +// | | | | +=v0320 +// b073 | | | +-+ +// | | | +=v0384 +// b019 | +-+ +// | +=v0010 +// b002 +-+ +// | | +=v0011 +// b020 | | +-+ +// | | | | +=v0027 +// b051 | | | | +-+ +// | | | | | | +=v0059 +// b074 | | | | | +-+ +// | | | | | +=v0060 +// b033 | | | +-+ +// | | | | +=v1472 +// b086 | | | | +-+ +// | | | | | +=v1536 +// b075 | | | | +-+ +// | | | | | | +=v1600 +// b087 | | | | | +-+ +// | | | | | +=v1728 +// b052 | | | +-+ +// | | | +=v0018 +// b010 | | +-+ +// | | | | +=v0024 +// b053 | | | | +-+ +// | | | | | | +=v0049 +// b076 | | | | | +-+ +// | | | | | +=v0050 +// b034 | | | | +-+ +// | | | | | | +=v0051 +// b077 | | | | | | +-+ +// | | | | | | | +=v0052 +// b054 | | | | | +-+ +// | | | | | +=v0025 +// b021 | | | +-+ +// | | | | +=v0055 +// b078 | | | | +-+ +// | | | | | +=v0056 +// b055 | | | | +-+ +// | | | | | | +=v0057 +// b079 | | | | | +-+ +// | | | | | +=v0058 +// b035 | | | +-+ +// | | | +=v0192 +// b005 | +-+ +// | | +=v1664 +// b036 | | +-+ +// | | | | +=v0448 +// b080 | | | | +-+ +// | | | | | +=v0512 +// b056 | | | +-+ +// | | | | +=v0704 +// b088 | | | | +-+ +// | | | | | +=v0768 +// b081 | | | +-+ +// | | | +=v0640 +// b022 | | +-+ +// | | | | +=v0576 +// b082 | | | | +-+ +// | | | | | | +=v0832 +// b089 | | | | | +-+ +// | | | | | +=v0896 +// b057 | | | | +-+ +// | | | | | | +=v0960 +// b090 | | | | | | +-+ +// | | | | | | | +=v1024 +// b083 | | | | | +-+ +// | | | | | | +=v1088 +// b091 | | | | | +-+ +// | | | | | +=v1152 +// b037 | | | +-+ +// | | | | +=v1216 +// b092 | | | | +-+ +// | | | | | +=v1280 +// b084 | | | | +-+ +// | | | | | | +=v1344 +// b093 | | | | | +-+ +// | | | | | +=v1408 +// b058 | | | +-+ +// | | | +=v0256 +// b011 | +-+ +// | +=v0002 +// b001 +-+ +// | +=v0003 +// b012 | +-+ +// | | | +=v0128 +// b023 | | +-+ +// | | +=v0008 +// b006 | +-+ +// | | | +=v0009 +// b024 | | | +-+ +// | | | | | +=v0016 +// b038 | | | | +-+ +// | | | | +=v0017 +// b013 | | +-+ +// | | +=v0004 +// b003 +-+ +// | +=v0005 +// b014 | +-+ +// | | | +=v0014 +// b039 | | | +-+ +// | | | | +=v0015 +// b025 | | +-+ +// | | +=v0064 +// b007 +-+ +// | +=v0006 +// b015 +-+ +// +=v0007 +var whiteDecodeTable = [...][2]int16{ + 0: {0, 0}, + 1: {2, 3}, + 2: {4, 5}, + 3: {6, 7}, + 4: {8, 9}, + 5: {10, 11}, + 6: {12, 13}, + 7: {14, 15}, + 8: {16, 17}, + 9: {18, 19}, + 10: {20, 21}, + 11: {22, ^2}, + 12: {^3, 23}, + 13: {24, ^4}, + 14: {^5, 25}, + 15: {^6, ^7}, + 16: {26, 27}, + 17: {28, 29}, + 18: {30, 31}, + 19: {32, ^10}, + 20: {^11, 33}, + 21: {34, 35}, + 22: {36, 37}, + 23: {^128, ^8}, + 24: {^9, 38}, + 25: {39, ^64}, + 26: {40, 41}, + 27: {42, ^13}, + 28: {43, 44}, + 29: {45, ^1}, + 30: {^12, 46}, + 31: {47, 48}, + 32: {49, 50}, + 33: {51, 52}, + 34: {53, 54}, + 35: {55, ^192}, + 36: {^1664, 56}, + 37: {57, 58}, + 38: {^16, ^17}, + 39: {^14, ^15}, + 40: {59, 60}, + 41: {61, ^22}, + 42: {^23, 62}, + 43: {^20, 63}, + 44: {64, 65}, + 45: {^19, 66}, + 46: {67, ^26}, + 47: {68, 69}, + 48: {70, ^21}, + 49: {^28, 71}, + 50: {72, 73}, + 51: {^27, 74}, + 52: {75, ^18}, + 53: {^24, 76}, + 54: {77, ^25}, + 55: {78, 79}, + 56: {80, 81}, + 57: {82, 83}, + 58: {84, ^256}, + 59: {0, 85}, + 60: {^29, ^30}, + 61: {^45, ^46}, + 62: {^47, ^48}, + 63: {^33, ^34}, + 64: {^35, ^36}, + 65: {^37, ^38}, + 66: {^31, ^32}, + 67: {^53, ^54}, + 68: {^39, ^40}, + 69: {^41, ^42}, + 70: {^43, ^44}, + 71: {^61, ^62}, + 72: {^63, ^0}, + 73: {^320, ^384}, + 74: {^59, ^60}, + 75: {86, 87}, + 76: {^49, ^50}, + 77: {^51, ^52}, + 78: {^55, ^56}, + 79: {^57, ^58}, + 80: {^448, ^512}, + 81: {88, ^640}, + 82: {^576, 89}, + 83: {90, 91}, + 84: {92, 93}, + 85: {94, 95}, + 86: {^1472, ^1536}, + 87: {^1600, ^1728}, + 88: {^704, ^768}, + 89: {^832, ^896}, + 90: {^960, ^1024}, + 91: {^1088, ^1152}, + 92: {^1216, ^1280}, + 93: {^1344, ^1408}, + 94: {96, 97}, + 95: {98, 99}, + 96: {^1792, 100}, + 97: {101, 102}, + 98: {^1856, ^1920}, + 99: {103, 104}, + 100: {^1984, ^2048}, + 101: {^2112, ^2176}, + 102: {^2240, ^2304}, + 103: {^2368, ^2432}, + 104: {^2496, ^2560}, +} + +// blackDecodeTable represents Tables 2 and 3 for a black run. +// +// +=XXXXX +// b017 +-+ +// | | +=v1792 +// b042 | | +-+ +// | | | | +=v1984 +// b063 | | | +-+ +// | | | +=v2048 +// b029 | | +-+ +// | | | | +=v2112 +// b064 | | | | +-+ +// | | | | | +=v2176 +// b043 | | | +-+ +// | | | | +=v2240 +// b065 | | | +-+ +// | | | +=v2304 +// b022 | +-+ +// | | +=v1856 +// b044 | | +-+ +// | | | +=v1920 +// b030 | +-+ +// | | +=v2368 +// b066 | | +-+ +// | | | +=v2432 +// b045 | +-+ +// | | +=v2496 +// b067 | +-+ +// | +=v2560 +// b013 +-+ +// | | +=v0018 +// b031 | | +-+ +// | | | | +=v0052 +// b068 | | | | +-+ +// | | | | | | +=v0640 +// b095 | | | | | +-+ +// | | | | | +=v0704 +// b046 | | | +-+ +// | | | | +=v0768 +// b096 | | | | +-+ +// | | | | | +=v0832 +// b069 | | | +-+ +// | | | +=v0055 +// b023 | | +-+ +// | | | | +=v0056 +// b070 | | | | +-+ +// | | | | | | +=v1280 +// b097 | | | | | +-+ +// | | | | | +=v1344 +// b047 | | | | +-+ +// | | | | | | +=v1408 +// b098 | | | | | | +-+ +// | | | | | | | +=v1472 +// b071 | | | | | +-+ +// | | | | | +=v0059 +// b032 | | | +-+ +// | | | | +=v0060 +// b072 | | | | +-+ +// | | | | | | +=v1536 +// b099 | | | | | +-+ +// | | | | | +=v1600 +// b048 | | | +-+ +// | | | +=v0024 +// b018 | +-+ +// | | +=v0025 +// b049 | | +-+ +// | | | | +=v1664 +// b100 | | | | +-+ +// | | | | | +=v1728 +// b073 | | | +-+ +// | | | +=v0320 +// b033 | | +-+ +// | | | | +=v0384 +// b074 | | | | +-+ +// | | | | | +=v0448 +// b050 | | | +-+ +// | | | | +=v0512 +// b101 | | | | +-+ +// | | | | | +=v0576 +// b075 | | | +-+ +// | | | +=v0053 +// b024 | +-+ +// | | +=v0054 +// b076 | | +-+ +// | | | | +=v0896 +// b102 | | | +-+ +// | | | +=v0960 +// b051 | | +-+ +// | | | | +=v1024 +// b103 | | | | +-+ +// | | | | | +=v1088 +// b077 | | | +-+ +// | | | | +=v1152 +// b104 | | | +-+ +// | | | +=v1216 +// b034 | +-+ +// | +=v0064 +// b010 +-+ +// | | +=v0013 +// b019 | | +-+ +// | | | | +=v0023 +// b052 | | | | +-+ +// | | | | | | +=v0050 +// b078 | | | | | +-+ +// | | | | | +=v0051 +// b035 | | | | +-+ +// | | | | | | +=v0044 +// b079 | | | | | | +-+ +// | | | | | | | +=v0045 +// b053 | | | | | +-+ +// | | | | | | +=v0046 +// b080 | | | | | +-+ +// | | | | | +=v0047 +// b025 | | | +-+ +// | | | | +=v0057 +// b081 | | | | +-+ +// | | | | | +=v0058 +// b054 | | | | +-+ +// | | | | | | +=v0061 +// b082 | | | | | +-+ +// | | | | | +=v0256 +// b036 | | | +-+ +// | | | +=v0016 +// b014 | +-+ +// | | +=v0017 +// b037 | | +-+ +// | | | | +=v0048 +// b083 | | | | +-+ +// | | | | | +=v0049 +// b055 | | | +-+ +// | | | | +=v0062 +// b084 | | | +-+ +// | | | +=v0063 +// b026 | | +-+ +// | | | | +=v0030 +// b085 | | | | +-+ +// | | | | | +=v0031 +// b056 | | | | +-+ +// | | | | | | +=v0032 +// b086 | | | | | +-+ +// | | | | | +=v0033 +// b038 | | | +-+ +// | | | | +=v0040 +// b087 | | | | +-+ +// | | | | | +=v0041 +// b057 | | | +-+ +// | | | +=v0022 +// b020 | +-+ +// | +=v0014 +// b008 +-+ +// | | +=v0010 +// b015 | | +-+ +// | | | +=v0011 +// b011 | +-+ +// | | +=v0015 +// b027 | | +-+ +// | | | | +=v0128 +// b088 | | | | +-+ +// | | | | | +=v0192 +// b058 | | | | +-+ +// | | | | | | +=v0026 +// b089 | | | | | +-+ +// | | | | | +=v0027 +// b039 | | | +-+ +// | | | | +=v0028 +// b090 | | | | +-+ +// | | | | | +=v0029 +// b059 | | | +-+ +// | | | +=v0019 +// b021 | | +-+ +// | | | | +=v0020 +// b060 | | | | +-+ +// | | | | | | +=v0034 +// b091 | | | | | +-+ +// | | | | | +=v0035 +// b040 | | | | +-+ +// | | | | | | +=v0036 +// b092 | | | | | | +-+ +// | | | | | | | +=v0037 +// b061 | | | | | +-+ +// | | | | | | +=v0038 +// b093 | | | | | +-+ +// | | | | | +=v0039 +// b028 | | | +-+ +// | | | | +=v0021 +// b062 | | | | +-+ +// | | | | | | +=v0042 +// b094 | | | | | +-+ +// | | | | | +=v0043 +// b041 | | | +-+ +// | | | +=v0000 +// b016 | +-+ +// | +=v0012 +// b006 +-+ +// | | +=v0009 +// b012 | | +-+ +// | | | +=v0008 +// b009 | +-+ +// | +=v0007 +// b004 +-+ +// | | +=v0006 +// b007 | +-+ +// | +=v0005 +// b002 +-+ +// | | +=v0001 +// b005 | +-+ +// | +=v0004 +// b001 +-+ +// | +=v0003 +// b003 +-+ +// +=v0002 +var blackDecodeTable = [...][2]int16{ + 0: {0, 0}, + 1: {2, 3}, + 2: {4, 5}, + 3: {^3, ^2}, + 4: {6, 7}, + 5: {^1, ^4}, + 6: {8, 9}, + 7: {^6, ^5}, + 8: {10, 11}, + 9: {12, ^7}, + 10: {13, 14}, + 11: {15, 16}, + 12: {^9, ^8}, + 13: {17, 18}, + 14: {19, 20}, + 15: {^10, ^11}, + 16: {21, ^12}, + 17: {0, 22}, + 18: {23, 24}, + 19: {^13, 25}, + 20: {26, ^14}, + 21: {27, 28}, + 22: {29, 30}, + 23: {31, 32}, + 24: {33, 34}, + 25: {35, 36}, + 26: {37, 38}, + 27: {^15, 39}, + 28: {40, 41}, + 29: {42, 43}, + 30: {44, 45}, + 31: {^18, 46}, + 32: {47, 48}, + 33: {49, 50}, + 34: {51, ^64}, + 35: {52, 53}, + 36: {54, ^16}, + 37: {^17, 55}, + 38: {56, 57}, + 39: {58, 59}, + 40: {60, 61}, + 41: {62, ^0}, + 42: {^1792, 63}, + 43: {64, 65}, + 44: {^1856, ^1920}, + 45: {66, 67}, + 46: {68, 69}, + 47: {70, 71}, + 48: {72, ^24}, + 49: {^25, 73}, + 50: {74, 75}, + 51: {76, 77}, + 52: {^23, 78}, + 53: {79, 80}, + 54: {81, 82}, + 55: {83, 84}, + 56: {85, 86}, + 57: {87, ^22}, + 58: {88, 89}, + 59: {90, ^19}, + 60: {^20, 91}, + 61: {92, 93}, + 62: {^21, 94}, + 63: {^1984, ^2048}, + 64: {^2112, ^2176}, + 65: {^2240, ^2304}, + 66: {^2368, ^2432}, + 67: {^2496, ^2560}, + 68: {^52, 95}, + 69: {96, ^55}, + 70: {^56, 97}, + 71: {98, ^59}, + 72: {^60, 99}, + 73: {100, ^320}, + 74: {^384, ^448}, + 75: {101, ^53}, + 76: {^54, 102}, + 77: {103, 104}, + 78: {^50, ^51}, + 79: {^44, ^45}, + 80: {^46, ^47}, + 81: {^57, ^58}, + 82: {^61, ^256}, + 83: {^48, ^49}, + 84: {^62, ^63}, + 85: {^30, ^31}, + 86: {^32, ^33}, + 87: {^40, ^41}, + 88: {^128, ^192}, + 89: {^26, ^27}, + 90: {^28, ^29}, + 91: {^34, ^35}, + 92: {^36, ^37}, + 93: {^38, ^39}, + 94: {^42, ^43}, + 95: {^640, ^704}, + 96: {^768, ^832}, + 97: {^1280, ^1344}, + 98: {^1408, ^1472}, + 99: {^1536, ^1600}, + 100: {^1664, ^1728}, + 101: {^512, ^576}, + 102: {^896, ^960}, + 103: {^1024, ^1088}, + 104: {^1152, ^1216}, +} + +const maxCodeLength = 13 + +// Each encodeTable is represented by an array of bitStrings. + +// bitString is a pair of uint32 values representing a bit code. +// The nBits low bits of bits make up the actual bit code. +// Eg. bitString{0x0004, 8} represents the bitcode "00000100". +type bitString struct { + bits uint32 + nBits uint32 +} + +// modeEncodeTable represents Table 1 and the End-of-Line code. +var modeEncodeTable = [...]bitString{ + 0: {0x0001, 4}, // "0001" + 1: {0x0001, 3}, // "001" + 2: {0x0001, 1}, // "1" + 3: {0x0003, 3}, // "011" + 4: {0x0003, 6}, // "000011" + 5: {0x0003, 7}, // "0000011" + 6: {0x0002, 3}, // "010" + 7: {0x0002, 6}, // "000010" + 8: {0x0002, 7}, // "0000010" + 9: {0x0001, 7}, // "0000001" + 10: {0x0001, 12}, // "000000000001" +} + +// whiteEncodeTable2 represents Table 2 for a white run. +var whiteEncodeTable2 = [...]bitString{ + 0: {0x0035, 8}, // "00110101" + 1: {0x0007, 6}, // "000111" + 2: {0x0007, 4}, // "0111" + 3: {0x0008, 4}, // "1000" + 4: {0x000b, 4}, // "1011" + 5: {0x000c, 4}, // "1100" + 6: {0x000e, 4}, // "1110" + 7: {0x000f, 4}, // "1111" + 8: {0x0013, 5}, // "10011" + 9: {0x0014, 5}, // "10100" + 10: {0x0007, 5}, // "00111" + 11: {0x0008, 5}, // "01000" + 12: {0x0008, 6}, // "001000" + 13: {0x0003, 6}, // "000011" + 14: {0x0034, 6}, // "110100" + 15: {0x0035, 6}, // "110101" + 16: {0x002a, 6}, // "101010" + 17: {0x002b, 6}, // "101011" + 18: {0x0027, 7}, // "0100111" + 19: {0x000c, 7}, // "0001100" + 20: {0x0008, 7}, // "0001000" + 21: {0x0017, 7}, // "0010111" + 22: {0x0003, 7}, // "0000011" + 23: {0x0004, 7}, // "0000100" + 24: {0x0028, 7}, // "0101000" + 25: {0x002b, 7}, // "0101011" + 26: {0x0013, 7}, // "0010011" + 27: {0x0024, 7}, // "0100100" + 28: {0x0018, 7}, // "0011000" + 29: {0x0002, 8}, // "00000010" + 30: {0x0003, 8}, // "00000011" + 31: {0x001a, 8}, // "00011010" + 32: {0x001b, 8}, // "00011011" + 33: {0x0012, 8}, // "00010010" + 34: {0x0013, 8}, // "00010011" + 35: {0x0014, 8}, // "00010100" + 36: {0x0015, 8}, // "00010101" + 37: {0x0016, 8}, // "00010110" + 38: {0x0017, 8}, // "00010111" + 39: {0x0028, 8}, // "00101000" + 40: {0x0029, 8}, // "00101001" + 41: {0x002a, 8}, // "00101010" + 42: {0x002b, 8}, // "00101011" + 43: {0x002c, 8}, // "00101100" + 44: {0x002d, 8}, // "00101101" + 45: {0x0004, 8}, // "00000100" + 46: {0x0005, 8}, // "00000101" + 47: {0x000a, 8}, // "00001010" + 48: {0x000b, 8}, // "00001011" + 49: {0x0052, 8}, // "01010010" + 50: {0x0053, 8}, // "01010011" + 51: {0x0054, 8}, // "01010100" + 52: {0x0055, 8}, // "01010101" + 53: {0x0024, 8}, // "00100100" + 54: {0x0025, 8}, // "00100101" + 55: {0x0058, 8}, // "01011000" + 56: {0x0059, 8}, // "01011001" + 57: {0x005a, 8}, // "01011010" + 58: {0x005b, 8}, // "01011011" + 59: {0x004a, 8}, // "01001010" + 60: {0x004b, 8}, // "01001011" + 61: {0x0032, 8}, // "00110010" + 62: {0x0033, 8}, // "00110011" + 63: {0x0034, 8}, // "00110100" +} + +// whiteEncodeTable3 represents Table 3 for a white run. +var whiteEncodeTable3 = [...]bitString{ + 0: {0x001b, 5}, // "11011" + 1: {0x0012, 5}, // "10010" + 2: {0x0017, 6}, // "010111" + 3: {0x0037, 7}, // "0110111" + 4: {0x0036, 8}, // "00110110" + 5: {0x0037, 8}, // "00110111" + 6: {0x0064, 8}, // "01100100" + 7: {0x0065, 8}, // "01100101" + 8: {0x0068, 8}, // "01101000" + 9: {0x0067, 8}, // "01100111" + 10: {0x00cc, 9}, // "011001100" + 11: {0x00cd, 9}, // "011001101" + 12: {0x00d2, 9}, // "011010010" + 13: {0x00d3, 9}, // "011010011" + 14: {0x00d4, 9}, // "011010100" + 15: {0x00d5, 9}, // "011010101" + 16: {0x00d6, 9}, // "011010110" + 17: {0x00d7, 9}, // "011010111" + 18: {0x00d8, 9}, // "011011000" + 19: {0x00d9, 9}, // "011011001" + 20: {0x00da, 9}, // "011011010" + 21: {0x00db, 9}, // "011011011" + 22: {0x0098, 9}, // "010011000" + 23: {0x0099, 9}, // "010011001" + 24: {0x009a, 9}, // "010011010" + 25: {0x0018, 6}, // "011000" + 26: {0x009b, 9}, // "010011011" + 27: {0x0008, 11}, // "00000001000" + 28: {0x000c, 11}, // "00000001100" + 29: {0x000d, 11}, // "00000001101" + 30: {0x0012, 12}, // "000000010010" + 31: {0x0013, 12}, // "000000010011" + 32: {0x0014, 12}, // "000000010100" + 33: {0x0015, 12}, // "000000010101" + 34: {0x0016, 12}, // "000000010110" + 35: {0x0017, 12}, // "000000010111" + 36: {0x001c, 12}, // "000000011100" + 37: {0x001d, 12}, // "000000011101" + 38: {0x001e, 12}, // "000000011110" + 39: {0x001f, 12}, // "000000011111" +} + +// blackEncodeTable2 represents Table 2 for a black run. +var blackEncodeTable2 = [...]bitString{ + 0: {0x0037, 10}, // "0000110111" + 1: {0x0002, 3}, // "010" + 2: {0x0003, 2}, // "11" + 3: {0x0002, 2}, // "10" + 4: {0x0003, 3}, // "011" + 5: {0x0003, 4}, // "0011" + 6: {0x0002, 4}, // "0010" + 7: {0x0003, 5}, // "00011" + 8: {0x0005, 6}, // "000101" + 9: {0x0004, 6}, // "000100" + 10: {0x0004, 7}, // "0000100" + 11: {0x0005, 7}, // "0000101" + 12: {0x0007, 7}, // "0000111" + 13: {0x0004, 8}, // "00000100" + 14: {0x0007, 8}, // "00000111" + 15: {0x0018, 9}, // "000011000" + 16: {0x0017, 10}, // "0000010111" + 17: {0x0018, 10}, // "0000011000" + 18: {0x0008, 10}, // "0000001000" + 19: {0x0067, 11}, // "00001100111" + 20: {0x0068, 11}, // "00001101000" + 21: {0x006c, 11}, // "00001101100" + 22: {0x0037, 11}, // "00000110111" + 23: {0x0028, 11}, // "00000101000" + 24: {0x0017, 11}, // "00000010111" + 25: {0x0018, 11}, // "00000011000" + 26: {0x00ca, 12}, // "000011001010" + 27: {0x00cb, 12}, // "000011001011" + 28: {0x00cc, 12}, // "000011001100" + 29: {0x00cd, 12}, // "000011001101" + 30: {0x0068, 12}, // "000001101000" + 31: {0x0069, 12}, // "000001101001" + 32: {0x006a, 12}, // "000001101010" + 33: {0x006b, 12}, // "000001101011" + 34: {0x00d2, 12}, // "000011010010" + 35: {0x00d3, 12}, // "000011010011" + 36: {0x00d4, 12}, // "000011010100" + 37: {0x00d5, 12}, // "000011010101" + 38: {0x00d6, 12}, // "000011010110" + 39: {0x00d7, 12}, // "000011010111" + 40: {0x006c, 12}, // "000001101100" + 41: {0x006d, 12}, // "000001101101" + 42: {0x00da, 12}, // "000011011010" + 43: {0x00db, 12}, // "000011011011" + 44: {0x0054, 12}, // "000001010100" + 45: {0x0055, 12}, // "000001010101" + 46: {0x0056, 12}, // "000001010110" + 47: {0x0057, 12}, // "000001010111" + 48: {0x0064, 12}, // "000001100100" + 49: {0x0065, 12}, // "000001100101" + 50: {0x0052, 12}, // "000001010010" + 51: {0x0053, 12}, // "000001010011" + 52: {0x0024, 12}, // "000000100100" + 53: {0x0037, 12}, // "000000110111" + 54: {0x0038, 12}, // "000000111000" + 55: {0x0027, 12}, // "000000100111" + 56: {0x0028, 12}, // "000000101000" + 57: {0x0058, 12}, // "000001011000" + 58: {0x0059, 12}, // "000001011001" + 59: {0x002b, 12}, // "000000101011" + 60: {0x002c, 12}, // "000000101100" + 61: {0x005a, 12}, // "000001011010" + 62: {0x0066, 12}, // "000001100110" + 63: {0x0067, 12}, // "000001100111" +} + +// blackEncodeTable3 represents Table 3 for a black run. +var blackEncodeTable3 = [...]bitString{ + 0: {0x000f, 10}, // "0000001111" + 1: {0x00c8, 12}, // "000011001000" + 2: {0x00c9, 12}, // "000011001001" + 3: {0x005b, 12}, // "000001011011" + 4: {0x0033, 12}, // "000000110011" + 5: {0x0034, 12}, // "000000110100" + 6: {0x0035, 12}, // "000000110101" + 7: {0x006c, 13}, // "0000001101100" + 8: {0x006d, 13}, // "0000001101101" + 9: {0x004a, 13}, // "0000001001010" + 10: {0x004b, 13}, // "0000001001011" + 11: {0x004c, 13}, // "0000001001100" + 12: {0x004d, 13}, // "0000001001101" + 13: {0x0072, 13}, // "0000001110010" + 14: {0x0073, 13}, // "0000001110011" + 15: {0x0074, 13}, // "0000001110100" + 16: {0x0075, 13}, // "0000001110101" + 17: {0x0076, 13}, // "0000001110110" + 18: {0x0077, 13}, // "0000001110111" + 19: {0x0052, 13}, // "0000001010010" + 20: {0x0053, 13}, // "0000001010011" + 21: {0x0054, 13}, // "0000001010100" + 22: {0x0055, 13}, // "0000001010101" + 23: {0x005a, 13}, // "0000001011010" + 24: {0x005b, 13}, // "0000001011011" + 25: {0x0064, 13}, // "0000001100100" + 26: {0x0065, 13}, // "0000001100101" + 27: {0x0008, 11}, // "00000001000" + 28: {0x000c, 11}, // "00000001100" + 29: {0x000d, 11}, // "00000001101" + 30: {0x0012, 12}, // "000000010010" + 31: {0x0013, 12}, // "000000010011" + 32: {0x0014, 12}, // "000000010100" + 33: {0x0015, 12}, // "000000010101" + 34: {0x0016, 12}, // "000000010110" + 35: {0x0017, 12}, // "000000010111" + 36: {0x001c, 12}, // "000000011100" + 37: {0x001d, 12}, // "000000011101" + 38: {0x001e, 12}, // "000000011110" + 39: {0x001f, 12}, // "000000011111" +} + +// COPY PASTE table.go BEGIN + +const ( + modePass = iota // Pass + modeH // Horizontal + modeV0 // Vertical-0 + modeVR1 // Vertical-Right-1 + modeVR2 // Vertical-Right-2 + modeVR3 // Vertical-Right-3 + modeVL1 // Vertical-Left-1 + modeVL2 // Vertical-Left-2 + modeVL3 // Vertical-Left-3 + modeExt // Extension + modeEOL // End-of-Line +) + +// COPY PASTE table.go END diff --git a/vendor/golang.org/x/image/ccitt/writer.go b/vendor/golang.org/x/image/ccitt/writer.go new file mode 100644 index 000000000..87130ab04 --- /dev/null +++ b/vendor/golang.org/x/image/ccitt/writer.go @@ -0,0 +1,102 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ccitt + +import ( + "encoding/binary" + "io" +) + +type bitWriter struct { + w io.Writer + + // order is whether to process w's bytes LSB first or MSB first. + order Order + + // The high nBits bits of the bits field hold encoded bits to be written to w. + bits uint64 + nBits uint32 + + // bytes[:bw] holds encoded bytes not yet written to w. + // Overflow protection is ensured by using a multiple of 8 as bytes length. + bw uint32 + bytes [1024]uint8 +} + +// flushBits copies 64 bits from b.bits to b.bytes. If b.bytes is then full, it +// is written to b.w. +func (b *bitWriter) flushBits() error { + binary.BigEndian.PutUint64(b.bytes[b.bw:], b.bits) + b.bits = 0 + b.nBits = 0 + b.bw += 8 + if b.bw < uint32(len(b.bytes)) { + return nil + } + b.bw = 0 + if b.order != MSB { + reverseBitsWithinBytes(b.bytes[:]) + } + _, err := b.w.Write(b.bytes[:]) + return err +} + +// close finalizes a bitcode stream by writing any +// pending bits to bitWriter's underlying io.Writer. +func (b *bitWriter) close() error { + // Write any encoded bits to bytes. + if b.nBits > 0 { + binary.BigEndian.PutUint64(b.bytes[b.bw:], b.bits) + b.bw += (b.nBits + 7) >> 3 + } + + if b.order != MSB { + reverseBitsWithinBytes(b.bytes[:b.bw]) + } + + // Write b.bw bytes to b.w. + _, err := b.w.Write(b.bytes[:b.bw]) + return err +} + +// alignToByteBoundary rounds b.nBits up to a multiple of 8. +// If all 64 bits are used, flush them to bitWriter's bytes. +func (b *bitWriter) alignToByteBoundary() error { + if b.nBits = (b.nBits + 7) &^ 7; b.nBits == 64 { + return b.flushBits() + } + return nil +} + +// writeCode writes a variable length bitcode to b's underlying io.Writer. +func (b *bitWriter) writeCode(bs bitString) error { + bits := bs.bits + nBits := bs.nBits + if 64-b.nBits >= nBits { + // b.bits has sufficient room for storing nBits bits. + b.bits |= uint64(bits) << (64 - nBits - b.nBits) + b.nBits += nBits + if b.nBits == 64 { + return b.flushBits() + } + return nil + } + + // Number of leading bits that fill b.bits. + i := 64 - b.nBits + + // Fill b.bits then flush and write remaining bits. + b.bits |= uint64(bits) >> (nBits - i) + b.nBits = 64 + + if err := b.flushBits(); err != nil { + return err + } + + nBits -= i + b.bits = uint64(bits) << (64 - nBits) + b.nBits = nBits + return nil +} diff --git a/vendor/golang.org/x/image/tiff/buffer.go b/vendor/golang.org/x/image/tiff/buffer.go new file mode 100644 index 000000000..d1801be48 --- /dev/null +++ b/vendor/golang.org/x/image/tiff/buffer.go @@ -0,0 +1,69 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tiff + +import "io" + +// buffer buffers an io.Reader to satisfy io.ReaderAt. +type buffer struct { + r io.Reader + buf []byte +} + +// fill reads data from b.r until the buffer contains at least end bytes. +func (b *buffer) fill(end int) error { + m := len(b.buf) + if end > m { + if end > cap(b.buf) { + newcap := 1024 + for newcap < end { + newcap *= 2 + } + newbuf := make([]byte, end, newcap) + copy(newbuf, b.buf) + b.buf = newbuf + } else { + b.buf = b.buf[:end] + } + if n, err := io.ReadFull(b.r, b.buf[m:end]); err != nil { + end = m + n + b.buf = b.buf[:end] + return err + } + } + return nil +} + +func (b *buffer) ReadAt(p []byte, off int64) (int, error) { + o := int(off) + end := o + len(p) + if int64(end) != off+int64(len(p)) { + return 0, io.ErrUnexpectedEOF + } + + err := b.fill(end) + return copy(p, b.buf[o:end]), err +} + +// Slice returns a slice of the underlying buffer. The slice contains +// n bytes starting at offset off. +func (b *buffer) Slice(off, n int) ([]byte, error) { + end := off + n + if err := b.fill(end); err != nil { + return nil, err + } + return b.buf[off:end], nil +} + +// newReaderAt converts an io.Reader into an io.ReaderAt. +func newReaderAt(r io.Reader) io.ReaderAt { + if ra, ok := r.(io.ReaderAt); ok { + return ra + } + return &buffer{ + r: r, + buf: make([]byte, 0, 1024), + } +} diff --git a/vendor/golang.org/x/image/tiff/compress.go b/vendor/golang.org/x/image/tiff/compress.go new file mode 100644 index 000000000..3f176f00a --- /dev/null +++ b/vendor/golang.org/x/image/tiff/compress.go @@ -0,0 +1,58 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tiff + +import ( + "bufio" + "io" +) + +type byteReader interface { + io.Reader + io.ByteReader +} + +// unpackBits decodes the PackBits-compressed data in src and returns the +// uncompressed data. +// +// The PackBits compression format is described in section 9 (p. 42) +// of the TIFF spec. +func unpackBits(r io.Reader) ([]byte, error) { + buf := make([]byte, 128) + dst := make([]byte, 0, 1024) + br, ok := r.(byteReader) + if !ok { + br = bufio.NewReader(r) + } + + for { + b, err := br.ReadByte() + if err != nil { + if err == io.EOF { + return dst, nil + } + return nil, err + } + code := int(int8(b)) + switch { + case code >= 0: + n, err := io.ReadFull(br, buf[:code+1]) + if err != nil { + return nil, err + } + dst = append(dst, buf[:n]...) + case code == -128: + // No-op. + default: + if b, err = br.ReadByte(); err != nil { + return nil, err + } + for j := 0; j < 1-code; j++ { + buf[j] = b + } + dst = append(dst, buf[:1-code]...) + } + } +} diff --git a/vendor/golang.org/x/image/tiff/consts.go b/vendor/golang.org/x/image/tiff/consts.go new file mode 100644 index 000000000..3e5f7f14d --- /dev/null +++ b/vendor/golang.org/x/image/tiff/consts.go @@ -0,0 +1,149 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tiff + +// A tiff image file contains one or more images. The metadata +// of each image is contained in an Image File Directory (IFD), +// which contains entries of 12 bytes each and is described +// on page 14-16 of the specification. An IFD entry consists of +// +// - a tag, which describes the signification of the entry, +// - the data type and length of the entry, +// - the data itself or a pointer to it if it is more than 4 bytes. +// +// The presence of a length means that each IFD is effectively an array. + +const ( + leHeader = "II\x2A\x00" // Header for little-endian files. + beHeader = "MM\x00\x2A" // Header for big-endian files. + + ifdLen = 12 // Length of an IFD entry in bytes. +) + +// Data types (p. 14-16 of the spec). +const ( + dtByte = 1 + dtASCII = 2 + dtShort = 3 + dtLong = 4 + dtRational = 5 +) + +// The length of one instance of each data type in bytes. +var lengths = [...]uint32{0, 1, 1, 2, 4, 8} + +// Tags (see p. 28-41 of the spec). +const ( + tImageWidth = 256 + tImageLength = 257 + tBitsPerSample = 258 + 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 + tTileByteCounts = 325 + + tXResolution = 282 + tYResolution = 283 + tResolutionUnit = 296 + + tPredictor = 317 + tColorMap = 320 + tExtraSamples = 338 + tSampleFormat = 339 +) + +// Compression types (defined in various places in the spec and supplements). +const ( + cNone = 1 + cCCITT = 2 + cG3 = 3 // Group 3 Fax. + cG4 = 4 // Group 4 Fax. + cLZW = 5 + cJPEGOld = 6 // Superseded by cJPEG. + cJPEG = 7 + cDeflate = 8 // zlib compression. + cPackBits = 32773 + cDeflateOld = 32946 // Superseded by cDeflate. +) + +// Photometric interpretation values (see p. 37 of the spec). +const ( + pWhiteIsZero = 0 + pBlackIsZero = 1 + pRGB = 2 + pPaletted = 3 + pTransMask = 4 // transparency mask + pCMYK = 5 + pYCbCr = 6 + pCIELab = 8 +) + +// Values for the tPredictor tag (page 64-65 of the spec). +const ( + prNone = 1 + prHorizontal = 2 +) + +// Values for the tResolutionUnit tag (page 18). +const ( + resNone = 1 + resPerInch = 2 // Dots per inch. + resPerCM = 3 // Dots per centimeter. +) + +// imageMode represents the mode of the image. +type imageMode int + +const ( + mBilevel imageMode = iota + mPaletted + mGray + mGrayInvert + 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 +} diff --git a/vendor/golang.org/x/image/tiff/fuzz.go b/vendor/golang.org/x/image/tiff/fuzz.go new file mode 100644 index 000000000..ec52c7882 --- /dev/null +++ b/vendor/golang.org/x/image/tiff/fuzz.go @@ -0,0 +1,29 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build gofuzz + +package tiff + +import "bytes" + +func Fuzz(data []byte) int { + cfg, err := DecodeConfig(bytes.NewReader(data)) + if err != nil { + return 0 + } + if cfg.Width*cfg.Height > 1e6 { + return 0 + } + img, err := Decode(bytes.NewReader(data)) + if err != nil { + return 0 + } + var w bytes.Buffer + err = Encode(&w, img, nil) + if err != nil { + panic(err) + } + return 1 +} diff --git a/vendor/golang.org/x/image/tiff/lzw/reader.go b/vendor/golang.org/x/image/tiff/lzw/reader.go new file mode 100644 index 000000000..78204ba92 --- /dev/null +++ b/vendor/golang.org/x/image/tiff/lzw/reader.go @@ -0,0 +1,272 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package lzw implements the Lempel-Ziv-Welch compressed data format, +// described in T. A. Welch, ``A Technique for High-Performance Data +// Compression'', Computer, 17(6) (June 1984), pp 8-19. +// +// In particular, it implements LZW as used by the TIFF file format, including +// an "off by one" algorithmic difference when compared to standard LZW. +package lzw // import "golang.org/x/image/tiff/lzw" + +/* +This file was branched from src/pkg/compress/lzw/reader.go in the +standard library. Differences from the original are marked with "NOTE". + +The tif_lzw.c file in the libtiff C library has this comment: + +---- +The 5.0 spec describes a different algorithm than Aldus +implements. Specifically, Aldus does code length transitions +one code earlier than should be done (for real LZW). +Earlier versions of this library implemented the correct +LZW algorithm, but emitted codes in a bit order opposite +to the TIFF spec. Thus, to maintain compatibility w/ Aldus +we interpret MSB-LSB ordered codes to be images written w/ +old versions of this library, but otherwise adhere to the +Aldus "off by one" algorithm. +---- + +The Go code doesn't read (invalid) TIFF files written by old versions of +libtiff, but the LZW algorithm in this package still differs from the one in +Go's standard package library to accomodate this "off by one" in valid TIFFs. +*/ + +import ( + "bufio" + "errors" + "fmt" + "io" +) + +// Order specifies the bit ordering in an LZW data stream. +type Order int + +const ( + // LSB means Least Significant Bits first, as used in the GIF file format. + LSB Order = iota + // MSB means Most Significant Bits first, as used in the TIFF and PDF + // file formats. + MSB +) + +const ( + maxWidth = 12 + decoderInvalidCode = 0xffff + flushBuffer = 1 << maxWidth +) + +// decoder is the state from which the readXxx method converts a byte +// stream into a code stream. +type decoder struct { + r io.ByteReader + bits uint32 + nBits uint + width uint + read func(*decoder) (uint16, error) // readLSB or readMSB + litWidth int // width in bits of literal codes + err error + + // The first 1<= 1<>= d.width + d.nBits -= d.width + return code, nil +} + +// readMSB returns the next code for "Most Significant Bits first" data. +func (d *decoder) readMSB() (uint16, error) { + for d.nBits < d.width { + x, err := d.r.ReadByte() + if err != nil { + return 0, err + } + d.bits |= uint32(x) << (24 - d.nBits) + d.nBits += 8 + } + code := uint16(d.bits >> (32 - d.width)) + d.bits <<= d.width + d.nBits -= d.width + return code, nil +} + +func (d *decoder) Read(b []byte) (int, error) { + for { + if len(d.toRead) > 0 { + n := copy(b, d.toRead) + d.toRead = d.toRead[n:] + return n, nil + } + if d.err != nil { + return 0, d.err + } + d.decode() + } +} + +// decode decompresses bytes from r and leaves them in d.toRead. +// read specifies how to decode bytes into codes. +// litWidth is the width in bits of literal codes. +func (d *decoder) decode() { + // Loop over the code stream, converting codes into decompressed bytes. +loop: + for { + code, err := d.read(d) + if err != nil { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + d.err = err + break + } + switch { + case code < d.clear: + // We have a literal code. + d.output[d.o] = uint8(code) + d.o++ + if d.last != decoderInvalidCode { + // Save what the hi code expands to. + d.suffix[d.hi] = uint8(code) + d.prefix[d.hi] = d.last + } + case code == d.clear: + d.width = 1 + uint(d.litWidth) + d.hi = d.eof + d.overflow = 1 << d.width + d.last = decoderInvalidCode + continue + case code == d.eof: + d.err = io.EOF + break loop + case code <= d.hi: + c, i := code, len(d.output)-1 + if code == d.hi && d.last != decoderInvalidCode { + // code == hi is a special case which expands to the last expansion + // followed by the head of the last expansion. To find the head, we walk + // the prefix chain until we find a literal code. + c = d.last + for c >= d.clear { + c = d.prefix[c] + } + d.output[i] = uint8(c) + i-- + c = d.last + } + // Copy the suffix chain into output and then write that to w. + for c >= d.clear { + d.output[i] = d.suffix[c] + i-- + c = d.prefix[c] + } + d.output[i] = uint8(c) + d.o += copy(d.output[d.o:], d.output[i:]) + if d.last != decoderInvalidCode { + // Save what the hi code expands to. + d.suffix[d.hi] = uint8(c) + d.prefix[d.hi] = d.last + } + default: + d.err = errors.New("lzw: invalid code") + break loop + } + d.last, d.hi = code, d.hi+1 + if d.hi+1 >= d.overflow { // NOTE: the "+1" is where TIFF's LZW differs from the standard algorithm. + if d.width == maxWidth { + d.last = decoderInvalidCode + } else { + d.width++ + d.overflow <<= 1 + } + } + if d.o >= flushBuffer { + break + } + } + // Flush pending output. + d.toRead = d.output[:d.o] + d.o = 0 +} + +var errClosed = errors.New("lzw: reader/writer is closed") + +func (d *decoder) Close() error { + d.err = errClosed // in case any Reads come along + return nil +} + +// NewReader creates a new io.ReadCloser. +// Reads from the returned io.ReadCloser read and decompress data from r. +// If r does not also implement io.ByteReader, +// the decompressor may read more data than necessary from r. +// It is the caller's responsibility to call Close on the ReadCloser when +// finished reading. +// The number of bits to use for literal codes, litWidth, must be in the +// range [2,8] and is typically 8. It must equal the litWidth +// used during compression. +func NewReader(r io.Reader, order Order, litWidth int) io.ReadCloser { + d := new(decoder) + switch order { + case LSB: + d.read = (*decoder).readLSB + case MSB: + d.read = (*decoder).readMSB + default: + d.err = errors.New("lzw: unknown order") + return d + } + if litWidth < 2 || 8 < litWidth { + d.err = fmt.Errorf("lzw: litWidth %d out of range", litWidth) + return d + } + if br, ok := r.(io.ByteReader); ok { + d.r = br + } else { + d.r = bufio.NewReader(r) + } + d.litWidth = litWidth + d.width = 1 + uint(litWidth) + d.clear = uint16(1) << uint(litWidth) + d.eof, d.hi = d.clear+1, d.clear+1 + d.overflow = uint16(1) << d.width + d.last = decoderInvalidCode + + return d +} diff --git a/vendor/golang.org/x/image/tiff/reader.go b/vendor/golang.org/x/image/tiff/reader.go new file mode 100644 index 000000000..c26ec36bb --- /dev/null +++ b/vendor/golang.org/x/image/tiff/reader.go @@ -0,0 +1,706 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package tiff implements a TIFF image decoder and encoder. +// +// The TIFF specification is at http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf +package tiff // import "golang.org/x/image/tiff" + +import ( + "compress/zlib" + "encoding/binary" + "fmt" + "image" + "image/color" + "io" + "io/ioutil" + "math" + + "golang.org/x/image/ccitt" + "golang.org/x/image/tiff/lzw" +) + +// A FormatError reports that the input is not a valid TIFF image. +type FormatError string + +func (e FormatError) Error() string { + return "tiff: invalid format: " + string(e) +} + +// An UnsupportedError reports that the input uses a valid but +// unimplemented feature. +type UnsupportedError string + +func (e UnsupportedError) Error() string { + return "tiff: unsupported feature: " + string(e) +} + +var errNoPixels = FormatError("not enough pixel data") + +type decoder struct { + r io.ReaderAt + byteOrder binary.ByteOrder + config image.Config + mode imageMode + bpp uint + features map[int][]uint + palette []color.Color + + buf []byte + off int // Current offset in buf. + v uint32 // Buffer value for reading with arbitrary bit depths. + nbits uint // Remaining number of bits in v. +} + +// firstVal returns the first uint of the features entry with the given tag, +// or 0 if the tag does not exist. +func (d *decoder) firstVal(tag int) uint { + f := d.features[tag] + if len(f) == 0 { + return 0 + } + return f[0] +} + +// ifdUint decodes the IFD entry in p, which must be of the Byte, Short +// or Long type, and returns the decoded uint values. +func (d *decoder) ifdUint(p []byte) (u []uint, err error) { + var raw []byte + if len(p) < ifdLen { + return nil, FormatError("bad IFD entry") + } + + datatype := d.byteOrder.Uint16(p[2:4]) + if dt := int(datatype); dt <= 0 || dt >= len(lengths) { + return nil, UnsupportedError("IFD entry datatype") + } + + count := d.byteOrder.Uint32(p[4:8]) + if count > math.MaxInt32/lengths[datatype] { + return nil, FormatError("IFD data too large") + } + if datalen := lengths[datatype] * 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]))) + } else { + raw = p[8 : 8+datalen] + } + if err != nil { + return nil, err + } + + u = make([]uint, count) + switch datatype { + case dtByte: + for i := uint32(0); i < count; i++ { + u[i] = uint(raw[i]) + } + case dtShort: + for i := uint32(0); i < count; i++ { + u[i] = uint(d.byteOrder.Uint16(raw[2*i : 2*(i+1)])) + } + case dtLong: + for i := uint32(0); i < count; i++ { + u[i] = uint(d.byteOrder.Uint32(raw[4*i : 4*(i+1)])) + } + default: + return nil, UnsupportedError("data type") + } + return u, nil +} + +// parseIFD decides whether the IFD entry in p is "interesting" and +// stows away the data in the decoder. It returns the tag number of the +// entry and an error, if any. +func (d *decoder) parseIFD(p []byte) (int, error) { + tag := d.byteOrder.Uint16(p[0:2]) + switch tag { + case tBitsPerSample, + tExtraSamples, + tPhotometricInterpretation, + tCompression, + tPredictor, + tStripOffsets, + tStripByteCounts, + tRowsPerStrip, + tTileWidth, + tTileLength, + tTileOffsets, + tTileByteCounts, + tImageLength, + tImageWidth, + tFillOrder, + tT4Options, + tT6Options: + val, err := d.ifdUint(p) + if err != nil { + return 0, err + } + d.features[int(tag)] = val + case tColorMap: + val, err := d.ifdUint(p) + if err != nil { + return 0, err + } + numcolors := len(val) / 3 + if len(val)%3 != 0 || numcolors <= 0 || numcolors > 256 { + return 0, FormatError("bad ColorMap length") + } + d.palette = make([]color.Color, numcolors) + for i := 0; i < numcolors; i++ { + d.palette[i] = color.RGBA64{ + uint16(val[i]), + uint16(val[i+numcolors]), + uint16(val[i+2*numcolors]), + 0xffff, + } + } + case tSampleFormat: + // Page 27 of the spec: If the SampleFormat is present and + // the value is not 1 [= unsigned integer data], a Baseline + // TIFF reader that cannot handle the SampleFormat value + // must terminate the import process gracefully. + val, err := d.ifdUint(p) + if err != nil { + return 0, err + } + for _, v := range val { + if v != 1 { + return 0, UnsupportedError("sample format") + } + } + } + return int(tag), nil +} + +// readBits reads n bits from the internal buffer starting at the current offset. +func (d *decoder) readBits(n uint) (v uint32, ok bool) { + for d.nbits < n { + d.v <<= 8 + if d.off >= len(d.buf) { + return 0, false + } + d.v |= uint32(d.buf[d.off]) + d.off++ + d.nbits += 8 + } + d.nbits -= n + rv := d.v >> d.nbits + d.v &^= rv << d.nbits + return rv, true +} + +// flushBits discards the unread bits in the buffer used by readBits. +// It is used at the end of a line. +func (d *decoder) flushBits() { + d.v = 0 + d.nbits = 0 +} + +// minInt returns the smaller of x or y. +func minInt(a, b int) int { + if a <= b { + return a + } + return b +} + +// decode decodes the raw data of an image. +// It reads from d.buf and writes the strip or tile into dst. +func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error { + d.off = 0 + + // Apply horizontal predictor if necessary. + // In this case, p contains the color difference to the preceding pixel. + // See page 64-65 of the spec. + if d.firstVal(tPredictor) == prHorizontal { + switch d.bpp { + case 16: + var off int + n := 2 * len(d.features[tBitsPerSample]) // bytes per sample times samples per pixel + for y := ymin; y < ymax; y++ { + off += n + for x := 0; x < (xmax-xmin-1)*n; x += 2 { + if off+2 > len(d.buf) { + return errNoPixels + } + v0 := d.byteOrder.Uint16(d.buf[off-n : off-n+2]) + v1 := d.byteOrder.Uint16(d.buf[off : off+2]) + d.byteOrder.PutUint16(d.buf[off:off+2], v1+v0) + off += 2 + } + } + case 8: + var off int + n := 1 * len(d.features[tBitsPerSample]) // bytes per sample times samples per pixel + for y := ymin; y < ymax; y++ { + off += n + for x := 0; x < (xmax-xmin-1)*n; x++ { + if off >= len(d.buf) { + return errNoPixels + } + d.buf[off] += d.buf[off-n] + off++ + } + } + case 1: + return UnsupportedError("horizontal predictor with 1 BitsPerSample") + } + } + + rMaxX := minInt(xmax, dst.Bounds().Max.X) + rMaxY := minInt(ymax, dst.Bounds().Max.Y) + switch d.mode { + case mGray, mGrayInvert: + if d.bpp == 16 { + img := dst.(*image.Gray16) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + if d.off+2 > len(d.buf) { + return errNoPixels + } + v := d.byteOrder.Uint16(d.buf[d.off : d.off+2]) + d.off += 2 + if d.mode == mGrayInvert { + v = 0xffff - v + } + img.SetGray16(x, y, color.Gray16{v}) + } + if rMaxX == img.Bounds().Max.X { + d.off += 2 * (xmax - img.Bounds().Max.X) + } + } + } else { + img := dst.(*image.Gray) + max := uint32((1 << d.bpp) - 1) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + v, ok := d.readBits(d.bpp) + if !ok { + return errNoPixels + } + v = v * 0xff / max + if d.mode == mGrayInvert { + v = 0xff - v + } + img.SetGray(x, y, color.Gray{uint8(v)}) + } + d.flushBits() + } + } + case mPaletted: + img := dst.(*image.Paletted) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + v, ok := d.readBits(d.bpp) + if !ok { + return errNoPixels + } + img.SetColorIndex(x, y, uint8(v)) + } + d.flushBits() + } + case mRGB: + if d.bpp == 16 { + img := dst.(*image.RGBA64) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + if d.off+6 > len(d.buf) { + return errNoPixels + } + r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2]) + g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4]) + b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6]) + d.off += 6 + img.SetRGBA64(x, y, color.RGBA64{r, g, b, 0xffff}) + } + } + } else { + img := dst.(*image.RGBA) + for y := ymin; y < rMaxY; y++ { + min := img.PixOffset(xmin, y) + max := img.PixOffset(rMaxX, y) + off := (y - ymin) * (xmax - xmin) * 3 + for i := min; i < max; i += 4 { + if off+3 > len(d.buf) { + return errNoPixels + } + img.Pix[i+0] = d.buf[off+0] + img.Pix[i+1] = d.buf[off+1] + img.Pix[i+2] = d.buf[off+2] + img.Pix[i+3] = 0xff + off += 3 + } + } + } + case mNRGBA: + if d.bpp == 16 { + img := dst.(*image.NRGBA64) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + if d.off+8 > len(d.buf) { + return errNoPixels + } + r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2]) + g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4]) + b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6]) + a := d.byteOrder.Uint16(d.buf[d.off+6 : d.off+8]) + d.off += 8 + img.SetNRGBA64(x, y, color.NRGBA64{r, g, b, a}) + } + } + } else { + img := dst.(*image.NRGBA) + for y := ymin; y < rMaxY; y++ { + min := img.PixOffset(xmin, y) + max := img.PixOffset(rMaxX, y) + i0, i1 := (y-ymin)*(xmax-xmin)*4, (y-ymin+1)*(xmax-xmin)*4 + if i1 > len(d.buf) { + return errNoPixels + } + copy(img.Pix[min:max], d.buf[i0:i1]) + } + } + case mRGBA: + if d.bpp == 16 { + img := dst.(*image.RGBA64) + for y := ymin; y < rMaxY; y++ { + for x := xmin; x < rMaxX; x++ { + if d.off+8 > len(d.buf) { + return errNoPixels + } + r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2]) + g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4]) + b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6]) + a := d.byteOrder.Uint16(d.buf[d.off+6 : d.off+8]) + d.off += 8 + img.SetRGBA64(x, y, color.RGBA64{r, g, b, a}) + } + } + } else { + img := dst.(*image.RGBA) + for y := ymin; y < rMaxY; y++ { + min := img.PixOffset(xmin, y) + max := img.PixOffset(rMaxX, y) + i0, i1 := (y-ymin)*(xmax-xmin)*4, (y-ymin+1)*(xmax-xmin)*4 + if i1 > len(d.buf) { + return errNoPixels + } + copy(img.Pix[min:max], d.buf[i0:i1]) + } + } + } + + return nil +} + +func newDecoder(r io.Reader) (*decoder, error) { + d := &decoder{ + r: newReaderAt(r), + features: make(map[int][]uint), + } + + p := make([]byte, 8) + if _, err := d.r.ReadAt(p, 0); err != nil { + return nil, err + } + switch string(p[0:4]) { + case leHeader: + d.byteOrder = binary.LittleEndian + case beHeader: + d.byteOrder = binary.BigEndian + default: + return nil, FormatError("malformed header") + } + + ifdOffset := int64(d.byteOrder.Uint32(p[4:8])) + + // The first two bytes contain the number of entries (12 bytes each). + if _, err := d.r.ReadAt(p[0:2], ifdOffset); err != nil { + return nil, err + } + numItems := int(d.byteOrder.Uint16(p[0:2])) + + // All IFD entries are read in one chunk. + p = make([]byte, ifdLen*numItems) + if _, err := d.r.ReadAt(p, ifdOffset+2); err != nil { + return nil, err + } + + prevTag := -1 + for i := 0; i < len(p); i += ifdLen { + tag, err := d.parseIFD(p[i : i+ifdLen]) + if err != nil { + return nil, err + } + if tag <= prevTag { + return nil, FormatError("tags are not sorted in ascending order") + } + prevTag = tag + } + + d.config.Width = int(d.firstVal(tImageWidth)) + d.config.Height = int(d.firstVal(tImageLength)) + + if _, ok := d.features[tBitsPerSample]; !ok { + // Default is 1 per specification. + d.features[tBitsPerSample] = []uint{1} + } + d.bpp = d.firstVal(tBitsPerSample) + switch d.bpp { + case 0: + return nil, FormatError("BitsPerSample must not be 0") + case 1, 8, 16: + // Nothing to do, these are accepted by this implementation. + default: + return nil, UnsupportedError(fmt.Sprintf("BitsPerSample of %v", d.bpp)) + } + + // Determine the image mode. + switch d.firstVal(tPhotometricInterpretation) { + case pRGB: + if d.bpp == 16 { + for _, b := range d.features[tBitsPerSample] { + if b != 16 { + return nil, FormatError("wrong number of samples for 16bit RGB") + } + } + } else { + for _, b := range d.features[tBitsPerSample] { + if b != 8 { + return nil, FormatError("wrong number of samples for 8bit RGB") + } + } + } + // RGB images normally have 3 samples per pixel. + // If there are more, ExtraSamples (p. 31-32 of the spec) + // gives their meaning (usually an alpha channel). + // + // This implementation does not support extra samples + // of an unspecified type. + switch len(d.features[tBitsPerSample]) { + case 3: + d.mode = mRGB + if d.bpp == 16 { + d.config.ColorModel = color.RGBA64Model + } else { + d.config.ColorModel = color.RGBAModel + } + case 4: + switch d.firstVal(tExtraSamples) { + case 1: + d.mode = mRGBA + if d.bpp == 16 { + d.config.ColorModel = color.RGBA64Model + } else { + d.config.ColorModel = color.RGBAModel + } + case 2: + d.mode = mNRGBA + if d.bpp == 16 { + d.config.ColorModel = color.NRGBA64Model + } else { + d.config.ColorModel = color.NRGBAModel + } + default: + return nil, FormatError("wrong number of samples for RGB") + } + default: + return nil, FormatError("wrong number of samples for RGB") + } + case pPaletted: + d.mode = mPaletted + d.config.ColorModel = color.Palette(d.palette) + case pWhiteIsZero: + d.mode = mGrayInvert + if d.bpp == 16 { + d.config.ColorModel = color.Gray16Model + } else { + d.config.ColorModel = color.GrayModel + } + case pBlackIsZero: + d.mode = mGray + if d.bpp == 16 { + d.config.ColorModel = color.Gray16Model + } else { + d.config.ColorModel = color.GrayModel + } + default: + return nil, UnsupportedError("color model") + } + + return d, nil +} + +// DecodeConfig returns the color model and dimensions of a TIFF image without +// decoding the entire image. +func DecodeConfig(r io.Reader) (image.Config, error) { + d, err := newDecoder(r) + if err != nil { + return image.Config{}, err + } + 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) { + d, err := newDecoder(r) + if err != nil { + return + } + + blockPadding := false + blockWidth := d.config.Width + blockHeight := d.config.Height + blocksAcross := 1 + blocksDown := 1 + + if d.config.Width == 0 { + blocksAcross = 0 + } + if d.config.Height == 0 { + blocksDown = 0 + } + + var blockOffsets, blockCounts []uint + + if int(d.firstVal(tTileWidth)) != 0 { + blockPadding = true + + blockWidth = int(d.firstVal(tTileWidth)) + blockHeight = int(d.firstVal(tTileLength)) + + if blockWidth != 0 { + blocksAcross = (d.config.Width + blockWidth - 1) / blockWidth + } + if blockHeight != 0 { + blocksDown = (d.config.Height + blockHeight - 1) / blockHeight + } + + blockCounts = d.features[tTileByteCounts] + blockOffsets = d.features[tTileOffsets] + + } else { + if int(d.firstVal(tRowsPerStrip)) != 0 { + blockHeight = int(d.firstVal(tRowsPerStrip)) + } + + if blockHeight != 0 { + blocksDown = (d.config.Height + blockHeight - 1) / blockHeight + } + + blockOffsets = d.features[tStripOffsets] + blockCounts = d.features[tStripByteCounts] + } + + // Check if we have the right number of strips/tiles, offsets and counts. + if n := blocksAcross * blocksDown; len(blockOffsets) < n || len(blockCounts) < n { + return nil, FormatError("inconsistent header") + } + + imgRect := image.Rect(0, 0, d.config.Width, d.config.Height) + switch d.mode { + case mGray, mGrayInvert: + if d.bpp == 16 { + img = image.NewGray16(imgRect) + } else { + img = image.NewGray(imgRect) + } + case mPaletted: + img = image.NewPaletted(imgRect, d.palette) + case mNRGBA: + if d.bpp == 16 { + img = image.NewNRGBA64(imgRect) + } else { + img = image.NewNRGBA(imgRect) + } + case mRGB, mRGBA: + if d.bpp == 16 { + img = image.NewRGBA64(imgRect) + } else { + img = image.NewRGBA(imgRect) + } + } + + for i := 0; i < blocksAcross; i++ { + blkW := blockWidth + if !blockPadding && i == blocksAcross-1 && d.config.Width%blockWidth != 0 { + blkW = d.config.Width % blockWidth + } + for j := 0; j < blocksDown; j++ { + blkH := blockHeight + if !blockPadding && j == blocksDown-1 && d.config.Height%blockHeight != 0 { + blkH = d.config.Height % blockHeight + } + offset := int64(blockOffsets[j*blocksAcross+i]) + n := int64(blockCounts[j*blocksAcross+i]) + switch d.firstVal(tCompression) { + + // According to the spec, Compression does not have a default value, + // but some tools interpret a missing Compression value as none so we do + // the same. + case cNone, 0: + if b, ok := d.r.(*buffer); ok { + d.buf, err = b.Slice(int(offset), int(n)) + } else { + 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) + r.Close() + case cDeflate, cDeflateOld: + var r io.ReadCloser + r, err = zlib.NewReader(io.NewSectionReader(d.r, offset, n)) + if err != nil { + return nil, err + } + d.buf, err = ioutil.ReadAll(r) + r.Close() + case cPackBits: + d.buf, err = unpackBits(io.NewSectionReader(d.r, offset, n)) + default: + err = UnsupportedError(fmt.Sprintf("compression value %d", d.firstVal(tCompression))) + } + if err != nil { + return nil, err + } + + xmin := i * blockWidth + ymin := j * blockHeight + xmax := xmin + blkW + ymax := ymin + blkH + err = d.decode(img, xmin, ymin, xmax, ymax) + if err != nil { + return nil, err + } + } + } + return +} + +func init() { + image.RegisterFormat("tiff", leHeader, Decode, DecodeConfig) + image.RegisterFormat("tiff", beHeader, Decode, DecodeConfig) +} diff --git a/vendor/golang.org/x/image/tiff/writer.go b/vendor/golang.org/x/image/tiff/writer.go new file mode 100644 index 000000000..c8a01cea7 --- /dev/null +++ b/vendor/golang.org/x/image/tiff/writer.go @@ -0,0 +1,438 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tiff + +import ( + "bytes" + "compress/zlib" + "encoding/binary" + "image" + "io" + "sort" +) + +// The TIFF format allows to choose the order of the different elements freely. +// The basic structure of a TIFF file written by this package is: +// +// 1. Header (8 bytes). +// 2. Image data. +// 3. Image File Directory (IFD). +// 4. "Pointer area" for larger entries in the IFD. + +// We only write little-endian TIFF files. +var enc = binary.LittleEndian + +// An ifdEntry is a single entry in an Image File Directory. +// A value of type dtRational is composed of two 32-bit values, +// thus data contains two uints (numerator and denominator) for a single number. +type ifdEntry struct { + tag int + datatype int + data []uint32 +} + +func (e ifdEntry) putData(p []byte) { + for _, d := range e.data { + switch e.datatype { + case dtByte, dtASCII: + p[0] = byte(d) + p = p[1:] + case dtShort: + enc.PutUint16(p, uint16(d)) + p = p[2:] + case dtLong, dtRational: + enc.PutUint32(p, uint32(d)) + p = p[4:] + } + } +} + +type byTag []ifdEntry + +func (d byTag) Len() int { return len(d) } +func (d byTag) Less(i, j int) bool { return d[i].tag < d[j].tag } +func (d byTag) Swap(i, j int) { d[i], d[j] = d[j], d[i] } + +func encodeGray(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error { + if !predictor { + return writePix(w, pix, dy, dx, stride) + } + buf := make([]byte, dx) + for y := 0; y < dy; y++ { + min := y*stride + 0 + max := y*stride + dx + off := 0 + var v0 uint8 + for i := min; i < max; i++ { + v1 := pix[i] + buf[off] = v1 - v0 + v0 = v1 + off++ + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +func encodeGray16(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error { + buf := make([]byte, dx*2) + for y := 0; y < dy; y++ { + min := y*stride + 0 + max := y*stride + dx*2 + off := 0 + var v0 uint16 + for i := min; i < max; i += 2 { + // An image.Gray16's Pix is in big-endian order. + v1 := uint16(pix[i])<<8 | uint16(pix[i+1]) + if predictor { + v0, v1 = v1, v1-v0 + } + // We only write little-endian TIFF files. + buf[off+0] = byte(v1) + buf[off+1] = byte(v1 >> 8) + off += 2 + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +func encodeRGBA(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error { + if !predictor { + return writePix(w, pix, dy, dx*4, stride) + } + buf := make([]byte, dx*4) + for y := 0; y < dy; y++ { + min := y*stride + 0 + max := y*stride + dx*4 + off := 0 + var r0, g0, b0, a0 uint8 + for i := min; i < max; i += 4 { + r1, g1, b1, a1 := pix[i+0], pix[i+1], pix[i+2], pix[i+3] + buf[off+0] = r1 - r0 + buf[off+1] = g1 - g0 + buf[off+2] = b1 - b0 + buf[off+3] = a1 - a0 + off += 4 + r0, g0, b0, a0 = r1, g1, b1, a1 + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +func encodeRGBA64(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error { + buf := make([]byte, dx*8) + for y := 0; y < dy; y++ { + min := y*stride + 0 + max := y*stride + dx*8 + off := 0 + var r0, g0, b0, a0 uint16 + for i := min; i < max; i += 8 { + // An image.RGBA64's Pix is in big-endian order. + r1 := uint16(pix[i+0])<<8 | uint16(pix[i+1]) + g1 := uint16(pix[i+2])<<8 | uint16(pix[i+3]) + b1 := uint16(pix[i+4])<<8 | uint16(pix[i+5]) + a1 := uint16(pix[i+6])<<8 | uint16(pix[i+7]) + if predictor { + r0, r1 = r1, r1-r0 + g0, g1 = g1, g1-g0 + b0, b1 = b1, b1-b0 + a0, a1 = a1, a1-a0 + } + // We only write little-endian TIFF files. + buf[off+0] = byte(r1) + buf[off+1] = byte(r1 >> 8) + buf[off+2] = byte(g1) + buf[off+3] = byte(g1 >> 8) + buf[off+4] = byte(b1) + buf[off+5] = byte(b1 >> 8) + buf[off+6] = byte(a1) + buf[off+7] = byte(a1 >> 8) + off += 8 + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +func encode(w io.Writer, m image.Image, predictor bool) error { + bounds := m.Bounds() + buf := make([]byte, 4*bounds.Dx()) + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + off := 0 + if predictor { + var r0, g0, b0, a0 uint8 + for x := bounds.Min.X; x < bounds.Max.X; x++ { + r, g, b, a := m.At(x, y).RGBA() + r1 := uint8(r >> 8) + g1 := uint8(g >> 8) + b1 := uint8(b >> 8) + a1 := uint8(a >> 8) + buf[off+0] = r1 - r0 + buf[off+1] = g1 - g0 + buf[off+2] = b1 - b0 + buf[off+3] = a1 - a0 + off += 4 + r0, g0, b0, a0 = r1, g1, b1, a1 + } + } else { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + r, g, b, a := m.At(x, y).RGBA() + buf[off+0] = uint8(r >> 8) + buf[off+1] = uint8(g >> 8) + buf[off+2] = uint8(b >> 8) + buf[off+3] = uint8(a >> 8) + off += 4 + } + } + if _, err := w.Write(buf); err != nil { + return err + } + } + return nil +} + +// writePix writes the internal byte array of an image to w. It is less general +// but much faster then encode. writePix is used when pix directly +// corresponds to one of the TIFF image types. +func writePix(w io.Writer, pix []byte, nrows, length, stride int) error { + if length == stride { + _, err := w.Write(pix[:nrows*length]) + return err + } + for ; nrows > 0; nrows-- { + if _, err := w.Write(pix[:length]); err != nil { + return err + } + pix = pix[stride:] + } + return nil +} + +func writeIFD(w io.Writer, ifdOffset int, d []ifdEntry) error { + var buf [ifdLen]byte + // Make space for "pointer area" containing IFD entry data + // longer than 4 bytes. + parea := make([]byte, 1024) + pstart := ifdOffset + ifdLen*len(d) + 6 + var o int // Current offset in parea. + + // The IFD has to be written with the tags in ascending order. + sort.Sort(byTag(d)) + + // Write the number of entries in this IFD. + if err := binary.Write(w, enc, uint16(len(d))); err != nil { + return err + } + for _, ent := range d { + enc.PutUint16(buf[0:2], uint16(ent.tag)) + enc.PutUint16(buf[2:4], uint16(ent.datatype)) + count := uint32(len(ent.data)) + if ent.datatype == dtRational { + count /= 2 + } + enc.PutUint32(buf[4:8], count) + datalen := int(count * lengths[ent.datatype]) + if datalen <= 4 { + ent.putData(buf[8:12]) + } else { + if (o + datalen) > len(parea) { + newlen := len(parea) + 1024 + for (o + datalen) > newlen { + newlen += 1024 + } + newarea := make([]byte, newlen) + copy(newarea, parea) + parea = newarea + } + ent.putData(parea[o : o+datalen]) + enc.PutUint32(buf[8:12], uint32(pstart+o)) + o += datalen + } + if _, err := w.Write(buf[:]); err != nil { + return err + } + } + // The IFD ends with the offset of the next IFD in the file, + // or zero if it is the last one (page 14). + if err := binary.Write(w, enc, uint32(0)); err != nil { + return err + } + _, err := w.Write(parea[:o]) + return err +} + +// Options are the encoding parameters. +type Options struct { + // Compression is the type of compression used. + Compression CompressionType + // Predictor determines whether a differencing predictor is used; + // if true, instead of each pixel's color, the color difference to the + // preceding one is saved. This improves the compression for certain + // types of images and compressors. For example, it works well for + // photos with Deflate compression. + Predictor bool +} + +// 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. +func Encode(w io.Writer, m image.Image, opt *Options) error { + d := m.Bounds().Size() + + compression := uint32(cNone) + predictor := false + if opt != nil { + compression = opt.Compression.specValue() + // The predictor field is only used with LZW. See page 64 of the spec. + predictor = opt.Predictor && compression == cLZW + } + + _, err := io.WriteString(w, leHeader) + if err != nil { + return err + } + + // Compressed data is written into a buffer first, so that we + // know the compressed size. + var buf bytes.Buffer + // dst holds the destination for the pixel data of the image -- + // either w or a writer to buf. + var dst io.Writer + // imageLen is the length of the pixel data in bytes. + // The offset of the IFD is imageLen + 8 header bytes. + var imageLen int + + switch compression { + case cNone: + dst = w + // Write IFD offset before outputting pixel data. + switch m.(type) { + case *image.Paletted: + imageLen = d.X * d.Y * 1 + case *image.Gray: + imageLen = d.X * d.Y * 1 + case *image.Gray16: + imageLen = d.X * d.Y * 2 + case *image.RGBA64: + imageLen = d.X * d.Y * 8 + case *image.NRGBA64: + imageLen = d.X * d.Y * 8 + default: + imageLen = d.X * d.Y * 4 + } + err = binary.Write(w, enc, uint32(imageLen+8)) + if err != nil { + return err + } + case cDeflate: + dst = zlib.NewWriter(&buf) + } + + pr := uint32(prNone) + photometricInterpretation := uint32(pRGB) + samplesPerPixel := uint32(4) + bitsPerSample := []uint32{8, 8, 8, 8} + extraSamples := uint32(0) + colorMap := []uint32{} + + if predictor { + pr = prHorizontal + } + switch m := m.(type) { + case *image.Paletted: + photometricInterpretation = pPaletted + samplesPerPixel = 1 + bitsPerSample = []uint32{8} + colorMap = make([]uint32, 256*3) + for i := 0; i < 256 && i < len(m.Palette); i++ { + r, g, b, _ := m.Palette[i].RGBA() + colorMap[i+0*256] = uint32(r) + colorMap[i+1*256] = uint32(g) + colorMap[i+2*256] = uint32(b) + } + err = encodeGray(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.Gray: + photometricInterpretation = pBlackIsZero + samplesPerPixel = 1 + bitsPerSample = []uint32{8} + err = encodeGray(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.Gray16: + photometricInterpretation = pBlackIsZero + samplesPerPixel = 1 + bitsPerSample = []uint32{16} + err = encodeGray16(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.NRGBA: + extraSamples = 2 // Unassociated alpha. + err = encodeRGBA(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.NRGBA64: + extraSamples = 2 // Unassociated alpha. + bitsPerSample = []uint32{16, 16, 16, 16} + err = encodeRGBA64(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.RGBA: + extraSamples = 1 // Associated alpha. + err = encodeRGBA(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + case *image.RGBA64: + extraSamples = 1 // Associated alpha. + bitsPerSample = []uint32{16, 16, 16, 16} + err = encodeRGBA64(dst, m.Pix, d.X, d.Y, m.Stride, predictor) + default: + extraSamples = 1 // Associated alpha. + err = encode(dst, m, predictor) + } + if err != nil { + return err + } + + if compression != cNone { + if err = dst.(io.Closer).Close(); err != nil { + return err + } + imageLen = buf.Len() + if err = binary.Write(w, enc, uint32(imageLen+8)); err != nil { + return err + } + if _, err = buf.WriteTo(w); err != nil { + return err + } + } + + ifd := []ifdEntry{ + {tImageWidth, dtShort, []uint32{uint32(d.X)}}, + {tImageLength, dtShort, []uint32{uint32(d.Y)}}, + {tBitsPerSample, dtShort, bitsPerSample}, + {tCompression, dtShort, []uint32{compression}}, + {tPhotometricInterpretation, dtShort, []uint32{photometricInterpretation}}, + {tStripOffsets, dtLong, []uint32{8}}, + {tSamplesPerPixel, dtShort, []uint32{samplesPerPixel}}, + {tRowsPerStrip, dtShort, []uint32{uint32(d.Y)}}, + {tStripByteCounts, dtLong, []uint32{uint32(imageLen)}}, + // There is currently no support for storing the image + // resolution, so give a bogus value of 72x72 dpi. + {tXResolution, dtRational, []uint32{72, 1}}, + {tYResolution, dtRational, []uint32{72, 1}}, + {tResolutionUnit, dtShort, []uint32{resPerInch}}, + } + if pr != prNone { + ifd = append(ifd, ifdEntry{tPredictor, dtShort, []uint32{pr}}) + } + if len(colorMap) != 0 { + ifd = append(ifd, ifdEntry{tColorMap, dtShort, colorMap}) + } + if extraSamples > 0 { + ifd = append(ifd, ifdEntry{tExtraSamples, dtShort, []uint32{extraSamples}}) + } + + return writeIFD(w, imageLen+8, ifd) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6004f1c8a..a59777756 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -69,6 +69,9 @@ github.com/coreos/go-oidc/v3/oidc # github.com/davecgh/go-spew v1.1.1 ## explicit github.com/davecgh/go-spew/spew +# github.com/disintegration/imaging v1.6.2 +## explicit +github.com/disintegration/imaging # github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b ## explicit; go 1.12 github.com/dsoprea/go-exif/v3 @@ -282,7 +285,6 @@ github.com/modern-go/concurrent github.com/modern-go/reflect2 # github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 ## explicit -github.com/nfnt/resize # github.com/oklog/ulid v1.3.1 ## explicit github.com/oklog/ulid @@ -617,6 +619,12 @@ golang.org/x/crypto/ssh/internal/bcrypt_pbkdf ## explicit; go 1.18 golang.org/x/exp/constraints golang.org/x/exp/slices +# golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 +## explicit; go 1.12 +golang.org/x/image/bmp +golang.org/x/image/ccitt +golang.org/x/image/tiff +golang.org/x/image/tiff/lzw # golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 ## explicit; go 1.17 golang.org/x/mod/semver