mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-11-01 02:43:35 +08:00
167 lines
5.3 KiB
C
167 lines
5.3 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <libheif/heif.h>
|
|
#include <jpeglib.h>
|
|
#include <setjmp.h>
|
|
#include "utils.h"
|
|
|
|
#define JPEG_QUALITY 50
|
|
|
|
struct filestash_heicjpeg_error_mgr {
|
|
struct jpeg_error_mgr pub;
|
|
jmp_buf jmp;
|
|
};
|
|
|
|
typedef struct filestash_heicjpeg_error_mgr *filestash_heicjpeg_error_ptr;
|
|
|
|
void filestash_heicjpeg_error_exit (j_common_ptr cinfo) {
|
|
filestash_heicjpeg_error_ptr filestash_err = (filestash_heicjpeg_error_ptr) cinfo->err;
|
|
longjmp(filestash_err->jmp, 1);
|
|
}
|
|
|
|
|
|
// adapted and inspired from:
|
|
// https://github.com/strukturag/libheif/blob/master/examples/heif_thumbnailer.cc
|
|
int heif_to_jpeg(int inputDesc, int outputDesc, int targetSize) {
|
|
#ifdef HAS_DEBUG
|
|
clock_t t;
|
|
t = clock();
|
|
#endif
|
|
int status = 0;
|
|
FILE* input = fdopen(inputDesc, "rb");
|
|
FILE* output = fdopen(outputDesc, "wb");
|
|
if (!input || !output) {
|
|
return 1;
|
|
}
|
|
|
|
// STEP1: write input to a file as that's the only things libheif can open
|
|
char fname_in[32] = "/tmp/filestash.XXXXXX";
|
|
int _mkstemp_in = mkstemp(fname_in);
|
|
if (_mkstemp_in == -1) {
|
|
ERROR("mkstemp_in");
|
|
status = 1;
|
|
goto CLEANUP_AND_ABORT;
|
|
}
|
|
FILE* f_in = fdopen(_mkstemp_in, "w");
|
|
if (!f_in) {
|
|
ERROR("fdopen");
|
|
status = 1;
|
|
goto CLEANUP_AND_ABORT;
|
|
}
|
|
char content[1024 * 4];
|
|
int read;
|
|
while ((read = fread(content, sizeof(char), 1024*4, input))) {
|
|
fwrite(content, read, sizeof(char), f_in);
|
|
}
|
|
fclose(f_in);
|
|
|
|
// STEP2: decode heic
|
|
struct heif_context* ctx = heif_context_alloc();
|
|
struct heif_image_handle* handle = NULL;
|
|
struct heif_image* img = NULL;
|
|
struct heif_error error = {};
|
|
error = heif_context_read_from_file(ctx, fname_in, NULL);
|
|
if (error.code != heif_error_Ok) {
|
|
status = 1;
|
|
goto CLEANUP_AND_ABORT_A;
|
|
}
|
|
DEBUG("heic after read");
|
|
error = heif_context_get_primary_image_handle(ctx, &handle);
|
|
if (error.code != heif_error_Ok) {
|
|
status = 1;
|
|
goto CLEANUP_AND_ABORT_B;
|
|
}
|
|
if (targetSize < 0) {
|
|
heif_item_id thumbnail_ID;
|
|
int nThumbnails = heif_image_handle_get_list_of_thumbnail_IDs(handle, &thumbnail_ID, 1);
|
|
if (nThumbnails > 0) {
|
|
struct heif_image_handle* thumbnail_handle;
|
|
error = heif_image_handle_get_thumbnail(handle, thumbnail_ID, &thumbnail_handle);
|
|
if (error.code != heif_error_Ok) {
|
|
status = 1;
|
|
goto CLEANUP_AND_ABORT_B;
|
|
}
|
|
heif_image_handle_release(handle);
|
|
handle = thumbnail_handle;
|
|
}
|
|
}
|
|
DEBUG("heic after extract");
|
|
struct heif_decoding_options* decode_options = heif_decoding_options_alloc();
|
|
// decode_options->convert_hdr_to_8bit = 1;
|
|
error = heif_decode_image(handle, &img, heif_colorspace_YCbCr, heif_chroma_420, decode_options);
|
|
heif_decoding_options_free(decode_options);
|
|
if (error.code != heif_error_Ok) {
|
|
status = 1;
|
|
goto CLEANUP_AND_ABORT_C;
|
|
}
|
|
DEBUG("heic after decode");
|
|
if (heif_image_get_bits_per_pixel(img, heif_channel_Y) != 8) {
|
|
status = 1;
|
|
goto CLEANUP_AND_ABORT_C;
|
|
}
|
|
DEBUG("heic after validation");
|
|
|
|
// STEP3: Create a jpeg
|
|
struct jpeg_compress_struct jpeg_config_output;
|
|
struct filestash_heicjpeg_error_mgr jerr;
|
|
int stride_y;
|
|
int stride_u;
|
|
int stride_v;
|
|
jpeg_create_compress(&jpeg_config_output);
|
|
jpeg_stdio_dest(&jpeg_config_output, output);
|
|
|
|
jpeg_config_output.image_width = heif_image_handle_get_width(handle);
|
|
jpeg_config_output.image_height = heif_image_handle_get_height(handle);
|
|
jpeg_config_output.input_components = 3;
|
|
jpeg_config_output.in_color_space = JCS_YCbCr;
|
|
jpeg_config_output.err = jpeg_std_error(&jerr.pub);
|
|
jpeg_set_defaults(&jpeg_config_output);
|
|
jpeg_set_quality(&jpeg_config_output, JPEG_QUALITY, TRUE);
|
|
if (setjmp(jerr.jmp)) {
|
|
ERROR("exception");
|
|
goto CLEANUP_AND_ABORT_D;
|
|
}
|
|
|
|
const uint8_t* row_y = heif_image_get_plane_readonly(img, heif_channel_Y, &stride_y);
|
|
const uint8_t* row_u = heif_image_get_plane_readonly(img, heif_channel_Cb, &stride_u);
|
|
const uint8_t* row_v = heif_image_get_plane_readonly(img, heif_channel_Cr, &stride_v);
|
|
int jpeg_row_stride = jpeg_config_output.image_width * jpeg_config_output.input_components;
|
|
jpeg_start_compress(&jpeg_config_output, TRUE);
|
|
jerr.pub.error_exit = filestash_heicjpeg_error_exit;
|
|
JSAMPARRAY buffer = jpeg_config_output.mem->alloc_sarray((j_common_ptr) &jpeg_config_output, JPOOL_IMAGE, jpeg_row_stride, 1);
|
|
DEBUG("jpeg initialised");
|
|
while (jpeg_config_output.next_scanline < jpeg_config_output.image_height) {
|
|
size_t offset_y = jpeg_config_output.next_scanline * stride_y;
|
|
const uint8_t* start_y = &row_y[offset_y];
|
|
size_t offset_u = (jpeg_config_output.next_scanline / 2) * stride_u;
|
|
const uint8_t* start_u = &row_u[offset_u];
|
|
size_t offset_v = (jpeg_config_output.next_scanline / 2) * stride_v;
|
|
const uint8_t* start_v = &row_v[offset_v];
|
|
JOCTET* bufp = buffer[0];
|
|
for (JDIMENSION x = 0; x < jpeg_config_output.image_width; ++x) {
|
|
*bufp++ = start_y[x];
|
|
*bufp++ = start_u[x / 2];
|
|
*bufp++ = start_v[x / 2];
|
|
}
|
|
jpeg_write_scanlines(&jpeg_config_output, buffer, 1);
|
|
}
|
|
jpeg_finish_compress(&jpeg_config_output);
|
|
DEBUG("jpeg cleanup");
|
|
|
|
CLEANUP_AND_ABORT_D:
|
|
jpeg_destroy_compress(&jpeg_config_output);
|
|
|
|
CLEANUP_AND_ABORT_C:
|
|
heif_image_release(img);
|
|
|
|
CLEANUP_AND_ABORT_B:
|
|
heif_image_handle_release(handle);
|
|
|
|
CLEANUP_AND_ABORT_A:
|
|
heif_context_free(ctx);
|
|
|
|
CLEANUP_AND_ABORT:
|
|
remove(fname_in);
|
|
return status;
|
|
}
|