io.go (11306B)
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 }