git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/imaging/io.go (about)

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