blob: 960bd378c997877f2960b9ed94caa8d20485c17c [file] [log] [blame]
DRCcd2c5d42011-08-11 11:18:34 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 * Copyright (C) 2011 D. R. Commander. All Rights Reserved.
Pierre Ossman4d0bc6e2014-02-12 13:12:31 +01003 * Copyright 2014 Pierre Ossman for Cendio AB
DRCcd2c5d42011-08-11 11:18:34 +00004 *
5 * This is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This software is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this software; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
18 * USA.
19 */
20
21#include <rfb/JpegCompressor.h>
22#include <rdr/Exception.h>
23#include <rfb/Rect.h>
24#include <rfb/PixelFormat.h>
Pierre Ossmanb948a912014-01-15 13:23:43 +010025#include <rfb/ConnParams.h>
DRC99009722011-08-17 11:24:58 +000026#include <os/print.h>
DRCcd2c5d42011-08-11 11:18:34 +000027
Pierre Ossman9144ae02011-09-07 11:35:04 +000028#include <stdio.h>
29extern "C" {
30#include <jpeglib.h>
31}
32#include <setjmp.h>
33
DRCcd2c5d42011-08-11 11:18:34 +000034using namespace rfb;
35
36//
Pierre Ossman4d0bc6e2014-02-12 13:12:31 +010037// Special formats that libjpeg can have optimised code paths for
38//
39
40static const PixelFormat pfRGBX(32, 24, false, true, 255, 255, 255, 0, 8, 16);
41static const PixelFormat pfBGRX(32, 24, false, true, 255, 255, 255, 16, 8, 0);
42static const PixelFormat pfXRGB(32, 24, false, true, 255, 255, 255, 8, 16, 24);
43static const PixelFormat pfXBGR(32, 24, false, true, 255, 255, 255, 24, 16, 8);
44
45//
DRCcd2c5d42011-08-11 11:18:34 +000046// Error manager implmentation for the JPEG library
47//
48
Pierre Ossman9144ae02011-09-07 11:35:04 +000049struct JPEG_ERROR_MGR {
50 struct jpeg_error_mgr pub;
51 jmp_buf jmpBuffer;
52 char lastError[JMSG_LENGTH_MAX];
53};
54
DRCcd2c5d42011-08-11 11:18:34 +000055static void
56JpegErrorExit(j_common_ptr cinfo)
57{
58 JPEG_ERROR_MGR *err = (JPEG_ERROR_MGR *)cinfo->err;
59
60 (*cinfo->err->output_message)(cinfo);
61 longjmp(err->jmpBuffer, 1);
62}
63
64static void
65JpegOutputMessage(j_common_ptr cinfo)
66{
67 JPEG_ERROR_MGR *err = (JPEG_ERROR_MGR *)cinfo->err;
68
69 (*cinfo->err->format_message)(cinfo, err->lastError);
70}
71
72//
73// Destination manager implementation for the JPEG library.
74//
75
Pierre Ossman9144ae02011-09-07 11:35:04 +000076struct JPEG_DEST_MGR {
77 struct jpeg_destination_mgr pub;
78 JpegCompressor *instance;
79};
80
DRCcd2c5d42011-08-11 11:18:34 +000081static void
82JpegInitDestination(j_compress_ptr cinfo)
83{
84 JPEG_DEST_MGR *dest = (JPEG_DEST_MGR *)cinfo->dest;
85 JpegCompressor *jc = dest->instance;
86
87 jc->clear();
88 dest->pub.next_output_byte = jc->getptr();
89 dest->pub.free_in_buffer = jc->getend() - jc->getptr();
90}
91
92static boolean
93JpegEmptyOutputBuffer(j_compress_ptr cinfo)
94{
95 JPEG_DEST_MGR *dest = (JPEG_DEST_MGR *)cinfo->dest;
96 JpegCompressor *jc = dest->instance;
97
98 jc->setptr(dest->pub.next_output_byte);
99 jc->overrun(jc->getend() - jc->getstart(), 1);
100 dest->pub.next_output_byte = jc->getptr();
101 dest->pub.free_in_buffer = jc->getend() - jc->getptr();
102
103 return TRUE;
104}
105
106static void
107JpegTermDestination(j_compress_ptr cinfo)
108{
109 JPEG_DEST_MGR *dest = (JPEG_DEST_MGR *)cinfo->dest;
110 JpegCompressor *jc = dest->instance;
111
112 jc->setptr(dest->pub.next_output_byte);
113}
114
115JpegCompressor::JpegCompressor(int bufferLen) : MemOutStream(bufferLen)
116{
Pierre Ossman9144ae02011-09-07 11:35:04 +0000117 cinfo = new jpeg_compress_struct;
DRCcd2c5d42011-08-11 11:18:34 +0000118
Pierre Ossman9144ae02011-09-07 11:35:04 +0000119 err = new struct JPEG_ERROR_MGR;
120 cinfo->err = jpeg_std_error(&err->pub);
121 snprintf(err->lastError, JMSG_LENGTH_MAX, "No error");
122 err->pub.error_exit = JpegErrorExit;
123 err->pub.output_message = JpegOutputMessage;
124
125 if(setjmp(err->jmpBuffer)) {
DRCcd2c5d42011-08-11 11:18:34 +0000126 // this will execute if libjpeg has an error
Pierre Ossman9144ae02011-09-07 11:35:04 +0000127 throw rdr::Exception(err->lastError);
DRCcd2c5d42011-08-11 11:18:34 +0000128 }
129
Pierre Ossman9144ae02011-09-07 11:35:04 +0000130 jpeg_create_compress(cinfo);
DRCcd2c5d42011-08-11 11:18:34 +0000131
Pierre Ossman9144ae02011-09-07 11:35:04 +0000132 dest = new struct JPEG_DEST_MGR;
133 dest->pub.init_destination = JpegInitDestination;
134 dest->pub.empty_output_buffer = JpegEmptyOutputBuffer;
135 dest->pub.term_destination = JpegTermDestination;
136 dest->instance = this;
137 cinfo->dest = (struct jpeg_destination_mgr *)dest;
DRCcd2c5d42011-08-11 11:18:34 +0000138}
139
140JpegCompressor::~JpegCompressor(void)
141{
Pierre Ossman9144ae02011-09-07 11:35:04 +0000142 if(setjmp(err->jmpBuffer)) {
DRCcd2c5d42011-08-11 11:18:34 +0000143 // this will execute if libjpeg has an error
144 return;
145 }
146
Pierre Ossman9144ae02011-09-07 11:35:04 +0000147 jpeg_destroy_compress(cinfo);
148
149 delete err;
150 delete dest;
151
152 delete cinfo;
DRCcd2c5d42011-08-11 11:18:34 +0000153}
154
Pierre Ossmana10d8fe2014-01-22 11:28:05 +0100155void JpegCompressor::compress(const rdr::U8 *buf, int stride, const Rect& r,
Pierre Ossmanb948a912014-01-15 13:23:43 +0100156 const PixelFormat& pf, int quality, int subsamp)
DRCcd2c5d42011-08-11 11:18:34 +0000157{
158 int w = r.width();
159 int h = r.height();
160 int pixelsize;
161 rdr::U8 *srcBuf = NULL;
162 bool srcBufIsTemp = false;
163 JSAMPROW *rowPointer = NULL;
164
Pierre Ossman9144ae02011-09-07 11:35:04 +0000165 if(setjmp(err->jmpBuffer)) {
DRCcd2c5d42011-08-11 11:18:34 +0000166 // this will execute if libjpeg has an error
Pierre Ossman9144ae02011-09-07 11:35:04 +0000167 jpeg_abort_compress(cinfo);
DRCcd2c5d42011-08-11 11:18:34 +0000168 if (srcBufIsTemp && srcBuf) delete[] srcBuf;
169 if (rowPointer) delete[] rowPointer;
Pierre Ossman9144ae02011-09-07 11:35:04 +0000170 throw rdr::Exception(err->lastError);
DRCcd2c5d42011-08-11 11:18:34 +0000171 }
172
Pierre Ossman9144ae02011-09-07 11:35:04 +0000173 cinfo->image_width = w;
174 cinfo->image_height = h;
175 cinfo->in_color_space = JCS_RGB;
DRCcd2c5d42011-08-11 11:18:34 +0000176 pixelsize = 3;
177
178#ifdef JCS_EXTENSIONS
Pierre Ossman4d0bc6e2014-02-12 13:12:31 +0100179 // Try to have libjpeg output directly to our native format
180 // libjpeg can only handle some "standard" formats
181 if (pfRGBX.equal(pf))
182 cinfo->in_color_space = JCS_EXT_RGBX;
183 else if (pfBGRX.equal(pf))
184 cinfo->in_color_space = JCS_EXT_BGRX;
185 else if (pfXRGB.equal(pf))
186 cinfo->in_color_space = JCS_EXT_XRGB;
187 else if (pfXBGR.equal(pf))
188 cinfo->in_color_space = JCS_EXT_XBGR;
DRCcd2c5d42011-08-11 11:18:34 +0000189
Pierre Ossman4d0bc6e2014-02-12 13:12:31 +0100190 if (cinfo->in_color_space != JCS_RGB) {
191 srcBuf = (rdr::U8 *)buf;
192 pixelsize = 4;
DRCcd2c5d42011-08-11 11:18:34 +0000193 }
194#endif
195
Pierre Ossmana10d8fe2014-01-22 11:28:05 +0100196 if (stride == 0)
197 stride = w;
DRCffe09d62011-08-17 02:27:59 +0000198
Pierre Ossman9144ae02011-09-07 11:35:04 +0000199 if (cinfo->in_color_space == JCS_RGB) {
DRCcd2c5d42011-08-11 11:18:34 +0000200 srcBuf = new rdr::U8[w * h * pixelsize];
201 srcBufIsTemp = true;
Pierre Ossmana10d8fe2014-01-22 11:28:05 +0100202 pf.rgbFromBuffer(srcBuf, (const rdr::U8 *)buf, w, stride, h);
203 stride = w;
DRCcd2c5d42011-08-11 11:18:34 +0000204 }
205
Pierre Ossman9144ae02011-09-07 11:35:04 +0000206 cinfo->input_components = pixelsize;
DRCcd2c5d42011-08-11 11:18:34 +0000207
Pierre Ossman9144ae02011-09-07 11:35:04 +0000208 jpeg_set_defaults(cinfo);
Pierre Ossmanca519532014-03-21 12:38:59 +0100209
210 if (quality >= 1 && quality <= 100) {
211 jpeg_set_quality(cinfo, quality, TRUE);
212 if (quality >= 96)
213 cinfo->dct_method = JDCT_ISLOW;
214 else
215 cinfo->dct_method = JDCT_FASTEST;
216 }
DRCcd2c5d42011-08-11 11:18:34 +0000217
218 switch (subsamp) {
Pierre Ossmanb948a912014-01-15 13:23:43 +0100219 case subsample16X:
220 case subsample8X:
221 // FIXME (fall through)
222 case subsample4X:
Pierre Ossman9144ae02011-09-07 11:35:04 +0000223 cinfo->comp_info[0].h_samp_factor = 2;
224 cinfo->comp_info[0].v_samp_factor = 2;
DRCcd2c5d42011-08-11 11:18:34 +0000225 break;
Pierre Ossmanb948a912014-01-15 13:23:43 +0100226 case subsample2X:
Pierre Ossman9144ae02011-09-07 11:35:04 +0000227 cinfo->comp_info[0].h_samp_factor = 2;
228 cinfo->comp_info[0].v_samp_factor = 1;
DRCcd2c5d42011-08-11 11:18:34 +0000229 break;
Pierre Ossmanb948a912014-01-15 13:23:43 +0100230 case subsampleGray:
Pierre Ossman9144ae02011-09-07 11:35:04 +0000231 jpeg_set_colorspace(cinfo, JCS_GRAYSCALE);
DRCcd2c5d42011-08-11 11:18:34 +0000232 default:
Pierre Ossman9144ae02011-09-07 11:35:04 +0000233 cinfo->comp_info[0].h_samp_factor = 1;
234 cinfo->comp_info[0].v_samp_factor = 1;
DRCcd2c5d42011-08-11 11:18:34 +0000235 }
236
237 rowPointer = new JSAMPROW[h];
238 for (int dy = 0; dy < h; dy++)
Pierre Ossmana10d8fe2014-01-22 11:28:05 +0100239 rowPointer[dy] = (JSAMPROW)(&srcBuf[dy * stride * pixelsize]);
DRCcd2c5d42011-08-11 11:18:34 +0000240
Pierre Ossman9144ae02011-09-07 11:35:04 +0000241 jpeg_start_compress(cinfo, TRUE);
242 while (cinfo->next_scanline < cinfo->image_height)
243 jpeg_write_scanlines(cinfo, &rowPointer[cinfo->next_scanline],
244 cinfo->image_height - cinfo->next_scanline);
DRCcd2c5d42011-08-11 11:18:34 +0000245
Pierre Ossman9144ae02011-09-07 11:35:04 +0000246 jpeg_finish_compress(cinfo);
DRCcd2c5d42011-08-11 11:18:34 +0000247
248 if (srcBufIsTemp) delete[] srcBuf;
249 delete[] rowPointer;
250}
251
252void JpegCompressor::writeBytes(const void* data, int length)
253{
254 throw rdr::Exception("writeBytes() is not valid with a JpegCompressor instance. Use compress() instead.");
255}