Commit 03376d79 authored by Matthias Clasen's avatar Matthias Clasen Committed by Matthias Clasen

New public API to save pixbufs to non-file locations. (#82203, Tim Evans)

Wed Jan  7 02:41:14 2004  Matthias Clasen  <maclas@gmx.de>

	* gdk-pixbuf-io.c (gdk_pixbuf_save_to_bufferv):
	* gdk-pixbuf-io.c (gdk_pixbuf_save_to_buffer):
	* gdk-pixbuf-io.c (gdk_pixbuf_save_to_callbackv):
	* gdk-pixbuf-io.c (gdk_pixbuf_save_to_callback): New public API
	to save pixbufs to non-file locations.  (#82203, Tim Evans)

	* gdk-pixbuf.h:
	* gdk_pixbuf.def: Declare the new public API.

	* gdk-pixbuf-io.h: Add save_to_callback to the module interface.

	* io-jpeg.c:
	* io-png.c: Implement save_to_callback.

	* gdk-pixbuf-io.c (gdk_pixbuf_savev):
	* gdk-pixbuf-io.c (gdk_pixbuf_save): Update documentation.

	* gdk-pixbuf-io.c (gdk_pixbuf_real_save): Support saving via
	image_module->save_to_callback.

	* gdk-pixbuf-io.c (gdk_pixbuf_real_save_to_callback): New generic
	save_to_callback function which falls back to image_module->save
	on a temp file.
parent 5b26e695
Wed Jan 7 02:41:14 2004 Matthias Clasen <maclas@gmx.de>
* gdk-pixbuf-io.c (gdk_pixbuf_save_to_bufferv):
* gdk-pixbuf-io.c (gdk_pixbuf_save_to_buffer):
* gdk-pixbuf-io.c (gdk_pixbuf_save_to_callbackv):
* gdk-pixbuf-io.c (gdk_pixbuf_save_to_callback): New public API
to save pixbufs to non-file locations. (#82203, Tim Evans)
* gdk-pixbuf.h:
* gdk-pixbuf.def: Declare the new public API.
* gdk-pixbuf-io.h: Add save_to_callback to the module interface.
* io-jpeg.c:
* io-png.c: Implement save_to_callback.
* gdk-pixbuf-io.c (gdk_pixbuf_savev):
* gdk-pixbuf-io.c (gdk_pixbuf_save): Update documentation.
* gdk-pixbuf-io.c (gdk_pixbuf_real_save): Support saving via
image_module->save_to_callback.
* gdk-pixbuf-io.c (gdk_pixbuf_real_save_to_callback): New generic
save_to_callback function which falls back to image_module->save
on a temp file.
Wed Jan 7 01:17:36 2004 Matthias Clasen <maclas@gmx.de>
* gdk-pixbuf-loader.c (gdk_pixbuf_loader_size_func):
......
......@@ -1073,6 +1073,27 @@ collect_save_options (va_list opts,
}
}
static gboolean
save_to_file_callback (const gchar *buf,
gsize count,
GError **error,
gpointer data)
{
FILE *filehandle = data;
gsize n;
n = fwrite (buf, 1, count, filehandle);
if (n != count) {
g_set_error (error,
G_FILE_ERROR,
g_file_error_from_errno (errno),
_("Error writing to image file: %s"),
g_strerror (errno));
return FALSE;
}
return TRUE;
}
static gboolean
gdk_pixbuf_real_save (GdkPixbuf *pixbuf,
FILE *filehandle,
......@@ -1092,7 +1113,137 @@ gdk_pixbuf_real_save (GdkPixbuf *pixbuf,
if (!_gdk_pixbuf_load_module (image_module, error))
return FALSE;
if (image_module->save == NULL) {
if (image_module->save) {
/* save normally */
return (* image_module->save) (filehandle, pixbuf,
keys, values,
error);
}
else if (image_module->save_to_callback) {
/* save with simple callback */
return (* image_module->save_to_callback) (save_to_file_callback,
filehandle, pixbuf,
keys, values,
error);
}
else {
/* can't save */
g_set_error (error,
GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_UNSUPPORTED_OPERATION,
_("This build of gdk-pixbuf does not support saving the image format: %s"),
type);
return FALSE;
}
}
#define TMP_FILE_BUF_SIZE 4096
static gboolean
save_to_callback_with_tmp_file (GdkPixbufModule *image_module,
GdkPixbuf *pixbuf,
GdkPixbufSaveFunc save_func,
gpointer user_data,
gchar **keys,
gchar **values,
GError **error)
{
int fd;
FILE *f = NULL;
gboolean retval = FALSE;
gchar *buf = NULL;
gsize n;
gchar *filename = NULL;
buf = g_try_malloc (TMP_FILE_BUF_SIZE);
if (buf == NULL) {
g_set_error (error,
GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
_("Insufficient memory to save image to callback"));
goto end;
}
fd = g_file_open_tmp ("gdkpixbuf-save-tmp.XXXXXX", &filename, error);
if (fd == -1)
goto end;
f = fdopen (fd, "wb+");
if (f == NULL) {
g_set_error (error,
G_FILE_ERROR,
g_file_error_from_errno (errno),
_("Failed to open temporary file"));
goto end;
}
if (!(* image_module->save) (f, pixbuf, keys, values, error))
goto end;
rewind (f);
for (;;) {
n = fread (buf, 1, TMP_FILE_BUF_SIZE, f);
if (n > 0) {
if (!save_func (buf, n, error, user_data))
goto end;
}
if (n != TMP_FILE_BUF_SIZE)
break;
}
if (ferror (f)) {
g_set_error (error,
G_FILE_ERROR,
g_file_error_from_errno (errno),
_("Failed to read from temporary file"));
goto end;
}
retval = TRUE;
end:
/* cleanup and return retval */
if (f)
fclose (f);
if (filename) {
unlink (filename);
g_free (filename);
}
g_free (buf);
return retval;
}
static gboolean
gdk_pixbuf_real_save_to_callback (GdkPixbuf *pixbuf,
GdkPixbufSaveFunc save_func,
gpointer user_data,
const char *type,
gchar **keys,
gchar **values,
GError **error)
{
GdkPixbufModule *image_module = NULL;
image_module = _gdk_pixbuf_get_named_module (type, error);
if (image_module == NULL)
return FALSE;
if (image_module->module == NULL)
if (!_gdk_pixbuf_load_module (image_module, error))
return FALSE;
if (image_module->save_to_callback) {
/* save normally */
return (* image_module->save_to_callback) (save_func, user_data,
pixbuf, keys, values,
error);
}
else if (image_module->save) {
/* use a temporary file */
return save_to_callback_with_tmp_file (image_module, pixbuf,
save_func, user_data,
keys, values,
error);
}
else {
/* can't save */
g_set_error (error,
GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_UNSUPPORTED_OPERATION,
......@@ -1100,10 +1251,6 @@ gdk_pixbuf_real_save (GdkPixbuf *pixbuf,
type);
return FALSE;
}
return (* image_module->save) (filehandle, pixbuf,
keys, values,
error);
}
......@@ -1115,8 +1262,25 @@ gdk_pixbuf_real_save (GdkPixbuf *pixbuf,
* @error: return location for error, or %NULL
* @Varargs: list of key-value save options
*
* Saves pixbuf to a file in @type, which is currently "jpeg", "png" or
* "ico". If @error is set, %FALSE will be returned. Possible errors include
* Saves pixbuf to a file in format @type. By default, "jpeg", "png" and
* "ico" are possible file formats to save in, but more formats may be
* installed. The list of all writable formats can be determined in the
* following way:
*
* <informalexample><programlisting>
* void add_if_writable (GdkPixbufFormat *data, GSList **list)
* {
* if (gdk_pixbuf_format_is_writable (data))
* *list = g_slist_prepend (*list, data);
* }
*
* GSList *formats = gdk_pixbuf_get_formats (<!-- -->);
* GSList *writable_formats = NULL;
* g_slist_foreach (formats, add_if_writable, &writable_formats);
* g_slist_free (formats);
* </programlisting></informalexample>
*
* If @error is set, %FALSE will be returned. Possible errors include
* those in the #GDK_PIXBUF_ERROR domain and those in the #G_FILE_ERROR domain.
*
* The variable argument list should be %NULL-terminated; if not empty,
......@@ -1178,9 +1342,9 @@ gdk_pixbuf_save (GdkPixbuf *pixbuf,
* @option_values: values for named options
* @error: return location for error, or %NULL
*
* Saves pixbuf to a file in @type, which is currently "jpeg" or "png".
* If @error is set, %FALSE will be returned. See gdk_pixbuf_save () for more
* details.
* Saves pixbuf to a file in @type, which is currently "jpeg", "png" or "ico".
* If @error is set, %FALSE will be returned.
* See gdk_pixbuf_save () for more details.
*
* Return value: whether an error was set
**/
......@@ -1236,6 +1400,251 @@ gdk_pixbuf_savev (GdkPixbuf *pixbuf,
return TRUE;
}
/**
* gdk_pixbuf_save_to_callback:
* @pixbuf: a #GdkPixbuf.
* @save_func: a function that is called to save each block of data that
* the save routine generates.
* @user_data: user data to pass to the save function.
* @type: name of file format.
* @error: return location for error, or %NULL
* @Varargs: list of key-value save options
*
* Saves pixbuf in format @type by feeding the produced data to a
* callback. Can be used when you want to store the image to something
* other than a file, such as an in-memory buffer or a socket.
* If @error is set, %FALSE will be returned. Possible errors
* include those in the #GDK_PIXBUF_ERROR domain and whatever the save
* function generates.
*
* See gdk_pixbuf_save() for more details.
*
* Return value: whether an error was set
*
* Since: 2.4
**/
gboolean
gdk_pixbuf_save_to_callback (GdkPixbuf *pixbuf,
GdkPixbufSaveFunc save_func,
gpointer user_data,
const char *type,
GError **error,
...)
{
gchar **keys = NULL;
gchar **values = NULL;
va_list args;
gboolean result;
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
va_start (args, error);
collect_save_options (args, &keys, &values);
va_end (args);
result = gdk_pixbuf_save_to_callbackv (pixbuf, save_func, user_data,
type, keys, values,
error);
g_strfreev (keys);
g_strfreev (values);
return result;
}
/**
* gdk_pixbuf_save_to_callbackv:
* @pixbuf: a #GdkPixbuf.
* @save_func: a function that is called to save each block of data that
* the save routine generates.
* @user_data: user data to pass to the save function.
* @type: name of file format.
* @option_keys: name of options to set, %NULL-terminated
* @option_values: values for named options
* @error: return location for error, or %NULL
*
* Saves pixbuf to a callback in format @type, which is currently "jpeg",
* "png" or "ico". If @error is set, %FALSE will be returned. See
* gdk_pixbuf_save_to_callback () for more details.
*
* Return value: whether an error was set
*
* Since: 2.4
**/
gboolean
gdk_pixbuf_save_to_callbackv (GdkPixbuf *pixbuf,
GdkPixbufSaveFunc save_func,
gpointer user_data,
const char *type,
char **option_keys,
char **option_values,
GError **error)
{
gboolean result;
g_return_val_if_fail (save_func != NULL, FALSE);
g_return_val_if_fail (type != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
result = gdk_pixbuf_real_save_to_callback (pixbuf,
save_func, user_data, type,
option_keys, option_values,
error);
if (!result) {
g_return_val_if_fail (error == NULL || *error != NULL, FALSE);
return FALSE;
}
return TRUE;
}
/**
* gdk_pixbuf_save_to_buffer:
* @pixbuf: a #GdkPixbuf.
* @buffer: location to receive a pointer to the new buffer.
* @buffer_size: location to receive the size of the new buffer.
* @type: name of file format.
* @error: return location for error, or %NULL
* @Varargs: list of key-value save options
*
* Saves pixbuf to a new buffer in format @type, which is currently "jpeg",
* "png" or "ico". This is a convenience function that uses
* gdk_pixbuf_save_to_callback() to do the real work. Note that the buffer
* is not nul-terminated and may contain embedded nuls.
* If @error is set, %FALSE will be returned and @string will be set to
* %NULL. Possible errors include those in the #GDK_PIXBUF_ERROR
* domain.
*
* See gdk_pixbuf_save() for more details.
*
* Return value: whether an error was set
*
* Since: 2.4
**/
gboolean
gdk_pixbuf_save_to_buffer (GdkPixbuf *pixbuf,
gchar **buffer,
gsize *buffer_size,
const char *type,
GError **error,
...)
{
gchar **keys = NULL;
gchar **values = NULL;
va_list args;
gboolean result;
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
va_start (args, error);
collect_save_options (args, &keys, &values);
va_end (args);
result = gdk_pixbuf_save_to_bufferv (pixbuf, buffer, buffer_size,
type, keys, values,
error);
g_strfreev (keys);
g_strfreev (values);
return result;
}
struct SaveToBufferData {
gchar *buffer;
gsize len, max;
};
static gboolean
save_to_buffer_callback (const gchar *data,
gsize count,
GError **error,
gpointer user_data)
{
struct SaveToBufferData *sdata = user_data;
gchar *new_buffer;
gsize new_max;
if (sdata->len + count > sdata->max) {
new_max = MAX (sdata->max*2, sdata->len + count);
new_buffer = g_try_realloc (sdata->buffer, new_max);
if (!new_buffer) {
g_set_error (error,
GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
_("Insufficient memory to save image into a buffer"));
return FALSE;
}
sdata->buffer = new_buffer;
sdata->max = new_max;
}
memcpy (sdata->buffer + sdata->len, data, count);
sdata->len += count;
return TRUE;
}
/**
* gdk_pixbuf_save_to_bufferv:
* @pixbuf: a #GdkPixbuf.
* @buffer: location to receive a pointer to the new buffer.
* @buffer_size: location to receive the size of the new buffer.
* @type: name of file format.
* @option_keys: name of options to set, %NULL-terminated
* @option_values: values for named options
* @error: return location for error, or %NULL
*
* Saves pixbuf to a new buffer in format @type, which is currently "jpeg",
* "png" or "ico". See gdk_pixbuf_save_to_buffer() for more details.
*
* Return value: whether an error was set
*
* Since: 2.4
**/
gboolean
gdk_pixbuf_save_to_bufferv (GdkPixbuf *pixbuf,
gchar **buffer,
gsize *buffer_size,
const char *type,
char **option_keys,
char **option_values,
GError **error)
{
static const gint initial_max = 1024;
struct SaveToBufferData sdata;
*buffer = NULL;
*buffer_size = 0;
sdata.buffer = g_try_malloc (initial_max);
sdata.max = initial_max;
sdata.len = 0;
if (!sdata.buffer) {
g_set_error (error,
GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
_("Insufficient memory to save image into a buffer"));
return FALSE;
}
if (!gdk_pixbuf_save_to_callbackv (pixbuf,
save_to_buffer_callback, &sdata,
type, option_keys, option_values,
error)) {
g_free (sdata.buffer);
return FALSE;
}
*buffer = sdata.buffer;
*buffer_size = sdata.len;
return TRUE;
}
/**
* gdk_pixbuf_format_get_name:
* @format: a #GdkPixbufFormat
......
......@@ -89,11 +89,19 @@ struct _GdkPixbufModule {
GdkPixbufAnimation *(* load_animation) (FILE *f,
GError **error);
/* Saving */
gboolean (* save) (FILE *f,
GdkPixbuf *pixbuf,
gchar **param_keys,
gchar **param_values,
GError **error);
gboolean (*save_to_callback) (GdkPixbufSaveFunc save_func,
gpointer user_data,
GdkPixbuf *pixbuf,
gchar **option_keys,
gchar **option_values,
GError **error);
/*< private >*/
void (*_reserved1) (void);
......@@ -101,7 +109,6 @@ struct _GdkPixbufModule {
void (*_reserved3) (void);
void (*_reserved4) (void);
void (*_reserved5) (void);
void (*_reserved6) (void);
};
......@@ -116,7 +123,7 @@ gboolean gdk_pixbuf_set_option (GdkPixbuf *pixbuf,
typedef enum /*< skip >*/
{
GDK_PIXBUF_FORMAT_WRITABLE = 1 << 0
GDK_PIXBUF_FORMAT_WRITABLE = 1 << 0,
} GdkPixbufFormatFlags;
struct _GdkPixbufFormat {
......
......@@ -225,7 +225,6 @@ gdk_pixbuf_loader_finalize (GObject *object)
if (!priv->closed)
g_warning ("GdkPixbufLoader finalized without calling gdk_pixbuf_loader_close() - this is not allowed. You must explicitly end the data stream to the loader before dropping the last reference.");
if (priv->animation)
g_object_unref (priv->animation);
......
......@@ -170,6 +170,45 @@ gboolean gdk_pixbuf_savev (GdkPixbuf *pixbuf,
char **option_values,
GError **error);
/* Saving to a callback function */
typedef gboolean (*GdkPixbufSaveFunc) (const gchar *buf,
gsize count,
GError **error,
gpointer data);
gboolean gdk_pixbuf_save_to_callback (GdkPixbuf *pixbuf,
GdkPixbufSaveFunc save_func,
gpointer user_data,
const char *type,
GError **error,
...);
gboolean gdk_pixbuf_save_to_callbackv (GdkPixbuf *pixbuf,
GdkPixbufSaveFunc save_func,
gpointer user_data,
const char *type,
char **option_keys,
char **option_values,
GError **error);
/* Saving into a newly allocated char array */
gboolean gdk_pixbuf_save_to_buffer (GdkPixbuf *pixbuf,
gchar **buffer,
gsize *buffer_size,
const char *type,
GError **error,
...);
gboolean gdk_pixbuf_save_to_bufferv (GdkPixbuf *pixbuf,
gchar **buffer,
gsize *buffer_size,
const char *type,
char **option_keys,
char **option_values,
GError **error);
/* Adding an alpha channel */
GdkPixbuf *gdk_pixbuf_add_alpha (const GdkPixbuf *pixbuf, gboolean substitute_color,
guchar r, guchar g, guchar b);
......
......@@ -33,6 +33,7 @@ EXPORTS
gdk_pixbuf_from_pixdata
gdk_pixbuf_get_bits_per_sample
gdk_pixbuf_get_colorspace
gdk_pixbuf_get_file_info
gdk_pixbuf_get_formats
gdk_pixbuf_get_has_alpha
gdk_pixbuf_get_height
......@@ -60,6 +61,10 @@ EXPORTS
gdk_pixbuf_ref
gdk_pixbuf_saturate_and_pixelate
gdk_pixbuf_save
gdk_pixbuf_save_to_callback
gdk_pixbuf_save_to_callbackv
gdk_pixbuf_save_to_buffer
gdk_pixbuf_save_to_bufferv
gdk_pixbuf_savev
gdk_pixbuf_scale
gdk_pixbuf_scale_simple
......
......@@ -780,12 +780,85 @@ gdk_pixbuf__jpeg_image_load_increment (gpointer data,
return TRUE;
}
/* Save */
#define TO_FUNCTION_BUF_SIZE 4096
typedef struct {
struct jpeg_destination_mgr pub;
JOCTET *buffer;
GdkPixbufSaveFunc save_func;
gpointer user_data;
GError **error;
} ToFunctionDestinationManager;
void
to_callback_init (j_compress_ptr cinfo)
{
ToFunctionDestinationManager *destmgr;
destmgr = (ToFunctionDestinationManager*) cinfo->dest;
destmgr->pub.next_output_byte = destmgr->buffer;
destmgr->pub.free_in_buffer = TO_FUNCTION_BUF_SIZE;
}
static void
to_callback_do_write (j_compress_ptr cinfo, gsize length)
{
ToFunctionDestinationManager *destmgr;
destmgr = (ToFunctionDestinationManager*) cinfo->dest;
if (!destmgr->save_func (destmgr->buffer,
length,
destmgr->error,
destmgr->user_data)) {
struct error_handler_data *errmgr;
errmgr = (struct error_handler_data *) cinfo->err;
/* Use a default error message if the callback didn't set one,
* which it should have.
*/
if (errmgr->error && *errmgr->error == NULL) {
g_set_error (errmgr->error,
GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
"write function failed");
}
siglongjmp (errmgr->setjmp_buffer, 1);
g_assert_not_reached ();
}
}
static guchar
to_callback_empty_output_buffer (j_compress_ptr cinfo)
{
ToFunctionDestinationManager *destmgr;
destmgr = (ToFunctionDestinationManager*) cinfo->dest;
to_callback_do_write (cinfo, TO_FUNCTION_BUF_SIZE);
destmgr->pub.next_output_byte = destmgr->buffer;
destmgr->pub.free_in_buffer = TO_FUNCTION_BUF_SIZE;
return TRUE;
}
void
to_callback_terminate (j_compress_ptr cinfo)
{
ToFunctionDestinationManager *destmgr;
destmgr = (ToFunctionDestinationManager*) cinfo->dest;
to_callback_do_write (cinfo, TO_FUNCTION_BUF_SIZE - destmgr->pub.free_in_buffer);
}
static gboolean
gdk_pixbuf__jpeg_image_save (FILE *f,
GdkPixbuf *pixbuf,
gchar **keys,
gchar **values,
GError **error)
real_save_jpeg (GdkPixbuf *pixbuf,
gchar **keys,
gchar **values,
GError **error,
gboolean to_callback,
FILE *f,
GdkPixbufSaveFunc save_func,
gpointer user_data)
{
/* FIXME error handling is broken */
......@@ -800,6 +873,9 @@ gdk_pixbuf__jpeg_image_save (FILE *f,
int w, h = 0;
int rowstride = 0;
struct error_handler_data jerr;
ToFunctionDestinationManager to_callback_destmgr;
to_callback_destmgr.buffer = NULL;
if (keys && *keys) {
gchar **kiter = keys;
......@@ -854,7 +930,9 @@ gdk_pixbuf__jpeg_image_save (FILE *f,
pixels = gdk_pixbuf_get_pixels (pixbuf);
g_return_val_if_fail (pixels != NULL, FALSE);
/* allocate a small buffer to convert image data */
/* Allocate a small buffer to convert image data,
* and a larger buffer if doing to_callback save.
*/
buf = g_try_malloc (w * 3 * sizeof (guchar));
if (!buf) {
g_set_error (error,
......@@ -863,6 +941,16 @@ gdk_pixbuf__jpeg_image_save (FILE *f,
_("Couldn't allocate memory for loading JPEG file"));
return FALSE;
}
if (to_callback) {
to_callback_destmgr.buffer = g_try_malloc (TO_FUNCTION_BUF_SIZE);
if (!to_callback_destmgr.buffer) {
g_set_error (error,
GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
_("Couldn't allocate memory for loading JPEG file"));
return FALSE;
}
}
/* set up error handling */
jerr.pub.error_exit = fatal_error_handler;
......@@ -873,12 +961,23 @@ gdk_pixbuf__jpeg_image_save (FILE *f,
if (sigsetjmp (jerr.setjmp_buffer, 1)) {
jpeg_destroy_compress (&cinfo);
g_free (buf);