file-webp-load.c 7.59 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * file-webp - WebP file format plug-in for the GIMP
 * 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
19
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20 21 22 23 24 25 26 27
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

28 29 30 31
#include <webp/decode.h>
#include <webp/demux.h>
#include <webp/mux.h>

32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
#include <gegl.h>

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>

#include "file-webp-load.h"

#include "libgimp/stdplugins-intl.h"


static void
create_layer (gint32   image_ID,
              uint8_t *layer_data,
              gint32   position,
              gchar   *name,
              gint     width,
48
              gint     height)
49 50
{
  gint32         layer_ID;
51
  GeglBuffer    *buffer;
52 53 54 55 56 57
  GeglRectangle  extent;

  layer_ID = gimp_layer_new (image_ID, name,
                             width, height,
                             GIMP_RGBA_IMAGE,
                             100,
58
                             gimp_image_get_default_new_layer_mode (image_ID));
59 60 61 62

  gimp_image_insert_layer (image_ID, layer_ID, -1, position);

  /* Retrieve the buffer for the layer */
63
  buffer = gimp_drawable_get_buffer (layer_ID);
64 65 66

  /* Copy the image data to the region */
  gegl_rectangle_set (&extent, 0, 0, width, height);
67
  gegl_buffer_set (buffer, &extent, 0, NULL, layer_data,
68 69 70
                   GEGL_AUTO_ROWSTRIDE);

  /* Flush the drawable and detach */
71 72
  gegl_buffer_flush (buffer);
  g_object_unref (buffer);
73 74 75 76 77 78 79
}

