summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPierre Joye <pierre.php@gmail.com>2021-08-13 16:54:21 +0700
committerPierre Joye <pierre.php@gmail.com>2021-08-13 16:54:21 +0700
commit521d7d68cc40bed34b3d10f3bd7f14e9ff4141aa (patch)
treee2dd2d39cf931a41b1f23eba1c1b45e5ad08629b
parentca9865e67b3153b079bcfcd65eced1d769fe1d4c (diff)
downloadlibgd-features/surface.tar.gz
add HIEF support for surfacefeatures/surface
-rw-r--r--src/gd.h17
-rw-r--r--src/gd_heif.c380
2 files changed, 387 insertions, 10 deletions
diff --git a/src/gd.h b/src/gd.h
index e28f5e3..01de17e 100644
--- a/src/gd.h
+++ b/src/gd.h
@@ -2185,6 +2185,23 @@ gdTransformAffineCopy(gdImagePtr dst, int x0, int y0, int x1, int y1,
BGD_DECLARE(gdSurfacePtr)
gdSurfaceCreateFromJpegCtxEx(gdIOCtx *infile, int ignore_warning);
+ BGD_DECLARE(void)
+ gdSurfaceHeif(gdSurfacePtr surface, FILE *outFile);
+ BGD_DECLARE(void *)
+ gdSurfaceHeifPtr(gdSurfacePtr surface, int *size);
+ BGD_DECLARE(void)
+ gdSurfaceHeifEx(gdSurfacePtr surface, FILE *outFile, int quality, gdHeifCodec codec, gdHeifChroma chroma);
+ BGD_DECLARE(void *)
+ gdSurfaceHeifPtrEx(gdSurfacePtr surface, int *size, int quality, gdHeifCodec codec, gdHeifChroma chroma);
+ BGD_DECLARE(void)
+ gdSurfaceHeifCtx(gdSurfacePtr surface, gdIOCtx *outfile, int quality, gdHeifCodec codec, gdHeifChroma chroma);
+ BGD_DECLARE(gdSurfacePtr)
+ gdSurfaceCreateFromHeif(FILE *inFile);
+ BGD_DECLARE(gdSurfacePtr)
+ gdSurfaceCreateFromHeifCtx(gdIOCtx *infile);
+
+
+
BGD_DECLARE(gdContextPtr)
gdContextCreate(gdSurfacePtr surface);
BGD_DECLARE(void)
diff --git a/src/gd_heif.c b/src/gd_heif.c
index 47ecd7a..91e29b6 100644
--- a/src/gd_heif.c
+++ b/src/gd_heif.c
@@ -10,9 +10,7 @@
#include <stdio.h>
-#include <math.h>
#include <string.h>
-#include <stdlib.h>
#include "gd.h"
#include "gd_errors.h"
#include "gdhelpers.h"
@@ -84,8 +82,13 @@ BGD_DECLARE(gdImagePtr) gdImageCreateFromHeif(FILE *inFile)
BGD_DECLARE(gdImagePtr) gdImageCreateFromHeifPtr(int size, void *data)
{
gdImagePtr im;
- gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0);
+ gdIOCtx *in;
+ if (size < 1) {
+ return NULL;
+ }
+
+ in = gdNewDynamicCtxEx(size, data, 0);
if (!in)
return NULL;
im = gdImageCreateFromHeifCtx(in);
@@ -110,6 +113,18 @@ static int _gdHeifCheckBrand(unsigned char *magic, gd_heif_brand expected_brand)
return GD_FALSE;
}
+static uint8_t _gd_heif_check_signature(gdIOCtx *infile, gd_heif_brand expected_brand) {
+ uint32_t magic_len;
+ uint8_t magic[GD_HEIF_HEADER];
+ magic_len = gdGetBuf(magic, GD_HEIF_HEADER, infile);
+ if (magic_len != GD_HEIF_HEADER || !_gdHeifCheckBrand(magic, expected_brand)) {
+ gd_error("gd-heif incorrect type of file\n");
+ return 0;
+ }
+ gdSeek(infile, 0);
+ return 1;
+}
+
static gdImagePtr _gdImageCreateFromHeifCtx(gdIOCtx *infile, gd_heif_brand expected_brand)
{
struct heif_context *heif_ctx;
@@ -120,19 +135,16 @@ static gdImagePtr _gdImageCreateFromHeifCtx(gdIOCtx *infile, gd_heif_brand expec
int width, height;
uint8_t *filedata = NULL;
uint8_t *rgba = NULL;
- unsigned char *read, *temp, magic[GD_HEIF_HEADER];
- int magic_len;
+ unsigned char *read, *temp;
size_t size = 0, n = GD_HEIF_ALLOC_STEP;
gdImagePtr im;
int x, y;
uint8_t *p;
- magic_len = gdGetBuf(magic, GD_HEIF_HEADER, infile);
- if (magic_len != GD_HEIF_HEADER || !_gdHeifCheckBrand(magic, expected_brand)) {
- gd_error("gd-heif incorrect type of file\n");
- return NULL;
+ if (_gd_heif_check_signature(infile, expected_brand) == 0) {
+ gd_error("gd-heif decode invalid signature\n");
+ return NULL;
}
- gdSeek(infile, 0);
while (n == GD_HEIF_ALLOC_STEP) {
temp = gdRealloc(filedata, size + GD_HEIF_ALLOC_STEP);
@@ -529,6 +541,354 @@ BGD_DECLARE(void *) gdImageHeifPtrEx(gdImagePtr im, int *size, int quality, gdHe
return rv;
}
+static inline int
+_to_premultiplied_channel (int alpha, int color)
+{
+ const int c = (alpha * color) + 128;
+ return ((c + (c >> 8)) >> 8);
+}
+
+static gdSurfacePtr _gdSurfaceCreateFromHeifCtx(gdIOCtx *infile, gd_heif_brand expected_brand)
+{
+ struct heif_context *heif_ctx;
+ struct heif_decoding_options *heif_dec_opts;
+ struct heif_image_handle *heif_imhandle;
+ struct heif_image *heif_im;
+ struct heif_error err;
+ uint32_t width, height;
+ uint8_t *filedata = NULL;
+ uint8_t *rgba = NULL;
+ unsigned char *read, *temp;
+ size_t size = 0, n = GD_HEIF_ALLOC_STEP;
+ gdSurfacePtr surface;
+ uint32_t x, y;
+ uint8_t *p;
+
+ if (_gd_heif_check_signature(infile, expected_brand) == 0) {
+ return NULL;
+ }
+ while (n == GD_HEIF_ALLOC_STEP) {
+ temp = gdRealloc(filedata, size + GD_HEIF_ALLOC_STEP);
+ if (temp) {
+ filedata = temp;
+ read = temp + size;
+ } else {
+ gdFree(filedata);
+ gd_error("gd-heif decode realloc failed\n");
+ return NULL;
+ }
+
+ n = gdGetBuf(read, GD_HEIF_ALLOC_STEP, infile);
+ if (n > 0) {
+ size += n;
+ }
+ }
+
+ heif_ctx = heif_context_alloc();
+ if (heif_ctx == NULL) {
+ gd_error("gd-heif could not allocate context\n");
+ gdFree(filedata);
+ return NULL;
+ }
+ err = heif_context_read_from_memory_without_copy(heif_ctx, filedata, size, NULL);
+ if (err.code != heif_error_Ok) {
+ gd_error("gd-heif context creation failed\n");
+ gdFree(filedata);
+ heif_context_free(heif_ctx);
+ return NULL;
+ }
+
+ heif_imhandle = NULL;
+ err = heif_context_get_primary_image_handle(heif_ctx, &heif_imhandle);
+ if (err.code != heif_error_Ok) {
+ gd_error("gd-heif cannot retreive handle\n");
+ gdFree(filedata);
+ heif_context_free(heif_ctx);
+ return NULL;
+ }
+
+ heif_im = NULL;
+ heif_dec_opts = heif_decoding_options_alloc();
+ if (heif_dec_opts == NULL) {
+ gd_error("gd-heif could not allocate decode options\n");
+ gdFree(filedata);
+ heif_image_handle_release(heif_imhandle);
+ heif_context_free(heif_ctx);
+ return NULL;
+ }
+
+ heif_dec_opts->convert_hdr_to_8bit = GD_TRUE;
+ heif_dec_opts->ignore_transformations = GD_TRUE;
+
+ err = heif_decode_image(heif_imhandle, &heif_im, heif_colorspace_RGB, heif_chroma_interleaved_RGBA, heif_dec_opts);
+ heif_decoding_options_free(heif_dec_opts);
+ if (err.code != heif_error_Ok) {
+ gd_error("gd-heif decoding failed\n");
+ gdFree(filedata);
+ heif_image_handle_release(heif_imhandle);
+ heif_context_free(heif_ctx);
+ return NULL;
+ }
+
+ width = heif_image_get_width(heif_im, heif_channel_interleaved);
+ height = heif_image_get_height(heif_im, heif_channel_interleaved);
+ surface = gdSurfaceCreate(width, height, GD_SURFACE_ARGB32);
+ if (!surface) {
+ gdFree(filedata);
+ heif_image_release(heif_im);
+ heif_image_handle_release(heif_imhandle);
+ heif_context_free(heif_ctx);
+ return NULL;
+ }
+
+ rgba = (uint8_t *)heif_image_get_plane_readonly(heif_im, heif_channel_interleaved, NULL);
+ if (!rgba) {
+ gd_error("gd-heif cannot get image plane\n");
+ gdFree(filedata);
+ heif_image_release(heif_im);
+ heif_image_handle_release(heif_imhandle);
+ heif_context_free(heif_ctx);
+ gdSurfaceDestroy(surface);
+ return NULL;
+ }
+ uint32_t *surface_data = (uint32_t *)gdSurfaceGetData(surface);
+ const uint32_t stride_32 = gdSurfaceGetStride(surface)/4;
+
+ for (y = 0, p = rgba; y < height; y++) {
+ uint32_t *row = surface_data + y * stride_32;
+ for (x = 0; x < width; x++) {
+ register uint8_t
+ r = *(p++),
+ g = *(p++),
+ b = *(p++),
+ a = *(p++);
+ /* opacity already converted, only need to premultiply rgb */
+ if (a != 0xff) {
+ r = _to_premultiplied_channel (a, r);
+ g = _to_premultiplied_channel (a, g);
+ b = _to_premultiplied_channel (a, b);
+ *(row+x) = ((uint32_t)a << 24) | (r << 16) | (g << 8) | (b << 0);
+ continue;
+ }
+ *(row+x) = ((uint32_t)0xFF000000) | (r << 16) | (g << 8) | (b << 0);
+ }
+ }
+
+ gdFree(filedata);
+ heif_image_release(heif_im);
+ heif_image_handle_release(heif_imhandle);
+ heif_context_free(heif_ctx);
+
+ return surface;
+}
+
+/*
+ Function: gdSurfaceCreateFromHeifCtx
+
+ See <gdSurfaceCreateFromHeif>.
+*/
+BGD_DECLARE(gdSurfacePtr) gdSurfaceCreateFromHeifCtx(gdIOCtx *infile)
+{
+ return _gdSurfaceCreateFromHeifCtx(infile, GD_HEIF_BRAND_AVIF | GD_HEIF_BRAND_MIF1 | GD_HEIF_BRAND_HEIC | GD_HEIF_BRAND_HEIX);
+}
+
+/*
+ Function: gdSurfaceCreateFromHeif
+
+ <gdSurfaceCreateFromHeif> is called to load truecolor images from
+ HEIF format files. Invoke <gdSurfaceCreateFromHeif> with an
+ already opened pointer to a file containing the desired
+ image. <gdSurfaceCreateFromHeif> returns a <gdImagePtr> to the new
+ truecolor image, or NULL if unable to load the image (most often
+ because the file is corrupt or does not contain a HEIF
+ image). <gdSurfaceCreateFromHeif> does not close the file.
+
+ You can inspect the sx and sy members of the image to determine
+ its size. The image must eventually be destroyed using
+ <gdImageDestroy>.
+
+ *The returned image is always a truecolor image.*
+
+ Parameters:
+
+ infile - The input FILE pointer.
+
+ Returns:
+
+ A pointer to the new *truecolor* image. This will need to be
+ destroyed with <gdImageDestroy> once it is no longer needed.
+
+ On error, returns NULL.
+*/
+BGD_DECLARE(gdSurfacePtr) gdSurfaceCreateFromHeif(FILE *inFile)
+{
+ gdSurfacePtr im;
+ gdIOCtx *in = gdNewFileCtx(inFile);
+
+ if (!in)
+ return NULL;
+ im = gdSurfaceCreateFromHeifCtx(in);
+ in->gd_free(in);
+
+ return im;
+}
+
+/* returns GD_TRUE on success, GD_FALSE on failure */
+static int _gdSurfaceHeifCtx(gdSurfacePtr surface, gdIOCtx *outfile, int quality, gdHeifCodec codec, gdHeifChroma chroma)
+{
+ struct heif_context *heif_ctx;
+ struct heif_encoder *heif_enc;
+ struct heif_image *heif_im;
+ struct heif_writer heif_wr;
+ struct heif_error err;
+ uint8_t *rgba;
+ int x, y;
+ uint8_t *p;
+
+ if (surface == NULL) {
+ return GD_FALSE;
+ }
+
+ if (codec != GD_HEIF_CODEC_HEVC && codec != GD_HEIF_CODEC_AV1) {
+ gd_error("Unsupported format by heif");
+ return GD_FALSE;
+ }
+
+ if (overflow2(gdSurfaceGetWidth(surface), 4)) {
+ return GD_FALSE;
+ }
+
+ if (overflow2(gdSurfaceGetWidth(surface) * 4, gdSurfaceGetHeight(surface))) {
+ return GD_FALSE;
+ }
+
+ heif_ctx = heif_context_alloc();
+ if (heif_ctx == NULL) {
+ gd_error("gd-heif could not allocate context\n");
+ return GD_FALSE;
+ }
+ err = heif_context_get_encoder_for_format(heif_ctx, (enum heif_compression_format)codec, &heif_enc);
+ if (err.code != heif_error_Ok) {
+ gd_error("gd-heif encoder acquisition failed (missing codec support?)\n");
+ heif_context_free(heif_ctx);
+ return GD_FALSE;
+ }
+
+ if (quality == 200) {
+ err = heif_encoder_set_lossless(heif_enc, GD_TRUE);
+ } else if (quality == -1) {
+ err = heif_encoder_set_lossy_quality(heif_enc, 80);
+ } else {
+ err = heif_encoder_set_lossy_quality(heif_enc, quality);
+ }
+ if (err.code != heif_error_Ok) {
+ gd_error("gd-heif invalid quality number\n");
+ heif_encoder_release(heif_enc);
+ heif_context_free(heif_ctx);
+ return GD_FALSE;
+ }
+
+ if (heif_get_version_number_major() >= 1 && heif_get_version_number_minor() >= 9) {
+ err = heif_encoder_set_parameter_string(heif_enc, "chroma", chroma);
+ if (err.code != heif_error_Ok) {
+ gd_error("gd-heif invalid chroma subsampling parameter\n");
+ heif_encoder_release(heif_enc);
+ heif_context_free(heif_ctx);
+ return GD_FALSE;
+ }
+ }
+
+ err = heif_image_create(gdSurfaceGetWidth(surface) , gdSurfaceGetHeight(surface) , heif_colorspace_RGB, heif_chroma_interleaved_RGBA, &heif_im);
+ if (err.code != heif_error_Ok) {
+ gd_error("gd-heif image creation failed");
+ heif_encoder_release(heif_enc);
+ heif_context_free(heif_ctx);
+ return GD_FALSE;
+ }
+
+ err = heif_image_add_plane(heif_im, heif_channel_interleaved, gdSurfaceGetWidth(surface), gdSurfaceGetHeight(surface), 32);
+ if (err.code != heif_error_Ok) {
+ gd_error("gd-heif cannot add image plane\n");
+ heif_image_release(heif_im);
+ heif_encoder_release(heif_enc);
+ heif_context_free(heif_ctx);
+ return GD_FALSE;
+ }
+
+ rgba = (uint8_t *)heif_image_get_plane_readonly(heif_im, heif_channel_interleaved, NULL);
+ if (!rgba) {
+ gd_error("gd-heif cannot get image plane\n");
+ heif_image_release(heif_im);
+ heif_encoder_release(heif_enc);
+ heif_context_free(heif_ctx);
+ return GD_FALSE;
+ }
+ p = rgba;
+ uint32_t *surface_data = (uint32_t *)gdSurfaceGetData(surface);
+ const uint32_t stride_32 = gdSurfaceGetStride(surface)/4;
+ for (y = 0; y < gdSurfaceGetHeight(surface); y++) {
+ const uint32_t *src = surface_data + y * stride_32;
+ for (x = 0; x < gdSurfaceGetWidth(surface); x++) {
+ const unsigned int a = *(src+x) >> 24;
+ if (a == 0) {
+ *p++ = 0;
+ *p++ = 0;
+ *p++ = 0;
+ *p++ = 0;
+ continue;
+ }
+ const uint32_t a_div_255 = 255/a;
+ *p++ = (( *(src+x) >> 16) & 0xff) * a_div_255;
+ *p++ = (( *(src+x) >> 8) & 0xff) * a_div_255;
+ *p++ = (( *(src+x) >> 0) & 0xff) * a_div_255;
+ *p++ = a;
+ }
+ }
+ err = heif_context_encode_image(heif_ctx, heif_im, heif_enc, NULL, NULL);
+ heif_encoder_release(heif_enc);
+ if (err.code != heif_error_Ok) {
+ gd_error("gd-heif encoding failed\n");
+ heif_image_release(heif_im);
+ heif_context_free(heif_ctx);
+ return GD_FALSE;
+ }
+ heif_wr.write = _gdImageWriteHeif;
+ heif_wr.writer_api_version = 1;
+ err = heif_context_write(heif_ctx, &heif_wr, (void *)outfile);
+
+ heif_image_release(heif_im);
+ heif_context_free(heif_ctx);
+
+ return GD_TRUE;
+}
+
+
+/*
+ Function: gdImageHeif
+
+ Variant of <gdImageHeifEx> which uses the default quality (-1), the
+ default codec (GD_HEIF_Codec_HEVC) and the default chroma
+ subsampling (GD_HEIF_CHROMA_444).
+
+ Parameters:
+
+ im - The image to save
+ outFile - The FILE pointer to write to.
+
+ Returns:
+
+ Nothing.
+*/
+BGD_DECLARE(void) gdSurfaceHeif(gdSurfacePtr surface, FILE *outFile)
+{
+ gdIOCtx *out = gdNewFileCtx(outFile);
+ if (out == NULL) {
+ return;
+ }
+ _gdSurfaceHeifCtx(surface, out, -1, GD_HEIF_CODEC_HEVC, GD_HEIF_CHROMA_444);
+ out->gd_free(out);
+}
+
#else /* HAVE_LIBHEIF */
static void _noHeifError(void)