Compare commits

...

20 Commits

Author SHA1 Message Date
Matthias Clasen
9d4f2414ba Load jpegs without gdk-pixbuf
Use our own loader for jpeg files.
2021-09-11 13:53:42 -04:00
Matthias Clasen
1c8c852c1b Add code to load jpegs
This lets us avoid gdk-pixbuf for loading
textures from the common image formats.

As a consequence, we are now linking against libjpeg.
2021-09-11 13:53:42 -04:00
Matthias Clasen
5923766623 Use our tiff loader for content (de)serialization
We still fall back to gdk-pixbuf for handling all
the other image formats.
2021-09-11 13:53:42 -04:00
Matthias Clasen
da59d7080e tiff: Handle all formats when loading
The deserialization framework expects us to handle any
data it throws at us, so lets make an effort.
2021-09-11 13:53:11 -04:00
Matthias Clasen
53f6766bb3 Add async tiff load/save code
This will be used in content (de)serialization.
2021-09-11 13:53:10 -04:00
Matthias Clasen
7725c8439c Use our png loader for content (de)serialization
We still fall back to gdk-pixbuf for handling all
the other image formats. But with this in place,
we no longer need to tweak the pixbuf formats to
ensure that png comes first.
2021-09-11 13:53:10 -04:00
Matthias Clasen
5a9c81ab6f png: Handle all formats when saving
The serialization framework expects us to handle any
data it throws at us, so lets make an effort.
2021-09-11 13:53:10 -04:00
Matthias Clasen
32ef5bed92 Add async png load/save code
This will be used in content (de)serialization.
2021-09-11 13:53:10 -04:00
Matthias Clasen
5e42e78f2d Add gdk_texture_download_float
This will allow getting float data out of textures.
2021-09-11 13:53:10 -04:00
Matthias Clasen
751ccf460d Allow conversion to hdr formats
Add conversion functions that produce
GDK_MEMORY_R32G32B32A32_PREMULTIPLIED data,
since we want to allow downloading textures
in that format. We make use of the new chaining
abilities here to cut down on the number of
conversion functions.
2021-09-11 13:53:10 -04:00
Matthias Clasen
0f43857676 Redo the conversion setup for memory formats
The two changes have to goal of limiting the
amount of conversion functions we have to write
and to keep the matrix from growing too big.

The first change is to pass the formats to the
conversion functions, which will let them 'chain'
internally (which is not very efficient, but
can be of use).

The second change is to make the matrix sparse,
since we are only interested in fully supporting
a few target formats.
2021-09-11 13:53:10 -04:00
Matthias Clasen
d389c113e9 Support loading and saving float textures
We use the newly added tiff support for this.
2021-09-11 13:53:10 -04:00
Matthias Clasen
97e34ed859 Add code to load and save tiff files
Add support for the tiff format, which is flexible
enough to handle all our memory texture formats
without loss.

As a consequence, we are now linking against libtiff.
2021-09-11 13:53:10 -04:00
Matthias Clasen
512580ba0d Add gdk_texture_save_to_file
This is a new API which avoids encoding the file format
into the function name, so we can change the format we
save textures in down the road without having to change
API.
2021-09-11 13:53:10 -04:00
Matthias Clasen
dcde46ab30 Support 32-bit floating point textures
Add R32G32B32_FLOAT and R32G32B32A32_FLOAT_PREMULTIPLIED
formats which are using floats.

The intention is to use these for HDR content.

Not done here: Update memory texture tests
to include floating point textures.
2021-09-11 13:53:10 -04:00
Matthias Clasen
9a9d20e274 Support 16-bit floating point textures
Add R16G16B16_FLOAT and R16B16B16A16_FLOAT_PREMULTIPLIED
formats, which are using half floats.

The intention is to use these for HDR content.

Not done here: Update memory texture tests
to include floating point textures.
2021-09-11 13:53:10 -04:00
Matthias Clasen
4fcb46a387 Support loading and saving 16-bit textures
This is using our new png loading code.
2021-09-11 13:53:10 -04:00
Matthias Clasen
3e4f8dc356 Add code to load and save pngs
Using libpng instead of the lowest-common-denominator
gdk-pixbuf loader allows us to load >8bit data, and
apply gamma correction.

As a consequence, we are now linking against libpng.
2021-09-11 13:53:10 -04:00
Matthias Clasen
e1295d3db1 Add private api to download textures
Add a private api to download textures in formats
other than the cairo one. This will let us download
16bit and float textures without downgrading them
to 8bpp.
2021-09-11 13:53:10 -04:00
Matthias Clasen
f64304503c Support 16bit textures
Add GDK_MEMORY_R16G16B16, and
GDK_MEMORY_R16G16B16A16_PREMULTIPLIED, which
matches the libpng PNG_FORMAT_LINEAR_RGB_ALPHA
format. These formats will be used to provide
linear (gamma-corrected) input in future commits.

GL can upload these formats directly. This is not
only less work for us, it also avoids losing
precision during upload.
2021-09-11 13:53:10 -04:00
18 changed files with 2628 additions and 56 deletions

View File

@@ -25,6 +25,8 @@
#include "filetransferportalprivate.h"
#include "gdktexture.h"
#include "gdkrgbaprivate.h"
#include "gdkpng.h"
#include "gdktiff.h"
#include <gdk-pixbuf/gdk-pixbuf.h>
@@ -655,6 +657,66 @@ pixbuf_deserializer (GdkContentDeserializer *deserializer)
deserializer);
}
static void
png_deserializer_finish (GObject *source,
GAsyncResult *res,
gpointer deserializer)
{
GdkTexture *texture;
GValue *value;
GError *error = NULL;
texture = gdk_load_png_finish (res, &error);
if (texture == NULL)
{
gdk_content_deserializer_return_error (deserializer, error);
return;
}
value = gdk_content_deserializer_get_value (deserializer);
g_value_take_object (value, texture);
gdk_content_deserializer_return_success (deserializer);
}
static void
png_deserializer (GdkContentDeserializer *deserializer)
{
gdk_load_png_async (gdk_content_deserializer_get_input_stream (deserializer),
gdk_content_deserializer_get_cancellable (deserializer),
png_deserializer_finish,
deserializer);
}
static void
tiff_deserializer_finish (GObject *source,
GAsyncResult *res,
gpointer deserializer)
{
GdkTexture *texture;
GValue *value;
GError *error = NULL;
texture = gdk_load_tiff_finish (res, &error);
if (texture == NULL)
{
gdk_content_deserializer_return_error (deserializer, error);
return;
}
value = gdk_content_deserializer_get_value (deserializer);
g_value_take_object (value, texture);
gdk_content_deserializer_return_success (deserializer);
}
static void
tiff_deserializer (GdkContentDeserializer *deserializer)
{
gdk_load_tiff_async (gdk_content_deserializer_get_input_stream (deserializer),
gdk_content_deserializer_get_cancellable (deserializer),
tiff_deserializer_finish,
deserializer);
}
static void
string_deserializer_finish (GObject *source,
GAsyncResult *result,
@@ -863,27 +925,38 @@ init (void)
initialized = TRUE;
gdk_content_register_deserializer ("image/png",
GDK_TYPE_TEXTURE,
png_deserializer,
NULL,
NULL);
gdk_content_register_deserializer ("image/tiff",
GDK_TYPE_TEXTURE,
tiff_deserializer,
NULL,
NULL);
formats = gdk_pixbuf_get_formats ();
/* Make sure png comes first */
for (f = formats; f; f = f->next)
{
GdkPixbufFormat *fmt = f->data;
char *name;
char *name;
name = gdk_pixbuf_format_get_name (fmt);
if (g_str_equal (name, "png"))
{
formats = g_slist_delete_link (formats, f);
formats = g_slist_prepend (formats, fmt);
{
formats = g_slist_delete_link (formats, f);
formats = g_slist_prepend (formats, fmt);
g_free (name);
break;
}
g_free (name);
break;
}
g_free (name);
}
}
for (f = formats; f; f = f->next)
{

View File

@@ -26,6 +26,9 @@
#include "filetransferportalprivate.h"
#include "gdktextureprivate.h"
#include "gdkrgba.h"
#include "gdkpng.h"
#include "gdktiff.h"
#include "gdkmemorytextureprivate.h"
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <string.h>
@@ -606,6 +609,7 @@ gdk_content_serialize_finish (GAsyncResult *result,
/*** SERIALIZERS ***/
static void
pixbuf_serializer_finish (GObject *source,
GAsyncResult *res,
@@ -658,6 +662,132 @@ pixbuf_serializer (GdkContentSerializer *serializer)
g_object_unref (pixbuf);
}
typedef struct {
GdkContentSerializer *serializer;
GBytes *bytes;
} PngSerializerData;
static void
png_serializer_finish (GObject *source,
GAsyncResult *res,
gpointer user_data)
{
PngSerializerData *data = user_data;
GError *error = NULL;
if (!gdk_save_png_finish (res, &error))
gdk_content_serializer_return_error (data->serializer, error);
else
gdk_content_serializer_return_success (data->serializer);
g_bytes_unref (data->bytes);
g_free (data);
}
static void
png_serializer (GdkContentSerializer *serializer)
{
const GValue *value;
GdkTexture *texture;
GdkMemoryFormat format;
GBytes *bytes = NULL;
PngSerializerData *data;
value = gdk_content_serializer_get_value (serializer);
texture = g_value_get_object (value);
for (int i = 0; i < GDK_MEMORY_N_FORMATS; i++)
{
bytes = gdk_texture_download_format (texture, i);
if (bytes)
{
format = i;
break;
}
}
g_assert (bytes != NULL);
data = g_new0 (PngSerializerData, 1);
data->serializer = serializer;
data->bytes = bytes;
gdk_save_png_async (gdk_content_serializer_get_output_stream (serializer),
g_bytes_get_data (bytes, NULL),
gdk_texture_get_width (texture),
gdk_texture_get_height (texture),
gdk_texture_get_width (texture) * gdk_memory_format_bytes_per_pixel (format),
format,
gdk_content_serializer_get_cancellable (serializer),
png_serializer_finish,
data);
g_object_unref (texture);
}
typedef struct {
GdkContentSerializer *serializer;
GBytes *bytes;
} TiffSerializerData;
static void
tiff_serializer_finish (GObject *source,
GAsyncResult *res,
gpointer user_data)
{
TiffSerializerData *data = user_data;
GError *error = NULL;
if (!gdk_save_tiff_finish (res, &error))
gdk_content_serializer_return_error (data->serializer, error);
else
gdk_content_serializer_return_success (data->serializer);
g_bytes_unref (data->bytes);
g_free (data);
}
static void
tiff_serializer (GdkContentSerializer *serializer)
{
const GValue *value;
GdkTexture *texture;
GdkMemoryFormat format;
GBytes *bytes = NULL;
TiffSerializerData *data;
value = gdk_content_serializer_get_value (serializer);
texture = g_value_get_object (value);
for (int i = 0; i < GDK_MEMORY_N_FORMATS; i++)
{
bytes = gdk_texture_download_format (texture, i);
if (bytes)
{
format = i;
break;
}
}
g_assert (bytes != NULL);
data = g_new0 (TiffSerializerData, 1);
data->serializer = serializer;
data->bytes = bytes;
gdk_save_tiff_async (gdk_content_serializer_get_output_stream (serializer),
g_bytes_get_data (bytes, NULL),
gdk_texture_get_width (texture),
gdk_texture_get_height (texture),
gdk_texture_get_width (texture) * gdk_memory_format_bytes_per_pixel (format),
format,
gdk_content_serializer_get_cancellable (serializer),
tiff_serializer_finish,
data);
g_object_unref (texture);
}
static void
string_serializer_finish (GObject *source,
GAsyncResult *result,
@@ -877,27 +1007,36 @@ init (void)
initialized = TRUE;
gdk_content_register_serializer (GDK_TYPE_TEXTURE,
"image/png",
png_serializer,
NULL, NULL);
gdk_content_register_serializer (GDK_TYPE_TEXTURE,
"image/tiff",
tiff_serializer,
NULL, NULL);
formats = gdk_pixbuf_get_formats ();
/* Make sure png comes first */
for (f = formats; f; f = f->next)
{
GdkPixbufFormat *fmt = f->data;
char *name;
char *name;
name = gdk_pixbuf_format_get_name (fmt);
if (g_str_equal (name, "png"))
{
formats = g_slist_delete_link (formats, f);
formats = g_slist_prepend (formats, fmt);
{
formats = g_slist_delete_link (formats, f);
formats = g_slist_prepend (formats, fmt);
g_free (name);
break;
}
g_free (name);
break;
}
g_free (name);
}
}
for (f = formats; f; f = f->next)
{

View File

@@ -278,6 +278,48 @@ gdk_gl_context_upload_texture (GdkGLContext *context,
gl_type = GL_UNSIGNED_BYTE;
bpp = 3;
}
else if (data_format == GDK_MEMORY_R16G16B16)
{
gl_internalformat = GL_RGBA16;
gl_format = GL_RGB;
gl_type = GL_UNSIGNED_SHORT;
bpp = 6;
}
else if (data_format == GDK_MEMORY_R16G16B16A16_PREMULTIPLIED)
{
gl_internalformat = GL_RGBA16;
gl_format = GL_RGBA;
gl_type = GL_UNSIGNED_SHORT;
bpp = 8;
}
else if (data_format == GDK_MEMORY_R16G16B16_FLOAT)
{
gl_internalformat = GL_RGB16F;
gl_format = GL_RGB;
gl_type = GL_HALF_FLOAT;
bpp = 6;
}
else if (data_format == GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED)
{
gl_internalformat = GL_RGBA16F;
gl_format = GL_RGBA;
gl_type = GL_HALF_FLOAT;
bpp = 8;
}
else if (data_format == GDK_MEMORY_R32G32B32_FLOAT)
{
gl_internalformat = GL_RGB32F;
gl_format = GL_RGB;
gl_type = GL_FLOAT;
bpp = 12;
}
else if (data_format == GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED)
{
gl_internalformat = GL_RGBA32F;
gl_format = GL_RGBA;
gl_type = GL_FLOAT;
bpp = 16;
}
else /* Fall-back, convert to cairo-surface-format */
{
copy = g_malloc (width * height * 4);

View File

@@ -22,6 +22,8 @@
#include "gdkcairo.h"
#include "gdktextureprivate.h"
#include "gdkmemorytextureprivate.h"
#include "gdkinternals.h"
#include <epoxy/gl.h>
@@ -110,6 +112,78 @@ gdk_gl_texture_download (GdkTexture *texture,
cairo_surface_destroy (surface);
}
static int
type_from_internal_format (int internal_format)
{
switch (internal_format)
{
case GL_RGB16:
case GL_RGBA16:
return GL_UNSIGNED_SHORT;
case GL_RGB16F:
case GL_RGBA16F:
return GL_HALF_FLOAT;
case GL_RGB32F:
case GL_RGBA32F:
return GL_FLOAT;
default:
g_assert_not_reached ();
}
}
static int
internal_format_for_format (GdkMemoryFormat format)
{
switch ((int)format)
{
case GDK_MEMORY_R16G16B16:
return GL_RGB16;
case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED:
return GL_RGBA16;
case GDK_MEMORY_R16G16B16_FLOAT:
return GL_RGB16F;
case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED:
return GL_RGB16F;
case GDK_MEMORY_R32G32B32_FLOAT:
return GL_RGB32F;
case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED:
return GL_RGB32F;
default:
g_assert_not_reached ();
}
}
static GBytes *
gdk_gl_texture_download_format (GdkTexture *texture,
GdkMemoryFormat format)
{
GdkGLTexture *self = GDK_GL_TEXTURE (texture);
GdkSurface *surface;
GdkGLContext *context;
int internal_format = 0;
gpointer data;
gsize size;
surface = gdk_gl_context_get_surface (self->context);
context = gdk_surface_get_paint_gl_context (surface, NULL);
gdk_gl_context_make_current (context);
glBindTexture (GL_TEXTURE_2D, self->id);
glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format);
if (internal_format != internal_format_for_format (format))
return NULL;
size = texture->width * texture->height * gdk_memory_format_bytes_per_pixel (format);
data = malloc (size);
glReadPixels (0, 0, texture->width, texture->height,
internal_format, type_from_internal_format (internal_format),
data);
return g_bytes_new_take (data, size);
}
static void
gdk_gl_texture_class_init (GdkGLTextureClass *klass)
{
@@ -117,6 +191,7 @@ gdk_gl_texture_class_init (GdkGLTextureClass *klass)
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
texture_class->download = gdk_gl_texture_download;
texture_class->download_format = gdk_gl_texture_download_format;
gobject_class->dispose = gdk_gl_texture_dispose;
}

234
gdk/gdkjpeg.c Normal file
View File

@@ -0,0 +1,234 @@
/* 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 "gdkjpeg.h"
#include "gdktexture.h"
#include "gdkmemorytextureprivate.h"
#include <jpeglib.h>
#include <jerror.h>
#include <setjmp.h>
/* {{{ IO handling */
#define BUF_SIZE 65536
typedef struct {
struct jpeg_source_mgr pub;
JOCTET buffer[BUF_SIZE];
long skip_next;
GInputStream *stream;
} my_source_mgr;
static void
init_source (j_decompress_ptr info)
{
my_source_mgr *src = (my_source_mgr *) info->src;
src->skip_next = 0;
}
static void
term_source (j_decompress_ptr info)
{
}
static boolean
fill_input_buffer (j_decompress_ptr info)
{
my_source_mgr *src = (my_source_mgr *) info->src;
size_t nbytes;
nbytes = g_input_stream_read (src->stream, src->buffer, BUF_SIZE, NULL, NULL);
if (nbytes <= 0)
{
/* Insert a fake EOI marker */
src->buffer[0] = (JOCTET) 0xFF;
src->buffer[1] = (JOCTET) JPEG_EOI;
nbytes = 2;
}
src->pub.next_input_byte = src->buffer;
src->pub.bytes_in_buffer = nbytes;
return TRUE;
}
static void
skip_input_data (j_decompress_ptr info,
long num_bytes)
{
my_source_mgr *src = (my_source_mgr *) info->src;
if (num_bytes > 0)
{
while (num_bytes > src->pub.bytes_in_buffer)
{
num_bytes -= src->pub.bytes_in_buffer;
fill_input_buffer (info);
}
src->pub.next_input_byte += num_bytes;
src->pub.bytes_in_buffer -= num_bytes;
}
}
/* }}} */
/* {{{ Error handling */
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;
errmgr = (struct error_handler_data *) cinfo->err;
siglongjmp (errmgr->setjmp_buffer, 1);
g_assert_not_reached ();
}
static void
output_message_handler (j_common_ptr cinfo)
{
/* do nothing */
}
/* }}} */
/* {{{ Public API */
GdkTexture *
gdk_load_jpeg (GInputStream *stream,
GError **error)
{
struct jpeg_decompress_struct info;
struct error_handler_data jerr;
struct jpeg_error_mgr err;
my_source_mgr src;
int width, height;
int size;
unsigned char *data;
unsigned char *row[1];
GBytes *bytes;
GdkTexture *texture;
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;
}
info.err = jpeg_std_error (&err);
jpeg_create_decompress (&info);
src.pub.init_source = init_source;
src.pub.fill_input_buffer = fill_input_buffer;
src.pub.skip_input_data = skip_input_data;
src.pub.resync_to_restart = jpeg_resync_to_restart;
src.pub.term_source = term_source;
src.pub.bytes_in_buffer = 0;
src.pub.next_input_byte = NULL;
src.stream = stream;
info.src = (struct jpeg_source_mgr *)&src;
jpeg_read_header (&info, TRUE);
jpeg_start_decompress (&info);
width = info.output_width;
height = info.output_height;
size = width * height * 3;
data = g_malloc (size);
while (info.output_scanline < info.output_height)
{
row[0] = (unsigned char *)(&data[3 *info.output_width * info.output_scanline]);
jpeg_read_scanlines (&info, row, 1);
}
jpeg_finish_decompress (&info);
jpeg_destroy_decompress (&info);
bytes = g_bytes_new_take (data, size);
texture = gdk_memory_texture_new (width, height,
GDK_MEMORY_R8G8B8,
bytes, width * 3);
g_bytes_unref (bytes);
return texture;
}
/* }}} */
/* {{{ Async code */
static void
load_jpeg_in_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
GInputStream *stream = source_object;
GdkTexture *texture;
GError *error = NULL;
texture = gdk_load_jpeg (stream, &error);
if (texture)
g_task_return_pointer (task, texture, g_object_unref);
else
g_task_return_error (task, error);
}
void
gdk_load_jpeg_async (GInputStream *stream,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (stream, cancellable, callback, user_data);
g_task_run_in_thread (task, load_jpeg_in_thread);
g_object_unref (task);
}
GdkTexture *
gdk_load_jpeg_finish (GAsyncResult *result,
GError **error)
{
return g_task_propagate_pointer (G_TASK (result), error);
}
/* }}} */
/* vim:set foldmethod=marker expandtab: */

35
gdk/gdkjpeg.h Normal file
View File

