static int ReadSubImage(GifFileType* gif, WebPPicture* pic, WebPPicture* view) { const GifImageDesc image_desc = gif->Image; const int offset_x = image_desc.Left; const int offset_y = image_desc.Top; const int sub_w = image_desc.Width; const int sub_h = image_desc.Height; uint32_t* dst = NULL; uint8_t* tmp = NULL; int ok = 0; // Use a view for the sub-picture: if (!WebPPictureView(pic, offset_x, offset_y, sub_w, sub_h, view)) { fprintf(stderr, "Sub-image %dx%d at position %d,%d is invalid!\n", sub_w, sub_h, offset_x, offset_y); goto End; } dst = view->argb; tmp = (uint8_t*)malloc(sub_w * sizeof(*tmp)); if (tmp == NULL) goto End; if (image_desc.Interlace) { // Interlaced image. // We need 4 passes, with the following offsets and jumps. const int interlace_offsets[] = { 0, 4, 2, 1 }; const int interlace_jumps[] = { 8, 8, 4, 2 }; int pass; for (pass = 0; pass < 4; ++pass) { int y; for (y = interlace_offsets[pass]; y < sub_h; y += interlace_jumps[pass]) { if (DGifGetLine(gif, tmp, sub_w) == GIF_ERROR) goto End; Remap(tmp, gif, dst + y * view->argb_stride, sub_w); } } } else { // Non-interlaced image. int y; for (y = 0; y < sub_h; ++y) { if (DGifGetLine(gif, tmp, sub_w) == GIF_ERROR) goto End; Remap(tmp, gif, dst + y * view->argb_stride, sub_w); } } // re-align the view with even offset (and adjust dimensions if needed). WebPPictureView(pic, offset_x & ~1, offset_y & ~1, sub_w + (offset_x & 1), sub_h + (offset_y & 1), view); ok = 1; End: free(tmp); return ok; }
// Given previous and current canvas, picks the optimal rectangle for the // current frame. The initial guess for 'rect' will be the full canvas. static int GetSubRect(const WebPPicture* const prev_canvas, const WebPPicture* const curr_canvas, int is_key_frame, int is_first_frame, int empty_rect_allowed, FrameRect* const rect, WebPPicture* const sub_frame) { rect->x_offset_ = 0; rect->y_offset_ = 0; rect->width_ = curr_canvas->width; rect->height_ = curr_canvas->height; if (!is_key_frame || is_first_frame) { // Optimize frame rectangle. // Note: This behaves as expected for first frame, as 'prev_canvas' is // initialized to a fully transparent canvas in the beginning. MinimizeChangeRectangle(prev_canvas, curr_canvas, rect); } if (IsEmptyRect(rect)) { if (empty_rect_allowed) { // No need to get 'sub_frame'. return 1; } else { // Force a 1x1 rectangle. rect->width_ = 1; rect->height_ = 1; assert(rect->x_offset_ == 0); assert(rect->y_offset_ == 0); } } SnapToEvenOffsets(rect); return WebPPictureView(curr_canvas, rect->x_offset_, rect->y_offset_, rect->width_, rect->height_, sub_frame); }
int WebPPictureDistortion(const WebPPicture* src, const WebPPicture* ref, int type, float results[5]) { int w, h, c; int ok = 0; WebPPicture p0, p1; double total_size = 0., total_distortion = 0.; if (src == NULL || ref == NULL || src->width != ref->width || src->height != ref->height || results == NULL) { return 0; } VP8SSIMDspInit(); if (!WebPPictureInit(&p0) || !WebPPictureInit(&p1)) return 0; w = src->width; h = src->height; if (!WebPPictureView(src, 0, 0, w, h, &p0)) goto Error; if (!WebPPictureView(ref, 0, 0, w, h, &p1)) goto Error; // We always measure distortion in ARGB space. if (p0.use_argb == 0 && !WebPPictureYUVAToARGB(&p0)) goto Error; if (p1.use_argb == 0 && !WebPPictureYUVAToARGB(&p1)) goto Error; for (c = 0; c < 4; ++c) { float distortion; const size_t stride0 = 4 * (size_t)p0.argb_stride; const size_t stride1 = 4 * (size_t)p1.argb_stride; if (!WebPPlaneDistortion((const uint8_t*)p0.argb + c, stride0, (const uint8_t*)p1.argb + c, stride1, w, h, 4, type, &distortion, results + c)) { goto Error; } total_distortion += distortion; total_size += w * h; } results[4] = (type == 1) ? (float)GetLogSSIM(total_distortion, total_size) : (float)GetPSNR(total_distortion, total_size); ok = 1; Error: WebPPictureFree(&p0); WebPPictureFree(&p1); return ok; }
int GIFReadFrame(GifFileType* const gif, int transparent_index, GIFFrameRect* const gif_rect, WebPPicture* const picture) { WebPPicture sub_image; const GifImageDesc* const image_desc = &gif->Image; uint32_t* dst = NULL; uint8_t* tmp = NULL; int ok = 0; GIFFrameRect rect = { image_desc->Left, image_desc->Top, image_desc->Width, image_desc->Height }; *gif_rect = rect; // Use a view for the sub-picture: if (!WebPPictureView(picture, rect.x_offset, rect.y_offset, rect.width, rect.height, &sub_image)) { fprintf(stderr, "Sub-image %dx%d at position %d,%d is invalid!\n", rect.width, rect.height, rect.x_offset, rect.y_offset); return 0; } dst = sub_image.argb; tmp = (uint8_t*)malloc(rect.width * sizeof(*tmp)); if (tmp == NULL) goto End; if (image_desc->Interlace) { // Interlaced image. // We need 4 passes, with the following offsets and jumps. const int interlace_offsets[] = { 0, 4, 2, 1 }; const int interlace_jumps[] = { 8, 8, 4, 2 }; int pass; for (pass = 0; pass < 4; ++pass) { int y; for (y = interlace_offsets[pass]; y < rect.height; y += interlace_jumps[pass]) { if (DGifGetLine(gif, tmp, rect.width) == GIF_ERROR) goto End; Remap(gif, tmp, rect.width, transparent_index, dst + y * sub_image.argb_stride); } } } else { // Non-interlaced image. int y; for (y = 0; y < rect.height; ++y) { if (DGifGetLine(gif, tmp, rect.width) == GIF_ERROR) goto End; Remap(gif, tmp, rect.width, transparent_index, dst + y * sub_image.argb_stride); } } ok = 1; End: if (!ok) picture->error_code = sub_image.error_code; WebPPictureFree(&sub_image); free(tmp); return ok; }
// Read the GIF image frame. static int ReadFrame(GifFileType* const gif, WebPFrameRect* const gif_rect, WebPPicture* const sub_image, WebPPicture* const curr) { const GifImageDesc image_desc = gif->Image; uint32_t* dst = NULL; uint8_t* tmp = NULL; int ok = 0; WebPFrameRect rect = { image_desc.Left, image_desc.Top, image_desc.Width, image_desc.Height }; *gif_rect = rect; // Use a view for the sub-picture: if (!WebPPictureView(curr, rect.x_offset, rect.y_offset, rect.width, rect.height, sub_image)) { fprintf(stderr, "Sub-image %dx%d at position %d,%d is invalid!\n", rect.width, rect.height, rect.x_offset, rect.y_offset); goto End; } dst = sub_image->argb; tmp = (uint8_t*)malloc(rect.width * sizeof(*tmp)); if (tmp == NULL) goto End; if (image_desc.Interlace) { // Interlaced image. // We need 4 passes, with the following offsets and jumps. const int interlace_offsets[] = { 0, 4, 2, 1 }; const int interlace_jumps[] = { 8, 8, 4, 2 }; int pass; for (pass = 0; pass < 4; ++pass) { int y; for (y = interlace_offsets[pass]; y < rect.height; y += interlace_jumps[pass]) { if (DGifGetLine(gif, tmp, rect.width) == GIF_ERROR) goto End; Remap(tmp, gif, dst + y * sub_image->argb_stride, rect.width); } } } else { // Non-interlaced image. int y; for (y = 0; y < rect.height; ++y) { if (DGifGetLine(gif, tmp, rect.width) == GIF_ERROR) goto End; Remap(tmp, gif, dst + y * sub_image->argb_stride, rect.width); } } ok = 1; End: free(tmp); return ok; }
// Given previous and current canvas, picks the optimal rectangle for the // current frame. The initial guess for 'rect' will be the full canvas. static int GetSubRect(const WebPPicture* const prev_canvas, const WebPPicture* const curr_canvas, int is_key_frame, int is_first_frame, FrameRect* const rect, WebPPicture* const sub_frame) { rect->x_offset_ = 0; rect->y_offset_ = 0; rect->width_ = curr_canvas->width; rect->height_ = curr_canvas->height; if (!is_key_frame || is_first_frame) { // Optimize frame rectangle. // Note: This behaves as expected for first frame, as 'prev_canvas' is // initialized to a fully transparent canvas in the beginning. MinimizeChangeRectangle(prev_canvas, curr_canvas, rect); } SnapToEvenOffsets(rect); return WebPPictureView(curr_canvas, rect->x_offset_, rect->y_offset_, rect->width_, rect->height_, sub_frame); }
int webp_encode(const char *in_file, const char *out_file, const FfiWebpEncodeConfig *encode_config) { int return_value = -1; FILE *out = NULL; int keep_alpha = 1; WebPPicture picture; WebPConfig config; // OPTIONS BEGIN if (encode_config->lossless == 0 || encode_config->lossless == 1){ config.lossless = encode_config->lossless; } if (encode_config->quality >= 0 && encode_config->quality <= 100){ config.quality = encode_config->quality; } if (encode_config->method >= 0 && encode_config->method <= 6){ config.method = encode_config->method; } if (encode_config->target_size > 0){ config.target_size = encode_config->target_size; } if (encode_config->target_PSNR > 0){ config.target_PSNR = encode_config->target_PSNR; } if (encode_config->segments >= 0 && encode_config->segments <= 4){ config.segments = encode_config->segments; } if (encode_config->sns_strength >= 0 && encode_config->sns_strength <= 100){ config.sns_strength = encode_config->sns_strength; } if (encode_config->filter_strength >= 0 && encode_config->filter_strength <= 100){ config.filter_strength = encode_config->filter_strength; } if (encode_config->filter_sharpness >= 0 && encode_config->filter_sharpness <= 7){ config.filter_sharpness = encode_config->filter_sharpness; } if (encode_config->filter_type == 0 || encode_config->filter_type == 1){ config.filter_type = encode_config->filter_type; } if (encode_config->autofilter == 0 || encode_config->autofilter == 1){ config.autofilter = encode_config->autofilter; } if (encode_config->alpha_compression == 0 || encode_config->alpha_compression == 1){ config.alpha_compression = encode_config->alpha_compression; } if (encode_config->alpha_filtering >= 0 && encode_config->alpha_filtering <= 2){ config.alpha_filtering = encode_config->alpha_filtering; } if (encode_config->alpha_quality >= 0 && encode_config->alpha_quality <= 100){ config.alpha_quality = encode_config->alpha_quality; } if (encode_config->pass >= 0 && encode_config->pass <= 10){ config.pass = encode_config->pass; } if (encode_config->show_compressed >= 0){ config.show_compressed = encode_config->show_compressed; } if (encode_config->preprocessing == 0 || encode_config->preprocessing == 1){ config.preprocessing = encode_config->preprocessing; } if (encode_config->partitions >= 0 && encode_config->partitions <= 3){ config.partitions = encode_config->partitions; } if (encode_config->partition_limit >= 0 && encode_config->partition_limit <= 100){ config.partition_limit = encode_config->partition_limit; } if ((encode_config->width | encode_config->height) > 0){ picture.width = encode_config->width; picture.height = encode_config->height; } // OPTIONS END if (!WebPPictureInit(&picture) || !WebPConfigInit(&config)) { //fprintf(stderr, "Error! Version mismatch!\n"); return 1; } if (!WebPValidateConfig(&config)) { //fprintf(stderr, "Error! Invalid configuration.\n"); return_value = 2; goto Error; } if (!UtilReadPicture(in_file, &picture, keep_alpha)) { //fprintf(stderr, "Error! Cannot read input picture file '%s'\n", in_file); return_value = 3; goto Error; } out = fopen(out_file, "wb"); if (out == NULL) { //fprintf(stderr, "Error! Cannot open output file '%s'\n", out_file); return_value = 4; goto Error; } picture.writer = EncodeWriter; picture.custom_ptr = (void*)out; if ((encode_config->crop_w | encode_config->crop_h) > 0){ if (!WebPPictureView(&picture, encode_config->crop_x, encode_config->crop_y, encode_config->crop_w, encode_config->crop_h, &picture)) { //fprintf(stderr, "Error! Cannot crop picture\n"); return_value = 5; goto Error; } } if ((encode_config->resize_w | encode_config->resize_h) > 0) { if (!WebPPictureRescale(&picture, encode_config->resize_w, encode_config->resize_h)) { //fprintf(stderr, "Error! Cannot resize picture\n"); return_value = 6; goto Error; } } if (picture.extra_info_type > 0) { AllocExtraInfo(&picture); } if (!WebPEncode(&config, &picture)) { //fprintf(stderr, "Error! Cannot encode picture as WebP\n"); return_value = 7; goto Error; } return_value = 0; Error: free(picture.extra_info); WebPPictureFree(&picture); if (out != NULL) { fclose(out); } return return_value; }
// Optimize the image frame for WebP and encode it. static int OptimizeAndEncodeFrame( const WebPConfig* const config, const WebPFrameRect* const gif_rect, WebPPicture* const curr, WebPPicture* const prev_canvas, WebPPicture* const curr_canvas, WebPPicture* const sub_image, WebPMuxFrameInfo* const info, WebPFrameCache* const cache) { WebPFrameRect rect = *gif_rect; // Snap to even offsets (and adjust dimensions if needed). rect.width += (rect.x_offset & 1); rect.height += (rect.y_offset & 1); rect.x_offset &= ~1; rect.y_offset &= ~1; if (!WebPPictureView(curr, rect.x_offset, rect.y_offset, rect.width, rect.height, sub_image)) { return 0; } info->x_offset = rect.x_offset; info->y_offset = rect.y_offset; if (is_first_frame || WebPUtilIsKeyFrame(curr, &rect, prev_canvas)) { // Add this as a key frame. if (!WebPFrameCacheAddFrame(cache, config, NULL, NULL, info, sub_image)) { return 0; } // Update prev_canvas by simply copying from 'curr'. WebPUtilCopyPixels(curr, prev_canvas); } else { if (!config->lossless) { // For lossy compression, it's better to replace transparent pixels of // 'curr' with actual RGB values, whenever possible. WebPUtilReduceTransparency(prev_canvas, &rect, curr); WebPUtilFlattenSimilarBlocks(prev_canvas, &rect, curr); } if (!WebPFrameCacheShouldTryKeyFrame(cache)) { // Add this as a frame rectangle. if (!WebPFrameCacheAddFrame(cache, config, info, sub_image, NULL, NULL)) { return 0; } // Update prev_canvas by blending 'curr' into it. WebPUtilBlendPixels(curr, gif_rect, prev_canvas); } else { WebPPicture full_image; WebPMuxFrameInfo full_image_info; int ok; // Convert to a key frame. WebPUtilCopyPixels(curr, curr_canvas); WebPUtilConvertToKeyFrame(prev_canvas, &rect, curr_canvas); if (!WebPPictureView(curr_canvas, rect.x_offset, rect.y_offset, rect.width, rect.height, &full_image)) { return 0; } full_image_info = *info; full_image_info.x_offset = rect.x_offset; full_image_info.y_offset = rect.y_offset; // Add both variants to cache: frame rectangle and key frame. ok = WebPFrameCacheAddFrame(cache, config, info, sub_image, &full_image_info, &full_image); WebPPictureFree(&full_image); if (!ok) return 0; // Update prev_canvas by simply copying from 'curr_canvas'. WebPUtilCopyPixels(curr_canvas, prev_canvas); } } return 1; }