Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
The MIT License
Copyright (c) Simple Things LLC and contributors
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
+39
View File
@@ -0,0 +1,39 @@
package vips
// #include <vips/vips.h>
import "C"
import (
"errors"
"fmt"
dbg "runtime/debug"
"unsafe"
)
var (
// ErrUnsupportedImageFormat when image type is unsupported
ErrUnsupportedImageFormat = errors.New("unsupported image format")
)
func handleImageError(out *C.VipsImage) error {
if out != nil {
clearImage(out)
}
return handleVipsError()
}
func handleSaveBufferError(out unsafe.Pointer) error {
if out != nil {
gFreePointer(out)
}
return handleVipsError()
}
func handleVipsError() error {
s := C.GoString(C.vips_error_buffer())
C.vips_error_clear()
return fmt.Errorf("%v\nStack:\n%s", s, dbg.Stack())
}
+601
View File
@@ -0,0 +1,601 @@
#include "foreign.h"
#include "lang.h"
void set_bool_param(Param *p, gboolean b) {
p->type = PARAM_TYPE_BOOL;
p->value.b = b;
p->is_set = TRUE;
}
void set_int_param(Param *p, gint i) {
p->type = PARAM_TYPE_INT;
p->value.i = i;
p->is_set = TRUE;
}
void set_double_param(Param *p, gdouble d) {
p->type = PARAM_TYPE_DOUBLE;
p->value.d = d;
p->is_set = TRUE;
}
int load_image_buffer(LoadParams *params, void *buf, size_t len,
VipsImage **out) {
int code = 1;
ImageType imageType = params->inputFormat;
if (imageType == JPEG) {
// shrink: int, fail: bool, autorotate: bool
code = vips_jpegload_buffer(buf, len, out, "fail", params->fail,
"autorotate", params->autorotate, "shrink",
params->jpegShrink, "access", params->access, NULL);
} else if (imageType == PNG) {
code = vips_pngload_buffer(buf, len, out, "access", params->access, NULL);
} else if (imageType == WEBP) {
// page: int, n: int, scale: double
code = vips_webpload_buffer(buf, len, out, "page", params->page, "n",
params->n, "access", params->access, NULL);
} else if (imageType == TIFF) {
// page: int, n: int, autorotate: bool, subifd: int
code =
vips_tiffload_buffer(buf, len, out, "page", params->page, "n",
params->n, "autorotate", params->autorotate, "access", params->access, NULL);
} else if (imageType == GIF) {
// page: int, n: int, scale: double
code = vips_gifload_buffer(buf, len, out, "page", params->page, "n",
params->n, "access", params->access, NULL);
} else if (imageType == PDF) {
// page: int, n: int, dpi: double, scale: double, background: color
code = vips_pdfload_buffer(buf, len, out, "page", params->page, "n",
params->n, "dpi", params->dpi, "access", params->access, NULL);
} else if (imageType == SVG) {
// dpi: double, scale: double, unlimited: bool
code = vips_svgload_buffer(buf, len, out, "dpi", params->dpi, "unlimited",
params->svgUnlimited, "access", params->access, NULL);
} else if (imageType == HEIF) {
// added autorotate on load as currently it addresses orientation issues
// https://github.com/libvips/libvips/pull/1680
// page: int, n: int, thumbnail: bool
code = vips_heifload_buffer(buf, len, out, "page", params->page, "n",
params->n, "thumbnail", params->heifThumbnail,
"autorotate", TRUE, "access", params->access, NULL);
} else if (imageType == MAGICK) {
// page: int, n: int, density: string
code = vips_magickload_buffer(buf, len, out, "page", params->page, "n",
params->n, "access", params->access, NULL);
} else if (imageType == AVIF) {
code = vips_heifload_buffer(buf, len, out, "page", params->page, "n",
params->n, "thumbnail", params->heifThumbnail,
"autorotate", params->autorotate, "access", params->access, NULL);
}
#if (VIPS_MAJOR_VERSION >= 8) && (VIPS_MINOR_VERSION >= 11)
else if (imageType == JP2K) {
code = vips_jp2kload_buffer(buf, len, out, "page", params->page, "access", params->access, NULL);
}
#endif
return code;
}
#define MAYBE_SET_BOOL(OP, PARAM, NAME) \
if (PARAM.is_set) { \
vips_object_set(VIPS_OBJECT(OP), NAME, PARAM.value.b, NULL); \
}
#define MAYBE_SET_INT(OP, PARAM, NAME) \
if (PARAM.is_set) { \
vips_object_set(VIPS_OBJECT(OP), NAME, PARAM.value.i, NULL); \
}
#define MAYBE_SET_DOUBLE(OP, PARAM, NAME) \
if (PARAM.is_set) { \
vips_object_set(VIPS_OBJECT(OP), NAME, PARAM.value.d, NULL); \
}
typedef int (*SetLoadOptionsFn)(VipsOperation *operation, LoadParams *params);
int set_jpegload_options(VipsOperation *operation, LoadParams *params) {
MAYBE_SET_BOOL(operation, params->autorotate, "autorotate");
MAYBE_SET_BOOL(operation, params->fail, "fail");
MAYBE_SET_INT(operation, params->jpegShrink, "shrink");
MAYBE_SET_INT(operation, params->access, "access");
return 0;
}
int set_pngload_options(VipsOperation *operation, LoadParams *params) {
MAYBE_SET_BOOL(operation, params->fail, "fail");
MAYBE_SET_INT(operation, params->access, "access");
return 0;
}
int set_webpload_options(VipsOperation *operation, LoadParams *params) {
MAYBE_SET_INT(operation, params->page, "page");
MAYBE_SET_INT(operation, params->n, "n");
MAYBE_SET_INT(operation, params->access, "access");
MAYBE_SET_DOUBLE(operation, params->webpScale, "scale");
return 0;
}
int set_tiffload_options(VipsOperation *operation, LoadParams *params) {
MAYBE_SET_BOOL(operation, params->autorotate, "autorotate");
MAYBE_SET_INT(operation, params->page, "page");
MAYBE_SET_INT(operation, params->n, "n");
MAYBE_SET_INT(operation, params->access, "access");
return 0;
}
int set_gifload_options(VipsOperation *operation, LoadParams *params) {
MAYBE_SET_INT(operation, params->page, "page");
MAYBE_SET_INT(operation, params->n, "n");
MAYBE_SET_INT(operation, params->access, "access");
return 0;
}
int set_pdfload_options(VipsOperation *operation, LoadParams *params) {
MAYBE_SET_INT(operation, params->page, "page");
MAYBE_SET_INT(operation, params->n, "n");
MAYBE_SET_DOUBLE(operation, params->dpi, "dpi");
MAYBE_SET_INT(operation, params->access, "access");
return 0;
}
int set_svgload_options(VipsOperation *operation, LoadParams *params) {
MAYBE_SET_BOOL(operation, params->svgUnlimited, "unlimited");
MAYBE_SET_DOUBLE(operation, params->dpi, "dpi");
MAYBE_SET_INT(operation, params->access, "access");
return 0;
}
int set_heifload_options(VipsOperation *operation, LoadParams *params) {
MAYBE_SET_BOOL(operation, params->autorotate, "autorotate");
MAYBE_SET_BOOL(operation, params->heifThumbnail, "thumbnail");
MAYBE_SET_INT(operation, params->page, "page");
MAYBE_SET_INT(operation, params->n, "n");
MAYBE_SET_INT(operation, params->access, "access");
return 0;
}
int set_jp2kload_options(VipsOperation *operation, LoadParams *params) {
MAYBE_SET_INT(operation, params->page, "page");
MAYBE_SET_INT(operation, params->access, "access");
return 0;
}
int set_jxlload_options(VipsOperation *operation, LoadParams *params) {
// nothing need to do
return 0;
}
int set_magickload_options(VipsOperation *operation, LoadParams *params) {
MAYBE_SET_INT(operation, params->page, "page");
MAYBE_SET_INT(operation, params->n, "n");
MAYBE_SET_INT(operation, params->access, "access");
return 0;
}
int load_buffer(const char *operationName, void *buf, size_t len,
LoadParams *params, SetLoadOptionsFn setLoadOptions) {
VipsBlob *blob = vips_blob_new(NULL, buf, len);
VipsOperation *operation = vips_operation_new(operationName);
if (!operation) {
return 1;
}
if (vips_object_set(VIPS_OBJECT(operation), "buffer", blob, NULL)) {
vips_area_unref(VIPS_AREA(blob));
return 1;
}
vips_area_unref(VIPS_AREA(blob));
if (setLoadOptions(operation, params)) {
vips_object_unref_outputs(VIPS_OBJECT(operation));
g_object_unref(operation);
return 1;
}
if (vips_cache_operation_buildp(&operation)) {
vips_object_unref_outputs(VIPS_OBJECT(operation));
g_object_unref(operation);
return 1;
}
g_object_get(VIPS_OBJECT(operation), "out", &params->outputImage, NULL);
vips_object_unref_outputs(VIPS_OBJECT(operation));
g_object_unref(operation);
return 0;
}
typedef int (*SetSaveOptionsFn)(VipsOperation *operation, SaveParams *params);
int save_buffer(const char *operationName, SaveParams *params,
SetSaveOptionsFn setSaveOptions) {
VipsBlob *blob;
VipsOperation *operation = vips_operation_new(operationName);
if (!operation) {
return 1;
}
if (vips_object_set(VIPS_OBJECT(operation), "in", params->inputImage, NULL)) {
return 1;
}
if (setSaveOptions(operation, params)) {
g_object_unref(operation);
return 1;
}
if (vips_cache_operation_buildp(&operation)) {
vips_object_unref_outputs(VIPS_OBJECT(operation));
g_object_unref(operation);
return 1;
}
g_object_get(VIPS_OBJECT(operation), "buffer", &blob, NULL);
g_object_unref(operation);
VipsArea *area = VIPS_AREA(blob);
params->outputBuffer = (char *)(area->data);
params->outputLen = area->length;
area->free_fn = NULL;
vips_area_unref(area);
return 0;
}
// https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-jpegsave-buffer
int set_jpegsave_options(VipsOperation *operation, SaveParams *params) {
int ret = vips_object_set(
VIPS_OBJECT(operation), "strip", params->stripMetadata, "optimize_coding",
params->jpegOptimizeCoding, "interlace", params->interlace,
"subsample_mode", params->jpegSubsample, "trellis_quant",
params->jpegTrellisQuant, "overshoot_deringing",
params->jpegOvershootDeringing, "optimize_scans",
params->jpegOptimizeScans, "quant_table", params->jpegQuantTable, NULL);
if (!ret && params->quality) {
ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL);
}
return ret;
}
// https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-pngsave-buffer
int set_pngsave_options(VipsOperation *operation, SaveParams *params) {
int ret =
vips_object_set(VIPS_OBJECT(operation), "strip", params->stripMetadata,
"compression", params->pngCompression, "interlace",
params->interlace, "filter", params->pngFilter, "palette",
params->pngPalette, NULL);
if (!ret && params->quality) {
ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL);
}
if (!ret && params->pngDither) {
ret = vips_object_set(VIPS_OBJECT(operation), "dither", params->pngDither, NULL);
}
if (!ret && params->pngBitdepth) {
ret = vips_object_set(VIPS_OBJECT(operation), "bitdepth", params->pngBitdepth, NULL);
}
// TODO: Handle `profile` param.
return ret;
}
// https://github.com/libvips/libvips/blob/master/libvips/foreign/webpsave.c#L524
// https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-webpsave-buffer
int set_webpsave_options(VipsOperation *operation, SaveParams *params) {
int ret =
vips_object_set(VIPS_OBJECT(operation),
"strip", params->stripMetadata,
"lossless", params->webpLossless,
"near_lossless", params->webpNearLossless,
"reduction_effort", params->webpReductionEffort,
"profile", params->webpIccProfile ? params->webpIccProfile : "none",
"min_size", params->webpMinSize,
"kmin", params->webpKMin,
"kmax", params->webpKMax,
NULL);
if (!ret && params->quality) {
ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL);
}
return ret;
}
// https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-tiffsave-buffer
int set_tiffsave_options(VipsOperation *operation, SaveParams *params) {
int ret = vips_object_set(
VIPS_OBJECT(operation), "strip", params->stripMetadata, "compression",
params->tiffCompression, "predictor", params->tiffPredictor, "pyramid",
params->tiffPyramid, "tile_height", params->tiffTileHeight, "tile_width",
params->tiffTileWidth, "tile", params->tiffTile, NULL);
if (!ret && params->quality) {
ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL);
}
return ret;
}
// 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", 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,
NULL);
}
return ret;
}
// https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-gifsave-buffer
int set_gifsave_options(VipsOperation *operation, SaveParams *params) {
int ret = 0;
// See for argument values: https://www.libvips.org/API/current/VipsForeignSave.html#vips-gifsave
if (params->gifDither > 0.0 && params->gifDither <= 10) {
ret = vips_object_set(VIPS_OBJECT(operation), "dither", params->gifDither, NULL);
}
if (params->gifEffort >= 1 && params->gifEffort <= 10) {
ret = vips_object_set(VIPS_OBJECT(operation), "effort", params->gifEffort, NULL);
}
if (params->gifBitdepth >= 1 && params->gifBitdepth <= 8) {
ret = vips_object_set(VIPS_OBJECT(operation), "bitdepth", params->gifBitdepth, NULL);
}
return ret;
}
// https://github.com/libvips/libvips/blob/master/libvips/foreign/heifsave.c#L653
int set_heifsave_options(VipsOperation *operation, SaveParams *params) {
int ret = vips_object_set(VIPS_OBJECT(operation), "lossless",
params->heifLossless, NULL);
#if (VIPS_MAJOR_VERSION >= 8) && (VIPS_MINOR_VERSION >= 13)
if (!ret && params->heifBitdepth && params->heifEffort) {
ret = vips_object_set(VIPS_OBJECT(operation), "bitdepth",
params->heifBitdepth, "effort", params->heifEffort,
NULL);
}
#else
if (!ret && params->heifEffort) {
ret = vips_object_set(VIPS_OBJECT(operation), "speed", params->heifEffort,
NULL);
}
#endif
if (!ret && params->quality) {
ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL);
}
return ret;
}
// https://github.com/libvips/libvips/blob/master/libvips/foreign/heifsave.c#L653
int set_avifsave_options(VipsOperation *operation, SaveParams *params) {
int ret = vips_object_set(VIPS_OBJECT(operation), "strip", params->stripMetadata, "compression",
VIPS_FOREIGN_HEIF_COMPRESSION_AV1, "lossless",
params->heifLossless, NULL);
#if (VIPS_MAJOR_VERSION >= 8) && (VIPS_MINOR_VERSION >= 13)
if (!ret && params->heifBitdepth && params->heifEffort) {
ret = vips_object_set(VIPS_OBJECT(operation), "bitdepth",
params->heifBitdepth, "effort", params->heifEffort,
NULL);
}
#else
if (!ret && params->heifEffort) {
ret = vips_object_set(VIPS_OBJECT(operation), "speed", params->heifEffort,
NULL);
}
#endif
if (!ret && params->quality) {
ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL);
}
return ret;
}
int set_jp2ksave_options(VipsOperation *operation, SaveParams *params) {
int ret = vips_object_set(
VIPS_OBJECT(operation), "subsample_mode", params->jpegSubsample,
"tile_height", params->jp2kTileHeight, "tile_width", params->jp2kTileWidth,
"lossless", params->jp2kLossless, NULL);
if (!ret && params->quality) {
ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL);
}
return ret;
}
int set_jxlsave_options(VipsOperation *operation, SaveParams *params) {
int ret = vips_object_set(
VIPS_OBJECT(operation), "tier", params->jxlTier,
"distance", params->jxlDistance, "effort", params->jxlEffort,
"lossless", params->jxlLossless, NULL);
if (!ret && params->quality) {
ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL);
}
return ret;
}
int load_from_buffer(LoadParams *params, void *buf, size_t len) {
switch (params->inputFormat) {
case JPEG:
return load_buffer("jpegload_buffer", buf, len, params,
set_jpegload_options);
case PNG:
return load_buffer("pngload_buffer", buf, len, params,
set_pngload_options);
case WEBP:
return load_buffer("webpload_buffer", buf, len, params,
set_webpload_options);
case HEIF:
return load_buffer("heifload_buffer", buf, len, params,
set_heifload_options);
case TIFF:
return load_buffer("tiffload_buffer", buf, len, params,
set_tiffload_options);
case SVG:
return load_buffer("svgload_buffer", buf, len, params,
set_svgload_options);
case GIF:
return load_buffer("gifload_buffer", buf, len, params,
set_gifload_options);
case PDF:
return load_buffer("pdfload_buffer", buf, len, params,
set_pdfload_options);
case MAGICK:
return load_buffer("magickload_buffer", buf, len, params,
set_magickload_options);
case AVIF:
return load_buffer("heifload_buffer", buf, len, params,
set_heifload_options);
case JP2K:
return load_buffer("jp2kload_buffer", buf, len, params,
set_jp2kload_options);
case JXL:
return load_buffer("jxlload_buffer", buf, len, params,
set_jxlload_options);
default:
g_warning("Unsupported input type given: %d", params->inputFormat);
}
return 1;
}
int save_to_buffer(SaveParams *params) {
switch (params->outputFormat) {
case JPEG:
return save_buffer("jpegsave_buffer", params, set_jpegsave_options);
case PNG:
return save_buffer("pngsave_buffer", params, set_pngsave_options);
case WEBP:
return save_buffer("webpsave_buffer", params, set_webpsave_options);
case HEIF:
return save_buffer("heifsave_buffer", params, set_heifsave_options);
case TIFF:
return save_buffer("tiffsave_buffer", params, set_tiffsave_options);
case GIF:
#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:
return save_buffer("heifsave_buffer", params, set_avifsave_options);
case JP2K:
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);
}
return 1;
}
LoadParams create_load_params(ImageType inputFormat) {
Param defaultParam = {};
LoadParams p = {
.inputFormat = inputFormat,
.inputBlob = NULL,
.outputImage = NULL,
.autorotate = defaultParam,
.fail = defaultParam,
.page = defaultParam,
.n = defaultParam,
.dpi = defaultParam,
.jpegShrink = defaultParam,
.webpScale = defaultParam,
.heifThumbnail = defaultParam,
.svgUnlimited = defaultParam,
.access = defaultParam,
};
return p;
}
// TODO: Change to same pattern as ImportParams
static SaveParams defaultSaveParams = {
.inputImage = NULL,
.outputBuffer = NULL,
.outputFormat = JPEG,
.outputLen = 0,
.interlace = FALSE,
.quality = 0,
.stripMetadata = FALSE,
.jpegOptimizeCoding = FALSE,
.jpegSubsample = VIPS_FOREIGN_SUBSAMPLE_ON,
.jpegTrellisQuant = FALSE,
.jpegOvershootDeringing = FALSE,
.jpegOptimizeScans = FALSE,
.jpegQuantTable = 0,
.pngCompression = 6,
.pngPalette = FALSE,
.pngBitdepth = 0,
.pngDither = 0,
.pngFilter = VIPS_FOREIGN_PNG_FILTER_NONE,
.gifDither = 0.0,
.gifEffort = 0,
.gifBitdepth = 0,
.webpLossless = FALSE,
.webpNearLossless = FALSE,
.webpReductionEffort = 4,
.webpIccProfile = NULL,
.webpKMax = 0,
.webpKMin = 0,
.webpMinSize = FALSE,
.heifBitdepth = 8,
.heifLossless = FALSE,
.heifEffort = 5,
.tiffCompression = VIPS_FOREIGN_TIFF_COMPRESSION_LZW,
.tiffPredictor = VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL,
.tiffPyramid = FALSE,
.tiffTile = FALSE,
.tiffTileHeight = 256,
.tiffTileWidth = 256,
.jp2kLossless = FALSE,
.jp2kTileHeight = 512,
.jp2kTileWidth = 512,
.jxlTier = 0,
.jxlDistance = 1.0,
.jxlEffort = 7,
.jxlLossless = FALSE,
};
SaveParams create_save_params(ImageType outputFormat) {
SaveParams params = defaultSaveParams;
params.outputFormat = outputFormat;
return params;
}
+582
View File
@@ -0,0 +1,582 @@
package vips
// #include "foreign.h"
import "C"
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"math"
"runtime"
"unsafe"
"golang.org/x/net/html/charset"
)
// SubsampleMode correlates to a libvips subsample mode
type SubsampleMode int
// SubsampleMode enum correlating to libvips subsample modes
const (
VipsForeignSubsampleAuto SubsampleMode = C.VIPS_FOREIGN_SUBSAMPLE_AUTO
VipsForeignSubsampleOn SubsampleMode = C.VIPS_FOREIGN_SUBSAMPLE_ON
VipsForeignSubsampleOff SubsampleMode = C.VIPS_FOREIGN_SUBSAMPLE_OFF
VipsForeignSubsampleLast SubsampleMode = C.VIPS_FOREIGN_SUBSAMPLE_LAST
)
// ImageType represents an image type
type ImageType int
// ImageType enum
const (
ImageTypeUnknown ImageType = C.UNKNOWN
ImageTypeGIF ImageType = C.GIF
ImageTypeJPEG ImageType = C.JPEG
ImageTypeMagick ImageType = C.MAGICK
ImageTypePDF ImageType = C.PDF
ImageTypePNG ImageType = C.PNG
ImageTypeSVG ImageType = C.SVG
ImageTypeTIFF ImageType = C.TIFF
ImageTypeWEBP ImageType = C.WEBP
ImageTypeHEIF ImageType = C.HEIF
ImageTypeBMP ImageType = C.BMP
ImageTypeAVIF ImageType = C.AVIF
ImageTypeJP2K ImageType = C.JP2K
ImageTypeJXL ImageType = C.JXL
ImageTypePSD ImageType = C.PSD
)
// Types which should be deligated to ImageMagick loader
var imageMagickTypes = map[ImageType]bool{
ImageTypeBMP: true,
ImageTypePSD: true,
}
var imageTypeExtensionMap = map[ImageType]string{
ImageTypeGIF: ".gif",
ImageTypeJPEG: ".jpeg",
ImageTypeMagick: ".magick",
ImageTypePDF: ".pdf",
ImageTypePNG: ".png",
ImageTypeSVG: ".svg",
ImageTypeTIFF: ".tiff",
ImageTypeWEBP: ".webp",
ImageTypeHEIF: ".heic",
ImageTypeBMP: ".bmp",
ImageTypeAVIF: ".avif",
ImageTypeJP2K: ".jp2",
ImageTypeJXL: ".jxl",
ImageTypePSD: ".psd",
}
// ImageTypes defines the various image types supported by govips
var ImageTypes = map[ImageType]string{
ImageTypeGIF: "gif",
ImageTypeJPEG: "jpeg",
ImageTypeMagick: "magick",
ImageTypePDF: "pdf",
ImageTypePNG: "png",
ImageTypeSVG: "svg",
ImageTypeTIFF: "tiff",
ImageTypeWEBP: "webp",
ImageTypeHEIF: "heif",
ImageTypeBMP: "bmp",
ImageTypeAVIF: "heif",
ImageTypeJP2K: "jp2k",
ImageTypeJXL: "jxl",
ImageTypePSD: "psd",
}
// TiffCompression represents method for compressing a tiff at export
type TiffCompression int
// TiffCompression enum
const (
TiffCompressionNone TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_NONE
TiffCompressionJpeg TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_JPEG
TiffCompressionDeflate TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_DEFLATE
TiffCompressionPackbits TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_PACKBITS
TiffCompressionFax4 TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_CCITTFAX4
TiffCompressionLzw TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_LZW
TiffCompressionWebp TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_WEBP
TiffCompressionZstd TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_ZSTD
)
// TiffPredictor represents method for compressing a tiff at export
type TiffPredictor int
// TiffPredictor enum
const (
TiffPredictorNone TiffPredictor = C.VIPS_FOREIGN_TIFF_PREDICTOR_NONE
TiffPredictorHorizontal TiffPredictor = C.VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL
TiffPredictorFloat TiffPredictor = C.VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT
)
// PngFilter represents filter algorithms that can be applied before compression.
// See https://www.w3.org/TR/PNG-Filters.html
type PngFilter int
// PngFilter enum
const (
PngFilterNone PngFilter = C.VIPS_FOREIGN_PNG_FILTER_NONE
PngFilterSub PngFilter = C.VIPS_FOREIGN_PNG_FILTER_SUB
PngFilterUo PngFilter = C.VIPS_FOREIGN_PNG_FILTER_UP
PngFilterAvg PngFilter = C.VIPS_FOREIGN_PNG_FILTER_AVG
PngFilterPaeth PngFilter = C.VIPS_FOREIGN_PNG_FILTER_PAETH
PngFilterAll PngFilter = C.VIPS_FOREIGN_PNG_FILTER_ALL
)
// Access represents how libvips opens files.
// See https://www.libvips.org/API/current/How-it-opens-files.html
const (
AccessRandom int = C.VIPS_ACCESS_RANDOM
AccessSequential int = C.VIPS_ACCESS_SEQUENTIAL
AccessSequentialUnbuffered int = C.VIPS_ACCESS_SEQUENTIAL_UNBUFFERED
AccessLast int = C.VIPS_ACCESS_LAST
)
// FileExt returns the canonical extension for the ImageType
func (i ImageType) FileExt() string {
if ext, ok := imageTypeExtensionMap[i]; ok {
return ext
}
return ""
}
// IsTypeSupported checks whether given image type is supported by govips
func IsTypeSupported(imageType ImageType) bool {
if err := startupIfNeeded(); err != nil {
govipsLog("govips", LogLevelError, fmt.Sprintf("failed to start vips: %v", err))
return false
}
// BMP is supported via the magick loader
if imageType == ImageTypeBMP {
return supportedImageTypes[ImageTypeMagick]
}
return supportedImageTypes[imageType]
}
// DetermineImageType attempts to determine the image type of the given buffer
func DetermineImageType(buf []byte) ImageType {
if len(buf) < 12 {
return ImageTypeUnknown
} else if isJPEG(buf) {
return ImageTypeJPEG
} else if isPNG(buf) {
return ImageTypePNG
} else if isGIF(buf) {
return ImageTypeGIF
} else if isTIFF(buf) {
return ImageTypeTIFF
} else if isWEBP(buf) {
return ImageTypeWEBP
} else if isAVIF(buf) {
return ImageTypeAVIF
} else if isHEIF(buf) {
return ImageTypeHEIF
} else if isSVG(buf) {
return ImageTypeSVG
} else if isBMP(buf) {
return ImageTypeBMP
} else if isJP2K(buf) {
return ImageTypeJP2K
} else if isJXL(buf) {
return ImageTypeJXL
} else if isPDF(buf) {
return ImageTypePDF
} else if isICO(buf) {
return ImageTypeMagick
} else if isPSD(buf) {
return ImageTypePSD
} else {
return ImageTypeUnknown
}
}
var jpeg = []byte("\xFF\xD8\xFF")
func isJPEG(buf []byte) bool {
return bytes.HasPrefix(buf, jpeg)
}
var gifHeader = []byte("\x47\x49\x46")
func isGIF(buf []byte) bool {
return bytes.HasPrefix(buf, gifHeader)
}
var pngHeader = []byte("\x89\x50\x4E\x47")
func isPNG(buf []byte) bool {
return bytes.HasPrefix(buf, pngHeader)
}
var tifII = []byte("\x49\x49\x2A\x00")
var tifMM = []byte("\x4D\x4D\x00\x2A")
func isTIFF(buf []byte) bool {
return bytes.HasPrefix(buf, tifII) || bytes.HasPrefix(buf, tifMM)
}
var webpHeader = []byte("\x57\x45\x42\x50")
func isWEBP(buf []byte) bool {
return bytes.Equal(buf[8:12], webpHeader)
}
// https://github.com/strukturag/libheif/blob/master/libheif/heif.cc
var ftyp = []byte("ftyp")
var heic = []byte("heic")
var heix = []byte("heix")
var heim = []byte("heim")
var heis = []byte("heis")
var mif1 = []byte("mif1")
var msf1 = []byte("msf1")
var avif = []byte("avif")
var avis = []byte("avis")
func isHEIF(buf []byte) bool {
return bytes.Equal(buf[4:8], ftyp) && (bytes.Equal(buf[8:12], heic) ||
bytes.Equal(buf[8:12], heix) ||
bytes.Equal(buf[8:12], heim) ||
bytes.Equal(buf[8:12], heis) ||
bytes.Equal(buf[8:12], mif1) ||
bytes.Equal(buf[8:12], msf1)) ||
isAVIF(buf)
}
func isAVIF(buf []byte) bool {
return bytes.Equal(buf[4:8], ftyp) &&
(bytes.Equal(buf[8:12], avif) || bytes.Equal(buf[8:12], avis))
}
var svg = []byte("<svg")
func isSVG(buf []byte) bool {
sub := buf[:int(math.Min(1024.0, float64(len(buf))))]
if bytes.Contains(sub, svg) {
data := &struct {
XMLName xml.Name `xml:"svg"`
}{}
reader := bytes.NewReader(buf)
decoder := xml.NewDecoder(reader)
decoder.Strict = false
decoder.CharsetReader = charset.NewReaderLabel
err := decoder.Decode(data)
return err == nil && data.XMLName.Local == "svg"
}
return false
}
var pdf = []byte("\x25\x50\x44\x46")
func isPDF(buf []byte) bool {
if len(buf) <= 1024 {
return bytes.Contains(buf, pdf)
}
return bytes.Contains(buf[:1024], pdf)
}
var bmpHeader = []byte("BM")
func isBMP(buf []byte) bool {
return bytes.HasPrefix(buf, bmpHeader)
}
// X'0000 000C 6A50 2020 0D0A 870A'
var jp2kHeader = []byte("\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A")
// https://datatracker.ietf.org/doc/html/rfc3745
func isJP2K(buf []byte) bool {
return bytes.HasPrefix(buf, jp2kHeader)
}
// As a 'naked' codestream
var jxlHeader = []byte("\xff\x0a")
// As an ISOBMFF-based container: 0x0000000C 4A584C20 0D0A870A
var jxlHeaderISOBMFF = []byte("\x00\x00\x00\x0C\x4A\x58\x4C\x20\x0D\x0A\x87\x0A")
func isJXL(buf []byte) bool {
if len(buf) >= 2 && buf[0] == 0xFF && buf[1] == 0x0A {
return true
}
if len(buf) >= 8 && bytes.Equal(buf[4:8], []byte("JXL ")) {
return true
}
return false
}
var icoHeader = []byte("\x00\x00\x01\x00")
func isICO(buf []byte) bool {
return bytes.HasPrefix(buf, icoHeader)
}
var psdHeader = []byte("\x38\x42\x50\x53")
func isPSD(buf []byte) bool {
return bytes.HasPrefix(buf, psdHeader)
}
func isNeedToChangeLoaderToMagick(t ImageType) bool {
return imageMagickTypes[t]
}
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.
defer runtime.KeepAlive(src)
originalType := DetermineImageType(src)
currentType := originalType
// Map image types which are not supported by libvips itself to ImageMagick
if isNeedToChangeLoaderToMagick(originalType) {
currentType = ImageTypeMagick
}
if !IsTypeSupported(currentType) {
govipsLog("govips", LogLevelInfo, fmt.Sprintf("failed to understand image format size=%d", len(src)))
return nil, currentType, originalType, ErrUnsupportedImageFormat
}
importParams := createImportParams(currentType, params)
if err := C.load_from_buffer(&importParams, unsafe.Pointer(&src[0]), C.size_t(len(src))); err != 0 {
return nil, currentType, originalType, handleImageError(importParams.outputImage)
}
return importParams.outputImage, currentType, originalType, nil
}
func maybeSetBoolParam(p BoolParameter, cp *C.Param) {
if p.IsSet() {
C.set_bool_param(cp, toGboolean(p.Get()))
}
}
func maybeSetIntParam(p IntParameter, cp *C.Param) {
if p.IsSet() {
C.set_int_param(cp, C.int(p.Get()))
}
}
func maybeSetDoubleParam(p Float64Parameter, cp *C.Param) {
if p.IsSet() {
C.set_double_param(cp, C.gdouble(p.Get()))
}
}
func createImportParams(format ImageType, params *ImportParams) C.LoadParams {
p := C.create_load_params(C.ImageType(format))
maybeSetBoolParam(params.AutoRotate, &p.autorotate)
maybeSetBoolParam(params.FailOnError, &p.fail)
maybeSetIntParam(params.Page, &p.page)
maybeSetIntParam(params.NumPages, &p.n)
maybeSetIntParam(params.JpegShrinkFactor, &p.jpegShrink)
maybeSetDoubleParam(params.WebpScaleFactor, &p.webpScale)
maybeSetBoolParam(params.HeifThumbnail, &p.heifThumbnail)
maybeSetBoolParam(params.SvgUnlimited, &p.svgUnlimited)
maybeSetIntParam(params.Access, &p.access)
if params.Density.IsSet() {
C.set_double_param(&p.dpi, C.gdouble(params.Density.Get()))
}
return p
}
func vipsSaveJPEGToBuffer(in *C.VipsImage, params JpegExportParams) ([]byte, error) {
incOpCounter("save_jpeg_buffer")
p := C.create_save_params(C.JPEG)
p.inputImage = in
p.stripMetadata = C.int(boolToInt(params.StripMetadata))
p.quality = C.int(params.Quality)
p.interlace = C.int(boolToInt(params.Interlace))
p.jpegOptimizeCoding = C.int(boolToInt(params.OptimizeCoding))
p.jpegSubsample = C.VipsForeignSubsample(params.SubsampleMode)
p.jpegTrellisQuant = C.int(boolToInt(params.TrellisQuant))
p.jpegOvershootDeringing = C.int(boolToInt(params.OvershootDeringing))
p.jpegOptimizeScans = C.int(boolToInt(params.OptimizeScans))
p.jpegQuantTable = C.int(params.QuantTable)
return vipsSaveToBuffer(p)
}
func vipsSavePNGToBuffer(in *C.VipsImage, params PngExportParams) ([]byte, error) {
incOpCounter("save_png_buffer")
p := C.create_save_params(C.PNG)
p.inputImage = in
p.quality = C.int(params.Quality)
p.stripMetadata = C.int(boolToInt(params.StripMetadata))
p.interlace = C.int(boolToInt(params.Interlace))
p.pngCompression = C.int(params.Compression)
p.pngFilter = C.VipsForeignPngFilter(params.Filter)
p.pngPalette = C.int(boolToInt(params.Palette))
p.pngDither = C.double(params.Dither)
p.pngBitdepth = C.int(params.Bitdepth)
return vipsSaveToBuffer(p)
}
func vipsSaveWebPToBuffer(in *C.VipsImage, params WebpExportParams) ([]byte, error) {
incOpCounter("save_webp_buffer")
p := C.create_save_params(C.WEBP)
p.inputImage = in
p.stripMetadata = C.int(boolToInt(params.StripMetadata))
p.quality = C.int(params.Quality)
p.webpLossless = C.int(boolToInt(params.Lossless))
p.webpNearLossless = C.int(boolToInt(params.NearLossless))
p.webpReductionEffort = C.int(params.ReductionEffort)
p.webpMinSize = C.int(boolToInt(params.MinSize))
p.webpKMin = C.int(params.MinKeyFrames)
p.webpKMax = C.int(params.MaxKeyFrames)
if params.IccProfile != "" {
p.webpIccProfile = C.CString(params.IccProfile)
defer C.free(unsafe.Pointer(p.webpIccProfile))
}
return vipsSaveToBuffer(p)
}
func vipsSaveTIFFToBuffer(in *C.VipsImage, params TiffExportParams) ([]byte, error) {
incOpCounter("save_tiff_buffer")
p := C.create_save_params(C.TIFF)
p.inputImage = in
p.stripMetadata = C.int(boolToInt(params.StripMetadata))
p.quality = C.int(params.Quality)
p.tiffCompression = C.VipsForeignTiffCompression(params.Compression)
p.tiffPyramid = C.int(boolToInt(params.Pyramid))
p.tiffTile = C.int(boolToInt(params.Tile))
tileHeight := params.TileHeight
tileWidth := params.TileWidth
if tileHeight <= 0 {
tileHeight = 256
}
if tileWidth <= 0 {
tileWidth = 256
}
p.tiffTileHeight = C.int(tileHeight)
p.tiffTileWidth = C.int(tileWidth)
return vipsSaveToBuffer(p)
}
func vipsSaveHEIFToBuffer(in *C.VipsImage, params HeifExportParams) ([]byte, error) {
incOpCounter("save_heif_buffer")
p := C.create_save_params(C.HEIF)
p.inputImage = in
p.outputFormat = C.HEIF
p.quality = C.int(params.Quality)
p.heifLossless = C.int(boolToInt(params.Lossless))
p.heifBitdepth = C.int(params.Bitdepth)
p.heifEffort = C.int(params.Effort)
return vipsSaveToBuffer(p)
}
func vipsSaveAVIFToBuffer(in *C.VipsImage, params AvifExportParams) ([]byte, error) {
incOpCounter("save_heif_buffer")
// Speed was deprecated but we want to avoid breaking code that still uses it:
effort := params.Effort
if params.Speed != 0 {
effort = params.Speed
}
p := C.create_save_params(C.AVIF)
p.inputImage = in
p.stripMetadata = C.int(boolToInt(params.StripMetadata))
p.outputFormat = C.AVIF
p.quality = C.int(params.Quality)
p.heifLossless = C.int(boolToInt(params.Lossless))
p.heifBitdepth = C.int(params.Bitdepth)
p.heifEffort = C.int(effort)
return vipsSaveToBuffer(p)
}
func vipsSaveJP2KToBuffer(in *C.VipsImage, params Jp2kExportParams) ([]byte, error) {
incOpCounter("save_jp2k_buffer")
p := C.create_save_params(C.JP2K)
p.inputImage = in
p.outputFormat = C.JP2K
p.quality = C.int(params.Quality)
p.jp2kLossless = C.int(boolToInt(params.Lossless))
p.jp2kTileWidth = C.int(params.TileWidth)
p.jp2kTileHeight = C.int(params.TileHeight)
p.jpegSubsample = C.VipsForeignSubsample(params.SubsampleMode)
return vipsSaveToBuffer(p)
}
func vipsSaveGIFToBuffer(in *C.VipsImage, params GifExportParams) ([]byte, error) {
incOpCounter("save_gif_buffer")
p := C.create_save_params(C.GIF)
p.inputImage = in
p.quality = C.int(params.Quality)
p.gifDither = C.double(params.Dither)
p.gifEffort = C.int(params.Effort)
p.gifBitdepth = C.int(params.Bitdepth)
return vipsSaveToBuffer(p)
}
func vipsSaveJxlToBuffer(in *C.VipsImage, params JxlExportParams) ([]byte, error) {
incOpCounter("save_jxl_buffer")
p := C.create_save_params(C.JXL)
p.inputImage = in
p.outputFormat = C.JXL
p.quality = C.int(params.Quality)
p.jxlLossless = C.int(boolToInt(params.Lossless))
p.jxlTier = C.int(params.Tier)
p.jxlDistance = C.double(params.Distance)
p.jxlEffort = C.int(params.Effort)
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)
}
buf := C.GoBytes(params.outputBuffer, C.int(params.outputLen))
defer gFreePointer(params.outputBuffer)
return buf, nil
}
+150
View File
@@ -0,0 +1,150 @@
// https://libvips.github.io/libvips/API/current/VipsForeignSave.html
// clang-format off
// include order matters
#include <stdlib.h>
#include <vips/vips.h>
#include <vips/foreign.h>
// clang-format n
#ifndef BOOL
#define BOOL int
#endif
typedef enum types {
UNKNOWN = 0,
JPEG,
WEBP,
PNG,
TIFF,
GIF,
PDF,
SVG,
MAGICK,
HEIF,
BMP,
AVIF,
JP2K,
JXL,
PSD,
} ImageType;
typedef enum ParamType {
PARAM_TYPE_NULL,
PARAM_TYPE_BOOL,
PARAM_TYPE_INT,
PARAM_TYPE_DOUBLE,
} ParamType;
typedef struct Param {
ParamType type;
union Value {
gboolean b;
gint i;
gdouble d;
} value;
gboolean is_set;
} Param;
void set_bool_param(Param *p, gboolean b);
void set_int_param(Param *p, gint i);
void set_double_param(Param *p, gdouble d);
typedef struct LoadParams {
ImageType inputFormat;
VipsBlob *inputBlob;
VipsImage *outputImage;
Param autorotate;
Param fail;
Param page;
Param n;
Param dpi;
Param jpegShrink;
Param webpScale;
Param heifThumbnail;
Param svgUnlimited;
Param access;
} LoadParams;
LoadParams create_load_params(ImageType inputFormat);
int load_from_buffer(LoadParams *params, void *buf, size_t len);
typedef struct SaveParams {
VipsImage *inputImage;
void *outputBuffer;
ImageType outputFormat;
size_t outputLen;
BOOL stripMetadata;
int quality;
BOOL interlace;
// JPEG
BOOL jpegOptimizeCoding;
VipsForeignSubsample jpegSubsample;
BOOL jpegTrellisQuant;
BOOL jpegOvershootDeringing;
BOOL jpegOptimizeScans;
int jpegQuantTable;
// PNG
int pngCompression;
VipsForeignPngFilter pngFilter;
BOOL pngPalette;
double pngDither;
int pngBitdepth;
// GIF (with CGIF)
double gifDither;
int gifEffort;
int gifBitdepth;
// WEBP
BOOL webpLossless;
BOOL webpNearLossless;
int webpReductionEffort;
char *webpIccProfile;
BOOL webpMinSize;
int webpKMin;
int webpKMax;
// HEIF - https://github.com/libvips/libvips/blob/master/libvips/foreign/heifsave.c#L71
int heifBitdepth; // Bitdepth to save at for >8 bit images
BOOL heifLossless; // Lossless compression
int heifEffort; // CPU effort (0 - 9)
// TIFF
VipsForeignTiffCompression tiffCompression;
VipsForeignTiffPredictor tiffPredictor;
BOOL tiffPyramid;
BOOL tiffTile;
int tiffTileHeight;
int tiffTileWidth;
// JPEG2000
BOOL jp2kLossless;
int jp2kTileWidth;
int jp2kTileHeight;
// JXL
int jxlTier;
double jxlDistance;
int jxlEffort;
BOOL jxlLossless;
// MAGICK
char *magickFormat;
BOOL magickOptimizeGifFrames;
BOOL magickOptimizeGifTransparency;
int magickBitDepth;
} SaveParams;
SaveParams create_save_params(ImageType outputFormat);
int save_to_buffer(SaveParams *params);
+139
View File
@@ -0,0 +1,139 @@
// Code generated by vipsgen. DO NOT EDIT.
//
// Enum types needed by generated code that are not yet defined in the
// hand-written codebase. These will move to gen_enums.go in Phase 4.
package vips
// #include <vips/vips.h>
import "C"
// Precision represents VipsPrecision for controlling computation precision.
type Precision int
const (
PrecisionInteger Precision = C.VIPS_PRECISION_INTEGER
PrecisionFloat Precision = C.VIPS_PRECISION_FLOAT
PrecisionApproximate Precision = C.VIPS_PRECISION_APPROXIMATE
)
// Combine represents VipsCombine for combining operations.
type Combine int
const (
CombineMax Combine = C.VIPS_COMBINE_MAX
CombineSum Combine = C.VIPS_COMBINE_SUM
CombineMin Combine = C.VIPS_COMBINE_MIN
)
// CombineMode represents VipsCombineMode for draw combine operations.
type CombineMode int
const (
CombineModeSet CombineMode = C.VIPS_COMBINE_MODE_SET
CombineModeAdd CombineMode = C.VIPS_COMBINE_MODE_ADD
)
// OperationBoolean represents VipsOperationBoolean for boolean operations.
type OperationBoolean int
const (
OperationBooleanAnd OperationBoolean = C.VIPS_OPERATION_BOOLEAN_AND
OperationBooleanOr OperationBoolean = C.VIPS_OPERATION_BOOLEAN_OR
OperationBooleanEor OperationBoolean = C.VIPS_OPERATION_BOOLEAN_EOR
OperationBooleanLshift OperationBoolean = C.VIPS_OPERATION_BOOLEAN_LSHIFT
OperationBooleanRshift OperationBoolean = C.VIPS_OPERATION_BOOLEAN_RSHIFT
)
// OperationComplex represents VipsOperationComplex for complex operations.
type OperationComplex int
const (
OperationComplexPolar OperationComplex = C.VIPS_OPERATION_COMPLEX_POLAR
OperationComplexRect OperationComplex = C.VIPS_OPERATION_COMPLEX_RECT
OperationComplexConj OperationComplex = C.VIPS_OPERATION_COMPLEX_CONJ
)
// OperationComplex2 represents VipsOperationComplex2 for binary complex operations.
type OperationComplex2 int
const (
OperationComplex2CrossPhase OperationComplex2 = C.VIPS_OPERATION_COMPLEX2_CROSS_PHASE
)
// OperationComplexget represents VipsOperationComplexget for getting complex components.
type OperationComplexget int
const (
OperationComplexgetReal OperationComplexget = C.VIPS_OPERATION_COMPLEXGET_REAL
OperationComplexgetImag OperationComplexget = C.VIPS_OPERATION_COMPLEXGET_IMAG
)
// OperationMath represents VipsOperationMath for math operations.
type OperationMath int
const (
OperationMathSin OperationMath = C.VIPS_OPERATION_MATH_SIN
OperationMathCos OperationMath = C.VIPS_OPERATION_MATH_COS
OperationMathTan OperationMath = C.VIPS_OPERATION_MATH_TAN
OperationMathAsin OperationMath = C.VIPS_OPERATION_MATH_ASIN
OperationMathAcos OperationMath = C.VIPS_OPERATION_MATH_ACOS
OperationMathAtan OperationMath = C.VIPS_OPERATION_MATH_ATAN
OperationMathLog OperationMath = C.VIPS_OPERATION_MATH_LOG
OperationMathLog10 OperationMath = C.VIPS_OPERATION_MATH_LOG10
OperationMathExp OperationMath = C.VIPS_OPERATION_MATH_EXP
OperationMathExp10 OperationMath = C.VIPS_OPERATION_MATH_EXP10
)
// OperationMath2 represents VipsOperationMath2 for binary math operations.
type OperationMath2 int
const (
OperationMath2Pow OperationMath2 = C.VIPS_OPERATION_MATH2_POW
OperationMath2Wop OperationMath2 = C.VIPS_OPERATION_MATH2_WOP
OperationMath2Atan2 OperationMath2 = C.VIPS_OPERATION_MATH2_ATAN2
)
// OperationRelational represents VipsOperationRelational for relational operations.
type OperationRelational int
const (
OperationRelationalEqual OperationRelational = C.VIPS_OPERATION_RELATIONAL_EQUAL
OperationRelationalNoteq OperationRelational = C.VIPS_OPERATION_RELATIONAL_NOTEQ
OperationRelationalLess OperationRelational = C.VIPS_OPERATION_RELATIONAL_LESS
OperationRelationalLesseq OperationRelational = C.VIPS_OPERATION_RELATIONAL_LESSEQ
OperationRelationalMore OperationRelational = C.VIPS_OPERATION_RELATIONAL_MORE
OperationRelationalMoreeq OperationRelational = C.VIPS_OPERATION_RELATIONAL_MOREEQ
)
// OperationRound represents VipsOperationRound for rounding operations.
type OperationRound int
const (
OperationRoundRint OperationRound = C.VIPS_OPERATION_ROUND_RINT
OperationRoundCeil OperationRound = C.VIPS_OPERATION_ROUND_CEIL
OperationRoundFloor OperationRound = C.VIPS_OPERATION_ROUND_FLOOR
)
// OperationMorphology represents VipsOperationMorphology for morphological operations.
type OperationMorphology int
const (
OperationMorphologyErode OperationMorphology = C.VIPS_OPERATION_MORPHOLOGY_ERODE
OperationMorphologyDilate OperationMorphology = C.VIPS_OPERATION_MORPHOLOGY_DILATE
)
// FailOn represents VipsFailOn for controlling error handling on load.
type FailOn int
const (
FailOnNone FailOn = C.VIPS_FAIL_ON_NONE
FailOnTruncated FailOn = C.VIPS_FAIL_ON_TRUNCATED
FailOnError FailOn = C.VIPS_FAIL_ON_ERROR
FailOnWarning FailOn = C.VIPS_FAIL_ON_WARNING
)
// ForeignKeep represents VipsForeignKeep flags for controlling metadata retention.
type ForeignKeep int
// Note: ForeignKeep is a flags (bitmask) type. Values combined with |.
+3
View File
@@ -0,0 +1,3 @@
package vips
//go:generate go run ../cmd/vipsgen --generate --output=.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
#include "govips.h"
static void govips_logging_handler(const gchar *log_domain,
GLogLevelFlags log_level,
const gchar *message, gpointer user_data) {
govipsLoggingHandler((char *)log_domain, (int)log_level, (char *)message);
}
static void null_logging_handler(const gchar *log_domain,
GLogLevelFlags log_level, const gchar *message,
gpointer user_data) {}
void vips_set_logging_handler(void) {
g_log_set_default_handler(govips_logging_handler, NULL);
}
void vips_unset_logging_handler(void) {
g_log_set_default_handler(null_logging_handler, NULL);
}
/* This function skips the Govips logging handler and logs
directly to stdout. To be used only for testing and debugging.
Needed for CI because of a Go macOS bug which doesn't clean cgo callbacks on
exit. */
void vips_default_logging_handler(void) {
g_log_set_default_handler(g_log_default_handler, NULL);
}
+288
View File
@@ -0,0 +1,288 @@
// Package vips provides go bindings for libvips, a fast image processing library.
package vips
// #cgo pkg-config: vips
// #include <vips/vips.h>
// #include "govips.h"
import "C"
import (
"errors"
"fmt"
"os"
"runtime"
"strings"
"sync"
"sync/atomic"
"testing"
)
const (
defaultConcurrencyLevel = 1
defaultMaxCacheMem = 50 * 1024 * 1024
defaultMaxCacheSize = 100
defaultMaxCacheFiles = 0
)
var (
// Version is the full libvips version string (x.y.z)
Version = C.GoString(C.vips_version_string())
// MajorVersion is the libvips major component of the version string (x in x.y.z)
MajorVersion = int(C.vips_version(0))
// MinorVersion is the libvips minor component of the version string (y in x.y.z)
MinorVersion = int(C.vips_version(1))
// MicroVersion is the libvips micro component of the version string (z in x.y.z)
// Also known as patch version
MicroVersion = int(C.vips_version(2))
running = false
hasShutdown = false
initLock sync.Mutex
statCollectorDone chan struct{}
once sync.Once
typeLoaders = make(map[string]ImageType)
supportedImageTypes = make(map[ImageType]bool)
)
// Config allows fine-tuning of libvips library
type Config struct {
ConcurrencyLevel int
MaxCacheFiles int
MaxCacheMem int
MaxCacheSize int
ReportLeaks bool
CacheTrace bool
CollectStats bool
}
// Startup sets up the libvips support and ensures the versions are correct. Pass in nil for
// default configuration.
func Startup(config *Config) error {
if hasShutdown {
return errors.New("govips cannot be stopped and restarted")
}
initLock.Lock()
defer initLock.Unlock()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if running {
govipsLog("govips", LogLevelInfo, "warning libvips already started")
return nil
}
if MajorVersion < 8 {
return errors.New("govips requires libvips version 8.10+")
}
if MajorVersion == 8 && MinorVersion < 10 {
return errors.New("govips requires libvips version 8.10+")
}
cName := C.CString("govips")
defer freeCString(cName)
// Initialize govips logging handler and verbosity filter to historical default
if !currentLoggingOverridden {
govipsLoggingSettings(nil, LogLevelInfo)
}
// Override default glib logging handler to intercept logging messages
enableLogging()
err := C.vips_init(cName)
if err != 0 {
return fmt.Errorf("failed to start vips code=%v", err)
}
running = true
if config != nil {
if config.CollectStats {
statCollectorDone = collectStats()
}
C.vips_leak_set(toGboolean(config.ReportLeaks))
if config.ConcurrencyLevel >= 0 {
C.vips_concurrency_set(C.int(config.ConcurrencyLevel))
} else {
C.vips_concurrency_set(defaultConcurrencyLevel)
}
if config.MaxCacheFiles >= 0 {
C.vips_cache_set_max_files(C.int(config.MaxCacheFiles))
} else {
C.vips_cache_set_max_files(defaultMaxCacheFiles)
}
if config.MaxCacheMem >= 0 {
C.vips_cache_set_max_mem(C.size_t(config.MaxCacheMem))
} else {
C.vips_cache_set_max_mem(defaultMaxCacheMem)
}
if config.MaxCacheSize >= 0 {
C.vips_cache_set_max(C.int(config.MaxCacheSize))
} else {
C.vips_cache_set_max(defaultMaxCacheSize)
}
C.vips_cache_set_trace(toGboolean(config.CacheTrace))
} else {
C.vips_concurrency_set(defaultConcurrencyLevel)
C.vips_cache_set_max(defaultMaxCacheSize)
C.vips_cache_set_max_mem(defaultMaxCacheMem)
C.vips_cache_set_max_files(defaultMaxCacheFiles)
C.vips_cache_set_trace(toGboolean(false))
}
govipsLog("govips", LogLevelInfo, fmt.Sprintf("vips %s started with concurrency=%d cache_max_files=%d cache_max_mem=%d cache_max=%d",
Version,
int(C.vips_concurrency_get()),
int(C.vips_cache_get_max_files()),
int(C.vips_cache_get_max_mem()),
int(C.vips_cache_get_max())))
initTypes()
return nil
}
func enableLogging() {
C.vips_set_logging_handler()
}
func disableLogging() {
C.vips_unset_logging_handler()
}
// consoleLogging overrides the Govips logging handler and makes glib
// use its default logging handler which outputs everything to console.
// Needed for CI unit testing due to a macOS bug in Go (doesn't clean cgo callbacks on exit)
func consoleLogging() {
C.vips_default_logging_handler()
}
// Shutdown libvips
func Shutdown() {
hasShutdown = true
if statCollectorDone != nil {
statCollectorDone <- struct{}{}
}
initLock.Lock()
defer initLock.Unlock()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if !running {
govipsLog("govips", LogLevelInfo, "warning libvips not started")
return
}
if temporaryDirectory != "" {
os.RemoveAll(temporaryDirectory)
}
C.vips_shutdown()
disableLogging()
running = false
}
// ShutdownThread clears the cache for for the given thread. This needs to be
// called when a thread using vips exits.
func ShutdownThread() {
C.vips_thread_shutdown()
}
// ClearCache drops the whole operation cache, handy for leak tracking.
func ClearCache() {
C.vips_cache_drop_all()
}
// PrintCache prints the whole operation cache to stdout for debugging purposes.
func PrintCache() {
C.vips_cache_print()
}
// PrintObjectReport outputs all of the current internal objects in libvips
func PrintObjectReport(label string) {
govipsLog("govips", LogLevelInfo, fmt.Sprintf("\n=======================================\nvips live objects: %s...\n", label))
C.vips_object_print_all()
govipsLog("govips", LogLevelInfo, "=======================================\n\n")
}
// MemoryStats is a data structure that houses various memory statistics from ReadVipsMemStats()
type MemoryStats struct {
Mem int64
MemHigh int64
Files int64
Allocs int64
}
var openImageRefs atomic.Int64
// OpenImageRefs returns the number of ImageRef instances that haven't been closed.
func OpenImageRefs() int64 {
return openImageRefs.Load()
}
// AssertNoLeaks fails the test if any ImageRef instances remain open.
// Call this at the end of tests to catch leaked images.
func AssertNoLeaks(t testing.TB) {
t.Helper()
n := openImageRefs.Load()
if n != 0 {
t.Errorf("govips: %d ImageRef(s) not closed (leaked)", n)
}
}
// ReadVipsMemStats returns various memory statistics such as allocated memory and open files.
func ReadVipsMemStats(stats *MemoryStats) {
stats.Mem = int64(C.vips_tracked_get_mem())
stats.MemHigh = int64(C.vips_tracked_get_mem_highwater())
stats.Allocs = int64(C.vips_tracked_get_allocs())
stats.Files = int64(C.vips_tracked_get_files())
}
func startupIfNeeded() error {
if !running {
govipsLog("govips", LogLevelInfo, "libvips was forcibly started automatically, consider calling Startup/Shutdown yourself")
if err := Startup(nil); err != nil {
return err
}
}
return nil
}
// InitTypes initializes caches and figures out which image types are supported
func initTypes() {
once.Do(func() {
cType := C.CString("VipsOperation")
defer freeCString(cType)
for k, v := range ImageTypes {
name := strings.ToLower("VipsForeignLoad" + v)
typeLoaders[name] = k
typeLoaders[name+"buffer"] = k
cFunc := C.CString(v + "load")
//noinspection GoDeferInLoop
defer freeCString(cFunc)
ret := C.vips_type_find(cType, cFunc)
supportedImageTypes[k] = int(ret) != 0
if supportedImageTypes[k] {
govipsLog("govips", LogLevelInfo, fmt.Sprintf("registered image type loader type=%s", v))
}
}
})
}
+26
View File
@@ -0,0 +1,26 @@
// clang-format off
// include order matters
#include <stdlib.h>
#include <glib.h>
#include <vips/vips.h>
// clang-format on
#if (VIPS_MAJOR_VERSION < 8)
error_requires_version_8
#endif
extern void
govipsLoggingHandler(char *log_domain, int log_level, char *message);
static void govips_logging_handler(const gchar *log_domain,
GLogLevelFlags log_level,
const gchar *message, gpointer user_data);
static void null_logging_handler(const gchar *log_domain,
GLogLevelFlags log_level, const gchar *message,
gpointer user_data);
void vips_set_logging_handler(void);
void vips_unset_logging_handler(void);
void vips_default_logging_handler(void);
+724
View File
@@ -0,0 +1,724 @@
package vips
import (
"os"
"path/filepath"
"sync"
)
var (
// ATTRIBUTION:
// The following micro icc profile taken from: https://github.com/saucecontrol/Compact-ICC-Profiles.
// Read more (very interesting): https://photosauce.net/blog/post/making-a-minimal-srgb-icc-profile-part-1-trim-the-fat-abuse-the-spec
sRGBV2MicroICCProfile = []byte{
0x00, 0x00, 0x01, 0xc8, 0x6c, 0x63, 0x6d, 0x73, 0x02, 0x10, 0x00, 0x00,
0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20,
0x07, 0xe2, 0x00, 0x03, 0x00, 0x14, 0x00, 0x09, 0x00, 0x0e, 0x00, 0x1d,
0x61, 0x63, 0x73, 0x70, 0x4d, 0x53, 0x46, 0x54, 0x00, 0x00, 0x00, 0x00,
0x73, 0x61, 0x77, 0x73, 0x63, 0x74, 0x72, 0x6c, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x68, 0x61, 0x6e, 0x64,
0x9d, 0x91, 0x00, 0x3d, 0x40, 0x80, 0xb0, 0x3d, 0x40, 0x74, 0x2c, 0x81,
0x9e, 0xa5, 0x22, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,
0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x5f,
0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x01, 0x0c, 0x00, 0x00, 0x00, 0x0c,
0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, 0x18, 0x00, 0x00, 0x00, 0x14,
0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x2c, 0x00, 0x00, 0x00, 0x14,
0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x14,
0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x54, 0x00, 0x00, 0x00, 0x14,
0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x68, 0x00, 0x00, 0x00, 0x60,
0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x68, 0x00, 0x00, 0x00, 0x60,
0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x68, 0x00, 0x00, 0x00, 0x60,
0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
0x75, 0x52, 0x47, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00,
0x43, 0x43, 0x30, 0x00, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xf3, 0x54, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x16, 0xc9,
0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xa0,
0x00, 0x00, 0x38, 0xf2, 0x00, 0x00, 0x03, 0x8f, 0x58, 0x59, 0x5a, 0x20,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x96, 0x00, 0x00, 0xb7, 0x89,
0x00, 0x00, 0x18, 0xda, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x24, 0xa0, 0x00, 0x00, 0x0f, 0x85, 0x00, 0x00, 0xb6, 0xc4,
0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a,
0x00, 0x00, 0x00, 0x7c, 0x00, 0xf8, 0x01, 0x9c, 0x02, 0x75, 0x03, 0x83,
0x04, 0xc9, 0x06, 0x4e, 0x08, 0x12, 0x0a, 0x18, 0x0c, 0x62, 0x0e, 0xf4,
0x11, 0xcf, 0x14, 0xf6, 0x18, 0x6a, 0x1c, 0x2e, 0x20, 0x43, 0x24, 0xac,
0x29, 0x6a, 0x2e, 0x7e, 0x33, 0xeb, 0x39, 0xb3, 0x3f, 0xd6, 0x46, 0x57,
0x4d, 0x36, 0x54, 0x76, 0x5c, 0x17, 0x64, 0x1d, 0x6c, 0x86, 0x75, 0x56,
0x7e, 0x8d, 0x88, 0x2c, 0x92, 0x36, 0x9c, 0xab, 0xa7, 0x8c, 0xb2, 0xdb,
0xbe, 0x99, 0xca, 0xc7, 0xd7, 0x65, 0xe4, 0x77, 0xf1, 0xf9, 0xff, 0xff,
}
// ATTRIBUTION:
// The following micro icc profile taken from: https://github.com/saucecontrol/Compact-ICC-Profiles.
// Read more (very interesting): https://photosauce.net/blog/post/making-a-minimal-srgb-icc-profile-part-1-trim-the-fat-abuse-the-spec
sGrayV2MicroICCProfile = []byte{
0x00, 0x00, 0x01, 0x50, 0x6c, 0x63, 0x6d, 0x73, 0x02, 0x10, 0x00, 0x00,
0x6d, 0x6e, 0x74, 0x72, 0x47, 0x52, 0x41, 0x59, 0x58, 0x59, 0x5a, 0x20,
0x07, 0xe2, 0x00, 0x03, 0x00, 0x14, 0x00, 0x09, 0x00, 0x0e, 0x00, 0x1d,
0x61, 0x63, 0x73, 0x70, 0x4d, 0x53, 0x46, 0x54, 0x00, 0x00, 0x00, 0x00,
0x73, 0x61, 0x77, 0x73, 0x63, 0x74, 0x72, 0x6c, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x68, 0x61, 0x6e, 0x64,
0x05, 0xd2, 0x02, 0xa7, 0xf9, 0xdd, 0x47, 0x94, 0xc7, 0x4f, 0x4c, 0x5f,
0x26, 0x82, 0x3a, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x5f,
0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x0c,
0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x00, 0xdc, 0x00, 0x00, 0x00, 0x14,
0x6b, 0x54, 0x52, 0x43, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x60,
0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
0x75, 0x47, 0x72, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00,
0x43, 0x43, 0x30, 0x00, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xf3, 0x54, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x16, 0xc9,
0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a,
0x00, 0x00, 0x00, 0x7c, 0x00, 0xf8, 0x01, 0x9c, 0x02, 0x75, 0x03, 0x83,
0x04, 0xc9, 0x06, 0x4e, 0x08, 0x12, 0x0a, 0x18, 0x0c, 0x62, 0x0e, 0xf4,
0x11, 0xcf, 0x14, 0xf6, 0x18, 0x6a, 0x1c, 0x2e, 0x20, 0x43, 0x24, 0xac,
0x29, 0x6a, 0x2e, 0x7e, 0x33, 0xeb, 0x39, 0xb3, 0x3f, 0xd6, 0x46, 0x57,
0x4d, 0x36, 0x54, 0x76, 0x5c, 0x17, 0x64, 0x1d, 0x6c, 0x86, 0x75, 0x56,
0x7e, 0x8d, 0x88, 0x2c, 0x92, 0x36, 0x9c, 0xab, 0xa7, 0x8c, 0xb2, 0xdb,
0xbe, 0x99, 0xca, 0xc7, 0xd7, 0x65, 0xe4, 0x77, 0xf1, 0xf9, 0xff, 0xff,
}
sRGBIEC6196621ICCProfile = []byte{
0x00, 0x00, 0x0b, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20,
0x07, 0xd9, 0x00, 0x03, 0x00, 0x1b, 0x00, 0x15, 0x00, 0x24, 0x00, 0x1f,
0x61, 0x63, 0x73, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x00, 0x00, 0x00, 0x00,
0x29, 0xf8, 0x3d, 0xde, 0xaf, 0xf2, 0x55, 0xae, 0x78, 0x42, 0xfa, 0xe4,
0xca, 0x83, 0x39, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x01, 0x44, 0x00, 0x00, 0x00, 0x79,
0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x14,
0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xd4, 0x00, 0x00, 0x08, 0x0c,
0x64, 0x6d, 0x64, 0x64, 0x00, 0x00, 0x09, 0xe0, 0x00, 0x00, 0x00, 0x88,
0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x0a, 0x68, 0x00, 0x00, 0x00, 0x14,
0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xd4, 0x00, 0x00, 0x08, 0x0c,
0x6c, 0x75, 0x6d, 0x69, 0x00, 0x00, 0x0a, 0x7c, 0x00, 0x00, 0x00, 0x14,
0x6d, 0x65, 0x61, 0x73, 0x00, 0x00, 0x0a, 0x90, 0x00, 0x00, 0x00, 0x24,
0x62, 0x6b, 0x70, 0x74, 0x00, 0x00, 0x0a, 0xb4, 0x00, 0x00, 0x00, 0x14,
0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x0a, 0xc8, 0x00, 0x00, 0x00, 0x14,
0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xd4, 0x00, 0x00, 0x08, 0x0c,
0x74, 0x65, 0x63, 0x68, 0x00, 0x00, 0x0a, 0xdc, 0x00, 0x00, 0x00, 0x0c,
0x76, 0x75, 0x65, 0x64, 0x00, 0x00, 0x0a, 0xe8, 0x00, 0x00, 0x00, 0x87,
0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x0b, 0x70, 0x00, 0x00, 0x00, 0x14,
0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x0b, 0x84, 0x00, 0x00, 0x00, 0x37,
0x63, 0x68, 0x61, 0x64, 0x00, 0x00, 0x0b, 0xbc, 0x00, 0x00, 0x00, 0x2c,
0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f,
0x73, 0x52, 0x47, 0x42, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, 0x39, 0x36,
0x36, 0x2d, 0x32, 0x2d, 0x31, 0x20, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x20,
0x73, 0x63, 0x61, 0x6c, 0x65, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x24, 0xa0, 0x00, 0x00, 0x0f, 0x84, 0x00, 0x00, 0xb6, 0xcf,
0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
0x00, 0x00, 0x00, 0x05, 0x00, 0x0a, 0x00, 0x0f, 0x00, 0x14, 0x00, 0x19,
0x00, 0x1e, 0x00, 0x23, 0x00, 0x28, 0x00, 0x2d, 0x00, 0x32, 0x00, 0x37,
0x00, 0x3b, 0x00, 0x40, 0x00, 0x45, 0x00, 0x4a, 0x00, 0x4f, 0x00, 0x54,
0x00, 0x59, 0x00, 0x5e, 0x00, 0x63, 0x00, 0x68, 0x00, 0x6d, 0x00, 0x72,
0x00, 0x77, 0x00, 0x7c, 0x00, 0x81, 0x00, 0x86, 0x00, 0x8b, 0x00, 0x90,
0x00, 0x95, 0x00, 0x9a, 0x00, 0x9f, 0x00, 0xa4, 0x00, 0xa9, 0x00, 0xae,
0x00, 0xb2, 0x00, 0xb7, 0x00, 0xbc, 0x00, 0xc1, 0x00, 0xc6, 0x00, 0xcb,
0x00, 0xd0, 0x00, 0xd5, 0x00, 0xdb, 0x00, 0xe0, 0x00, 0xe5, 0x00, 0xeb,
0x00, 0xf0, 0x00, 0xf6, 0x00, 0xfb, 0x01, 0x01, 0x01, 0x07, 0x01, 0x0d,
0x01, 0x13, 0x01, 0x19, 0x01, 0x1f, 0x01, 0x25, 0x01, 0x2b, 0x01, 0x32,
0x01, 0x38, 0x01, 0x3e, 0x01, 0x45, 0x01, 0x4c, 0x01, 0x52, 0x01, 0x59,
0x01, 0x60, 0x01, 0x67, 0x01, 0x6e, 0x01, 0x75, 0x01, 0x7c, 0x01, 0x83,
0x01, 0x8b, 0x01, 0x92, 0x01, 0x9a, 0x01, 0xa1, 0x01, 0xa9, 0x01, 0xb1,
0x01, 0xb9, 0x01, 0xc1, 0x01, 0xc9, 0x01, 0xd1, 0x01, 0xd9, 0x01, 0xe1,
0x01, 0xe9, 0x01, 0xf2, 0x01, 0xfa, 0x02, 0x03, 0x02, 0x0c, 0x02, 0x14,
0x02, 0x1d, 0x02, 0x26, 0x02, 0x2f, 0x02, 0x38, 0x02, 0x41, 0x02, 0x4b,
0x02, 0x54, 0x02, 0x5d, 0x02, 0x67, 0x02, 0x71, 0x02, 0x7a, 0x02, 0x84,
0x02, 0x8e, 0x02, 0x98, 0x02, 0xa2, 0x02, 0xac, 0x02, 0xb6, 0x02, 0xc1,
0x02, 0xcb, 0x02, 0xd5, 0x02, 0xe0, 0x02, 0xeb, 0x02, 0xf5, 0x03, 0x00,
0x03, 0x0b, 0x03, 0x16, 0x03, 0x21, 0x03, 0x2d, 0x03, 0x38, 0x03, 0x43,
0x03, 0x4f, 0x03, 0x5a, 0x03, 0x66, 0x03, 0x72, 0x03, 0x7e, 0x03, 0x8a,
0x03, 0x96, 0x03, 0xa2, 0x03, 0xae, 0x03, 0xba, 0x03, 0xc7, 0x03, 0xd3,
0x03, 0xe0, 0x03, 0xec, 0x03, 0xf9, 0x04, 0x06, 0x04, 0x13, 0x04, 0x20,
0x04, 0x2d, 0x04, 0x3b, 0x04, 0x48, 0x04, 0x55, 0x04, 0x63, 0x04, 0x71,
0x04, 0x7e, 0x04, 0x8c, 0x04, 0x9a, 0x04, 0xa8, 0x04, 0xb6, 0x04, 0xc4,
0x04, 0xd3, 0x04, 0xe1, 0x04, 0xf0, 0x04, 0xfe, 0x05, 0x0d, 0x05, 0x1c,
0x05, 0x2b, 0x05, 0x3a, 0x05, 0x49, 0x05, 0x58, 0x05, 0x67, 0x05, 0x77,
0x05, 0x86, 0x05, 0x96, 0x05, 0xa6, 0x05, 0xb5, 0x05, 0xc5, 0x05, 0xd5,
0x05, 0xe5, 0x05, 0xf6, 0x06, 0x06, 0x06, 0x16, 0x06, 0x27, 0x06, 0x37,
0x06, 0x48, 0x06, 0x59, 0x06, 0x6a, 0x06, 0x7b, 0x06, 0x8c, 0x06, 0x9d,
0x06, 0xaf, 0x06, 0xc0, 0x06, 0xd1, 0x06, 0xe3, 0x06, 0xf5, 0x07, 0x07,
0x07, 0x19, 0x07, 0x2b, 0x07, 0x3d, 0x07, 0x4f, 0x07, 0x61, 0x07, 0x74,
0x07, 0x86, 0x07, 0x99, 0x07, 0xac, 0x07, 0xbf, 0x07, 0xd2, 0x07, 0xe5,
0x07, 0xf8, 0x08, 0x0b, 0x08, 0x1f, 0x08, 0x32, 0x08, 0x46, 0x08, 0x5a,
0x08, 0x6e, 0x08, 0x82, 0x08, 0x96, 0x08, 0xaa, 0x08, 0xbe, 0x08, 0xd2,
0x08, 0xe7, 0x08, 0xfb, 0x09, 0x10, 0x09, 0x25, 0x09, 0x3a, 0x09, 0x4f,
0x09, 0x64, 0x09, 0x79, 0x09, 0x8f, 0x09, 0xa4, 0x09, 0xba, 0x09, 0xcf,
0x09, 0xe5, 0x09, 0xfb, 0x0a, 0x11, 0x0a, 0x27, 0x0a, 0x3d, 0x0a, 0x54,
0x0a, 0x6a, 0x0a, 0x81, 0x0a, 0x98, 0x0a, 0xae, 0x0a, 0xc5, 0x0a, 0xdc,
0x0a, 0xf3, 0x0b, 0x0b, 0x0b, 0x22, 0x0b, 0x39, 0x0b, 0x51, 0x0b, 0x69,
0x0b, 0x80, 0x0b, 0x98, 0x0b, 0xb0, 0x0b, 0xc8, 0x0b, 0xe1, 0x0b, 0xf9,
0x0c, 0x12, 0x0c, 0x2a, 0x0c, 0x43, 0x0c, 0x5c, 0x0c, 0x75, 0x0c, 0x8e,
0x0c, 0xa7, 0x0c, 0xc0, 0x0c, 0xd9, 0x0c, 0xf3, 0x0d, 0x0d, 0x0d, 0x26,
0x0d, 0x40, 0x0d, 0x5a, 0x0d, 0x74, 0x0d, 0x8e, 0x0d, 0xa9, 0x0d, 0xc3,
0x0d, 0xde, 0x0d, 0xf8, 0x0e, 0x13, 0x0e, 0x2e, 0x0e, 0x49, 0x0e, 0x64,
0x0e, 0x7f, 0x0e, 0x9b, 0x0e, 0xb6, 0x0e, 0xd2, 0x0e, 0xee, 0x0f, 0x09,
0x0f, 0x25, 0x0f, 0x41, 0x0f, 0x5e, 0x0f, 0x7a, 0x0f, 0x96, 0x0f, 0xb3,
0x0f, 0xcf, 0x0f, 0xec, 0x10, 0x09, 0x10, 0x26, 0x10, 0x43, 0x10, 0x61,
0x10, 0x7e, 0x10, 0x9b, 0x10, 0xb9, 0x10, 0xd7, 0x10, 0xf5, 0x11, 0x13,
0x11, 0x31, 0x11, 0x4f, 0x11, 0x6d, 0x11, 0x8c, 0x11, 0xaa, 0x11, 0xc9,
0x11, 0xe8, 0x12, 0x07, 0x12, 0x26, 0x12, 0x45, 0x12, 0x64, 0x12, 0x84,
0x12, 0xa3, 0x12, 0xc3, 0x12, 0xe3, 0x13, 0x03, 0x13, 0x23, 0x13, 0x43,
0x13, 0x63, 0x13, 0x83, 0x13, 0xa4, 0x13, 0xc5, 0x13, 0xe5, 0x14, 0x06,
0x14, 0x27, 0x14, 0x49, 0x14, 0x6a, 0x14, 0x8b, 0x14, 0xad, 0x14, 0xce,
0x14, 0xf0, 0x15, 0x12, 0x15, 0x34, 0x15, 0x56, 0x15, 0x78, 0x15, 0x9b,
0x15, 0xbd, 0x15, 0xe0, 0x16, 0x03, 0x16, 0x26, 0x16, 0x49, 0x16, 0x6c,
0x16, 0x8f, 0x16, 0xb2, 0x16, 0xd6, 0x16, 0xfa, 0x17, 0x1d, 0x17, 0x41,
0x17, 0x65, 0x17, 0x89, 0x17, 0xae, 0x17, 0xd2, 0x17, 0xf7, 0x18, 0x1b,
0x18, 0x40, 0x18, 0x65, 0x18, 0x8a, 0x18, 0xaf, 0x18, 0xd5, 0x18, 0xfa,
0x19, 0x20, 0x19, 0x45, 0x19, 0x6b, 0x19, 0x91, 0x19, 0xb7, 0x19, 0xdd,
0x1a, 0x04, 0x1a, 0x2a, 0x1a, 0x51, 0x1a, 0x77, 0x1a, 0x9e, 0x1a, 0xc5,
0x1a, 0xec, 0x1b, 0x14, 0x1b, 0x3b, 0x1b, 0x63, 0x1b, 0x8a, 0x1b, 0xb2,
0x1b, 0xda, 0x1c, 0x02, 0x1c, 0x2a, 0x1c, 0x52, 0x1c, 0x7b, 0x1c, 0xa3,
0x1c, 0xcc, 0x1c, 0xf5, 0x1d, 0x1e, 0x1d, 0x47, 0x1d, 0x70, 0x1d, 0x99,
0x1d, 0xc3, 0x1d, 0xec, 0x1e, 0x16, 0x1e, 0x40, 0x1e, 0x6a, 0x1e, 0x94,
0x1e, 0xbe, 0x1e, 0xe9, 0x1f, 0x13, 0x1f, 0x3e, 0x1f, 0x69, 0x1f, 0x94,
0x1f, 0xbf, 0x1f, 0xea, 0x20, 0x15, 0x20, 0x41, 0x20, 0x6c, 0x20, 0x98,
0x20, 0xc4, 0x20, 0xf0, 0x21, 0x1c, 0x21, 0x48, 0x21, 0x75, 0x21, 0xa1,
0x21, 0xce, 0x21, 0xfb, 0x22, 0x27, 0x22, 0x55, 0x22, 0x82, 0x22, 0xaf,
0x22, 0xdd, 0x23, 0x0a, 0x23, 0x38, 0x23, 0x66, 0x23, 0x94, 0x23, 0xc2,
0x23, 0xf0, 0x24, 0x1f, 0x24, 0x4d, 0x24, 0x7c, 0x24, 0xab, 0x24, 0xda,
0x25, 0x09, 0x25, 0x38, 0x25, 0x68, 0x25, 0x97, 0x25, 0xc7, 0x25, 0xf7,
0x26, 0x27, 0x26, 0x57, 0x26, 0x87, 0x26, 0xb7, 0x26, 0xe8, 0x27, 0x18,
0x27, 0x49, 0x27, 0x7a, 0x27, 0xab, 0x27, 0xdc, 0x28, 0x0d, 0x28, 0x3f,
0x28, 0x71, 0x28, 0xa2, 0x28, 0xd4, 0x29, 0x06, 0x29, 0x38, 0x29, 0x6b,
0x29, 0x9d, 0x29, 0xd0, 0x2a, 0x02, 0x2a, 0x35, 0x2a, 0x68, 0x2a, 0x9b,
0x2a, 0xcf, 0x2b, 0x02, 0x2b, 0x36, 0x2b, 0x69, 0x2b, 0x9d, 0x2b, 0xd1,
0x2c, 0x05, 0x2c, 0x39, 0x2c, 0x6e, 0x2c, 0xa2, 0x2c, 0xd7, 0x2d, 0x0c,
0x2d, 0x41, 0x2d, 0x76, 0x2d, 0xab, 0x2d, 0xe1, 0x2e, 0x16, 0x2e, 0x4c,
0x2e, 0x82, 0x2e, 0xb7, 0x2e, 0xee, 0x2f, 0x24, 0x2f, 0x5a, 0x2f, 0x91,
0x2f, 0xc7, 0x2f, 0xfe, 0x30, 0x35, 0x30, 0x6c, 0x30, 0xa4, 0x30, 0xdb,
0x31, 0x12, 0x31, 0x4a, 0x31, 0x82, 0x31, 0xba, 0x31, 0xf2, 0x32, 0x2a,
0x32, 0x63, 0x32, 0x9b, 0x32, 0xd4, 0x33, 0x0d, 0x33, 0x46, 0x33, 0x7f,
0x33, 0xb8, 0x33, 0xf1, 0x34, 0x2b, 0x34, 0x65, 0x34, 0x9e, 0x34, 0xd8,
0x35, 0x13, 0x35, 0x4d, 0x35, 0x87, 0x35, 0xc2, 0x35, 0xfd, 0x36, 0x37,
0x36, 0x72, 0x36, 0xae, 0x36, 0xe9, 0x37, 0x24, 0x37, 0x60, 0x37, 0x9c,
0x37, 0xd7, 0x38, 0x14, 0x38, 0x50, 0x38, 0x8c, 0x38, 0xc8, 0x39, 0x05,
0x39, 0x42, 0x39, 0x7f, 0x39, 0xbc, 0x39, 0xf9, 0x3a, 0x36, 0x3a, 0x74,
0x3a, 0xb2, 0x3a, 0xef, 0x3b, 0x2d, 0x3b, 0x6b, 0x3b, 0xaa, 0x3b, 0xe8,
0x3c, 0x27, 0x3c, 0x65, 0x3c, 0xa4, 0x3c, 0xe3, 0x3d, 0x22, 0x3d, 0x61,
0x3d, 0xa1, 0x3d, 0xe0, 0x3e, 0x20, 0x3e, 0x60, 0x3e, 0xa0, 0x3e, 0xe0,
0x3f, 0x21, 0x3f, 0x61, 0x3f, 0xa2, 0x3f, 0xe2, 0x40, 0x23, 0x40, 0x64,
0x40, 0xa6, 0x40, 0xe7, 0x41, 0x29, 0x41, 0x6a, 0x41, 0xac, 0x41, 0xee,
0x42, 0x30, 0x42, 0x72, 0x42, 0xb5, 0x42, 0xf7, 0x43, 0x3a, 0x43, 0x7d,
0x43, 0xc0, 0x44, 0x03, 0x44, 0x47, 0x44, 0x8a, 0x44, 0xce, 0x45, 0x12,
0x45, 0x55, 0x45, 0x9a, 0x45, 0xde, 0x46, 0x22, 0x46, 0x67, 0x46, 0xab,
0x46, 0xf0, 0x47, 0x35, 0x47, 0x7b, 0x47, 0xc0, 0x48, 0x05, 0x48, 0x4b,
0x48, 0x91, 0x48, 0xd7, 0x49, 0x1d, 0x49, 0x63, 0x49, 0xa9, 0x49, 0xf0,
0x4a, 0x37, 0x4a, 0x7d, 0x4a, 0xc4, 0x4b, 0x0c, 0x4b, 0x53, 0x4b, 0x9a,
0x4b, 0xe2, 0x4c, 0x2a, 0x4c, 0x72, 0x4c, 0xba, 0x4d, 0x02, 0x4d, 0x4a,
0x4d, 0x93, 0x4d, 0xdc, 0x4e, 0x25, 0x4e, 0x6e, 0x4e, 0xb7, 0x4f, 0x00,
0x4f, 0x49, 0x4f, 0x93, 0x4f, 0xdd, 0x50, 0x27, 0x50, 0x71, 0x50, 0xbb,
0x51, 0x06, 0x51, 0x50, 0x51, 0x9b, 0x51, 0xe6, 0x52, 0x31, 0x52, 0x7c,
0x52, 0xc7, 0x53, 0x13, 0x53, 0x5f, 0x53, 0xaa, 0x53, 0xf6, 0x54, 0x42,
0x54, 0x8f, 0x54, 0xdb, 0x55, 0x28, 0x55, 0x75, 0x55, 0xc2, 0x56, 0x0f,
0x56, 0x5c, 0x56, 0xa9, 0x56, 0xf7, 0x57, 0x44, 0x57, 0x92, 0x57, 0xe0,
0x58, 0x2f, 0x58, 0x7d, 0x58, 0xcb, 0x59, 0x1a, 0x59, 0x69, 0x59, 0xb8,
0x5a, 0x07, 0x5a, 0x56, 0x5a, 0xa6, 0x5a, 0xf5, 0x5b, 0x45, 0x5b, 0x95,
0x5b, 0xe5, 0x5c, 0x35, 0x5c, 0x86, 0x5c, 0xd6, 0x5d, 0x27, 0x5d, 0x78,
0x5d, 0xc9, 0x5e, 0x1a, 0x5e, 0x6c, 0x5e, 0xbd, 0x5f, 0x0f, 0x5f, 0x61,
0x5f, 0xb3, 0x60, 0x05, 0x60, 0x57, 0x60, 0xaa, 0x60, 0xfc, 0x61, 0x4f,
0x61, 0xa2, 0x61, 0xf5, 0x62, 0x49, 0x62, 0x9c, 0x62, 0xf0, 0x63, 0x43,
0x63, 0x97, 0x63, 0xeb, 0x64, 0x40, 0x64, 0x94, 0x64, 0xe9, 0x65, 0x3d,
0x65, 0x92, 0x65, 0xe7, 0x66, 0x3d, 0x66, 0x92, 0x66, 0xe8, 0x67, 0x3d,
0x67, 0x93, 0x67, 0xe9, 0x68, 0x3f, 0x68, 0x96, 0x68, 0xec, 0x69, 0x43,
0x69, 0x9a, 0x69, 0xf1, 0x6a, 0x48, 0x6a, 0x9f, 0x6a, 0xf7, 0x6b, 0x4f,
0x6b, 0xa7, 0x6b, 0xff, 0x6c, 0x57, 0x6c, 0xaf, 0x6d, 0x08, 0x6d, 0x60,
0x6d, 0xb9, 0x6e, 0x12, 0x6e, 0x6b, 0x6e, 0xc4, 0x6f, 0x1e, 0x6f, 0x78,
0x6f, 0xd1, 0x70, 0x2b, 0x70, 0x86, 0x70, 0xe0, 0x71, 0x3a, 0x71, 0x95,
0x71, 0xf0, 0x72, 0x4b, 0x72, 0xa6, 0x73, 0x01, 0x73, 0x5d, 0x73, 0xb8,
0x74, 0x14, 0x74, 0x70, 0x74, 0xcc, 0x75, 0x28, 0x75, 0x85, 0x75, 0xe1,
0x76, 0x3e, 0x76, 0x9b, 0x76, 0xf8, 0x77, 0x56, 0x77, 0xb3, 0x78, 0x11,
0x78, 0x6e, 0x78, 0xcc, 0x79, 0x2a, 0x79, 0x89, 0x79, 0xe7, 0x7a, 0x46,
0x7a, 0xa5, 0x7b, 0x04, 0x7b, 0x63, 0x7b, 0xc2, 0x7c, 0x21, 0x7c, 0x81,
0x7c, 0xe1, 0x7d, 0x41, 0x7d, 0xa1, 0x7e, 0x01, 0x7e, 0x62, 0x7e, 0xc2,
0x7f, 0x23, 0x7f, 0x84, 0x7f, 0xe5, 0x80, 0x47, 0x80, 0xa8, 0x81, 0x0a,
0x81, 0x6b, 0x81, 0xcd, 0x82, 0x30, 0x82, 0x92, 0x82, 0xf4, 0x83, 0x57,
0x83, 0xba, 0x84, 0x1d, 0x84, 0x80, 0x84, 0xe3, 0x85, 0x47, 0x85, 0xab,
0x86, 0x0e, 0x86, 0x72, 0x86, 0xd7, 0x87, 0x3b, 0x87, 0x9f, 0x88, 0x04,
0x88, 0x69, 0x88, 0xce, 0x89, 0x33, 0x89, 0x99, 0x89, 0xfe, 0x8a, 0x64,
0x8a, 0xca, 0x8b, 0x30, 0x8b, 0x96, 0x8b, 0xfc, 0x8c, 0x63, 0x8c, 0xca,
0x8d, 0x31, 0x8d, 0x98, 0x8d, 0xff, 0x8e, 0x66, 0x8e, 0xce, 0x8f, 0x36,
0x8f, 0x9e, 0x90, 0x06, 0x90, 0x6e, 0x90, 0xd6, 0x91, 0x3f, 0x91, 0xa8,
0x92, 0x11, 0x92, 0x7a, 0x92, 0xe3, 0x93, 0x4d, 0x93, 0xb6, 0x94, 0x20,
0x94, 0x8a, 0x94, 0xf4, 0x95, 0x5f, 0x95, 0xc9, 0x96, 0x34, 0x96, 0x9f,
0x97, 0x0a, 0x97, 0x75, 0x97, 0xe0, 0x98, 0x4c, 0x98, 0xb8, 0x99, 0x24,
0x99, 0x90, 0x99, 0xfc, 0x9a, 0x68, 0x9a, 0xd5, 0x9b, 0x42, 0x9b, 0xaf,
0x9c, 0x1c, 0x9c, 0x89, 0x9c, 0xf7, 0x9d, 0x64, 0x9d, 0xd2, 0x9e, 0x40,
0x9e, 0xae, 0x9f, 0x1d, 0x9f, 0x8b, 0x9f, 0xfa, 0xa0, 0x69, 0xa0, 0xd8,
0xa1, 0x47, 0xa1, 0xb6, 0xa2, 0x26, 0xa2, 0x96, 0xa3, 0x06, 0xa3, 0x76,
0xa3, 0xe6, 0xa4, 0x56, 0xa4, 0xc7, 0xa5, 0x38, 0xa5, 0xa9, 0xa6, 0x1a,
0xa6, 0x8b, 0xa6, 0xfd, 0xa7, 0x6e, 0xa7, 0xe0, 0xa8, 0x52, 0xa8, 0xc4,
0xa9, 0x37, 0xa9, 0xa9, 0xaa, 0x1c, 0xaa, 0x8f, 0xab, 0x02, 0xab, 0x75,
0xab, 0xe9, 0xac, 0x5c, 0xac, 0xd0, 0xad, 0x44, 0xad, 0xb8, 0xae, 0x2d,
0xae, 0xa1, 0xaf, 0x16, 0xaf, 0x8b, 0xb0, 0x00, 0xb0, 0x75, 0xb0, 0xea,
0xb1, 0x60, 0xb1, 0xd6, 0xb2, 0x4b, 0xb2, 0xc2, 0xb3, 0x38, 0xb3, 0xae,
0xb4, 0x25, 0xb4, 0x9c, 0xb5, 0x13, 0xb5, 0x8a, 0xb6, 0x01, 0xb6, 0x79,
0xb6, 0xf0, 0xb7, 0x68, 0xb7, 0xe0, 0xb8, 0x59, 0xb8, 0xd1, 0xb9, 0x4a,
0xb9, 0xc2, 0xba, 0x3b, 0xba, 0xb5, 0xbb, 0x2e, 0xbb, 0xa7, 0xbc, 0x21,
0xbc, 0x9b, 0xbd, 0x15, 0xbd, 0x8f, 0xbe, 0x0a, 0xbe, 0x84, 0xbe, 0xff,
0xbf, 0x7a, 0xbf, 0xf5, 0xc0, 0x70, 0xc0, 0xec, 0xc1, 0x67, 0xc1, 0xe3,
0xc2, 0x5f, 0xc2, 0xdb, 0xc3, 0x58, 0xc3, 0xd4, 0xc4, 0x51, 0xc4, 0xce,
0xc5, 0x4b, 0xc5, 0xc8, 0xc6, 0x46, 0xc6, 0xc3, 0xc7, 0x41, 0xc7, 0xbf,
0xc8, 0x3d, 0xc8, 0xbc, 0xc9, 0x3a, 0xc9, 0xb9, 0xca, 0x38, 0xca, 0xb7,
0xcb, 0x36, 0xcb, 0xb6, 0xcc, 0x35, 0xcc, 0xb5, 0xcd, 0x35, 0xcd, 0xb5,
0xce, 0x36, 0xce, 0xb6, 0xcf, 0x37, 0xcf, 0xb8, 0xd0, 0x39, 0xd0, 0xba,
0xd1, 0x3c, 0xd1, 0xbe, 0xd2, 0x3f, 0xd2, 0xc1, 0xd3, 0x44, 0xd3, 0xc6,
0xd4, 0x49, 0xd4, 0xcb, 0xd5, 0x4e, 0xd5, 0xd1, 0xd6, 0x55, 0xd6, 0xd8,
0xd7, 0x5c, 0xd7, 0xe0, 0xd8, 0x64, 0xd8, 0xe8, 0xd9, 0x6c, 0xd9, 0xf1,
0xda, 0x76, 0xda, 0xfb, 0xdb, 0x80, 0xdc, 0x05, 0xdc, 0x8a, 0xdd, 0x10,
0xdd, 0x96, 0xde, 0x1c, 0xde, 0xa2, 0xdf, 0x29, 0xdf, 0xaf, 0xe0, 0x36,
0xe0, 0xbd, 0xe1, 0x44, 0xe1, 0xcc, 0xe2, 0x53, 0xe2, 0xdb, 0xe3, 0x63,
0xe3, 0xeb, 0xe4, 0x73, 0xe4, 0xfc, 0xe5, 0x84, 0xe6, 0x0d, 0xe6, 0x96,
0xe7, 0x1f, 0xe7, 0xa9, 0xe8, 0x32, 0xe8, 0xbc, 0xe9, 0x46, 0xe9, 0xd0,
0xea, 0x5b, 0xea, 0xe5, 0xeb, 0x70, 0xeb, 0xfb, 0xec, 0x86, 0xed, 0x11,
0xed, 0x9c, 0xee, 0x28, 0xee, 0xb4, 0xef, 0x40, 0xef, 0xcc, 0xf0, 0x58,
0xf0, 0xe5, 0xf1, 0x72, 0xf1, 0xff, 0xf2, 0x8c, 0xf3, 0x19, 0xf3, 0xa7,
0xf4, 0x34, 0xf4, 0xc2, 0xf5, 0x50, 0xf5, 0xde, 0xf6, 0x6d, 0xf6, 0xfb,
0xf7, 0x8a, 0xf8, 0x19, 0xf8, 0xa8, 0xf9, 0x38, 0xf9, 0xc7, 0xfa, 0x57,
0xfa, 0xe7, 0xfb, 0x77, 0xfc, 0x07, 0xfc, 0x98, 0xfd, 0x29, 0xfd, 0xba,
0xfe, 0x4b, 0xfe, 0xdc, 0xff, 0x6d, 0xff, 0xff, 0x64, 0x65, 0x73, 0x63,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2e, 0x49, 0x45, 0x43, 0x20,
0x36, 0x31, 0x39, 0x36, 0x36, 0x2d, 0x32, 0x2d, 0x31, 0x20, 0x44, 0x65,
0x66, 0x61, 0x75, 0x6c, 0x74, 0x20, 0x52, 0x47, 0x42, 0x20, 0x43, 0x6f,
0x6c, 0x6f, 0x75, 0x72, 0x20, 0x53, 0x70, 0x61, 0x63, 0x65, 0x20, 0x2d,
0x20, 0x73, 0x52, 0x47, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x99,
0x00, 0x00, 0xb7, 0x85, 0x00, 0x00, 0x18, 0xda, 0x58, 0x59, 0x5a, 0x20,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x6d, 0x65, 0x61, 0x73, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x03, 0x16, 0x00, 0x00, 0x03, 0x33, 0x00, 0x00, 0x02, 0xa4,
0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xa2,
0x00, 0x00, 0x38, 0xf5, 0x00, 0x00, 0x03, 0x90, 0x73, 0x69, 0x67, 0x20,
0x00, 0x00, 0x00, 0x00, 0x43, 0x52, 0x54, 0x20, 0x64, 0x65, 0x73, 0x63,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x52, 0x65, 0x66, 0x65,
0x72, 0x65, 0x6e, 0x63, 0x65, 0x20, 0x56, 0x69, 0x65, 0x77, 0x69, 0x6e,
0x67, 0x20, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20,
0x69, 0x6e, 0x20, 0x49, 0x45, 0x43, 0x20, 0x36, 0x31, 0x39, 0x36, 0x36,
0x2d, 0x32, 0x2d, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x74, 0x65, 0x78, 0x74,
0x00, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68,
0x74, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x61, 0x6c, 0x20, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x43, 0x6f,
0x6e, 0x73, 0x6f, 0x72, 0x74, 0x69, 0x75, 0x6d, 0x2c, 0x20, 0x32, 0x30,
0x30, 0x39, 0x00, 0x00, 0x73, 0x66, 0x33, 0x32, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x0c, 0x44, 0x00, 0x00, 0x05, 0xdf, 0xff, 0xff, 0xf3, 0x26,
0x00, 0x00, 0x07, 0x94, 0x00, 0x00, 0xfd, 0x8f, 0xff, 0xff, 0xfb, 0xa1,
0xff, 0xff, 0xfd, 0xa2, 0x00, 0x00, 0x03, 0xdb, 0x00, 0x00, 0xc0, 0x75,
}
genericGrayGamma22ICCProfile = []byte{
0x00, 0x00, 0x0e, 0x04, 0x61, 0x70, 0x70, 0x6c, 0x02, 0x00, 0x00, 0x00,
0x6d, 0x6e, 0x74, 0x72, 0x47, 0x52, 0x41, 0x59, 0x58, 0x59, 0x5a, 0x20,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x61, 0x63, 0x73, 0x70, 0x41, 0x50, 0x50, 0x4c, 0x00, 0x00, 0x00, 0x00,
0x6e, 0x6f, 0x6e, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x61, 0x70, 0x70, 0x6c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
0x6b, 0x54, 0x52, 0x43, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x08, 0x0c,
0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x08, 0xcc, 0x00, 0x00, 0x00, 0x14,
0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x08, 0xe0, 0x00, 0x00, 0x00, 0x23,
0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x09, 0x04, 0x00, 0x00, 0x00, 0x79,
0x64, 0x73, 0x63, 0x6d, 0x00, 0x00, 0x09, 0x80, 0x00, 0x00, 0x04, 0x82,
0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
0x00, 0x00, 0x00, 0x05, 0x00, 0x0a, 0x00, 0x0f, 0x00, 0x14, 0x00, 0x19,
0x00, 0x1e, 0x00, 0x23, 0x00, 0x28, 0x00, 0x2d, 0x00, 0x32, 0x00, 0x37,
0x00, 0x3b, 0x00, 0x40, 0x00, 0x45, 0x00, 0x4a, 0x00, 0x4f, 0x00, 0x54,
0x00, 0x59, 0x00, 0x5e, 0x00, 0x63, 0x00, 0x68, 0x00, 0x6d, 0x00, 0x72,
0x00, 0x77, 0x00, 0x7c, 0x00, 0x81, 0x00, 0x86, 0x00, 0x8b, 0x00, 0x90,
0x00, 0x95, 0x00, 0x9a, 0x00, 0x9f, 0x00, 0xa4, 0x00, 0xa9, 0x00, 0xae,
0x00, 0xb2, 0x00, 0xb7, 0x00, 0xbc, 0x00, 0xc1, 0x00, 0xc6, 0x00, 0xcb,
0x00, 0xd0, 0x00, 0xd5, 0x00, 0xdb, 0x00, 0xe0, 0x00, 0xe5, 0x00, 0xeb,
0x00, 0xf0, 0x00, 0xf6, 0x00, 0xfb, 0x01, 0x01, 0x01, 0x07, 0x01, 0x0d,
0x01, 0x13, 0x01, 0x19, 0x01, 0x1f, 0x01, 0x25, 0x01, 0x2b, 0x01, 0x32,
0x01, 0x38, 0x01, 0x3e, 0x01, 0x45, 0x01, 0x4c, 0x01, 0x52, 0x01, 0x59,
0x01, 0x60, 0x01, 0x67, 0x01, 0x6e, 0x01, 0x75, 0x01, 0x7c, 0x01, 0x83,
0x01, 0x8b, 0x01, 0x92, 0x01, 0x9a, 0x01, 0xa1, 0x01, 0xa9, 0x01, 0xb1,
0x01, 0xb9, 0x01, 0xc1, 0x01, 0xc9, 0x01, 0xd1, 0x01, 0xd9, 0x01, 0xe1,
0x01, 0xe9, 0x01, 0xf2, 0x01, 0xfa, 0x02, 0x03, 0x02, 0x0c, 0x02, 0x14,
0x02, 0x1d, 0x02, 0x26, 0x02, 0x2f, 0x02, 0x38, 0x02, 0x41, 0x02, 0x4b,
0x02, 0x54, 0x02, 0x5d, 0x02, 0x67, 0x02, 0x71, 0x02, 0x7a, 0x02, 0x84,
0x02, 0x8e, 0x02, 0x98, 0x02, 0xa2, 0x02, 0xac, 0x02, 0xb6, 0x02, 0xc1,
0x02, 0xcb, 0x02, 0xd5, 0x02, 0xe0, 0x02, 0xeb, 0x02, 0xf5, 0x03, 0x00,
0x03, 0x0b, 0x03, 0x16, 0x03, 0x21, 0x03, 0x2d, 0x03, 0x38, 0x03, 0x43,
0x03, 0x4f, 0x03, 0x5a, 0x03, 0x66, 0x03, 0x72, 0x03, 0x7e, 0x03, 0x8a,
0x03, 0x96, 0x03, 0xa2, 0x03, 0xae, 0x03, 0xba, 0x03, 0xc7, 0x03, 0xd3,
0x03, 0xe0, 0x03, 0xec, 0x03, 0xf9, 0x04, 0x06, 0x04, 0x13, 0x04, 0x20,
0x04, 0x2d, 0x04, 0x3b, 0x04, 0x48, 0x04, 0x55, 0x04, 0x63, 0x04, 0x71,
0x04, 0x7e, 0x04, 0x8c, 0x04, 0x9a, 0x04, 0xa8, 0x04, 0xb6, 0x04, 0xc4,
0x04, 0xd3, 0x04, 0xe1, 0x04, 0xf0, 0x04, 0xfe, 0x05, 0x0d, 0x05, 0x1c,
0x05, 0x2b, 0x05, 0x3a, 0x05, 0x49, 0x05, 0x58, 0x05, 0x67, 0x05, 0x77,
0x05, 0x86, 0x05, 0x96, 0x05, 0xa6, 0x05, 0xb5, 0x05, 0xc5, 0x05, 0xd5,
0x05, 0xe5, 0x05, 0xf6, 0x06, 0x06, 0x06, 0x16, 0x06, 0x27, 0x06, 0x37,
0x06, 0x48, 0x06, 0x59, 0x06, 0x6a, 0x06, 0x7b, 0x06, 0x8c, 0x06, 0x9d,
0x06, 0xaf, 0x06, 0xc0, 0x06, 0xd1, 0x06, 0xe3, 0x06, 0xf5, 0x07, 0x07,
0x07, 0x19, 0x07, 0x2b, 0x07, 0x3d, 0x07, 0x4f, 0x07, 0x61, 0x07, 0x74,
0x07, 0x86, 0x07, 0x99, 0x07, 0xac, 0x07, 0xbf, 0x07, 0xd2, 0x07, 0xe5,
0x07, 0xf8, 0x08, 0x0b, 0x08, 0x1f, 0x08, 0x32, 0x08, 0x46, 0x08, 0x5a,
0x08, 0x6e, 0x08, 0x82, 0x08, 0x96, 0x08, 0xaa, 0x08, 0xbe, 0x08, 0xd2,
0x08, 0xe7, 0x08, 0xfb, 0x09, 0x10, 0x09, 0x25, 0x09, 0x3a, 0x09, 0x4f,
0x09, 0x64, 0x09, 0x79, 0x09, 0x8f, 0x09, 0xa4, 0x09, 0xba, 0x09, 0xcf,
0x09, 0xe5, 0x09, 0xfb, 0x0a, 0x11, 0x0a, 0x27, 0x0a, 0x3d, 0x0a, 0x54,
0x0a, 0x6a, 0x0a, 0x81, 0x0a, 0x98, 0x0a, 0xae, 0x0a, 0xc5, 0x0a, 0xdc,
0x0a, 0xf3, 0x0b, 0x0b, 0x0b, 0x22, 0x0b, 0x39, 0x0b, 0x51, 0x0b, 0x69,
0x0b, 0x80, 0x0b, 0x98, 0x0b, 0xb0, 0x0b, 0xc8, 0x0b, 0xe1, 0x0b, 0xf9,
0x0c, 0x12, 0x0c, 0x2a, 0x0c, 0x43, 0x0c, 0x5c, 0x0c, 0x75, 0x0c, 0x8e,
0x0c, 0xa7, 0x0c, 0xc0, 0x0c, 0xd9, 0x0c, 0xf3, 0x0d, 0x0d, 0x0d, 0x26,
0x0d, 0x40, 0x0d, 0x5a, 0x0d, 0x74, 0x0d, 0x8e, 0x0d, 0xa9, 0x0d, 0xc3,
0x0d, 0xde, 0x0d, 0xf8, 0x0e, 0x13, 0x0e, 0x2e, 0x0e, 0x49, 0x0e, 0x64,
0x0e, 0x7f, 0x0e, 0x9b, 0x0e, 0xb6, 0x0e, 0xd2, 0x0e, 0xee, 0x0f, 0x09,
0x0f, 0x25, 0x0f, 0x41, 0x0f, 0x5e, 0x0f, 0x7a, 0x0f, 0x96, 0x0f, 0xb3,
0x0f, 0xcf, 0x0f, 0xec, 0x10, 0x09, 0x10, 0x26, 0x10, 0x43, 0x10, 0x61,
0x10, 0x7e, 0x10, 0x9b, 0x10, 0xb9, 0x10, 0xd7, 0x10, 0xf5, 0x11, 0x13,
0x11, 0x31, 0x11, 0x4f, 0x11, 0x6d, 0x11, 0x8c, 0x11, 0xaa, 0x11, 0xc9,
0x11, 0xe8, 0x12, 0x07, 0x12, 0x26, 0x12, 0x45, 0x12, 0x64, 0x12, 0x84,
0x12, 0xa3, 0x12, 0xc3, 0x12, 0xe3, 0x13, 0x03, 0x13, 0x23, 0x13, 0x43,
0x13, 0x63, 0x13, 0x83, 0x13, 0xa4, 0x13, 0xc5, 0x13, 0xe5, 0x14, 0x06,
0x14, 0x27, 0x14, 0x49, 0x14, 0x6a, 0x14, 0x8b, 0x14, 0xad, 0x14, 0xce,
0x14, 0xf0, 0x15, 0x12, 0x15, 0x34, 0x15, 0x56, 0x15, 0x78, 0x15, 0x9b,
0x15, 0xbd, 0x15, 0xe0, 0x16, 0x03, 0x16, 0x26, 0x16, 0x49, 0x16, 0x6c,
0x16, 0x8f, 0x16, 0xb2, 0x16, 0xd6, 0x16, 0xfa, 0x17, 0x1d, 0x17, 0x41,
0x17, 0x65, 0x17, 0x89, 0x17, 0xae, 0x17, 0xd2, 0x17, 0xf7, 0x18, 0x1b,
0x18, 0x40, 0x18, 0x65, 0x18, 0x8a, 0x18, 0xaf, 0x18, 0xd5, 0x18, 0xfa,
0x19, 0x20, 0x19, 0x45, 0x19, 0x6b, 0x19, 0x91, 0x19, 0xb7, 0x19, 0xdd,
0x1a, 0x04, 0x1a, 0x2a, 0x1a, 0x51, 0x1a, 0x77, 0x1a, 0x9e, 0x1a, 0xc5,
0x1a, 0xec, 0x1b, 0x14, 0x1b, 0x3b, 0x1b, 0x63, 0x1b, 0x8a, 0x1b, 0xb2,
0x1b, 0xda, 0x1c, 0x02, 0x1c, 0x2a, 0x1c, 0x52, 0x1c, 0x7b, 0x1c, 0xa3,
0x1c, 0xcc, 0x1c, 0xf5, 0x1d, 0x1e, 0x1d, 0x47, 0x1d, 0x70, 0x1d, 0x99,
0x1d, 0xc3, 0x1d, 0xec, 0x1e, 0x16, 0x1e, 0x40, 0x1e, 0x6a, 0x1e, 0x94,
0x1e, 0xbe, 0x1e, 0xe9, 0x1f, 0x13, 0x1f, 0x3e, 0x1f, 0x69, 0x1f, 0x94,
0x1f, 0xbf, 0x1f, 0xea, 0x20, 0x15, 0x20, 0x41, 0x20, 0x6c, 0x20, 0x98,
0x20, 0xc4, 0x20, 0xf0, 0x21, 0x1c, 0x21, 0x48, 0x21, 0x75, 0x21, 0xa1,
0x21, 0xce, 0x21, 0xfb, 0x22, 0x27, 0x22, 0x55, 0x22, 0x82, 0x22, 0xaf,
0x22, 0xdd, 0x23, 0x0a, 0x23, 0x38, 0x23, 0x66, 0x23, 0x94, 0x23, 0xc2,
0x23, 0xf0, 0x24, 0x1f, 0x24, 0x4d, 0x24, 0x7c, 0x24, 0xab, 0x24, 0xda,
0x25, 0x09, 0x25, 0x38, 0x25, 0x68, 0x25, 0x97, 0x25, 0xc7, 0x25, 0xf7,
0x26, 0x27, 0x26, 0x57, 0x26, 0x87, 0x26, 0xb7, 0x26, 0xe8, 0x27, 0x18,
0x27, 0x49, 0x27, 0x7a, 0x27, 0xab, 0x27, 0xdc, 0x28, 0x0d, 0x28, 0x3f,
0x28, 0x71, 0x28, 0xa2, 0x28, 0xd4, 0x29, 0x06, 0x29, 0x38, 0x29, 0x6b,
0x29, 0x9d, 0x29, 0xd0, 0x2a, 0x02, 0x2a, 0x35, 0x2a, 0x68, 0x2a, 0x9b,
0x2a, 0xcf, 0x2b, 0x02, 0x2b, 0x36, 0x2b, 0x69, 0x2b, 0x9d, 0x2b, 0xd1,
0x2c, 0x05, 0x2c, 0x39, 0x2c, 0x6e, 0x2c, 0xa2, 0x2c, 0xd7, 0x2d, 0x0c,
0x2d, 0x41, 0x2d, 0x76, 0x2d, 0xab, 0x2d, 0xe1, 0x2e, 0x16, 0x2e, 0x4c,
0x2e, 0x82, 0x2e, 0xb7, 0x2e, 0xee, 0x2f, 0x24, 0x2f, 0x5a, 0x2f, 0x91,
0x2f, 0xc7, 0x2f, 0xfe, 0x30, 0x35, 0x30, 0x6c, 0x30, 0xa4, 0x30, 0xdb,
0x31, 0x12, 0x31, 0x4a, 0x31, 0x82, 0x31, 0xba, 0x31, 0xf2, 0x32, 0x2a,
0x32, 0x63, 0x32, 0x9b, 0x32, 0xd4, 0x33, 0x0d, 0x33, 0x46, 0x33, 0x7f,
0x33, 0xb8, 0x33, 0xf1, 0x34, 0x2b, 0x34, 0x65, 0x34, 0x9e, 0x34, 0xd8,
0x35, 0x13, 0x35, 0x4d, 0x35, 0x87, 0x35, 0xc2, 0x35, 0xfd, 0x36, 0x37,
0x36, 0x72, 0x36, 0xae, 0x36, 0xe9, 0x37, 0x24, 0x37, 0x60, 0x37, 0x9c,
0x37, 0xd7, 0x38, 0x14, 0x38, 0x50, 0x38, 0x8c, 0x38, 0xc8, 0x39, 0x05,
0x39, 0x42, 0x39, 0x7f, 0x39, 0xbc, 0x39, 0xf9, 0x3a, 0x36, 0x3a, 0x74,
0x3a, 0xb2, 0x3a, 0xef, 0x3b, 0x2d, 0x3b, 0x6b, 0x3b, 0xaa, 0x3b, 0xe8,
0x3c, 0x27, 0x3c, 0x65, 0x3c, 0xa4, 0x3c, 0xe3, 0x3d, 0x22, 0x3d, 0x61,
0x3d, 0xa1, 0x3d, 0xe0, 0x3e, 0x20, 0x3e, 0x60, 0x3e, 0xa0, 0x3e, 0xe0,
0x3f, 0x21, 0x3f, 0x61, 0x3f, 0xa2, 0x3f, 0xe2, 0x40, 0x23, 0x40, 0x64,
0x40, 0xa6, 0x40, 0xe7, 0x41, 0x29, 0x41, 0x6a, 0x41, 0xac, 0x41, 0xee,
0x42, 0x30, 0x42, 0x72, 0x42, 0xb5, 0x42, 0xf7, 0x43, 0x3a, 0x43, 0x7d,
0x43, 0xc0, 0x44, 0x03, 0x44, 0x47, 0x44, 0x8a, 0x44, 0xce, 0x45, 0x12,
0x45, 0x55, 0x45, 0x9a, 0x45, 0xde, 0x46, 0x22, 0x46, 0x67, 0x46, 0xab,
0x46, 0xf0, 0x47, 0x35, 0x47, 0x7b, 0x47, 0xc0, 0x48, 0x05, 0x48, 0x4b,
0x48, 0x91, 0x48, 0xd7, 0x49, 0x1d, 0x49, 0x63, 0x49, 0xa9, 0x49, 0xf0,
0x4a, 0x37, 0x4a, 0x7d, 0x4a, 0xc4, 0x4b, 0x0c, 0x4b, 0x53, 0x4b, 0x9a,
0x4b, 0xe2, 0x4c, 0x2a, 0x4c, 0x72, 0x4c, 0xba, 0x4d, 0x02, 0x4d, 0x4a,
0x4d, 0x93, 0x4d, 0xdc, 0x4e, 0x25, 0x4e, 0x6e, 0x4e, 0xb7, 0x4f, 0x00,
0x4f, 0x49, 0x4f, 0x93, 0x4f, 0xdd, 0x50, 0x27, 0x50, 0x71, 0x50, 0xbb,
0x51, 0x06, 0x51, 0x50, 0x51, 0x9b, 0x51, 0xe6, 0x52, 0x31, 0x52, 0x7c,
0x52, 0xc7, 0x53, 0x13, 0x53, 0x5f, 0x53, 0xaa, 0x53, 0xf6, 0x54, 0x42,
0x54, 0x8f, 0x54, 0xdb, 0x55, 0x28, 0x55, 0x75, 0x55, 0xc2, 0x56, 0x0f,
0x56, 0x5c, 0x56, 0xa9, 0x56, 0xf7, 0x57, 0x44, 0x57, 0x92, 0x57, 0xe0,
0x58, 0x2f, 0x58, 0x7d, 0x58, 0xcb, 0x59, 0x1a, 0x59, 0x69, 0x59, 0xb8,
0x5a, 0x07, 0x5a, 0x56, 0x5a, 0xa6, 0x5a, 0xf5, 0x5b, 0x45, 0x5b, 0x95,
0x5b, 0xe5, 0x5c, 0x35, 0x5c, 0x86, 0x5c, 0xd6, 0x5d, 0x27, 0x5d, 0x78,
0x5d, 0xc9, 0x5e, 0x1a, 0x5e, 0x6c, 0x5e, 0xbd, 0x5f, 0x0f, 0x5f, 0x61,
0x5f, 0xb3, 0x60, 0x05, 0x60, 0x57, 0x60, 0xaa, 0x60, 0xfc, 0x61, 0x4f,
0x61, 0xa2, 0x61, 0xf5, 0x62, 0x49, 0x62, 0x9c, 0x62, 0xf0, 0x63, 0x43,
0x63, 0x97, 0x63, 0xeb, 0x64, 0x40, 0x64, 0x94, 0x64, 0xe9, 0x65, 0x3d,
0x65, 0x92, 0x65, 0xe7, 0x66, 0x3d, 0x66, 0x92, 0x66, 0xe8, 0x67, 0x3d,
0x67, 0x93, 0x67, 0xe9, 0x68, 0x3f, 0x68, 0x96, 0x68, 0xec, 0x69, 0x43,
0x69, 0x9a, 0x69, 0xf1, 0x6a, 0x48, 0x6a, 0x9f, 0x6a, 0xf7, 0x6b, 0x4f,
0x6b, 0xa7, 0x6b, 0xff, 0x6c, 0x57, 0x6c, 0xaf, 0x6d, 0x08, 0x6d, 0x60,
0x6d, 0xb9, 0x6e, 0x12, 0x6e, 0x6b, 0x6e, 0xc4, 0x6f, 0x1e, 0x6f, 0x78,
0x6f, 0xd1, 0x70, 0x2b, 0x70, 0x86, 0x70, 0xe0, 0x71, 0x3a, 0x71, 0x95,
0x71, 0xf0, 0x72, 0x4b, 0x72, 0xa6, 0x73, 0x01, 0x73, 0x5d, 0x73, 0xb8,
0x74, 0x14, 0x74, 0x70, 0x74, 0xcc, 0x75, 0x28, 0x75, 0x85, 0x75, 0xe1,
0x76, 0x3e, 0x76, 0x9b, 0x76, 0xf8, 0x77, 0x56, 0x77, 0xb3, 0x78, 0x11,
0x78, 0x6e, 0x78, 0xcc, 0x79, 0x2a, 0x79, 0x89, 0x79, 0xe7, 0x7a, 0x46,
0x7a, 0xa5, 0x7b, 0x04, 0x7b, 0x63, 0x7b, 0xc2, 0x7c, 0x21, 0x7c, 0x81,
0x7c, 0xe1, 0x7d, 0x41, 0x7d, 0xa1, 0x7e, 0x01, 0x7e, 0x62, 0x7e, 0xc2,
0x7f, 0x23, 0x7f, 0x84, 0x7f, 0xe5, 0x80, 0x47, 0x80, 0xa8, 0x81, 0x0a,
0x81, 0x6b, 0x81, 0xcd, 0x82, 0x30, 0x82, 0x92, 0x82, 0xf4, 0x83, 0x57,
0x83, 0xba, 0x84, 0x1d, 0x84, 0x80, 0x84, 0xe3, 0x85, 0x47, 0x85, 0xab,
0x86, 0x0e, 0x86, 0x72, 0x86, 0xd7, 0x87, 0x3b, 0x87, 0x9f, 0x88, 0x04,
0x88, 0x69, 0x88, 0xce, 0x89, 0x33, 0x89, 0x99, 0x89, 0xfe, 0x8a, 0x64,
0x8a, 0xca, 0x8b, 0x30, 0x8b, 0x96, 0x8b, 0xfc, 0x8c, 0x63, 0x8c, 0xca,
0x8d, 0x31, 0x8d, 0x98, 0x8d, 0xff, 0x8e, 0x66, 0x8e, 0xce, 0x8f, 0x36,
0x8f, 0x9e, 0x90, 0x06, 0x90, 0x6e, 0x90, 0xd6, 0x91, 0x3f, 0x91, 0xa8,
0x92, 0x11, 0x92, 0x7a, 0x92, 0xe3, 0x93, 0x4d, 0x93, 0xb6, 0x94, 0x20,
0x94, 0x8a, 0x94, 0xf4, 0x95, 0x5f, 0x95, 0xc9, 0x96, 0x34, 0x96, 0x9f,
0x97, 0x0a, 0x97, 0x75, 0x97, 0xe0, 0x98, 0x4c, 0x98, 0xb8, 0x99, 0x24,
0x99, 0x90, 0x99, 0xfc, 0x9a, 0x68, 0x9a, 0xd5, 0x9b, 0x42, 0x9b, 0xaf,
0x9c, 0x1c, 0x9c, 0x89, 0x9c, 0xf7, 0x9d, 0x64, 0x9d, 0xd2, 0x9e, 0x40,
0x9e, 0xae, 0x9f, 0x1d, 0x9f, 0x8b, 0x9f, 0xfa, 0xa0, 0x69, 0xa0, 0xd8,
0xa1, 0x47, 0xa1, 0xb6, 0xa2, 0x26, 0xa2, 0x96, 0xa3, 0x06, 0xa3, 0x76,
0xa3, 0xe6, 0xa4, 0x56, 0xa4, 0xc7, 0xa5, 0x38, 0xa5, 0xa9, 0xa6, 0x1a,
0xa6, 0x8b, 0xa6, 0xfd, 0xa7, 0x6e, 0xa7, 0xe0, 0xa8, 0x52, 0xa8, 0xc4,
0xa9, 0x37, 0xa9, 0xa9, 0xaa, 0x1c, 0xaa, 0x8f, 0xab, 0x02, 0xab, 0x75,
0xab, 0xe9, 0xac, 0x5c, 0xac, 0xd0, 0xad, 0x44, 0xad, 0xb8, 0xae, 0x2d,
0xae, 0xa1, 0xaf, 0x16, 0xaf, 0x8b, 0xb0, 0x00, 0xb0, 0x75, 0xb0, 0xea,
0xb1, 0x60, 0xb1, 0xd6, 0xb2, 0x4b, 0xb2, 0xc2, 0xb3, 0x38, 0xb3, 0xae,
0xb4, 0x25, 0xb4, 0x9c, 0xb5, 0x13, 0xb5, 0x8a, 0xb6, 0x01, 0xb6, 0x79,
0xb6, 0xf0, 0xb7, 0x68, 0xb7, 0xe0, 0xb8, 0x59, 0xb8, 0xd1, 0xb9, 0x4a,
0xb9, 0xc2, 0xba, 0x3b, 0xba, 0xb5, 0xbb, 0x2e, 0xbb, 0xa7, 0xbc, 0x21,
0xbc, 0x9b, 0xbd, 0x15, 0xbd, 0x8f, 0xbe, 0x0a, 0xbe, 0x84, 0xbe, 0xff,
0xbf, 0x7a, 0xbf, 0xf5, 0xc0, 0x70, 0xc0, 0xec, 0xc1, 0x67, 0xc1, 0xe3,
0xc2, 0x5f, 0xc2, 0xdb, 0xc3, 0x58, 0xc3, 0xd4, 0xc4, 0x51, 0xc4, 0xce,
0xc5, 0x4b, 0xc5, 0xc8, 0xc6, 0x46, 0xc6, 0xc3, 0xc7, 0x41, 0xc7, 0xbf,
0xc8, 0x3d, 0xc8, 0xbc, 0xc9, 0x3a, 0xc9, 0xb9, 0xca, 0x38, 0xca, 0xb7,
0xcb, 0x36, 0xcb, 0xb6, 0xcc, 0x35, 0xcc, 0xb5, 0xcd, 0x35, 0xcd, 0xb5,
0xce, 0x36, 0xce, 0xb6, 0xcf, 0x37, 0xcf, 0xb8, 0xd0, 0x39, 0xd0, 0xba,
0xd1, 0x3c, 0xd1, 0xbe, 0xd2, 0x3f, 0xd2, 0xc1, 0xd3, 0x44, 0xd3, 0xc6,
0xd4, 0x49, 0xd4, 0xcb, 0xd5, 0x4e, 0xd5, 0xd1, 0xd6, 0x55, 0xd6, 0xd8,
0xd7, 0x5c, 0xd7, 0xe0, 0xd8, 0x64, 0xd8, 0xe8, 0xd9, 0x6c, 0xd9, 0xf1,
0xda, 0x76, 0xda, 0xfb, 0xdb, 0x80, 0xdc, 0x05, 0xdc, 0x8a, 0xdd, 0x10,
0xdd, 0x96, 0xde, 0x1c, 0xde, 0xa2, 0xdf, 0x29, 0xdf, 0xaf, 0xe0, 0x36,
0xe0, 0xbd, 0xe1, 0x44, 0xe1, 0xcc, 0xe2, 0x53, 0xe2, 0xdb, 0xe3, 0x63,
0xe3, 0xeb, 0xe4, 0x73, 0xe4, 0xfc, 0xe5, 0x84, 0xe6, 0x0d, 0xe6, 0x96,
0xe7, 0x1f, 0xe7, 0xa9, 0xe8, 0x32, 0xe8, 0xbc, 0xe9, 0x46, 0xe9, 0xd0,
0xea, 0x5b, 0xea, 0xe5, 0xeb, 0x70, 0xeb, 0xfb, 0xec, 0x86, 0xed, 0x11,
0xed, 0x9c, 0xee, 0x28, 0xee, 0xb4, 0xef, 0x40, 0xef, 0xcc, 0xf0, 0x58,
0xf0, 0xe5, 0xf1, 0x72, 0xf1, 0xff, 0xf2, 0x8c, 0xf3, 0x19, 0xf3, 0xa7,
0xf4, 0x34, 0xf4, 0xc2, 0xf5, 0x50, 0xf5, 0xde, 0xf6, 0x6d, 0xf6, 0xfb,
0xf7, 0x8a, 0xf8, 0x19, 0xf8, 0xa8, 0xf9, 0x38, 0xf9, 0xc7, 0xfa, 0x57,
0xfa, 0xe7, 0xfb, 0x77, 0xfc, 0x07, 0xfc, 0x98, 0xfd, 0x29, 0xfd, 0xba,
0xfe, 0x4b, 0xfe, 0xdc, 0xff, 0x6d, 0xff, 0xff, 0x58, 0x59, 0x5a, 0x20,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3, 0x51, 0x00, 0x01, 0x00, 0x00,
0x00, 0x01, 0x16, 0xcc, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00,
0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x41, 0x70,
0x70, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x2c, 0x20, 0x32, 0x30,
0x30, 0x38, 0x00, 0x00, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1f, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20,
0x47, 0x72, 0x61, 0x79, 0x20, 0x47, 0x61, 0x6d, 0x6d, 0x61, 0x20, 0x32,
0x2e, 0x32, 0x20, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x6c, 0x75, 0x63,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0c,
0x65, 0x6e, 0x55, 0x53, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0xdc,
0x65, 0x73, 0x45, 0x53, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x01, 0x18,
0x64, 0x61, 0x44, 0x4b, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x02, 0x2a,
0x64, 0x65, 0x44, 0x45, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x01, 0xde,
0x66, 0x69, 0x46, 0x49, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x02, 0xa0,
0x66, 0x72, 0x46, 0x55, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x02, 0x62,
0x69, 0x74, 0x49, 0x54, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x03, 0x70,
0x6e, 0x6c, 0x4e, 0x4c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x64,
0x6e, 0x62, 0x4e, 0x4f, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x03, 0xc4,
0x70, 0x74, 0x42, 0x52, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x03, 0x26,
0x73, 0x76, 0x53, 0x45, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x02, 0x2a,
0x6a, 0x61, 0x4a, 0x50, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x03, 0xfe,
0x6b, 0x6f, 0x4b, 0x52, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x04, 0x24,
0x7a, 0x68, 0x54, 0x57, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x04, 0x46,
0x7a, 0x68, 0x43, 0x4e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x04, 0x64,
0x72, 0x75, 0x52, 0x55, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x01, 0xa4,
0x70, 0x6c, 0x50, 0x4c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x02, 0xe6,
0x00, 0x47, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x69,
0x00, 0x63, 0x00, 0x20, 0x00, 0x47, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79,
0x00, 0x20, 0x00, 0x47, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61,
0x00, 0x20, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x32, 0x00, 0x20, 0x00, 0x50,
0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65,
0x00, 0x50, 0x00, 0x65, 0x00, 0x72, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c,
0x00, 0x20, 0x00, 0x67, 0x00, 0x65, 0x00, 0x6e, 0x00, 0xe9, 0x00, 0x72,
0x00, 0x69, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x64, 0x00, 0x65,
0x00, 0x20, 0x00, 0x67, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61,
0x00, 0x20, 0x00, 0x64, 0x00, 0x65, 0x00, 0x20, 0x00, 0x67, 0x00, 0x72,
0x00, 0x69, 0x00, 0x73, 0x00, 0x65, 0x00, 0x73, 0x00, 0x20, 0x00, 0x32,
0x00, 0x2c, 0x00, 0x32, 0x00, 0x41, 0x00, 0x6c, 0x00, 0x67, 0x00, 0x65,
0x00, 0x6d, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x67,
0x00, 0x72, 0x00, 0x69, 0x00, 0x6a, 0x00, 0x73, 0x00, 0x20, 0x00, 0x67,
0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x20, 0x00, 0x32,
0x00, 0x2c, 0x00, 0x32, 0x00, 0x2d, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f,
0x00, 0x66, 0x00, 0x69, 0x00, 0x65, 0x00, 0x6c, 0x04, 0x1e, 0x04, 0x31,
0x04, 0x49, 0x04, 0x30, 0x04, 0x4f, 0x00, 0x20, 0x04, 0x41, 0x04, 0x35,
0x04, 0x40, 0x04, 0x30, 0x04, 0x4f, 0x00, 0x20, 0x04, 0x33, 0x04, 0x30,
0x04, 0x3c, 0x04, 0x3c, 0x04, 0x30, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2c,
0x00, 0x32, 0x00, 0x2d, 0x04, 0x3f, 0x04, 0x40, 0x04, 0x3e, 0x04, 0x44,
0x04, 0x38, 0x04, 0x3b, 0x04, 0x4c, 0x00, 0x41, 0x00, 0x6c, 0x00, 0x6c,
0x00, 0x67, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x69, 0x00, 0x6e,
0x00, 0x65, 0x00, 0x73, 0x00, 0x20, 0x00, 0x47, 0x00, 0x72, 0x00, 0x61,
0x00, 0x75, 0x00, 0x73, 0x00, 0x74, 0x00, 0x75, 0x00, 0x66, 0x00, 0x65,
0x00, 0x6e, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69,
0x00, 0x6c, 0x00, 0x20, 0x00, 0x47, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d,
0x00, 0x61, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2c, 0x00, 0x32, 0x00, 0x47,
0x00, 0x65, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x69, 0x00, 0x73,
0x00, 0x6b, 0x00, 0x20, 0x00, 0x67, 0x00, 0x72, 0x00, 0xe5, 0x00, 0x20,
0x00, 0x32, 0x00, 0x2c, 0x00, 0x32, 0x00, 0x20, 0x00, 0x67, 0x00, 0x61,
0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f,
0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x50, 0x00, 0x72, 0x00, 0x6f,
0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x67, 0x00, 0xe9,
0x00, 0x6e, 0x00, 0xe9, 0x00, 0x72, 0x00, 0x69, 0x00, 0x71, 0x00, 0x75,
0x00, 0x65, 0x00, 0x20, 0x00, 0x67, 0x00, 0x72, 0x00, 0x69, 0x00, 0x73,
0x00, 0x20, 0x00, 0x67, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61,
0x00, 0x20, 0x00, 0x32, 0x00, 0x2c, 0x00, 0x32, 0x00, 0x59, 0x00, 0x6c,
0x00, 0x65, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x20,
0x00, 0x68, 0x00, 0x61, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x61,
0x00, 0x6e, 0x00, 0x20, 0x00, 0x67, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d,
0x00, 0x61, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2c, 0x00, 0x32, 0x00, 0x20,
0x00, 0x2d, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69,
0x00, 0x69, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x4f, 0x00, 0x67, 0x00, 0xf3,
0x00, 0x6c, 0x00, 0x6e, 0x00, 0x79, 0x00, 0x20, 0x00, 0x70, 0x00, 0x72,
0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x73,
0x00, 0x7a, 0x00, 0x61, 0x00, 0x72, 0x00, 0x6f, 0x01, 0x5b, 0x00, 0x63,
0x00, 0x69, 0x00, 0x20, 0x00, 0x67, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d,
0x00, 0x61, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2c, 0x00, 0x32, 0x00, 0x50,
0x00, 0x65, 0x00, 0x72, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x20,
0x00, 0x47, 0x00, 0x65, 0x00, 0x6e, 0x00, 0xe9, 0x00, 0x72, 0x00, 0x69,
0x00, 0x63, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x64, 0x00, 0x61, 0x00, 0x20,
0x00, 0x47, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x20, 0x00, 0x64,
0x00, 0x65, 0x00, 0x20, 0x00, 0x43, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x7a,
0x00, 0x61, 0x00, 0x73, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2c, 0x00, 0x32,
0x00, 0x50, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c,
0x00, 0x6f, 0x00, 0x20, 0x00, 0x67, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x65,
0x00, 0x72, 0x00, 0x69, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x64,
0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x20, 0x00, 0x67,
0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x20, 0x00, 0x64,
0x00, 0x65, 0x00, 0x69, 0x00, 0x20, 0x00, 0x67, 0x00, 0x72, 0x00, 0x69,
0x00, 0x67, 0x00, 0x69, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2c, 0x00, 0x32,
0x00, 0x47, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x69,
0x00, 0x73, 0x00, 0x6b, 0x00, 0x20, 0x00, 0x67, 0x00, 0x72, 0x00, 0xe5,
0x00, 0x20, 0x00, 0x67, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x6d, 0x00, 0x61,
0x00, 0x20, 0x00, 0x32, 0x00, 0x2c, 0x00, 0x32, 0x00, 0x2d, 0x00, 0x70,
0x00, 0x72, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x69, 0x00, 0x6c, 0x4e, 0x00,
0x82, 0x2c, 0x30, 0xb0, 0x30, 0xec, 0x30, 0xa4, 0x30, 0xac, 0x30, 0xf3,
0x30, 0xde, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x32, 0x00, 0x20,
0x30, 0xd7, 0x30, 0xed, 0x30, 0xd5, 0x30, 0xa1, 0x30, 0xa4, 0x30, 0xeb,
0xc7, 0x7c, 0xbc, 0x18, 0x00, 0x20, 0xd6, 0x8c, 0xc0, 0xc9, 0x00, 0x20,
0xac, 0x10, 0xb9, 0xc8, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x32,
0x00, 0x20, 0xd5, 0x04, 0xb8, 0x5c, 0xd3, 0x0c, 0xc7, 0x7c, 0x90, 0x1a,
0x75, 0x28, 0x70, 0x70, 0x96, 0x8e, 0x51, 0x49, 0x5e, 0xa6, 0x00, 0x20,
0x00, 0x32, 0x00, 0x2e, 0x00, 0x32, 0x00, 0x20, 0x82, 0x72, 0x5f, 0x69,
0x63, 0xcf, 0x8f, 0xf0, 0x90, 0x1a, 0x75, 0x28, 0x70, 0x70, 0x5e, 0xa6,
0x7c, 0xfb, 0x65, 0x70, 0x00, 0x20, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x32,
0x00, 0x20, 0x63, 0xcf, 0x8f, 0xf0, 0x65, 0x87, 0x4e, 0xf6, 0x00, 0x00,
}
sRGBV2MicroICCProfilePathToken = "\x00srgb_v2_micro.icc"
sGrayV2MicroICCProfilePathToken = "\x00sgray_v2_micro.icc"
sRGBIEC6196621ICCProfilePathToken = "\x00srgb_iec61966_2_1.icc"
genericGrayGamma22ICCProfilePathToken = "\x00generic_gray_gamma_2_2.icc"
temporaryDirectory = ""
SRGBV2MicroICCProfilePath = sRGBV2MicroICCProfilePathToken
SGrayV2MicroICCProfilePath = sGrayV2MicroICCProfilePathToken
SRGBIEC6196621ICCProfilePath = sRGBIEC6196621ICCProfilePathToken
GenericGrayGamma22ICCProfilePath = genericGrayGamma22ICCProfilePathToken
)
// Back support
func ensureLoadICCPath(name *string) (err error) {
if len(*name) > 0 && (*name)[0] == 0 {
switch *name {
case sRGBV2MicroICCProfilePathToken:
*name, err = GetSRGBV2MicroICCProfilePath()
return
case sGrayV2MicroICCProfilePathToken:
*name, err = GetSGrayV2MicroICCProfilePath()
return
case sRGBIEC6196621ICCProfilePathToken:
*name, err = GetSRGBIEC6196621ICCProfilePath()
return
case genericGrayGamma22ICCProfilePathToken:
*name, err = GetGenericGrayGamma22ICCProfilePath()
return
}
}
return
}
var tempDirOnce sync.Once
var tempDirErr error
func getTemporaryDirectory() (string, error) {
tempDirOnce.Do(func() {
temporaryDirectory, tempDirErr = os.MkdirTemp("", "govips-")
})
return temporaryDirectory, tempDirErr
}
var lockIcc sync.Mutex
func GetSRGBV2MicroICCProfilePath() (string, error) {
return getOrLoad(&SRGBV2MicroICCProfilePath, "srgb_v2_micro.icc", sRGBV2MicroICCProfile)
}
func GetSGrayV2MicroICCProfilePath() (string, error) {
return getOrLoad(&SGrayV2MicroICCProfilePath, "sgray_v2_micro.icc", sGrayV2MicroICCProfile)
}
func GetSRGBIEC6196621ICCProfilePath() (string, error) {
return getOrLoad(&SRGBIEC6196621ICCProfilePath, "srgb_iec61966_2_1.icc", sRGBIEC6196621ICCProfile)
}
func GetGenericGrayGamma22ICCProfilePath() (string, error) {
return getOrLoad(&GenericGrayGamma22ICCProfilePath, "generic_gray_gamma_2_2.icc", genericGrayGamma22ICCProfile)
}
func getOrLoad(pathFile *string, name string, fileBytes []byte) (string, error) {
lockIcc.Lock()
defer lockIcc.Unlock()
if len(*pathFile) > 0 && (*pathFile)[0] != 0 {
return *pathFile, nil
}
if _, err := getTemporaryDirectory(); err != nil {
return "", err
}
*pathFile = filepath.Join(temporaryDirectory, name)
if err := os.WriteFile(*pathFile, fileBytes, 0600); err != nil {
return "", err
}
return *pathFile, nil
}
+16
View File
@@ -0,0 +1,16 @@
#include "image.h"
int has_alpha_channel(VipsImage *image) { return vips_image_hasalpha(image); }
void clear_image(VipsImage **image) {
// Reference-counting in libvips: https://www.libvips.org/API/current/using-from-c.html#using-C-ref
// https://docs.gtk.org/gobject/method.Object.unref.html
if (G_IS_OBJECT(*image)) g_object_unref(*image);
}
VipsImage *create_image_from_memory_copy(const void *data, size_t size,
int width, int height, int bands,
VipsBandFormat format) {
return vips_image_new_from_memory_copy(data, size, width, height, bands,
format);
}
+799
View File
@@ -0,0 +1,799 @@
package vips
// #include "image.h"
import "C"
import (
"errors"
"fmt"
"image"
"image/draw"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"io"
"os"
"runtime"
"strconv"
"strings"
"sync"
"unsafe"
_ "golang.org/x/image/webp"
)
const GaussBlurDefaultMinAMpl = 0.2
// PreMultiplicationState stores the pre-multiplication band format of the image
type PreMultiplicationState struct {
bandFormat BandFormat
}
// ImageRef contains a libvips image and manages its lifecycle.
type ImageRef struct {
// NOTE: We keep a reference to this so that the input buffer is
// never garbage collected during processing. Some image loaders use random
// access transcoding and therefore need the original buffer to be in memory.
buf []byte
image *C.VipsImage
format ImageType
originalFormat ImageType
lock sync.Mutex
preMultiplication *PreMultiplicationState
optimizedIccProfile string
}
// ImageMetadata is a data structure holding the width, height, orientation and other metadata of the picture.
type ImageMetadata struct {
Format ImageType
Width int
Height int
Colorspace Interpretation
Orientation int
Pages int
}
type Parameter struct {
value interface{}
isSet bool
}
func (p *Parameter) IsSet() bool {
return p.isSet
}
func (p *Parameter) set(v interface{}) {
p.value = v
p.isSet = true
}
type BoolParameter struct {
Parameter
}
func (p *BoolParameter) Set(v bool) {
p.set(v)
}
func (p *BoolParameter) Get() bool {
return p.value.(bool)
}
type IntParameter struct {
Parameter
}
func (p *IntParameter) Set(v int) {
p.set(v)
}
func (p *IntParameter) Get() int {
return p.value.(int)
}
type Float64Parameter struct {
Parameter
}
func (p *Float64Parameter) Set(v float64) {
p.set(v)
}
func (p *Float64Parameter) Get() float64 {
return p.value.(float64)
}
// ImportParams are options for loading an image. Some are type-specific.
// For default loading, use NewImportParams() or specify nil
type ImportParams struct {
AutoRotate BoolParameter
FailOnError BoolParameter
Page IntParameter
NumPages IntParameter
Density IntParameter
JpegShrinkFactor IntParameter
WebpScaleFactor Float64Parameter
HeifThumbnail BoolParameter
SvgUnlimited BoolParameter
Access IntParameter
}
// NewImportParams creates default ImportParams
func NewImportParams() *ImportParams {
p := &ImportParams{}
p.FailOnError.Set(true)
return p
}
// OptionString convert import params to option_string
func (i *ImportParams) OptionString() string {
var values []string
if v := i.NumPages; v.IsSet() {
values = append(values, "n="+strconv.Itoa(v.Get()))
}
if v := i.Page; v.IsSet() {
values = append(values, "page="+strconv.Itoa(v.Get()))
}
if v := i.Density; v.IsSet() {
values = append(values, "dpi="+strconv.Itoa(v.Get()))
}
if v := i.FailOnError; v.IsSet() {
values = append(values, "fail="+boolToStr(v.Get()))
}
if v := i.JpegShrinkFactor; v.IsSet() {
values = append(values, "shrink="+strconv.Itoa(v.Get()))
}
if v := i.WebpScaleFactor; v.IsSet() {
values = append(values, "scale="+strconv.FormatFloat(v.Get(), 'f', -1, 64))
}
if v := i.AutoRotate; v.IsSet() {
values = append(values, "autorotate="+boolToStr(v.Get()))
}
if v := i.SvgUnlimited; v.IsSet() {
values = append(values, "unlimited="+boolToStr(v.Get()))
}
if v := i.HeifThumbnail; v.IsSet() {
values = append(values, "thumbnail="+boolToStr(v.Get()))
}
if v := i.Access; v.IsSet() {
values = append(values, "access="+strconv.Itoa(v.Get()))
}
return strings.Join(values, ",")
}
func boolToStr(v bool) string {
if v {
return "TRUE"
}
return "FALSE"
}
// ExportParams are options when exporting an image to file or buffer.
// Deprecated: Use format-specific params
type ExportParams struct {
Format ImageType
Quality int
Compression int
Interlaced bool
Lossless bool
Effort int
StripMetadata bool
OptimizeCoding bool // jpeg param
SubsampleMode SubsampleMode // jpeg param
TrellisQuant bool // jpeg param
OvershootDeringing bool // jpeg param
OptimizeScans bool // jpeg param
QuantTable int // jpeg param
Speed int // avif param
}
// NewDefaultExportParams creates default values for an export when image type is not JPEG, PNG or WEBP.
// By default, govips creates interlaced, lossy images with a quality of 80/100 and compression of 6/10.
// As these are default values for a wide variety of image formats, their application varies.
// Some formats use the quality parameters, some compression, etc.
// Deprecated: Use format-specific params
func NewDefaultExportParams() *ExportParams {
return &ExportParams{
Format: ImageTypeUnknown, // defaults to the starting encoder
Quality: 80,
Compression: 6,
Interlaced: true,
Lossless: false,
Effort: 4,
}
}
// NewDefaultJPEGExportParams creates default values for an export of a JPEG image.
// By default, govips creates interlaced JPEGs with a quality of 80/100.
// Deprecated: Use NewJpegExportParams
func NewDefaultJPEGExportParams() *ExportParams {
return &ExportParams{
Format: ImageTypeJPEG,
Quality: 80,
Interlaced: true,
}
}
// NewDefaultPNGExportParams creates default values for an export of a PNG image.
// By default, govips creates non-interlaced PNGs with a compression of 6/10.
// Deprecated: Use NewPngExportParams
func NewDefaultPNGExportParams() *ExportParams {
return &ExportParams{
Format: ImageTypePNG,
Compression: 6,
Interlaced: false,
}
}
// NewDefaultWEBPExportParams creates default values for an export of a WEBP image.
// By default, govips creates lossy images with a quality of 75/100.
// Deprecated: Use NewWebpExportParams
func NewDefaultWEBPExportParams() *ExportParams {
return &ExportParams{
Format: ImageTypeWEBP,
Quality: 75,
Lossless: false,
Effort: 4,
}
}
// JpegExportParams are options when exporting a JPEG to file or buffer
type JpegExportParams struct {
StripMetadata bool
Quality int
Interlace bool
OptimizeCoding bool
SubsampleMode SubsampleMode
TrellisQuant bool
OvershootDeringing bool
OptimizeScans bool
QuantTable int
}
// NewJpegExportParams creates default values for an export of a JPEG image.
// By default, govips creates interlaced JPEGs with a quality of 80/100.
func NewJpegExportParams() *JpegExportParams {
return &JpegExportParams{
Quality: 80,
Interlace: true,
}
}
// PngExportParams are options when exporting a PNG to file or buffer
type PngExportParams struct {
StripMetadata bool
Compression int
Filter PngFilter
Interlace bool
Quality int
Palette bool
Dither float64
Bitdepth int
Profile string
}
// NewPngExportParams creates default values for an export of a PNG image.
// By default, govips creates non-interlaced PNGs with a compression of 6/10.
func NewPngExportParams() *PngExportParams {
return &PngExportParams{
Compression: 6,
Filter: PngFilterNone,
Interlace: false,
Palette: false,
}
}
// WebpExportParams are options when exporting a WEBP to file or buffer
// see https://www.libvips.org/API/current/VipsForeignSave.html#vips-webpsave
// for details on each parameter
type WebpExportParams struct {
StripMetadata bool
Quality int
Lossless bool
NearLossless bool
ReductionEffort int
IccProfile string
MinSize bool
MinKeyFrames int
MaxKeyFrames int
}
// NewWebpExportParams creates default values for an export of a WEBP image.
// By default, govips creates lossy images with a quality of 75/100.
func NewWebpExportParams() *WebpExportParams {
return &WebpExportParams{
Quality: 75,
Lossless: false,
NearLossless: false,
ReductionEffort: 4,
}
}
// TiffExportParams are options when exporting a TIFF to file or buffer
type TiffExportParams struct {
StripMetadata bool
Quality int
Compression TiffCompression
Predictor TiffPredictor
Pyramid bool
Tile bool
TileHeight int
TileWidth int
}
// NewTiffExportParams creates default values for an export of a TIFF image.
func NewTiffExportParams() *TiffExportParams {
return &TiffExportParams{
Quality: 80,
Compression: TiffCompressionLzw,
Predictor: TiffPredictorHorizontal,
Pyramid: false,
Tile: false,
TileHeight: 256,
TileWidth: 256,
}
}
// GifExportParams are options when exporting a GIF to file or buffer.
//
// For vips 8.12+, native gifsave is used. The relevant parameters are Dither,
// Effort, and Bitdepth. Quality is ignored because native gifsave does not
// support a quality parameter.
//
// For vips below 8.12, magicksave is used as a fallback. The relevant
// parameters are Quality and Bitdepth.
//
// StripMetadata has no effect on GIF images.
type GifExportParams struct {
StripMetadata bool
// Quality is only used with vips < 8.12 (magicksave fallback).
// Ignored by native gifsave in vips 8.12+.
Quality int
Dither float64
Effort int
Bitdepth int
}
// NewGifExportParams creates default values for an export of a GIF image.
func NewGifExportParams() *GifExportParams {
return &GifExportParams{
Quality: 75,
Effort: 7,
Bitdepth: 8,
}
}
// HeifExportParams are options when exporting a HEIF to file or buffer
type HeifExportParams struct {
Quality int
Bitdepth int
Effort int
Lossless bool
}
// NewHeifExportParams creates default values for an export of a HEIF image.
func NewHeifExportParams() *HeifExportParams {
return &HeifExportParams{
Quality: 80,
Bitdepth: 8,
Effort: 5,
Lossless: false,
}
}
// AvifExportParams are options when exporting an AVIF to file or buffer.
type AvifExportParams struct {
StripMetadata bool
Quality int
Bitdepth int
Effort int
Lossless bool
// DEPRECATED - Use Effort instead.
Speed int
}
// NewAvifExportParams creates default values for an export of an AVIF image.
func NewAvifExportParams() *AvifExportParams {
return &AvifExportParams{
Quality: 80,
Bitdepth: 8,
Effort: 5,
Lossless: false,
}
}
// Jp2kExportParams are options when exporting an JPEG2000 to file or buffer.
type Jp2kExportParams struct {
Quality int
Lossless bool
TileWidth int
TileHeight int
SubsampleMode SubsampleMode
}
// NewJp2kExportParams creates default values for an export of an JPEG2000 image.
func NewJp2kExportParams() *Jp2kExportParams {
return &Jp2kExportParams{
Quality: 80,
Lossless: false,
TileWidth: 512,
TileHeight: 512,
}
}
// JxlExportParams are options when exporting an JXL to file or buffer.
type JxlExportParams struct {
Quality int
Lossless bool
Tier int
Distance float64
Effort int
}
// NewJxlExportParams creates default values for an export of an JXL image.
func NewJxlExportParams() *JxlExportParams {
return &JxlExportParams{
Quality: 75,
Lossless: false,
Effort: 7,
Distance: 1.0,
}
}
// 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)
if err != nil {
return nil, err
}
return NewImageFromBuffer(buf)
}
// NewImageFromFile loads an image from file and creates a new ImageRef
func NewImageFromFile(file string) (*ImageRef, error) {
return LoadImageFromFile(file, nil)
}
// LoadImageFromFile loads an image from file and creates a new ImageRef
func LoadImageFromFile(file string, params *ImportParams) (*ImageRef, error) {
buf, err := os.ReadFile(file)
if err != nil {
return nil, err
}
govipsLog("govips", LogLevelDebug, fmt.Sprintf("creating imageRef from file %s", file))
return LoadImageFromBuffer(buf, params)
}
// NewImageFromBuffer loads an image buffer and creates a new Image
func NewImageFromBuffer(buf []byte) (*ImageRef, error) {
return LoadImageFromBuffer(buf, nil)
}
// LoadImageFromBuffer loads an image buffer and creates a new Image
func LoadImageFromBuffer(buf []byte, params *ImportParams) (*ImageRef, error) {
if err := startupIfNeeded(); err != nil {
return nil, err
}
if params == nil {
params = NewImportParams()
}
vipsImage, currentFormat, originalFormat, err := vipsLoadFromBuffer(buf, params)
if err != nil {
return nil, err
}
ref := newImageRef(vipsImage, currentFormat, originalFormat, buf)
govipsLog("govips", LogLevelDebug, fmt.Sprintf("created imageRef %p", ref))
return ref, nil
}
// NewThumbnailFromFile loads an image from file and creates a new ImageRef with thumbnail crop
func NewThumbnailFromFile(file string, width, height int, crop Interesting) (*ImageRef, error) {
return LoadThumbnailFromFile(file, width, height, crop, SizeBoth, nil)
}
// NewThumbnailFromBuffer loads an image buffer and creates a new Image with thumbnail crop
func NewThumbnailFromBuffer(buf []byte, width, height int, crop Interesting) (*ImageRef, error) {
return LoadThumbnailFromBuffer(buf, width, height, crop, SizeBoth, nil)
}
// NewThumbnailWithSizeFromFile loads an image from file and creates a new ImageRef with thumbnail crop and size
func NewThumbnailWithSizeFromFile(file string, width, height int, crop Interesting, size Size) (*ImageRef, error) {
return LoadThumbnailFromFile(file, width, height, crop, size, nil)
}
// LoadThumbnailFromFile loads an image from file and creates a new ImageRef with thumbnail crop and size
func LoadThumbnailFromFile(file string, width, height int, crop Interesting, size Size, params *ImportParams) (*ImageRef, error) {
if err := startupIfNeeded(); err != nil {
return nil, err
}
vipsImage, format, err := vipsThumbnailFromFile(file, width, height, crop, size, params)
if err != nil {
return nil, err
}
ref := newImageRef(vipsImage, format, format, nil)
govipsLog("govips", LogLevelDebug, fmt.Sprintf("created imageref %p", ref))
return ref, nil
}
// NewThumbnailWithSizeFromBuffer loads an image buffer and creates a new Image with thumbnail crop and size
func NewThumbnailWithSizeFromBuffer(buf []byte, width, height int, crop Interesting, size Size) (*ImageRef, error) {
return LoadThumbnailFromBuffer(buf, width, height, crop, size, nil)
}
// LoadThumbnailFromBuffer loads an image buffer and creates a new Image with thumbnail crop and size
func LoadThumbnailFromBuffer(buf []byte, width, height int, crop Interesting, size Size, params *ImportParams) (*ImageRef, error) {
if err := startupIfNeeded(); err != nil {
return nil, err
}
vipsImage, format, err := vipsThumbnailFromBuffer(buf, width, height, crop, size, params)
if err != nil {
return nil, err
}
ref := newImageRef(vipsImage, format, format, buf)
govipsLog("govips", LogLevelDebug, fmt.Sprintf("created imageref %p", ref))
return ref, nil
}
// Metadata returns the metadata (ImageMetadata struct) of the associated ImageRef
func (r *ImageRef) Metadata() *ImageMetadata {
return &ImageMetadata{
Format: r.Format(),
Width: r.Width(),
Height: r.Height(),
Orientation: r.Orientation(),
Colorspace: r.ColorSpace(),
Pages: r.Pages(),
}
}
// Copy creates a new copy of the given image.
func (r *ImageRef) Copy() (*ImageRef, error) {
defer runtime.KeepAlive(r)
out, err := vipsGenCopy(r.image, nil)
if err != nil {
return nil, err
}
return newImageRef(out, r.format, r.originalFormat, r.buf), nil
}
// Copy creates a new copy of the given image with the new X and Y resolution (PPI).
func (r *ImageRef) CopyChangingResolution(xres, yres float64) (*ImageRef, error) {
defer runtime.KeepAlive(r)
out, err := vipsGenCopy(r.image, &CopyOptions{Xres: &xres, Yres: &yres})
if err != nil {
return nil, err
}
return newImageRef(out, r.format, r.originalFormat, r.buf), nil
}
// Copy creates a new copy of the given image with the interpretation.
func (r *ImageRef) CopyChangingInterpretation(interpretation Interpretation) (*ImageRef, error) {
defer runtime.KeepAlive(r)
out, err := vipsGenCopy(r.image, &CopyOptions{Interpretation: &interpretation})
if err != nil {
return nil, err
}
return newImageRef(out, r.format, r.originalFormat, r.buf), nil
}
// XYZ creates a two-band uint32 image where the elements in the first band have the value of their x coordinate
// and elements in the second band have their y coordinate.
func XYZ(width, height int) (*ImageRef, error) {
vipsImage, err := vipsGenXyz(width, height, nil)
return newImageRef(vipsImage, ImageTypeUnknown, ImageTypeUnknown, nil), err
}
// Identity creates an identity lookup table, which will leave an image unchanged when applied with Maplut.
// Each entry in the table has a value equal to its position.
func Identity(ushort bool) (*ImageRef, error) {
img, err := vipsGenIdentity(&IdentityOptions{Ushort: &ushort})
return newImageRef(img, ImageTypeUnknown, ImageTypeUnknown, nil), err
}
// Black creates a new black image of the specified size
func Black(width, height int) (*ImageRef, error) {
vipsImage, err := vipsGenBlack(width, height, nil)
if err != nil {
return nil, err
}
return newImageRef(vipsImage, ImageTypeUnknown, ImageTypeUnknown, nil), nil
}
// Grey creates a horizontal gradient image (ramp from black to white).
// When uchar is true, pixel values are 0-255 uint8; when false, 0.0-1.0 float.
// Useful for creating gradient overlays when combined with rotation, BandJoin, and Composite.
func Grey(width, height int, uchar bool) (*ImageRef, error) {
img, err := vipsGenGrey(width, height, &GreyOptions{Uchar: &uchar})
if err != nil {
return nil, err
}
return newImageRef(img, ImageTypeUnknown, ImageTypeUnknown, nil), nil
}
// NewTransparentCanvas creates a fully transparent RGBA image of the given dimensions.
// The image is in sRGB color space with 4 bands (RGBA), all channels set to 0.
func NewTransparentCanvas(width, height int) (*ImageRef, error) {
ref, err := Black(width, height)
if err != nil {
return nil, err
}
if err := ref.ToColorSpace(InterpretationSRGB); err != nil {
ref.Close()
return nil, err
}
if err := ref.BandJoinConst([]float64{0}); err != nil {
ref.Close()
return nil, err
}
return ref, nil
}
// Text draws the string text to an image.
func Text(params *TextParams) (*ImageRef, error) {
img, err := vipsText(params)
return newImageRef(img, ImageTypeUnknown, ImageTypeUnknown, nil), err
}
// NewImageFromGoImage creates a new ImageRef from a Go image.Image.
// The image is normalized to NRGBA (non-premultiplied RGBA, 8-bit) and
// imported into libvips in sRGB color space.
func NewImageFromGoImage(img image.Image) (*ImageRef, error) {
if err := startupIfNeeded(); err != nil {
return nil, err
}
bounds := img.Bounds()
width := bounds.Dx()
height := bounds.Dy()
if width == 0 || height == 0 {
return nil, errors.New("image has zero dimensions")
}
// Normalize to NRGBA
var nrgba *image.NRGBA
if n, ok := img.(*image.NRGBA); ok && n.Rect.Min.X == 0 && n.Rect.Min.Y == 0 && n.Stride == width*4 {
nrgba = n
} else {
nrgba = image.NewNRGBA(image.Rect(0, 0, width, height))
draw.Draw(nrgba, nrgba.Bounds(), img, bounds.Min, draw.Src)
}
// Create vips image from pixel data (copies data, safe for Go GC)
vipsImage := C.create_image_from_memory_copy(
unsafe.Pointer(&nrgba.Pix[0]),
C.size_t(len(nrgba.Pix)),
C.int(width),
C.int(height),
4,
C.VIPS_FORMAT_UCHAR,
)
runtime.KeepAlive(nrgba)
if vipsImage == nil {
return nil, errors.New("failed to create vips image from memory")
}
// Set interpretation to sRGB
vipsImage.Type = C.VIPS_INTERPRETATION_sRGB
return newImageRef(vipsImage, ImageTypeUnknown, ImageTypeUnknown, nil), nil
}
func newImageRef(vipsImage *C.VipsImage, currentFormat ImageType, originalFormat ImageType, buf []byte) *ImageRef {
imageRef := &ImageRef{
image: vipsImage,
format: currentFormat,
originalFormat: originalFormat,
buf: buf,
}
openImageRefs.Add(1)
runtime.SetFinalizer(imageRef, finalizeImage)
return imageRef
}
func finalizeImage(ref *ImageRef) {
govipsLog("govips", LogLevelDebug, fmt.Sprintf("closing image %p", ref))
ref.Close()
}
// Close manually closes the image and frees the memory. Calling Close() is optional.
// Images are automatically closed by GC. However, in high volume applications the GC
// can't keep up with the amount of memory, so you might want to manually close the images.
func (r *ImageRef) Close() {
r.lock.Lock()
if r.image != nil {
clearImage(r.image)
r.image = nil
openImageRefs.Add(-1)
}
r.buf = nil
r.lock.Unlock()
}
// setImage resets the image for this image and frees the previous one
func (r *ImageRef) setImage(image *C.VipsImage) {
r.lock.Lock()
defer r.lock.Unlock()
if r.image == image {
return
}
if r.image != nil {
clearImage(r.image)
}
r.image = image
}
func vipsHasAlpha(in *C.VipsImage) bool {
return int(C.has_alpha_channel(in)) > 0
}
func clearImage(ref *C.VipsImage) {
C.clear_image(&ref)
}
// Coding represents VIPS_CODING type
type Coding int
// Coding enum
//
//goland:noinspection GoUnusedConst
const (
CodingError Coding = C.VIPS_CODING_ERROR
CodingNone Coding = C.VIPS_CODING_NONE
CodingLABQ Coding = C.VIPS_CODING_LABQ
CodingRAD Coding = C.VIPS_CODING_RAD
)
func (r *ImageRef) newMetadata(format ImageType) *ImageMetadata {
return &ImageMetadata{
Format: format,
Width: r.Width(),
Height: r.Height(),
Colorspace: r.ColorSpace(),
Orientation: r.Orientation(),
Pages: r.Pages(),
}
}
+12
View File
@@ -0,0 +1,12 @@
// https://libvips.github.io/libvips/API/current/VipsImage.html
#include <stdlib.h>
#include <vips/vips.h>
int has_alpha_channel(VipsImage *image);
void clear_image(VipsImage **image);
VipsImage *create_image_from_memory_copy(const void *data, size_t size,
int width, int height, int bands,
VipsBandFormat format);
+173
View File
@@ -0,0 +1,173 @@
package vips
// #include "image.h"
import "C"
import (
"errors"
"runtime"
)
// ToColorSpace changes the color space of the image to the interpretation supplied as the parameter.
func (r *ImageRef) ToColorSpace(interpretation Interpretation) error {
defer runtime.KeepAlive(r)
out, err := vipsToColorSpace(r.image, interpretation)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Flatten removes the alpha channel from the image and replaces it with the background color
func (r *ImageRef) Flatten(backgroundColor *Color) error {
defer runtime.KeepAlive(r)
opts := &FlattenOptions{}
if backgroundColor != nil {
opts.Background = []float64{float64(backgroundColor.R), float64(backgroundColor.G), float64(backgroundColor.B)}
}
out, err := vipsGenFlatten(r.image, opts)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Modulate the colors
func (r *ImageRef) Modulate(brightness, saturation, hue float64) error {
defer runtime.KeepAlive(r)
var err error
var multiplications []float64
var additions []float64
colorspace := r.ColorSpace()
if colorspace == InterpretationRGB {
colorspace = InterpretationSRGB
}
multiplications = []float64{brightness, saturation, 1}
additions = []float64{0, 0, hue}
if r.HasAlpha() {
multiplications = append(multiplications, 1)
additions = append(additions, 0)
}
err = r.ToColorSpace(InterpretationLCH)
if err != nil {
return err
}
err = r.Linear(multiplications, additions)
if err != nil {
return err
}
err = r.ToColorSpace(colorspace)
if err != nil {
return err
}
return nil
}
// ModulateHSV modulates the image HSV values based on the supplier parameters.
func (r *ImageRef) ModulateHSV(brightness, saturation float64, hue int) error {
defer runtime.KeepAlive(r)
var err error
var multiplications []float64
var additions []float64
colorspace := r.ColorSpace()
if colorspace == InterpretationRGB {
colorspace = InterpretationSRGB
}
if r.HasAlpha() {
multiplications = []float64{1, saturation, brightness, 1}
additions = []float64{float64(hue), 0, 0, 0}
} else {
multiplications = []float64{1, saturation, brightness}
additions = []float64{float64(hue), 0, 0}
}
err = r.ToColorSpace(InterpretationHSV)
if err != nil {
return err
}
err = r.Linear(multiplications, additions)
if err != nil {
return err
}
err = r.ToColorSpace(colorspace)
if err != nil {
return err
}
return nil
}
// Invert inverts the image
func (r *ImageRef) Invert() error {
defer runtime.KeepAlive(r)
out, err := vipsGenInvert(r.image)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Adjusts the image's gamma value.
// See https://www.libvips.org/API/current/libvips-conversion.html#vips-gamma
func (r *ImageRef) Gamma(gamma float64) error {
defer runtime.KeepAlive(r)
out, err := vipsGenGamma(r.image, &GammaOptions{Exponent: &gamma})
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Linear passes an image through a linear transformation (i.e. output = input * a + b).
// See https://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-linear
func (r *ImageRef) Linear(a, b []float64) error {
defer runtime.KeepAlive(r)
if len(a) != len(b) {
return errors.New("a and b must be of same length")
}
out, err := vipsGenLinear(r.image, a, b, nil)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Linear1 runs Linear() with a single constant.
// See https://libvips.github.io/libvips/API/current/libvips-arithmetic.html#vips-linear1
func (r *ImageRef) Linear1(a, b float64) error {
defer runtime.KeepAlive(r)
out, err := vipsGenLinear(r.image, []float64{a}, []float64{b}, nil)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Cast converts the image to a target band format
func (r *ImageRef) Cast(format BandFormat) error {
defer runtime.KeepAlive(r)
out, err := vipsGenCast(r.image, format, nil)
if err != nil {
return err
}
r.setImage(out)
return nil
}
+70
View File
@@ -0,0 +1,70 @@
package vips
// #include "image.h"
import "C"
import "runtime"
// CompositeMulti composites the given overlay image on top of the associated image with provided blending mode.
func (r *ImageRef) CompositeMulti(ins []*ImageComposite) error {
defer runtime.KeepAlive(r)
out, err := vipsComposite(toVipsCompositeStructs(r, ins))
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Composite composites the given overlay image on top of the associated image with provided blending mode.
func (r *ImageRef) Composite(overlay *ImageRef, mode BlendMode, x, y int) error {
defer runtime.KeepAlive(r)
out, err := vipsGenComposite2(r.image, overlay.image, mode, &Composite2Options{X: &x, Y: &y})
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Insert draws the image on top of the associated image at the given coordinates.
func (r *ImageRef) Insert(sub *ImageRef, x, y int, expand bool, background *ColorRGBA) error {
defer runtime.KeepAlive(r)
insertOpts := &InsertOptions{Expand: &expand}
if background != nil {
insertOpts.Background = []float64{float64(background.R), float64(background.G), float64(background.B), float64(background.A)}
}
out, err := vipsGenInsert(r.image, sub.image, x, y, insertOpts)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Join joins this image with another in the direction specified
func (r *ImageRef) Join(in *ImageRef, dir Direction) error {
defer runtime.KeepAlive(r)
out, err := vipsJoin(r.image, in.image, dir)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// ArrayJoin joins an array of images together wrapping at each n images
func (r *ImageRef) ArrayJoin(images []*ImageRef, across int) error {
defer runtime.KeepAlive(r)
allImages := append([]*ImageRef{r}, images...)
inputs := make([]*C.VipsImage, len(allImages))
for i := range inputs {
inputs[i] = allImages[i].image
}
out, err := vipsGenArrayjoin(inputs, &ArrayjoinOptions{Across: &across})
if err != nil {
return err
}
r.setImage(out)
return nil
}
+392
View File
@@ -0,0 +1,392 @@
package vips
// #include "image.h"
import "C"
import (
"bytes"
"errors"
"fmt"
"image"
"runtime"
"unsafe"
)
// Export creates a byte array of the image for use.
// The function returns a byte array that can be written to a file e.g. via os.WriteFile().
// N.B. govips does not currently have built-in support for directly exporting to a file.
// The function also returns a copy of the image metadata as well as an error.
// Deprecated: Use ExportNative or format-specific Export methods
func (r *ImageRef) Export(params *ExportParams) ([]byte, *ImageMetadata, error) {
if params == nil || params.Format == ImageTypeUnknown {
return r.ExportNative()
}
format := params.Format
if !IsTypeSupported(format) {
return nil, r.newMetadata(ImageTypeUnknown), fmt.Errorf("cannot save to %#v", ImageTypes[format])
}
switch format {
case ImageTypeGIF:
return r.ExportGIF(&GifExportParams{
Quality: params.Quality,
})
case ImageTypeWEBP:
return r.ExportWebp(&WebpExportParams{
StripMetadata: params.StripMetadata,
Quality: params.Quality,
Lossless: params.Lossless,
ReductionEffort: params.Effort,
})
case ImageTypePNG:
return r.ExportPng(&PngExportParams{
StripMetadata: params.StripMetadata,
Compression: params.Compression,
Interlace: params.Interlaced,
})
case ImageTypeTIFF:
compression := TiffCompressionLzw
if params.Lossless {
compression = TiffCompressionNone
}
return r.ExportTiff(&TiffExportParams{
StripMetadata: params.StripMetadata,
Quality: params.Quality,
Compression: compression,
})
case ImageTypeHEIF:
return r.ExportHeif(&HeifExportParams{
Quality: params.Quality,
Lossless: params.Lossless,
})
case ImageTypeAVIF:
return r.ExportAvif(&AvifExportParams{
StripMetadata: params.StripMetadata,
Quality: params.Quality,
Lossless: params.Lossless,
Speed: params.Speed,
})
case ImageTypeJXL:
return r.ExportJxl(&JxlExportParams{
Quality: params.Quality,
Lossless: params.Lossless,
Effort: params.Effort,
})
default:
format = ImageTypeJPEG
return r.ExportJpeg(&JpegExportParams{
Quality: params.Quality,
StripMetadata: params.StripMetadata,
Interlace: params.Interlaced,
OptimizeCoding: params.OptimizeCoding,
SubsampleMode: params.SubsampleMode,
TrellisQuant: params.TrellisQuant,
OvershootDeringing: params.OvershootDeringing,
OptimizeScans: params.OptimizeScans,
QuantTable: params.QuantTable,
})
}
}
// ExportNative exports the image to a buffer based on its native format with default parameters.
func (r *ImageRef) ExportNative() ([]byte, *ImageMetadata, error) {
switch r.format {
case ImageTypeJPEG:
return r.ExportJpeg(NewJpegExportParams())
case ImageTypePNG:
return r.ExportPng(NewPngExportParams())
case ImageTypeWEBP:
return r.ExportWebp(NewWebpExportParams())
case ImageTypeHEIF:
return r.ExportHeif(NewHeifExportParams())
case ImageTypeTIFF:
return r.ExportTiff(NewTiffExportParams())
case ImageTypeAVIF:
return r.ExportAvif(NewAvifExportParams())
case ImageTypeJP2K:
return r.ExportJp2k(NewJp2kExportParams())
case ImageTypeGIF:
return r.ExportGIF(NewGifExportParams())
case ImageTypeJXL:
return r.ExportJxl(NewJxlExportParams())
default:
return r.ExportJpeg(NewJpegExportParams())
}
}
// ExportJpeg exports the image as JPEG to a buffer.
func (r *ImageRef) ExportJpeg(params *JpegExportParams) ([]byte, *ImageMetadata, error) {
defer runtime.KeepAlive(r)
if params == nil {
params = NewJpegExportParams()
}
buf, err := vipsSaveJPEGToBuffer(r.image, *params)
if err != nil {
return nil, nil, err
}
return buf, r.newMetadata(ImageTypeJPEG), nil
}
// ExportPng exports the image as PNG to a buffer.
func (r *ImageRef) ExportPng(params *PngExportParams) ([]byte, *ImageMetadata, error) {
defer runtime.KeepAlive(r)
if params == nil {
params = NewPngExportParams()
}
buf, err := vipsSavePNGToBuffer(r.image, *params)
if err != nil {
return nil, nil, err
}
return buf, r.newMetadata(ImageTypePNG), nil
}
// ExportWebp exports the image as WEBP to a buffer.
func (r *ImageRef) ExportWebp(params *WebpExportParams) ([]byte, *ImageMetadata, error) {
defer runtime.KeepAlive(r)
if params == nil {
params = NewWebpExportParams()
}
paramsWithIccProfile := *params
paramsWithIccProfile.IccProfile = r.optimizedIccProfile
buf, err := vipsSaveWebPToBuffer(r.image, paramsWithIccProfile)
if err != nil {
return nil, nil, err
}
return buf, r.newMetadata(ImageTypeWEBP), nil
}
// ExportHeif exports the image as HEIF to a buffer.
func (r *ImageRef) ExportHeif(params *HeifExportParams) ([]byte, *ImageMetadata, error) {
defer runtime.KeepAlive(r)
if params == nil {
params = NewHeifExportParams()
}
buf, err := vipsSaveHEIFToBuffer(r.image, *params)
if err != nil {
return nil, nil, err
}
return buf, r.newMetadata(ImageTypeHEIF), nil
}
// ExportTiff exports the image as TIFF to a buffer.
func (r *ImageRef) ExportTiff(params *TiffExportParams) ([]byte, *ImageMetadata, error) {
defer runtime.KeepAlive(r)
if params == nil {
params = NewTiffExportParams()
}
buf, err := vipsSaveTIFFToBuffer(r.image, *params)
if err != nil {
return nil, nil, err
}
return buf, r.newMetadata(ImageTypeTIFF), nil
}
// ExportGIF exports the image as GIF to a buffer.
func (r *ImageRef) ExportGIF(params *GifExportParams) ([]byte, *ImageMetadata, error) {
defer runtime.KeepAlive(r)
if params == nil {
params = NewGifExportParams()
}
buf, err := vipsSaveGIFToBuffer(r.image, *params)
if err != nil {
return nil, nil, err
}
return buf, r.newMetadata(ImageTypeGIF), nil
}
// ExportAvif exports the image as AVIF to a buffer.
func (r *ImageRef) ExportAvif(params *AvifExportParams) ([]byte, *ImageMetadata, error) {
defer runtime.KeepAlive(r)
if params == nil {
params = NewAvifExportParams()
}
buf, err := vipsSaveAVIFToBuffer(r.image, *params)
if err != nil {
return nil, nil, err
}
return buf, r.newMetadata(ImageTypeAVIF), nil
}
// ExportJp2k exports the image as JPEG2000 to a buffer.
func (r *ImageRef) ExportJp2k(params *Jp2kExportParams) ([]byte, *ImageMetadata, error) {
defer runtime.KeepAlive(r)
if params == nil {
params = NewJp2kExportParams()
}
buf, err := vipsSaveJP2KToBuffer(r.image, *params)
if err != nil {
return nil, nil, err
}
return buf, r.newMetadata(ImageTypeJP2K), nil
}
// ExportJxl exports the image as JPEG XL to a buffer.
func (r *ImageRef) ExportJxl(params *JxlExportParams) ([]byte, *ImageMetadata, error) {
defer runtime.KeepAlive(r)
if params == nil {
params = NewJxlExportParams()
}
buf, err := vipsSaveJxlToBuffer(r.image, *params)
if err != nil {
return nil, nil, err
}
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) {
defer runtime.KeepAlive(r)
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
}
// ToBytes writes the image to memory in VIPs format and returns the raw bytes, useful for storage.
func (r *ImageRef) ToBytes() ([]byte, error) {
defer runtime.KeepAlive(r)
var cSize C.size_t
cData := C.vips_image_write_to_memory(r.image, &cSize)
if cData == nil {
return nil, errors.New("failed to write image to memory")
}
defer C.free(cData)
data := C.GoBytes(unsafe.Pointer(cData), C.int(cSize))
return data, nil
}
// ToImage converts a VIPs image to a golang image.Image object, useful for interoperability with other golang libraries.
// Deprecated: Use ToGoImage for a faster, direct conversion without encoding round-trip.
func (r *ImageRef) ToImage(params *ExportParams) (image.Image, error) {
imageBytes, _, err := r.ExportNative()
if err != nil {
return nil, err
}
reader := bytes.NewReader(imageBytes)
img, _, err := image.Decode(reader)
if err != nil {
return nil, err
}
return img, nil
}
// ToGoImage converts a vips image directly to a Go image.Image without encoding.
// This is significantly faster than ToImage() which round-trips through JPEG/PNG.
// The resulting image will be in sRGB color space with 8-bit depth.
func (r *ImageRef) ToGoImage() (image.Image, error) {
defer runtime.KeepAlive(r)
// Work on a copy to avoid mutating the receiver
tmp, err := vipsGenCopy(r.image, nil)
if err != nil {
return nil, err
}
defer clearImage(tmp)
// Convert to sRGB if needed (keep B_W for grayscale)
interp := Interpretation(int(tmp.Type))
if interp != InterpretationSRGB && interp != InterpretationBW {
out, err := vipsToColorSpace(tmp, InterpretationSRGB)
if err != nil {
return nil, err
}
clearImage(tmp)
tmp = out
}
// Cast to uchar if needed
if BandFormat(int(tmp.BandFmt)) != BandFormatUchar {
out, err := vipsGenCast(tmp, BandFormatUchar, nil)
if err != nil {
return nil, err
}
clearImage(tmp)
tmp = out
}
// Extract raw pixel data
var cSize C.size_t
cData := C.vips_image_write_to_memory(tmp, &cSize)
if cData == nil {
return nil, errors.New("failed to write image to memory")
}
defer C.free(cData)
width := int(tmp.Xsize)
height := int(tmp.Ysize)
bands := int(tmp.Bands)
pixels := C.GoBytes(unsafe.Pointer(cData), C.int(cSize))
switch bands {
case 1:
img := image.NewGray(image.Rect(0, 0, width, height))
copy(img.Pix, pixels)
return img, nil
case 2:
// Grayscale + alpha
img := image.NewNRGBA(image.Rect(0, 0, width, height))
srcIdx := 0
dstIdx := 0
for srcIdx+1 < len(pixels) {
v := pixels[srcIdx]
img.Pix[dstIdx] = v
img.Pix[dstIdx+1] = v
img.Pix[dstIdx+2] = v
img.Pix[dstIdx+3] = pixels[srcIdx+1]
srcIdx += 2
dstIdx += 4
}
return img, nil
case 3:
// RGB, add opaque alpha
img := image.NewNRGBA(image.Rect(0, 0, width, height))
srcIdx := 0
dstIdx := 0
for srcIdx+2 < len(pixels) {
img.Pix[dstIdx] = pixels[srcIdx]
img.Pix[dstIdx+1] = pixels[srcIdx+1]
img.Pix[dstIdx+2] = pixels[srcIdx+2]
img.Pix[dstIdx+3] = 255
srcIdx += 3
dstIdx += 4
}
return img, nil
case 4:
img := image.NewNRGBA(image.Rect(0, 0, width, height))
copy(img.Pix, pixels)
return img, nil
default:
return nil, fmt.Errorf("unsupported number of bands: %d", bands)
}
}
+108
View File
@@ -0,0 +1,108 @@
package vips
// #include "image.h"
import "C"
import (
"fmt"
"runtime"
)
// GetICCProfile retrieves the ICC profile data (if any) from the image.
func (r *ImageRef) GetICCProfile() []byte {
defer runtime.KeepAlive(r)
bytes, _ := vipsGetICCProfile(r.image)
return bytes
}
// RemoveICCProfile removes the ICC Profile information from the image.
// Typically, browsers and other software assume images without profile to be in the sRGB color space.
func (r *ImageRef) RemoveICCProfile() error {
defer runtime.KeepAlive(r)
out, err := vipsGenCopy(r.image, nil)
if err != nil {
return err
}
vipsRemoveICCProfile(out)
r.setImage(out)
return nil
}
// TransformICCProfileWithFallback transforms from the embedded ICC profile of the image to the ICC profile at the given path.
// The fallback ICC profile is used if the image does not have an embedded ICC profile.
func (r *ImageRef) TransformICCProfileWithFallback(targetProfilePath, fallbackProfilePath string) error {
defer runtime.KeepAlive(r)
if err := ensureLoadICCPath(&targetProfilePath); err != nil {
return err
}
if err := ensureLoadICCPath(&fallbackProfilePath); err != nil {
return err
}
depth := 16
if r.BandFormat() == BandFormatUchar || r.BandFormat() == BandFormatChar || r.BandFormat() == BandFormatNotSet {
depth = 8
}
out, err := vipsICCTransform(r.image, targetProfilePath, fallbackProfilePath, IntentPerceptual, depth, true)
if err != nil {
govipsLog("govips", LogLevelError, fmt.Sprintf("failed to do icc transform: %v", err.Error()))
return err
}
r.setImage(out)
return nil
}
// TransformICCProfile transforms from the embedded ICC profile of the image to the icc profile at the given path.
func (r *ImageRef) TransformICCProfile(outputProfilePath string) error {
return r.TransformICCProfileWithFallback(outputProfilePath, SRGBIEC6196621ICCProfilePath)
}
// OptimizeICCProfile optimizes the ICC color profile of the image.
// For two color channel images, it sets a grayscale profile.
// For color images, it sets a CMYK or non-CMYK profile based on the image metadata.
func (r *ImageRef) OptimizeICCProfile() error {
defer runtime.KeepAlive(r)
inputProfile := r.determineInputICCProfile()
if !r.HasICCProfile() && (inputProfile == "") {
// No embedded ICC profile in the input image and no input profile determined, nothing to do.
return nil
}
r.optimizedIccProfile = SRGBV2MicroICCProfilePath
if r.Bands() <= 2 {
r.optimizedIccProfile = SGrayV2MicroICCProfilePath
}
if err := ensureLoadICCPath(&r.optimizedIccProfile); err != nil {
return err
}
embedded := r.HasICCProfile() && (inputProfile == "")
depth := 16
if r.BandFormat() == BandFormatUchar || r.BandFormat() == BandFormatChar || r.BandFormat() == BandFormatNotSet {
depth = 8
}
out, err := vipsICCTransform(r.image, r.optimizedIccProfile, inputProfile, IntentPerceptual, depth, embedded)
if err != nil {
govipsLog("govips", LogLevelError, fmt.Sprintf("failed to do icc transform: %v", err.Error()))
return err
}
r.setImage(out)
return nil
}
func (r *ImageRef) determineInputICCProfile() (inputProfile string) {
if r.Interpretation() == InterpretationCMYK {
if !r.HasICCProfile() {
inputProfile = "cmyk"
}
}
return
}
+338
View File
@@ -0,0 +1,338 @@
package vips
// #include "image.h"
import "C"
import (
"runtime"
"strings"
)
// Format returns the current format of the vips image.
func (r *ImageRef) Format() ImageType {
return r.format
}
// OriginalFormat returns the original format of the image when loaded.
// In some cases the loaded image is converted on load, for example, a BMP is automatically converted to PNG
// This method returns the format of the original buffer, as opposed to Format() with will return the format of the
// currently held buffer content.
func (r *ImageRef) OriginalFormat() ImageType {
return r.originalFormat
}
// Width returns the width of this image.
func (r *ImageRef) Width() int {
return int(r.image.Xsize)
}
// Height returns the height of this image.
func (r *ImageRef) Height() int {
return int(r.image.Ysize)
}
// Bands returns the number of bands for this image.
func (r *ImageRef) Bands() int {
return int(r.image.Bands)
}
// HasProfile returns if the image has an ICC profile embedded.
func (r *ImageRef) HasProfile() bool {
defer runtime.KeepAlive(r)
return vipsHasICCProfile(r.image)
}
// HasICCProfile checks whether the image has an ICC profile embedded. Alias to HasProfile
func (r *ImageRef) HasICCProfile() bool {
return r.HasProfile()
}
// HasIPTC returns a boolean whether the image in question has IPTC data associated with it.
func (r *ImageRef) HasIPTC() bool {
defer runtime.KeepAlive(r)
return vipsHasIPTC(r.image)
}
// HasAlpha returns if the image has an alpha layer.
func (r *ImageRef) HasAlpha() bool {
defer runtime.KeepAlive(r)
return vipsHasAlpha(r.image)
}
// Orientation returns the orientation number as it appears in the EXIF, if present
func (r *ImageRef) Orientation() int {
defer runtime.KeepAlive(r)
return vipsGetMetaOrientation(r.image)
}
// Deprecated: use Orientation() instead
func (r *ImageRef) GetOrientation() int {
return r.Orientation()
}
// SetOrientation sets the orientation in the EXIF header of the associated image.
func (r *ImageRef) SetOrientation(orientation int) error {
defer runtime.KeepAlive(r)
out, err := vipsGenCopy(r.image, nil)
if err != nil {
return err
}
vipsSetMetaOrientation(out, orientation)
r.setImage(out)
return nil
}
// RemoveOrientation removes the EXIF orientation information of the image.
func (r *ImageRef) RemoveOrientation() error {
defer runtime.KeepAlive(r)
out, err := vipsGenCopy(r.image, nil)
if err != nil {
return err
}
vipsRemoveMetaOrientation(out)
r.setImage(out)
return nil
}
// ResX returns the X resolution
func (r *ImageRef) ResX() float64 {
return float64(r.image.Xres)
}
// ResY returns the Y resolution
func (r *ImageRef) ResY() float64 {
return float64(r.image.Yres)
}
// OffsetX returns the X offset
func (r *ImageRef) OffsetX() int {
return int(r.image.Xoffset)
}
// OffsetY returns the Y offset
func (r *ImageRef) OffsetY() int {
return int(r.image.Yoffset)
}
// BandFormat returns the current band format
func (r *ImageRef) BandFormat() BandFormat {
return BandFormat(int(r.image.BandFmt))
}
// Coding returns the image coding
func (r *ImageRef) Coding() Coding {
return Coding(int(r.image.Coding))
}
// Interpretation returns the current interpretation of the color space of the image.
func (r *ImageRef) Interpretation() Interpretation {
return Interpretation(int(r.image.Type))
}
// ColorSpace returns the interpretation of the current color space. Alias to Interpretation().
func (r *ImageRef) ColorSpace() Interpretation {
return r.Interpretation()
}
// IsColorSpaceSupported returns a boolean whether the image's color space is supported by libvips.
func (r *ImageRef) IsColorSpaceSupported() bool {
defer runtime.KeepAlive(r)
return vipsIsColorSpaceSupported(r.image)
}
// Pages returns the number of pages in the Image
// For animated images this corresponds to the number of frames
func (r *ImageRef) Pages() int {
// libvips uses the same attribute (n_pages) to represent the number of pyramid layers in JP2K
// as we interpret the attribute as frames and JP2K does not support animation we override this with 1
if r.format == ImageTypeJP2K {
return 1
}
defer runtime.KeepAlive(r)
return vipsGetImageNPages(r.image)
}
// Deprecated: use Pages() instead
func (r *ImageRef) GetPages() int {
return r.Pages()
}
// SetPages sets the number of pages in the Image
// For animated images this corresponds to the number of frames
func (r *ImageRef) SetPages(pages int) error {
defer runtime.KeepAlive(r)
out, err := vipsGenCopy(r.image, nil)
if err != nil {
return err
}
vipsSetImageNPages(out, pages)
r.setImage(out)
return nil
}
// PageHeight return the height of a single page
func (r *ImageRef) PageHeight() int {
defer runtime.KeepAlive(r)
return vipsGetPageHeight(r.image)
}
// GetPageHeight return the height of a single page
// Deprecated use PageHeight() instead
func (r *ImageRef) GetPageHeight() int {
defer runtime.KeepAlive(r)
return vipsGetPageHeight(r.image)
}
// SetPageHeight set the height of a page
// For animated images this is used when "unrolling" back to frames
func (r *ImageRef) SetPageHeight(height int) error {
defer runtime.KeepAlive(r)
out, err := vipsGenCopy(r.image, nil)
if err != nil {
return err
}
vipsSetPageHeight(out, height)
r.setImage(out)
return nil
}
// PageDelay get the page delay array for animation
func (r *ImageRef) PageDelay() ([]int, error) {
defer runtime.KeepAlive(r)
n := vipsGetImageNPages(r.image)
if n <= 1 {
// should not call if not multi page
return nil, nil
}
return vipsImageGetDelay(r.image, n)
}
// SetPageDelay set the page delay array for animation
func (r *ImageRef) SetPageDelay(delay []int) error {
defer runtime.KeepAlive(r)
var data []C.int
for _, d := range delay {
data = append(data, C.int(d))
}
return vipsImageSetDelay(r.image, data)
}
// Loop returns the loop count for animated images.
// A value of 0 means infinite looping.
func (r *ImageRef) Loop() int {
defer runtime.KeepAlive(r)
return vipsImageGetLoop(r.image)
}
// SetLoop sets the loop count for animated images.
// A value of 0 means infinite looping.
func (r *ImageRef) SetLoop(loop int) error {
defer runtime.KeepAlive(r)
vipsImageSetLoop(r.image, loop)
return nil
}
// Background get the background of image.
func (r *ImageRef) Background() ([]float64, error) {
defer runtime.KeepAlive(r)
out, err := vipsImageGetBackground(r.image)
if err != nil {
return nil, err
}
return out, nil
}
func (r *ImageRef) ImageFields() []string {
return r.GetFields()
}
func (r *ImageRef) GetFields() []string {
defer runtime.KeepAlive(r)
return vipsImageGetFields(r.image)
}
func (r *ImageRef) SetBlob(name string, data []byte) {
defer runtime.KeepAlive(r)
vipsImageSetBlob(r.image, name, data)
}
func (r *ImageRef) GetBlob(name string) []byte {
defer runtime.KeepAlive(r)
return vipsImageGetBlob(r.image, name)
}
func (r *ImageRef) SetDouble(name string, f float64) {
defer runtime.KeepAlive(r)
vipsImageSetDouble(r.image, name, f)
}
func (r *ImageRef) GetDouble(name string) float64 {
defer runtime.KeepAlive(r)
return vipsImageGetDouble(r.image, name)
}
func (r *ImageRef) SetInt(name string, i int) {
defer runtime.KeepAlive(r)
vipsImageSetInt(r.image, name, i)
}
func (r *ImageRef) GetInt(name string) int {
defer runtime.KeepAlive(r)
return vipsImageGetInt(r.image, name)
}
func (r *ImageRef) SetString(name string, str string) {
defer runtime.KeepAlive(r)
vipsImageSetString(r.image, name, str)
}
func (r *ImageRef) GetString(name string) string {
defer runtime.KeepAlive(r)
return vipsImageGetString(r.image, name)
}
func (r *ImageRef) GetAsString(name string) string {
defer runtime.KeepAlive(r)
return vipsImageGetAsString(r.image, name)
}
func (r *ImageRef) HasExif() bool {
for _, field := range r.ImageFields() {
if strings.HasPrefix(field, "exif-") {
return true
}
}
return false
}
func (r *ImageRef) GetExif() map[string]string {
defer runtime.KeepAlive(r)
return vipsImageGetExifData(r.image)
}
// RemoveMetadata removes the EXIF metadata from the image.
// N.B. this function won't remove the ICC profile, orientation and pages metadata
// because govips needs it to correctly display the image.
func (r *ImageRef) RemoveMetadata(keep ...string) error {
defer runtime.KeepAlive(r)
out, err := vipsGenCopy(r.image, nil)
if err != nil {
return err
}
vipsRemoveMetadata(out, keep...)
r.setImage(out)
return nil
}
+390
View File
@@ -0,0 +1,390 @@
package vips
// #include "image.h"
import "C"
import (
"errors"
"runtime"
)
// GaussianBlur blurs the image
// add support minAmpl
func (r *ImageRef) GaussianBlur(sigmas ...float64) error {
defer runtime.KeepAlive(r)
var (
sigma = sigmas[0]
minAmpl = GaussBlurDefaultMinAMpl
)
if len(sigmas) >= 2 {
minAmpl = sigmas[1]
}
out, err := vipsGenGaussblur(r.image, sigma, &GaussblurOptions{MinAmpl: &minAmpl})
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Sharpen sharpens the image
// sigma: sigma of the gaussian
// x1: flat/jaggy threshold
// m2: slope for jaggy areas
func (r *ImageRef) Sharpen(sigma float64, x1 float64, m2 float64) error {
defer runtime.KeepAlive(r)
out, err := vipsGenSharpen(r.image, &SharpenOptions{Sigma: &sigma, X1: &x1, M2: &m2})
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Apply Sobel edge detector to the image.
func (r *ImageRef) Sobel() error {
defer runtime.KeepAlive(r)
out, err := vipsGenSobel(r.image)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Rank does rank filtering on an image. A window of size width by height is passed over the image.
// At each position, the pixels inside the window are sorted into ascending order and the pixel at position
// index is output. index numbers from 0.
func (r *ImageRef) Rank(width int, height int, index int) error {
defer runtime.KeepAlive(r)
out, err := vipsGenRank(r.image, width, height, index)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Mapim resamples an image using index to look up pixels
func (r *ImageRef) Mapim(index *ImageRef) error {
defer runtime.KeepAlive(r)
out, err := vipsGenMapim(r.image, index.image, nil)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Maplut maps an image through another image acting as a LUT (Look Up Table)
func (r *ImageRef) Maplut(lut *ImageRef) error {
defer runtime.KeepAlive(r)
out, err := vipsGenMaplut(r.image, lut.image, nil)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// ExtractBand extracts one or more bands out of the image (replacing the associated ImageRef)
func (r *ImageRef) ExtractBand(band int, num int) error {
defer runtime.KeepAlive(r)
out, err := vipsGenExtractBand(r.image, band, &ExtractBandOptions{N: &num})
if err != nil {
return err
}
r.setImage(out)
return nil
}
// ExtractBandToImage extracts one or more bands out of the image to a new image
func (r *ImageRef) ExtractBandToImage(band int, num int) (*ImageRef, error) {
defer runtime.KeepAlive(r)
out, err := vipsGenExtractBand(r.image, band, &ExtractBandOptions{N: &num})
if err != nil {
return nil, err
}
return newImageRef(out, ImageTypeUnknown, ImageTypeUnknown, nil), nil
}
// BandJoin joins a set of images together, bandwise.
func (r *ImageRef) BandJoin(images ...*ImageRef) error {
defer runtime.KeepAlive(r)
vipsImages := []*C.VipsImage{r.image}
for _, vipsImage := range images {
vipsImages = append(vipsImages, vipsImage.image)
}
out, err := vipsGenBandjoin(vipsImages)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// BandSplit split an n-band image into n separate images..
func (r *ImageRef) BandSplit() ([]*ImageRef, error) {
defer runtime.KeepAlive(r)
var out []*ImageRef
n := 1
for i := 0; i < r.Bands(); i++ {
img, err := vipsGenExtractBand(r.image, i, &ExtractBandOptions{N: &n})
if err != nil {
return out, err
}
out = append(out, &ImageRef{image: img})
}
return out, nil
}
// BandJoinConst appends a set of constant bands to an image.
func (r *ImageRef) BandJoinConst(constants []float64) error {
defer runtime.KeepAlive(r)
if len(constants) == 0 {
return errors.New("BandJoinConst: empty constants slice")
}
out, err := vipsGenBandjoinConst(r.image, constants)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// AddAlpha adds an alpha channel to the associated image.
func (r *ImageRef) AddAlpha() error {
defer runtime.KeepAlive(r)
if vipsHasAlpha(r.image) {
return nil
}
out, err := vipsAddAlpha(r.image)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// PremultiplyAlpha premultiplies the alpha channel.
// See https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-premultiply
func (r *ImageRef) PremultiplyAlpha() error {
defer runtime.KeepAlive(r)
if r.preMultiplication != nil || !vipsHasAlpha(r.image) {
return nil
}
band := r.BandFormat()
out, err := vipsGenPremultiply(r.image, nil)
if err != nil {
return err
}
r.preMultiplication = &PreMultiplicationState{
bandFormat: band,
}
r.setImage(out)
return nil
}
// UnpremultiplyAlpha unpremultiplies any alpha channel.
// See https://libvips.github.io/libvips/API/current/libvips-conversion.html#vips-unpremultiply
func (r *ImageRef) UnpremultiplyAlpha() error {
defer runtime.KeepAlive(r)
if r.preMultiplication == nil {
return nil
}
unpremultiplied, err := vipsGenUnpremultiply(r.image, nil)
if err != nil {
return err
}
defer clearImage(unpremultiplied)
out, err := vipsGenCast(unpremultiplied, r.preMultiplication.bandFormat, nil)
if err != nil {
return err
}
r.preMultiplication = nil
r.setImage(out)
return nil
}
// Add calculates a sum of the image + addend and stores it back in the image
func (r *ImageRef) Add(addend *ImageRef) error {
defer runtime.KeepAlive(r)
out, err := vipsGenAdd(r.image, addend.image)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Multiply calculates the product of the image * multiplier and stores it back in the image
func (r *ImageRef) Multiply(multiplier *ImageRef) error {
defer runtime.KeepAlive(r)
out, err := vipsGenMultiply(r.image, multiplier.image)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Divide calculates the product of the image / denominator and stores it back in the image
func (r *ImageRef) Divide(denominator *ImageRef) error {
defer runtime.KeepAlive(r)
out, err := vipsGenDivide(r.image, denominator.image)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Average finds the average value in an image
func (r *ImageRef) Average() (float64, error) {
defer runtime.KeepAlive(r)
out, err := vipsGenAvg(r.image)
if err != nil {
return 0, err
}
return out, nil
}
// FindTrim returns the bounding box of the non-border part of the image
// Returned values are left, top, width, height
func (r *ImageRef) FindTrim(threshold float64, backgroundColor *Color) (int, int, int, int, error) {
defer runtime.KeepAlive(r)
return vipsFindTrim(r.image, threshold, backgroundColor)
}
// GetPoint reads a single pixel on an image.
// The pixel values are returned in a slice of length n.
func (r *ImageRef) GetPoint(x int, y int) ([]float64, error) {
defer runtime.KeepAlive(r)
n := 3
if vipsHasAlpha(r.image) {
n = 4
}
return vipsGetPoint(r.image, n, x, y)
}
// Stats find many image statistics in a single pass through the data. Image is changed into a one-band
// `BandFormatDouble` image of at least 10 columns by n + 1 (where n is number of bands in image in)
// rows. Columns are statistics, and are, in order: minimum, maximum, sum, sum of squares, mean,
// standard deviation, x coordinate of minimum, y coordinate of minimum, x coordinate of maximum,
// y coordinate of maximum.
//
// Row 0 has statistics for all bands together, row 1 has stats for band 1, and so on.
//
// If there is more than one maxima or minima, one of them will be chosen at random.
func (r *ImageRef) Stats() error {
defer runtime.KeepAlive(r)
out, err := vipsGenStats(r.image)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// HistogramFind find the histogram the image.
// Find the histogram for all bands (producing a one-band histogram).
// char and uchar images are cast to uchar before histogramming, all other image types are cast to ushort.
func (r *ImageRef) HistogramFind() error {
defer runtime.KeepAlive(r)
out, err := vipsGenHistFind(r.image, nil)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// HistogramCumulative form cumulative histogram.
func (r *ImageRef) HistogramCumulative() error {
defer runtime.KeepAlive(r)
out, err := vipsGenHistCum(r.image)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// HistogramNormalise
// The maximum of each band becomes equal to the maximum index, so for example the max for a uchar
// image becomes 255. Normalise each band separately.
func (r *ImageRef) HistogramNormalise() error {
defer runtime.KeepAlive(r)
out, err := vipsGenHistNorm(r.image)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// HistogramEntropy estimate image entropy from a histogram. Entropy is calculated as:
// `-sum(p * log2(p))`
// where p is histogram-value / sum-of-histogram-values.
func (r *ImageRef) HistogramEntropy() (float64, error) {
defer runtime.KeepAlive(r)
return vipsGenHistEntropy(r.image)
}
// DrawRect draws an (optionally filled) rectangle with a single colour
func (r *ImageRef) DrawRect(ink ColorRGBA, left int, top int, width int, height int, fill bool) error {
defer runtime.KeepAlive(r)
err := vipsDrawRect(r.image, ink, left, top, width, height, fill)
if err != nil {
return err
}
return nil
}
// Subtract calculate subtract operation between two images.
func (r *ImageRef) Subtract(in2 *ImageRef) error {
defer runtime.KeepAlive(r)
out, err := vipsGenSubtract(r.image, in2.image)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Abs calculate abs operation.
func (r *ImageRef) Abs() error {
defer runtime.KeepAlive(r)
out, err := vipsGenAbs(r.image)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Project calculate project operation.
func (r *ImageRef) Project() (*ImageRef, *ImageRef, error) {
defer runtime.KeepAlive(r)
col, row, err := vipsGenProject(r.image)
if err != nil {
return nil, nil, err
}
return newImageRef(col, r.format, r.originalFormat, nil), newImageRef(row, r.format, r.originalFormat, nil), nil
}
// Min finds the minimum value in an image.
func (r *ImageRef) Min() (float64, int, int, error) {
defer runtime.KeepAlive(r)
return vipsMin(r.image)
}
+402
View File
@@ -0,0 +1,402 @@
package vips
// #include "image.h"
import "C"
import (
"errors"
"math"
"runtime"
"unsafe"
)
// GetRotationAngleFromExif returns the angle which the image is currently rotated in.
// First returned value is the angle and second is a boolean indicating whether image is flipped.
// This is based on the EXIF orientation tag standard.
// If no proper orientation number is provided, the picture will be assumed to be upright.
func GetRotationAngleFromExif(orientation int) (Angle, bool) {
switch orientation {
case 0, 1, 2:
return Angle0, orientation == 2
case 3, 4:
return Angle180, orientation == 4
case 5, 8:
return Angle90, orientation == 5
case 6, 7:
return Angle270, orientation == 7
}
return Angle0, false
}
// AutoRotate rotates the image upright based on the EXIF Orientation tag.
// It also resets the orientation information in the EXIF tag to be 1 (i.e. upright).
// N.B. libvips does not flip images currently (i.e. no support for orientations 2, 4, 5 and 7).
// N.B. due to the HEIF image standard, HEIF images are always autorotated by default on load.
// Thus, calling AutoRotate for HEIF images is not needed.
// todo: use https://www.libvips.org/API/current/libvips-conversion.html#vips-autorot-remove-angle
func (r *ImageRef) AutoRotate() error {
defer runtime.KeepAlive(r)
out, _, _, err := vipsGenAutorot(r.image)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// ExtractArea crops the image to a specified area
func (r *ImageRef) ExtractArea(left, top, width, height int) error {
defer runtime.KeepAlive(r)
if r.Height() > r.PageHeight() {
// use animated extract area if more than 1 pages loaded
out, err := vipsExtractAreaMultiPage(r.image, left, top, width, height)
if err != nil {
return err
}
r.setImage(out)
} else {
out, err := vipsExtractArea(r.image, left, top, width, height)
if err != nil {
return err
}
r.setImage(out)
}
return nil
}
// Resize resizes the image based on the scale, maintaining aspect ratio
func (r *ImageRef) Resize(scale float64, kernel Kernel) error {
return r.ResizeWithVScale(scale, -1, kernel)
}
// ResizeWithVScale resizes the image with both horizontal and vertical scaling.
// The parameters are the scaling factors.
func (r *ImageRef) ResizeWithVScale(hScale, vScale float64, kernel Kernel) error {
defer runtime.KeepAlive(r)
if err := r.PremultiplyAlpha(); err != nil {
return err
}
pages := r.Pages()
pageHeight := r.PageHeight()
out, err := vipsResizeWithVScale(r.image, hScale, vScale, kernel)
if err != nil {
return err
}
r.setImage(out)
if pages > 1 {
scale := hScale
if vScale != -1 {
scale = vScale
}
newPageHeight := int(math.Round(float64(pageHeight) * scale))
if err := r.SetPageHeight(newPageHeight); err != nil {
return err
}
}
return r.UnpremultiplyAlpha()
}
// Thumbnail resizes the image to the given width and height.
// crop decides algorithm vips uses to shrink and crop to fill target,
func (r *ImageRef) Thumbnail(width, height int, crop Interesting) error {
defer runtime.KeepAlive(r)
out, err := vipsThumbnail(r.image, width, height, crop, SizeBoth)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// ThumbnailWithSize resizes the image to the given width and height.
// crop decides algorithm vips uses to shrink and crop to fill target,
// size controls upsize, downsize, both or force
func (r *ImageRef) ThumbnailWithSize(width, height int, crop Interesting, size Size) error {
defer runtime.KeepAlive(r)
out, err := vipsThumbnail(r.image, width, height, crop, size)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Embed embeds the given picture in a new one, i.e. the opposite of ExtractArea
func (r *ImageRef) Embed(left, top, width, height int, extend ExtendStrategy) error {
defer runtime.KeepAlive(r)
if r.Height() > r.PageHeight() {
out, err := vipsEmbedMultiPage(r.image, left, top, width, height, extend)
if err != nil {
return err
}
r.setImage(out)
} else {
out, err := vipsEmbed(r.image, left, top, width, height, extend)
if err != nil {
return err
}
r.setImage(out)
}
return nil
}
// EmbedBackground embeds the given picture with a background color
func (r *ImageRef) EmbedBackground(left, top, width, height int, backgroundColor *Color) error {
defer runtime.KeepAlive(r)
c := &ColorRGBA{
R: backgroundColor.R,
G: backgroundColor.G,
B: backgroundColor.B,
A: 255,
}
if r.Height() > r.PageHeight() {
out, err := vipsEmbedMultiPageBackground(r.image, left, top, width, height, c)
if err != nil {
return err
}
r.setImage(out)
} else {
out, err := vipsEmbedBackground(r.image, left, top, width, height, c)
if err != nil {
return err
}
r.setImage(out)
}
return nil
}
// EmbedBackgroundRGBA embeds the given picture with a background rgba color
func (r *ImageRef) EmbedBackgroundRGBA(left, top, width, height int, backgroundColor *ColorRGBA) error {
defer runtime.KeepAlive(r)
if r.Height() > r.PageHeight() {
out, err := vipsEmbedMultiPageBackground(r.image, left, top, width, height, backgroundColor)
if err != nil {
return err
}
r.setImage(out)
} else {
out, err := vipsEmbedBackground(r.image, left, top, width, height, backgroundColor)
if err != nil {
return err
}
r.setImage(out)
}
return nil
}
// Zoom zooms the image by repeating pixels (fast nearest-neighbour)
func (r *ImageRef) Zoom(xFactor int, yFactor int) error {
defer runtime.KeepAlive(r)
out, err := vipsGenZoom(r.image, xFactor, yFactor)
if err != nil {
return err
}
r.setImage(out)
return nil
}
func (r *ImageRef) Gravity(gravity Gravity, width int, height int) error {
defer runtime.KeepAlive(r)
out, err := vipsGenGravity(r.image, gravity, width, height, nil)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Flip flips the image either horizontally or vertically based on the parameter
func (r *ImageRef) Flip(direction Direction) error {
defer runtime.KeepAlive(r)
out, err := vipsFlip(r.image, direction)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Recomb recombines the image bands using the matrix provided
func (r *ImageRef) Recomb(matrix [][]float64) error {
defer runtime.KeepAlive(r)
numBands := r.Bands()
// Ensure the provided matrix is 3x3
if len(matrix) != 3 || len(matrix[0]) != 3 || len(matrix[1]) != 3 || len(matrix[2]) != 3 {
return errors.New("Invalid recombination matrix")
}
// If the image is RGBA, expand the matrix to 4x4
if numBands == 4 {
matrix = append(matrix, []float64{0, 0, 0, 1})
for i := 0; i < 3; i++ {
matrix[i] = append(matrix[i], 0)
}
} else if numBands != 3 {
return errors.New("Unsupported number of bands")
}
// Flatten the matrix
matrixValues := make([]float64, 0, numBands*numBands)
for _, row := range matrix {
for _, value := range row {
matrixValues = append(matrixValues, value)
}
}
// Convert the Go slice to a C array and get its size
matrixPtr := unsafe.Pointer(&matrixValues[0])
matrixSize := C.size_t(len(matrixValues) * 8) // 8 bytes for each float64
// Create a VipsImage from the matrix in memory
matrixImage := C.vips_image_new_from_memory(matrixPtr, matrixSize, C.int(numBands), C.int(numBands), 1, C.VIPS_FORMAT_DOUBLE)
defer clearImage(matrixImage)
if matrixImage == nil {
return handleVipsError()
}
// Recombine the image using the matrix
out, err := vipsGenRecomb(r.image, matrixImage)
runtime.KeepAlive(matrixValues)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Rotate rotates the image by multiples of 90 degrees. To rotate by arbitrary angles use Similarity.
func (r *ImageRef) Rotate(angle Angle) error {
defer runtime.KeepAlive(r)
width := r.Width()
if r.Pages() > 1 && (angle == Angle90 || angle == Angle270) {
if angle == Angle270 {
if err := r.Flip(DirectionHorizontal); err != nil {
return err
}
}
if err := r.Grid(r.PageHeight(), r.Pages(), 1); err != nil {
return err
}
if angle == Angle270 {
if err := r.Flip(DirectionHorizontal); err != nil {
return err
}
}
}
out, err := vipsGenRot(r.image, angle)
if err != nil {
return err
}
r.setImage(out)
if r.Pages() > 1 && (angle == Angle90 || angle == Angle270) {
if err := r.SetPageHeight(width); err != nil {
return err
}
}
return nil
}
// Similarity lets you scale, offset and rotate images by arbitrary angles in a single operation while defining the
// color of new background pixels. If the input image has no alpha channel, the alpha on `backgroundColor` will be
// ignored. You can add an alpha channel to an image with `BandJoinConst` (e.g. `img.BandJoinConst([]float64{255})`) or
// AddAlpha.
func (r *ImageRef) Similarity(scale float64, angle float64, backgroundColor *ColorRGBA,
idx float64, idy float64, odx float64, ody float64) error {
defer runtime.KeepAlive(r)
out, err := vipsSimilarity(r.image, scale, angle, backgroundColor, idx, idy, odx, ody)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Grid tiles the image pages into a matrix across*down
func (r *ImageRef) Grid(tileHeight, across, down int) error {
defer runtime.KeepAlive(r)
out, err := vipsGenGrid(r.image, tileHeight, across, down)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// SmartCrop will crop the image based on interesting factor
func (r *ImageRef) SmartCrop(width int, height int, interesting Interesting) error {
defer runtime.KeepAlive(r)
out, err := vipsSmartCrop(r.image, width, height, interesting)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Crop will crop the image based on coordinate and box size
func (r *ImageRef) Crop(left int, top int, width int, height int) error {
defer runtime.KeepAlive(r)
out, err := vipsCrop(r.image, left, top, width, height)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Label overlays a label on top of the image
func (r *ImageRef) Label(labelParams *LabelParams) error {
defer runtime.KeepAlive(r)
out, err := labelImage(r.image, labelParams)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Replicate repeats an image many times across and down
func (r *ImageRef) Replicate(across int, down int) error {
defer runtime.KeepAlive(r)
out, err := vipsGenReplicate(r.image, across, down)
if err != nil {
return err
}
r.setImage(out)
return nil
}
// Pixelate applies a simple pixelate filter to the image
func Pixelate(imageRef *ImageRef, factor float64) (err error) {
if factor < 1 {
return errors.New("factor must be greater then 1")
}
width := imageRef.Width()
height := imageRef.Height()
if err = imageRef.Resize(1/factor, KernelAuto); err != nil {
return
}
hScale := float64(width) / float64(imageRef.Width())
vScale := float64(height) / float64(imageRef.Height())
if err = imageRef.ResizeWithVScale(hScale, vScale, KernelNearest); err != nil {
return
}
return
}
+20
View File
@@ -0,0 +1,20 @@
package vips
// #include <vips/vips.h>
import "C"
import "unsafe"
// NewInterpolate creates a VipsInterpolate from a name string.
// Common names: "nearest", "bilinear", "bicubic", "nohalo", "vsqbs", "lbb".
// The caller should call g_object_unref on the result when done, or pass it
// to a generated operation (which does not take ownership).
func NewInterpolate(name string) (*C.VipsInterpolate, error) {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
interp := C.vips_interpolate_new(cName)
if interp == nil {
return nil, handleVipsError()
}
return interp, nil
}
+62
View File
@@ -0,0 +1,62 @@
package vips
// #include <vips/vips.h>
// #include <stdlib.h>
import "C"
import (
"reflect"
"unsafe"
)
func freeCString(s *C.char) {
C.free(unsafe.Pointer(s))
}
func gFreePointer(ref unsafe.Pointer) {
C.g_free(C.gpointer(ref))
}
func boolToInt(b bool) int {
if b {
return 1
}
return 0
}
func toGboolean(b bool) C.gboolean {
if b {
return C.gboolean(1)
}
return C.gboolean(0)
}
func fromGboolean(b C.gboolean) bool {
return b != 0
}
func fromCArrayInt(out *C.int, n int) []int {
var result = make([]int, n)
var data []C.int
sh := (*reflect.SliceHeader)(unsafe.Pointer(&data))
sh.Data = uintptr(unsafe.Pointer(out))
sh.Len = n
sh.Cap = n
for i := range data {
result[i] = int(data[i])
}
return result
}
func fromCArrayDouble(out *C.double, n int) []float64 {
if out == nil || n <= 0 {
return nil
}
data := make([]float64, n)
for i := 0; i < n; i++ {
data[i] = float64(*(*C.double)(unsafe.Pointer(uintptr(unsafe.Pointer(out)) + uintptr(i)*unsafe.Sizeof(C.double(0)))))
}
return data
}
+2
View File
@@ -0,0 +1,2 @@
#include <stdlib.h>
#include <vips/vips.h>
+98
View File
@@ -0,0 +1,98 @@
package vips
// #include <glib.h>
import "C"
import (
"log"
)
// LogLevel is the enum controlling logging message verbosity.
type LogLevel int
// The logging verbosity levels classify and filter logging messages.
// From most to least verbose, they are debug, info, message, warning, critical and error.
const (
LogLevelError LogLevel = C.G_LOG_LEVEL_ERROR
LogLevelCritical LogLevel = C.G_LOG_LEVEL_CRITICAL
LogLevelWarning LogLevel = C.G_LOG_LEVEL_WARNING
LogLevelMessage LogLevel = C.G_LOG_LEVEL_MESSAGE
LogLevelInfo LogLevel = C.G_LOG_LEVEL_INFO
LogLevelDebug LogLevel = C.G_LOG_LEVEL_DEBUG
)
// Three global variables which keep state of the current logging handler
// function, desired verbosity for logging and whether defaults have been
// overridden. Set by LoggingSettings()
var (
currentLoggingHandlerFunction LoggingHandlerFunction
currentLoggingVerbosity LogLevel
currentLoggingOverridden bool
)
// govipsLoggingHandler is the private bridge function exported to the C library
// and called by glib and libvips for each logging message. It will call govipsLog
// which in turn will filter based on verbosity and direct the messages to the
// currently chosen LoggingHandlerFunction.
//
//export govipsLoggingHandler
func govipsLoggingHandler(messageDomain *C.char, messageLevel C.int, message *C.char) {
govipsLog(C.GoString(messageDomain), LogLevel(messageLevel), C.GoString(message))
}
// LoggingHandlerFunction is a function which will be called for each log message.
// By default, govips sends logging messages to os.Stderr. If you want to log elsewhere
// such as to a file or to a state variable which you inspect yourself, define a new
// logging handler function and set it via LoggingSettings().
type LoggingHandlerFunction func(messageDomain string, messageLevel LogLevel, message string)
// LoggingSettings sets the logging handler and logging verbosity for govips.
// The handler function is the function which will be called for each log message.
// You can define one yourself to log somewhere else besides the default (stderr).
// Use nil as handler to use standard logging handler.
// Verbosity is the minimum logLevel you want to log. Default is logLevelInfo
// due to backwards compatibility but it's quite verbose for a library.
// Suggest setting it to at least logLevelWarning. Use logLevelDebug for debugging.
func LoggingSettings(handler LoggingHandlerFunction, verbosity LogLevel) {
currentLoggingOverridden = true
govipsLoggingSettings(handler, verbosity)
}
func govipsLoggingSettings(handler LoggingHandlerFunction, verbosity LogLevel) {
if handler == nil {
currentLoggingHandlerFunction = defaultLoggingHandlerFunction
} else {
currentLoggingHandlerFunction = handler
}
currentLoggingVerbosity = verbosity
// TODO turn on debugging in libvips and redirect to handler when setting verbosity to debug
// This way debugging information would go to the same channel as all other logging
}
func defaultLoggingHandlerFunction(messageDomain string, messageLevel LogLevel, message string) {
var messageLevelDescription string
switch messageLevel {
case LogLevelError:
messageLevelDescription = "error"
case LogLevelCritical:
messageLevelDescription = "critical"
case LogLevelWarning:
messageLevelDescription = "warning"
case LogLevelMessage:
messageLevelDescription = "message"
case LogLevelInfo:
messageLevelDescription = "info"
case LogLevelDebug:
messageLevelDescription = "debug"
}
log.Printf("[%v.%v] %v", messageDomain, messageLevelDescription, message)
}
// govipsLog is the default function used to log debug or error messages internally in govips.
// It's used by all govips functionality directly, as well as by glib and libvips via the C bridge.
func govipsLog(messageDomain string, messageLevel LogLevel, message string) {
if messageLevel <= currentLoggingVerbosity {
currentLoggingHandlerFunction(messageDomain, messageLevel, message)
}
}
+57
View File
@@ -0,0 +1,57 @@
package vips
import "math"
// Scalar is the basic scalar measurement of an image's height, width or offset coordinate.
type Scalar struct {
Value float64
Relative bool
}
// ValueOf takes a floating point value and returns a corresponding Scalar struct
func ValueOf(value float64) Scalar {
return Scalar{value, false}
}
// IsZero checks whether the associated Scalar's value is zero.
func (s *Scalar) IsZero() bool {
return s.Value == 0 && !s.Relative
}
// SetInt sets an integer value for the associated Scalar.
func (s *Scalar) SetInt(value int) {
s.Set(float64(value))
}
// Set sets a float value for the associated Scalar.
func (s *Scalar) Set(value float64) {
s.Value = value
s.Relative = false
}
// SetScale sets a float value for the associated Scalar and makes it relative.
func (s *Scalar) SetScale(f float64) {
s.Value = f
s.Relative = true
}
// Get returns the value of the scalar. Either absolute, or if relative, multiplied by the base given as parameter.
func (s *Scalar) Get(base int) float64 {
if s.Relative {
return s.Value * float64(base)
}
return s.Value
}
// GetRounded returns the value of the associated Scalar, rounded to the nearest integer, if absolute.
// If the Scalar is relative, it will be multiplied by the supplied base parameter.
func (s *Scalar) GetRounded(base int) int {
return roundFloat(s.Get(base))
}
func roundFloat(f float64) int {
if f < 0 {
return int(math.Ceil(f - 0.5))
}
return int(math.Floor(f + 0.5))
}
+503
View File
@@ -0,0 +1,503 @@
// operations.c - Hand-written C bridge functions for libvips operations
#include "lang.h"
#include "operations.h"
#include <unistd.h>
static int is_16bit(VipsInterpretation interpretation);
// Arithmetic
int find_trim(VipsImage *in, int *left, int *top, int *width, int *height,
double threshold, double r, double g, double b) {
if (in->Type == VIPS_INTERPRETATION_RGB16 || in->Type == VIPS_INTERPRETATION_GREY16) {
r = 65535 * r / 255;
g = 65535 * g / 255;
b = 65535 * b / 255;
}
double background[3] = {r, g, b};
VipsArrayDouble *vipsBackground = vips_array_double_new(background, 3);
int code = vips_find_trim(in, left, top, width, height, "threshold", threshold, "background", vipsBackground, NULL);
vips_area_unref(VIPS_AREA(vipsBackground));
return code;
}
int getpoint(VipsImage *in, double **vector, int n, int x, int y) {
return vips_getpoint(in, vector, &n, x, y, NULL);
}
int minOp(VipsImage *in, double *out, int *x, int *y, int size) {
return vips_min(in, out, "x", x, "y", y, "size", size, NULL);
}
// Color
int is_colorspace_supported(VipsImage *in) {
return vips_colourspace_issupported(in) ? 1 : 0;
}
int to_colorspace(VipsImage *in, VipsImage **out, VipsInterpretation space) {
return vips_colourspace(in, out, space, NULL);
}
// https://libvips.github.io/libvips/API/8.6/libvips-colour.html#vips-icc-transform
int icc_transform(VipsImage *in, VipsImage **out, const char *output_profile, const char *input_profile, VipsIntent intent,
int depth, gboolean embedded) {
return vips_icc_transform(
in, out, output_profile,
"input_profile", input_profile ? input_profile : "none",
"intent", intent,
"depth", depth ? depth : 8,
"embedded", embedded,
NULL);
}
// Conversion
int embed_image(VipsImage *in, VipsImage **out, int left, int top, int width,
int height, int extend) {
return vips_embed(in, out, left, top, width, height, "extend", extend, NULL);
}
int embed_image_background(VipsImage *in, VipsImage **out, int left, int top, int width,
int height, double r, double g, double b, double a) {
double background[3] = {r, g, b};
double backgroundRGBA[4] = {r, g, b, a};
VipsArrayDouble *vipsBackground;
if (in->Bands <= 3) {
vipsBackground = vips_array_double_new(background, 3);
} else {
vipsBackground = vips_array_double_new(backgroundRGBA, 4);
}
int code = vips_embed(in, out, left, top, width, height,
"extend", VIPS_EXTEND_BACKGROUND, "background", vipsBackground, NULL);
vips_area_unref(VIPS_AREA(vipsBackground));
return code;
}
int embed_multi_page_image(VipsImage *in, VipsImage **out, int left, int top, int width,
int height, int extend) {
VipsObject *base = VIPS_OBJECT(vips_image_new());
int page_height = vips_image_get_page_height(in);
int in_width = in->Xsize;
int n_pages = in->Ysize / page_height;
VipsImage **page = (VipsImage **) vips_object_local_array(base, n_pages);
VipsImage **embedded_page = (VipsImage **) vips_object_local_array(base, n_pages);
VipsImage **copy = (VipsImage **) vips_object_local_array(base, 1);
// split image into cropped frames
for (int i = 0; i < n_pages; i++) {
if (
vips_extract_area(in, &page[i], 0, page_height * i, in_width, page_height, NULL) ||
vips_embed(page[i], &embedded_page[i], left, top, width, height, "extend", extend, NULL)
) {
g_object_unref(base);
return -1;
}
}
// reassemble frames and set page height
// copy before modifying metadata
if(
vips_arrayjoin(embedded_page, &copy[0], n_pages, "across", 1, NULL) ||
vips_copy(copy[0], out, NULL)
) {
g_object_unref(base);
return -1;
}
vips_image_set_int(*out, VIPS_META_PAGE_HEIGHT, height);
g_object_unref(base);
return 0;
}
int embed_multi_page_image_background(VipsImage *in, VipsImage **out, int left, int top, int width,
int height, double r, double g, double b, double a) {
double background[3] = {r, g, b};
double backgroundRGBA[4] = {r, g, b, a};
VipsArrayDouble *vipsBackground;
if (in->Bands <= 3) {
vipsBackground = vips_array_double_new(background, 3);
} else {
vipsBackground = vips_array_double_new(backgroundRGBA, 4);
}
VipsObject *base = VIPS_OBJECT(vips_image_new());
int page_height = vips_image_get_page_height(in);
int in_width = in->Xsize;
int n_pages = in->Ysize / page_height;
VipsImage **page = (VipsImage **) vips_object_local_array(base, n_pages);
VipsImage **embedded_page = (VipsImage **) vips_object_local_array(base, n_pages);
VipsImage **copy = (VipsImage **) vips_object_local_array(base, 1);
// split image into cropped frames
for (int i = 0; i < n_pages; i++) {
if (
vips_extract_area(in, &page[i], 0, page_height * i, in_width, page_height, NULL) ||
vips_embed(page[i], &embedded_page[i], left, top, width, height,
"extend", VIPS_EXTEND_BACKGROUND, "background", vipsBackground, NULL)
) {
vips_area_unref(VIPS_AREA(vipsBackground));
g_object_unref(base);
return -1;
}
}
// reassemble frames and set page height
// copy before modifying metadata
if(
vips_arrayjoin(embedded_page, &copy[0], n_pages, "across", 1, NULL) ||
vips_copy(copy[0], out, NULL)
) {
vips_area_unref(VIPS_AREA(vipsBackground));
g_object_unref(base);
return -1;
}
vips_image_set_int(*out, VIPS_META_PAGE_HEIGHT, height);
vips_area_unref(VIPS_AREA(vipsBackground));
g_object_unref(base);
return 0;
}
int similarity(VipsImage *in, VipsImage **out, double scale, double angle,
double r, double g, double b, double a, double idx, double idy,
double odx, double ody) {
if (is_16bit(in->Type)) {
r = 65535 * r / 255;
g = 65535 * g / 255;
b = 65535 * b / 255;
a = 65535 * a / 255;
}
double background[3] = {r, g, b};
double backgroundRGBA[4] = {r, g, b, a};
VipsArrayDouble *vipsBackground;
// Ignore the alpha channel if the image doesn't have one
if (in->Bands <= 3) {
vipsBackground = vips_array_double_new(background, 3);
} else {
vipsBackground = vips_array_double_new(backgroundRGBA, 4);
}
int code = vips_similarity(in, out, "scale", scale, "angle", angle,
"background", vipsBackground, "idx", idx, "idy",
idy, "odx", odx, "ody", ody, NULL);
vips_area_unref(VIPS_AREA(vipsBackground));
return code;
}
int crop(VipsImage *in, VipsImage **out, int left, int top,
int width, int height) {
// resolve image pages
int page_height = vips_image_get_page_height(in);
int n_pages = in->Ysize / page_height;
if (n_pages <= 1) {
return vips_crop(in, out, left, top, width, height, NULL);
}
int in_width = in->Xsize;
VipsObject *base = VIPS_OBJECT(vips_image_new());
VipsImage **page = (VipsImage **) vips_object_local_array(base, n_pages);
VipsImage **cropped_page = (VipsImage **) vips_object_local_array(base, n_pages);
VipsImage **copy = (VipsImage **) vips_object_local_array(base, 1);
// split image into cropped frames
for (int i = 0; i < n_pages; i++) {
if (
vips_extract_area(in, &page[i], 0, page_height * i, in_width, page_height, NULL) ||
vips_crop(page[i], &cropped_page[i], left, top, width, height, NULL)
) {
g_object_unref(base);
return -1;
}
}
// reassemble frames and set page height
// copy before modifying metadata
if(
vips_arrayjoin(cropped_page, &copy[0], n_pages, "across", 1, NULL) ||
vips_copy(copy[0], out, NULL)
) {
g_object_unref(base);
return -1;
}
vips_image_set_int(*out, VIPS_META_PAGE_HEIGHT, height);
g_object_unref(base);
return 0;
}
static int is_16bit(VipsInterpretation interpretation) {
return interpretation == VIPS_INTERPRETATION_RGB16 ||
interpretation == VIPS_INTERPRETATION_GREY16;
}
int composite_image(VipsImage **in, VipsImage **out, int n, int *mode, int *x,
int *y) {
VipsArrayInt *xs = vips_array_int_new(x, n - 1);
VipsArrayInt *ys = vips_array_int_new(y, n - 1);
int code = vips_composite(in, out, n, mode, "x", xs, "y", ys, NULL);
vips_area_unref(VIPS_AREA(xs));
vips_area_unref(VIPS_AREA(ys));
return code;
}
int join(VipsImage *in1, VipsImage *in2, VipsImage **out, int direction) {
return vips_join(in1, in2, out, direction, NULL);
}
int add_alpha(VipsImage *in, VipsImage **out) {
return vips_addalpha(in, out, NULL);
}
// Create
// https://libvips.github.io/libvips/API/current/libvips-create.html#vips-text
int text(VipsImage **out, TextOptions *o) {
return vips_text(out, o->Text, "font", o->Font, "width", o->Width, "height", o->Height, "align", o->Align,
"dpi", o->DPI, "rgba", o->RGBA, "justify", o->Justify, "spacing", o->Spacing, "wrap", o->Wrap, NULL);
}
// Draw
int draw_rect(VipsImage *in, double r, double g, double b, double a, int left,
int top, int width, int height, int fill) {
if (is_16bit(in->Type)) {
r = 65535 * r / 255;
g = 65535 * g / 255;
b = 65535 * b / 255;
a = 65535 * a / 255;
}
double background[3] = {r, g, b};
double backgroundRGBA[4] = {r, g, b, a};
if (in->Bands <= 3) {
return vips_draw_rect(in, background, 3, left, top, width, height, "fill",
fill, NULL);
} else {
return vips_draw_rect(in, backgroundRGBA, 4, left, top, width, height,
"fill", fill, NULL);
}
}
// Header
unsigned long has_icc_profile(VipsImage *in) {
return vips_image_get_typeof(in, VIPS_META_ICC_NAME);
}
unsigned long get_icc_profile(VipsImage *in, const void **data,
size_t *dataLength) {
return image_get_blob(in, VIPS_META_ICC_NAME, data, dataLength);
}
gboolean remove_icc_profile(VipsImage *in) {
return vips_image_remove(in, VIPS_META_ICC_NAME);
}
unsigned long has_iptc(VipsImage *in) {
return vips_image_get_typeof(in, VIPS_META_IPTC_NAME);
}
char **image_get_fields(VipsImage *in) { return vips_image_get_fields(in); }
void image_set_string(VipsImage *in, const char *name, const char *str) {
vips_image_set_string(in, name, str);
}
unsigned long image_get_string(VipsImage *in, const char *name,
const char **out) {
return vips_image_get_string(in, name, out);
}
unsigned long image_get_as_string(VipsImage *in, const char *name, char **out) {
return vips_image_get_as_string(in, name, out);
}
void remove_field(VipsImage *in, char *field) { vips_image_remove(in, field); }
int get_meta_orientation(VipsImage *in) {
int orientation = 0;
if (vips_image_get_typeof(in, VIPS_META_ORIENTATION) != 0) {
vips_image_get_int(in, VIPS_META_ORIENTATION, &orientation);
}
return orientation;
}
void remove_meta_orientation(VipsImage *in) {
vips_image_remove(in, VIPS_META_ORIENTATION);
}
void set_meta_orientation(VipsImage *in, int orientation) {
vips_image_set_int(in, VIPS_META_ORIENTATION, orientation);
}
// https://libvips.github.io/libvips/API/current/libvips-header.html#vips-image-get-n-pages
int get_image_n_pages(VipsImage *in) {
int n_pages = 0;
n_pages = vips_image_get_n_pages(in);
return n_pages;
}
void set_image_n_pages(VipsImage *in, int n_pages) {
vips_image_set_int(in, VIPS_META_N_PAGES, n_pages);
}
// https://www.libvips.org/API/current/libvips-header.html#vips-image-get-page-height
int get_page_height(VipsImage *in) {
int page_height = 0;
page_height = vips_image_get_page_height(in);
return page_height;
}
void set_page_height(VipsImage *in, int height) {
vips_image_set_int(in, VIPS_META_PAGE_HEIGHT, height);
}
int get_meta_loader(const VipsImage *in, const char **out) {
return vips_image_get_string(in, VIPS_META_LOADER, out);
}
int get_background(VipsImage *in, double **out, int *n) {
return vips_image_get_array_double(in, "background", out, n);
}
int get_image_delay(VipsImage *in, int **out) {
return vips_image_get_array_int(in, "delay", out, NULL);
}
void set_image_delay(VipsImage *in, const int *array, int n) {
vips_image_set_array_int(in, "delay", array, n);
}
int get_image_loop(VipsImage *in) {
int loop = 0;
if (vips_image_get_typeof(in, "loop") != 0) {
vips_image_get_int(in, "loop", &loop);
}
return loop;
}
void set_image_loop(VipsImage *in, int loop) {
vips_image_set_int(in, "loop", loop);
}
void image_set_double(VipsImage *in, const char *name, double i) {
vips_image_set_double(in, name, i);
}
unsigned long image_get_double(VipsImage *in, const char *name, double *out) {
return vips_image_get_double(in, name, out);
}
void image_set_int(VipsImage *in, const char *name, int i) {
vips_image_set_int(in, name, i);
}
unsigned long image_get_int(VipsImage *in, const char *name, int *out) {
return vips_image_get_int(in, name, out);
}
void image_set_blob(VipsImage *in, const char *name, const void *data,
size_t dataLength) {
vips_image_set_blob_copy(in, name, data, dataLength);
}
unsigned long image_get_blob(VipsImage *in, const char *name, const void **data,
size_t *dataLength) {
if (vips_image_get_typeof(in, name) == 0) {
return 0;
}
if (vips_image_get_blob(in, name, data, dataLength)) {
return -1;
}
return 0;
}
// Label
int label(VipsImage *in, VipsImage **out, LabelOptions *o) {
double ones[3] = {1, 1, 1};
VipsImage *base = vips_image_new();
VipsImage **t = (VipsImage **)vips_object_local_array(VIPS_OBJECT(base), 9);
if (vips_text(&t[0], o->Text, "font", o->Font, "width", o->Width, "height",
o->Height, "align", o->Align, NULL) ||
vips_linear1(t[0], &t[1], o->Opacity, 0.0, NULL) ||
vips_cast(t[1], &t[2], VIPS_FORMAT_UCHAR, NULL) ||
vips_embed(t[2], &t[3], o->OffsetX, o->OffsetY, t[2]->Xsize + o->OffsetX,
t[2]->Ysize + o->OffsetY, NULL)) {
g_object_unref(base);
return -1;
}
if (vips_black(&t[4], 1, 1, NULL) ||
vips_linear(t[4], &t[5], ones, o->Color, 3, NULL) ||
vips_cast(t[5], &t[6], VIPS_FORMAT_UCHAR, NULL) ||
vips_copy(t[6], &t[7], "interpretation", in->Type, NULL) ||
vips_embed(t[7], &t[8], 0, 0, in->Xsize, in->Ysize, "extend",
VIPS_EXTEND_COPY, NULL)) {
g_object_unref(base);
return -1;
}
if (vips_ifthenelse(t[3], t[8], in, out, "blend", TRUE, NULL)) {
g_object_unref(base);
return -1;
}
g_object_unref(base);
return 0;
}
// Resample
int resize_image(VipsImage *in, VipsImage **out, double scale, gdouble vscale,
int kernel) {
if (vscale > 0) {
return vips_resize(in, out, scale, "vscale", vscale, "kernel", kernel,
NULL);
}
return vips_resize(in, out, scale, "kernel", kernel, NULL);
}
int thumbnail(const char *filename, VipsImage **out,
int width, int height, int crop, int size) {
return vips_thumbnail(filename, out, width, "height", height,
"crop", crop, "size", size, NULL);
}
int thumbnail_image(VipsImage *in, VipsImage **out, int width, int height,
int crop, int size) {
return vips_thumbnail_image(in, out, width, "height", height, "crop", crop,
"size", size, NULL);
}
int thumbnail_buffer_with_option(void *buf, size_t len, VipsImage **out,
int width, int height, int crop, int size,
const char *option_string) {
return vips_thumbnail_buffer(buf, len, out, width, "height", height,
"crop", crop, "size", size,
"option_string", option_string, NULL);
}
int thumbnail_buffer(void *buf, size_t len, VipsImage **out,
int width, int height, int crop, int size) {
return vips_thumbnail_buffer(buf, len, out, width, "height", height,
"crop", crop, "size", size, NULL);
}
File diff suppressed because it is too large Load Diff
+149
View File
@@ -0,0 +1,149 @@
// operations.h - Hand-written C bridge functions for libvips operations
#ifndef OPERATIONS_H
#define OPERATIONS_H
#include <stdlib.h>
#include <vips/vips.h>
#include <vips/foreign.h>
// Arithmetic
// https://libvips.github.io/libvips/API/current/libvips-arithmetic.html
int find_trim(VipsImage *in, int *left, int *top, int *width, int *height,
double threshold, double r, double g, double b);
int getpoint(VipsImage *in, double **vector, int n, int x, int y);
int minOp(VipsImage *in, double *out, int *x, int *y, int size);
// Color
// https://libvips.github.io/libvips/API/current/libvips-colour.html
int is_colorspace_supported(VipsImage *in);
int to_colorspace(VipsImage *in, VipsImage **out, VipsInterpretation space);
int icc_transform(VipsImage *in, VipsImage **out, const char *output_profile,
const char *input_profile, VipsIntent intent, int depth,
gboolean embedded);
// Conversion
// https://libvips.github.io/libvips/API/current/libvips-conversion.html
int embed_image(VipsImage *in, VipsImage **out, int left, int top, int width,
int height, int extend);
int embed_image_background(VipsImage *in, VipsImage **out, int left, int top,
int width, int height, double r, double g, double b, double a);
int embed_multi_page_image(VipsImage *in, VipsImage **out, int left, int top,
int width, int height, int extend);
int embed_multi_page_image_background(VipsImage *in, VipsImage **out, int left,
int top, int width, int height, double r, double g, double b,
double a);
int crop(VipsImage *in, VipsImage **out, int left, int top, int width,
int height);
int similarity(VipsImage *in, VipsImage **out, double scale, double angle,
double r, double g, double b, double a, double idx, double idy,
double odx, double ody);
int composite_image(VipsImage **in, VipsImage **out, int n, int *mode, int *x,
int *y);
int join(VipsImage *in1, VipsImage *in2, VipsImage **out, int direction);
int add_alpha(VipsImage *in, VipsImage **out);
// Create
// https://libvips.github.io/libvips/API/current/libvips-create.html
typedef struct {
const char *Text;
const char *Font;
int Width;
int Height;
int DPI;
gboolean RGBA;
gboolean Justify;
int Spacing;
VipsAlign Align;
VipsTextWrap Wrap;
} TextOptions;
int text(VipsImage **out, TextOptions *o);
// Draw
// https://libvips.github.io/libvips/API/current/libvips-draw.html
int draw_rect(VipsImage *in, double r, double g, double b, double a, int left,
int top, int width, int height, int fill);
// Header
// https://libvips.github.io/libvips/API/current/libvips-header.html
unsigned long has_icc_profile(VipsImage *in);
unsigned long get_icc_profile(VipsImage *in, const void **data,
size_t *dataLength);
int remove_icc_profile(VipsImage *in);
unsigned long has_iptc(VipsImage *in);
char **image_get_fields(VipsImage *in);
void image_set_string(VipsImage *in, const char *name, const char *str);
unsigned long image_get_string(VipsImage *in, const char *name,
const char **out);
unsigned long image_get_as_string(VipsImage *in, const char *name, char **out);
void remove_field(VipsImage *in, char *field);
int get_meta_orientation(VipsImage *in);
void remove_meta_orientation(VipsImage *in);
void set_meta_orientation(VipsImage *in, int orientation);
int get_image_n_pages(VipsImage *in);
void set_image_n_pages(VipsImage *in, int n_pages);
int get_page_height(VipsImage *in);
void set_page_height(VipsImage *in, int height);
int get_meta_loader(const VipsImage *in, const char **out);
int get_image_delay(VipsImage *in, int **out);
void set_image_delay(VipsImage *in, const int *array, int n);
int get_image_loop(VipsImage *in);
void set_image_loop(VipsImage *in, int loop);
int get_background(VipsImage *in, double **out, int *n);
void image_set_blob(VipsImage *in, const char *name, const void *data,
size_t dataLength);
unsigned long image_get_blob(VipsImage *in, const char *name, const void **data,
size_t *dataLength);
void image_set_double(VipsImage *in, const char *name, double i);
unsigned long image_get_double(VipsImage *in, const char *name, double *out);
void image_set_int(VipsImage *in, const char *name, int i);
unsigned long image_get_int(VipsImage *in, const char *name, int *out);
// Label
typedef struct {
const char *Text;
const char *Font;
int Width;
int Height;
int OffsetX;
int OffsetY;
VipsAlign Align;
int DPI;
int Margin;
float Opacity;
double Color[3];
} LabelOptions;
int label(VipsImage *in, VipsImage **out, LabelOptions *o);
// Resample
// https://libvips.github.io/libvips/API/current/libvips-resample.html
int resize_image(VipsImage *in, VipsImage **out, double scale, gdouble vscale,
int kernel);
int thumbnail(const char *filename, VipsImage **out, int width, int height,
int crop, int size);
int thumbnail_image(VipsImage *in, VipsImage **out, int width, int height,
int crop, int size);
int thumbnail_buffer(void *buf, size_t len, VipsImage **out, int width,
int height, int crop, int size);
int thumbnail_buffer_with_option(void *buf, size_t len, VipsImage **out,
int width, int height, int crop, int size,
const char *option_string);
#endif // OPERATIONS_H
+56
View File
@@ -0,0 +1,56 @@
package vips
import "sync"
// RuntimeStats is a data structure to house a map of govips operation counts
type RuntimeStats struct {
OperationCounts map[string]int64
}
var (
operationCounter chan string
runtimeStats *RuntimeStats
statLock sync.RWMutex
)
func incOpCounter(op string) {
if operationCounter != nil {
operationCounter <- op
}
}
func collectStats() chan struct{} {
operationCounter = make(chan string, 100)
done := make(chan struct{})
exit := false
go func() {
for !exit {
select {
case op := <-operationCounter:
statLock.Lock()
runtimeStats.OperationCounts[op] = runtimeStats.OperationCounts[op] + 1
statLock.Unlock()
case <-done:
exit = true
break
}
}
}()
return done
}
// ReadRuntimeStats returns operation counts for govips
func ReadRuntimeStats(stats *RuntimeStats) {
statLock.RLock()
defer statLock.RUnlock()
stats.OperationCounts = make(map[string]int64)
for k, v := range runtimeStats.OperationCounts {
stats.OperationCounts[k] = v
}
}
func init() {
runtimeStats = &RuntimeStats{
OperationCounts: make(map[string]int64),
}
}
@@ -0,0 +1,6 @@
package vips
// relative to "/vips/.."
const (
resources = "../resources/"
)