Thu, 07 Aug 2025 21:32:18 -0500
Clean up and modernize PurpleImage
Testing Done:
Ran the tests under valgrind and called in the turtles.
Reviewed at https://reviews.imfreedom.org/r/4074/
--- a/libpurple/image.c Thu Jul 31 20:57:19 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,463 +0,0 @@ -/* - * Purple - Internet Messaging Library - * Copyright (C) Pidgin Developers <devel@pidgin.im> - * - * Purple is the legal property of its developers, whose names are too numerous - * to list here. Please refer to the COPYRIGHT file distributed with this - * source distribution. - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU 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 General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this library; if not, see <https://www.gnu.org/licenses/>. - */ - -#include "debug.h" -#include "image.h" -#include "util.h" - -typedef struct { - gchar *path; - - GBytes *contents; - - const gchar *extension; - const gchar *mime; - gchar *gen_filename; - gchar *friendly_filename; -} PurpleImagePrivate; - -enum { - PROP_0, - PROP_PATH, - PROP_CONTENTS, - PROP_SIZE, - N_PROPERTIES, -}; - -static GParamSpec *properties[N_PROPERTIES] = {NULL, }; - -G_DEFINE_TYPE_WITH_PRIVATE(PurpleImage, purple_image, G_TYPE_OBJECT); - -/****************************************************************************** - * Helpers - ******************************************************************************/ -static void -_purple_image_set_path(PurpleImage *image, const gchar *path) { - PurpleImagePrivate *priv = purple_image_get_instance_private(image); - - g_set_str(&priv->path, path); -} - -static void -_purple_image_set_contents(PurpleImage *image, GBytes *bytes) { - PurpleImagePrivate *priv = purple_image_get_instance_private(image); - - if(priv->contents) - g_bytes_unref(priv->contents); - - priv->contents = (bytes) ? g_bytes_ref(bytes) : NULL; -} - -/****************************************************************************** - * Object stuff - ******************************************************************************/ -static void -purple_image_init(G_GNUC_UNUSED PurpleImage *image) { -} - -static void -purple_image_finalize(GObject *obj) { - PurpleImage *image = PURPLE_IMAGE(obj); - PurpleImagePrivate *priv = purple_image_get_instance_private(image); - - if(priv->contents) - g_bytes_unref(priv->contents); - - g_free(priv->path); - g_free(priv->gen_filename); - g_free(priv->friendly_filename); - - G_OBJECT_CLASS(purple_image_parent_class)->finalize(obj); -} - -static void -purple_image_set_property(GObject *obj, guint param_id, - const GValue *value, GParamSpec *pspec) -{ - PurpleImage *image = PURPLE_IMAGE(obj); - - switch (param_id) { - case PROP_PATH: - _purple_image_set_path(image, g_value_get_string(value)); - break; - case PROP_CONTENTS: - _purple_image_set_contents(image, g_value_get_boxed(value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); - break; - } -} - -static void -purple_image_get_property(GObject *obj, guint param_id, GValue *value, - GParamSpec *pspec) -{ - PurpleImage *image = PURPLE_IMAGE(obj); - - switch (param_id) { - case PROP_PATH: - g_value_set_string(value, purple_image_get_path(image)); - break; - case PROP_CONTENTS: - g_value_set_boxed(value, purple_image_get_contents(image)); - break; - case PROP_SIZE: - g_value_set_uint64(value, purple_image_get_data_size(image)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); - break; - } -} - -static void -purple_image_class_init(PurpleImageClass *klass) { - GObjectClass *gobj_class = G_OBJECT_CLASS(klass); - - gobj_class->finalize = purple_image_finalize; - gobj_class->get_property = purple_image_get_property; - gobj_class->set_property = purple_image_set_property; - - /** - * PurpleImage:path: - * - * The file path for the image if one was provided. - * - * Since: 3.0 - */ - properties[PROP_PATH] = g_param_spec_string( - "path", NULL, NULL, - NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); - - /** - * PurpleImage:contents: - * - * The contents of the image. - * - * Since: 3.0 - */ - properties[PROP_CONTENTS] = g_param_spec_boxed( - "contents", NULL, NULL, - G_TYPE_BYTES, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); - - /** - * PurpleImage:size: - * - * The size of the image in bytes. - * - * Since: 3.0 - */ - properties[PROP_SIZE] = g_param_spec_uint64( - "size", NULL, NULL, - 0, G_MAXUINT64, 0, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); - - g_object_class_install_properties(gobj_class, N_PROPERTIES, properties); -} - -/****************************************************************************** - * API - ******************************************************************************/ -PurpleImage * -purple_image_new_from_bytes(GBytes *bytes) { - return g_object_new( - PURPLE_TYPE_IMAGE, - "contents", bytes, - NULL - ); -} - -PurpleImage * -purple_image_new_from_file(const gchar *path, GError **error) { - PurpleImage *image = NULL; - GBytes *bytes = NULL; - gchar *contents = NULL; - gsize length = 0; - - if(!g_file_get_contents(path, &contents, &length, error)) { - return NULL; - } - - bytes = g_bytes_new_take(contents, length); - - image = g_object_new( - PURPLE_TYPE_IMAGE, - "contents", bytes, - "path", path, - NULL - ); - - g_bytes_unref(bytes); - - return image; -} - -PurpleImage * -purple_image_new_from_data(const guint8 *data, gsize length) { - PurpleImage *image; - GBytes *bytes = NULL; - - bytes = g_bytes_new(data, length); - - image = purple_image_new_from_bytes(bytes); - - g_bytes_unref(bytes); - - return image; -} - -PurpleImage * -purple_image_new_take_data(guint8 *data, gsize length) { - PurpleImage *image; - GBytes *bytes = NULL; - - bytes = g_bytes_new_take(data, length); - - image = purple_image_new_from_bytes(bytes); - - g_bytes_unref(bytes); - - return image; -} - -gboolean -purple_image_save(PurpleImage *image, const gchar *path) { - PurpleImagePrivate *priv = NULL; - gconstpointer data; - gsize len; - gboolean succ; - - g_return_val_if_fail(PURPLE_IS_IMAGE(image), FALSE); - g_return_val_if_fail(path != NULL, FALSE); - g_return_val_if_fail(path[0] != '\0', FALSE); - - priv = purple_image_get_instance_private(image); - data = purple_image_get_data(image); - len = purple_image_get_data_size(image); - - g_return_val_if_fail(data != NULL, FALSE); - g_return_val_if_fail(len > 0, FALSE); - - succ = g_file_set_contents(path, data, len, NULL); - if (succ && priv->path == NULL) - priv->path = g_strdup(path); - - return succ; -} - -GBytes * -purple_image_get_contents(PurpleImage *image) -{ - PurpleImagePrivate *priv = NULL; - - g_return_val_if_fail(PURPLE_IS_IMAGE(image), NULL); - - priv = purple_image_get_instance_private(image); - - if(priv->contents) - return g_bytes_ref(priv->contents); - - return NULL; -} - -const gchar * -purple_image_get_path(PurpleImage *image) { - PurpleImagePrivate *priv = NULL; - - g_return_val_if_fail(PURPLE_IS_IMAGE(image), NULL); - - priv = purple_image_get_instance_private(image); - - return priv->path ? priv->path : purple_image_generate_filename(image); -} - -gsize -purple_image_get_data_size(PurpleImage *image) { - PurpleImagePrivate *priv; - - g_return_val_if_fail(PURPLE_IS_IMAGE(image), 0); - - priv = purple_image_get_instance_private(image); - - if(priv->contents) - return g_bytes_get_size(priv->contents); - - return 0; -} - -gconstpointer -purple_image_get_data(PurpleImage *image) { - PurpleImagePrivate *priv = NULL; - - g_return_val_if_fail(PURPLE_IS_IMAGE(image), NULL); - - priv = purple_image_get_instance_private(image); - - if(priv->contents) - return g_bytes_get_data(priv->contents, NULL); - - return NULL; -} - -const gchar * -purple_image_get_extension(PurpleImage *image) { - PurpleImagePrivate *priv = NULL; - gconstpointer data; - - g_return_val_if_fail(PURPLE_IS_IMAGE(image), NULL); - - priv = purple_image_get_instance_private(image); - - if (priv->extension) - return priv->extension; - - if (purple_image_get_data_size(image) < 4) - return NULL; - - data = purple_image_get_data(image); - g_assert(data != NULL); - - if (memcmp(data, "GIF8", 4) == 0) - return priv->extension = "gif"; - if (memcmp(data, "\xff\xd8\xff", 3) == 0) /* 4th may be e0 through ef */ - return priv->extension = "jpg"; - if (memcmp(data, "\x89PNG", 4) == 0) - return priv->extension = "png"; - if (memcmp(data, "MM", 2) == 0) - return priv->extension = "tif"; - if (memcmp(data, "II", 2) == 0) - return priv->extension = "tif"; - if (memcmp(data, "BM", 2) == 0) - return priv->extension = "bmp"; - if (memcmp(data, "\x00\x00\x01\x00", 4) == 0) - return priv->extension = "ico"; - - return NULL; -} - -const gchar * -purple_image_get_mimetype(PurpleImage *image) { - PurpleImagePrivate *priv = NULL; - const gchar *ext = purple_image_get_extension(image); - - g_return_val_if_fail(PURPLE_IS_IMAGE(image), NULL); - - priv = purple_image_get_instance_private(image); - - if (priv->mime) - return priv->mime; - - g_return_val_if_fail(ext != NULL, NULL); - - if (g_strcmp0(ext, "gif") == 0) - return priv->mime = "image/gif"; - if (g_strcmp0(ext, "jpg") == 0) - return priv->mime = "image/jpeg"; - if (g_strcmp0(ext, "png") == 0) - return priv->mime = "image/png"; - if (g_strcmp0(ext, "tif") == 0) - return priv->mime = "image/tiff"; - if (g_strcmp0(ext, "bmp") == 0) - return priv->mime = "image/bmp"; - if (g_strcmp0(ext, "ico") == 0) - return priv->mime = "image/vnd.microsoft.icon"; - - return NULL; -} - -const gchar * -purple_image_generate_filename(PurpleImage *image) { - PurpleImagePrivate *priv = NULL; - gconstpointer data; - gsize len; - const gchar *ext = NULL; - gchar *checksum; - - g_return_val_if_fail(PURPLE_IS_IMAGE(image), NULL); - - priv = purple_image_get_instance_private(image); - - if (priv->gen_filename) - return priv->gen_filename; - - /* grab the image's data and size of that data */ - data = purple_image_get_data(image); - len = purple_image_get_data_size(image); - - /* create a checksum of it and use it as the start of our filename */ - checksum = g_compute_checksum_for_data(G_CHECKSUM_SHA1, data, len); - - /* if the image has a known format, set the extension appropriately */ - ext = purple_image_get_extension(image); - if(ext != NULL) { - priv->gen_filename = g_strdup_printf("%s.%s", checksum, ext); - g_free(checksum); - } else { - priv->gen_filename = checksum; - } - - return priv->gen_filename; -} - -void -purple_image_set_friendly_filename(PurpleImage *image, const gchar *filename) { - PurpleImagePrivate *priv = NULL; - gchar *newname; - const gchar *escaped; - - g_return_if_fail(PURPLE_IS_IMAGE(image)); - - priv = purple_image_get_instance_private(image); - - newname = g_path_get_basename(filename); - escaped = purple_escape_filename(newname); - g_free(newname); - newname = NULL; - - if (g_strcmp0(escaped, "") == 0 || g_strcmp0(escaped, ".") == 0 || - g_strcmp0(escaped, G_DIR_SEPARATOR_S) == 0 || - g_strcmp0(escaped, "/") == 0 || g_strcmp0(escaped, "\\") == 0) - { - escaped = NULL; - } - - g_set_str(&priv->friendly_filename, escaped); -} - -const gchar * -purple_image_get_friendly_filename(PurpleImage *image) { - PurpleImagePrivate *priv = NULL; - - g_return_val_if_fail(PURPLE_IS_IMAGE(image), NULL); - - priv = purple_image_get_instance_private(image); - - if(priv->friendly_filename) { - return priv->friendly_filename; - } - - return purple_image_generate_filename(image); -} -
--- a/libpurple/image.h Thu Jul 31 20:57:19 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,274 +0,0 @@ -/* - * Purple - Internet Messaging Library - * Copyright (C) Pidgin Developers <devel@pidgin.im> - * - * Purple is the legal property of its developers, whose names are too numerous - * to list here. Please refer to the COPYRIGHT file distributed with this - * source distribution. - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU 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 General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this library; if not, see <https://www.gnu.org/licenses/>. - */ - -#if !defined(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION) -# error "only <purple.h> may be included directly" -#endif - -#ifndef PURPLE_IMAGE_H -#define PURPLE_IMAGE_H - -#include <glib-object.h> - -#include "purpleversion.h" - -#define PURPLE_TYPE_IMAGE purple_image_get_type() - -struct _PurpleImageClass { - /*< private >*/ - GObjectClass parent_class; - - void (*purple_reserved1)(void); - void (*purple_reserved2)(void); - void (*purple_reserved3)(void); - void (*purple_reserved4)(void); -}; - -G_BEGIN_DECLS - -/** - * PurpleImage: - * - * #PurpleImage object is a container for raw image data. It doesn't manipulate - * image data, just stores it in its binary format - png, jpeg etc. Thus, it's - * totally independent from the UI. - * - * This class also provides certain file-related features, like: friendly - * filenames (not necessarily real filename for displaying); remote images - * (which data is not yet loaded) or guessing file format from its header. - * - * Since: 3.0 - */ -PURPLE_AVAILABLE_IN_3_0 -G_DECLARE_DERIVABLE_TYPE(PurpleImage, purple_image, PURPLE, IMAGE, GObject) - -/** - * purple_image_new_from_bytes: - * @bytes: (transfer none): A #GBytes containing the raw image data. - * - * Loads a raw image data as a new #PurpleImage object. - * - * Returns: the new #PurpleImage. - * - * Since: 3.0 - */ -PURPLE_AVAILABLE_IN_3_0 -PurpleImage *purple_image_new_from_bytes(GBytes *bytes); - -/** - * purple_image_new_from_file: - * @path: the path to the image file. - * @error: Return address for a #GError, or %NULL. - * - * Loads an image file as a new #PurpleImage object. The @path must exists, be - * readable and should point to a valid image file. If you don't set @be_eager - * parameter, there will be a risk that file will be removed from disk before - * you access its data. - * - * Returns: the new #PurpleImage. - * - * Since: 3.0 - */ -PURPLE_AVAILABLE_IN_3_0 -PurpleImage *purple_image_new_from_file(const gchar *path, GError **error); - -/** - * purple_image_new_from_data: - * @data: the pointer to the image data buffer. - * @length: the length of @data. - * - * Creates a new #PurpleImage object with contents of @data buffer. - * - * The @data buffer is owned by #PurpleImage object, so you might want - * to g_memdup2() it first. - * - * Returns: the new #PurpleImage. - * - * Since: 3.0 - */ -PURPLE_AVAILABLE_IN_3_0 -PurpleImage *purple_image_new_from_data(const guint8 *data, gsize length); - -/** - * purple_image_new_take_data: - * @data: (transfer full): the pointer to the image data buffer. - * @length: the length of @data. - * - * Creates a new #PurpleImage object with contents of @data buffer. - * - * The @data buffer is owned by #PurpleImage object, so you might want - * to g_memdup2() it first. - * - * Returns: the new #PurpleImage. - * - * Since: 3.0 - */ -PURPLE_AVAILABLE_IN_3_0 -PurpleImage *purple_image_new_take_data(guint8 *data, gsize length); - -/** - * purple_image_save: - * @image: the image. - * @path: destination of a saved image file. - * - * Saves an @image to the disk. - * - * Returns: %TRUE if succeeded, %FALSE otherwise. - * - * Since: 3.0 - */ -PURPLE_AVAILABLE_IN_3_0 -gboolean purple_image_save(PurpleImage *image, const gchar *path); - -/** - * purple_image_get_contents: - * @image: The #PurpleImage. - * - * Returns a new reference to the #GBytes that contains the image data. - * - * Returns: (transfer full): A #GBytes containing the image data. - * - * Since: 3.0 - */ -PURPLE_AVAILABLE_IN_3_0 -GBytes *purple_image_get_contents(PurpleImage *image); - - -/** - * purple_image_get_path: - * @image: the image. - * - * Returns the physical path of the @image file. It is set only, if the @image is - * really backed by an existing file. In the other case it returns %NULL. - * - * Returns: the physical path of the @image, or %NULL. - * - * Since: 3.0 - */ -PURPLE_AVAILABLE_IN_3_0 -const gchar *purple_image_get_path(PurpleImage *image); - -/** - * purple_image_get_data_size: - * @image: the image. - * - * Returns the size of @image's data. - * - * Returns: the size of data, or 0 in case of failure. - * - * Since: 3.0 - */ -PURPLE_AVAILABLE_IN_3_0 -gsize purple_image_get_data_size(PurpleImage *image); - -/** - * purple_image_get_data: - * @image: the image. - * - * Returns the pointer to the buffer containing image data. - * - * Returns: (transfer none): the @image data. - * - * Since: 3.0 - */ -PURPLE_AVAILABLE_IN_3_0 -gconstpointer purple_image_get_data(PurpleImage *image); - -/** - * purple_image_get_extension: - * @image: the image. - * - * Guesses the @image format based on its contents. - * - * Returns: (transfer none): the file extension suitable for @image format. - * - * Since: 3.0 - */ -PURPLE_AVAILABLE_IN_3_0 -const gchar *purple_image_get_extension(PurpleImage *image); - -/** - * purple_image_get_mimetype: - * @image: the image. - * - * Guesses the @image mime-type based on its contents. - * - * Returns: (transfer none): the mime-type suitable for @image format. - * - * Since: 3.0 - */ -PURPLE_AVAILABLE_IN_3_0 -const gchar *purple_image_get_mimetype(PurpleImage *image); - -/** - * purple_image_generate_filename: - * @image: the image. - * - * Calculates almost-unique filename by computing checksum from file contents - * and appending a suitable extension. You should not assume the checksum - * is SHA-1, because it may change in the future. - * - * Returns: (transfer none): the generated file name. - * - * Since: 3.0 - */ -PURPLE_AVAILABLE_IN_3_0 -const gchar *purple_image_generate_filename(PurpleImage *image); - -/** - * purple_image_set_friendly_filename: - * @image: the image. - * @filename: the friendly filename. - * - * Sets the "friendly filename" for the @image. This don't have to be a real - * name, because it's used for displaying or as a default file name when the - * user wants to save the @image to the disk. - * - * The provided @filename may either be a full path, or contain - * filesystem-unfriendly characters, because it will be reformatted. - * - * Since: 3.0 - */ -PURPLE_AVAILABLE_IN_3_0 -void purple_image_set_friendly_filename(PurpleImage *image, const gchar *filename); - -/** - * purple_image_get_friendly_filename: - * @image: the image. - * - * Returns the "friendly filename" for the @image, to be displayed or used as - * a default name when saving a file to the disk. - * See #purple_image_set_friendly_filename. - * - * If the friendly filename was not set, it will be generated with - * #purple_image_generate_filename. - * - * Returns: (transfer none): the friendly filename. - * - * Since: 3.0 - */ -PURPLE_AVAILABLE_IN_3_0 -const gchar *purple_image_get_friendly_filename(PurpleImage *image); - -G_END_DECLS - -#endif /* PURPLE_IMAGE_H */
--- a/libpurple/meson.build Thu Jul 31 20:57:19 2025 -0500 +++ b/libpurple/meson.build Thu Aug 07 21:32:18 2025 -0500 @@ -2,7 +2,6 @@ 'accounts.c', 'core.c', 'debug.c', - 'image.c', 'network.c', 'plugins.c', 'prefs.c', @@ -41,6 +40,7 @@ 'purplehistoryadapter.c', 'purplehistorymanager.c', 'purpleidlemanager.c', + 'purpleimage.c', 'purplekeyvaluepair.c', 'purplemarkup.c', 'purplemenu.c', @@ -97,7 +97,6 @@ 'accounts.h', 'core.h', 'debug.h', - 'image.h', 'network.h', 'plugins.h', 'prefs.h', @@ -136,6 +135,7 @@ 'purplehistoryadapter.h', 'purplehistorymanager.h', 'purpleidlemanager.h', + 'purpleimage.h', 'purplekeyvaluepair.h', 'purplemarkup.h', 'purplemenu.h',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/purpleimage.c Thu Aug 07 21:32:18 2025 -0500 @@ -0,0 +1,332 @@ +/* + * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this library; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <birb.h> + +#include "purpleimage.h" + +struct _PurpleImage { + GObject parent; + + char *filename; + + GBytes *contents; +}; + +enum { + PROP_0, + PROP_CONTENTS, + PROP_DATA, + PROP_FILENAME, + PROP_SIZE, + N_PROPERTIES, +}; +static GParamSpec *properties[N_PROPERTIES] = {NULL, }; + +/****************************************************************************** + * Helpers + *****************************************************************************/ +static void +purple_image_set_filename(PurpleImage *image, const char *filename) { + g_return_if_fail(PURPLE_IS_IMAGE(image)); + + if(g_set_str(&image->filename, filename)) { + g_object_notify_by_pspec(G_OBJECT(image), properties[PROP_FILENAME]); + } +} + +static void +purple_image_set_contents(PurpleImage *image, GBytes *contents) { + g_return_if_fail(PURPLE_IS_IMAGE(image)); + + if(image->contents != contents) { + GObject *obj = G_OBJECT(image); + + g_clear_pointer(&image->contents, g_bytes_unref); + if(contents != NULL) { + image->contents = g_bytes_ref(contents); + } + + g_object_freeze_notify(obj); + g_object_notify_by_pspec(G_OBJECT(image), properties[PROP_CONTENTS]); + g_object_notify_by_pspec(G_OBJECT(image), properties[PROP_DATA]); + g_object_notify_by_pspec(G_OBJECT(image), properties[PROP_SIZE]); + g_object_thaw_notify(obj); + } +} + +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +G_DEFINE_FINAL_TYPE(PurpleImage, purple_image, G_TYPE_OBJECT); + +static void +purple_image_finalize(GObject *obj) { + PurpleImage *image = PURPLE_IMAGE(obj); + + g_clear_pointer(&image->contents, g_bytes_unref); + g_clear_pointer(&image->filename, g_free); + + G_OBJECT_CLASS(purple_image_parent_class)->finalize(obj); +} + +static void +purple_image_get_property(GObject *obj, guint param_id, GValue *value, + GParamSpec *pspec) +{ + PurpleImage *image = PURPLE_IMAGE(obj); + gsize size = 0; + + switch (param_id) { + case PROP_CONTENTS: + g_value_set_boxed(value, purple_image_get_contents(image)); + break; + case PROP_DATA: + g_value_set_pointer(value, + (gpointer)purple_image_get_data(image, NULL)); + break; + case PROP_FILENAME: + g_value_set_string(value, purple_image_get_filename(image)); + break; + case PROP_SIZE: + purple_image_get_data(image, &size); + g_value_set_uint64(value, size); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); + break; + } +} + +static void +purple_image_set_property(GObject *obj, guint param_id, const GValue *value, + GParamSpec *pspec) +{ + PurpleImage *image = PURPLE_IMAGE(obj); + + switch (param_id) { + case PROP_FILENAME: + purple_image_set_filename(image, g_value_get_string(value)); + break; + case PROP_CONTENTS: + purple_image_set_contents(image, g_value_get_boxed(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); + break; + } +} + +static void +purple_image_init(G_GNUC_UNUSED PurpleImage *image) { +} + +static void +purple_image_class_init(PurpleImageClass *klass) { + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + + obj_class->finalize = purple_image_finalize; + obj_class->get_property = purple_image_get_property; + obj_class->set_property = purple_image_set_property; + + /** + * PurpleImage:contents: + * + * The contents of the image. + * + * Since: 3.0 + */ + properties[PROP_CONTENTS] = g_param_spec_boxed( + "contents", NULL, NULL, + G_TYPE_BYTES, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS | + G_PARAM_EXPLICIT_NOTIFY); + + /** + * PurpleImage:data: + * + * The raw image data. + * + * Generally the [property@Image:contents] property should be used, but if + * just the data is necessary this saves a step. + * + * Since: 3.0 + */ + properties[PROP_DATA] = g_param_spec_pointer( + "data", NULL, NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * PurpleImage:filename: + * + * The filename for the image if one was provided. + * + * Since: 3.0 + */ + properties[PROP_FILENAME] = g_param_spec_string( + "filename", NULL, NULL, + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS | + G_PARAM_EXPLICIT_NOTIFY); + + /** + * PurpleImage:size: + * + * The size of the image in bytes. + * + * Since: 3.0 + */ + properties[PROP_SIZE] = g_param_spec_uint64( + "size", NULL, NULL, + 0, G_MAXUINT64, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(obj_class, N_PROPERTIES, properties); +} + +/****************************************************************************** + * Public API + *****************************************************************************/ +GBytes * +purple_image_get_contents(PurpleImage *image) { + g_return_val_if_fail(PURPLE_IS_IMAGE(image), NULL); + + return image->contents; +} + +gconstpointer +purple_image_get_data(PurpleImage *image, gsize *size) { + g_return_val_if_fail(PURPLE_IS_IMAGE(image), NULL); + + if(image->contents != NULL) { + return g_bytes_get_data(image->contents, size); + } + + return NULL; +} + +const char * +purple_image_get_filename(PurpleImage *image) { + g_return_val_if_fail(PURPLE_IS_IMAGE(image), NULL); + + return image->filename; +} + +PurpleImage * +purple_image_new_from_bytes(GBytes *bytes) { + g_return_val_if_fail(bytes != NULL, NULL); + + return g_object_new( + PURPLE_TYPE_IMAGE, + "contents", bytes, + NULL); +} + +PurpleImage * +purple_image_new_from_data(gconstpointer data, gsize size) { + PurpleImage *image = NULL; + GBytes *contents = NULL; + + g_return_val_if_fail(data != NULL, NULL); + g_return_val_if_fail(size > 0, NULL); + + contents = g_bytes_new(data, size); + + image = g_object_new( + PURPLE_TYPE_IMAGE, + "contents", contents, + NULL); + + g_bytes_unref(contents); + + return image; +} + +PurpleImage * +purple_image_new_from_filename(const char *filename, GError **error) { + PurpleImage *image = NULL; + GBytes *bytes = NULL; + GError *local_error = NULL; + char *contents = NULL; + gsize length = 0; + + if(!g_file_get_contents(filename, &contents, &length, &local_error)) { + g_propagate_error(error, local_error); + + return NULL; + } + + bytes = g_bytes_new_take(contents, length); + + image = g_object_new( + PURPLE_TYPE_IMAGE, + "contents", bytes, + "filename", filename, + NULL + ); + + g_bytes_unref(bytes); + + return image; +} + +PurpleImage * +purple_image_new_from_resource(const char *path, GError **error) { + PurpleImage *image = NULL; + GBytes *contents = NULL; + GError *local_error = NULL; + + g_return_val_if_fail(!birb_str_is_empty(path), NULL); + + contents = g_resources_lookup_data(path, G_RESOURCE_LOOKUP_FLAGS_NONE, + &local_error); + + if(local_error != NULL) { + g_clear_pointer(&contents, g_bytes_unref); + + g_propagate_error(error, local_error); + + return NULL; + } + + image = g_object_new( + PURPLE_TYPE_IMAGE, + "contents", contents, + NULL); + + g_clear_pointer(&contents, g_bytes_unref); + + return image; +} + +gboolean +purple_image_save(PurpleImage *image, const char *filename, GError **error) { + gconstpointer data = NULL; + gsize size = 0; + + g_return_val_if_fail(PURPLE_IS_IMAGE(image), FALSE); + g_return_val_if_fail(!birb_str_is_empty(filename), FALSE); + + data = g_bytes_get_data(image->contents, &size); + + return g_file_set_contents(filename, data, size, error); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/purpleimage.h Thu Aug 07 21:32:18 2025 -0500 @@ -0,0 +1,163 @@ +/* + * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this library; if not, see <https://www.gnu.org/licenses/>. + */ + +#if !defined(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION) +# error "only <purple.h> may be included directly" +#endif + +#ifndef PURPLE_IMAGE_H +#define PURPLE_IMAGE_H + +#include <glib-object.h> + +#include "purpleversion.h" + + +G_BEGIN_DECLS + +#define PURPLE_TYPE_IMAGE (purple_image_get_type()) + +/** + * PurpleImage: + * + * A container for raw image data. It doesn't manipulate the image data, it + * just stores it in its binary format - png, jpeg etc. + * + * Since: 3.0 + */ +PURPLE_AVAILABLE_IN_3_0 +G_DECLARE_FINAL_TYPE(PurpleImage, purple_image, PURPLE, IMAGE, GObject) + +/** + * purple_image_get_contents: + * + * Gets the contents of the image. + * + * Returns: (transfer none): The contents. + * + * Since: 3.0 + */ +PURPLE_AVAILABLE_IN_3_0 +GBytes *purple_image_get_contents(PurpleImage *image); + +/** + * purple_image_get_data: + * @size: (out) (nullable): a return address for the length of the data + * + * Gets the data of the image. + * + * Optionally the size can be returned as well via @size parameter. + * + * Returns: (transfer none): The data. + * + * Since: 3.0 + */ +PURPLE_AVAILABLE_IN_3_0 +gconstpointer purple_image_get_data(PurpleImage *image, gsize *size); + +/** + * purple_image_get_filename: + * + * Gets the filename to the image + * + * Returns: (nullable): The filename of the image. + * + * Since: 3.0 + */ +PURPLE_AVAILABLE_IN_3_0 +const char *purple_image_get_filename(PurpleImage *image); + +/** + * purple_image_new_from_bytes: + * @bytes: (transfer none): the new image data + * + * Creates an image from bytes. + * + * Returns: (transfer full): The new instance. + * + * Since: 3.0 + */ +PURPLE_AVAILABLE_IN_3_0 +PurpleImage *purple_image_new_from_bytes(GBytes *bytes); + +/** + * purple_image_new_from_data: + * @data: (transfer none): the raw image data + * @size: the size of the raw image data + * + * Creates a new image from raw data. + * + * Returns: (transfer full): The new instance. + * + * Since: 3.0 + */ +PURPLE_AVAILABLE_IN_3_0 +PurpleImage *purple_image_new_from_data(gconstpointer data, gsize size); + +/** + * purple_image_new_from_filename: + * @filename: the filename of the image file + * @error: (out) (nullable): a return address for a #GError + * + * Creates an image from a file. + * + * The @filename must exist, be readable, and have valid image contents. + * + * Returns: (transfer full): The new instance. + * + * Since: 3.0 + */ +PURPLE_AVAILABLE_IN_3_0 +PurpleImage *purple_image_new_from_filename(const char *filename, GError **error); + +/** + * purple_image_new_from_resource: + * @path: the path of the resource + * @error: (out) (nullable): a return address for a #GError + * + * Creates a new image from a resource. + * + * Returns: (transfer full) (nullable): The new image on success; otherwise + * null with @error set. + * + * Since: 3.0 + */ +PURPLE_AVAILABLE_IN_3_0 +PurpleImage *purple_image_new_from_resource(const char *path, GError **error); + +/** + * purple_image_save: + * @filename: the filename to save to + * @error: (out) (nullable): a return address for a #GError + * + * Saves an @image to the disk. + * + * Returns: %TRUE if succeeded, %FALSE otherwise. + * + * Since: 3.0 + */ +PURPLE_AVAILABLE_IN_3_0 +gboolean purple_image_save(PurpleImage *image, const char *filename, GError **error); + +G_END_DECLS + +#endif /* PURPLE_IMAGE_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/tests/image/meson.build Thu Aug 07 21:32:18 2025 -0500 @@ -0,0 +1,20 @@ +TEST_PURPLE_IMAGE_SOURCES = [ + 'test_image.c' +] + +TEST_PURPLE_IMAGE_RESOURCES = gnome.compile_resources( + 'test_image_resources', + 'test_image.gresource.xml', + source_dir : '.', + c_name : 'test_image') +TEST_PURPLE_IMAGE_SOURCES += TEST_PURPLE_IMAGE_RESOURCES + +test_image = executable( + 'test_image', + TEST_PURPLE_IMAGE_SOURCES, + c_args : [ + '-DTEST_DATA_DIR="@0@"'.format(meson.current_source_dir()), + ], + dependencies : [libpurple_dep, glib]) + +test('image', test_image)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/tests/image/test_image.c Thu Aug 07 21:32:18 2025 -0500 @@ -0,0 +1,170 @@ +/* + * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this library; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <glib.h> + +#include <birb.h> + +#include <purple.h> + +// generated via: +// $ cat test-image.png | hexdump -v -e '1 1 "0x%02x," " "' | xargs -n 8 echo +static gsize test_image_data_len = 160; +static const guint8 test_image_data[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, + 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, + 0x08, 0x02, 0x00, 0x00, 0x00, 0xfd, 0xd4, 0x9a, + 0x73, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, + 0x73, 0x00, 0x00, 0x0b, 0x13, 0x00, 0x00, 0x0b, + 0x13, 0x01, 0x00, 0x9a, 0x9c, 0x18, 0x00, 0x00, + 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xe0, + 0x0a, 0x02, 0x16, 0x30, 0x22, 0x28, 0xa4, 0xc9, + 0xdd, 0x00, 0x00, 0x00, 0x1d, 0x69, 0x54, 0x58, + 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, + 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0x64, 0x2e, + 0x65, 0x07, 0x00, 0x00, 0x00, 0x16, 0x49, 0x44, + 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xff, 0xff, + 0x3f, 0x03, 0x03, 0x03, 0xe3, 0xb3, 0x4c, 0xb5, + 0x9b, 0x4e, 0x0b, 0x00, 0x2f, 0xa9, 0x06, 0x2f, + 0x8a, 0xd1, 0xc6, 0xb3, 0x00, 0x00, 0x00, 0x00, + 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, +}; + +/****************************************************************************** + * Helpers + *****************************************************************************/ +static void +test_image(PurpleImage *image, const char *filename, + const guint8 *expected_data, gsize expected_size) +{ + GBytes *actual_contents = NULL; + gconstpointer actual_data = NULL; + gconstpointer contents_data = NULL; + char *actual_filename = NULL; + gsize actual_size = 0; + gsize contents_size = 0; + + birb_assert_type(image, PURPLE_TYPE_IMAGE); + + g_object_get( + G_OBJECT(image), + "contents", &actual_contents, + "data", &actual_data, + "filename", &actual_filename, + "size", &actual_size, + NULL); + + g_assert_cmpmem(actual_data, actual_size, + expected_data, expected_size); + + g_assert_nonnull(actual_contents); + contents_data = g_bytes_get_data(actual_contents, &contents_size); + g_assert_cmpmem(contents_data, contents_size, + expected_data, expected_size); + g_clear_pointer(&actual_contents, g_bytes_unref); + + g_assert_cmpstr(actual_filename, ==, filename); + g_clear_pointer(&actual_filename, g_free); + + g_assert_finalize_object(image); +} + +/****************************************************************************** + * Tests + *****************************************************************************/ +static void +test_image_new_from_bytes(void) { + PurpleImage *image = NULL; + GBytes *contents = NULL; + + contents = g_bytes_new((gconstpointer)test_image_data, + test_image_data_len); + + image = purple_image_new_from_bytes(contents); + g_clear_pointer(&contents, g_bytes_unref); + + test_image(image, NULL, (gconstpointer)test_image_data, + test_image_data_len); +} + +static void +test_image_new_from_data(void) { + PurpleImage *image = NULL; + + image = purple_image_new_from_data(test_image_data, test_image_data_len); + + test_image(image, NULL, (gconstpointer)test_image_data, + test_image_data_len); +} + +static void +test_image_new_from_filename(void) { + PurpleImage *image = NULL; + GError *error = NULL; + char *filename = NULL; + char *expected_data = NULL; + gsize expected_size = 0; + + filename = g_build_filename(TEST_DATA_DIR, "test.png", NULL); + image = purple_image_new_from_filename(filename, &error); + g_assert_no_error(error); + + g_file_get_contents(filename, &expected_data, &expected_size, &error); + g_assert_no_error(error); + + test_image(image, filename, (gconstpointer)expected_data, expected_size); + + g_clear_pointer(&filename, g_free); + g_clear_pointer(&expected_data, g_free); +} + +static void +test_image_new_from_resource(void) { + PurpleImage *image = NULL; + GError *error = NULL; + + image = purple_image_new_from_resource("/im/pidgin/libpurple/tests/image/test.png", + &error); + g_assert_no_error(error); + + test_image(image, NULL, (gconstpointer)test_image_data, + test_image_data_len); +} + +/****************************************************************************** + * Main + *****************************************************************************/ +int +main(int argc, char **argv) { + g_test_init(&argc, &argv, NULL); + g_test_set_nonfatal_assertions(); + + g_test_add_func("/image/new/from-bytes", test_image_new_from_bytes); + g_test_add_func("/image/new/from-data", test_image_new_from_data); + g_test_add_func("/image/new/from-filename", test_image_new_from_filename); + g_test_add_func("/image/new/from-resource", test_image_new_from_resource); + + return g_test_run(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/tests/image/test_image.gresource.xml Thu Aug 07 21:32:18 2025 -0500 @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/im/pidgin/libpurple/tests/image/"> + <file>test.png</file> + </gresource> +</gresources>
--- a/libpurple/tests/meson.build Thu Jul 31 20:57:19 2025 -0500 +++ b/libpurple/tests/meson.build Thu Aug 07 21:32:18 2025 -0500 @@ -27,7 +27,6 @@ 'history_adapter', 'history_manager', 'idle_manager', - 'image', 'keyvaluepair', 'markup', 'menu', @@ -79,7 +78,6 @@ foreach prog : PROGS e = executable(f'test_@prog@', f'test_@prog@.c', c_args : [ - '-DTEST_DATA_DIR="@0@/data"'.format(meson.current_source_dir()), '-DTEST_CACHE_DIR="@0@/cache"'.format(meson.current_build_dir()), ], dependencies : [libpurple_dep, glib], @@ -91,3 +89,4 @@ endforeach subdir('avatar') +subdir('image')
--- a/libpurple/tests/test_image.c Thu Jul 31 20:57:19 2025 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,173 +0,0 @@ -/* - * Purple - * - * Purple is the legal property of its developers, whose names are too - * numerous to list here. Please refer to the COPYRIGHT file distributed - * with this source distribution - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#include <glib.h> -#include <string.h> - -#include <purple.h> - -// generated via: -// $ cat test-image.png | hexdump -v -e '1 1 "0x%02x," " "' | xargs -n 8 echo -static const gsize test_image_data_len = 160; -static const guint8 test_image_data[] = { - 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, - 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, - 0x08, 0x02, 0x00, 0x00, 0x00, 0xfd, 0xd4, 0x9a, - 0x73, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, - 0x73, 0x00, 0x00, 0x0b, 0x13, 0x00, 0x00, 0x0b, - 0x13, 0x01, 0x00, 0x9a, 0x9c, 0x18, 0x00, 0x00, - 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xe0, - 0x0a, 0x02, 0x16, 0x30, 0x22, 0x28, 0xa4, 0xc9, - 0xdd, 0x00, 0x00, 0x00, 0x1d, 0x69, 0x54, 0x58, - 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, - 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0x64, 0x2e, - 0x65, 0x07, 0x00, 0x00, 0x00, 0x16, 0x49, 0x44, - 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xff, 0xff, - 0x3f, 0x03, 0x03, 0x03, 0xe3, 0xb3, 0x4c, 0xb5, - 0x9b, 0x4e, 0x0b, 0x00, 0x2f, 0xa9, 0x06, 0x2f, - 0x8a, 0xd1, 0xc6, 0xb3, 0x00, 0x00, 0x00, 0x00, - 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, -}; - -/****************************************************************************** - * Helpers - *****************************************************************************/ -static void -_test_image(PurpleImage *image, - const guint8 *edata, - gsize elen, - const char *path, - const char *ext, - const char *mimetype) -{ - GBytes *bytes = NULL; - const guint8 *adata = NULL; - gsize alen; - - g_assert(PURPLE_IS_IMAGE(image)); - - bytes = purple_image_get_contents(image); - adata = g_bytes_get_data(bytes, &alen); - g_assert_cmpmem(adata, alen, edata, elen); - g_bytes_unref(bytes); - - /* if the caller provided a path, check it, otherwise just make sure we - * have something. - */ - if(path != NULL) { - g_assert_cmpstr(purple_image_get_path(image), ==, path); - } else { - const char *apath = purple_image_get_path(image); - - g_assert(apath); - g_assert_cmpstr(apath, !=, ""); - } - - g_assert_cmpstr(purple_image_get_extension(image), ==, ext); - g_assert_cmpstr(purple_image_get_mimetype(image), ==, mimetype); - - g_object_unref(image); -} - -/****************************************************************************** - * Tests - *****************************************************************************/ -static void -test_image_new_from_bytes(void) { - GBytes *bytes = g_bytes_new(test_image_data, test_image_data_len); - PurpleImage *image = purple_image_new_from_bytes(bytes); - - _test_image( - image, - g_bytes_get_data(bytes, NULL), - g_bytes_get_size(bytes), - NULL, - "png", - "image/png" - ); - - g_bytes_unref(bytes); -} - - -static void -test_image_new_from_data(void) { - PurpleImage *image = purple_image_new_from_data( - test_image_data, - test_image_data_len - ); - - _test_image( - image, - test_image_data, - test_image_data_len, - NULL, - "png", - "image/png" - ); -} - -static void -test_image_new_from_file(void) { - PurpleImage *image = NULL; - GError *error = NULL; - char *path = NULL; - char *edata = NULL; - gsize elen; - - path = g_build_filename(TEST_DATA_DIR, "test-image.png", NULL); - image = purple_image_new_from_file(path, &error); - g_assert_no_error(error); - - g_file_get_contents(path, &edata, &elen, &error); - g_assert_no_error(error); - - _test_image( - image, - (guint8 *)edata, - elen, - path, - "png", - "image/png" - ); - - g_free(edata); - g_free(path); -} - -/****************************************************************************** - * Main - *****************************************************************************/ -int -main(int argc, char **argv) { - g_test_init(&argc, &argv, NULL); - g_test_set_nonfatal_assertions(); - - g_test_add_func("/image/new-from-bytes", test_image_new_from_bytes); - g_test_add_func("/image/new-from-data", test_image_new_from_data); - g_test_add_func("/image/new-from-file", test_image_new_from_file); - - return g_test_run(); -}
--- a/pidgin/pidginaccounteditor.c Thu Jul 31 20:57:19 2025 -0500 +++ b/pidgin/pidginaccounteditor.c Thu Aug 07 21:32:18 2025 -0500 @@ -279,7 +279,7 @@ editor->avatar_texture = gdk_texture_new_from_bytes(contents, &error); if(error != NULL) { g_warning("Failed to create texture from file %s: %s", - purple_image_get_path(image), + purple_image_get_filename(image), error->message); g_clear_error(&error); } else {
--- a/po/POTFILES.in Thu Jul 31 20:57:19 2025 -0500 +++ b/po/POTFILES.in Thu Aug 07 21:32:18 2025 -0500 @@ -25,7 +25,6 @@ libpurple/accounts.c libpurple/core.c libpurple/debug.c -libpurple/image.c libpurple/network.c libpurple/plugins.c libpurple/prefs.c @@ -63,6 +62,7 @@ libpurple/purplehistoryadapter.c libpurple/purplehistorymanager.c libpurple/purpleidlemanager.c +libpurple/purpleimage.c libpurple/purplekeyvaluepair.c libpurple/purplemarkup.c libpurple/purplemessage.c