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