blob: 23b8f8cf14ab2cfcf95820de299f94956d867cf6 [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 Ossman0d3ce872018-06-18 15:59:00 +020025#include <rfb/ClientParams.h>
DRCcd2c5d42011-08-11 11:18:34 +000026
Pierre Ossman9144ae02011-09-07 11:35:04 +000027#include <stdio.h>
28extern "C" {
29#include <jpeglib.h>
30}
31#include <setjmp.h>
32
DRCcd2c5d42011-08-11 11:18:34 +000033using namespace rfb;
34
35//
Pierre Ossman4d0bc6e2014-02-12 13:12:31 +010036// Special formats that libjpeg can have optimised code paths for
37//
38
39static const PixelFormat pfRGBX(32, 24, false, true, 255, 255, 255, 0, 8, 16);
40static const PixelFormat pfBGRX(32, 24, false, true, 255, 255, 255, 16, 8, 0);
41static const PixelFormat pfXRGB(32, 24, false, true, 255, 255, 255, 8, 16, 24);
42static const PixelFormat pfXBGR(32, 24, false, true, 255, 255, 255, 24, 16, 8);
43
44//
klemens0536d092017-01-28 20:56:56 +010045// Error manager implementation for the JPEG library
DRCcd2c5d42011-08-11 11:18:34 +000046//
47
Pierre Ossman9144ae02011-09-07 11:35:04 +000048struct JPEG_ERROR_MGR {
49 struct jpeg_error_mgr pub;
50 jmp_buf jmpBuffer;
51 char lastError[JMSG_LENGTH_MAX];
52};
53
DRCcd2c5d42011-08-11 11:18:34 +000054static void
55JpegErrorExit(j_common_ptr cinfo)
56{
57 JPEG_ERROR_MGR *err = (JPEG_ERROR_MGR *)cinfo->err;
58
59 (*cinfo->err->output_message)(cinfo);
60 longjmp(err->jmpBuffer, 1);
61}
62
63static void
64JpegOutputMessage(j_common_ptr cinfo)
65{
66 JPEG_ERROR_MGR *err = (JPEG_ERROR_MGR *)cinfo->err;
67
68 (*cinfo->err->format_message)(cinfo, err->lastError);
69}
70
71//
72// Destination manager implementation for the JPEG library.
73//
74
Pierre Ossman9144ae02011-09-07 11:35:04 +000075struct JPEG_DEST_MGR {
76 struct jpeg_destination_mgr pub;
77 JpegCompressor *instance;
78};
79
DRCcd2c5d42011-08-11 11:18:34 +000080static void
81JpegInitDestination(j_compress_ptr cinfo)
82{
83 JPEG_DEST_MGR *dest = (JPEG_DEST_MGR *)cinfo->dest;
84 JpegCompressor *jc = dest->instance;
85
86 jc->clear();
87 dest->pub.next_output_byte = jc->getptr();
88 dest->pub.free_in_buffer = jc->getend() - jc->getptr();
89}
90
91static boolean
92JpegEmptyOutputBuffer(j_compress_ptr cinfo)
93{
94 JPEG_DEST_MGR *dest = (JPEG_DEST_MGR *)cinfo->dest;
95 JpegCompressor *jc = dest->instance;
96
Pierre Ossman1e613252015-10-16 13:03:14 +020097 jc->setptr(jc->getend());
DRCcd2c5d42011-08-11 11:18:34 +000098 jc->overrun(jc->getend() - jc->getstart(), 1);
99 dest->pub.next_output_byte = jc->getptr();
100 dest->pub.free_in_buffer = jc->getend() - jc->getptr();
101
102 return TRUE;
103}
104
105static void
106JpegTermDestination(j_compress_ptr cinfo)
107{
108 JPEG_DEST_MGR *dest = (JPEG_DEST_MGR *)cinfo->dest;
109 JpegCompressor *jc = dest->instance;
110
111 jc->setptr(dest->pub.next_output_byte);
112}
113
114JpegCompressor::JpegCompressor(int bufferLen) : MemOutStream(bufferLen)
115{
Pierre Ossman9144ae02011-09-07 11:35:04 +0000116 cinfo = new jpeg_compress_struct;
DRCcd2c5d42011-08-11 11:18:34 +0000117
Pierre Ossman9144ae02011-09-07 11:35:04 +0000118 err = new struct JPEG_ERROR_MGR;
119 cinfo->err = jpeg_std_error(&err->pub);
120 snprintf(err->lastError, JMSG_LENGTH_MAX, "No error");
121 err->pub.error_exit = JpegErrorExit;
122 err->pub.output_message = JpegOutputMessage;
123
124 if(setjmp(err->jmpBuffer)) {
DRCcd2c5d42011-08-11 11:18:34 +0000125 // this will execute if libjpeg has an error
Pierre Ossmana7bbe9c2015-03-03 16:17:51 +0100126 throw rdr::Exception("%s", err->lastError);
DRCcd2c5d42011-08-11 11:18:34 +0000127 }
128
Pierre Ossman9144ae02011-09-07 11:35:04 +0000129 jpeg_create_compress(cinfo);
DRCcd2c5d42011-08-11 11:18:34 +0000130
Pierre Ossman9144ae02011-09-07 11:35:04 +0000131 dest = new struct JPEG_DEST_MGR;
132 dest->pub.init_destination = JpegInitDestination;
133 dest->pub.empty_output_buffer = JpegEmptyOutputBuffer;
134 dest->pub.term_destination = JpegTermDestination;
135 dest->instance = this;
136 cinfo->dest = (struct jpeg_destination_mgr *)dest;
DRCcd2c5d42011-08-11 11:18:34 +0000137}
138
139JpegCompressor::~JpegCompressor(void)
140{
Pierre Ossman9144ae02011-09-07 11:35:04 +0000141 if(setjmp(err->jmpBuffer)) {
DRCcd2c5d42011-08-11 11:18:34 +0000142 // this will execute if libjpeg has an error
143 return;
144 }
145
Pierre Ossman9144ae02011-09-07 11:35:04 +0000146 jpeg_destroy_compress(cinfo);
147
148 delete err;
149 delete dest;
150
151 delete cinfo;
DRCcd2c5d42011-08-11 11:18:34 +0000152}
153
Pierre Ossmana10d8fe2014-01-22 11:28:05 +0100154void JpegCompressor::compress(const rdr::U8 *buf, int stride, const Rect& r,
Pierre Ossmanb948a912014-01-15 13:23:43 +0100155 const PixelFormat& pf, int quality, int subsamp)
DRCcd2c5d42011-08-11 11:18:34 +0000156{
157 int w = r.width();
158 int h = r.height();
159 int pixelsize;
160 rdr::U8 *srcBuf = NULL;
161 bool srcBufIsTemp = false;
162 JSAMPROW *rowPointer = NULL;
163
Pierre Ossman9144ae02011-09-07 11:35:04 +0000164 if(setjmp(err->jmpBuffer)) {
DRCcd2c5d42011-08-11 11:18:34 +0000165 // this will execute if libjpeg has an error
Pierre Ossman9144ae02011-09-07 11:35:04 +0000166 jpeg_abort_compress(cinfo);
DRCcd2c5d42011-08-11 11:18:34 +0000167 if (srcBufIsTemp && srcBuf) delete[] srcBuf;
168 if (rowPointer) delete[] rowPointer;
Pierre Ossmana7bbe9c2015-03-03 16:17:51 +0100169 throw rdr::Exception("%s", err->lastError);
DRCcd2c5d42011-08-11 11:18:34 +0000170 }
171
Pierre Ossman9144ae02011-09-07 11:35:04 +0000172 cinfo->image_width = w;
173 cinfo->image_height = h;
174 cinfo->in_color_space = JCS_RGB;
DRCcd2c5d42011-08-11 11:18:34 +0000175 pixelsize = 3;
176
177#ifdef JCS_EXTENSIONS
Pierre Ossman4d0bc6e2014-02-12 13:12:31 +0100178 // Try to have libjpeg output directly to our native format
179 // libjpeg can only handle some "standard" formats
180 if (pfRGBX.equal(pf))
181 cinfo->in_color_space = JCS_EXT_RGBX;
182 else if (pfBGRX.equal(pf))
183 cinfo->in_color_space = JCS_EXT_BGRX;
184 else if (pfXRGB.equal(pf))
185 cinfo->in_color_space = JCS_EXT_XRGB;
186 else if (pfXBGR.equal(pf))
187 cinfo->in_color_space = JCS_EXT_XBGR;
DRCcd2c5d42011-08-11 11:18:34 +0000188
Pierre Ossman4d0bc6e2014-02-12 13:12:31 +0100189 if (cinfo->in_color_space != JCS_RGB) {
190 srcBuf = (rdr::U8 *)buf;
191 pixelsize = 4;
DRCcd2c5d42011-08-11 11:18:34 +0000192 }
193#endif
194
Pierre Ossmana10d8fe2014-01-22 11:28:05 +0100195 if (stride == 0)
196 stride = w;
DRCffe09d62011-08-17 02:27:59 +0000197
Pierre Ossman9144ae02011-09-07 11:35:04 +0000198 if (cinfo->in_color_space == JCS_RGB) {
DRCcd2c5d42011-08-11 11:18:34 +0000199 srcBuf = new rdr::U8[w * h * pixelsize];
200 srcBufIsTemp = true;
Pierre Ossmana10d8fe2014-01-22 11:28:05 +0100201 pf.rgbFromBuffer(srcBuf, (const rdr::U8 *)buf, w, stride, h);
202 stride = w;
DRCcd2c5d42011-08-11 11:18:34 +0000203 }
204
Pierre Ossman9144ae02011-09-07 11:35:04 +0000205 cinfo->input_components = pixelsize;
DRCcd2c5d42011-08-11 11:18:34 +0000206
Pierre Ossman9144ae02011-09-07 11:35:04 +0000207 jpeg_set_defaults(cinfo);
Pierre Ossmanca519532014-03-21 12:38:59 +0100208
209 if (quality >= 1 && quality <= 100) {
210 jpeg_set_quality(cinfo, quality, TRUE);
211 if (quality >= 96)
212 cinfo->dct_method = JDCT_ISLOW;
213 else
214 cinfo->dct_method = JDCT_FASTEST;
215 }
DRCcd2c5d42011-08-11 11:18:34 +0000216
217 switch (subsamp) {
Pierre Ossmanb948a912014-01-15 13:23:43 +0100218 case subsample16X:
219 case subsample8X:
220 // FIXME (fall through)
221 case subsample4X:
Pierre Ossman9144ae02011-09-07 11:35:04 +0000222 cinfo->comp_info[0].h_samp_factor = 2;
223 cinfo->comp_info[0].v_samp_factor = 2;
DRCcd2c5d42011-08-11 11:18:34 +0000224 break;
Pierre Ossmanb948a912014-01-15 13:23:43 +0100225 case subsample2X:
Pierre Ossman9144ae02011-09-07 11:35:04 +0000226 cinfo->comp_info[0].h_samp_factor = 2;
227 cinfo->comp_info[0].v_samp_factor = 1;
DRCcd2c5d42011-08-11 11:18:34 +0000228 break;
Pierre Ossmanb948a912014-01-15 13:23:43 +0100229 case subsampleGray:
Pierre Ossman9144ae02011-09-07 11:35:04 +0000230 jpeg_set_colorspace(cinfo, JCS_GRAYSCALE);
DRCcd2c5d42011-08-11 11:18:34 +0000231 default:
Pierre Ossman9144ae02011-09-07 11:35:04 +0000232 cinfo->comp_info[0].h_samp_factor = 1;
233 cinfo->comp_info[0].v_samp_factor = 1;
DRCcd2c5d42011-08-11 11:18:34 +0000234 }
235
236 rowPointer = new JSAMPROW[h];
237 for (int dy = 0; dy < h; dy++)
Pierre Ossmana10d8fe2014-01-22 11:28:05 +0100238 rowPointer[dy] = (JSAMPROW)(&srcBuf[dy * stride * pixelsize]);
DRCcd2c5d42011-08-11 11:18:34 +0000239
Pierre Ossman9144ae02011-09-07 11:35:04 +0000240 jpeg_start_compress(cinfo, TRUE);
241 while (cinfo->next_scanline < cinfo->image_height)
242 jpeg_write_scanlines(cinfo, &rowPointer[cinfo->next_scanline],
243 cinfo->image_height - cinfo->next_scanline);
DRCcd2c5d42011-08-11 11:18:34 +0000244
Pierre Ossman9144ae02011-09-07 11:35:04 +0000245 jpeg_finish_compress(cinfo);
DRCcd2c5d42011-08-11 11:18:34 +0000246
247 if (srcBufIsTemp) delete[] srcBuf;
248 delete[] rowPointer;
249}
250
251void JpegCompressor::writeBytes(const void* data, int length)
252{
253 throw rdr::Exception("writeBytes() is not valid with a JpegCompressor instance. Use compress() instead.");
254}