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, ×tamp))
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