gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }