libheif/plugins/encoder_openjpeg.cc (439 lines of code) (raw):
/*
* OpenJPEG codec.
* Copyright (c) 2023 Devon Sookhoo
* 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_openjpeg.h"
#include <openjpeg.h>
#include <string.h>
#include <vector>
#include <string>
#include <cassert>
using namespace std;
static const int OPJ_PLUGIN_PRIORITY = 80;
struct encoder_struct_opj
{
int quality = 70;
heif_chroma chroma = heif_chroma_undefined;
opj_cparameters_t parameters;
// --- output
std::vector<uint8_t> codestream; //contains encoded pixel data
bool data_read = false;
// --- parameters
// std::vector<parameter> parameters;
// void add_param(const parameter&);
// void add_param(const std::string& name, int value);
// void add_param(const std::string& name, bool value);
// void add_param(const std::string& name, const std::string& value);
// parameter get_param(const std::string& name) const;
// std::string preset;
// std::string tune;
// int logLevel = X265_LOG_NONE;
};
static const char* kParam_chroma = "chroma";
static const char* const kParam_chroma_valid_values[] = {
"420", "422", "444", nullptr
};
#define MAX_PLUGIN_NAME_LENGTH 80
static char plugin_name[MAX_PLUGIN_NAME_LENGTH];
const char* opj_plugin_name()
{
snprintf(plugin_name, MAX_PLUGIN_NAME_LENGTH, "OpenJPEG %s", opj_version());
plugin_name[MAX_PLUGIN_NAME_LENGTH - 1] = 0;
return plugin_name;
}
#define MAX_NPARAMETERS 10
static struct heif_encoder_parameter opj_encoder_params[MAX_NPARAMETERS];
static const struct heif_encoder_parameter* opj_encoder_parameter_ptrs[MAX_NPARAMETERS + 1];
static void opj_init_parameters()
{
struct heif_encoder_parameter* p = opj_encoder_params;
const struct heif_encoder_parameter** d = opj_encoder_parameter_ptrs;
int i = 0;
assert(i < MAX_NPARAMETERS);
p->version = 2;
p->name = kParam_chroma;
p->type = heif_encoder_parameter_type_string;
//p->string.default_value = "420";
p->has_default = false;
p->string.valid_values = kParam_chroma_valid_values;
d[i++] = p++;
d[i++] = nullptr;
}
void opj_init_plugin()
{
opj_init_parameters();
}
void opj_cleanup_plugin()
{
}
static void opj_set_default_parameters(void* encoder);
struct heif_error opj_new_encoder(void** encoder_out)
{
struct encoder_struct_opj* encoder = new encoder_struct_opj();
*encoder_out = encoder;
opj_set_default_parameters(encoder);
opj_set_default_encoder_parameters(&(encoder->parameters));
return heif_error_ok;
}
void opj_free_encoder(void* encoder_raw)
{
struct encoder_struct_opj* encoder = (struct encoder_struct_opj*) encoder_raw;
delete encoder;
}
struct heif_error opj_set_parameter_quality(void* encoder_raw, int quality)
{
auto* encoder = (struct encoder_struct_opj*) encoder_raw;
if (quality < 0 || quality > 100) {
return heif_error_invalid_parameter_value;
}
encoder->quality = quality;
return heif_error_ok;
}
struct heif_error opj_get_parameter_quality(void* encoder_raw, int* quality)
{
auto* encoder = (struct encoder_struct_opj*) encoder_raw;
*quality = encoder->quality;
return heif_error_ok;
}
struct heif_error opj_set_parameter_lossless(void* encoder_raw, int lossless)
{
auto* encoder = (struct encoder_struct_opj*) encoder_raw;
encoder->parameters.irreversible = lossless ? 0 : 1;
return heif_error_ok;
}
struct heif_error opj_get_parameter_lossless(void* encoder_raw, int* lossless)
{
auto* encoder = (struct encoder_struct_opj*) encoder_raw;
*lossless = (encoder->parameters.irreversible == 0);
return heif_error_ok;
}
struct heif_error opj_set_parameter_logging_level(void* encoder, int logging)
{
return heif_error_ok;
}
struct heif_error opj_get_parameter_logging_level(void* encoder, int* logging)
{
return heif_error_ok;
}
const struct heif_encoder_parameter** opj_list_parameters(void* encoder)
{
return opj_encoder_parameter_ptrs;
}
struct heif_error opj_set_parameter_integer(void* encoder_raw, const char* name, int value)
{
struct encoder_struct_opj* encoder = (struct encoder_struct_opj*) encoder_raw;
if (strcmp(name, heif_encoder_parameter_name_quality) == 0) {
return opj_set_parameter_quality(encoder, value);
}
return heif_error_unsupported_parameter;
}
struct heif_error opj_get_parameter_integer(void* encoder_raw, const char* name, int* value)
{
struct encoder_struct_opj* encoder = (struct encoder_struct_opj*) encoder_raw;
if (strcmp(name, heif_encoder_parameter_name_quality) == 0) {
return opj_get_parameter_quality(encoder, value);
}
return heif_error_ok;
}
struct heif_error opj_set_parameter_boolean(void* encoder, const char* name, int value)
{
return heif_error_ok;
}
struct heif_error opj_get_parameter_boolean(void* encoder, const char* name, int* value)
{
return heif_error_ok;
}
struct heif_error opj_set_parameter_string(void* encoder_raw, const char* name, const char* value)
{
auto* encoder = (struct encoder_struct_opj*) encoder_raw;
if (strcmp(name, kParam_chroma) == 0) {
if (strcmp(value, "420") == 0) {
encoder->chroma = heif_chroma_420;
return heif_error_ok;
}
else if (strcmp(value, "422") == 0) {
encoder->chroma = heif_chroma_422;
return heif_error_ok;
}
else if (strcmp(value, "444") == 0) {
encoder->chroma = heif_chroma_444;
return heif_error_ok;
}
else {
return heif_error_invalid_parameter_value;
}
}
return heif_error_unsupported_parameter;
}
static void save_strcpy(char* dst, int dst_size, const char* src)
{
strncpy(dst, src, dst_size - 1);
dst[dst_size - 1] = 0;
}
struct heif_error opj_get_parameter_string(void* encoder_raw, const char* name, char* value, int value_size)
{
struct encoder_struct_opj* encoder = (struct encoder_struct_opj*) encoder_raw;
if (strcmp(name, kParam_chroma) == 0) {
switch (encoder->chroma) {
case heif_chroma_420:
save_strcpy(value, value_size, "420");
break;
case heif_chroma_422:
save_strcpy(value, value_size, "422");
break;
case heif_chroma_444:
save_strcpy(value, value_size, "444");
break;
case heif_chroma_undefined:
save_strcpy(value, value_size, "undefined");
break;
default:
assert(false);
return heif_error_invalid_parameter_value;
}
return heif_error_ok;
}
return heif_error_unsupported_parameter;
}
static void opj_set_default_parameters(void* encoder)
{
for (const struct heif_encoder_parameter** p = opj_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:
opj_set_parameter_integer(encoder, param->name, param->integer.default_value);
break;
case heif_encoder_parameter_type_boolean:
opj_set_parameter_boolean(encoder, param->name, param->boolean.default_value);
break;
case heif_encoder_parameter_type_string:
opj_set_parameter_string(encoder, param->name, param->string.default_value);
break;
}
}
}
}
void opj_query_input_colorspace(enum heif_colorspace* inout_colorspace, enum heif_chroma* inout_chroma)
{
// Replace the input colorspace/chroma with the one that is supported by the encoder and that
// comes as close to the input colorspace/chroma as possible.
if (*inout_colorspace == heif_colorspace_monochrome) {
*inout_colorspace = heif_colorspace_monochrome;
*inout_chroma = heif_chroma_monochrome;
}
else {
*inout_colorspace = heif_colorspace_YCbCr;
*inout_chroma = heif_chroma_444;
}
}
void opj_query_input_colorspace2(void* encoder_raw, enum heif_colorspace* inout_colorspace, enum heif_chroma* inout_chroma)
{
auto* encoder = (struct encoder_struct_opj*) encoder_raw;
if (*inout_colorspace == heif_colorspace_monochrome) {
*inout_colorspace = heif_colorspace_monochrome;
*inout_chroma = heif_chroma_monochrome;
}
else {
*inout_colorspace = heif_colorspace_YCbCr;
if (encoder->chroma != heif_chroma_undefined) {
*inout_chroma = encoder->chroma;
}
else {
*inout_chroma = heif_chroma_444;
}
}
}
// OpenJPEG will encode a portion of the image and then call this function
// @param src_data_raw - Newly encoded bytes provided by OpenJPEG
// @param nb_bytes - The number of bytes or size of src_data_raw
// @param encoder_raw - Out the new
// @return - The number of bytes successfuly transfered
static OPJ_SIZE_T opj_write_from_buffer(void* src_data_raw, OPJ_SIZE_T nb_bytes, void* encoder_raw)
{
uint8_t* src_data = (uint8_t*) src_data_raw;
struct encoder_struct_opj* encoder = (struct encoder_struct_opj*) encoder_raw;
for (size_t i = 0; i < nb_bytes; i++) {
encoder->codestream.push_back(src_data[i]);
}
return nb_bytes;
}
static void opj_close_from_buffer(void* p_user_data)
{
}
// The codestream is defined in ISO/IEC 15444-1. It contains the
// compressed image pixel data and very basic metadata.
// @param data - Uncompressed image pixel data
// @param encoder - The function will output codestream in encoder->codestream
static heif_error generate_codestream(opj_image_t* image, struct encoder_struct_opj* encoder)
{
heif_error error;
OPJ_BOOL success;
encoder->parameters.cp_disto_alloc = 1;
encoder->parameters.tcp_numlayers = 1;
encoder->parameters.tcp_rates[0] = (float)(1 + (100 - encoder->quality)/2);
#if 0
//Insert a human readable comment into the codestream
if (parameters.cp_comment == NULL) {
char buf[80];
#ifdef _WIN32
sprintf_s(buf, 80, "Created by OpenJPEG version %s", opj_version());
#else
snprintf(buf, 80, "Created by OpenJPEG version %s", opj_version());
#endif
parameters.cp_comment = strdup(buf);
}
#endif
//OPJ_CODEC_J2K - Only generate the codestream
//OPJ_CODEC_JP2 - Generate the entire jp2 file (which contains a codestream)
OPJ_CODEC_FORMAT codec_format = OPJ_CODEC_J2K;
opj_codec_t* codec = opj_create_compress(codec_format);
success = opj_setup_encoder(codec, &(encoder->parameters), image);
if (!success) {
opj_destroy_codec(codec);
error = {heif_error_Encoding_error, heif_suberror_Unspecified, "Failed to setup OpenJPEG encoder"};
return error;
}
//Create Stream
size_t bufferSize = 64 * 1024; // 64k
opj_stream_t* stream = opj_stream_create(bufferSize, false /* read only mode */);
if (stream == NULL) {
opj_destroy_codec(codec);
error = {heif_error_Encoding_error, heif_suberror_Unspecified, "Failed to create opj_stream_t"};
return error;
}
// When OpenJPEG encodes the image, it will pass the 'encoder' into the write function
opj_stream_set_user_data(stream, encoder, opj_close_from_buffer);
// Tell OpenJPEG how and where to write the output data
opj_stream_set_write_function(stream, (opj_stream_write_fn) opj_write_from_buffer);
// TODO: should we use this function?
// opj_stream_set_user_data_length(stream, 0);
success = opj_start_compress(codec, image, stream);
if (!success) {
opj_stream_destroy(stream);
opj_destroy_codec(codec);
error = {heif_error_Encoding_error, heif_suberror_Unspecified, "Failed opj_start_compress()"};
return error;
}
success = opj_encode(codec, stream);
if (!success) {
opj_stream_destroy(stream);
opj_destroy_codec(codec);
error = {heif_error_Encoding_error, heif_suberror_Unspecified, "Failed opj_encode()"};
return error;
}
success = opj_end_compress(codec, stream);
if (!success) {
opj_stream_destroy(stream);
opj_destroy_codec(codec);
error = {heif_error_Encoding_error, heif_suberror_Unspecified, "Failed opj_end_compress()"};
return error;
}
opj_stream_destroy(stream);
opj_destroy_codec(codec);
return heif_error_ok;
}
struct heif_error opj_encode_image(void* encoder_raw, const struct heif_image* image, enum heif_image_input_class image_class)
{
struct encoder_struct_opj* encoder = (struct encoder_struct_opj*) encoder_raw;
struct heif_error err;
heif_chroma chroma = heif_image_get_chroma_format(image);
heif_colorspace colorspace = heif_image_get_colorspace(image);
int width = heif_image_get_primary_width(image);
int height = heif_image_get_primary_height(image);
std::vector<heif_channel> channels;
OPJ_COLOR_SPACE opj_colorspace;
switch (colorspace) {
case heif_colorspace_YCbCr:
channels = {heif_channel_Y, heif_channel_Cb, heif_channel_Cr};
opj_colorspace = OPJ_CLRSPC_SYCC;
break;
case heif_colorspace_RGB:
channels = {heif_channel_R, heif_channel_G, heif_channel_B};
opj_colorspace = OPJ_CLRSPC_SRGB;
break;
case heif_colorspace_monochrome:
channels = {heif_channel_Y};
opj_colorspace = OPJ_CLRSPC_GRAY;
break;
default:
assert(false);
return heif_error{heif_error_Encoding_error, heif_suberror_Unspecified, "OpenJPEG encoder plugin received image with invalid colorspace."};
}
int band_count = (int) channels.size();
opj_image_cmptparm_t component_params[4];
memset(&component_params, 0, band_count * sizeof(opj_image_cmptparm_t));
for (int comp = 0; comp < band_count; comp++) {
int bpp = heif_image_get_bits_per_pixel_range(image, channels[comp]);
int sub_dx = 1, sub_dy = 1;
switch (chroma) {
case heif_chroma_420:
sub_dx = 2;
sub_dy = 2;
break;
case heif_chroma_422:
sub_dx = 2;
sub_dy = 1;
break;
default:
break;
}
component_params[comp].prec = bpp;
component_params[comp].sgnd = 0;
component_params[comp].dx = comp == 0 ? 1 : sub_dx;
component_params[comp].dy = comp == 0 ? 1 : sub_dy;
component_params[comp].w = comp == 0 ? width : (width + sub_dx / 2) / sub_dx;
component_params[comp].h = comp == 0 ? height : (height + sub_dy / 2) / sub_dy;
}
opj_image_t* opj_image = opj_image_create(band_count, &component_params[0], opj_colorspace);
if (image == nullptr) {
// Failed to create image
err = {heif_error_Encoding_error, heif_suberror_Unspecified, "Failed create OpenJPEG image"};
return err;
}
opj_image->x0 = 0;
opj_image->y0 = 0;
opj_image->x1 = width;
opj_image->y1 = height;
for (int comp = 0; comp < band_count; comp++) {
int stride;
const uint8_t* p = heif_image_get_plane_readonly(image, channels[comp], &stride);
int cwidth = component_params[comp].w;
int cheight = component_params[comp].h;
// Note: obj_image data is 32bit integer
for (int y = 0; y < cheight; y++) {
for (int x = 0; x < cwidth; x++) {
opj_image->comps[comp].data[y * cwidth + x] = p[y * stride + x];
}
}
}
encoder->data_read = false;
encoder->codestream.clear(); //Fixes issue when encoding multiple images and old data persists.
//Encodes the image into a 'codestream' which is stored in the 'encoder' variable
err = generate_codestream(opj_image, encoder);
opj_image_destroy(opj_image);
return err;
}
struct heif_error opj_get_compressed_data(void* encoder_raw, uint8_t** data, int* size, enum heif_encoded_data_type* type)
{
// Get a packet of decoded data. The data format depends on the codec.
struct encoder_struct_opj* encoder = (struct encoder_struct_opj*) encoder_raw;
if (encoder->data_read) {
*size = 0;
*data = nullptr;
}
else {
*size = (int) encoder->codestream.size();
*data = encoder->codestream.data();
encoder->data_read = true;
}
return heif_error_ok;
}
void opj_query_encoded_size(void* encoder, uint32_t input_width, uint32_t input_height, uint32_t* encoded_width, uint32_t* encoded_height)
{
// --- version 3 ---
// The encoded image size may be different from the input frame size, e.g. because
// of required rounding, or a required minimum size. Use this function to return
// the encoded size for a given input image size.
// You may set this to NULL if no padding is required for any image size.
}
static const struct heif_encoder_plugin encoder_plugin_openjpeg{
/* plugin_api_version */ 3,
/* compression_format */ heif_compression_JPEG2000,
/* id_name */ "openjpeg",
/* priority */ OPJ_PLUGIN_PRIORITY,
/* supports_lossy_compression */ false,
/* supports_lossless_compression */ true,
/* get_plugin_name */ opj_plugin_name,
/* init_plugin */ opj_init_plugin,
/* cleanup_plugin */ opj_cleanup_plugin,
/* new_encoder */ opj_new_encoder,
/* free_encoder */ opj_free_encoder,
/* set_parameter_quality */ opj_set_parameter_quality,
/* get_parameter_quality */ opj_get_parameter_quality,
/* set_parameter_lossless */ opj_set_parameter_lossless,
/* get_parameter_lossless */ opj_get_parameter_lossless,
/* set_parameter_logging_level */ opj_set_parameter_logging_level,
/* get_parameter_logging_level */ opj_get_parameter_logging_level,
/* list_parameters */ opj_list_parameters,
/* set_parameter_integer */ opj_set_parameter_integer,
/* get_parameter_integer */ opj_get_parameter_integer,
/* set_parameter_boolean */ opj_set_parameter_boolean,
/* get_parameter_boolean */ opj_get_parameter_boolean,
/* set_parameter_string */ opj_set_parameter_string,
/* get_parameter_string */ opj_get_parameter_string,
/* query_input_colorspace */ opj_query_input_colorspace,
/* encode_image */ opj_encode_image,
/* get_compressed_data */ opj_get_compressed_data,
/* query_input_colorspace (v2) */ opj_query_input_colorspace2,
/* query_encoded_size (v3) */ opj_query_encoded_size
};
const struct heif_encoder_plugin* get_encoder_plugin_openjpeg()
{
return &encoder_plugin_openjpeg;
}
#if PLUGIN_OPENJPEG_ENCODER
heif_plugin_info plugin_info {
1,
heif_plugin_type_encoder,
&encoder_plugin_openjpeg
};
#endif