package imaging import ( "errors" "image" "image/color" "image/draw" "image/gif" "image/jpeg" "image/png" "io" "os" "path/filepath" "strings" "golang.org/x/image/bmp" "golang.org/x/image/tiff" ) // Format is an image file format. type Format int // Image file formats. const ( JPEG Format = iota PNG GIF TIFF BMP ) func (f Format) String() string { switch f { case JPEG: return "JPEG" case PNG: return "PNG" case GIF: return "GIF" case TIFF: return "TIFF" case BMP: return "BMP" default: return "Unsupported" } } var ( // ErrUnsupportedFormat means the given image format (or file extension) is unsupported. ErrUnsupportedFormat = errors.New("imaging: unsupported image format") ) type fileSystem interface { Create(string) (io.WriteCloser, error) Open(string) (io.ReadCloser, error) } type localFS struct{} func (localFS) Create(name string) (io.WriteCloser, error) { return os.Create(name) } func (localFS) Open(name string) (io.ReadCloser, error) { return os.Open(name) } var fs fileSystem = localFS{} // Decode reads an image from r. func Decode(r io.Reader) (image.Image, error) { img, _, err := image.Decode(r) return img, err } // Open loads an image from file func Open(filename string) (image.Image, error) { file, err := fs.Open(filename) if err != nil { return nil, err } defer file.Close() return Decode(file) } type encodeConfig struct { jpegQuality int gifNumColors int gifQuantizer draw.Quantizer gifDrawer draw.Drawer pngCompressionLevel png.CompressionLevel } var defaultEncodeConfig = encodeConfig{ jpegQuality: 95, gifNumColors: 256, gifQuantizer: nil, gifDrawer: nil, pngCompressionLevel: png.DefaultCompression, } // EncodeOption sets an optional parameter for the Encode and Save functions. type EncodeOption func(*encodeConfig) // JPEGQuality returns an EncodeOption that sets the output JPEG quality. // Quality ranges from 1 to 100 inclusive, higher is better. Default is 95. func JPEGQuality(quality int) EncodeOption { return func(c *encodeConfig) { c.jpegQuality = quality } } // GIFNumColors returns an EncodeOption that sets the maximum number of colors // used in the GIF-encoded image. It ranges from 1 to 256. Default is 256. func GIFNumColors(numColors int) EncodeOption { return func(c *encodeConfig) { c.gifNumColors = numColors } } // GIFQuantizer returns an EncodeOption that sets the quantizer that is used to produce // a palette of the GIF-encoded image. func GIFQuantizer(quantizer draw.Quantizer) EncodeOption { return func(c *encodeConfig) { c.gifQuantizer = quantizer } } // GIFDrawer returns an EncodeOption that sets the drawer that is used to convert // the source image to the desired palette of the GIF-encoded image. func GIFDrawer(drawer draw.Drawer) EncodeOption { return func(c *encodeConfig) { c.gifDrawer = drawer } } // PNGCompressionLevel returns an EncodeOption that sets the compression level // of the PNG-encoded image. Default is png.DefaultCompression. func PNGCompressionLevel(level png.CompressionLevel) EncodeOption { return func(c *encodeConfig) { c.pngCompressionLevel = level } } // Encode writes the image img to w in the specified format (JPEG, PNG, GIF, TIFF or BMP). func Encode(w io.Writer, img image.Image, format Format, opts ...EncodeOption) error { cfg := defaultEncodeConfig for _, option := range opts { option(&cfg) } var err error switch format { case JPEG: var rgba *image.RGBA if nrgba, ok := img.(*image.NRGBA); ok { if nrgba.Opaque() { rgba = &image.RGBA{ Pix: nrgba.Pix, Stride: nrgba.Stride, Rect: nrgba.Rect, } } } if rgba != nil { err = jpeg.Encode(w, rgba, &jpeg.Options{Quality: cfg.jpegQuality}) } else { err = jpeg.Encode(w, img, &jpeg.Options{Quality: cfg.jpegQuality}) } case PNG: enc := png.Encoder{CompressionLevel: cfg.pngCompressionLevel} err = enc.Encode(w, img) case GIF: err = gif.Encode(w, img, &gif.Options{ NumColors: cfg.gifNumColors, Quantizer: cfg.gifQuantizer, Drawer: cfg.gifDrawer, }) case TIFF: err = tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true}) case BMP: err = bmp.Encode(w, img) default: err = ErrUnsupportedFormat } return err } // Save saves the image to file with the specified filename. // The format is determined from the filename extension: "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported. // // Examples: // // // Save the image as PNG. // err := imaging.Save(img, "out.png") // // // Save the image as JPEG with optional quality parameter set to 80. // err := imaging.Save(img, "out.jpg", imaging.JPEGQuality(80)) // func Save(img image.Image, filename string, opts ...EncodeOption) (err error) { formats := map[string]Format{ ".jpg": JPEG, ".jpeg": JPEG, ".png": PNG, ".tif": TIFF, ".tiff": TIFF, ".bmp": BMP, ".gif": GIF, } ext := strings.ToLower(filepath.Ext(filename)) f, ok := formats[ext] if !ok { return ErrUnsupportedFormat } file, err := fs.Create(filename) if err != nil { return err } defer func() { cerr := file.Close() if err == nil { err = cerr } }() return Encode(file, img, f, opts...) } // New creates a new image with the specified width and height, and fills it with the specified color. func New(width, height int, fillColor color.Color) *image.NRGBA { if width <= 0 || height <= 0 { return &image.NRGBA{} } dst := image.NewNRGBA(image.Rect(0, 0, width, height)) c := color.NRGBAModel.Convert(fillColor).(color.NRGBA) if c.R == 0 && c.G == 0 && c.B == 0 && c.A == 0 { return dst } // Fill the first row. i := 0 for x := 0; x < width; x++ { dst.Pix[i+0] = c.R dst.Pix[i+1] = c.G dst.Pix[i+2] = c.B dst.Pix[i+3] = c.A i += 4 } // Copy the first row to other rows. size := width * 4 parallel(1, height, func(ys <-chan int) { for y := range ys { i = y * dst.Stride copy(dst.Pix[i:i+size], dst.Pix[0:size]) } }) return dst } // Clone returns a copy of the given image. func Clone(img image.Image) *image.NRGBA { src := newScanner(img) dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h)) size := src.w * 4 parallel(0, src.h, func(ys <-chan int) { for y := range ys { i := y * dst.Stride src.scan(0, y, src.w, y+1, dst.Pix[i:i+size]) } }) return dst }