// Package imaging provides basic image manipulation functions (resize, rotate, flip, crop, etc.). // This package is based on the standard Go image package and works best along with it. // // Image manipulation functions provided by the package take any image type // that implements `image.Image` interface as an input, and return a new image of // `*image.NRGBA` type (32bit RGBA colors, not premultiplied by alpha). package imaging import ( "errors" "image" "image/color" "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") ) // Decode reads an image from r. func Decode(r io.Reader) (image.Image, error) { img, _, err := image.Decode(r) if err != nil { return nil, err } return toNRGBA(img), nil } // Open loads an image from file func Open(filename string) (image.Image, error) { file, err := os.Open(filename) if err != nil { return nil, err } defer file.Close() img, err := Decode(file) return img, err } type encodeConfig struct { jpegQuality int } var defaultEncodeConfig = encodeConfig{ jpegQuality: 95, } // 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 } } // 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: err = png.Encode(w, img) case GIF: err = gif.Encode(w, img, &gif.Options{NumColors: 256}) 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 := os.Create(filename) if err != nil { return err } defer file.Close() 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 } cs := []uint8{c.R, c.G, c.B, c.A} // fill the first row for x := 0; x < width; x++ { copy(dst.Pix[x*4:(x+1)*4], cs) } // copy the first row to other rows for y := 1; y < height; y++ { copy(dst.Pix[y*dst.Stride:y*dst.Stride+width*4], dst.Pix[0:width*4]) } return dst }