/*
Copyright (C) 2009 Red Hat, Inc.
This library 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 2.1 of the License, or (at your option) any later version.
This library 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 this library; if not, see .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include "red-common.h"
#include "jpeg-encoder.h"
#include
typedef struct JpegEncoder {
JpegEncoderUsrContext *usr;
struct jpeg_destination_mgr dest_mgr;
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
struct {
JpegEncoderImageType type;
int width;
int height;
int stride;
unsigned int out_size;
void (*convert_line_to_RGB24) (void *line, int width, uint8_t **out_line);
} cur_image;
} JpegEncoder;
/* jpeg destination manager callbacks */
static void dest_mgr_init_destination(j_compress_ptr cinfo)
{
JpegEncoder *enc = (JpegEncoder *)cinfo->client_data;
if (enc->dest_mgr.free_in_buffer == 0) {
enc->dest_mgr.free_in_buffer = enc->usr->more_space(enc->usr,
&enc->dest_mgr.next_output_byte);
if (enc->dest_mgr.free_in_buffer == 0) {
spice_error("not enough space");
}
}
enc->cur_image.out_size = enc->dest_mgr.free_in_buffer;
}
static boolean dest_mgr_empty_output_buffer(j_compress_ptr cinfo)
{
JpegEncoder *enc = (JpegEncoder *)cinfo->client_data;
enc->dest_mgr.free_in_buffer = enc->usr->more_space(enc->usr,
&enc->dest_mgr.next_output_byte);
if (enc->dest_mgr.free_in_buffer == 0) {
spice_error("not enough space");
}
enc->cur_image.out_size += enc->dest_mgr.free_in_buffer;
return TRUE;
}
static void dest_mgr_term_destination(j_compress_ptr cinfo)
{
JpegEncoder *enc = (JpegEncoder *)cinfo->client_data;
enc->cur_image.out_size -= enc->dest_mgr.free_in_buffer;
}
JpegEncoderContext* jpeg_encoder_create(JpegEncoderUsrContext *usr)
{
JpegEncoder *enc;
if (!usr->more_space || !usr->more_lines) {
return NULL;
}
enc = spice_new0(JpegEncoder, 1);
enc->usr = usr;
enc->dest_mgr.init_destination = dest_mgr_init_destination;
enc->dest_mgr.empty_output_buffer = dest_mgr_empty_output_buffer;
enc->dest_mgr.term_destination = dest_mgr_term_destination;
enc->cinfo.err = jpeg_std_error(&enc->jerr);
jpeg_create_compress(&enc->cinfo);
enc->cinfo.client_data = enc;
enc->cinfo.dest = &enc->dest_mgr;
return (JpegEncoderContext*)enc;
}
void jpeg_encoder_destroy(JpegEncoderContext* encoder)
{
jpeg_destroy_compress(&((JpegEncoder*)encoder)->cinfo);
free(encoder);
}
static void convert_RGB16_to_RGB24(void *line, int width, uint8_t **out_line)
{
uint16_t *src_line = line;
uint8_t *out_pix;
int x;
spice_assert(out_line && *out_line);
out_pix = *out_line;
for (x = 0; x < width; x++) {
uint16_t pixel = *src_line++;
*out_pix++ = ((pixel >> 7) & 0xf8) | ((pixel >> 12) & 0x7);
*out_pix++ = ((pixel >> 2) & 0xf8) | ((pixel >> 7) & 0x7);
*out_pix++ = ((pixel << 3) & 0xf8) | ((pixel >> 2) & 0x7);
}
}
static void convert_BGR24_to_RGB24(void *in_line, int width, uint8_t **out_line)
{
int x;
uint8_t *out_pix;
uint8_t *line = in_line;
spice_assert(out_line && *out_line);
out_pix = *out_line;
for (x = 0; x < width; x++) {
*out_pix++ = line[2];
*out_pix++ = line[1];
*out_pix++ = line[0];
line += 3;
}
}
static void convert_BGRX32_to_RGB24(void *line, int width, uint8_t **out_line)
{
uint32_t *src_line = line;
uint8_t *out_pix;
int x;
spice_assert(out_line && *out_line);
out_pix = *out_line;
for (x = 0; x < width; x++) {
uint32_t pixel = *src_line++;
*out_pix++ = (pixel >> 16) & 0xff;
*out_pix++ = (pixel >> 8) & 0xff;
*out_pix++ = pixel & 0xff;
}
}
static void convert_RGB24_to_RGB24(void *line, int width, uint8_t **out_line)
{
*out_line = line;
}
#define FILL_LINES() { \
if (lines == lines_end) { \
int n = jpeg->usr->more_lines(jpeg->usr, &lines); \
if (n <= 0) { \
spice_error("more lines failed"); \
} \
lines_end = lines + n * stride; \
} \
}
static void do_jpeg_encode(JpegEncoder *jpeg, uint8_t *lines, unsigned int num_lines)
{
uint8_t *lines_end;
uint8_t *RGB24_line;
int stride, width;
JSAMPROW row_pointer[1];
width = jpeg->cur_image.width;
stride = jpeg->cur_image.stride;
if (jpeg->cur_image.type != JPEG_IMAGE_TYPE_RGB24) {
RGB24_line = (uint8_t *)spice_malloc(width*3);
}
lines_end = lines + (stride * num_lines);
for (;jpeg->cinfo.next_scanline < jpeg->cinfo.image_height; lines += stride) {
FILL_LINES();
jpeg->cur_image.convert_line_to_RGB24(lines, width, &RGB24_line);
row_pointer[0] = RGB24_line;
jpeg_write_scanlines(&jpeg->cinfo, row_pointer, 1);
}
if (jpeg->cur_image.type != JPEG_IMAGE_TYPE_RGB24) {
free(RGB24_line);
}
}
int jpeg_encode(JpegEncoderContext *jpeg, int quality, JpegEncoderImageType type,
int width, int height, uint8_t *lines, unsigned int num_lines, int stride,
uint8_t *io_ptr, unsigned int num_io_bytes)
{
JpegEncoder *enc = (JpegEncoder *)jpeg;
enc->cur_image.type = type;
enc->cur_image.width = width;
enc->cur_image.height = height;
enc->cur_image.stride = stride;
enc->cur_image.out_size = 0;
switch (type) {
case JPEG_IMAGE_TYPE_RGB16:
enc->cur_image.convert_line_to_RGB24 = convert_RGB16_to_RGB24;
break;
case JPEG_IMAGE_TYPE_RGB24:
enc->cur_image.convert_line_to_RGB24 = convert_RGB24_to_RGB24;
break;
case JPEG_IMAGE_TYPE_BGR24:
enc->cur_image.convert_line_to_RGB24 = convert_BGR24_to_RGB24;
break;
case JPEG_IMAGE_TYPE_BGRX32:
enc->cur_image.convert_line_to_RGB24 = convert_BGRX32_to_RGB24;
break;
default:
spice_error("bad image type");
}
enc->cinfo.image_width = width;
enc->cinfo.image_height = height;
enc->cinfo.input_components = 3;
enc->cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&enc->cinfo);
jpeg_set_quality(&enc->cinfo, quality, TRUE);
enc->dest_mgr.next_output_byte = io_ptr;
enc->dest_mgr.free_in_buffer = num_io_bytes;
jpeg_start_compress(&enc->cinfo, TRUE);
do_jpeg_encode(enc, lines, num_lines);
jpeg_finish_compress(&enc->cinfo);
return enc->cur_image.out_size;
}