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}