1/* GDK - The GIMP Drawing Kit
2 * Copyright (C) 2021 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include <stdlib.h>
19
20#include "config.h"
21
22#include "gdkjpegprivate.h"
23
24#include "gdkintl.h"
25#include "gdktexture.h"
26#include "gdkmemorytextureprivate.h"
27
28#include "gdkprofilerprivate.h"
29
30#include <jpeglib.h>
31#include <jerror.h>
32#include <setjmp.h>
33
34/* {{{ Error handling */
35
36/* No sigsetjmp on Windows */
37#ifndef HAVE_SIGSETJMP
38#define sigjmp_buf jmp_buf
39#define sigsetjmp(jb, x) setjmp(jb)
40#define siglongjmp longjmp
41#endif
42
43struct error_handler_data {
44 struct jpeg_error_mgr pub;
45 sigjmp_buf setjmp_buffer;
46 GError **error;
47};
48
49G_GNUC_NORETURN static void
50fatal_error_handler (j_common_ptr cinfo)
51{
52 struct error_handler_data *errmgr;
53 char buffer[JMSG_LENGTH_MAX];
54
55 errmgr = (struct error_handler_data *) cinfo->err;
56
57 cinfo->err->format_message (cinfo, buffer);
58
59 if (errmgr->error && *errmgr->error == NULL)
60 g_set_error (err: errmgr->error,
61 GDK_TEXTURE_ERROR,
62 code: GDK_TEXTURE_ERROR_CORRUPT_IMAGE,
63 _("Error interpreting JPEG image file (%s)"), buffer);
64
65 siglongjmp (env: errmgr->setjmp_buffer, val: 1);
66
67 g_assert_not_reached ();
68}
69
70static void
71output_message_handler (j_common_ptr cinfo)
72{
73 /* do nothing */
74}
75
76/* }}} */
77/* {{{ Format conversion */
78
79static void
80convert_grayscale_to_rgb (guchar *data,
81 int width,
82 int height,
83 int stride)
84{
85 gsize x, y;
86 guchar *dest, *src;
87
88 for (y = 0; y < height; y++)
89 {
90 src = data + width;
91 dest = data + 3 * width;
92 for (x = 0; x < width; x++)
93 {
94 dest -= 3;
95 src -= 1;
96 dest[0] = *src;
97 dest[1] = *src;
98 dest[2] = *src;
99 }
100 data += stride;
101 }
102}
103
104static void
105convert_cmyk_to_rgba (guchar *data,
106 int width,
107 int height,
108 int stride)
109{
110 gsize x, r;
111 guchar *dest;
112
113 for (r = 0; r < height; r++)
114 {
115 dest = data;
116 for (x = 0; x < width; x++)
117 {
118 int c, m, y, k;
119
120 c = dest[0];
121 m = dest[1];
122 y = dest[2];
123 k = dest[3];
124 dest[0] = k * c / 255;
125 dest[1] = k * m / 255;
126 dest[2] = k * y / 255;
127 dest[3] = 255;
128 dest += 4;
129 }
130 data += stride;
131 }
132}
133
134 /* }}} */
135/* {{{ Public API */
136
137GdkTexture *
138gdk_load_jpeg (GBytes *input_bytes,
139 GError **error)
140{
141 struct jpeg_decompress_struct info;
142 struct error_handler_data jerr;
143 guint width, height, stride;
144 unsigned char *data;
145 unsigned char *row[1];
146 GBytes *bytes;
147 GdkTexture *texture;
148 GdkMemoryFormat format;
149 G_GNUC_UNUSED guint64 before = GDK_PROFILER_CURRENT_TIME;
150
151 info.err = jpeg_std_error (err: &jerr.pub);
152 jerr.pub.error_exit = fatal_error_handler;
153 jerr.pub.output_message = output_message_handler;
154 jerr.error = error;
155
156 if (sigsetjmp (jerr.setjmp_buffer, 1))
157 {
158 jpeg_destroy_decompress (cinfo: &info);
159 return NULL;
160 }
161
162 jpeg_create_decompress (&info);
163
164 jpeg_mem_src (cinfo: &info,
165 inbuffer: g_bytes_get_data (bytes: input_bytes, NULL),
166 insize: g_bytes_get_size (bytes: input_bytes));
167
168 jpeg_read_header (cinfo: &info, TRUE);
169 jpeg_start_decompress (cinfo: &info);
170
171 width = info.output_width;
172 height = info.output_height;
173
174 switch ((int)info.out_color_space)
175 {
176 case JCS_GRAYSCALE:
177 case JCS_RGB:
178 stride = 3 * width;
179 data = g_try_malloc_n (n_blocks: stride, n_block_bytes: height);
180 format = GDK_MEMORY_R8G8B8;
181 break;
182 case JCS_CMYK:
183 stride = 4 * width;
184 data = g_try_malloc_n (n_blocks: stride, n_block_bytes: height);
185 format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED;
186 break;
187 default:
188 g_set_error (err: error,
189 GDK_TEXTURE_ERROR, code: GDK_TEXTURE_ERROR_UNSUPPORTED_CONTENT,
190 _("Unsupported JPEG colorspace (%d)"), info.out_color_space);
191 jpeg_destroy_decompress (cinfo: &info);
192 return NULL;
193 }
194
195 if (!data)
196 {
197 g_set_error (err: error,
198 GDK_TEXTURE_ERROR, code: GDK_TEXTURE_ERROR_TOO_LARGE,
199 _("Not enough memory for image size %ux%u"), width, height);
200 jpeg_destroy_decompress (cinfo: &info);
201 return NULL;
202 }
203
204 while (info.output_scanline < info.output_height)
205 {
206 row[0] = (unsigned char *)(&data[stride * info.output_scanline]);
207 jpeg_read_scanlines (cinfo: &info, scanlines: row, max_lines: 1);
208 }
209
210 switch ((int)info.out_color_space)
211 {
212 case JCS_GRAYSCALE:
213 convert_grayscale_to_rgb (data, width, height, stride);
214 format = GDK_MEMORY_R8G8B8;
215 break;
216 case JCS_RGB:
217 break;
218 case JCS_CMYK:
219 convert_cmyk_to_rgba (data, width, height, stride);
220 break;
221 default:
222 g_assert_not_reached ();
223 }
224
225 jpeg_finish_decompress (cinfo: &info);
226 jpeg_destroy_decompress (cinfo: &info);
227
228 bytes = g_bytes_new_take (data, size: stride * height);
229
230 texture = gdk_memory_texture_new (width, height,
231 format,
232 bytes, stride);
233
234 g_bytes_unref (bytes);
235
236 gdk_profiler_end_mark (before, "jpeg load", NULL);
237
238 return texture;
239}
240
241GBytes *
242gdk_save_jpeg (GdkTexture *texture)
243{
244 struct jpeg_compress_struct info;
245 struct error_handler_data jerr;
246 struct jpeg_error_mgr err;
247 guchar *data;
248 gulong size = 0;
249 guchar *input = NULL;
250 GdkMemoryTexture *memtex = NULL;
251 const guchar *texdata;
252 gsize texstride;
253 guchar *row;
254 int width, height;
255
256 width = gdk_texture_get_width (texture);
257 height = gdk_texture_get_height (texture);
258
259 info.err = jpeg_std_error (err: &jerr.pub);
260 jerr.pub.error_exit = fatal_error_handler;
261 jerr.pub.output_message = output_message_handler;
262 jerr.error = NULL;
263
264 if (sigsetjmp (jerr.setjmp_buffer, 1))
265 {
266 free (ptr: data);
267 g_free (mem: input);
268 jpeg_destroy_compress (cinfo: &info);
269 g_clear_object (&memtex);
270 return NULL;
271 }
272
273 info.err = jpeg_std_error (err: &err);
274 jpeg_create_compress (&info);
275 info.image_width = width;
276 info.image_height = height;
277 info.input_components = 3;
278 info.in_color_space = JCS_RGB;
279
280 jpeg_set_defaults (cinfo: &info);
281 jpeg_set_quality (cinfo: &info, quality: 75, TRUE);
282
283 jpeg_mem_dest (cinfo: &info, outbuffer: &data, outsize: &size);
284
285 memtex = gdk_memory_texture_from_texture (texture,
286 format: GDK_MEMORY_R8G8B8);
287 texdata = gdk_memory_texture_get_data (self: memtex);
288 texstride = gdk_memory_texture_get_stride (self: memtex);
289
290 jpeg_start_compress (cinfo: &info, TRUE);
291
292 while (info.next_scanline < info.image_height)
293 {
294 row = (guchar *) texdata + info.next_scanline * texstride;
295 jpeg_write_scanlines (cinfo: &info, scanlines: &row, num_lines: 1);
296 }
297
298 jpeg_finish_compress (cinfo: &info);
299
300 g_object_unref (object: memtex);
301 g_free (mem: input);
302 jpeg_destroy_compress (cinfo: &info);
303
304 return g_bytes_new_with_free_func (data, size, free_func: (GDestroyNotify) free, NULL);
305}
306
307/* }}} */
308
309/* vim:set foldmethod=marker expandtab: */
310

source code of gtk/gdk/loaders/gdkjpeg.c