| package imaging |
| |
| import ( |
| "bytes" |
| "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 formatFromExt = map[string]Format{ |
| ".jpg": JPEG, |
| ".jpeg": JPEG, |
| ".png": PNG, |
| ".tif": TIFF, |
| ".tiff": TIFF, |
| ".bmp": BMP, |
| ".gif": GIF, |
| } |
| |
| // FormatFromFilename parses image format from filename extension: |
| // "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported. |
| func FormatFromFilename(filename string) (Format, error) { |
| ext := strings.ToLower(filepath.Ext(filename)) |
| if f, ok := formatFromExt[ext]; ok { |
| return f, nil |
| } |
| return -1, ErrUnsupportedFormat |
| } |
| |
| 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) { |
| f, err := FormatFromFilename(filename) |
| if err != nil { |
| return err |
| } |
| 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{} |
| } |
| |
| c := color.NRGBAModel.Convert(fillColor).(color.NRGBA) |
| if (c == color.NRGBA{0, 0, 0, 0}) { |
| return image.NewNRGBA(image.Rect(0, 0, width, height)) |
| } |
| |
| return &image.NRGBA{ |
| Pix: bytes.Repeat([]byte{c.R, c.G, c.B, c.A}, width*height), |
| Stride: 4 * width, |
| Rect: image.Rect(0, 0, width, height), |
| } |
| } |
| |
| // 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 |
| } |