1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * file-webp - WebP file format plug-in for the GIMP
5  * Copyright (C) 2015  Nathan Osman
6  * Copyright (C) 2016  Ben Touchette
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include "config.h"
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <stdint.h>
27 
28 #include <webp/decode.h>
29 #include <webp/demux.h>
30 #include <webp/mux.h>
31 
32 #include <gegl.h>
33 
34 #include <libgimp/gimp.h>
35 #include <libgimp/gimpui.h>
36 
37 #include "file-webp-load.h"
38 
39 #include "libgimp/stdplugins-intl.h"
40 
41 
42 static void
create_layer(gint32 image_ID,uint8_t * layer_data,gint32 position,gchar * name,gint width,gint height)43 create_layer (gint32   image_ID,
44               uint8_t *layer_data,
45               gint32   position,
46               gchar   *name,
47               gint     width,
48               gint     height)
49 {
50   gint32         layer_ID;
51   GeglBuffer    *buffer;
52   GeglRectangle  extent;
53 
54   layer_ID = gimp_layer_new (image_ID, name,
55                              width, height,
56                              GIMP_RGBA_IMAGE,
57                              100,
58                              gimp_image_get_default_new_layer_mode (image_ID));
59 
60   gimp_image_insert_layer (image_ID, layer_ID, -1, position);
61 
62   /* Retrieve the buffer for the layer */
63   buffer = gimp_drawable_get_buffer (layer_ID);
64 
65   /* Copy the image data to the region */
66   gegl_rectangle_set (&extent, 0, 0, width, height);
67   gegl_buffer_set (buffer, &extent, 0, NULL, layer_data,
68                    GEGL_AUTO_ROWSTRIDE);
69 
70   /* Flush the drawable and detach */
71   gegl_buffer_flush (buffer);
72   g_object_unref (buffer);
73 }
74 
75 gint32
load_image(const gchar * filename,gboolean interactive,GError ** error)76 load_image (const gchar *filename,
77             gboolean     interactive,
78             GError      **error)
79 {
80   uint8_t          *indata = NULL;
81   gsize             indatalen;
82   gint              width;
83   gint              height;
84   gint32            image_ID;
85   WebPMux          *mux;
86   WebPData          wp_data;
87   GimpColorProfile *profile   = NULL;
88   uint32_t          flags;
89   gboolean          animation = FALSE;
90   gboolean          icc       = FALSE;
91   gboolean          exif      = FALSE;
92   gboolean          xmp       = FALSE;
93 
94   /* Attempt to read the file contents from disk */
95   if (! g_file_get_contents (filename,
96                              (gchar **) &indata,
97                              &indatalen,
98                              error))
99     {
100       return -1;
101     }
102 
103   /* Validate WebP data */
104   if (! WebPGetInfo (indata, indatalen, &width, &height))
105     {
106       g_set_error (error, G_FILE_ERROR, 0,
107                    _("Invalid WebP file '%s'"),
108                    gimp_filename_to_utf8 (filename));
109       return -1;
110     }
111 
112   wp_data.bytes = indata;
113   wp_data.size  = indatalen;
114 
115   mux = WebPMuxCreate (&wp_data, 1);
116   if (! mux)
117     return -1;
118 
119   WebPMuxGetFeatures (mux, &flags);
120 
121   if (flags & ANIMATION_FLAG)
122     animation = TRUE;
123 
124   if (flags & ICCP_FLAG)
125     icc = TRUE;
126 
127   if (flags & EXIF_FLAG)
128     exif = TRUE;
129 
130   if (flags & XMP_FLAG)
131     xmp = TRUE;
132 
133   /* TODO: decode the image in "chunks" or "tiles" */
134   /* TODO: check if an alpha channel is present */
135 
136   /* Create the new image and associated layer */
137   image_ID = gimp_image_new (width, height, GIMP_RGB);
138 
139   if (icc)
140     {
141       WebPData icc_profile;
142 
143       WebPMuxGetChunk (mux, "ICCP", &icc_profile);
144       profile = gimp_color_profile_new_from_icc_profile (icc_profile.bytes,
145                                                          icc_profile.size, NULL);
146       if (profile)
147         gimp_image_set_color_profile (image_ID, profile);
148     }
149 
150   if (! animation)
151     {
152       uint8_t *outdata;
153 
154       /* Attempt to decode the data as a WebP image */
155       outdata = WebPDecodeRGBA (indata, indatalen, &width, &height);
156 
157       /* Check to ensure the image data was loaded correctly */
158       if (! outdata)
159         {
160           WebPMuxDelete (mux);
161           return -1;
162         }
163 
164       create_layer (image_ID, outdata, 0, _("Background"),
165                     width, height);
166 
167       /* Free the image data */
168       free (outdata);
169     }
170   else
171     {
172       WebPAnimDecoder       *dec = NULL;
173       WebPAnimInfo           anim_info;
174       WebPAnimDecoderOptions dec_options;
175       gint                   frame_num = 1;
176       WebPDemuxer           *demux     = NULL;
177       WebPIterator           iter      = { 0, };
178 
179       if (! WebPAnimDecoderOptionsInit (&dec_options))
180         {
181         error:
182           if (dec)
183             WebPAnimDecoderDelete (dec);
184 
185           if (demux)
186             {
187               WebPDemuxReleaseIterator (&iter);
188               WebPDemuxDelete (demux);
189             }
190 
191           WebPMuxDelete (mux);
192           return -1;
193         }
194 
195       /* dec_options.color_mode is MODE_RGBA by default here */
196       dec = WebPAnimDecoderNew (&wp_data, &dec_options);
197       if (! dec)
198         {
199           g_set_error (error, G_FILE_ERROR, 0,
200                        _("Failed to decode animated WebP file '%s'"),
201                        gimp_filename_to_utf8 (filename));
202           goto error;
203         }
204 
205       if (! WebPAnimDecoderGetInfo (dec, &anim_info))
206         {
207           g_set_error (error, G_FILE_ERROR, 0,
208                        _("Failed to decode animated WebP information from '%s'"),
209                        gimp_filename_to_utf8 (filename));
210           goto error;
211         }
212 
213       demux = WebPDemux (&wp_data);
214       if (! demux || ! WebPDemuxGetFrame (demux, 1, &iter))
215         goto error;
216 
217       /* Attempt to decode the data as a WebP animation image */
218       while (WebPAnimDecoderHasMoreFrames (dec))
219         {
220           uint8_t *outdata;
221           int      timestamp;
222           gchar   *name;
223 
224           if (! WebPAnimDecoderGetNext (dec, &outdata, &timestamp))
225             {
226               g_set_error (error, G_FILE_ERROR, 0,
227                            _("Failed to decode animated WebP frame from '%s'"),
228                            gimp_filename_to_utf8 (filename));
229               goto error;
230             }
231 
232           name = g_strdup_printf (_("Frame %d (%dms)"), frame_num, iter.duration);
233           create_layer (image_ID, outdata, 0, name, width, height);
234           g_free (name);
235 
236           frame_num++;
237           WebPDemuxNextFrame (&iter);
238         }
239 
240       WebPAnimDecoderDelete (dec);
241       WebPDemuxReleaseIterator (&iter);
242       WebPDemuxDelete (demux);
243     }
244 
245   /* Free the original compressed data */
246   g_free (indata);
247 
248   if (exif || xmp)
249     {
250       GimpMetadata *metadata;
251       GFile        *file;
252 
253       if (exif)
254         {
255           WebPData exif;
256 
257           WebPMuxGetChunk (mux, "EXIF", &exif);
258         }
259 
260       if (xmp)
261         {
262           WebPData xmp;
263 
264           WebPMuxGetChunk (mux, "XMP ", &xmp);
265         }
266 
267       file = g_file_new_for_path (filename);
268       metadata = gimp_image_metadata_load_prepare (image_ID, "image/webp",
269                                                    file, NULL);
270       if (metadata)
271         {
272           GimpMetadataLoadFlags flags = GIMP_METADATA_LOAD_ALL;
273 
274           if (profile)
275             flags &= ~GIMP_METADATA_LOAD_COLORSPACE;
276 
277           gimp_image_metadata_load_finish (image_ID, "image/webp",
278                                            metadata, flags,
279                                            interactive);
280           g_object_unref (metadata);
281         }
282 
283       g_object_unref (file);
284     }
285 
286   WebPMuxDelete (mux);
287 
288   gimp_image_set_filename (image_ID, filename);
289 
290   if (profile)
291     g_object_unref (profile);
292 
293   return image_ID;
294 }
295