gint32
load_image (const gchar *filename,
            gboolean     interactive,
            GError      **error)
{
80 81 82 83 84 85 86 87 88 89 90 91
  uint8_t  *indata = NULL;
  gsize     indatalen;
  gint      width;
  gint      height;
  gint32    image_ID;
  WebPMux  *mux;
  WebPData  wp_data;
  uint32_t  flags;
  gboolean  animation = FALSE;
  gboolean  icc       = FALSE;
  gboolean  exif      = FALSE;
  gboolean  xmp       = FALSE;
92 93 94 95 96 97 98 99 100 101 102 103 104

  /* Attempt to read the file contents from disk */
  if (! g_file_get_contents (filename,
                             (gchar **) &indata,
                             &indatalen,
                             error))
    {
      return -1;
    }

  /* Validate WebP data */
  if (! WebPGetInfo (indata, indatalen, &width, &height))
    {
105
      g_set_error (error, G_FILE_ERROR, 0,
106
                   _("Invalid WebP file '%s'"),
107
                   gimp_filename_to_utf8 (filename));
108 109 110 111
      return -1;
    }

  wp_data.bytes = indata;
112
  wp_data.size  = indatalen;
113 114 115 116 117

  mux = WebPMuxCreate (&wp_data, 1);
  if (! mux)
    return -1;

118
  WebPMuxGetFeatures (mux, &flags);
119

120 121
  if (flags & ANIMATION_FLAG)
    animation = TRUE;
122

123 124
  if (flags & ICCP_FLAG)
    icc = TRUE;
125

126 127 128 129 130
  if (flags & EXIF_FLAG)
    exif = TRUE;

  if (flags & XMP_FLAG)
    xmp = TRUE;
131 132 133 134 135 136 137 138 139

  /* TODO: decode the image in "chunks" or "tiles" */
  /* TODO: check if an alpha channel is present */

  /* Create the new image and associated layer */
  image_ID = gimp_image_new (width, height, GIMP_RGB);

  if (! animation)
    {
140 141
      uint8_t *outdata;

142 143 144 145 146
      /* 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)
147 148 149 150
        {
          WebPMuxDelete (mux);
          return -1;
        }
151 152

      create_layer (image_ID, outdata, 0, _("Background"),
153
                    width, height);
154 155 156 157 158 159

      /* Free the image data */
      free (outdata);
    }
  else
    {
160 161 162 163
      WebPAnimDecoder       *dec = NULL;
      WebPAnimInfo           anim_info;
      WebPAnimDecoderOptions dec_options;
      gint                   frame_num = 1;
164 165
      WebPDemuxer           *demux     = NULL;
      WebPIterator           iter      = { 0, };
166

167
      if (! WebPAnimDecoderOptionsInit (&dec_options))
168
        {
169 170 171
        error:
          if (dec)
            WebPAnimDecoderDelete (dec);
172

173 174 175 176 177 178
          if (demux)
            {
              WebPDemuxReleaseIterator (&iter);
              WebPDemuxDelete (demux);
            }

179
          WebPMuxDelete (mux);
180 181
          return -1;
        }
182

183 184 185 186 187 188 189 190 191
      /* 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'"),
                       gimp_filename_to_utf8 (filename));
          goto error;
        }
192

193 194 195 196 197 198 199
      if (! WebPAnimDecoderGetInfo (dec, &anim_info))
        {
          g_set_error (error, G_FILE_ERROR, 0,
                       _("Failed to decode animated WebP information from '%s'"),
                       gimp_filename_to_utf8 (filename));
          goto error;
        }
200

201 202 203 204
      demux = WebPDemux (&wp_data);
      if (! demux || ! WebPDemuxGetFrame (demux, 1, &iter))
        goto error;

205 206 207 208 209 210
      /* Attempt to decode the data as a WebP animation image */
      while (WebPAnimDecoderHasMoreFrames (dec))
        {
          uint8_t *outdata;
          int      timestamp;
          gchar   *name;
211

212 213 214 215 216 217
          if (! WebPAnimDecoderGetNext (dec, &outdata, &timestamp))
            {
              g_set_error (error, G_FILE_ERROR, 0,
                           _("Failed to decode animated WebP frame from '%s'"),
                           gimp_filename_to_utf8 (filename));
              goto error;
218 219
            }

220
          name = g_strdup_printf (_("Frame %d (%dms)"), frame_num, iter.duration);
221 222 223 224
          create_layer (image_ID, outdata, 0, name, width, height);
          g_free (name);

          frame_num++;
225
          WebPDemuxNextFrame (&iter);
226
        }
227 228

      WebPAnimDecoderDelete (dec);
229 230
      WebPDemuxReleaseIterator (&iter);
      WebPDemuxDelete (demux);
231 232
    }

233 234
  /* Free the original compressed data */
  g_free (indata);
235

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
  if (icc)
    {
      WebPData          icc_profile;
      GimpColorProfile *profile;

      WebPMuxGetChunk (mux, "ICCP", &icc_profile);
      profile = gimp_color_profile_new_from_icc_profile (icc_profile.bytes,
                                                         icc_profile.size, NULL);
      if (profile)
        {
          gimp_image_set_color_profile (image_ID, profile);
          g_object_unref (profile);
        }
    }

  if (exif || xmp)
    {
253 254 255
      GimpMetadata *metadata;
      GFile        *file;

256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
      if (exif)
        {
          WebPData exif;

          WebPMuxGetChunk (mux, "EXIF", &exif);
        }

      if (xmp)
        {
          WebPData xmp;

          WebPMuxGetChunk (mux, "XMP ", &xmp);
        }

      file = g_file_new_for_path (filename);
      metadata = gimp_image_metadata_load_prepare (image_ID, "image/webp",
                                                   file, NULL);
      if (metadata)
        {
          gimp_image_metadata_load_finish (image_ID, "image/webp",
                                           metadata, GIMP_METADATA_LOAD_ALL,
                                           interactive);
          g_object_unref (metadata);
        }

      g_object_unref (file);
    }

284 285
  WebPMuxDelete (mux);

286 287 288 289
  gimp_image_set_filename (image_ID, filename);

  return image_ID;
}