Skip to content

Add Magick Save, Support ICO LOAD #470

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

Merged
merged 1 commit into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
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
12 changes: 11 additions & 1 deletion vips/foreign.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand All @@ -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);
}
Expand Down
27 changes: 27 additions & 0 deletions vips/foreign.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "C"
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"math"
"runtime"
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions vips/foreign.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
31 changes: 31 additions & 0 deletions vips/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand Down
33 changes: 33 additions & 0 deletions vips/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading