libheif/plugins/encoder_jpeg.cc (341 lines of code) (raw):

/* * HEIF codec. * Copyright (c) 2023 Dirk Farin <dirk.farin@gmail.com> * * This file is part of libheif. * * libheif is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * libheif is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libheif. If not, see <http://www.gnu.org/licenses/>. */ #include "libheif/heif.h" #include "libheif/heif_plugin.h" #include "encoder_jpeg.h" #include <vector> #include <cstring> #include <cassert> #include <algorithm> #include <memory> #include <cstdio> #include <csetjmp> extern "C" { #include <jpeglib.h> } struct encoder_struct_jpeg { int quality; // --- output std::vector<uint8_t> compressed_data; bool data_read = false; }; static const int JPEG_PLUGIN_PRIORITY = 100; #define MAX_PLUGIN_NAME_LENGTH 80 static char plugin_name[MAX_PLUGIN_NAME_LENGTH]; static void jpeg_set_default_parameters(void* encoder); #define xstr(s) str(s) #define str(s) #s static const char* jpeg_plugin_name() { #ifdef LIBJPEG_TURBO_VERSION snprintf(plugin_name, MAX_PLUGIN_NAME_LENGTH - 1, "libjpeg-turbo " xstr(LIBJPEG_TURBO_VERSION) " (libjpeg %d.%d)", JPEG_LIB_VERSION / 10, JPEG_LIB_VERSION % 10); plugin_name[MAX_PLUGIN_NAME_LENGTH - 1] = 0; #else sprintf(plugin_name, "libjpeg %d.%d", JPEG_LIB_VERSION/10, JPEG_LIB_VERSION%10); #endif return plugin_name; } #define MAX_NPARAMETERS 10 static struct heif_encoder_parameter jpeg_encoder_params[MAX_NPARAMETERS]; static const struct heif_encoder_parameter* jpeg_encoder_parameter_ptrs[MAX_NPARAMETERS + 1]; static void jpeg_init_parameters() { struct heif_encoder_parameter* p = jpeg_encoder_params; const struct heif_encoder_parameter** d = jpeg_encoder_parameter_ptrs; int i = 0; assert(i < MAX_NPARAMETERS); p->version = 2; p->name = heif_encoder_parameter_name_quality; p->type = heif_encoder_parameter_type_integer; p->integer.default_value = 50; p->has_default = true; p->integer.have_minimum_maximum = true; p->integer.minimum = 0; p->integer.maximum = 100; p->integer.valid_values = NULL; p->integer.num_valid_values = 0; d[i++] = p++; /* assert(i < MAX_NPARAMETERS); p->version = 2; p->name = heif_encoder_parameter_name_lossless; p->type = heif_encoder_parameter_type_boolean; p->boolean.default_value = false; p->has_default = true; d[i++] = p++; */ d[i++] = nullptr; } const struct heif_encoder_parameter** jpeg_list_parameters(void* encoder) { return jpeg_encoder_parameter_ptrs; } static void jpeg_init_plugin() { jpeg_init_parameters(); } static void jpeg_cleanup_plugin() { } struct heif_error jpeg_new_encoder(void** enc) { auto* encoder = new encoder_struct_jpeg(); struct heif_error err = heif_error_ok; *enc = encoder; // set default parameters jpeg_set_default_parameters(encoder); return err; } void jpeg_free_encoder(void* encoder_raw) { auto* encoder = (struct encoder_struct_jpeg*) encoder_raw; delete encoder; } struct heif_error jpeg_set_parameter_quality(void* encoder_raw, int quality) { auto* encoder = (struct encoder_struct_jpeg*) encoder_raw; if (quality < 0 || quality > 100) { return heif_error_invalid_parameter_value; } encoder->quality = quality; return heif_error_ok; } struct heif_error jpeg_get_parameter_quality(void* encoder_raw, int* quality) { auto* encoder = (struct encoder_struct_jpeg*) encoder_raw; *quality = encoder->quality; return heif_error_ok; } struct heif_error jpeg_set_parameter_lossless(void* encoder_raw, int enable) { auto* encoder = (struct encoder_struct_jpeg*) encoder_raw; if (enable) { encoder->quality = 100; // not really lossless, but the best we can do } return heif_error_ok; } struct heif_error jpeg_get_parameter_lossless(void* encoder_raw, int* enable) { auto* encoder = (struct encoder_struct_jpeg*) encoder_raw; *enable = (encoder->quality == 100); // not really correct, but matches the setting above return heif_error_ok; } struct heif_error jpeg_set_parameter_logging_level(void* encoder_raw, int logging) { return heif_error_ok; } struct heif_error jpeg_get_parameter_logging_level(void* encoder_raw, int* loglevel) { *loglevel = 0; return heif_error_ok; } #define set_value(paramname, paramvar) if (strcmp(name, paramname)==0) { encoder->paramvar = value; return heif_error_ok; } #define get_value(paramname, paramvar) if (strcmp(name, paramname)==0) { *value = encoder->paramvar; return heif_error_ok; } struct heif_error jpeg_set_parameter_integer(void* encoder_raw, const char* name, int value) { struct encoder_struct_jpeg* encoder = (struct encoder_struct_jpeg*) encoder_raw; if (strcmp(name, heif_encoder_parameter_name_quality) == 0) { return jpeg_set_parameter_quality(encoder, value); } else if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) { return jpeg_set_parameter_lossless(encoder, value); } return heif_error_unsupported_parameter; } struct heif_error jpeg_get_parameter_integer(void* encoder_raw, const char* name, int* value) { auto* encoder = (struct encoder_struct_jpeg*) encoder_raw; if (strcmp(name, heif_encoder_parameter_name_quality) == 0) { return jpeg_get_parameter_quality(encoder, value); } else if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) { return jpeg_get_parameter_lossless(encoder, value); } return heif_error_unsupported_parameter; } struct heif_error jpeg_set_parameter_boolean(void* encoder_raw, const char* name, int value) { auto* encoder = (struct encoder_struct_jpeg*) encoder_raw; if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) { return jpeg_set_parameter_lossless(encoder, value); } //set_value(kParam_realtime, realtime_mode); return heif_error_unsupported_parameter; } struct heif_error jpeg_get_parameter_boolean(void* encoder_raw, const char* name, int* value) { auto* encoder = (struct encoder_struct_jpeg*) encoder_raw; if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) { return jpeg_get_parameter_lossless(encoder, value); } //get_value(kParam_realtime, realtime_mode); return heif_error_unsupported_parameter; } struct heif_error jpeg_set_parameter_string(void* encoder_raw, const char* name, const char* value) { //auto* encoder = (struct encoder_struct_jpeg*) encoder_raw; return heif_error_unsupported_parameter; } struct heif_error jpeg_get_parameter_string(void* encoder_raw, const char* name, char* value, int value_size) { //auto* encoder = (struct encoder_struct_jpeg*) encoder_raw; return heif_error_unsupported_parameter; } static void jpeg_set_default_parameters(void* encoder) { for (const struct heif_encoder_parameter** p = jpeg_encoder_parameter_ptrs; *p; p++) { const struct heif_encoder_parameter* param = *p; if (param->has_default) { switch (param->type) { case heif_encoder_parameter_type_integer: jpeg_set_parameter_integer(encoder, param->name, param->integer.default_value); break; case heif_encoder_parameter_type_boolean: jpeg_set_parameter_boolean(encoder, param->name, param->boolean.default_value); break; case heif_encoder_parameter_type_string: // NOLINTNEXTLINE(build/include_what_you_use) jpeg_set_parameter_string(encoder, param->name, param->string.default_value); break; } } } } void jpeg_query_input_colorspace(heif_colorspace* colorspace, heif_chroma* chroma) { *colorspace = heif_colorspace_YCbCr; *chroma = heif_chroma_420; } void jpeg_query_input_colorspace2(void* encoder_raw, heif_colorspace* colorspace, heif_chroma* chroma) { //auto* encoder = (struct encoder_struct_jpeg*) encoder_raw; // TODO: support encoding greyscale JPEGs *colorspace = heif_colorspace_YCbCr; *chroma = heif_chroma_420; } void jpeg_query_encoded_size(void* encoder_raw, uint32_t input_width, uint32_t input_height, uint32_t* encoded_width, uint32_t* encoded_height) { *encoded_width = input_width; *encoded_height = input_height; } struct ErrorHandler { struct jpeg_error_mgr pub; /* "public" fields */ jmp_buf setjmp_buffer; /* for return to caller */ }; static void OnJpegError(j_common_ptr cinfo) { ErrorHandler* handler = reinterpret_cast<ErrorHandler*>(cinfo->err); longjmp(handler->setjmp_buffer, 1); } struct heif_error jpeg_encode_image(void* encoder_raw, const struct heif_image* image, heif_image_input_class input_class) { auto* encoder = (struct encoder_struct_jpeg*) encoder_raw; struct jpeg_compress_struct cinfo; struct ErrorHandler jerr; cinfo.err = jpeg_std_error(reinterpret_cast<struct jpeg_error_mgr*>(&jerr)); jerr.pub.error_exit = &OnJpegError; if (setjmp(jerr.setjmp_buffer)) { cinfo.err->output_message(reinterpret_cast<j_common_ptr>(&cinfo)); jpeg_destroy_compress(&cinfo); return heif_error{heif_error_Encoding_error, heif_suberror_Encoder_encoding, "JPEG encoding error"}; } if (heif_image_get_bits_per_pixel(image, heif_channel_Y) != 8) { jpeg_destroy_compress(&cinfo); return heif_error{heif_error_Encoding_error, heif_suberror_Encoder_encoding, "Cannot write JPEG image with >8 bpp."}; } uint8_t* outbuffer = nullptr; #ifdef LIBJPEG_TURBO_VERSION unsigned long outlength = 0; #else size_t outlength = 0; #endif jpeg_create_compress(&cinfo); jpeg_mem_dest(&cinfo, &outbuffer, &outlength); cinfo.image_width = heif_image_get_width(image, heif_channel_Y); cinfo.image_height = heif_image_get_height(image, heif_channel_Y); cinfo.input_components = 3; cinfo.in_color_space = JCS_YCbCr; jpeg_set_defaults(&cinfo); static const boolean kForceBaseline = TRUE; jpeg_set_quality(&cinfo, encoder->quality, kForceBaseline); static const boolean kWriteAllTables = TRUE; jpeg_start_compress(&cinfo, kWriteAllTables); int stride_y; const uint8_t* row_y = heif_image_get_plane_readonly(image, heif_channel_Y, &stride_y); int stride_u; const uint8_t* row_u = heif_image_get_plane_readonly(image, heif_channel_Cb, &stride_u); int stride_v; const uint8_t* row_v = heif_image_get_plane_readonly(image, heif_channel_Cr, &stride_v); JSAMPARRAY buffer = cinfo.mem->alloc_sarray( reinterpret_cast<j_common_ptr>(&cinfo), JPOOL_IMAGE, cinfo.image_width * cinfo.input_components, 1); JSAMPROW row[1] = {buffer[0]}; while (cinfo.next_scanline < cinfo.image_height) { size_t offset_y = cinfo.next_scanline * stride_y; const uint8_t* start_y = &row_y[offset_y]; size_t offset_u = (cinfo.next_scanline / 2) * stride_u; const uint8_t* start_u = &row_u[offset_u]; size_t offset_v = (cinfo.next_scanline / 2) * stride_v; const uint8_t* start_v = &row_v[offset_v]; JOCTET* bufp = buffer[0]; for (JDIMENSION x = 0; x < cinfo.image_width; ++x) { *bufp++ = start_y[x]; *bufp++ = start_u[x / 2]; *bufp++ = start_v[x / 2]; } jpeg_write_scanlines(&cinfo, row, 1); } jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); encoder->data_read = false; encoder->compressed_data.resize(outlength); memcpy(encoder->compressed_data.data(), outbuffer, outlength); free(outbuffer); return heif_error_ok; } struct heif_error jpeg_get_compressed_data(void* encoder_raw, uint8_t** data, int* size, enum heif_encoded_data_type* type) { auto* encoder = (struct encoder_struct_jpeg*) encoder_raw; if (encoder->data_read) { *data = nullptr; *size = 0; } else { *data = encoder->compressed_data.data(); *size = (int) encoder->compressed_data.size(); encoder->data_read = true; } return heif_error_ok; } static const struct heif_encoder_plugin encoder_plugin_jpeg { /* plugin_api_version */ 3, /* compression_format */ heif_compression_JPEG, /* id_name */ "jpeg", /* priority */ JPEG_PLUGIN_PRIORITY, /* supports_lossy_compression */ true, /* supports_lossless_compression */ false, /* get_plugin_name */ jpeg_plugin_name, /* init_plugin */ jpeg_init_plugin, /* cleanup_plugin */ jpeg_cleanup_plugin, /* new_encoder */ jpeg_new_encoder, /* free_encoder */ jpeg_free_encoder, /* set_parameter_quality */ jpeg_set_parameter_quality, /* get_parameter_quality */ jpeg_get_parameter_quality, /* set_parameter_lossless */ jpeg_set_parameter_lossless, /* get_parameter_lossless */ jpeg_get_parameter_lossless, /* set_parameter_logging_level */ jpeg_set_parameter_logging_level, /* get_parameter_logging_level */ jpeg_get_parameter_logging_level, /* list_parameters */ jpeg_list_parameters, /* set_parameter_integer */ jpeg_set_parameter_integer, /* get_parameter_integer */ jpeg_get_parameter_integer, /* set_parameter_boolean */ jpeg_set_parameter_boolean, /* get_parameter_boolean */ jpeg_get_parameter_boolean, /* set_parameter_string */ jpeg_set_parameter_string, /* get_parameter_string */ jpeg_get_parameter_string, /* query_input_colorspace */ jpeg_query_input_colorspace, /* encode_image */ jpeg_encode_image, /* get_compressed_data */ jpeg_get_compressed_data, /* query_input_colorspace (v2) */ jpeg_query_input_colorspace2, /* query_encoded_size (v3) */ jpeg_query_encoded_size }; const struct heif_encoder_plugin* get_encoder_plugin_jpeg() { return &encoder_plugin_jpeg; } #if PLUGIN_JPEG_ENCODER heif_plugin_info plugin_info{ 1, heif_plugin_type_encoder, &encoder_plugin_jpeg }; #endif