diff --git a/CHANGELOG.md b/CHANGELOG.md index 8be0b2d5..bd888e6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] +### Fix +- Fix ICO saving. ## [2.14.0] - 2020-07-17 ### Added diff --git a/utils.go b/utils.go index 2b794f4d..fc83ab86 100644 --- a/utils.go +++ b/utils.go @@ -3,6 +3,7 @@ package main import ( "math" "strings" + "unsafe" ) func maxInt(a, b int) int { @@ -49,3 +50,7 @@ func trimAfter(s string, sep byte) string { } return s[:i] } + +func ptrToBytes(ptr unsafe.Pointer, size int) []byte { + return (*[math.MaxInt32]byte)(ptr)[:int(size):int(size)] +} diff --git a/vips.c b/vips.c index 67b6c538..2879a5d8 100644 --- a/vips.c +++ b/vips.c @@ -106,7 +106,7 @@ vips_type_find_save_go(int imgtype) { case (GIF): return vips_type_find("VipsOperation", "magicksave_buffer"); case (ICO): - return vips_type_find("VipsOperation", "magicksave_buffer"); + return vips_type_find("VipsOperation", "pngsave_buffer"); case (BMP): return vips_type_find("VipsOperation", "magicksave_buffer"); case (TIFF): @@ -585,16 +585,6 @@ vips_gifsave_go(VipsImage *in, void **buf, size_t *len) { #endif } -int -vips_icosave_go(VipsImage *in, void **buf, size_t *len) { -#if VIPS_SUPPORT_MAGICK - return vips_magicksave_buffer(in, buf, len, "format", "ico", NULL); -#else - vips_error("vips_icosave_go", "Saving ICO is not supported (libvips 8.7+ reuired)"); - return 1; -#endif -} - int vips_tiffsave_go(VipsImage *in, void **buf, size_t *len, int quality) { #if VIPS_SUPPORT_TIFF @@ -608,7 +598,7 @@ vips_tiffsave_go(VipsImage *in, void **buf, size_t *len, int quality) { int vips_bmpsave_go(VipsImage *in, void **buf, size_t *len) { #if VIPS_SUPPORT_MAGICK - return vips_magicksave_buffer(in, buf, len, "format", "bmp", "quality", NULL); + return vips_magicksave_buffer(in, buf, len, "format", "bmp", NULL); #else vips_error("vips_bmpsave_go", "Saving BMP is not supported"); return 1; diff --git a/vips.go b/vips.go index 93a25bd8..0a80039d 100644 --- a/vips.go +++ b/vips.go @@ -8,9 +8,11 @@ package main */ import "C" import ( + "bytes" "context" + "encoding/binary" + "errors" "fmt" - "math" "os" "runtime" "unsafe" @@ -179,6 +181,11 @@ func (img *vipsImage) Load(data []byte, imgtype imageType, shrink int, scale flo } func (img *vipsImage) Save(imgtype imageType, quality int, stripMeta bool) ([]byte, context.CancelFunc, error) { + if imgtype == imageTypeICO { + b, err := img.SaveAsIco() + return b, func() {}, err + } + var ptr unsafe.Pointer cancel := func() { @@ -198,8 +205,6 @@ func (img *vipsImage) Save(imgtype imageType, quality int, stripMeta bool) ([]by err = C.vips_webpsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality), gbool(stripMeta)) case imageTypeGIF: err = C.vips_gifsave_go(img.VipsImage, &ptr, &imgsize) - case imageTypeICO: - err = C.vips_icosave_go(img.VipsImage, &ptr, &imgsize) case imageTypeBMP: err = C.vips_bmpsave_go(img.VipsImage, &ptr, &imgsize) case imageTypeTIFF: @@ -210,11 +215,82 @@ func (img *vipsImage) Save(imgtype imageType, quality int, stripMeta bool) ([]by return nil, cancel, vipsError() } - b := (*[math.MaxInt32]byte)(ptr)[:int(imgsize):int(imgsize)] + b := ptrToBytes(ptr, int(imgsize)) return b, cancel, nil } +func (img *vipsImage) SaveAsIco() ([]byte, error) { + if img.Width() > 256 || img.Height() > 256 { + return nil, errors.New("Image dimensions is too big. Max dimension size for ICO is 256") + } + + var ptr unsafe.Pointer + imgsize := C.size_t(0) + + defer func() { + C.g_free_go(&ptr) + }() + + if C.vips_pngsave_go(img.VipsImage, &ptr, &imgsize, 0, 0, 256) != 0 { + return nil, vipsError() + } + + b := ptrToBytes(ptr, int(imgsize)) + + buf := new(bytes.Buffer) + buf.Grow(22 + int(imgsize)) + + // ICONDIR header + if _, err := buf.Write([]byte{0, 0, 1, 0, 1, 0}); err != nil { + return nil, err + } + + // ICONDIRENTRY + if _, err := buf.Write([]byte{ + byte(img.Width() % 256), + byte(img.Height() % 256), + }); err != nil { + return nil, err + } + // Number of colors. Not supported in our case + if err := buf.WriteByte(0); err != nil { + return nil, err + } + // Reserved + if err := buf.WriteByte(0); err != nil { + return nil, err + } + // Color planes. Always 1 in our case + if _, err := buf.Write([]byte{1, 0}); err != nil { + return nil, err + } + // Bits per pixel + if img.HasAlpha() { + if _, err := buf.Write([]byte{32, 0}); err != nil { + return nil, err + } + } else { + if _, err := buf.Write([]byte{24, 0}); err != nil { + return nil, err + } + } + // Image data size + if err := binary.Write(buf, binary.LittleEndian, uint32(imgsize)); err != nil { + return nil, err + } + // Image data offset. Always 22 in our case + if _, err := buf.Write([]byte{22, 0, 0, 0}); err != nil { + return nil, err + } + + if _, err := buf.Write(b); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + func (img *vipsImage) Clear() { if img.VipsImage != nil { C.clear_image(&img.VipsImage)