blob: 9bb65af4a849b54be16e01135379432c4d5a97b3 [file] [log] [blame]
/*
* Copyright (C) 2014 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 "jpegutil.h"
#include <memory.h>
#include <array>
#include <vector>
#include <cstring>
#include <cstdio>
#include <setjmp.h>
extern "C" {
#include "jpeglib.h"
}
using namespace std;
template <typename T>
void safeDelete(T& t) {
if (t != nullptr) {
delete t;
t = nullptr;
}
}
template <typename T>
void safeDeleteArray(T& t) {
if (t != nullptr) {
delete[] t;
t = nullptr;
}
}
jpegutil::Plane::RowIterator::RowIterator(const Plane* plane) : plane_(plane) {
// We must be able to supply up to 8 * 2 lines at a time to libjpeg.
// 8 = vertical size of blocks transformed with DCT.
// 2 = scaling factor for Y vs UV planes.
bufRowCount_ = 16;
// Rows must be padded to the next multiple of 16
// TODO OPTIMIZE Cb and Cr components only need to be padded to a multiple of
// 8.
rowPadding_ = (16 - (plane_->planeWidth_ % 16)) % 16;
bufRowStride_ = plane_->planeWidth_ + rowPadding_;
// Round up to the nearest multiple of 64 for cache alignment
bufRowStride_ = (bufRowStride_ + 63) & ~63;
// Allocate an extra 64 bytes to allow for cache alignment
size_t bufSize = bufRowStride_ * bufRowCount_ + 64;
// TODO OPTIMIZE if the underlying data has a pixel-stride of 1, and an image
// width which is a multiple of 16, we can avoid this allocation and simply
// return pointers into the underlying data in operator()(int) instead of
// copying the data.
buffer_ = unique_ptr<unsigned char[]>(new unsigned char[bufSize]);
// Find the start of the 64-byte aligned buffer we allocated.
size_t bufStart = reinterpret_cast<size_t>(&buffer_[0]);
size_t alignedBufStart = (bufStart + 63) & ~63;
alignedBuffer_ = reinterpret_cast<unsigned char*>(alignedBufStart);
bufCurRow_ = 0;
}
unsigned char* jpegutil::Plane::RowIterator::operator()(int y) {
unsigned char* bufCurRowPtr = alignedBuffer_ + bufRowStride_ * bufCurRow_;
unsigned char* srcPtr = &plane_->data_[y * plane_->rowStride_];
unsigned char* dstPtr = bufCurRowPtr;
// Use memcpy when possible.
if (plane_->pixelStride_ == 1) {
memcpy(dstPtr, srcPtr, plane_->planeWidth_);
} else {
int pixelStride = plane_->pixelStride_;
for (int i = 0; i < plane_->planeWidth_; i++) {
*dstPtr = *srcPtr;
srcPtr += pixelStride;
dstPtr++;
}
}
// Add padding to the right side by replicating the rightmost column of
// (actual) image values into the padding bytes.
memset(&bufCurRowPtr[plane_->planeWidth_],
bufCurRowPtr[plane_->planeWidth_ - 1], rowPadding_);
bufCurRow_++;
// Wrap within ring buffer.
bufCurRow_ %= bufRowCount_;
return bufCurRowPtr;
}
jpegutil::Plane::Plane(int imgWidth, int imgHeight, int planeWidth,
int planeHeight, unsigned char* data, int pixelStride,
int rowStride)
: imgWidth_(imgWidth),
imgHeight_(imgHeight),
planeWidth_(planeWidth),
planeHeight_(planeHeight),
data_(data),
rowStride_(rowStride),
pixelStride_(pixelStride) {}
int jpegutil::compress(const Plane& yPlane, const Plane& cbPlane,
const Plane& crPlane, unsigned char* outBuf,
size_t outBufCapacity, std::function<void(size_t)> flush,
int quality) {
int imgWidth = yPlane.imgWidth();
int imgHeight = yPlane.imgHeight();
// libjpeg requires the use of setjmp/longjmp to recover from errors. Since
// this doesn't play well with RAII, we must use pointers and manually call
// delete. See POSIX documentation for longjmp() for details on why the
// volatile keyword is necessary.
volatile jpeg_compress_struct cinfov;
jpeg_compress_struct& cinfo =
*const_cast<struct jpeg_compress_struct*>(&cinfov);
JSAMPROW* volatile yArr = nullptr;
JSAMPROW* volatile cbArr = nullptr;
JSAMPROW* volatile crArr = nullptr;
Plane::RowIterator* volatile yRowGenerator = nullptr;
Plane::RowIterator* volatile cbRowGenerator = nullptr;
Plane::RowIterator* volatile crRowGenerator = nullptr;
JSAMPARRAY imgArr[3];
// Error handling
struct my_error_mgr {
struct jpeg_error_mgr pub;
jmp_buf setjmp_buffer;
} err;
cinfo.err = jpeg_std_error(&err.pub);
// Default error_exit will call exit(), so override
// to return control via setjmp/longjmp.
err.pub.error_exit = [](j_common_ptr cinfo) {
my_error_mgr* myerr = reinterpret_cast<my_error_mgr*>(cinfo->err);
(*cinfo->err->output_message)(cinfo);
// Return control to the setjmp point (see call to setjmp()).
longjmp(myerr->setjmp_buffer, 1);
};
cinfo.err = (struct jpeg_error_mgr*)&err;
// Set the setjmp point to return to in case of error.
if (setjmp(err.setjmp_buffer)) {
// If libjpeg hits an error, control will jump to this point (see call to
// longjmp()).
jpeg_destroy_compress(&cinfo);
safeDeleteArray(yArr);
safeDeleteArray(cbArr);
safeDeleteArray(crArr);
safeDelete(yRowGenerator);
safeDelete(cbRowGenerator);
safeDelete(crRowGenerator);
return -1;
}
// Create jpeg compression context
jpeg_create_compress(&cinfo);
// Stores data needed by our c-style callbacks into libjpeg
struct ClientData {
unsigned char* outBuf;
size_t outBufCapacity;
std::function<void(size_t)> flush;
int totalOutputBytes;
} clientData{outBuf, outBufCapacity, flush, 0};
cinfo.client_data = &clientData;
// Initialize destination manager
jpeg_destination_mgr dest;
dest.init_destination = [](j_compress_ptr cinfo) {
ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data);
cinfo->dest->next_output_byte = cdata.outBuf;
cinfo->dest->free_in_buffer = cdata.outBufCapacity;
};
dest.empty_output_buffer = [](j_compress_ptr cinfo) -> boolean {
ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data);
size_t numBytesInBuffer = cdata.outBufCapacity;
cdata.flush(numBytesInBuffer);
cdata.totalOutputBytes += numBytesInBuffer;
// Reset the buffer
cinfo->dest->next_output_byte = cdata.outBuf;
cinfo->dest->free_in_buffer = cdata.outBufCapacity;
return true;
};
dest.term_destination = [](j_compress_ptr cinfo) {
// do nothing to terminate the output buffer
};
cinfo.dest = &dest;
// Set jpeg parameters
cinfo.image_width = imgWidth;
cinfo.image_height = imgHeight;
cinfo.input_components = 3;
// Set defaults based on the above values
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, true);
cinfo.dct_method = JDCT_IFAST;
cinfo.raw_data_in = true;
jpeg_set_colorspace(&cinfo, JCS_YCbCr);
cinfo.comp_info[0].h_samp_factor = 2;
cinfo.comp_info[0].v_samp_factor = 2;
cinfo.comp_info[1].h_samp_factor = 1;
cinfo.comp_info[1].v_samp_factor = 1;
cinfo.comp_info[2].h_samp_factor = 1;
cinfo.comp_info[2].v_samp_factor = 1;
jpeg_start_compress(&cinfo, true);
yArr = new JSAMPROW[cinfo.comp_info[0].v_samp_factor * DCTSIZE];
cbArr = new JSAMPROW[cinfo.comp_info[1].v_samp_factor * DCTSIZE];
crArr = new JSAMPROW[cinfo.comp_info[2].v_samp_factor * DCTSIZE];
imgArr[0] = const_cast<JSAMPARRAY>(yArr);
imgArr[1] = const_cast<JSAMPARRAY>(cbArr);
imgArr[2] = const_cast<JSAMPARRAY>(crArr);
yRowGenerator = new Plane::RowIterator(&yPlane);
cbRowGenerator = new Plane::RowIterator(&cbPlane);
crRowGenerator = new Plane::RowIterator(&crPlane);
Plane::RowIterator& yRG = *const_cast<Plane::RowIterator*>(yRowGenerator);
Plane::RowIterator& cbRG = *const_cast<Plane::RowIterator*>(cbRowGenerator);
Plane::RowIterator& crRG = *const_cast<Plane::RowIterator*>(crRowGenerator);
for (int y = 0; y < imgHeight; y += DCTSIZE * 2) {
for (int row = 0; row < DCTSIZE * 2; row++) {
yArr[row] = yRG(y + row);
}
for (int row = 0; row < DCTSIZE; row++) {
// The y-index within the subsampled chroma planes to send to libjpeg.
const int chY = y / 2 + row;
if (chY < imgHeight / 2) {
cbArr[row] = cbRG(chY);
crArr[row] = crRG(chY);
} else {
// When we have run out of rows in the chroma planes to compress, send
// the last row as padding.
cbArr[row] = cbRG(imgHeight / 2 - 1);
crArr[row] = crRG(imgHeight / 2 - 1);
}
}
jpeg_write_raw_data(&cinfo, imgArr, DCTSIZE * 2);
}
jpeg_finish_compress(&cinfo);
int numBytesInBuffer = cinfo.dest->next_output_byte - outBuf;
flush(numBytesInBuffer);
clientData.totalOutputBytes += numBytesInBuffer;
safeDeleteArray(yArr);
safeDeleteArray(cbArr);
safeDeleteArray(crArr);
safeDelete(yRowGenerator);
safeDelete(cbRowGenerator);
safeDelete(crRowGenerator);
return clientData.totalOutputBytes;
}