io.go raw

   1  package imaging
   2  
   3  import (
   4  	"encoding/binary"
   5  	"errors"
   6  	"image"
   7  	"image/draw"
   8  	"image/gif"
   9  	"image/jpeg"
  10  	"image/png"
  11  	"io"
  12  	"io/ioutil"
  13  	"os"
  14  	"path/filepath"
  15  	"strings"
  16  
  17  	"golang.org/x/image/bmp"
  18  	"golang.org/x/image/tiff"
  19  )
  20  
  21  type fileSystem interface {
  22  	Create(string) (io.WriteCloser, error)
  23  	Open(string) (io.ReadCloser, error)
  24  }
  25  
  26  type localFS struct{}
  27  
  28  func (localFS) Create(name string) (io.WriteCloser, error) { return os.Create(name) }
  29  func (localFS) Open(name string) (io.ReadCloser, error)    { return os.Open(name) }
  30  
  31  var fs fileSystem = localFS{}
  32  
  33  type decodeConfig struct {
  34  	autoOrientation bool
  35  }
  36  
  37  var defaultDecodeConfig = decodeConfig{
  38  	autoOrientation: false,
  39  }
  40  
  41  // DecodeOption sets an optional parameter for the Decode and Open functions.
  42  type DecodeOption func(*decodeConfig)
  43  
  44  // AutoOrientation returns a DecodeOption that sets the auto-orientation mode.
  45  // If auto-orientation is enabled, the image will be transformed after decoding
  46  // according to the EXIF orientation tag (if present). By default it's disabled.
  47  func AutoOrientation(enabled bool) DecodeOption {
  48  	return func(c *decodeConfig) {
  49  		c.autoOrientation = enabled
  50  	}
  51  }
  52  
  53  // Decode reads an image from r.
  54  func Decode(r io.Reader, opts ...DecodeOption) (image.Image, error) {
  55  	cfg := defaultDecodeConfig
  56  	for _, option := range opts {
  57  		option(&cfg)
  58  	}
  59  
  60  	if !cfg.autoOrientation {
  61  		img, _, err := image.Decode(r)
  62  		return img, err
  63  	}
  64  
  65  	var orient orientation
  66  	pr, pw := io.Pipe()
  67  	r = io.TeeReader(r, pw)
  68  	done := make(chan struct{})
  69  	go func() {
  70  		defer close(done)
  71  		orient = readOrientation(pr)
  72  		io.Copy(ioutil.Discard, pr)
  73  	}()
  74  
  75  	img, _, err := image.Decode(r)
  76  	pw.Close()
  77  	<-done
  78  	if err != nil {
  79  		return nil, err
  80  	}
  81  
  82  	return fixOrientation(img, orient), nil
  83  }
  84  
  85  // Open loads an image from file.
  86  //
  87  // Examples:
  88  //
  89  //	// Load an image from file.
  90  //	img, err := imaging.Open("test.jpg")
  91  //
  92  //	// Load an image and transform it depending on the EXIF orientation tag (if present).
  93  //	img, err := imaging.Open("test.jpg", imaging.AutoOrientation(true))
  94  //
  95  func Open(filename string, opts ...DecodeOption) (image.Image, error) {
  96  	file, err := fs.Open(filename)
  97  	if err != nil {
  98  		return nil, err
  99  	}
 100  	defer file.Close()
 101  	return Decode(file, opts...)
 102  }
 103  
 104  // Format is an image file format.
 105  type Format int
 106  
 107  // Image file formats.
 108  const (
 109  	JPEG Format = iota
 110  	PNG
 111  	GIF
 112  	TIFF
 113  	BMP
 114  )
 115  
 116  var formatExts = map[string]Format{
 117  	"jpg":  JPEG,
 118  	"jpeg": JPEG,
 119  	"png":  PNG,
 120  	"gif":  GIF,
 121  	"tif":  TIFF,
 122  	"tiff": TIFF,
 123  	"bmp":  BMP,
 124  }
 125  
 126  var formatNames = map[Format]string{
 127  	JPEG: "JPEG",
 128  	PNG:  "PNG",
 129  	GIF:  "GIF",
 130  	TIFF: "TIFF",
 131  	BMP:  "BMP",
 132  }
 133  
 134  func (f Format) String() string {
 135  	return formatNames[f]
 136  }
 137  
 138  // ErrUnsupportedFormat means the given image format is not supported.
 139  var ErrUnsupportedFormat = errors.New("imaging: unsupported image format")
 140  
 141  // FormatFromExtension parses image format from filename extension:
 142  // "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
 143  func FormatFromExtension(ext string) (Format, error) {
 144  	if f, ok := formatExts[strings.ToLower(strings.TrimPrefix(ext, "."))]; ok {
 145  		return f, nil
 146  	}
 147  	return -1, ErrUnsupportedFormat
 148  }
 149  
 150  // FormatFromFilename parses image format from filename:
 151  // "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
 152  func FormatFromFilename(filename string) (Format, error) {
 153  	ext := filepath.Ext(filename)
 154  	return FormatFromExtension(ext)
 155  }
 156  
 157  type encodeConfig struct {
 158  	jpegQuality         int
 159  	gifNumColors        int
 160  	gifQuantizer        draw.Quantizer
 161  	gifDrawer           draw.Drawer
 162  	pngCompressionLevel png.CompressionLevel
 163  }
 164  
 165  var defaultEncodeConfig = encodeConfig{
 166  	jpegQuality:         95,
 167  	gifNumColors:        256,
 168  	gifQuantizer:        nil,
 169  	gifDrawer:           nil,
 170  	pngCompressionLevel: png.DefaultCompression,
 171  }
 172  
 173  // EncodeOption sets an optional parameter for the Encode and Save functions.
 174  type EncodeOption func(*encodeConfig)
 175  
 176  // JPEGQuality returns an EncodeOption that sets the output JPEG quality.
 177  // Quality ranges from 1 to 100 inclusive, higher is better. Default is 95.
 178  func JPEGQuality(quality int) EncodeOption {
 179  	return func(c *encodeConfig) {
 180  		c.jpegQuality = quality
 181  	}
 182  }
 183  
 184  // GIFNumColors returns an EncodeOption that sets the maximum number of colors
 185  // used in the GIF-encoded image. It ranges from 1 to 256.  Default is 256.
 186  func GIFNumColors(numColors int) EncodeOption {
 187  	return func(c *encodeConfig) {
 188  		c.gifNumColors = numColors
 189  	}
 190  }
 191  
 192  // GIFQuantizer returns an EncodeOption that sets the quantizer that is used to produce
 193  // a palette of the GIF-encoded image.
 194  func GIFQuantizer(quantizer draw.Quantizer) EncodeOption {
 195  	return func(c *encodeConfig) {
 196  		c.gifQuantizer = quantizer
 197  	}
 198  }
 199  
 200  // GIFDrawer returns an EncodeOption that sets the drawer that is used to convert
 201  // the source image to the desired palette of the GIF-encoded image.
 202  func GIFDrawer(drawer draw.Drawer) EncodeOption {
 203  	return func(c *encodeConfig) {
 204  		c.gifDrawer = drawer
 205  	}
 206  }
 207  
 208  // PNGCompressionLevel returns an EncodeOption that sets the compression level
 209  // of the PNG-encoded image. Default is png.DefaultCompression.
 210  func PNGCompressionLevel(level png.CompressionLevel) EncodeOption {
 211  	return func(c *encodeConfig) {
 212  		c.pngCompressionLevel = level
 213  	}
 214  }
 215  
 216  // Encode writes the image img to w in the specified format (JPEG, PNG, GIF, TIFF or BMP).
 217  func Encode(w io.Writer, img image.Image, format Format, opts ...EncodeOption) error {
 218  	cfg := defaultEncodeConfig
 219  	for _, option := range opts {
 220  		option(&cfg)
 221  	}
 222  
 223  	switch format {
 224  	case JPEG:
 225  		if nrgba, ok := img.(*image.NRGBA); ok && nrgba.Opaque() {
 226  			rgba := &image.RGBA{
 227  				Pix:    nrgba.Pix,
 228  				Stride: nrgba.Stride,
 229  				Rect:   nrgba.Rect,
 230  			}
 231  			return jpeg.Encode(w, rgba, &jpeg.Options{Quality: cfg.jpegQuality})
 232  		}
 233  		return jpeg.Encode(w, img, &jpeg.Options{Quality: cfg.jpegQuality})
 234  
 235  	case PNG:
 236  		encoder := png.Encoder{CompressionLevel: cfg.pngCompressionLevel}
 237  		return encoder.Encode(w, img)
 238  
 239  	case GIF:
 240  		return gif.Encode(w, img, &gif.Options{
 241  			NumColors: cfg.gifNumColors,
 242  			Quantizer: cfg.gifQuantizer,
 243  			Drawer:    cfg.gifDrawer,
 244  		})
 245  
 246  	case TIFF:
 247  		return tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
 248  
 249  	case BMP:
 250  		return bmp.Encode(w, img)
 251  	}
 252  
 253  	return ErrUnsupportedFormat
 254  }
 255  
 256  // Save saves the image to file with the specified filename.
 257  // The format is determined from the filename extension:
 258  // "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
 259  //
 260  // Examples:
 261  //
 262  //	// Save the image as PNG.
 263  //	err := imaging.Save(img, "out.png")
 264  //
 265  //	// Save the image as JPEG with optional quality parameter set to 80.
 266  //	err := imaging.Save(img, "out.jpg", imaging.JPEGQuality(80))
 267  //
 268  func Save(img image.Image, filename string, opts ...EncodeOption) (err error) {
 269  	f, err := FormatFromFilename(filename)
 270  	if err != nil {
 271  		return err
 272  	}
 273  	file, err := fs.Create(filename)
 274  	if err != nil {
 275  		return err
 276  	}
 277  	err = Encode(file, img, f, opts...)
 278  	errc := file.Close()
 279  	if err == nil {
 280  		err = errc
 281  	}
 282  	return err
 283  }
 284  
 285  // orientation is an EXIF flag that specifies the transformation
 286  // that should be applied to image to display it correctly.
 287  type orientation int
 288  
 289  const (
 290  	orientationUnspecified = 0
 291  	orientationNormal      = 1
 292  	orientationFlipH       = 2
 293  	orientationRotate180   = 3
 294  	orientationFlipV       = 4
 295  	orientationTranspose   = 5
 296  	orientationRotate270   = 6
 297  	orientationTransverse  = 7
 298  	orientationRotate90    = 8
 299  )
 300  
 301  // readOrientation tries to read the orientation EXIF flag from image data in r.
 302  // If the EXIF data block is not found or the orientation flag is not found
 303  // or any other error occures while reading the data, it returns the
 304  // orientationUnspecified (0) value.
 305  func readOrientation(r io.Reader) orientation {
 306  	const (
 307  		markerSOI      = 0xffd8
 308  		markerAPP1     = 0xffe1
 309  		exifHeader     = 0x45786966
 310  		byteOrderBE    = 0x4d4d
 311  		byteOrderLE    = 0x4949
 312  		orientationTag = 0x0112
 313  	)
 314  
 315  	// Check if JPEG SOI marker is present.
 316  	var soi uint16
 317  	if err := binary.Read(r, binary.BigEndian, &soi); err != nil {
 318  		return orientationUnspecified
 319  	}
 320  	if soi != markerSOI {
 321  		return orientationUnspecified // Missing JPEG SOI marker.
 322  	}
 323  
 324  	// Find JPEG APP1 marker.
 325  	for {
 326  		var marker, size uint16
 327  		if err := binary.Read(r, binary.BigEndian, &marker); err != nil {
 328  			return orientationUnspecified
 329  		}
 330  		if err := binary.Read(r, binary.BigEndian, &size); err != nil {
 331  			return orientationUnspecified
 332  		}
 333  		if marker>>8 != 0xff {
 334  			return orientationUnspecified // Invalid JPEG marker.
 335  		}
 336  		if marker == markerAPP1 {
 337  			break
 338  		}
 339  		if size < 2 {
 340  			return orientationUnspecified // Invalid block size.
 341  		}
 342  		if _, err := io.CopyN(ioutil.Discard, r, int64(size-2)); err != nil {
 343  			return orientationUnspecified
 344  		}
 345  	}
 346  
 347  	// Check if EXIF header is present.
 348  	var header uint32
 349  	if err := binary.Read(r, binary.BigEndian, &header); err != nil {
 350  		return orientationUnspecified
 351  	}
 352  	if header != exifHeader {
 353  		return orientationUnspecified
 354  	}
 355  	if _, err := io.CopyN(ioutil.Discard, r, 2); err != nil {
 356  		return orientationUnspecified
 357  	}
 358  
 359  	// Read byte order information.
 360  	var (
 361  		byteOrderTag uint16
 362  		byteOrder    binary.ByteOrder
 363  	)
 364  	if err := binary.Read(r, binary.BigEndian, &byteOrderTag); err != nil {
 365  		return orientationUnspecified
 366  	}
 367  	switch byteOrderTag {
 368  	case byteOrderBE:
 369  		byteOrder = binary.BigEndian
 370  	case byteOrderLE:
 371  		byteOrder = binary.LittleEndian
 372  	default:
 373  		return orientationUnspecified // Invalid byte order flag.
 374  	}
 375  	if _, err := io.CopyN(ioutil.Discard, r, 2); err != nil {
 376  		return orientationUnspecified
 377  	}
 378  
 379  	// Skip the EXIF offset.
 380  	var offset uint32
 381  	if err := binary.Read(r, byteOrder, &offset); err != nil {
 382  		return orientationUnspecified
 383  	}
 384  	if offset < 8 {
 385  		return orientationUnspecified // Invalid offset value.
 386  	}
 387  	if _, err := io.CopyN(ioutil.Discard, r, int64(offset-8)); err != nil {
 388  		return orientationUnspecified
 389  	}
 390  
 391  	// Read the number of tags.
 392  	var numTags uint16
 393  	if err := binary.Read(r, byteOrder, &numTags); err != nil {
 394  		return orientationUnspecified
 395  	}
 396  
 397  	// Find the orientation tag.
 398  	for i := 0; i < int(numTags); i++ {
 399  		var tag uint16
 400  		if err := binary.Read(r, byteOrder, &tag); err != nil {
 401  			return orientationUnspecified
 402  		}
 403  		if tag != orientationTag {
 404  			if _, err := io.CopyN(ioutil.Discard, r, 10); err != nil {
 405  				return orientationUnspecified
 406  			}
 407  			continue
 408  		}
 409  		if _, err := io.CopyN(ioutil.Discard, r, 6); err != nil {
 410  			return orientationUnspecified
 411  		}
 412  		var val uint16
 413  		if err := binary.Read(r, byteOrder, &val); err != nil {
 414  			return orientationUnspecified
 415  		}
 416  		if val < 1 || val > 8 {
 417  			return orientationUnspecified // Invalid tag value.
 418  		}
 419  		return orientation(val)
 420  	}
 421  	return orientationUnspecified // Missing orientation tag.
 422  }
 423  
 424  // fixOrientation applies a transform to img corresponding to the given orientation flag.
 425  func fixOrientation(img image.Image, o orientation) image.Image {
 426  	switch o {
 427  	case orientationNormal:
 428  	case orientationFlipH:
 429  		img = FlipH(img)
 430  	case orientationFlipV:
 431  		img = FlipV(img)
 432  	case orientationRotate90:
 433  		img = Rotate90(img)
 434  	case orientationRotate180:
 435  		img = Rotate180(img)
 436  	case orientationRotate270:
 437  		img = Rotate270(img)
 438  	case orientationTranspose:
 439  		img = Transpose(img)
 440  	case orientationTransverse:
 441  		img = Transverse(img)
 442  	}
 443  	return img
 444  }
 445