From b039796423f87e7caca7f8eaace36878f2cce73d Mon Sep 17 00:00:00 2001 From: Grigory Dryapak Date: Thu, 8 Mar 2018 16:13:05 +0300 Subject: [PATCH] add gif encode options --- helpers.go | 41 +++++++++++++++++++++++++++++--- helpers_test.go | 63 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 84 insertions(+), 20 deletions(-) diff --git a/helpers.go b/helpers.go index 19fbcb2..b54db5f 100644 --- a/helpers.go +++ b/helpers.go @@ -4,6 +4,7 @@ import ( "errors" "image" "image/color" + "image/draw" "image/gif" "image/jpeg" "image/png" @@ -79,11 +80,17 @@ func Open(filename string) (image.Image, error) { } type encodeConfig struct { - jpegQuality int + jpegQuality int + gifNumColors int + gifQuantizer draw.Quantizer + gifDrawer draw.Drawer } var defaultEncodeConfig = encodeConfig{ - jpegQuality: 95, + jpegQuality: 95, + gifNumColors: 256, + gifQuantizer: nil, + gifDrawer: nil, } // EncodeOption sets an optional parameter for the Encode and Save functions. @@ -97,6 +104,30 @@ func JPEGQuality(quality int) EncodeOption { } } +// 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 + } +} + // 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 @@ -126,7 +157,11 @@ func Encode(w io.Writer, img image.Image, format Format, opts ...EncodeOption) e case PNG: err = png.Encode(w, img) case GIF: - err = gif.Encode(w, img, &gif.Options{NumColors: 256}) + 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: diff --git a/helpers_test.go b/helpers_test.go index 287537e..008b643 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -5,6 +5,8 @@ import ( "errors" "image" "image/color" + "image/color/palette" + "image/draw" "io" "io/ioutil" "os" @@ -39,6 +41,23 @@ func (badFile) Close() error { return errClose } +type quantizer struct { + palette []color.Color +} + +func (q quantizer) Quantize(p color.Palette, m image.Image) color.Palette { + pal := make([]color.Color, len(p), cap(p)) + copy(pal, p) + n := cap(p) - len(p) + if n > len(q.palette) { + n = len(q.palette) + } + for i := 0; i < n; i++ { + pal = append(pal, q.palette[i]) + } + return pal +} + func TestOpenSave(t *testing.T) { imgWithoutAlpha := image.NewNRGBA(image.Rect(0, 0, 4, 6)) imgWithoutAlpha.Pix = []uint8{ @@ -59,8 +78,16 @@ func TestOpenSave(t *testing.T) { 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x88, 0x88, 0x88, 0x00, 0x88, 0x88, 0x88, 0x00, } - options := []EncodeOption{ - JPEGQuality(100), + options := [][]EncodeOption{ + { + JPEGQuality(100), + }, + { + JPEGQuality(99), + GIFDrawer(draw.FloydSteinberg), + GIFNumColors(256), + GIFQuantizer(quantizer{palette.Plan9}), + }, } dir, err := ioutil.TempDir("", "imaging") @@ -77,24 +104,26 @@ func TestOpenSave(t *testing.T) { img = imgWithAlpha } - err := Save(img, filename, options...) - if err != nil { - t.Fatalf("failed to save image (%q): %v", filename, err) - } + for _, opts := range options { + err := Save(img, filename, opts...) + if err != nil { + t.Fatalf("failed to save image (%q): %v", filename, err) + } - img2, err := Open(filename) - if err != nil { - t.Fatalf("failed to open image (%q): %v", filename, err) - } - got := Clone(img2) + img2, err := Open(filename) + if err != nil { + t.Fatalf("failed to open image (%q): %v", filename, err) + } + got := Clone(img2) - delta := 0 - if ext == "jpg" || ext == "jpeg" || ext == "gif" { - delta = 3 - } + delta := 0 + if ext == "jpg" || ext == "jpeg" || ext == "gif" { + delta = 3 + } - if !compareNRGBA(got, img, delta) { - t.Fatalf("bad encode-decode result (ext=%q): got %#v want %#v", ext, got, img) + if !compareNRGBA(got, img, delta) { + t.Fatalf("bad encode-decode result (ext=%q): got %#v want %#v", ext, got, img) + } } }