//+build cgo package thumbnail import ( "bytes" "fmt" "image" "image/color" "image/png" "code.ivysaur.me/imagequant/v2" ) func goImageToRgba32(im image.Image) []byte { w := im.Bounds().Max.X h := im.Bounds().Max.Y ret := make([]byte, w*h*4) p := 0 for y := 0; y < h; y += 1 { for x := 0; x < w; x += 1 { r16, g16, b16, a16 := im.At(x, y).RGBA() // Each value ranges within [0, 0xffff] ret[p+0] = uint8(r16 >> 8) ret[p+1] = uint8(g16 >> 8) ret[p+2] = uint8(b16 >> 8) ret[p+3] = uint8(a16 >> 8) p += 4 } } return ret } func rgb8PaletteToGoImage(w, h int, rgb8data []byte, pal color.Palette) image.Image { rect := image.Rectangle{ Max: image.Point{ X: w, Y: h, }, } ret := image.NewPaletted(rect, pal) for y := 0; y < h; y += 1 { for x := 0; x < w; x += 1 { ret.SetColorIndex(x, y, rgb8data[y*h+x]) } } return ret } func crushFast(img image.Image) ([]byte, error) { return crush(img, imagequant.SPEED_FASTEST) } func crush(img image.Image, speed int) ([]byte, error) { width := img.Bounds().Max.X height := img.Bounds().Max.Y attr, err := imagequant.NewAttributes() if err != nil { return nil, fmt.Errorf("NewAttributes: %s", err.Error()) } defer attr.Release() err = attr.SetSpeed(speed) if err != nil { return nil, fmt.Errorf("SetSpeed: %s", err.Error()) } rgba32data := goImageToRgba32(img) iqm, err := imagequant.NewImage(attr, string(rgba32data), width, height, 0) if err != nil { return nil, fmt.Errorf("NewImage: %s", err.Error()) } defer iqm.Release() res, err := iqm.Quantize(attr) if err != nil { return nil, fmt.Errorf("Quantize: %s", err.Error()) } defer res.Release() rgb8data, err := res.WriteRemappedImage() if err != nil { return nil, fmt.Errorf("WriteRemappedImage: %s", err.Error()) } im2 := rgb8PaletteToGoImage(res.GetImageWidth(), res.GetImageHeight(), rgb8data, res.GetPalette()) buff := bytes.Buffer{} penc := png.Encoder{ CompressionLevel: png.BestCompression, } err = penc.Encode(&buff, im2) if err != nil { return nil, fmt.Errorf("png.Encode: %s", err.Error()) } return buff.Bytes(), nil }