@@ -0,0 +1,35 @@
/* 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/>.
*/
#ifndef __GDK_JPEG_H__
#define __GDK_JPEG_H__
#include "gdkmemorytexture.h"
#include <gio/gio.h>
GdkTexture *gdk_load_jpeg (GInputStream *stream,
GError **error);
void gdk_load_jpeg_async (GInputStream *stream,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GdkTexture *gdk_load_jpeg_finish (GAsyncResult *result,
GError **error);
#endif

View File

@@ -20,6 +20,7 @@
#include "config.h"
#include "gdkmemorytextureprivate.h"
#include "gsk/ngl/fp16private.h"
/**
* GdkMemoryTexture:
@@ -62,6 +63,22 @@ gdk_memory_format_bytes_per_pixel (GdkMemoryFormat format)
case GDK_MEMORY_B8G8R8:
return 3;
case GDK_MEMORY_R16G16B16:
return 6;
case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED:
case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED:
return 8;
case GDK_MEMORY_R16G16B16_FLOAT:
return 6;
case GDK_MEMORY_R32G32B32_FLOAT:
return 12;
case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED:
return 16;
case GDK_MEMORY_N_FORMATS:
default:
g_assert_not_reached ();
@@ -97,6 +114,18 @@ gdk_memory_texture_download (GdkTexture *texture,
area->width, area->height);
}
static GBytes *
gdk_memory_texture_download_format (GdkTexture *texture,
GdkMemoryFormat format)
{
GdkMemoryTexture *self = GDK_MEMORY_TEXTURE (texture);
if (self->format == format)
return g_bytes_ref (self->bytes);
return NULL;
}
static void
gdk_memory_texture_class_init (GdkMemoryTextureClass *klass)
{
@@ -104,6 +133,7 @@ gdk_memory_texture_class_init (GdkMemoryTextureClass *klass)
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
texture_class->download = gdk_memory_texture_download;
texture_class->download_format = gdk_memory_texture_download_format;
gobject_class->dispose = gdk_memory_texture_dispose;
}
@@ -148,7 +178,7 @@ gdk_memory_texture_new (int width,
return GDK_TEXTURE (self);
}
GdkMemoryFormat
GdkMemoryFormat
gdk_memory_texture_get_format (GdkMemoryTexture *self)
{
return self->format;
@@ -169,8 +199,10 @@ gdk_memory_texture_get_stride (GdkMemoryTexture *self)
static void
convert_memcpy (guchar *dest_data,
gsize dest_stride,
GdkMemoryFormat dest_format,
const guchar *src_data,
gsize src_stride,
GdkMemoryFormat src_format,
gsize width,
gsize height)
{
@@ -184,8 +216,10 @@ convert_memcpy (guchar *dest_data,
static void \
convert_swizzle ## A ## R ## G ## B (guchar *dest_data, \
gsize dest_stride, \
GdkMemoryFormat dest_format, \
const guchar *src_data, \
gsize src_stride, \
GdkMemoryFormat src_format, \
gsize width, \
gsize height) \
{ \
@@ -215,8 +249,10 @@ SWIZZLE(1,2,3,0)
static void \
convert_swizzle_opaque_## A ## R ## G ## B (guchar *dest_data, \
gsize dest_stride, \
GdkMemoryFormat dest_format, \
const guchar *src_data, \
gsize src_stride, \
GdkMemoryFormat src_format, \
gsize width, \
gsize height) \
{ \
@@ -248,8 +284,10 @@ static void \
convert_swizzle_premultiply_ ## A ## R ## G ## B ## _ ## A2 ## R2 ## G2 ## B2 \
(guchar *dest_data, \
gsize dest_stride, \
GdkMemoryFormat dest_format, \
const guchar *src_data, \
gsize src_stride, \
GdkMemoryFormat src_format, \
gsize width, \
gsize height) \
{ \
@@ -283,24 +321,447 @@ SWIZZLE_PREMULTIPLY (3,0,1,2, 0,1,2,3)
SWIZZLE_PREMULTIPLY (3,0,1,2, 3,0,1,2)
SWIZZLE_PREMULTIPLY (3,0,1,2, 0,3,2,1)
#define SWIZZLE_16TO8_OPAQUE(A,R,G,B) \
static void \
convert_16to8_swizzle_opaque_ ## A ## R ## G ## B (guchar *dest_data, \
gsize dest_stride, \
GdkMemoryFormat dest_format, \
const guchar *src_data, \
gsize src_stride, \
GdkMemoryFormat src_format, \
gsize width, \
gsize height) \
{ \
gsize x, y; \
\
for (y = 0; y < height; y++) \
{ \
guint16 *src = (guint16 *)src_data; \
for (x = 0; x < width; x++) \
{ \
dest_data[4 * x + A] = 255; \
dest_data[4 * x + R] = (guchar)(src[4 * x + 0] >> 8); \
dest_data[4 * x + G] = (guchar)(src[4 * x + 1] >> 8); \
dest_data[4 * x + B] = (guchar)(src[4 * x + 2] >> 8); \
} \
\
dest_data += dest_stride; \
src_data += src_stride; \
} \
}
SWIZZLE_16TO8_OPAQUE(0,1,2,3)
SWIZZLE_16TO8_OPAQUE(2,1,0,3)
SWIZZLE_16TO8_OPAQUE(1,2,3,0)
#define SWIZZLE_16TO8(A,R,G,B) \
static void \
convert_16to8_swizzle_ ## A ## R ## G ## B (guchar *dest_data, \
gsize dest_stride, \
GdkMemoryFormat dest_format, \
const guchar *src_data, \
gsize src_stride, \
GdkMemoryFormat src_format, \
gsize width, \
gsize height) \
{ \
gsize x, y; \
\
for (y = 0; y < height; y++) \
{ \
guint16 *src = (guint16 *)src_data; \
for (x = 0; x < width; x++) \
{ \
dest_data[4 * x + A] = (guchar)(src[4 * x + 0] >> 8); \
dest_data[4 * x + R] = (guchar)(src[4 * x + 1] >> 8); \
dest_data[4 * x + G] = (guchar)(src[4 * x + 2] >> 8); \
dest_data[4 * x + B] = (guchar)(src[4 * x + 3] >> 8); \
} \
\
dest_data += dest_stride; \
src_data += src_stride; \
} \
}
SWIZZLE_16TO8(0,1,2,3)
SWIZZLE_16TO8(2,1,0,3)
SWIZZLE_16TO8(1,2,3,0)
#define SWIZZLE_FP16_OPAQUE(A,R,G,B) \
static void \
convert_fp16_swizzle_opaque_ ## A ## R ## G ## B (guchar *dest_data, \
gsize dest_stride, \
GdkMemoryFormat dest_format, \
const guchar *src_data, \
gsize src_stride, \
GdkMemoryFormat src_format, \
gsize width, \
gsize height) \
{ \
gsize x, y; \
float *c; \
\
c = g_malloc (width * sizeof (float)); \
for (y = 0; y < height; y++) \
{ \
guint16 *src = (guint16 *)src_data; \
half_to_float (src, c, width); \
for (x = 0; x < width; x++) \
{ \
dest_data[4 * x + A] = 255; \
dest_data[4 * x + R] = (guchar)(255 * c[3 * x + 0]); \
dest_data[4 * x + G] = (guchar)(255 * c[3 * x + 1]); \
dest_data[4 * x + B] = (guchar)(255 * c[3 * x + 2]); \
} \
dest_data += dest_stride; \
src_data += src_stride; \
} \
g_free (c); \
}
SWIZZLE_FP16_OPAQUE(3,2,1,0)
SWIZZLE_FP16_OPAQUE(0,1,2,3)
SWIZZLE_FP16_OPAQUE(3,0,1,2)
#define SWIZZLE_FP16(A,R,G,B) \
static void \
convert_fp16_swizzle_ ## A ## R ## G ## B (guchar *dest_data, \
gsize dest_stride, \
GdkMemoryFormat dest_format, \
const guchar *src_data, \
gsize src_stride, \
GdkMemoryFormat src_format, \
gsize width, \
gsize height) \
{ \
gsize x, y; \
float *c; \
\
c = g_malloc (width * sizeof (float)); \
for (y = 0; y < height; y++) \
{ \
guint16 *src = (guint16 *)src_data; \
half_to_float (src, c, width); \
for (x = 0; x < width; x++) \
{ \
dest_data[4 * x + A] = (guchar)(255 * c[4 * x + 0]); \
dest_data[4 * x + R] = (guchar)(255 * c[4 * x + 1]); \
dest_data[4 * x + G] = (guchar)(255 * c[4 * x + 2]); \
dest_data[4 * x + B] = (guchar)(255 * c[4 * x + 3]); \
} \
dest_data += dest_stride; \
src_data += src_stride; \
} \
g_free (c); \
}
SWIZZLE_FP16(3,2,1,0)
SWIZZLE_FP16(0,1,2,3)
SWIZZLE_FP16(3,0,1,2)
#define SWIZZLE_FLOAT_OPAQUE(A,R,G,B) \
static void \
convert_float_swizzle_opaque_ ## A ## R ## G ## B (guchar *dest_data, \
gsize dest_stride, \
GdkMemoryFormat dest_format, \
const guchar *src_data, \
gsize src_stride, \
GdkMemoryFormat src_format, \
gsize width, \
gsize height) \
{ \
gsize x, y; \
\
for (y = 0; y < height; y++) \
{ \
float *src = (float *)src_data; \
for (x = 0; x < width; x++) \
{ \
dest_data[4 * x + A] = 255; \
dest_data[4 * x + R] = (guchar)(255 * src[3 * x + 0]); \
dest_data[4 * x + G] = (guchar)(255 * src[3 * x + 1]); \
dest_data[4 * x + B] = (guchar)(255 * src[3 * x + 2]); \
} \
\
dest_data += dest_stride; \
src_data += src_stride; \
} \
}
SWIZZLE_FLOAT_OPAQUE(3,2,1,0)
SWIZZLE_FLOAT_OPAQUE(0,1,2,3)
SWIZZLE_FLOAT_OPAQUE(3,0,1,2)
#define SWIZZLE_FLOAT(A,R,G,B) \
static void \
convert_float_swizzle_ ## A ## R ## G ## B (guchar *dest_data, \
gsize dest_stride, \
GdkMemoryFormat dest_format, \
const guchar *src_data, \
gsize src_stride, \
GdkMemoryFormat src_format, \
gsize width, \
gsize height) \
{ \
gsize x, y; \
\
for (y = 0; y < height; y++) \
{ \
float *src = (float *)src_data; \
for (x = 0; x < width; x++) \
{ \
dest_data[4 * x + A] = (guchar)(255 * src[3 * x + 0]); \
dest_data[4 * x + R] = (guchar)(255 * src[3 * x + 1]); \
dest_data[4 * x + G] = (guchar)(255 * src[3 * x + 2]); \
dest_data[4 * x + B] = (guchar)(255 * src[3 * x + 3]); \
} \
\
dest_data += dest_stride; \
src_data += src_stride; \
} \
}
SWIZZLE_FLOAT(3,2,1,0)
SWIZZLE_FLOAT(0,1,2,3)
SWIZZLE_FLOAT(3,0,1,2)
typedef void (* ConversionFunc) (guchar *dest_data,
gsize dest_stride,
GdkMemoryFormat dest_format,
const guchar *src_data,
gsize src_stride,
GdkMemoryFormat src_format,
gsize width,
gsize height);
static ConversionFunc converters[GDK_MEMORY_N_FORMATS][3] =
static ConversionFunc converters_to_bgra[GDK_MEMORY_N_FORMATS] =
{
{ convert_memcpy, convert_swizzle3210, convert_swizzle2103 },
{ convert_swizzle3210, convert_memcpy, convert_swizzle3012 },
{ convert_swizzle2103, convert_swizzle1230, convert_memcpy },
{ convert_swizzle_premultiply_3210_3210, convert_swizzle_premultiply_0123_3210, convert_swizzle_premultiply_3012_3210, },
{ convert_swizzle_premultiply_3210_0123, convert_swizzle_premultiply_0123_0123, convert_swizzle_premultiply_3012_0123 },
{ convert_swizzle_premultiply_3210_3012, convert_swizzle_premultiply_0123_3012, convert_swizzle_premultiply_3012_3012 },
{ convert_swizzle_premultiply_3210_0321, convert_swizzle_premultiply_0123_0321, convert_swizzle_premultiply_3012_0321 },
{ convert_swizzle_opaque_3210, convert_swizzle_opaque_0123, convert_swizzle_opaque_3012 },
{ convert_swizzle_opaque_3012, convert_swizzle_opaque_0321, convert_swizzle_opaque_3210 }
convert_memcpy,
convert_swizzle3210,
convert_swizzle2103,
convert_swizzle_premultiply_3210_3210,
convert_swizzle_premultiply_3210_0123,
convert_swizzle_premultiply_3210_3012,
convert_swizzle_premultiply_3210_0321,
convert_swizzle_opaque_3210,
convert_swizzle_opaque_3012,
convert_16to8_swizzle_opaque_2103,
convert_16to8_swizzle_2103,
convert_fp16_swizzle_opaque_3210,
convert_fp16_swizzle_3210,
convert_float_swizzle_opaque_3210,
convert_float_swizzle_3210,
};
static ConversionFunc converters_to_argb[GDK_MEMORY_N_FORMATS] =
{
convert_swizzle3210,
convert_memcpy,
convert_swizzle1230,
convert_swizzle_premultiply_0123_3210,
convert_swizzle_premultiply_0123_0123,
convert_swizzle_premultiply_0123_3012,
convert_swizzle_premultiply_0123_0321,
convert_swizzle_opaque_0123,
convert_swizzle_opaque_0321,
convert_16to8_swizzle_opaque_1230,
convert_16to8_swizzle_1230,
convert_fp16_swizzle_opaque_0123,
convert_fp16_swizzle_0123,
convert_float_swizzle_opaque_0123,
convert_float_swizzle_0123,
};
static ConversionFunc converters_to_rgba[GDK_MEMORY_N_FORMATS] =
{
convert_swizzle2103,
convert_swizzle3012,
convert_memcpy,
convert_swizzle_premultiply_3012_3210,
convert_swizzle_premultiply_3012_0123,
convert_swizzle_premultiply_3012_3012,
convert_swizzle_premultiply_3012_0321,
convert_swizzle_opaque_3012,
convert_swizzle_opaque_3210,
convert_16to8_swizzle_opaque_0123,
convert_16to8_swizzle_0123,
convert_fp16_swizzle_opaque_3012,
convert_fp16_swizzle_3012,
convert_float_swizzle_opaque_3012,
convert_float_swizzle_3012
};
static void
convert_rgba_to_hdr (guchar *dest_data,
gsize dest_stride,
GdkMemoryFormat dest_format,
const guchar *src_data,
gsize src_stride,
GdkMemoryFormat src_format,
gsize width,
gsize height)
{
gsize x, y;
for (y = 0; y < height; y++)
{
guint16 *src = (guint16 *)src_data;
float *dest = (float *)dest_data;
for (x = 0; x < width; x++)
{
dest[4 * (y * width + x) + 0] = src[3 * (y * width + x) + 0] / 255.f;
dest[4 * (y * width + x) + 1] = src[3 * (y * width + x) + 1] / 255.f;
dest[4 * (y * width + x) + 2] = src[3 * (y * width + x) + 2] / 255.f;
dest[4 * (y * width + x) + 3] = src[3 * (y * width + x) + 3] / 255.f;
}
dest_data += dest_stride;
src_data += src_stride;
}
}
static void
convert_8bit_to_hdr (guchar *dest_data,
gsize dest_stride,
GdkMemoryFormat dest_format,
const guchar *src_data,
gsize src_stride,
GdkMemoryFormat src_format,
gsize width,
gsize height)
{
guchar *data;
/* TODO: this could perhaps be done in-place */
data = g_malloc (width * height * 4);
gdk_memory_convert (data, 4 * width, GDK_MEMORY_R8G8B8A8_PREMULTIPLIED,
src_data, src_stride, src_format,
width, height);
convert_rgba_to_hdr (dest_data, dest_stride, dest_format,
data, 4 * width, GDK_MEMORY_R8G8B8A8_PREMULTIPLIED,
width, height);
g_free (data);
}
static void
convert_16bit_to_hdr (guchar *dest_data,
gsize dest_stride,
GdkMemoryFormat dest_format,
const guchar *src_data,
gsize src_stride,
GdkMemoryFormat src_format,
gsize width,
gsize height)
{
gsize x, y;
for (y = 0; y < height; y++)
{
guint16 *src = (guint16 *)src_data;
float *dest = (float *)dest_data;
for (x = 0; x < width; x++)
{
dest[4 * (y * width + x) + 0] = src[3 * (y * width + x) + 0] / 65535.f;
dest[4 * (y * width + x) + 1] = src[3 * (y * width + x) + 1] / 65535.f;
dest[4 * (y * width + x) + 2] = src[3 * (y * width + x) + 2] / 65535.f;
dest[4 * (y * width + x) + 3] = src[3 * (y * width + x) + 3] / 65535.f;
}
dest_data += dest_stride;
src_data += src_stride;
}
}
static void
convert_fp16_to_hdr (guchar *dest_data,
gsize dest_stride,
GdkMemoryFormat dest_format,
const guchar *src_data,
gsize src_stride,
GdkMemoryFormat src_format,
gsize width,
gsize height)
{
gsize x, y;
int src_bpp;
if (src_format == GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED)
src_bpp = 4;
else
src_bpp = 3;
for (y = 0; y < height; y++)
{
guint16 *src = (guint16 *)src_data;
float *dest = (float *)dest_data;
for (x = 0; x < width; x++)
{
guint16 h[4];
h[0] = src[src_bpp * (y * width + x)];
h[1] = src[src_bpp * (y * width + x) + 1];
h[2] = src[src_bpp * (y * width + x) + 2];
if (src_bpp == 4)
h[3] = src[src_bpp * (y * width + x) + 3];
else
h[3] = FP16_ONE;
half_to_float4 (h, &dest[4 * (y * width + x)]);
}
dest_data += dest_stride;
src_data += src_stride;
}
}
static void
convert_float_to_hdr (guchar *dest_data,
gsize dest_stride,
GdkMemoryFormat dest_format,
const guchar *src_data,
gsize src_stride,
GdkMemoryFormat src_format,
gsize width,
gsize height)
{
gsize x, y;
for (y = 0; y < height; y++)
{
float *src = (float *)src_data;
float *dest = (float *)dest_data;
for (x = 0; x < width; x++)
{
dest[4 * (y * width + x) + 0] = src[3 * (y * width + x) + 0];
dest[4 * (y * width + x) + 1] = src[3 * (y * width + x) + 1];
dest[4 * (y * width + x) + 2] = src[3 * (y * width + x) + 2];
dest[4 * (y * width + x) + 3] = 1.0;
}
dest_data += dest_stride;
src_data += src_stride;
}
}
static ConversionFunc converters_to_hdr[GDK_MEMORY_N_FORMATS] =
{
convert_8bit_to_hdr,
convert_8bit_to_hdr,
convert_rgba_to_hdr,
convert_8bit_to_hdr,
convert_8bit_to_hdr,
convert_8bit_to_hdr,
convert_8bit_to_hdr,
convert_8bit_to_hdr,
convert_8bit_to_hdr,
convert_16bit_to_hdr,
convert_fp16_to_hdr,
convert_fp16_to_hdr,
convert_float_to_hdr,
convert_memcpy
};
static ConversionFunc* converters[GDK_MEMORY_N_FORMATS] = {
converters_to_bgra, converters_to_argb, converters_to_rgba,
NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
converters_to_hdr
};
void
@@ -316,5 +777,7 @@ gdk_memory_convert (guchar *dest_data,
g_assert (dest_format < 3);
g_assert (src_format < GDK_MEMORY_N_FORMATS);
converters[src_format][dest_format] (dest_data, dest_stride, src_data, src_stride, width, height);
converters[dest_format][src_format] (dest_data, dest_stride, dest_format,
src_data, src_stride, src_format,
width, height);
}

