gtk2/gdk/loaders/gdkjpeg.c
2021-09-17 02:02:51 +02:00

330 lines
7.7 KiB
C

/* 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gdkjpegprivate.h"
#include "gdktexture.h"
#include "gdkmemorytextureprivate.h"
#include <jpeglib.h>
#include <jerror.h>
#include <setjmp.h>
/* {{{ 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;
};
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,
cinfo->err->msg_code == JERR_OUT_OF_MEMORY
? GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY
: 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_rgba_to_rgb (guchar *data,
int width,
int height,
int stride)
{
gsize x, y;
guchar *src, *dest;
for (y = 0; y < height; y++)
{
src = data;
dest = data;
for (x = 0; x < width; x++)
{
guint32 pixel;
memcpy (&pixel, src, sizeof (guint32));
dest[0] = (pixel & 0x00ff0000) >> 16;
dest[1] = (pixel & 0x0000ff00) >> 8;
dest[2] = (pixel & 0x000000ff) >> 0;
dest += 3;
src += 4;
}
data += stride;
}
}
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;
unsigned char *row[1];
GBytes *bytes;
GdkTexture *texture;
GdkMemoryFormat format;
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))
{
jpeg_destroy_decompress (&info);
return NULL;
}
jpeg_create_decompress (&info);
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,
"Unsupported colorspace in jpeg (%d)", info.out_color_space);
jpeg_destroy_decompress (&info);
return NULL;
}
if (!data)
{
g_set_error_literal (error,
GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_INSUFFICIENT_MEMORY,
"Not enough memory to load jpeg");
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);
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;
guchar *row;
int width, height, stride;
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);
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);
jpeg_mem_dest (&info, &data, &size);
stride = width * 4;
input = g_malloc (stride * height);
gdk_texture_download (texture, input, stride);
convert_rgba_to_rgb (data, width, height, stride);
jpeg_start_compress (&info, TRUE);
while (info.next_scanline < info.image_height)
{
row = &input[info.next_scanline * stride];
jpeg_write_scanlines (&info, &row, 1);
}
jpeg_finish_compress (&info);
g_free (input);
jpeg_destroy_compress (&info);
return g_bytes_new_with_free_func (data, size, (GDestroyNotify) free, NULL);
}
/* }}} */
/* vim:set foldmethod=marker expandtab: */