summaryrefslogtreecommitdiff
path: root/src/gd_heif.c
diff options
context:
space:
mode:
authorMartin Reboredo <39890836+YakoYakoYokuYoku@users.noreply.github.com>2021-03-02 14:32:03 -0300
committerGitHub <noreply@github.com>2021-03-02 12:32:03 -0500
commit39c4644fa01f6b9f002119e4109e252bc665b8f1 (patch)
tree852d151b50717219ebff215b4b6f65a945ebf9f3 /src/gd_heif.c
parentd6a061850d40110a98364766f654030f929ec0ed (diff)
downloadlibgd-39c4644fa01f6b9f002119e4109e252bc665b8f1.tar.gz
HEIF support through libheif (#670)
With the adoption of AVIF by Firefox and Chromium based browsers (still in experimental phase), the newer incorporation of HEIF by Canon and Sony in their cameras and the newer support of both of them in modern software like ImageMagick, GIMP and Krita, `gd` haven't seen any endorsement for the formats up until this PR. Reading and writing is done by `libheif`, with functionality for chroma subsampling (for now `4:2:0`, `4:2:2` and `4:4:4`), quality (with new `200` for lossless) and compression (whether `HEVC` or `AV1`) selection. This was tested with `libheif` version `1.11.0` in my Solus machine. Also, fixes both #395 and #557.
Diffstat (limited to 'src/gd_heif.c')
-rw-r--r--src/gd_heif.c582
1 files changed, 582 insertions, 0 deletions
diff --git a/src/gd_heif.c b/src/gd_heif.c
new file mode 100644
index 0000000..3b00a6c
--- /dev/null
+++ b/src/gd_heif.c
@@ -0,0 +1,582 @@
+/**
+ * File: HEIF IO
+ *
+ * Read and write HEIF images.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <stdlib.h>
+#include "gd.h"
+#include "gd_errors.h"
+#include "gdhelpers.h"
+
+#ifdef HAVE_LIBHEIF
+#include <libheif/heif.h>
+
+#define GD_HEIF_ALLOC_STEP (4*1024)
+#define GD_HEIF_HEADER 12
+
+typedef enum gd_heif_brand {
+ GD_HEIF_BRAND_AVIF = 1,
+ GD_HEIF_BRAND_MIF1 = 2,
+ GD_HEIF_BRAND_HEIC = 4,
+ GD_HEIF_BRAND_HEIX = 8,
+} gd_heif_brand;
+
+/*
+ Function: gdImageCreateFromHeif
+
+ <gdImageCreateFromHeif> is called to load truecolor images from
+ HEIF format files. Invoke <gdImageCreateFromHeif> with an
+ already opened pointer to a file containing the desired
+ image. <gdImageCreateFromHeif> 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). <gdImageCreateFromHeif> 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(gdImagePtr) gdImageCreateFromHeif(FILE *inFile)
+{
+ gdImagePtr im;
+ gdIOCtx *in = gdNewFileCtx(inFile);
+
+ if (!in)
+ return NULL;
+ im = gdImageCreateFromHeifCtx(in);
+ in->gd_free(in);
+
+ return im;
+}
+
+/*
+ Function: gdImageCreateFromHeifPtr
+
+ See <gdImageCreateFromHeif>.
+
+ Parameters:
+
+ size - size of HEIF data in bytes.
+ data - pointer to HEIF data.
+*/
+BGD_DECLARE(gdImagePtr) gdImageCreateFromHeifPtr(int size, void *data)
+{
+ gdImagePtr im;
+ gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0);
+
+ if (!in)
+ return NULL;
+ im = gdImageCreateFromHeifCtx(in);
+ in->gd_free(in);
+
+ return im;
+}
+
+static int _gdHeifCheckBrand(unsigned char *magic, gd_heif_brand expected_brand)
+{
+ if (memcmp(magic + 4, "ftyp", 4) != 0)
+ return GD_FALSE;
+ if (memcmp(magic + 8, "avif", 4) == 0 && expected_brand & GD_HEIF_BRAND_AVIF)
+ return GD_TRUE;
+ if (memcmp(magic + 8, "heic", 4) == 0 && expected_brand & GD_HEIF_BRAND_HEIC)
+ return GD_TRUE;
+ if (memcmp(magic + 8, "heix", 4) == 0 && expected_brand & GD_HEIF_BRAND_HEIX)
+ return GD_TRUE;
+ if (memcmp(magic + 8, "mif1", 4) == 0 && expected_brand & GD_HEIF_BRAND_MIF1)
+ return GD_TRUE;
+
+ return GD_FALSE;
+}
+
+static gdImagePtr _gdImageCreateFromHeifCtx(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;
+ int width, height;
+ uint8_t *filedata = NULL;
+ uint8_t *rgba = NULL;
+ unsigned char *read, *temp, magic[GD_HEIF_HEADER];
+ int magic_len;
+ 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;
+ }
+ gdSeek(infile, 0);
+
+ 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);
+
+ im = gdImageCreateTrueColor(width, height);
+ if (!im) {
+ 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);
+ gdImageDestroy(im);
+ return NULL;
+ }
+ for (y = 0, p = rgba; y < height; y++) {
+ for (x = 0; x < width; x++) {
+ uint8_t r = *(p++);
+ uint8_t g = *(p++);
+ uint8_t b = *(p++);
+ uint8_t a = gdAlphaMax - (*(p++) >> 1);
+ im->tpixels[y][x] = gdTrueColorAlpha(r, g, b, a);
+ }
+ }
+ gdFree(filedata);
+ heif_image_release(heif_im);
+ heif_image_handle_release(heif_imhandle);
+ heif_context_free(heif_ctx);
+
+ return im;
+}
+
+/*
+ Function: gdImageCreateFromHeifCtx
+
+ See <gdImageCreateFromHeif>.
+*/
+BGD_DECLARE(gdImagePtr) gdImageCreateFromHeifCtx(gdIOCtx *infile)
+{
+ return _gdImageCreateFromHeifCtx(infile, GD_HEIF_BRAND_AVIF | GD_HEIF_BRAND_MIF1 | GD_HEIF_BRAND_HEIC | GD_HEIF_BRAND_HEIX);
+}
+
+
+static struct heif_error _gdImageWriteHeif(struct heif_context *heif_ctx, const void *data, size_t size, void *userdata)
+{
+ gdIOCtx *outfile;
+ struct heif_error err;
+
+ outfile = (gdIOCtx *)userdata;
+
+ gdPutBuf(data, size, outfile);
+
+ err.code = heif_error_Ok;
+ err.subcode = heif_suberror_Unspecified;
+ err.message = "";
+
+ return err;
+}
+
+/* returns GD_TRUE on success, GD_FALSE on failure */
+static int _gdImageHeifCtx(gdImagePtr im, 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 (im == 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 (!gdImageTrueColor(im)) {
+ gd_error("Palette image not supported by heif\n");
+ return GD_FALSE;
+ }
+
+ if (overflow2(gdImageSX(im), 4)) {
+ return GD_FALSE;
+ }
+
+ if (overflow2(gdImageSX(im) * 4, gdImageSY(im))) {
+ 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;
+ }
+
+ 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(gdImageSX(im), gdImageSY(im), 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, gdImageSX(im), gdImageSY(im), 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;
+ for (y = 0; y < gdImageSY(im); y++) {
+ for (x = 0; x < gdImageSX(im); x++) {
+ int c;
+ char a;
+ c = im->tpixels[y][x];
+ a = gdTrueColorGetAlpha(c);
+ if (a == 127) {
+ a = 0;
+ } else {
+ a = 255 - ((a << 1) + (a >> 6));
+ }
+ *(p++) = gdTrueColorGetRed(c);
+ *(p++) = gdTrueColorGetGreen(c);
+ *(p++) = gdTrueColorGetBlue(c);
+ *(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: gdImageHeifCtx
+
+ Write the image as HEIF data via a <gdIOCtx>. See <gdImageHeifEx>
+ for more details.
+
+ Parameters:
+
+ im - The image to write.
+ outfile - The output sink.
+ quality - Image quality.
+ codec - The output coding format.
+ chroma - The output chroma subsampling format.
+
+ Returns:
+
+ Nothing.
+*/
+BGD_DECLARE(void) gdImageHeifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, gdHeifCodec codec, gdHeifChroma chroma)
+{
+ _gdImageHeifCtx(im, outfile, quality, codec, chroma);
+}
+
+/*
+ Function: gdImageHeifEx
+
+ <gdImageHeifEx> outputs the specified image to the specified file in
+ HEIF format. The file must be open for writing. Under MSDOS and
+ all versions of Windows, it is important to use "wb" as opposed to
+ simply "w" as the mode when opening the file, and under Unix there
+ is no penalty for doing so. <gdImageHeifEx> does not close the file;
+ your code must do so.
+
+ If _quality_ is -1, a reasonable quality value (which should yield a
+ good general quality / size tradeoff for most situations) is used. Otherwise
+ _quality_ should be a value in the range 0-100, higher quality values
+ usually implying both higher quality and larger image sizes or 200, for
+ lossless codec.
+
+ Variants:
+
+ <gdImageHeifCtx> stores the image using a <gdIOCtx> struct.
+
+ <gdImageHeifPtrEx> stores the image to RAM.
+
+ Parameters:
+
+ im - The image to save.
+ outFile - The FILE pointer to write to.
+ quality - Codec quality (0-100).
+ codec - The output coding format.
+ chroma - The output chroma subsampling format.
+
+ Returns:
+
+ Nothing.
+*/
+BGD_DECLARE(void) gdImageHeifEx(gdImagePtr im, FILE *outFile, int quality, gdHeifCodec codec, gdHeifChroma chroma)
+{
+ gdIOCtx *out = gdNewFileCtx(outFile);
+ if (out == NULL) {
+ return;
+ }
+ _gdImageHeifCtx(im, out, quality, codec, chroma);
+ out->gd_free(out);
+}
+
+/*
+ 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) gdImageHeif(gdImagePtr im, FILE *outFile)
+{
+ gdIOCtx *out = gdNewFileCtx(outFile);
+ if (out == NULL) {
+ return;
+ }
+ _gdImageHeifCtx(im, out, -1, GD_HEIF_CODEC_HEVC, GD_HEIF_CHROMA_444);
+ out->gd_free(out);
+}
+
+/*
+ Function: gdImageHeifPtr
+
+ See <gdImageHeifEx>.
+*/
+BGD_DECLARE(void *) gdImageHeifPtr(gdImagePtr im, int *size)
+{
+ void *rv;
+ gdIOCtx *out = gdNewDynamicCtx(2048, NULL);
+ if (out == NULL) {
+ return NULL;
+ }
+ if (_gdImageHeifCtx(im, out, -1, GD_HEIF_CODEC_HEVC, GD_HEIF_CHROMA_444)) {
+ rv = gdDPExtractData(out, size);
+ } else {
+ rv = NULL;
+ }
+ out->gd_free(out);
+
+ return rv;
+}
+
+/*
+ Function: gdImageHeifPtrEx
+
+ See <gdImageHeifEx>.
+*/
+BGD_DECLARE(void *) gdImageHeifPtrEx(gdImagePtr im, int *size, int quality, gdHeifCodec codec, gdHeifChroma chroma)
+{
+ void *rv;
+ gdIOCtx *out = gdNewDynamicCtx(2048, NULL);
+ if (out == NULL) {
+ return NULL;
+ }
+ if (_gdImageHeifCtx(im, out, quality, codec, chroma)) {
+ rv = gdDPExtractData(out, size);
+ } else {
+ rv = NULL;
+ }
+ out->gd_free(out);
+ return rv;
+}
+
+#else /* HAVE_LIBHEIF */
+
+static void _noHeifError(void)
+{
+ gd_error("HEIF image support has been disabled\n");
+}
+
+BGD_DECLARE(gdImagePtr) gdImageCreateFromHeif(FILE *inFile)
+{
+ _noHeifError();
+ return NULL;
+}
+
+BGD_DECLARE(gdImagePtr) gdImageCreateFromHeifPtr(int size, void *data)
+{
+ _noHeifError();
+ return NULL;
+}
+
+BGD_DECLARE(gdImagePtr) gdImageCreateFromHeifCtx(gdIOCtx *infile)
+{
+ _noHeifError();
+ return NULL;
+}
+
+BGD_DECLARE(void) gdImageHeifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, gdHeifCodec codec, gdHeifChroma chroma)
+{
+ _noHeifError();
+}
+
+BGD_DECLARE(void) gdImageHeifEx(gdImagePtr im, FILE *outFile, int quality, gdHeifCodec codec, gdHeifChroma chroma)
+{
+ _noHeifError();
+}
+
+BGD_DECLARE(void) gdImageHeif(gdImagePtr im, FILE *outFile)
+{
+ _noHeifError();
+}
+
+BGD_DECLARE(void *) gdImageHeifPtr(gdImagePtr im, int *size)
+{
+ _noHeifError();
+ return NULL;
+}
+
+BGD_DECLARE(void *) gdImageHeifPtrEx(gdImagePtr im, int *size, int quality, gdHeifCodec codec, gdHeifChroma chroma)
+{
+ _noHeifError();
+ return NULL;
+}
+
+#endif /* HAVE_LIBHEIF */