View File

@@ -42,6 +42,20 @@ G_BEGIN_DECLS
* @GDK_MEMORY_A8B8G8R8: 4 bytes; for alpha, blue, green, red.
* @GDK_MEMORY_R8G8B8: 3 bytes; for red, green, blue. The data is opaque.
* @GDK_MEMORY_B8G8R8: 3 bytes; for blue, green, red. The data is opaque.
* @GDK_MEMORY_R16G16B16: 3 guint16 values; for red, green, blue. Since 4.6
* @GDK_MEMORY_R16G16B16A16_PREMULTIPLIED: 4 guint16 values; for red, green,
* blue, alpha. The color values are premultiplied with the alpha value.
* Since 4.6
* @GDK_MEMORY_R16G16B16_FLOAT: 3 half-float values; for red, green, blue.
* The data is opaque. Since 4.6
* @GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED: 4 half-float values; for
* red, green, blue and alpha. The color values are premultiplied with
* the alpha value. Since 4.6
* @GDK_MEMORY_B32G32R32_FLOAT: 3 float values; for blue, green, red.
* The data is opaque. Since 4.6
* @GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED: 4 float values; for
* red, green, blue and alpha. The color values are premultiplied with
* the alpha value. Since 4.6
* @GDK_MEMORY_N_FORMATS: The number of formats. This value will change as
* more formats get added, so do not rely on its concrete integer.
*
@@ -53,6 +67,8 @@ G_BEGIN_DECLS
* CAIRO_FORMAT_ARGB32 is represented by different `GdkMemoryFormats`
* on architectures with different endiannesses.
*
* Note that color data is assumed to be linear.
*
* Its naming is modelled after
* [VkFormat](https://www.khronos.org/registry/vulkan/specs/1.0/html/vkspec.html#VkFormat)
* for details).
@@ -67,6 +83,12 @@ typedef enum {
GDK_MEMORY_A8B8G8R8,
GDK_MEMORY_R8G8B8,
GDK_MEMORY_B8G8R8,
GDK_MEMORY_R16G16B16,
GDK_MEMORY_R16G16B16A16_PREMULTIPLIED,
GDK_MEMORY_R16G16B16_FLOAT,
GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED,
GDK_MEMORY_R32G32B32_FLOAT,
GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED,
GDK_MEMORY_N_FORMATS
} GdkMemoryFormat;

489
gdk/gdkpng.c Normal file
View File

