blob: 2bbec6221a26f92e5a2c1a38d1a8894fa7666114 [file] [log] [blame]
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <errno.h>
#include <setjmp.h>
#include <cmath>
#include <cstring>
#include <map>
#include <memory>
#include <string>
#include "ultrahdr/ultrahdrcommon.h"
#include "ultrahdr/jpegencoderhelper.h"
namespace ultrahdr {
/*!\brief map of sub sampling format and jpeg h_samp_factor, v_samp_factor */
std::map<uhdr_img_fmt_t, std::vector<int>> sample_factors = {
{UHDR_IMG_FMT_8bppYCbCr400,
{1 /*h0*/, 1 /*v0*/, 0 /*h1*/, 0 /*v1*/, 0 /*h2*/, 0 /*v2*/, 1 /*maxh*/, 1 /*maxv*/}},
{UHDR_IMG_FMT_24bppYCbCr444,
{1 /*h0*/, 1 /*v0*/, 1 /*h1*/, 1 /*v1*/, 1 /*h2*/, 1 /*v2*/, 1 /*maxh*/, 1 /*maxv*/}},
{UHDR_IMG_FMT_16bppYCbCr440,
{1 /*h0*/, 2 /*v0*/, 1 /*h1*/, 1 /*v1*/, 1 /*h2*/, 1 /*v2*/, 1 /*maxh*/, 2 /*maxv*/}},
{UHDR_IMG_FMT_16bppYCbCr422,
{2 /*h0*/, 1 /*v0*/, 1 /*h1*/, 1 /*v1*/, 1 /*h2*/, 1 /*v2*/, 2 /*maxh*/, 1 /*maxv*/}},
{UHDR_IMG_FMT_12bppYCbCr420,
{2 /*h0*/, 2 /*v0*/, 1 /*h1*/, 1 /*v1*/, 1 /*h2*/, 1 /*v2*/, 2 /*maxh*/, 2 /*maxv*/}},
{UHDR_IMG_FMT_12bppYCbCr411,
{4 /*h0*/, 1 /*v0*/, 1 /*h1*/, 1 /*v1*/, 1 /*h2*/, 1 /*v2*/, 4 /*maxh*/, 1 /*maxv*/}},
{UHDR_IMG_FMT_10bppYCbCr410,
{4 /*h0*/, 2 /*v0*/, 1 /*h1*/, 1 /*v1*/, 1 /*h2*/, 1 /*v2*/, 4 /*maxh*/, 2 /*maxv*/}},
{UHDR_IMG_FMT_24bppRGB888,
{1 /*h0*/, 1 /*v0*/, 1 /*h1*/, 1 /*v1*/, 1 /*h2*/, 1 /*v2*/, 1 /*maxh*/, 1 /*maxv*/}},
};
/*!\brief jpeg encoder library destination manager callback functions implementation */
/*!\brief called by jpeg_start_compress() before any data is actually written. This function is
* expected to initialize fields next_output_byte (place to write encoded output) and
* free_in_buffer (size of the buffer supplied) of jpeg destination manager. free_in_buffer must
* be initialized to a positive value.*/
static void initDestination(j_compress_ptr cinfo) {
destination_mgr_impl* dest = reinterpret_cast<destination_mgr_impl*>(cinfo->dest);
std::vector<JOCTET>& buffer = dest->mResultBuffer;
buffer.resize(dest->kBlockSize);
dest->next_output_byte = &buffer[0];
dest->free_in_buffer = buffer.size();
}
/*!\brief called if buffer provided for storing encoded data is exhausted during encoding. This
* function is expected to consume the encoded output and provide fresh buffer to continue
* encoding. */
static boolean emptyOutputBuffer(j_compress_ptr cinfo) {
destination_mgr_impl* dest = reinterpret_cast<destination_mgr_impl*>(cinfo->dest);
std::vector<JOCTET>& buffer = dest->mResultBuffer;
size_t oldsize = buffer.size();
buffer.resize(oldsize + dest->kBlockSize);
dest->next_output_byte = &buffer[oldsize];
dest->free_in_buffer = dest->kBlockSize;
return TRUE;
}
/*!\brief called by jpeg_finish_compress() to flush out all the remaining encoded data. client
* can use either next_output_byte or free_in_buffer to determine how much data is in the buffer.
*/
static void terminateDestination(j_compress_ptr cinfo) {
destination_mgr_impl* dest = reinterpret_cast<destination_mgr_impl*>(cinfo->dest);
std::vector<JOCTET>& buffer = dest->mResultBuffer;
buffer.resize(buffer.size() - dest->free_in_buffer);
}
/*!\brief module for managing error */
struct jpeg_error_mgr_impl : jpeg_error_mgr {
jmp_buf setjmp_buffer;
};
/*!\brief jpeg encoder library error manager callback function implementations */
static void jpegrerror_exit(j_common_ptr cinfo) {
jpeg_error_mgr_impl* err = reinterpret_cast<jpeg_error_mgr_impl*>(cinfo->err);
longjmp(err->setjmp_buffer, 1);
}
/* receive most recent jpeg error message and print */
static void outputErrorMessage(j_common_ptr cinfo) {
char buffer[JMSG_LENGTH_MAX];
/* Create the message */
(*cinfo->err->format_message)(cinfo, buffer);
ALOGE("%s\n", buffer);
}
uhdr_error_info_t JpegEncoderHelper::compressImage(const uhdr_raw_image_t* img, const int qfactor,
const void* iccBuffer, const size_t iccSize) {
const uint8_t* planes[3]{reinterpret_cast<uint8_t*>(img->planes[UHDR_PLANE_Y]),
reinterpret_cast<uint8_t*>(img->planes[UHDR_PLANE_U]),
reinterpret_cast<uint8_t*>(img->planes[UHDR_PLANE_V])};
const unsigned int strides[3]{img->stride[UHDR_PLANE_Y], img->stride[UHDR_PLANE_U],
img->stride[UHDR_PLANE_V]};
return compressImage(planes, strides, img->w, img->h, img->fmt, qfactor, iccBuffer, iccSize);
}
uhdr_error_info_t JpegEncoderHelper::compressImage(const uint8_t* planes[3],
const unsigned int strides[3], const int width,
const int height, const uhdr_img_fmt_t format,
const int qfactor, const void* iccBuffer,
const size_t iccSize) {
return encode(planes, strides, width, height, format, qfactor, iccBuffer, iccSize);
}
uhdr_compressed_image_t JpegEncoderHelper::getCompressedImage() {
uhdr_compressed_image_t img;
img.data = mDestMgr.mResultBuffer.data();
img.capacity = img.data_sz = mDestMgr.mResultBuffer.size();
img.cg = UHDR_CG_UNSPECIFIED;
img.ct = UHDR_CT_UNSPECIFIED;
img.range = UHDR_CR_UNSPECIFIED;
return img;
}
uhdr_error_info_t JpegEncoderHelper::encode(const uint8_t* planes[3], const unsigned int strides[3],
const int width, const int height,
const uhdr_img_fmt_t format, const int qfactor,
const void* iccBuffer, const size_t iccSize) {
jpeg_compress_struct cinfo;
jpeg_error_mgr_impl myerr;
uhdr_error_info_t status = g_no_error;
if (sample_factors.find(format) == sample_factors.end()) {
status.error_code = UHDR_CODEC_INVALID_PARAM;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail, "unrecognized input format %d", format);
return status;
}
std::vector<int>& factors = sample_factors.find(format)->second;
cinfo.err = jpeg_std_error(&myerr);
myerr.error_exit = jpegrerror_exit;
myerr.output_message = outputErrorMessage;
if (0 == setjmp(myerr.setjmp_buffer)) {
jpeg_create_compress(&cinfo);
// initialize destination manager
mDestMgr.init_destination = &initDestination;
mDestMgr.empty_output_buffer = &emptyOutputBuffer;
mDestMgr.term_destination = &terminateDestination;
mDestMgr.mResultBuffer.clear();
cinfo.dest = reinterpret_cast<struct jpeg_destination_mgr*>(&mDestMgr);
// initialize configuration parameters
cinfo.image_width = width;
cinfo.image_height = height;
bool isGainMapImg = true;
if (format == UHDR_IMG_FMT_24bppRGB888) {
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
} else {
if (format == UHDR_IMG_FMT_8bppYCbCr400) {
cinfo.input_components = 1;
cinfo.in_color_space = JCS_GRAYSCALE;
} else if (format == UHDR_IMG_FMT_12bppYCbCr420 || format == UHDR_IMG_FMT_24bppYCbCr444 ||
format == UHDR_IMG_FMT_16bppYCbCr422 || format == UHDR_IMG_FMT_16bppYCbCr440 ||
format == UHDR_IMG_FMT_12bppYCbCr411 || format == UHDR_IMG_FMT_10bppYCbCr410) {
cinfo.input_components = 3;
cinfo.in_color_space = JCS_YCbCr;
isGainMapImg = false;
} else {
status.error_code = UHDR_CODEC_ERROR;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"unrecognized input color format for encoding, color format %d", format);
jpeg_destroy_compress(&cinfo);
return status;
}
}
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, qfactor, TRUE);
for (int i = 0; i < cinfo.num_components; i++) {
cinfo.comp_info[i].h_samp_factor = factors[i * 2];
cinfo.comp_info[i].v_samp_factor = factors[i * 2 + 1];
mPlaneWidth[i] =
std::ceil(((float)cinfo.image_width * cinfo.comp_info[i].h_samp_factor) / factors[6]);
mPlaneHeight[i] =
std::ceil(((float)cinfo.image_height * cinfo.comp_info[i].v_samp_factor) / factors[7]);
}
if (format != UHDR_IMG_FMT_24bppRGB888) cinfo.raw_data_in = TRUE;
cinfo.dct_method = JDCT_ISLOW;
// start compress
jpeg_start_compress(&cinfo, TRUE);
if (iccBuffer != nullptr && iccSize > 0) {
jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize);
}
if (isGainMapImg) {
char comment[255];
snprintf(comment, sizeof comment,
"Source: google libuhdr v%s, Coder: libjpeg v%d, Attrib: GainMap Image",
UHDR_LIB_VERSION_STR, JPEG_LIB_VERSION);
jpeg_write_marker(&cinfo, JPEG_COM, reinterpret_cast<JOCTET*>(comment), strlen(comment));
}
if (format == UHDR_IMG_FMT_24bppRGB888) {
while (cinfo.next_scanline < cinfo.image_height) {
JSAMPROW row_pointer[]{
const_cast<JSAMPROW>(&planes[0][cinfo.next_scanline * strides[0] * 3])};
JDIMENSION processed = jpeg_write_scanlines(&cinfo, row_pointer, 1);
if (1 != processed) {
status.error_code = UHDR_CODEC_ERROR;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"jpeg_read_scanlines returned %d, expected %d", processed, 1);
jpeg_destroy_compress(&cinfo);
return status;
}
}
} else {
status = compressYCbCr(&cinfo, planes, strides);
if (status.error_code != UHDR_CODEC_OK) {
jpeg_destroy_compress(&cinfo);
return status;
}
}
} else {
status.error_code = UHDR_CODEC_ERROR;
status.has_detail = 1;
cinfo.err->format_message((j_common_ptr)&cinfo, status.detail);
jpeg_destroy_compress(&cinfo);
return status;
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
return status;
}
uhdr_error_info_t JpegEncoderHelper::compressYCbCr(jpeg_compress_struct* cinfo,
const uint8_t* planes[3],
const unsigned int strides[3]) {
JSAMPROW mcuRows[kMaxNumComponents][2 * DCTSIZE];
JSAMPROW mcuRowsTmp[kMaxNumComponents][2 * DCTSIZE];
size_t alignedPlaneWidth[kMaxNumComponents]{};
JSAMPARRAY subImage[kMaxNumComponents];
for (int i = 0; i < cinfo->num_components; i++) {
alignedPlaneWidth[i] = ALIGNM(mPlaneWidth[i], DCTSIZE);
if (strides[i] < alignedPlaneWidth[i]) {
mPlanesMCURow[i] = std::make_unique<uint8_t[]>(alignedPlaneWidth[i] * DCTSIZE *
cinfo->comp_info[i].v_samp_factor);
uint8_t* mem = mPlanesMCURow[i].get();
for (int j = 0; j < DCTSIZE * cinfo->comp_info[i].v_samp_factor;
j++, mem += alignedPlaneWidth[i]) {
mcuRowsTmp[i][j] = mem;
if (i > 0) {
memset(mem + mPlaneWidth[i], 128, alignedPlaneWidth[i] - mPlaneWidth[i]);
}
}
} else if (mPlaneHeight[i] % DCTSIZE != 0) {
mPlanesMCURow[i] = std::make_unique<uint8_t[]>(alignedPlaneWidth[i]);
if (i > 0) {
memset(mPlanesMCURow[i].get(), 128, alignedPlaneWidth[i]);
}
}
subImage[i] = strides[i] < alignedPlaneWidth[i] ? mcuRowsTmp[i] : mcuRows[i];
}
while (cinfo->next_scanline < cinfo->image_height) {
JDIMENSION mcu_scanline_start[kMaxNumComponents];
for (int i = 0; i < cinfo->num_components; i++) {
mcu_scanline_start[i] =
std::ceil(((float)cinfo->next_scanline * cinfo->comp_info[i].v_samp_factor) /
cinfo->max_v_samp_factor);
for (int j = 0; j < cinfo->comp_info[i].v_samp_factor * DCTSIZE; j++) {
JDIMENSION scanline = mcu_scanline_start[i] + j;
if (scanline < mPlaneHeight[i]) {
mcuRows[i][j] = const_cast<uint8_t*>(planes[i] + (size_t)scanline * strides[i]);
if (strides[i] < alignedPlaneWidth[i]) {
memcpy(mcuRowsTmp[i][j], mcuRows[i][j], mPlaneWidth[i]);
}
} else {
mcuRows[i][j] = mPlanesMCURow[i].get();
}
}
}
int processed = jpeg_write_raw_data(cinfo, subImage, DCTSIZE * cinfo->max_v_samp_factor);
if (processed != DCTSIZE * cinfo->max_v_samp_factor) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_ERROR;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"number of scan lines processed %d does not equal requested scan lines %d ",
processed, DCTSIZE * cinfo->max_v_samp_factor);
return status;
}
}
return g_no_error;
}
} // namespace ultrahdr