/* PIKA - Photo and Image Kooker Application
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
*
* Original copyright, applying to most contents (license remains unchanged):
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* file-webp - WebP file format plug-in for the PIKA
* Copyright (C) 2015 Nathan Osman
* Copyright (C) 2016 Ben Touchette
*
* 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 3 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, see .
*/
#include "config.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "file-webp-load.h"
#include "libpika/stdplugins-intl.h"
static void
create_layer (PikaImage *image,
uint8_t *layer_data,
gint32 position,
gchar *name,
gint width,
gint height)
{
PikaLayer *layer;
GeglBuffer *buffer;
GeglRectangle extent;
layer = pika_layer_new (image, name,
width, height,
PIKA_RGBA_IMAGE,
100,
pika_image_get_default_new_layer_mode (image));
pika_image_insert_layer (image, layer, NULL, position);
/* Retrieve the buffer for the layer */
buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (layer));
/* Copy the image data to the region */
gegl_rectangle_set (&extent, 0, 0, width, height);
gegl_buffer_set (buffer, &extent, 0, NULL, layer_data,
GEGL_AUTO_ROWSTRIDE);
/* Flush the drawable and detach */
gegl_buffer_flush (buffer);
g_object_unref (buffer);
}
PikaImage *
load_image (GFile *file,
gboolean interactive,
GError **error)
{
uint8_t *indata = NULL;
gsize indatalen;
gint width;
gint height;
PikaImage *image;
WebPMux *mux;
WebPData wp_data;
PikaColorProfile *profile = NULL;
uint32_t flags;
gboolean animation = FALSE;
gboolean icc = FALSE;
gboolean exif = FALSE;
gboolean xmp = FALSE;
/* Attempt to read the file contents from disk */
if (! g_file_get_contents (g_file_peek_path (file),
(gchar **) &indata,
&indatalen,
error))
{
return NULL;
}
/* Validate WebP data */
if (! WebPGetInfo (indata, indatalen, &width, &height))
{
g_set_error (error, G_FILE_ERROR, 0,
_("Invalid WebP file '%s'"),
pika_file_get_utf8_name (file));
return NULL;
}
wp_data.bytes = indata;
wp_data.size = indatalen;
mux = WebPMuxCreate (&wp_data, 1);
if (! mux)
{
return NULL;
}
WebPMuxGetFeatures (mux, &flags);
if (flags & ANIMATION_FLAG)
animation = TRUE;
if (flags & ICCP_FLAG)
icc = TRUE;
if (flags & EXIF_FLAG)
exif = TRUE;
if (flags & XMP_FLAG)
xmp = TRUE;
/* TODO: decode the image in "chunks" or "tiles" */
/* TODO: check if an alpha channel is present */
/* Create the new image and associated layer */
image = pika_image_new (width, height, PIKA_RGB);
if (icc)
{
WebPData icc_profile;
WebPMuxGetChunk (mux, "ICCP", &icc_profile);
profile = pika_color_profile_new_from_icc_profile (icc_profile.bytes,
icc_profile.size, NULL);
if (profile)
pika_image_set_color_profile (image, profile);
}
if (! animation)
{
uint8_t *outdata;
/* Attempt to decode the data as a WebP image */
outdata = WebPDecodeRGBA (indata, indatalen, &width, &height);
/* Check to ensure the image data was loaded correctly */
if (! outdata)
{
WebPMuxDelete (mux);
return NULL;
}
create_layer (image, outdata, 0, _("Background"),
width, height);
/* Free the image data */
free (outdata);
}
else
{
WebPAnimDecoder *dec = NULL;
WebPAnimInfo anim_info;
WebPAnimDecoderOptions dec_options;
gint frame_num = 1;
WebPDemuxer *demux = NULL;
WebPIterator iter = { 0, };
if (! WebPAnimDecoderOptionsInit (&dec_options))
{
error:
if (dec)
WebPAnimDecoderDelete (dec);
if (demux)
{
WebPDemuxReleaseIterator (&iter);
WebPDemuxDelete (demux);
}
WebPMuxDelete (mux);
return NULL;
}
/* dec_options.color_mode is MODE_RGBA by default here */
dec = WebPAnimDecoderNew (&wp_data, &dec_options);
if (! dec)
{
g_set_error (error, G_FILE_ERROR, 0,
_("Failed to decode animated WebP file '%s'"),
pika_file_get_utf8_name (file));
goto error;
}
if (! WebPAnimDecoderGetInfo (dec, &anim_info))
{
g_set_error (error, G_FILE_ERROR, 0,
_("Failed to decode animated WebP information from '%s'"),
pika_file_get_utf8_name (file));
goto error;
}
demux = WebPDemux (&wp_data);
if (! demux || ! WebPDemuxGetFrame (demux, 1, &iter))
goto error;
/* Attempt to decode the data as a WebP animation image */
while (WebPAnimDecoderHasMoreFrames (dec))
{
uint8_t *outdata;
int timestamp;
gchar *name;
if (! WebPAnimDecoderGetNext (dec, &outdata, ×tamp))
{
g_set_error (error, G_FILE_ERROR, 0,
_("Failed to decode animated WebP frame from '%s'"),
pika_file_get_utf8_name (file));
goto error;
}
name = g_strdup_printf (_("Frame %d (%dms)"), frame_num, iter.duration);
create_layer (image, outdata, 0, name, width, height);
g_free (name);
frame_num++;
WebPDemuxNextFrame (&iter);
}
WebPAnimDecoderDelete (dec);
WebPDemuxReleaseIterator (&iter);
WebPDemuxDelete (demux);
}
/* Free the original compressed data */
g_free (indata);
if (exif || xmp)
{
PikaMetadata *metadata;
if (exif)
{
WebPData exif;
WebPMuxGetChunk (mux, "EXIF", &exif);
}
if (xmp)
{
WebPData xmp;
WebPMuxGetChunk (mux, "XMP ", &xmp);
}
metadata = pika_image_metadata_load_prepare (image, "image/webp",
file, NULL);
if (metadata)
{
PikaMetadataLoadFlags flags = PIKA_METADATA_LOAD_ALL;
if (profile)
flags &= ~PIKA_METADATA_LOAD_COLORSPACE;
pika_image_metadata_load_finish (image, "image/webp",
metadata, flags);
g_object_unref (metadata);
}
}
WebPMuxDelete (mux);
if (profile)
g_object_unref (profile);
return image;
}