@@ -0,0 +1,489 @@
/* 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 "gdkpng.h"
#include "gdktexture.h"
#include "gdkmemorytextureprivate.h"
#include "gsk/ngl/fp16private.h"
#include <png.h>
#include <stdio.h>
/* {{{ IO handling */
#ifdef HAVE_FOPENCOOKIE
static ssize_t
read_stream (void *cookie,
char *buf,
size_t size)
{
GInputStream *stream = cookie;
return g_input_stream_read (stream, buf, size, NULL, NULL);
}
static ssize_t
write_stream (void *cookie,
const char *buf,
size_t size)
{
GOutputStream *stream = cookie;
return g_output_stream_write (stream, buf, size, NULL, NULL);
}
static int
seek_stream (void *cookie,
off64_t *offset,
int whence)
{
GSeekable *seekable = cookie;
GSeekType seek_type;
if (whence == SEEK_SET)
seek_type = G_SEEK_SET;
else if (whence == SEEK_CUR)
seek_type = G_SEEK_CUR;
else if (whence == SEEK_END)
seek_type = G_SEEK_END;
else
g_assert_not_reached ();
if (g_seekable_seek (seekable, *offset, seek_type, NULL, NULL))
{
*offset = g_seekable_tell (seekable);
return 0;
}
return -1;
}
static int
close_stream (void *cookie)
{
GObject *stream = cookie;
g_object_unref (stream);
return 0;
}
static cookie_io_functions_t cookie_funcs = {
read_stream,
write_stream,
seek_stream,
close_stream
};
#else
static GBytes *
read_all_data (GInputStream *source,
GError **error)
{
GOutputStream *output;
gssize size;
GBytes *bytes;
output = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
size = g_output_stream_splice (output, source, 0, NULL, error);
if (size == -1)
{
g_object_unref (output);
return NULL;
}
g_output_stream_close (output, NULL, NULL);
bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (output));
g_object_unref (output);
return bytes;
}
#endif
/* }}} */
/* {{{ Format conversion */
static void
convert_half_float (guchar *dest_data,
gsize dest_stride,
GdkMemoryFormat dest_format,
const guchar *src_data,
gsize src_stride,
GdkMemoryFormat src_format,
gsize width,
gsize height)
{
gsize x, y;
guint16 *dest;
const guint16 *src;
float *c;
c = g_malloc (width * 4 * sizeof (float));
for (y = 0; y < height; y++)
{
dest = (guint16 *)dest_data;
src = (const guint16 *)src_data;
half_to_float (src, c, width);
for (x = 0; x < width; x++)
{
dest[4 * x ] = (guint16)(65535 * c[4 * x ]);
dest[4 * x + 1] = (guint16)(65535 * c[4 * x + 1]);
dest[4 * x + 2] = (guint16)(65535 * c[4 * x + 2]);
if (src_format == GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED)
dest[4 * x + 3] = (guint16)(65535 * c[4 * x + 3]);
else
dest[4 * x + 3] = 65535;
}
dest_data += dest_stride;
src_data += src_stride;
}
g_free (c);
}
static void
convert_float (guchar *dest_data,
gsize dest_stride,
GdkMemoryFormat dest_format,
const guchar *src_data,
gsize src_stride,
GdkMemoryFormat src_format,
gsize width,
gsize height)
{
gsize x, y;
guint16 *dest;
const float *src;
for (y = 0; y < height; y++)
{
dest = (guint16 *)dest_data;
src = (const float *)src_data;
for (x = 0; x < width; x++)
{
dest[4 * x ] = (guint16)(65535 * src[4 * x ]);
dest[4 * x + 1] = (guint16)(65535 * src[4 * x + 1]);
dest[4 * x + 2] = (guint16)(65535 * src[4 * x + 2]);
if (src_format == GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED)
dest[4 * x + 3] = (guint16)(65535 * src[4 * x + 3]);
else
dest[4 * x + 3] = 65535;
}
dest_data += dest_stride;
src_data += src_stride;
}
}
static void
convert (guchar *dest_data,
gsize dest_stride,
GdkMemoryFormat dest_format,
const guchar *src_data,
gsize src_stride,
GdkMemoryFormat src_format,
gsize width,
gsize height)
{
if (dest_format < 3)
gdk_memory_convert (dest_data, dest_stride, dest_format,
src_data, src_stride, src_format,
width, height);
else
{
g_assert (dest_format == GDK_MEMORY_R16G16B16A16_PREMULTIPLIED);
switch ((int)src_format)
{
case GDK_MEMORY_R16G16B16_FLOAT:
case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED:
convert_half_float (dest_data, dest_stride, dest_format,
src_data, src_stride, src_format,
width, height);
break;
case GDK_MEMORY_R32G32B32_FLOAT:
case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED:
convert_float (dest_data, dest_stride, dest_format,
src_data, src_stride, src_format,
width, height);
break;
default:
g_assert_not_reached ();
}
}
}
/* }}} */
/* {{{ Public API */
GdkTexture *
gdk_load_png (GInputStream *stream,
GError **error)
{
png_image image = { NULL, PNG_IMAGE_VERSION, 0, };
gsize size;
gsize stride;
guint16 *buffer;
GBytes *bytes;
GdkTexture *texture;
#ifdef HAVE_FOPENCOOKIE
FILE *file = fopencookie (g_object_ref (stream), "r", cookie_funcs);
png_image_begin_read_from_stdio (&image, file);
#else
GBytes *data;
data = read_all_data (stream, error);
if (!data)
return NULL;
png_image_begin_read_from_memory (&image,
g_bytes_get_data (data, NULL),
g_bytes_get_size (data));
#endif
image.format = PNG_FORMAT_LINEAR_RGB_ALPHA;
stride = PNG_IMAGE_ROW_STRIDE (image);
size = PNG_IMAGE_BUFFER_SIZE (image, stride);
buffer = g_malloc (size);
png_image_finish_read (&image, NULL, buffer, stride, NULL);
#ifdef HAVE_FOPENCOOKIE
fclose (file);
#else
g_bytes_unref (data);
#endif
bytes = g_bytes_new_take (buffer, size);
texture = gdk_memory_texture_new (image.width, image.height,
GDK_MEMORY_R16G16B16A16_PREMULTIPLIED,
bytes, 2 * stride);
g_bytes_unref (bytes);
png_image_free (&image);
return texture;
}
gboolean
gdk_save_png (GOutputStream *stream,
const guchar *data,
int width,
int height,
int stride,
GdkMemoryFormat format,
GError **error)
{
png_image image = { NULL, PNG_IMAGE_VERSION, 0, };
gboolean result;
guchar *new_data = NULL;
switch ((int)format)
{
case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED:
image.format = PNG_FORMAT_RGBA;
break;
case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED:
case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED:
case GDK_MEMORY_B8G8R8A8:
case GDK_MEMORY_A8R8G8B8:
case GDK_MEMORY_R8G8B8A8:
case GDK_MEMORY_A8B8G8R8:
case GDK_MEMORY_R8G8B8:
case GDK_MEMORY_B8G8R8:
stride = width * 4;
new_data = g_malloc (stride * height);
convert (new_data, stride, GDK_MEMORY_R8G8B8A8_PREMULTIPLIED,
data, width * gdk_memory_format_bytes_per_pixel (format), format,
width, height);
data = new_data;
image.format = PNG_FORMAT_RGBA;
break;
case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED:
image.format = PNG_FORMAT_LINEAR_RGB_ALPHA;
break;
case GDK_MEMORY_R16G16B16:
image.format = PNG_FORMAT_LINEAR_RGB;
break;
case GDK_MEMORY_R16G16B16_FLOAT:
case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED:
case GDK_MEMORY_R32G32B32_FLOAT:
case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED:
stride = width * 8;
new_data = g_malloc (stride * height);
convert (new_data, stride, GDK_MEMORY_R16G16B16A16_PREMULTIPLIED,
data, width * gdk_memory_format_bytes_per_pixel (format), format,
width, height);
image.format = PNG_FORMAT_LINEAR_RGB_ALPHA;
break;
break;
default:
g_assert_not_reached ();
}
if (image.format & PNG_FORMAT_FLAG_LINEAR)
stride /= 2;
#ifdef HAVE_FOPENCOOKIE
FILE *file = fopencookie (g_object_ref (stream), "w", cookie_funcs);
result = png_image_write_to_stdio (&image, file, FALSE, data, stride, NULL);
fclose (file);
#else
gsize written;
png_alloc_size_t size;
gpointer buffer;
png_image_write_get_memory_size (image, size, FALSE, data, stride, NULL);
buffer = g_malloc (size);
result = png_image_write_to_memory (&image, buffer, &size, FALSE, data, stride, NULL);
if (result)
result = g_output_stream_write_all (stream, buffer, (gsize)size, &written, NULL, NULL);
g_free (buffer);
#endif
if (!result)
{
g_set_error_literal (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
"Saving png failed");
}
png_image_free (&image);
g_free (new_data);
return result;
}
/* }}} */
/* {{{ Async code */
static void
load_png_in_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
GInputStream *stream = source_object;
GdkTexture *texture;
GError *error = NULL;
texture = gdk_load_png (stream, &error);
if (texture)
g_task_return_pointer (task, texture, g_object_unref);
else
g_task_return_error (task, error);
}
void
gdk_load_png_async (GInputStream *stream,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (stream, cancellable, callback, user_data);
g_task_run_in_thread (task, load_png_in_thread);
g_object_unref (task);
}
GdkTexture *
gdk_load_png_finish (GAsyncResult *result,
GError **error)
{
return g_task_propagate_pointer (G_TASK (result), error);
}
typedef struct {
const guchar *data;
int width;
int height;
int stride;
GdkMemoryFormat format;
} SavePngData;
static void
save_png_in_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
GOutputStream *stream = source_object;
SavePngData *data = task_data;
GError *error = NULL;
gboolean result;
result = gdk_save_png (stream,
data->data,
data->width,
data->height,
data->stride,
data->format,
&error);
if (result)
g_task_return_boolean (task, result);
else
g_task_return_error (task, error);
}
void
gdk_save_png_async (GOutputStream *stream,
const guchar *data,
int width,
int height,
int stride,
GdkMemoryFormat format,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
SavePngData *save_data;
save_data = g_new0 (SavePngData, 1);
save_data->data = data;
save_data->width = width;
save_data->height = height;
save_data->stride = stride;
save_data->format = format;
task = g_task_new (stream, cancellable, callback, user_data);
g_task_set_task_data (task, save_data, g_free);
g_task_run_in_thread (task, save_png_in_thread);
g_object_unref (task);
}
gboolean
gdk_save_png_finish (GAsyncResult *result,
GError **error)
{
return g_task_propagate_boolean (G_TASK (result), error);
}
/* }}} */
/* vim:set foldmethod=marker expandtab: */

56
gdk/gdkpng.h Normal file
View File

