/* GDK - The GIMP Drawing Kit * Copyright (C) 2021 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 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 . */ #include #include "config.h" #include "gdkjpegprivate.h" #include #include "gdktexture.h" #include "gdktexturedownloaderprivate.h" #include "gdkprofilerprivate.h" #include #include #include /* {{{ Error handling */ /* No sigsetjmp on Windows */ #ifndef HAVE_SIGSETJMP #define sigjmp_buf jmp_buf #define sigsetjmp(jb, x) setjmp(jb) #define siglongjmp longjmp #endif struct error_handler_data { struct jpeg_error_mgr pub; sigjmp_buf setjmp_buffer; GError **error; }; G_GNUC_NORETURN static void fatal_error_handler (j_common_ptr cinfo) { struct error_handler_data *errmgr; char buffer[JMSG_LENGTH_MAX]; errmgr = (struct error_handler_data *) cinfo->err; cinfo->err->format_message (cinfo, buffer); if (errmgr->error && *errmgr->error == NULL) g_set_error (errmgr->error, GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_CORRUPT_IMAGE, _("Error interpreting JPEG image file (%s)"), buffer); siglongjmp (errmgr->setjmp_buffer, 1); g_assert_not_reached (); } static void output_message_handler (j_common_ptr cinfo) { /* do nothing */ } /* }}} */ /* {{{ Format conversion */ static void convert_grayscale_to_rgb (guchar *data, int width, int height, int stride) { gsize x, y; guchar *dest, *src; for (y = 0; y < height; y++) { src = data + width; dest = data + 3 * width; for (x = 0; x < width; x++) { dest -= 3; src -= 1; dest[0] = *src; dest[1] = *src; dest[2] = *src; } data += stride; } } static void convert_cmyk_to_rgba (guchar *data, int width, int height, int stride) { gsize x, r; guchar *dest; for (r = 0; r < height; r++) { dest = data; for (x = 0; x < width; x++) { int c, m, y, k; c = dest[0]; m = dest[1]; y = dest[2]; k = dest[3]; dest[0] = k * c / 255; dest[1] = k * m / 255; dest[2] = k * y / 255; dest[3] = 255; dest += 4; } data += stride; } } /* }}} */ /* {{{ Public API */ GdkTexture * gdk_load_jpeg (GBytes *input_bytes, GError **error) { struct jpeg_decompress_struct info; struct error_handler_data jerr; guint width, height, stride; unsigned char *data = NULL; unsigned char *row[1]; GBytes *bytes; GdkTexture *texture; GdkMemoryFormat format; G_GNUC_UNUSED guint64 before = GDK_PROFILER_CURRENT_TIME; info.err = jpeg_std_error (&jerr.pub); jerr.pub.error_exit = fatal_error_handler; jerr.pub.output_message = output_message_handler; jerr.error = error; if (sigsetjmp (jerr.setjmp_buffer, 1)) { g_free (data); jpeg_destroy_decompress (&info); return NULL; } jpeg_create_decompress (&info); /* Limit to 1GB to avoid OOM with large images */ info.mem->max_memory_to_use = 1024 * 1024 * 1024; jpeg_mem_src (&info, g_bytes_get_data (input_bytes, NULL), g_bytes_get_size (input_bytes)); jpeg_read_header (&info, TRUE); jpeg_start_decompress (&info); width = info.output_width; height = info.output_height; switch ((int)info.out_color_space) { case JCS_GRAYSCALE: case JCS_RGB: stride = 3 * width; data = g_try_malloc_n (stride, height); format = GDK_MEMORY_R8G8B8; break; case JCS_CMYK: stride = 4 * width; data = g_try_malloc_n (stride, height); format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; break; default: g_set_error (error, GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_UNSUPPORTED_CONTENT, _("Unsupported JPEG colorspace (%d)"), info.out_color_space); jpeg_destroy_decompress (&info); return NULL; } if (!data) { g_set_error (error, GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_TOO_LARGE, _("Not enough memory for image size %ux%u"), width, height); jpeg_destroy_decompress (&info); return NULL; } while (info.output_scanline < info.output_height) { row[0] = (unsigned char *)(&data[stride * info.output_scanline]); jpeg_read_scanlines (&info, row, 1); } switch ((int)info.out_color_space) { case JCS_GRAYSCALE: convert_grayscale_to_rgb (data, width, height, stride); format = GDK_MEMORY_R8G8B8; break; case JCS_RGB: break; case JCS_CMYK: convert_cmyk_to_rgba (data, width, height, stride); break; default: g_assert_not_reached (); } jpeg_finish_decompress (&info); jpeg_destroy_decompress (&info); bytes = g_bytes_new_take (data, stride * height); texture = gdk_memory_texture_new (width, height, format, bytes, stride); g_bytes_unref (bytes); gdk_profiler_end_mark (before, "jpeg load", NULL); return texture; } GBytes * gdk_save_jpeg (GdkTexture *texture) { struct jpeg_compress_struct info; struct error_handler_data jerr; struct jpeg_error_mgr err; guchar *data = NULL; gulong size = 0; guchar *input = NULL; GdkTextureDownloader downloader; GBytes *texbytes = NULL; const guchar *texdata; gsize texstride; guchar *row; int width, height; width = gdk_texture_get_width (texture); height = gdk_texture_get_height (texture); info.err = jpeg_std_error (&jerr.pub); jerr.pub.error_exit = fatal_error_handler; jerr.pub.output_message = output_message_handler; jerr.error = NULL; if (sigsetjmp (jerr.setjmp_buffer, 1)) { free (data); g_free (input); jpeg_destroy_compress (&info); g_clear_pointer (&texbytes, g_bytes_unref); return NULL; } info.err = jpeg_std_error (&err); jpeg_create_compress (&info); info.image_width = width; info.image_height = height; info.input_components = 3; info.in_color_space = JCS_RGB; jpeg_set_defaults (&info); jpeg_set_quality (&info, 75, TRUE); info.mem->max_memory_to_use = 300 * 1024 * 1024; jpeg_mem_dest (&info, &data, &size); gdk_texture_downloader_init (&downloader, texture); gdk_texture_downloader_set_format (&downloader, GDK_MEMORY_R8G8B8); texbytes = gdk_texture_downloader_download_bytes (&downloader, &texstride); gdk_texture_downloader_finish (&downloader); texdata = g_bytes_get_data (texbytes, NULL); jpeg_start_compress (&info, TRUE); while (info.next_scanline < info.image_height) { row = (guchar *) texdata + info.next_scanline * texstride; jpeg_write_scanlines (&info, &row, 1); } jpeg_finish_compress (&info); g_bytes_unref (texbytes); g_free (input); jpeg_destroy_compress (&info); return g_bytes_new_with_free_func (data, size, (GDestroyNotify) free, NULL); } /* }}} */ /* vim:set foldmethod=marker expandtab: */