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
+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/"
)