mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-02-07 11:36:25 +02:00
Ability to keep ICC profile
This commit is contained in:
parent
ca97c715a4
commit
40563f9573
@ -4,6 +4,7 @@
|
||||
### Added
|
||||
- AVIF support.
|
||||
- Azure Blob Storage support.
|
||||
- `IMGPROXY_STRIP_COLOR_PROFILE` config and [strip_color_profile](https://docs.imgproxy.net/#/generating_the_url_advanced?id=strip-color-profile) processing option.
|
||||
- (pro) Remove Adobe Illustrator garbage from SVGs.
|
||||
|
||||
### Changed
|
||||
|
@ -199,6 +199,7 @@ type config struct {
|
||||
Quality int
|
||||
GZipCompression int
|
||||
StripMetadata bool
|
||||
StripColorProfile bool
|
||||
|
||||
EnableWebpDetection bool
|
||||
EnforceWebp bool
|
||||
@ -291,6 +292,7 @@ var conf = config{
|
||||
PngQuantizationColors: 256,
|
||||
Quality: 80,
|
||||
StripMetadata: true,
|
||||
StripColorProfile: true,
|
||||
UserAgent: fmt.Sprintf("imgproxy/%s", version),
|
||||
Presets: make(presets),
|
||||
WatermarkOpacity: 1,
|
||||
@ -349,6 +351,7 @@ func configure() error {
|
||||
intEnvConfig(&conf.Quality, "IMGPROXY_QUALITY")
|
||||
intEnvConfig(&conf.GZipCompression, "IMGPROXY_GZIP_COMPRESSION")
|
||||
boolEnvConfig(&conf.StripMetadata, "IMGPROXY_STRIP_METADATA")
|
||||
boolEnvConfig(&conf.StripColorProfile, "IMGPROXY_STRIP_COLOR_PROFILE")
|
||||
|
||||
boolEnvConfig(&conf.EnableWebpDetection, "IMGPROXY_ENABLE_WEBP_DETECTION")
|
||||
boolEnvConfig(&conf.EnforceWebp, "IMGPROXY_ENFORCE_WEBP")
|
||||
|
@ -322,4 +322,5 @@ imgproxy can send logs to syslog, but this feature is disabled by default. To en
|
||||
* `IMGPROXY_BASE_URL`: base URL prefix that will be added to every requested image URL. For example, if the base URL is `http://example.com/images` and `/path/to/image.png` is requested, imgproxy will download the source image from `http://example.com/images/path/to/image.png`. Default: blank.
|
||||
* `IMGPROXY_USE_LINEAR_COLORSPACE`: when `true`, imgproxy will process images in linear colorspace. This will slow down processing. Note that images won't be fully processed in linear colorspace while shrink-on-load is enabled (see below).
|
||||
* `IMGPROXY_DISABLE_SHRINK_ON_LOAD`: when `true`, disables shrink-on-load for JPEG and WebP. Allows to process the whole image in linear colorspace but dramatically slows down resizing and increases memory usage when working with large images.
|
||||
* `IMGPROXY_STRIP_METADATA`: whether to strip all metadata (EXIF, IPTC, etc.) from JPEG and WebP output images. Default: `true`.
|
||||
* `IMGPROXY_STRIP_METADATA`: when `true`, imgproxy will strip all metadata (EXIF, IPTC, etc.) from JPEG and WebP output images. Default: `true`.
|
||||
* `IMGPROXY_STRIP_COLOR_PROFILE`: when `true`, imgproxy will transform the embedded color profile (ICC) to sRGB and remove it from the image. Otherwise, imgproxy will try to keep it as is. Default: `true`.
|
||||
|
@ -479,6 +479,17 @@ When set to `1`, `t` or `true`, imgproxy will strip the metadata (EXIF, IPTC, et
|
||||
|
||||
Default: `false`
|
||||
|
||||
#### Strip Color Profile
|
||||
|
||||
```
|
||||
strip_color_profile:%strip_color_profile
|
||||
scp:%strip_color_profile
|
||||
```
|
||||
|
||||
When set to `1`, `t` or `true`, imgproxy will transform the embedded color profile (ICC) to sRGB and remove it from the image. Otherwise, imgproxy will try to keep it as is. Normally this is controlled by the [IMGPROXY_STRIP_COLOR_PROFILE](configuration.md#miscellaneous) configuration but this procesing option allows the configuration to be set for each request.
|
||||
|
||||
Default: `false`
|
||||
|
||||
#### Filename
|
||||
|
||||
```
|
||||
|
@ -125,3 +125,9 @@ func (it imageType) ContentDispositionFromURL(imageURL string) string {
|
||||
func (it imageType) SupportsAlpha() bool {
|
||||
return it != imageTypeJPEG && it != imageTypeBMP
|
||||
}
|
||||
|
||||
func (it imageType) SupportsColourProfile() bool {
|
||||
return it == imageTypeJPEG ||
|
||||
it == imageTypeWEBP ||
|
||||
it == imageTypeAVIF
|
||||
}
|
||||
|
40
process.go
40
process.go
@ -436,12 +436,36 @@ func transformImage(ctx context.Context, img *vipsImage, data []byte, po *proces
|
||||
}
|
||||
}
|
||||
|
||||
keepProfile := !po.StripColorProfile && po.Format.SupportsColourProfile()
|
||||
|
||||
if iccImported {
|
||||
if err = img.RgbColourspace(); err != nil {
|
||||
if keepProfile {
|
||||
// We imported ICC profile and want to keep it,
|
||||
// so we need to export it
|
||||
if err = img.ExportColourProfile(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// We imported ICC profile but don't want to keep it,
|
||||
// so we need to export image to sRGB for maximum compatibility
|
||||
if err = img.ExportColourProfileToSRGB(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if !keepProfile {
|
||||
// We don't import ICC profile and don't want to keep it,
|
||||
// so we need to transform it to sRGB for maximum compatibility
|
||||
if err = img.TransformColourProfile(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = img.TransformColourProfile(); err != nil {
|
||||
}
|
||||
|
||||
if err = img.RgbColourspace(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !keepProfile {
|
||||
if err = img.RemoveColourProfile(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -512,6 +536,12 @@ func transformImage(ctx context.Context, img *vipsImage, data []byte, po *proces
|
||||
return err
|
||||
}
|
||||
|
||||
if po.StripMetadata {
|
||||
if err := img.Strip(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return copyMemoryAndCheckTimeout(ctx, img)
|
||||
}
|
||||
|
||||
@ -653,7 +683,7 @@ func saveImageToFitBytes(po *processingOptions, img *vipsImage) ([]byte, context
|
||||
img.CopyMemory()
|
||||
|
||||
for {
|
||||
result, cancel, err := img.Save(po.Format, quality, po.StripMetadata)
|
||||
result, cancel, err := img.Save(po.Format, quality)
|
||||
if len(result) <= po.MaxBytes || quality <= 10 || err != nil {
|
||||
return result, cancel, err
|
||||
}
|
||||
@ -776,5 +806,5 @@ func processImage(ctx context.Context) ([]byte, context.CancelFunc, error) {
|
||||
return saveImageToFitBytes(po, img)
|
||||
}
|
||||
|
||||
return img.Save(po.Format, po.Quality, po.StripMetadata)
|
||||
return img.Save(po.Format, po.Quality)
|
||||
}
|
||||
|
@ -126,24 +126,25 @@ type watermarkOptions struct {
|
||||
}
|
||||
|
||||
type processingOptions struct {
|
||||
ResizingType resizeType
|
||||
Width int
|
||||
Height int
|
||||
Dpr float64
|
||||
Gravity gravityOptions
|
||||
Enlarge bool
|
||||
Extend extendOptions
|
||||
Crop cropOptions
|
||||
Padding paddingOptions
|
||||
Trim trimOptions
|
||||
Format imageType
|
||||
Quality int
|
||||
MaxBytes int
|
||||
Flatten bool
|
||||
Background rgbColor
|
||||
Blur float32
|
||||
Sharpen float32
|
||||
StripMetadata bool
|
||||
ResizingType resizeType
|
||||
Width int
|
||||
Height int
|
||||
Dpr float64
|
||||
Gravity gravityOptions
|
||||
Enlarge bool
|
||||
Extend extendOptions
|
||||
Crop cropOptions
|
||||
Padding paddingOptions
|
||||
Trim trimOptions
|
||||
Format imageType
|
||||
Quality int
|
||||
MaxBytes int
|
||||
Flatten bool
|
||||
Background rgbColor
|
||||
Blur float32
|
||||
Sharpen float32
|
||||
StripMetadata bool
|
||||
StripColorProfile bool
|
||||
|
||||
CacheBuster string
|
||||
|
||||
@ -212,23 +213,24 @@ var (
|
||||
func newProcessingOptions() *processingOptions {
|
||||
newProcessingOptionsOnce.Do(func() {
|
||||
_newProcessingOptions = processingOptions{
|
||||
ResizingType: resizeFit,
|
||||
Width: 0,
|
||||
Height: 0,
|
||||
Gravity: gravityOptions{Type: gravityCenter},
|
||||
Enlarge: false,
|
||||
Extend: extendOptions{Enabled: false, Gravity: gravityOptions{Type: gravityCenter}},
|
||||
Padding: paddingOptions{Enabled: false},
|
||||
Trim: trimOptions{Enabled: false, Threshold: 10, Smart: true},
|
||||
Quality: conf.Quality,
|
||||
MaxBytes: 0,
|
||||
Format: imageTypeUnknown,
|
||||
Background: rgbColor{255, 255, 255},
|
||||
Blur: 0,
|
||||
Sharpen: 0,
|
||||
Dpr: 1,
|
||||
Watermark: watermarkOptions{Opacity: 1, Replicate: false, Gravity: gravityOptions{Type: gravityCenter}},
|
||||
StripMetadata: conf.StripMetadata,
|
||||
ResizingType: resizeFit,
|
||||
Width: 0,
|
||||
Height: 0,
|
||||
Gravity: gravityOptions{Type: gravityCenter},
|
||||
Enlarge: false,
|
||||
Extend: extendOptions{Enabled: false, Gravity: gravityOptions{Type: gravityCenter}},
|
||||
Padding: paddingOptions{Enabled: false},
|
||||
Trim: trimOptions{Enabled: false, Threshold: 10, Smart: true},
|
||||
Quality: conf.Quality,
|
||||
MaxBytes: 0,
|
||||
Format: imageTypeUnknown,
|
||||
Background: rgbColor{255, 255, 255},
|
||||
Blur: 0,
|
||||
Sharpen: 0,
|
||||
Dpr: 1,
|
||||
Watermark: watermarkOptions{Opacity: 1, Replicate: false, Gravity: gravityOptions{Type: gravityCenter}},
|
||||
StripMetadata: conf.StripMetadata,
|
||||
StripColorProfile: conf.StripColorProfile,
|
||||
}
|
||||
})
|
||||
|
||||
@ -860,6 +862,16 @@ func applyStripMetadataOption(po *processingOptions, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyStripColorProfileOption(po *processingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid strip color profile arguments: %v", args)
|
||||
}
|
||||
|
||||
po.StripColorProfile = parseBoolOption(args[0])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyProcessingOption(po *processingOptions, name string, args []string) error {
|
||||
switch name {
|
||||
case "format", "f", "ext":
|
||||
@ -906,6 +918,8 @@ func applyProcessingOption(po *processingOptions, name string, args []string) er
|
||||
return applyCacheBusterOption(po, args)
|
||||
case "strip_metadata", "sm":
|
||||
return applyStripMetadataOption(po, args)
|
||||
case "strip_color_profile", "scp":
|
||||
return applyStripColorProfileOption(po, args)
|
||||
case "filename", "fn":
|
||||
return applyFilenameOption(po, args)
|
||||
}
|
||||
|
59
vips.c
59
vips.c
@ -345,18 +345,27 @@ vips_has_embedded_icc(VipsImage *in) {
|
||||
|
||||
int
|
||||
vips_icc_import_go(VipsImage *in, VipsImage **out) {
|
||||
if (vips_icc_import(in, out, "embedded", TRUE, "pcs", VIPS_PCS_XYZ, NULL))
|
||||
return 1;
|
||||
return vips_icc_import(in, out, "embedded", TRUE, "pcs", VIPS_PCS_XYZ, NULL);
|
||||
}
|
||||
|
||||
vips_image_remove(*out, VIPS_META_ICC_NAME);
|
||||
int
|
||||
vips_icc_export_go(VipsImage *in, VipsImage **out) {
|
||||
return vips_icc_export(in, out, NULL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
int
|
||||
vips_icc_export_srgb(VipsImage *in, VipsImage **out) {
|
||||
return vips_icc_export(in, out, "output_profile", "sRGB", NULL);
|
||||
}
|
||||
|
||||
int
|
||||
vips_icc_transform_go(VipsImage *in, VipsImage **out) {
|
||||
if (vips_icc_transform(in, out, "sRGB", "embedded", TRUE, "pcs", VIPS_PCS_XYZ, NULL))
|
||||
return 1;
|
||||
return vips_icc_transform(in, out, "sRGB", "embedded", TRUE, "pcs", VIPS_PCS_XYZ, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
vips_icc_remove(VipsImage *in, VipsImage **out) {
|
||||
if (vips_copy(in, out, NULL)) return 1;
|
||||
|
||||
vips_image_remove(*out, VIPS_META_ICC_NAME);
|
||||
|
||||
@ -568,15 +577,39 @@ vips_arrayjoin_go(VipsImage **in, VipsImage **out, int n) {
|
||||
}
|
||||
|
||||
int
|
||||
vips_jpegsave_go(VipsImage *in, void **buf, size_t *len, int quality, int interlace, gboolean strip) {
|
||||
return vips_jpegsave_buffer(in, buf, len, "profile", "none", "Q", quality, "strip", strip, "optimize_coding", TRUE, "interlace", interlace, NULL);
|
||||
vips_strip(VipsImage *in, VipsImage **out) {
|
||||
if (vips_copy(in, out, NULL)) return 1;
|
||||
|
||||
gchar **fields = vips_image_get_fields(in);
|
||||
|
||||
for (int i = 0; fields[i] != NULL; i++) {
|
||||
gchar *name = fields[i];
|
||||
|
||||
if (strcmp(name, VIPS_META_ICC_NAME) == 0) continue;
|
||||
|
||||
vips_image_remove(*out, name);
|
||||
}
|
||||
|
||||
g_strfreev(fields);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
vips_jpegsave_go(VipsImage *in, void **buf, size_t *len, int quality, int interlace) {
|
||||
return vips_jpegsave_buffer(
|
||||
in, buf, len,
|
||||
"Q", quality,
|
||||
"optimize_coding", TRUE,
|
||||
"interlace", interlace,
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
int
|
||||
vips_pngsave_go(VipsImage *in, void **buf, size_t *len, int interlace, int quantize, int colors) {
|
||||
return vips_pngsave_buffer(
|
||||
in, buf, len,
|
||||
"profile", "none",
|
||||
"filter", VIPS_FOREIGN_PNG_FILTER_NONE,
|
||||
"interlace", interlace,
|
||||
#if VIPS_SUPPORT_PNG_QUANTIZATION
|
||||
@ -587,8 +620,12 @@ vips_pngsave_go(VipsImage *in, void **buf, size_t *len, int interlace, int quant
|
||||
}
|
||||
|
||||
int
|
||||
vips_webpsave_go(VipsImage *in, void **buf, size_t *len, int quality, gboolean strip) {
|
||||
return vips_webpsave_buffer(in, buf, len, "Q", quality, "strip", strip, NULL);
|
||||
vips_webpsave_go(VipsImage *in, void **buf, size_t *len, int quality) {
|
||||
return vips_webpsave_buffer(
|
||||
in, buf, len,
|
||||
"Q", quality,
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
int
|
||||
|
63
vips.go
63
vips.go
@ -180,7 +180,7 @@ func (img *vipsImage) Load(data []byte, imgtype imageType, shrink int, scale flo
|
||||
return nil
|
||||
}
|
||||
|
||||
func (img *vipsImage) Save(imgtype imageType, quality int, stripMeta bool) ([]byte, context.CancelFunc, error) {
|
||||
func (img *vipsImage) Save(imgtype imageType, quality int) ([]byte, context.CancelFunc, error) {
|
||||
if imgtype == imageTypeICO {
|
||||
b, err := img.SaveAsIco()
|
||||
return b, func() {}, err
|
||||
@ -198,11 +198,11 @@ func (img *vipsImage) Save(imgtype imageType, quality int, stripMeta bool) ([]by
|
||||
|
||||
switch imgtype {
|
||||
case imageTypeJPEG:
|
||||
err = C.vips_jpegsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality), vipsConf.JpegProgressive, gbool(stripMeta))
|
||||
err = C.vips_jpegsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality), vipsConf.JpegProgressive)
|
||||
case imageTypePNG:
|
||||
err = C.vips_pngsave_go(img.VipsImage, &ptr, &imgsize, vipsConf.PngInterlaced, vipsConf.PngQuantize, vipsConf.PngQuantizationColors)
|
||||
case imageTypeWEBP:
|
||||
err = C.vips_webpsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality), gbool(stripMeta))
|
||||
err = C.vips_webpsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality))
|
||||
case imageTypeGIF:
|
||||
err = C.vips_gifsave_go(img.VipsImage, &ptr, &imgsize)
|
||||
case imageTypeAVIF:
|
||||
@ -532,6 +532,40 @@ func (img *vipsImage) ImportColourProfile() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (img *vipsImage) ExportColourProfile() error {
|
||||
var tmp *C.VipsImage
|
||||
|
||||
// Don't export is there's no embedded profile or embedded profile is sRGB
|
||||
if C.vips_has_embedded_icc(img.VipsImage) == 0 || C.vips_icc_is_srgb_iec61966(img.VipsImage) == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if C.vips_icc_export_go(img.VipsImage, &tmp) == 0 {
|
||||
C.swap_and_clear(&img.VipsImage, tmp)
|
||||
} else {
|
||||
logWarning("Can't export ICC profile: %s", vipsError())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (img *vipsImage) ExportColourProfileToSRGB() error {
|
||||
var tmp *C.VipsImage
|
||||
|
||||
// Don't export is there's no embedded profile or embedded profile is sRGB
|
||||
if C.vips_has_embedded_icc(img.VipsImage) == 0 || C.vips_icc_is_srgb_iec61966(img.VipsImage) == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if C.vips_icc_export_srgb(img.VipsImage, &tmp) == 0 {
|
||||
C.swap_and_clear(&img.VipsImage, tmp)
|
||||
} else {
|
||||
logWarning("Can't export ICC profile: %s", vipsError())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (img *vipsImage) TransformColourProfile() error {
|
||||
var tmp *C.VipsImage
|
||||
|
||||
@ -549,6 +583,18 @@ func (img *vipsImage) TransformColourProfile() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (img *vipsImage) RemoveColourProfile() error {
|
||||
var tmp *C.VipsImage
|
||||
|
||||
if C.vips_icc_remove(img.VipsImage, &tmp) == 0 {
|
||||
C.swap_and_clear(&img.VipsImage, tmp)
|
||||
} else {
|
||||
logWarning("Can't remove ICC profile: %s", vipsError())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (img *vipsImage) IsSRGB() bool {
|
||||
return img.VipsImage.Type == C.VIPS_INTERPRETATION_sRGB
|
||||
}
|
||||
@ -635,3 +681,14 @@ func (img *vipsImage) ApplyWatermark(wm *vipsImage, opacity float64) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (img *vipsImage) Strip() error {
|
||||
var tmp *C.VipsImage
|
||||
|
||||
if C.vips_strip(img.VipsImage, &tmp) != 0 {
|
||||
return vipsError()
|
||||
}
|
||||
C.swap_and_clear(&img.VipsImage, tmp)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
9
vips.h
9
vips.h
@ -61,7 +61,10 @@ int vips_resize_with_premultiply(VipsImage *in, VipsImage **out, double scale);
|
||||
int vips_icc_is_srgb_iec61966(VipsImage *in);
|
||||
int vips_has_embedded_icc(VipsImage *in);
|
||||
int vips_icc_import_go(VipsImage *in, VipsImage **out);
|
||||
int vips_icc_export_go(VipsImage *in, VipsImage **out);
|
||||
int vips_icc_export_srgb(VipsImage *in, VipsImage **out);
|
||||
int vips_icc_transform_go(VipsImage *in, VipsImage **out);
|
||||
int vips_icc_remove(VipsImage *in, VipsImage **out);
|
||||
int vips_colourspace_go(VipsImage *in, VipsImage **out, VipsInterpretation cs);
|
||||
|
||||
int vips_rot_go(VipsImage *in, VipsImage **out, VipsAngle angle);
|
||||
@ -87,9 +90,11 @@ int vips_apply_watermark(VipsImage *in, VipsImage *watermark, VipsImage **out, d
|
||||
|
||||
int vips_arrayjoin_go(VipsImage **in, VipsImage **out, int n);
|
||||
|
||||
int vips_jpegsave_go(VipsImage *in, void **buf, size_t *len, int quality, int interlace, gboolean strip);
|
||||
int vips_strip(VipsImage *in, VipsImage **out);
|
||||
|
||||
int vips_jpegsave_go(VipsImage *in, void **buf, size_t *len, int quality, int interlace);
|
||||
int vips_pngsave_go(VipsImage *in, void **buf, size_t *len, int interlace, int quantize, int colors);
|
||||
int vips_webpsave_go(VipsImage *in, void **buf, size_t *len, int quality, gboolean strip);
|
||||
int vips_webpsave_go(VipsImage *in, void **buf, size_t *len, int quality);
|
||||
int vips_gifsave_go(VipsImage *in, void **buf, size_t *len);
|
||||
int vips_avifsave_go(VipsImage *in, void **buf, size_t *len, int quality);
|
||||
int vips_bmpsave_go(VipsImage *in, void **buf, size_t *len);
|
||||
|
Loading…
x
Reference in New Issue
Block a user