@@ -0,0 +1,56 @@
/* 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/>.
*/
#ifndef __GDK_PNG_H__
#define __GDK_PNG_H__
#include "gdkmemorytexture.h"
#include <gio/gio.h>
GdkTexture *gdk_load_png (GInputStream *stream,
GError **error);
void gdk_load_png_async (GInputStream *stream,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GdkTexture *gdk_load_png_finish (GAsyncResult *result,
GError **error);
gboolean gdk_save_png (GOutputStream *stream,
const guchar *data,
int width,
int height,
int stride,
GdkMemoryFormat format,
GError **error);
void gdk_save_png_async (GOutputStream *stream,
const guchar *data,
int width,
int height,
int stride,
GdkMemoryFormat format,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean gdk_save_png_finish (GAsyncResult *result,
GError **error);
#endif

View File

@@ -46,6 +46,9 @@
#include "gdksnapshot.h"
#include <graphene.h>
#include "gdkpng.h"
#include "gdktiff.h"
#include "gdkjpeg.h"
/* HACK: So we don't need to include any (not-yet-created) GSK or GTK headers */
void
@@ -124,6 +127,13 @@ gdk_texture_real_download (GdkTexture *self,
GDK_TEXTURE_WARN_NOT_IMPLEMENTED_METHOD (self, download);
}
static GBytes *
gdk_texture_real_download_format (GdkTexture *self,
GdkMemoryFormat format)
{
return NULL;
}
static void
gdk_texture_set_property (GObject *gobject,
guint prop_id,
@@ -188,6 +198,7 @@ gdk_texture_class_init (GdkTextureClass *klass)
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
klass->download = gdk_texture_real_download;
klass->download_format = gdk_texture_real_download_format;
gobject_class->set_property = gdk_texture_set_property;
gobject_class->get_property = gdk_texture_get_property;
@@ -351,6 +362,10 @@ gdk_texture_new_from_resource (const char *resource_path)
* The file format is detected automatically. The supported formats
* are PNG and JPEG, though more formats might be available.
*
* For PNG files, this function supports conversion from SRGB to
* linear data (including gamma correction) and can load 16bit
* data.
*
* If %NULL is returned, then @error will be set.
*
* Return value: A newly-created `GdkTexture`
@@ -362,6 +377,9 @@ gdk_texture_new_from_file (GFile *file,
GdkTexture *texture;
GdkPixbuf *pixbuf;
GInputStream *stream;
GInputStream *buffered;
const void *data;
gsize size;
g_return_val_if_fail (G_IS_FILE (file), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
@@ -370,6 +388,35 @@ gdk_texture_new_from_file (GFile *file,
if (stream == NULL)
return NULL;
buffered = g_buffered_input_stream_new (stream);
g_object_unref (stream);
stream = buffered;
g_buffered_input_stream_fill (G_BUFFERED_INPUT_STREAM (stream), 8, NULL, NULL);
data = g_buffered_input_stream_peek_buffer (G_BUFFERED_INPUT_STREAM (stream), &size);
if (memcmp (data, "\x89PNG", 4) == 0)
{
texture = gdk_load_png (stream, error);
g_object_unref (stream);
return texture;
}
if (memcmp (data, "MM\x00\x2a", 4) == 0 ||
memcmp (data, "II\x2a\x00", 4) == 0)
{
texture = gdk_load_tiff (stream, error);
g_object_unref (stream);
return texture;
}
if (memcmp (data, "\xff\xd8", 2) == 0)
{
texture = gdk_load_jpeg (stream, error);
g_object_unref (stream);
return texture;
}
pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, error);
g_object_unref (stream);
if (pixbuf == NULL)
@@ -491,6 +538,95 @@ gdk_texture_download (GdkTexture *texture,
stride);
}
/* Returns the texture data in the requested format if that
* can be done without conversion. NULL, otherwise.
*/
GBytes *
gdk_texture_download_format (GdkTexture *texture,
GdkMemoryFormat format)
{
return GDK_TEXTURE_GET_CLASS (texture)->download_format (texture, format);
}
/* Returns the texture data in the requested format, converting
* it if necessary. This will only return NULL if we don't know
* how to convert from the texture's format to the requested one.
*/
GBytes *
gdk_texture_convert_format (GdkTexture *texture,
GdkMemoryFormat format)
{
GdkMemoryFormat src_format;
GBytes *bytes;
int width, height, stride;
guchar *data;
for (int i = 0; i < GDK_MEMORY_N_FORMATS; i++)
{
bytes = gdk_texture_download_format (texture, i);
if (bytes)
{
src_format = i;
break;
}
}
if (!bytes || src_format == format)
return bytes;
/* convert from src_format to format */
width = gdk_texture_get_width (texture);
height = gdk_texture_get_height (texture);
stride = width * gdk_memory_format_bytes_per_pixel (format);
data = g_malloc (height * stride);
gdk_memory_convert (data, stride, format,
g_bytes_get_data (bytes, NULL),
g_bytes_get_size (bytes) / height,
src_format,
width, height);
g_bytes_unref (bytes);
return g_bytes_new_take (data, height * stride);
}
/*
* gdk_texture_download_float:
* @texture: a `GdkTexture`
* @data: (array): pointer to enough memory to be filled with the
* downloaded data of @texture
*
* Downloads the @texture into local memory.
*
* This may be an expensive operation, as the actual texture data
* may reside on a GPU or on a remote display server.
*
* The data format of the downloaded data is equivalent to
* GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED, so every downloaded
* pixel requires 16 bytes of memory.
*
* Note that the caller is responsible to provide sufficiently
* aligned memory to access the resulting data directly as floats.
*
* Since: 4.6
*/
void
gdk_texture_download_float (GdkTexture *texture,
float *data)
{
GBytes *bytes;
bytes = gdk_texture_convert_format (texture, GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED);
g_assert (bytes);
memcpy (data, g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes));
g_bytes_unref (bytes);
}
gboolean
gdk_texture_set_render_data (GdkTexture *self,
gpointer key,
@@ -537,6 +673,9 @@ gdk_texture_get_render_data (GdkTexture *self,
*
* Store the given @texture to the @filename as a PNG file.
*
* If the texture contains 16bit data, the generated PNG file
* will have linear 16bit data, otherwise it will contain SRGB.
*
* This is a utility function intended for debugging and testing.
* If you want more control over formats, proper error handling or
* want to store to a `GFile` or other location, you might want to
@@ -548,30 +687,121 @@ gboolean
gdk_texture_save_to_png (GdkTexture *texture,
const char *filename)
{
cairo_surface_t *surface;
cairo_status_t status;
int width, height, stride;
GBytes *bytes;
GdkMemoryFormat format;
gboolean result;
GFile *file;
GOutputStream *stream;
g_return_val_if_fail (GDK_IS_TEXTURE (texture), FALSE);
g_return_val_if_fail (filename != NULL, FALSE);
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
gdk_texture_get_width (texture),
gdk_texture_get_height (texture));
gdk_texture_download (texture,
cairo_image_surface_get_data (surface),
cairo_image_surface_get_stride (surface));
cairo_surface_mark_dirty (surface);
width = gdk_texture_get_width (texture);
height = gdk_texture_get_height (texture);
status = cairo_surface_write_to_png (surface, filename);
if (status != CAIRO_STATUS_SUCCESS ||
cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)
result = FALSE;
bytes = gdk_texture_download_format (texture, GDK_MEMORY_R16G16B16A16_PREMULTIPLIED);
if (bytes)
{
format = GDK_MEMORY_R16G16B16A16_PREMULTIPLIED;
stride = width * 8;
}
else
result = TRUE;
{
gpointer data;
cairo_surface_destroy (surface);
stride = width * 4;
data = g_malloc (stride * height);
gdk_texture_download (texture, data, stride);
bytes = g_bytes_new_take (data, stride * height);
format = GDK_MEMORY_DEFAULT;
}
file = g_file_new_for_path (filename);
stream = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
G_FILE_CREATE_NONE,
NULL, NULL));
g_object_unref (file);
result = gdk_save_png (stream,
g_bytes_get_data (bytes, NULL),
width, height, stride,
format,
NULL);
g_output_stream_close (stream, NULL, NULL);
g_object_unref (stream);
g_bytes_unref (bytes);
return result;
}
/**
* gdk_texture_save_to_file:
* @texture: a `GdkTexture`
* @filename: (type filename): the filename to store to
*
* Store the given @texture to the @filename.
*
* GTK will choose a suitable file format to save the data in
* depending on the format of the texture. Currently, that is
* tiff, for all kinds of textures.
*
* This is a utility function intended for debugging and testing.
* If you want more control over formats, proper error handling or
* want to store to a `GFile` or other location, you might want to
* look into using the gdk-pixbuf library.
*
* Returns: %TRUE if saving succeeded, %FALSE on failure.
*
* Since: 4.6
*/
gboolean
gdk_texture_save_to_file (GdkTexture *texture,
const char *filename)
{
GBytes *bytes = NULL;
gboolean result;
GdkMemoryFormat format;
GFile *file;
GOutputStream *stream;
g_return_val_if_fail (GDK_IS_TEXTURE (texture), FALSE);
g_return_val_if_fail (filename != NULL, FALSE);
for (int i = 0; i < GDK_MEMORY_N_FORMATS; i++)
{
bytes = gdk_texture_download_format (texture, i);
if (bytes)
{
format = i;
break;
}
}
if (!bytes)
return FALSE;
file = g_file_new_for_path (filename);
stream = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
G_FILE_CREATE_NONE,
NULL, NULL));
g_object_unref (file);
result = gdk_save_tiff (stream,
g_bytes_get_data (bytes, NULL),
gdk_texture_get_width (texture),
gdk_texture_get_height (texture),
gdk_texture_get_width (texture) * gdk_memory_format_bytes_per_pixel (format),
format,
NULL);
g_clear_object (&stream);
g_clear_pointer (&bytes, g_bytes_unref);
return result;
}

View File

@@ -59,9 +59,15 @@ GDK_AVAILABLE_IN_ALL
void gdk_texture_download (GdkTexture *texture,
guchar *data,
gsize stride);
GDK_AVAILABLE_IN_4_6
void gdk_texture_download_float (GdkTexture *texture,
float *data);
GDK_AVAILABLE_IN_ALL
gboolean gdk_texture_save_to_png (GdkTexture *texture,
const char *filename);
GDK_AVAILABLE_IN_4_6
gboolean gdk_texture_save_to_file (GdkTexture *texture,
const char *filename);
G_END_DECLS

View File

