Bladeren bron

Save ICO without ImageMagick

DarthSim 5 jaren geleden
bovenliggende
commit
494af926df
4 gewijzigde bestanden met toevoegingen van 89 en 16 verwijderingen
  1. 2 0
      CHANGELOG.md
  2. 5 0
      utils.go
  3. 2 12
      vips.c
  4. 80 4
      vips.go

+ 2 - 0
CHANGELOG.md

@@ -1,6 +1,8 @@
 # Changelog
 
 ## [Unreleased]
+### Fix
+- Fix ICO saving.
 
 ## [2.14.0] - 2020-07-17
 ### Added

+ 5 - 0
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)]
+}

+ 2 - 12
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;

+ 80 - 4
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)