int ReadAnimatedImage(const char filename[], AnimatedImage* const image, int dump_frames, const char dump_folder[]) { int ok = 0; WebPData webp_data; WebPDataInit(&webp_data); memset(image, 0, sizeof(*image)); if (!ExUtilReadFile(filename, &webp_data.bytes, &webp_data.size)) { fprintf(stderr, "Error reading file: %s\n", filename); return 0; } if (IsWebP(&webp_data)) { ok = ReadAnimatedWebP(filename, &webp_data, image, dump_frames, dump_folder); } else if (IsGIF(&webp_data)) { ok = ReadAnimatedGIF(filename, image, dump_frames, dump_folder); } else { fprintf(stderr, "Unknown file type: %s. Supported file types are WebP and GIF\n", filename); ok = 0; } if (!ok) ClearAnimatedImage(image); WebPDataClear(&webp_data); return ok; }
static void ResetCommandLineArguments(int argc, const char* argv[], CommandLineArguments* const args) { assert(args != NULL); args->argc_ = argc; args->argv_ = argv; args->own_argv_ = 0; WebPDataInit(&args->argv_data_); }
static void ResetFrameInfo(WebPMuxFrameInfo* const info) { WebPDataInit(&info->bitstream); info->x_offset = 0; info->y_offset = 0; info->duration = 0; info->id = WEBP_CHUNK_ANMF; info->dispose_method = WEBP_MUX_DISPOSE_NONE; info->blend_method = WEBP_MUX_BLEND; transparent_index = -1; // Opaque frame by default. }
static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx, uint32_t nth, WebPData* const data) { assert(mux != NULL); assert(!IsWPI(kChunks[idx].id)); WebPDataInit(data); SWITCH_ID_LIST(IDX_VP8X, mux->vp8x_); SWITCH_ID_LIST(IDX_ICCP, mux->iccp_); SWITCH_ID_LIST(IDX_ANIM, mux->anim_); SWITCH_ID_LIST(IDX_EXIF, mux->exif_); SWITCH_ID_LIST(IDX_XMP, mux->xmp_); SWITCH_ID_LIST(IDX_UNKNOWN, mux->unknown_); return WEBP_MUX_NOT_FOUND; }
// Outputs image data given a bitstream. The bitstream can either be a // single-image WebP file or raw VP8/VP8L data. // Also outputs 'is_lossless' to be true if the given bitstream is lossless. static WebPMuxError GetImageData(const WebPData* const bitstream, WebPData* const image, WebPData* const alpha, int* const is_lossless) { WebPDataInit(alpha); // Default: no alpha. if (bitstream->size < TAG_SIZE || memcmp(bitstream->bytes, "RIFF", TAG_SIZE)) { // It is NOT webp file data. Return input data as is. *image = *bitstream; } else { // It is webp file data. Extract image data from it. const WebPMuxImage* wpi; WebPMux* const mux = WebPMuxCreate(bitstream, 0); if (mux == NULL) return WEBP_MUX_BAD_DATA; wpi = mux->images_; assert(wpi != NULL && wpi->img_ != NULL); *image = wpi->img_->data_; if (wpi->alpha_ != NULL) { *alpha = wpi->alpha_->data_; } WebPMuxDelete(mux); } *is_lossless = VP8LCheckSignature(image->bytes, image->size); return WEBP_MUX_OK; }
int main(int argc, const char *argv[]) { int verbose = 0; int gif_error = GIF_ERROR; WebPMuxError err = WEBP_MUX_OK; int ok = 0; const char *in_file = NULL, *out_file = NULL; FILE* out = NULL; GifFileType* gif = NULL; int frame_duration = 0; int frame_timestamp = 0; GIFDisposeMethod orig_dispose = GIF_DISPOSE_NONE; WebPPicture frame; // Frame rectangle only (not disposed). WebPPicture curr_canvas; // Not disposed. WebPPicture prev_canvas; // Disposed. WebPPicture prev_to_prev_canvas; // Disposed. WebPAnimEncoder* enc = NULL; WebPAnimEncoderOptions enc_options; WebPConfig config; int is_first_frame = 1; // Whether we are processing the first frame. int done; int c; int quiet = 0; WebPData webp_data; int keep_metadata = METADATA_XMP; // ICC not output by default. WebPData icc_data; int stored_icc = 0; // Whether we have already stored an ICC profile. WebPData xmp_data; int stored_xmp = 0; // Whether we have already stored an XMP profile. int loop_count = 0; int stored_loop_count = 0; // Whether we have found an explicit loop count. WebPMux* mux = NULL; int default_kmin = 1; // Whether to use default kmin value. int default_kmax = 1; if (!WebPConfigInit(&config) || !WebPAnimEncoderOptionsInit(&enc_options) || !WebPPictureInit(&frame) || !WebPPictureInit(&curr_canvas) || !WebPPictureInit(&prev_canvas) || !WebPPictureInit(&prev_to_prev_canvas)) { fprintf(stderr, "Error! Version mismatch!\n"); return -1; } config.lossless = 1; // Use lossless compression by default. WebPDataInit(&webp_data); WebPDataInit(&icc_data); WebPDataInit(&xmp_data); if (argc == 1) { Help(); return 0; } for (c = 1; c < argc; ++c) { int parse_error = 0; if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) { Help(); return 0; } else if (!strcmp(argv[c], "-o") && c < argc - 1) { out_file = argv[++c]; } else if (!strcmp(argv[c], "-lossy")) { config.lossless = 0; } else if (!strcmp(argv[c], "-mixed")) { enc_options.allow_mixed = 1; config.lossless = 0; } else if (!strcmp(argv[c], "-q") && c < argc - 1) { config.quality = ExUtilGetFloat(argv[++c], &parse_error); } else if (!strcmp(argv[c], "-m") && c < argc - 1) { config.method = ExUtilGetInt(argv[++c], 0, &parse_error); } else if (!strcmp(argv[c], "-min_size")) { enc_options.minimize_size = 1; } else if (!strcmp(argv[c], "-kmax") && c < argc - 1) { enc_options.kmax = ExUtilGetInt(argv[++c], 0, &parse_error); default_kmax = 0; } else if (!strcmp(argv[c], "-kmin") && c < argc - 1) { enc_options.kmin = ExUtilGetInt(argv[++c], 0, &parse_error); default_kmin = 0; } else if (!strcmp(argv[c], "-f") && c < argc - 1) { config.filter_strength = ExUtilGetInt(argv[++c], 0, &parse_error); } else if (!strcmp(argv[c], "-metadata") && c < argc - 1) { static const struct { const char* option; int flag; } kTokens[] = { { "all", METADATA_ALL }, { "none", 0 }, { "icc", METADATA_ICC }, { "xmp", METADATA_XMP }, }; const size_t kNumTokens = sizeof(kTokens) / sizeof(*kTokens); const char* start = argv[++c]; const char* const end = start + strlen(start); keep_metadata = 0; while (start < end) { size_t i; const char* token = strchr(start, ','); if (token == NULL) token = end; for (i = 0; i < kNumTokens; ++i) { if ((size_t)(token - start) == strlen(kTokens[i].option) && !strncmp(start, kTokens[i].option, strlen(kTokens[i].option))) { if (kTokens[i].flag != 0) { keep_metadata |= kTokens[i].flag; } else { keep_metadata = 0; } break; } } if (i == kNumTokens) { fprintf(stderr, "Error! Unknown metadata type '%.*s'\n", (int)(token - start), start); Help(); return -1; } start = token + 1; } } else if (!strcmp(argv[c], "-mt")) { ++config.thread_level; } else if (!strcmp(argv[c], "-version")) { const int enc_version = WebPGetEncoderVersion(); const int mux_version = WebPGetMuxVersion(); printf("WebP Encoder version: %d.%d.%d\nWebP Mux version: %d.%d.%d\n", (enc_version >> 16) & 0xff, (enc_version >> 8) & 0xff, enc_version & 0xff, (mux_version >> 16) & 0xff, (mux_version >> 8) & 0xff, mux_version & 0xff); return 0; } else if (!strcmp(argv[c], "-quiet")) { quiet = 1; } else if (!strcmp(argv[c], "-v")) { verbose = 1; enc_options.verbose = 1; } else if (!strcmp(argv[c], "--")) { if (c < argc - 1) in_file = argv[++c]; break; } else if (argv[c][0] == '-') { fprintf(stderr, "Error! Unknown option '%s'\n", argv[c]); Help(); return -1; } else { in_file = argv[c]; } if (parse_error) { Help(); return -1; } }
static WebPMuxError MuxPushFrameTileInternal( WebPMux* const mux, const WebPData* const bitstream, int x_offset, int y_offset, int duration, int copy_data, uint32_t tag) { WebPChunk chunk; WebPData image; WebPData alpha; WebPMuxImage wpi; WebPMuxError err; WebPData frame_tile; const int is_frame = (tag == kChunks[IDX_FRAME].tag) ? 1 : 0; int is_lossless; int image_tag; // Sanity checks. if (mux == NULL || bitstream == NULL || bitstream->bytes_ == NULL || bitstream->size_ > MAX_CHUNK_PAYLOAD) { return WEBP_MUX_INVALID_ARGUMENT; } if (x_offset < 0 || x_offset >= MAX_POSITION_OFFSET || y_offset < 0 || y_offset >= MAX_POSITION_OFFSET || duration <= 0 || duration > MAX_DURATION) { return WEBP_MUX_INVALID_ARGUMENT; } // Snap offsets to even positions. x_offset &= ~1; y_offset &= ~1; // If given data is for a whole webp file, // extract only the VP8/VP8L data from it. err = GetImageData(bitstream, &image, &alpha, &is_lossless); if (err != WEBP_MUX_OK) return err; image_tag = is_lossless ? kChunks[IDX_VP8L].tag : kChunks[IDX_VP8].tag; WebPDataInit(&frame_tile); ChunkInit(&chunk); MuxImageInit(&wpi); if (alpha.bytes_ != NULL) { // Add alpha chunk. err = ChunkAssignData(&chunk, &alpha, copy_data, kChunks[IDX_ALPHA].tag); if (err != WEBP_MUX_OK) goto Err; err = ChunkSetNth(&chunk, &wpi.alpha_, 1); if (err != WEBP_MUX_OK) goto Err; ChunkInit(&chunk); // chunk owned by wpi.alpha_ now. } // Add image chunk. err = ChunkAssignData(&chunk, &image, copy_data, image_tag); if (err != WEBP_MUX_OK) goto Err; err = ChunkSetNth(&chunk, &wpi.img_, 1); if (err != WEBP_MUX_OK) goto Err; ChunkInit(&chunk); // chunk owned by wpi.img_ now. // Create frame/tile data. err = CreateFrameTileData(&image, x_offset, y_offset, duration, is_lossless, is_frame, &frame_tile); if (err != WEBP_MUX_OK) goto Err; // Add frame/tile chunk (with copy_data = 1). err = ChunkAssignData(&chunk, &frame_tile, 1, tag); if (err != WEBP_MUX_OK) goto Err; WebPDataClear(&frame_tile); err = ChunkSetNth(&chunk, &wpi.header_, 1); if (err != WEBP_MUX_OK) goto Err; ChunkInit(&chunk); // chunk owned by wpi.header_ now. // Add this WebPMuxImage to mux. err = MuxImagePush(&wpi, &mux->images_); if (err != WEBP_MUX_OK) goto Err; // All is well. return WEBP_MUX_OK; Err: // Something bad happened. WebPDataClear(&frame_tile); ChunkRelease(&chunk); MuxImageRelease(&wpi); return err; }
PyObject* _anim_encoder_assemble(PyObject* self, PyObject* args) { uint8_t* icc_bytes; uint8_t* exif_bytes; uint8_t* xmp_bytes; Py_ssize_t icc_size; Py_ssize_t exif_size; Py_ssize_t xmp_size; WebPData webp_data; WebPAnimEncoderObject* encp = (WebPAnimEncoderObject*)self; WebPAnimEncoder* enc = encp->enc; WebPMux* mux = NULL; PyObject* ret = NULL; if (!PyArg_ParseTuple(args, "s#s#s#", &icc_bytes, &icc_size, &exif_bytes, &exif_size, &xmp_bytes, &xmp_size)) { return NULL; } // Init the output buffer WebPDataInit(&webp_data); // Assemble everything into the output buffer if (!WebPAnimEncoderAssemble(enc, &webp_data)) { PyErr_SetString(PyExc_RuntimeError, WebPAnimEncoderGetError(enc)); return NULL; } // Re-mux to add metadata as needed if (icc_size > 0 || exif_size > 0 || xmp_size > 0) { WebPMuxError err = WEBP_MUX_OK; int i_icc_size = (int)icc_size; int i_exif_size = (int)exif_size; int i_xmp_size = (int)xmp_size; WebPData icc_profile = { icc_bytes, i_icc_size }; WebPData exif = { exif_bytes, i_exif_size }; WebPData xmp = { xmp_bytes, i_xmp_size }; mux = WebPMuxCreate(&webp_data, 1); if (mux == NULL) { PyErr_SetString(PyExc_RuntimeError, "could not re-mux to add metadata"); return NULL; } WebPDataClear(&webp_data); // Add ICCP chunk if (i_icc_size > 0) { err = WebPMuxSetChunk(mux, "ICCP", &icc_profile, 1); if (err != WEBP_MUX_OK) { return HandleMuxError(err, "ICCP"); } } // Add EXIF chunk if (i_exif_size > 0) { err = WebPMuxSetChunk(mux, "EXIF", &exif, 1); if (err != WEBP_MUX_OK) { return HandleMuxError(err, "EXIF"); } } // Add XMP chunk if (i_xmp_size > 0) { err = WebPMuxSetChunk(mux, "XMP ", &xmp, 1); if (err != WEBP_MUX_OK) { return HandleMuxError(err, "XMP"); } } err = WebPMuxAssemble(mux, &webp_data); if (err != WEBP_MUX_OK) { return HandleMuxError(err, NULL); } } // Convert to Python bytes ret = PyBytes_FromStringAndSize((char*)webp_data.bytes, webp_data.size); WebPDataClear(&webp_data); // If we had to re-mux, we should free it now that we're done with it if (mux != NULL) { WebPMuxDelete(mux); } return ret; }
/* Save an animation to disk */ gboolean save_animation(gint32 nLayers, gint32 *allLayers, FILE *outfile, WebPSaveParams *params, GError **error) { gboolean status = FALSE; gboolean innerStatus = TRUE; WebPAnimEncoderOptions enc_options; WebPAnimEncoder *enc = NULL; int frame_timestamp = 0; WebPData webp_data = {0}; WebPMux *mux; WebPMuxAnimParams anim_params = {0}; /* Prepare for encoding an animation */ WebPAnimEncoderOptionsInit(&enc_options); do { int i; gint32 drawable_ID = allLayers[0]; /* Create the encoder */ enc = WebPAnimEncoderNew(gimp_drawable_width(drawable_ID), gimp_drawable_height(drawable_ID), &enc_options); /* Encode each layer */ for (i = 0; i < nLayers; i++) { if ((innerStatus = save_layer(allLayers[i], NULL, NULL, TRUE, enc, frame_timestamp, params, error)) == FALSE) { break; } } /* Check to make sure each layer was encoded correctly */ if (innerStatus == FALSE) { break; } /* Add NULL frame */ WebPAnimEncoderAdd(enc, NULL, frame_timestamp, NULL); /* Initialize the WebP image structure */ WebPDataInit(&webp_data); /* Write the animation to the image */ if (!WebPAnimEncoderAssemble(enc, &webp_data)) { g_set_error(error, G_FILE_ERROR, 0, "Encoding error: '%s'", WebPAnimEncoderGetError(enc)); break; } /* Create a Mux */ mux = WebPMuxCreate(&webp_data, 1); /* Set animation parameters */ anim_params.loop_count = params->loop == TRUE ? 0 : 1; WebPMuxSetAnimationParams(mux, &anim_params); /* Assemble the image */ WebPMuxAssemble(mux, &webp_data); /* Write to disk */ if (fwrite(webp_data.bytes, webp_data.size, 1, outfile) != 1) { break; } /* Everything succeeded */ status = TRUE; } while(0); /* Free image data */ WebPDataClear(&webp_data); /* Free the animation encoder */ if (enc) { WebPAnimEncoderDelete(enc); } return status; }