@@ -2,6 +2,7 @@
#define __GDK_TEXTURE_PRIVATE_H__
#include "gdktexture.h"
#include "gdkmemorytexture.h"
G_BEGIN_DECLS
@@ -28,6 +29,8 @@ struct _GdkTextureClass {
const GdkRectangle *area,
guchar *data,
gsize stride);
GBytes * (* download_format) (GdkTexture *texture,
GdkMemoryFormat format);
};
gpointer gdk_texture_new (const GdkTextureClass *klass,
@@ -48,6 +51,11 @@ void gdk_texture_clear_render_data (GdkTexture
gpointer gdk_texture_get_render_data (GdkTexture *self,
gpointer key);
GBytes * gdk_texture_download_format (GdkTexture *texture,
GdkMemoryFormat format);
GBytes * gdk_texture_convert_format (GdkTexture *texture,
GdkMemoryFormat format);
G_END_DECLS
#endif /* __GDK_TEXTURE_PRIVATE_H__ */

628
gdk/gdktiff.c Normal file
View File

@@ -0,0 +1,628 @@
/* 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 "gdktiff.h"
#include "gdktexture.h"
#include "gdkmemorytextureprivate.h"
#include <tiffio.h>
/* {{{ IO handling */
typedef struct
{
GObject *stream;
GInputStream *input;
GOutputStream *output;
gchar *buffer;
gsize allocated;
gsize used;
gsize position;
} TiffIO;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
static void
tiff_io_warning (const char *module,
const char *fmt,
va_list ap)
{
g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, ap);
}
static void
tiff_io_error (const char *module,
const char *fmt,
va_list ap)
{
g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, ap);
}
#pragma GCC diagnostic pop
static tsize_t
tiff_io_read (thandle_t handle,
tdata_t buffer,
tsize_t size)
{
TiffIO *io = (TiffIO *) handle;
GError *error = NULL;
gssize read = -1;
gsize bytes_read = 0;
if (! g_input_stream_read_all (io->input,
(void *) buffer, (gsize) size,
&bytes_read,
NULL, &error))
{
g_printerr ("%s", error->message);
g_clear_error (&error);
}
read = bytes_read;
return (tsize_t) read;
}
static tsize_t
tiff_io_write (thandle_t handle,
tdata_t buffer,
tsize_t size)
{
TiffIO *io = (TiffIO *) handle;
GError *error = NULL;
gssize written = -1;
gsize bytes_written = 0;
if (! g_output_stream_write_all (io->output,
(void *) buffer, (gsize) size,
&bytes_written,
NULL, &error))
{
g_printerr ("%s", error->message);
g_clear_error (&error);
}
written = bytes_written;
return (tsize_t) written;
}
static GSeekType
lseek_to_seek_type (int whence)
{
switch (whence)
{
default:
case SEEK_SET:
return G_SEEK_SET;
case SEEK_CUR:
return G_SEEK_CUR;
case SEEK_END:
return G_SEEK_END;
}
}
static toff_t
tiff_io_seek (thandle_t handle,
toff_t offset,
int whence)
{
TiffIO *io = (TiffIO *) handle;
GError *error = NULL;
gboolean sought = FALSE;
goffset position = -1;
sought = g_seekable_seek (G_SEEKABLE (io->stream),
(goffset) offset, lseek_to_seek_type (whence),
NULL, &error);
if (sought)
{
position = g_seekable_tell (G_SEEKABLE (io->stream));
}
else
{
g_printerr ("%s", error->message);
g_clear_error (&error);
}
return (toff_t) position;
}
static int
tiff_io_close (thandle_t handle)
{
TiffIO *io = (TiffIO *) handle;
GError *error = NULL;
gboolean closed = FALSE;
if (io->input)
closed = g_input_stream_close (io->input, NULL, &error);
else if (io->output)
closed = g_output_stream_close (io->output, NULL, &error);
if (!closed)
{
g_printerr ("%s", error->message);
g_clear_error (&error);
}
g_clear_object (&io->stream);
io->input = NULL;
io->output = NULL;
g_clear_pointer (&io->buffer, g_free);
io->allocated = 0;
io->used = 0;
io->position = 0;
g_free (io);
return closed ? 0 : -1;
}
static goffset
input_stream_query_size (GInputStream *stream)
{
goffset size = 0;
while (G_IS_FILTER_INPUT_STREAM (stream))
stream = g_filter_input_stream_get_base_stream (G_FILTER_INPUT_STREAM (stream));
if (G_IS_FILE_INPUT_STREAM (stream))
{
GFileInfo *info;
info = g_file_input_stream_query_info (G_FILE_INPUT_STREAM (stream),
G_FILE_ATTRIBUTE_STANDARD_SIZE,
NULL,
NULL);
if (info)
{
size = g_file_info_get_size (info);
g_object_unref (info);
}
}
return size;
}
static goffset
output_stream_query_size (GOutputStream *stream)
{
goffset size = 0;
while (G_IS_FILTER_OUTPUT_STREAM (stream))
stream = g_filter_output_stream_get_base_stream (G_FILTER_OUTPUT_STREAM (stream));
if (G_IS_FILE_OUTPUT_STREAM (stream))
{
GFileInfo *info;
info = g_file_output_stream_query_info (G_FILE_OUTPUT_STREAM (stream),
G_FILE_ATTRIBUTE_STANDARD_SIZE,
NULL,
NULL);
if (info)
{
size = g_file_info_get_size (info);
g_object_unref (info);
}
}
return size;
}
static toff_t
tiff_io_get_file_size (thandle_t handle)
{
TiffIO *io = (TiffIO *) handle;
if (io->input)
return (toff_t) input_stream_query_size (io->input);
else if (io->output)
return (toff_t) output_stream_query_size (io->output);
return (toff_t) 0;
}
static TIFF *
tiff_open (gpointer stream,
const gchar *mode,
GError **error)
{
TIFFSetWarningHandler ((TIFFErrorHandler) tiff_io_warning);
TIFFSetErrorHandler ((TIFFErrorHandler) tiff_io_error);
TiffIO *io;
io = g_new0 (TiffIO, 1);
if (strcmp (mode, "r") == 0)
{
io->input = G_INPUT_STREAM (stream);
io->stream = g_object_ref (G_OBJECT (stream));
}
else if (strcmp (mode, "w") == 0)
{
io->output = G_OUTPUT_STREAM (stream);
io->stream = g_object_ref (G_OBJECT (stream));
}
else
{
g_assert_not_reached ();
}
return TIFFClientOpen ("GTK", mode,
(thandle_t) io,
tiff_io_read,
tiff_io_write,
tiff_io_seek,
tiff_io_close,
tiff_io_get_file_size,
NULL, NULL);
}
/* }}} */
/* {{{ Public API */
static struct {
GdkMemoryFormat format;
guint16 bits_per_sample;
guint16 samples_per_pixel;
guint16 sample_format;
} format_data[] = {
{ GDK_MEMORY_DEFAULT, 8, 4, SAMPLEFORMAT_UINT },
{ GDK_MEMORY_R8G8B8, 8, 3, SAMPLEFORMAT_UINT },
{ GDK_MEMORY_R16G16B16A16_PREMULTIPLIED, 16, 4, SAMPLEFORMAT_UINT },
{ GDK_MEMORY_R16G16B16_FLOAT, 16, 3, SAMPLEFORMAT_IEEEFP },
{ GDK_MEMORY_R32G32B32_FLOAT, 32, 3, SAMPLEFORMAT_IEEEFP },
};
gboolean
gdk_save_tiff (GOutputStream *stream,
const guchar *data,
int width,
int height,
int stride,
GdkMemoryFormat format,
GError **error)
{
TIFF *tif;
guint16 bits_per_sample = 0;
guint16 samples_per_pixel = 0;
guint16 sample_format = 0;
const guchar *line;
tif = tiff_open (stream, "w", error);
if (!tif)
return FALSE;
for (int i = 0; i < G_N_ELEMENTS (format_data); i++)
{
if (format == format_data[i].format)
{
bits_per_sample = format_data[i].bits_per_sample;
samples_per_pixel = format_data[i].samples_per_pixel;
sample_format = format_data[i].sample_format;
break;
}
}
g_assert (bits_per_sample != 0);
TIFFSetField (tif, TIFFTAG_SOFTWARE, "GTK");
TIFFSetField (tif, TIFFTAG_IMAGEWIDTH, width);
TIFFSetField (tif, TIFFTAG_IMAGELENGTH, height);
TIFFSetField (tif, TIFFTAG_BITSPERSAMPLE, bits_per_sample);
TIFFSetField (tif, TIFFTAG_SAMPLESPERPIXEL, samples_per_pixel);
TIFFSetField (tif, TIFFTAG_SAMPLEFORMAT, sample_format);
TIFFSetField (tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
TIFFSetField (tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
// TODO: save gamma / colorspace
if (samples_per_pixel > 3)
{
guint16 extra_samples[] = { EXTRASAMPLE_ASSOCALPHA };
TIFFSetField (tif, TIFFTAG_EXTRASAMPLES, 1, extra_samples);
}
TIFFSetField (tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
TIFFSetField (tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
line = (const guchar *)data;
for (int y = 0; y < height; y++)
{
if (TIFFWriteScanline (tif, (void *)line, y, 0) == -1)
{
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
"Writing data failed at row %d", y);
TIFFClose (tif);
return FALSE;
}
line += stride;
}
TIFFFlushData (tif);
TIFFClose (tif);
return TRUE;
}
static GdkTexture *
load_fallback (TIFF *tif,
GError **error)
{
int width, height;
guchar *pixels;
GBytes *bytes;
GdkTexture *texture;
TIFFGetField (tif, TIFFTAG_IMAGEWIDTH, &width);
TIFFGetField (tif, TIFFTAG_IMAGELENGTH, &height);
pixels = g_malloc (width * height * 4);
if (!TIFFReadRGBAImageOriented (tif, width, height, (uint32 *)pixels, ORIENTATION_TOPLEFT, 1))
{
g_set_error_literal (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to load RGB data from TIFF file");
g_free (pixels);
return NULL;
}
bytes = g_bytes_new_take (pixels, width * height * 4);
texture = gdk_memory_texture_new (width, height,
GDK_MEMORY_R8G8B8A8_PREMULTIPLIED,
bytes,
width * 4);
g_bytes_unref (bytes);
return texture;
}
/* This isn't meant to be a very versatile tiff loader.
* It just aims to load the subset that we're saving
* ourselves, above.
*/
GdkTexture *
gdk_load_tiff (GInputStream *stream,
GError **error)
{
TIFF *tif;
guint16 samples_per_pixel;
guint16 bits_per_sample;
guint16 photometric;
guint16 planarconfig;
guint16 sample_format;
guint16 orientation;
guint32 width, height;
GdkMemoryFormat format;
guchar *data, *line;
gsize stride;
int bpp;
GBytes *bytes;
GdkTexture *texture;
tif = tiff_open (stream, "r", error);
if (!tif)
return NULL;
TIFFSetDirectory (tif, 0);
TIFFGetFieldDefaulted (tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel);
TIFFGetFieldDefaulted (tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
TIFFGetFieldDefaulted (tif, TIFFTAG_SAMPLEFORMAT, &sample_format);
TIFFGetFieldDefaulted (tif, TIFFTAG_PHOTOMETRIC, &photometric);
TIFFGetFieldDefaulted (tif, TIFFTAG_PLANARCONFIG, &planarconfig);
TIFFGetFieldDefaulted (tif, TIFFTAG_ORIENTATION, &orientation);
TIFFGetFieldDefaulted (tif, TIFFTAG_IMAGEWIDTH, &width);
TIFFGetFieldDefaulted (tif, TIFFTAG_IMAGELENGTH, &height);
if (samples_per_pixel == 4)
{
guint16 extra;
guint16 *extra_types;
if (!TIFFGetField (tif, TIFFTAG_EXTRASAMPLES, &extra, &extra_types))
extra = 0;
if (extra == 0 || extra_types[0] != EXTRASAMPLE_ASSOCALPHA)
{
texture = load_fallback (tif, error);
TIFFClose (tif);
return texture;
}
}
format = 0;
for (int i = 0; i < G_N_ELEMENTS (format_data); i++)
{
if (format_data[i].sample_format == sample_format &&
format_data[i].bits_per_sample == bits_per_sample &&
format_data[i].samples_per_pixel == samples_per_pixel)
{
format = format_data[i].format;
break;
}
}
if (format == 0 ||
photometric != PHOTOMETRIC_RGB ||
planarconfig != PLANARCONFIG_CONTIG ||
TIFFIsTiled (tif) ||
orientation != ORIENTATION_TOPLEFT)
{
texture = load_fallback (tif, error);
TIFFClose (tif);
return texture;
}
stride = width * gdk_memory_format_bytes_per_pixel (format);
g_assert (TIFFScanlineSize (tif) == stride);
data = g_new (guchar, height * stride);
line = data;
for (int y = 0; y < height; y++)
{
if (TIFFReadScanline (tif, line, y, 0) == -1)
{
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
"Reading data failed at row %d", y);
TIFFClose (tif);
g_free (data);
return NULL;
}
line += stride;
}
bpp = gdk_memory_format_bytes_per_pixel (format);
bytes = g_bytes_new_take (data, width * height * bpp);
texture = gdk_memory_texture_new (width, height,
format,
bytes, width * bpp);
g_bytes_unref (bytes);
return texture;
}
/* }}} */
/* {{{ Async code */
static void
load_tiff_in_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
GInputStream *stream = source_object;
GdkTexture *texture;
GError *error = NULL;
texture = gdk_load_tiff (stream, &error);
if (texture)
g_task_return_pointer (task, texture, g_object_unref);
else
g_task_return_error (task, error);
}
void
gdk_load_tiff_async (GInputStream *stream,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (stream, cancellable, callback, user_data);
g_task_run_in_thread (task, load_tiff_in_thread);
g_object_unref (task);
}
GdkTexture *
gdk_load_tiff_finish (GAsyncResult *result,
GError **error)
{
return g_task_propagate_pointer (G_TASK (result), error);
}
typedef struct {
const guchar *data;
int width;
int height;
int stride;
GdkMemoryFormat format;
} SaveTiffData;
static void
save_tiff_in_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
GOutputStream *stream = source_object;
SaveTiffData *data = task_data;
GError *error = NULL;
gboolean result;
result = gdk_save_tiff (stream,
data->data,
data->width,
data->height,
data->stride,
data->format,
&error);
if (result)
g_task_return_boolean (task, result);
else
g_task_return_error (task, error);
}
void
gdk_save_tiff_async (GOutputStream *stream,
const guchar *data,
int width,
int height,
int stride,
GdkMemoryFormat format,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
SaveTiffData *save_data;
save_data = g_new0 (SaveTiffData, 1);
save_data->data = data;
save_data->width = width;
save_data->height = height;
save_data->stride = stride;
save_data->format = format;
task = g_task_new (stream, cancellable, callback, user_data);
g_task_set_task_data (task, save_data, g_free);
g_task_run_in_thread (task, save_tiff_in_thread);
g_object_unref (task);
}
gboolean
gdk_save_tiff_finish (GAsyncResult *result,
GError **error)
{
return g_task_propagate_boolean (G_TASK (result), error);
}
/* }}} */
/* vim:set foldmethod=marker expandtab: */

56
gdk/gdktiff.h Normal file
View File

@@ -0,0 +1,56 @@
/* 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/>.
*/
#ifndef __GDK_TIFF_H__
#define __GDK_TIFF_H__
#include "gdkmemorytexture.h"
#include <gio/gio.h>
GdkTexture *gdk_load_tiff (GInputStream *stream,
GError **error);
void gdk_load_tiff_async (GInputStream *stream,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GdkTexture *gdk_load_tiff_finish (GAsyncResult *result,
GError **error);
gboolean gdk_save_tiff (GOutputStream *stream,
const guchar *data,
int width,
int height,
int stride,
GdkMemoryFormat format,
GError **error);
void gdk_save_tiff_async (GOutputStream *stream,
const guchar *data,
int width,
int height,
int stride,
GdkMemoryFormat format,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean gdk_save_tiff_finish (GAsyncResult *result,
GError **error);
#endif

View File

@@ -50,6 +50,9 @@ gdk_public_sources = files([
'gdktoplevelsize.c',
'gdktoplevel.c',
'gdkdragsurface.c',
'gdkpng.c',
'gdktiff.c',
'gdkjpeg.c',
])
gdk_public_headers = files([
@@ -254,8 +257,8 @@ endif
libgdk = static_library('gdk',
sources: [gdk_sources, gdk_backends_gen_headers, gdkconfig],
dependencies: gdk_deps + [libgtk_css_dep],
link_with: [libgtk_css, ],
dependencies: gdk_deps + [libgtk_css_dep, png_dep, tiff_dep, jpeg_dep],
link_with: [libgtk_css],
include_directories: [confinc, gdkx11_inc, wlinc],
c_args: libgdk_c_args + common_cflags,
link_whole: gdk_backends,

View File

@@ -199,6 +199,7 @@ check_functions = [
'mallinfo2',
'sincos',
'sincosf',
'fopencookie',
]
foreach func : check_functions
@@ -389,6 +390,9 @@ pangocairo_dep = dependency('pangocairo', version: pango_req,
pixbuf_dep = dependency('gdk-pixbuf-2.0', version: gdk_pixbuf_req,
fallback : ['gdk-pixbuf', 'gdkpixbuf_dep'],
default_options: ['png=enabled', 'jpeg=enabled', 'builtin_loaders=png,jpeg', 'man=false'])
png_dep = dependency('libpng16')
tiff_dep = dependency('libtiff-4')
jpeg_dep = dependency('libjpeg')
epoxy_dep = dependency('epoxy', version: epoxy_req,
fallback: ['libepoxy', 'libepoxy_dep'])
harfbuzz_dep = dependency('harfbuzz', version: '>= 2.1.0', required: false,

View File

@@ -2,7 +2,7 @@
#include <gdk/gdk.h>
/* maximum bytes per pixel */
#define MAX_BPP 4
#define MAX_BPP 8
typedef enum {
BLUE,
@@ -33,6 +33,7 @@ typedef struct _TestData {
} TestData;
#define RGBA(a, b, c, d) { 0x ## a, 0x ## b, 0x ## c, 0x ## d }
#define RGBA16(a, b, c, d) { 0x ## a, 0x ## a, 0x ## b, 0x ## b, 0x ## c, 0x ## c, 0x ## d, 0x ## d }
static MemoryData tests[GDK_MEMORY_N_FORMATS] = {
{ 4, FALSE, { RGBA(FF,00,00,FF), RGBA(00,FF,00,FF), RGBA(00,00,FF,FF), RGBA(00,00,00,00), RGBA(66,22,44,AA) } },
@@ -44,12 +45,14 @@ static MemoryData tests[GDK_MEMORY_N_FORMATS] = {
{ 4, FALSE, { RGBA(FF,FF,00,00), RGBA(FF,00,FF,00), RGBA(FF,00,00,FF), RGBA(00,00,00,00), RGBA(AA,99,33,66) } },
{ 3, TRUE, { RGBA(00,00,FF,00), RGBA(00,FF,00,00), RGBA(FF,00,00,00), RGBA(00,00,00,00), RGBA(44,22,66,00) } },
{ 3, TRUE, { RGBA(FF,00,00,00), RGBA(00,FF,00,00), RGBA(00,00,FF,00), RGBA(00,00,00,00), RGBA(66,22,44,00) } },
{ 8, FALSE, { RGBA16(00,00,FF,FF), RGBA16(00,FF,00,FF), RGBA16(FF,00,00,FF), RGBA16(00,00,00,00), RGBA16(44,22,66,AA) } },
};
static void
compare_textures (GdkTexture *expected,
GdkTexture *test,
gboolean ignore_alpha)
gboolean ignore_alpha,
int bpp)
{
guchar *expected_data, *test_data;
int width, height;
@@ -122,7 +125,7 @@ test_download_1x1 (gconstpointer data)
expected = create_texture (GDK_MEMORY_DEFAULT, test_data->color, 1, 1, tests[test_data->format].bytes_per_pixel);
test = create_texture (test_data->format, test_data->color, 1, 1, tests[test_data->format].bytes_per_pixel);
compare_textures (expected, test, tests[test_data->format].opaque);
compare_textures (expected, test, tests[test_data->format].opaque, tests[test_data->format].bytes_per_pixel);
g_object_unref (expected);
g_object_unref (test);
@@ -137,7 +140,7 @@ test_download_1x1_with_stride (gconstpointer data)
expected = create_texture (GDK_MEMORY_DEFAULT, test_data->color, 1, 1, 4);
test = create_texture (test_data->format, test_data->color, 1, 1, 2 * MAX_BPP);
compare_textures (expected, test, tests[test_data->format].opaque);
compare_textures (expected, test, tests[test_data->format].opaque, tests[test_data->format].bytes_per_pixel);
g_object_unref (expected);
g_object_unref (test);
@@ -152,7 +155,7 @@ test_download_4x4 (gconstpointer data)
expected = create_texture (GDK_MEMORY_DEFAULT, test_data->color, 4, 4, 16);
test = create_texture (test_data->format, test_data->color, 4, 4, 4 * tests[test_data->format].bytes_per_pixel);
compare_textures (expected, test, tests[test_data->format].opaque);
compare_textures (expected, test, tests[test_data->format].opaque, tests[test_data->format].bytes_per_pixel);
g_object_unref (expected);
g_object_unref (test);
@@ -167,7 +170,7 @@ test_download_4x4_with_stride (gconstpointer data)
expected = create_texture (GDK_MEMORY_DEFAULT, test_data->color, 4, 4, 16);
test = create_texture (test_data->format, test_data->color, 4, 4, 4 * MAX_BPP);
compare_textures (expected, test, tests[test_data->format].opaque);
compare_textures (expected, test, tests[test_data->format].opaque, tests[test_data->format].bytes_per_pixel);
g_object_unref (expected);
g_object_unref (test);
@@ -186,6 +189,12 @@ main (int argc, char *argv[])
for (format = 0; format < GDK_MEMORY_N_FORMATS; format++)
{
if (format == GDK_MEMORY_R16G16B16_FLOAT ||
format == GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED ||
format == GDK_MEMORY_R32G32B32_FLOAT ||
format == GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED)
continue;
for (color = 0; color < N_COLORS; color++)
{
TestData *test_data = g_new (TestData, 1);