io.go

  1package imaging
  2
  3import (
  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
 21type fileSystem interface {
 22	Create(string) (io.WriteCloser, error)
 23	Open(string) (io.ReadCloser, error)
 24}
 25
 26type localFS struct{}
 27
 28func (localFS) Create(name string) (io.WriteCloser, error) { return os.Create(name) }
 29func (localFS) Open(name string) (io.ReadCloser, error)    { return os.Open(name) }
 30
 31var fs fileSystem = localFS{}
 32
 33type decodeConfig struct {
 34	autoOrientation bool
 35}
 36
 37var defaultDecodeConfig = decodeConfig{
 38	autoOrientation: false,
 39}
 40
 41// DecodeOption sets an optional parameter for the Decode and Open functions.
 42type 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.
 47func AutoOrientation(enabled bool) DecodeOption {
 48	return func(c *decodeConfig) {
 49		c.autoOrientation = enabled
 50	}
 51}
 52
 53// Decode reads an image from r.
 54func 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//
 95func 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.
105type Format int
106
107// Image file formats.
108const (
109	JPEG Format = iota
110	PNG
111	GIF
112	TIFF
113	BMP
114)
115
116var 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
126var formatNames = map[Format]string{
127	JPEG: "JPEG",
128	PNG:  "PNG",
129	GIF:  "GIF",
130	TIFF: "TIFF",
131	BMP:  "BMP",
132}
133
134func (f Format) String() string {
135	return formatNames[f]
136}
137
138// ErrUnsupportedFormat means the given image format is not supported.
139var 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.
143func 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.
152func FormatFromFilename(filename string) (Format, error) {
153	ext := filepath.Ext(filename)
154	return FormatFromExtension(ext)
155}
156
157type encodeConfig struct {
158	jpegQuality         int
159	gifNumColors        int
160	gifQuantizer        draw.Quantizer
161	gifDrawer           draw.Drawer
162	pngCompressionLevel png.CompressionLevel
163}
164
165var 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.
174type 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.
178func 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.
186func 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.
194func 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.
202func 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.
210func 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).
217func 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//
268func 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.
287type orientation int
288
289const (
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.
305func 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.
425func 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}