Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Magick Save, Support ICO LOAD #470

Merged
merged 1 commit into from
Mar 14, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Add Magick Save, Support ICO LOAD
steve.3282 committed Mar 13, 2025
commit 887e56bc076d0d30990cc6538e91ab58c7036794
12 changes: 11 additions & 1 deletion vips/foreign.c
Original file line number Diff line number Diff line change
@@ -329,7 +329,10 @@ int set_tiffsave_options(VipsOperation *operation, SaveParams *params) {

// https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-magicksave-buffer
int set_magicksave_options(VipsOperation *operation, SaveParams *params) {
int ret = vips_object_set(VIPS_OBJECT(operation), "format", "GIF", "bitdepth", params->gifBitdepth, NULL);
int ret = vips_object_set(VIPS_OBJECT(operation), "format", params->magickFormat,
"optimize_gif_frames", params->magickOptimizeGifFrames,
"optimize_gif_transparency", params->magickOptimizeGifTransparency,
"bitdepth", params->magickBitDepth, NULL);

if (!ret && params->quality) {
ret = vips_object_set(VIPS_OBJECT(operation), "quality", params->quality,
@@ -491,6 +494,11 @@ int save_to_buffer(SaveParams *params) {
#if (VIPS_MAJOR_VERSION >= 8) && (VIPS_MINOR_VERSION >= 12)
return save_buffer("gifsave_buffer", params, set_gifsave_options);
#else
// Gif save through ImageMagick below Vips Version 8.12
params->magickFormat="GIF";
params->magickOptimizeGifFrames=FALSE;
params->magickOptimizeGifTransparency=FALSE;
params->magickBitDepth=params->gifBitdepth;
return save_buffer("magicksave_buffer", params, set_magicksave_options);
#endif
case AVIF:
@@ -499,6 +507,8 @@ int save_to_buffer(SaveParams *params) {
return save_buffer("jp2ksave_buffer", params, set_jp2ksave_options);
case JXL:
return save_buffer("jxlsave_buffer", params, set_jxlsave_options);
case MAGICK:
return save_buffer("magicksave_buffer", params, set_magicksave_options);
default:
g_warning("Unsupported output type given: %d", params->outputFormat);
}
27 changes: 27 additions & 0 deletions vips/foreign.go
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import "C"
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"math"
"runtime"
@@ -174,6 +175,8 @@ func DetermineImageType(buf []byte) ImageType {
return ImageTypeJXL
} else if isPDF(buf) {
return ImageTypePDF
} else if isICO(buf) {
return ImageTypeMagick
} else {
return ImageTypeUnknown
}
@@ -282,6 +285,12 @@ func isJXL(buf []byte) bool {
return bytes.HasPrefix(buf, jxlHeader) || bytes.HasPrefix(buf, jxlHeaderISOBMFF)
}

var icoHeader = []byte("\x00\x00\x01\x00")

func isICO(buf []byte) bool {
return bytes.HasPrefix(buf, icoHeader)
}

func vipsLoadFromBuffer(buf []byte, params *ImportParams) (*C.VipsImage, ImageType, ImageType, error) {
src := buf
// Reference src here so it's not garbage collected during image initialization.
@@ -490,6 +499,24 @@ func vipsSaveJxlToBuffer(in *C.VipsImage, params JxlExportParams) ([]byte, error
return vipsSaveToBuffer(p)
}

func vipsSaveMagickToBuffer(in *C.VipsImage, params MagickExportParams) ([]byte, error) {
incOpCounter("save_magick_buffer")

if params.Format == "" {
return nil, errors.New("magick format required")
}
p := C.create_save_params(C.MAGICK)
p.inputImage = in
p.outputFormat = C.MAGICK
p.quality = C.int(params.Quality)
p.magickFormat = C.CString(params.Format)
p.magickOptimizeGifFrames = C.int(boolToInt(params.OptimizeGifFrames))
p.magickOptimizeGifTransparency = C.int(boolToInt(params.OptimizeGifTransparency))
p.magickBitDepth = C.int(params.BitDepth)

return vipsSaveToBuffer(p)
}

func vipsSaveToBuffer(params C.struct_SaveParams) ([]byte, error) {
if err := C.save_to_buffer(&params); err != 0 {
return nil, handleSaveBufferError(params.outputBuffer)
6 changes: 6 additions & 0 deletions vips/foreign.h
Original file line number Diff line number Diff line change
@@ -135,6 +135,12 @@ typedef struct SaveParams {
double jxlDistance;
int jxlEffort;
BOOL jxlLossless;

// MAGICK
char *magickFormat;
BOOL magickOptimizeGifFrames;
BOOL magickOptimizeGifTransparency;
int magickBitDepth;
} SaveParams;

SaveParams create_save_params(ImageType outputFormat);
31 changes: 31 additions & 0 deletions vips/image.go
Original file line number Diff line number Diff line change
@@ -428,6 +428,22 @@ func NewJxlExportParams() *JxlExportParams {
}
}

// MagickExportParams are options when exporting an image to file or buffer by ImageMagick.
type MagickExportParams struct {
Quality int
Format string
OptimizeGifFrames bool
OptimizeGifTransparency bool
BitDepth int
}

// NewMagickExportParams creates default values for an export of an image by ImageMagick.
func NewMagickExportParams() *MagickExportParams {
return &MagickExportParams{
Quality: 75,
}
}

// NewImageFromReader loads an ImageRef from the given reader
func NewImageFromReader(r io.Reader) (*ImageRef, error) {
buf, err := io.ReadAll(r)
@@ -1079,6 +1095,21 @@ func (r *ImageRef) ExportJxl(params *JxlExportParams) ([]byte, *ImageMetadata, e
return buf, r.newMetadata(ImageTypeJXL), nil
}

// ExportMagick exports the image as Format set in param to a buffer.
func (r *ImageRef) ExportMagick(params *MagickExportParams) ([]byte, *ImageMetadata, error) {
if params == nil {
params = NewMagickExportParams()
params.Format = "JPG"
}

buf, err := vipsSaveMagickToBuffer(r.image, *params)
if err != nil {
return nil, nil, err
}

return buf, r.newMetadata(ImageTypeMagick), nil
}

// CompositeMulti composites the given overlay image on top of the associated image with provided blending mode.
func (r *ImageRef) CompositeMulti(ins []*ImageComposite) error {
out, err := vipsComposite(toVipsCompositeStructs(r, ins))
33 changes: 33 additions & 0 deletions vips/image_test.go
Original file line number Diff line number Diff line change
@@ -1278,6 +1278,39 @@ func Test_LoadImageWithAccessMode(t *testing.T) {
assert.NotNil(t, gifImg)
}

func Test_SaveImageWithMagick(t *testing.T) {
Startup(nil)

// GIF Save With Magick
param := NewImportParams()
gifImage, err := LoadImageFromFile(resources+"gif-animated.gif", param)
require.NoError(t, err)
require.NotNil(t, gifImage)

exportParam := NewMagickExportParams()
exportParam.Format = "GIF"
exportParam.BitDepth = 8

gifBuf, _, err := gifImage.ExportMagick(exportParam)
require.NoError(t, err)
require.NotNil(t, gifBuf)
require.True(t, isGIF(gifBuf))

// BMP Save With Magick
param = NewImportParams()
bmpImage, err := LoadImageFromFile(resources+"koala.bmp", param)
require.NoError(t, err)
require.NotNil(t, bmpImage)

exportParam = NewMagickExportParams()
exportParam.Format = "BMP"

bmpBuf, _, err := bmpImage.ExportMagick(exportParam)
require.NoError(t, err)
require.NotNil(t, bmpBuf)
require.True(t, isBMP(bmpBuf))
}

// TODO unit tests to cover:
// NewImageFromReader failing test
// NewImageFromFile failing test