diff options
| author | Pierre Joye <pierre.php@gmail.com> | 2021-08-13 16:54:21 +0700 |
|---|---|---|
| committer | Pierre Joye <pierre.php@gmail.com> | 2021-08-13 16:54:21 +0700 |
| commit | 521d7d68cc40bed34b3d10f3bd7f14e9ff4141aa (patch) | |
| tree | e2dd2d39cf931a41b1f23eba1c1b45e5ad08629b | |
| parent | ca9865e67b3153b079bcfcd65eced1d769fe1d4c (diff) | |
| download | libgd-features/surface.tar.gz | |
add HIEF support for surfacefeatures/surface
| -rw-r--r-- | src/gd.h | 17 | ||||
| -rw-r--r-- | src/gd_heif.c | 380 |
2 files changed, 387 insertions, 10 deletions
@@ -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) |
