Files
lime/project/src/graphics/format/JPEG.cpp
Joseph Cloutier a03c0c31f3 Fix whitespace.
2024-11-19 20:09:22 -05:00

498 lines
8.7 KiB
C++

#include <system/System.h>
extern "C" {
#include <sys/types.h>
#include <stdio.h>
#include <jpeglib.h>
}
#include <setjmp.h>
#include <graphics/format/JPEG.h>
namespace lime {
struct ErrorData {
struct jpeg_error_mgr base;
jmp_buf on_error;
};
static void OnOutput (j_common_ptr cinfo) {}
// #ifdef NDEBUG
// char errorMessage[JMSG_LENGTH_MAX];
// #endif
static void OnError (j_common_ptr cinfo) {
ErrorData * err = (ErrorData *)cinfo->err;
// #ifdef NDEBUG
// ( *(cinfo->err->format_message) ) (cinfo, errorMessage);
// fprintf(stderr, "%s\n", errorMessage);
// #endif
longjmp (err->on_error, 1);
}
struct MySrcManager {
MySrcManager (const JOCTET *inData, int inLen) : mData (inData), mLen (inLen) {
pub.init_source = my_init_source;
pub.fill_input_buffer = my_fill_input_buffer;
pub.skip_input_data = my_skip_input_data;
pub.resync_to_restart = my_resync_to_restart;
pub.term_source = my_term_source;
pub.next_input_byte = 0;
pub.bytes_in_buffer = 0;
mUsed = false;
mEOI[0] = 0xff;
mEOI[1] = JPEG_EOI;
}
struct jpeg_source_mgr pub; /* public fields */
const JOCTET * mData;
size_t mLen;
bool mUsed;
unsigned char mEOI[2];
static void my_init_source (j_decompress_ptr cinfo) {
MySrcManager *man = (MySrcManager *)cinfo->src;
man->mUsed = false;
}
static boolean my_fill_input_buffer (j_decompress_ptr cinfo) {
MySrcManager *man = (MySrcManager *)cinfo->src;
if (man->mUsed) {
man->pub.next_input_byte = man->mEOI;
man->pub.bytes_in_buffer = 2;
} else {
man->pub.next_input_byte = man->mData;
man->pub.bytes_in_buffer = man->mLen;
man->mUsed = true;
}
return TRUE;
}
static void my_skip_input_data (j_decompress_ptr cinfo, long num_bytes) {
MySrcManager *man = (MySrcManager *)cinfo->src;
man->pub.next_input_byte += num_bytes;
man->pub.bytes_in_buffer -= num_bytes;
// was < 0 and was always false PJK 16JUN12
if (man->pub.bytes_in_buffer == 0) {
man->pub.next_input_byte = man->mEOI;
man->pub.bytes_in_buffer = 2;
}
}
static boolean my_resync_to_restart (j_decompress_ptr cinfo, int desired) {
MySrcManager *man = (MySrcManager *)cinfo->src;
man->mUsed = false;
return TRUE;
}
static void my_term_source (j_decompress_ptr cinfo) {}
};
struct MyDestManager {
enum { BUF_SIZE = 4096 };
struct jpeg_destination_mgr pub; /* public fields */
QuickVec<unsigned char> mOutput;
unsigned char mTmpBuf[BUF_SIZE];
MyDestManager () {
pub.init_destination = init_buffer;
pub.empty_output_buffer = copy_buffer;
pub.term_destination = term_buffer;
pub.next_output_byte = mTmpBuf;
pub.free_in_buffer = BUF_SIZE;
}
void CopyBuffer () {
mOutput.append (mTmpBuf, BUF_SIZE);
pub.next_output_byte = mTmpBuf;
pub.free_in_buffer = BUF_SIZE;
}
void TermBuffer () {
mOutput.append (mTmpBuf, BUF_SIZE - pub.free_in_buffer);
}
static void init_buffer (jpeg_compress_struct* cinfo) {}
static boolean copy_buffer (jpeg_compress_struct* cinfo) {
MyDestManager *man = (MyDestManager *)cinfo->dest;
man->CopyBuffer ();
return TRUE;
}
static void term_buffer (jpeg_compress_struct* cinfo) {
MyDestManager *man = (MyDestManager *)cinfo->dest;
man->TermBuffer ();
}
};
bool JPEG::Decode (Resource *resource, ImageBuffer* imageBuffer, bool decodeData) {
struct jpeg_decompress_struct cinfo;
//struct jpeg_error_mgr jerr;
//cinfo.err = jpeg_std_error (&jerr);
struct ErrorData jpegError;
cinfo.err = jpeg_std_error (&jpegError.base);
jpegError.base.error_exit = OnError;
jpegError.base.output_message = OnOutput;
FILE_HANDLE *file = NULL;
Bytes *data = NULL;
MySrcManager *manager = NULL;
if (resource->path) {
file = lime::fopen (resource->path, "rb");
if (!file) {
return false;
}
}
if (setjmp (jpegError.on_error)) {
if (file) {
lime::fclose (file);
}
if (manager) {
delete manager;
}
if (data) {
delete data;
}
jpeg_destroy_decompress (&cinfo);
return false;
}
jpeg_create_decompress (&cinfo);
jpeg_save_markers (&cinfo, JPEG_APP0 + 14, 0xFF);
if (file) {
if (file->isFile ()) {
jpeg_stdio_src (&cinfo, file->getFile ());
} else {
data = new Bytes ();
data->ReadFile (resource->path);
manager = new MySrcManager (data->b, data->length);
cinfo.src = &manager->pub;
}
} else {
manager = new MySrcManager (resource->data->b, resource->data->length);
cinfo.src = &manager->pub;
}
bool decoded = false;
if (jpeg_read_header (&cinfo, TRUE) == JPEG_HEADER_OK) {
switch (cinfo.jpeg_color_space) {
case JCS_CMYK:
case JCS_YCCK:
cinfo.out_color_space = JCS_CMYK;
break;
case JCS_GRAYSCALE:
case JCS_RGB:
case JCS_YCbCr:
default:
cinfo.out_color_space = JCS_RGB;
break;
// case JCS_BG_RGB:
// case JCS_BG_YCC:
// case JCS_UNKNOWN:
}
if (decodeData) {
jpeg_start_decompress (&cinfo);
int components = cinfo.output_components;
imageBuffer->Resize (cinfo.output_width, cinfo.output_height, 32);
unsigned char *bytes = imageBuffer->data->buffer->b;
unsigned char *scanline = new unsigned char [imageBuffer->width * components];
if (cinfo.out_color_space == JCS_CMYK) {
bool invert = false;
jpeg_saved_marker_ptr marker;
marker = cinfo.marker_list;
while (marker) {
if (marker->marker == JPEG_APP0 + 14 && marker->data_length >= 12 && !strncmp ((const char*)marker->data, "Adobe", 5)) {
// Are there other transforms?
invert = true;
}
marker = marker->next;
}
while (cinfo.output_scanline < cinfo.output_height) {
jpeg_read_scanlines (&cinfo, &scanline, 1);
const unsigned char *line = scanline;
const unsigned char *const end = line + imageBuffer->width * components;
unsigned char c, m, y, k;
while (line < end) {
if (invert) {
c = 0xFF - *line++;
m = 0xFF - *line++;
y = 0xFF - *line++;
k = 0xFF - *line++;
} else {
c = *line++;
m = *line++;
y = *line++;
k = *line++;
}
*bytes++ = (unsigned char)((0xFF - c) * (0xFF - k) / 0xFF);
*bytes++ = (unsigned char)((0xFF - m) * (0xFF - k) / 0xFF);
*bytes++ = (unsigned char)((0xFF - y) * (0xFF - k) / 0xFF);
*bytes++ = 0xFF;
}
}
} else {
while (cinfo.output_scanline < cinfo.output_height) {
jpeg_read_scanlines (&cinfo, &scanline, 1);
// convert 24-bit scanline to 32-bit
const unsigned char *line = scanline;
const unsigned char *const end = line + imageBuffer->width * components;
while (line < end) {
*bytes++ = *line++;
*bytes++ = *line++;
*bytes++ = *line++;
*bytes++ = 0xFF;
}
}
}
delete[] scanline;
jpeg_finish_decompress (&cinfo);
} else {
imageBuffer->width = cinfo.image_width;
imageBuffer->height = cinfo.image_height;
}
decoded = true;
}
if (file) {
lime::fclose (file);
}
if (manager) {
delete manager;
}
if (data) {
delete data;
}
jpeg_destroy_decompress (&cinfo);
return decoded;
}
bool JPEG::Encode (ImageBuffer *imageBuffer, Bytes *bytes, int quality) {
struct jpeg_compress_struct cinfo;
struct ErrorData jpegError;
cinfo.err = jpeg_std_error (&jpegError.base);
jpegError.base.error_exit = OnError;
jpegError.base.output_message = OnOutput;
MyDestManager dest;
int w = imageBuffer->width;
int h = imageBuffer->height;
QuickVec<unsigned char> row_buf (w * 3);
jpeg_create_compress (&cinfo);
if (setjmp (jpegError.on_error)) {
jpeg_destroy_compress (&cinfo);
return false;
}
cinfo.dest = (jpeg_destination_mgr *)&dest;
cinfo.image_width = w;
cinfo.image_height = h;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults (&cinfo);
jpeg_set_quality (&cinfo, quality, TRUE);
jpeg_start_compress (&cinfo, TRUE);
JSAMPROW row_pointer = &row_buf[0];
unsigned char* imageData = imageBuffer->data->buffer->b;
int stride = imageBuffer->Stride ();
while (cinfo.next_scanline < cinfo.image_height) {
const unsigned char *src = (const unsigned char *)(imageData + (stride * cinfo.next_scanline));
unsigned char *dest = &row_buf[0];
for(int x = 0; x < w; x++) {
dest[0] = src[0];
dest[1] = src[1];
dest[2] = src[2];
dest += 3;
src += 4;
}
jpeg_write_scanlines (&cinfo, &row_pointer, 1);
}
jpeg_finish_compress (&cinfo);
int size = dest.mOutput.size ();
if (size > 0) {
bytes->Resize (size);
memcpy (bytes->b, &dest.mOutput[0], size);
}
jpeg_destroy_compress (&cinfo);
return true;
}
}