summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/disintegration/imaging/io.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/disintegration/imaging/io.go')
-rw-r--r--vendor/github.com/disintegration/imaging/io.go463
1 files changed, 463 insertions, 0 deletions
diff --git a/vendor/github.com/disintegration/imaging/io.go b/vendor/github.com/disintegration/imaging/io.go
new file mode 100644
index 000000000..557bf2f3d
--- /dev/null
+++ b/vendor/github.com/disintegration/imaging/io.go
@@ -0,0 +1,463 @@
+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"
+)
+
+// Format is an image file format.
+type Format int
+
+// Image file formats.
+const (
+ JPEG Format = iota
+ PNG
+ GIF
+ TIFF
+ BMP
+)
+
+func (f Format) String() string {
+ switch f {
+ case JPEG:
+ return "JPEG"
+ case PNG:
+ return "PNG"
+ case GIF:
+ return "GIF"
+ case TIFF:
+ return "TIFF"
+ case BMP:
+ return "BMP"
+ default:
+ return "Unsupported"
+ }
+}
+
+var formatFromExt = map[string]Format{
+ "jpg": JPEG,
+ "jpeg": JPEG,
+ "png": PNG,
+ "tif": TIFF,
+ "tiff": TIFF,
+ "bmp": BMP,
+ "gif": GIF,
+}
+
+// FormatFromExtension parses image format from extension:
+// "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
+func FormatFromExtension(ext string) (Format, error) {
+ if f, ok := formatFromExt[strings.ToLower(strings.TrimPrefix(ext, "."))]; ok {
+ return f, nil
+ }
+ return -1, ErrUnsupportedFormat
+}
+
+// FormatFromFilename parses image format from filename extension:
+// "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)
+}
+
+var (
+ // ErrUnsupportedFormat means the given image format (or file extension) is unsupported.
+ ErrUnsupportedFormat = errors.New("imaging: unsupported image format")
+)
+
+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...)
+}
+
+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)
+ }
+
+ var err error
+ switch format {
+ case JPEG:
+ var rgba *image.RGBA
+ if nrgba, ok := img.(*image.NRGBA); ok {
+ if nrgba.Opaque() {
+ rgba = &image.RGBA{
+ Pix: nrgba.Pix,
+ Stride: nrgba.Stride,
+ Rect: nrgba.Rect,
+ }
+ }
+ }
+ if rgba != nil {
+ err = jpeg.Encode(w, rgba, &jpeg.Options{Quality: cfg.jpegQuality})
+ } else {
+ err = jpeg.Encode(w, img, &jpeg.Options{Quality: cfg.jpegQuality})
+ }
+
+ case PNG:
+ enc := png.Encoder{CompressionLevel: cfg.pngCompressionLevel}
+ err = enc.Encode(w, img)
+
+ case GIF:
+ err = gif.Encode(w, img, &gif.Options{
+ NumColors: cfg.gifNumColors,
+ Quantizer: cfg.gifQuantizer,
+ Drawer: cfg.gifDrawer,
+ })
+
+ case TIFF:
+ err = tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
+
+ case BMP:
+ err = bmp.Encode(w, img)
+
+ default:
+ err = ErrUnsupportedFormat
+ }
+ return err
+}
+
+// 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
+ }
+
+ defer func() {
+ cerr := file.Close()
+ if err == nil {
+ err = cerr
+ }
+ }()
+
+ return Encode(file, img, f, opts...)
+}
+
+// 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
+}