mirror of
				https://github.com/webmproject/libwebp.git
				synced 2025-10-31 10:25:46 +01:00 
			
		
		
		
	gif2webp: Separate out each step into a method
We now have ReadFrame(), OptimizeFrame(), EncodeFrame() and DisposeFrame() methods. Change-Id: I522834bad18dd6a7a3ddac7c00dfd829c48248f8
This commit is contained in:
		| @@ -34,7 +34,13 @@ | |||||||
|  |  | ||||||
| //------------------------------------------------------------------------------ | //------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
| static int transparent_index = -1;  // No transparency by default. | // Global variables gathered in a struct. | ||||||
|  | static struct { | ||||||
|  |   int transparent_index;         // Index of transparent color in the color map. | ||||||
|  |   int is_first_frame; | ||||||
|  |   size_t count_since_key_frame;  // Frames seen since the last key frame. | ||||||
|  |   size_t key_frame_interval;     // Max distance between key frames. | ||||||
|  | } kParams; | ||||||
|  |  | ||||||
| static void ClearRectangle(WebPPicture* const picture, | static void ClearRectangle(WebPPicture* const picture, | ||||||
|                            int left, int top, int width, int height) { |                            int left, int top, int width, int height) { | ||||||
| @@ -104,7 +110,7 @@ static void Remap(const uint8_t* const src, const GifFileType* const gif, | |||||||
|  |  | ||||||
|   for (i = 0; i < len; ++i) { |   for (i = 0; i < len; ++i) { | ||||||
|     const GifColorType c = colors[src[i]]; |     const GifColorType c = colors[src[i]]; | ||||||
|     dst[i] = (src[i] == transparent_index) ? TRANSPARENT_COLOR |     dst[i] = (src[i] == kParams.transparent_index) ? TRANSPARENT_COLOR | ||||||
|            : c.Blue | (c.Green << 8) | (c.Red << 16) | (0xff << 24); |            : c.Blue | (c.Green << 8) | (c.Red << 16) | (0xff << 24); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -117,6 +123,8 @@ static int IsKeyFrame(const WebPPicture* const curr, | |||||||
|   int i, j; |   int i, j; | ||||||
|   int is_key_frame = 1; |   int is_key_frame = 1; | ||||||
|  |  | ||||||
|  |   if (kParams.is_first_frame) return 1; | ||||||
|  |  | ||||||
|   // If previous canvas (with previous frame disposed) is all transparent, |   // If previous canvas (with previous frame disposed) is all transparent, | ||||||
|   // current frame is a key frame. |   // current frame is a key frame. | ||||||
|   for (i = 0; i < prev->width; ++i) { |   for (i = 0; i < prev->width; ++i) { | ||||||
| @@ -183,11 +191,9 @@ static void ConvertToKeyFrame(WebPPicture* const curr, | |||||||
|   rect->height = curr->height; |   rect->height = curr->height; | ||||||
| } | } | ||||||
|  |  | ||||||
| static int ReadSubImage(GifFileType* gif, WebPPicture* const prev, | // Read the GIF image frame. | ||||||
|                         int is_first_frame, size_t key_frame_interval, | static int ReadFrame(GifFileType* const gif, WebPFrameRect* const gif_rect, | ||||||
|                         size_t* const count_since_key_frame, |                      WebPPicture* const sub_image, WebPPicture* const curr) { | ||||||
|                         WebPFrameRect* const gif_rect, WebPPicture* sub_image, |  | ||||||
|                         WebPMuxFrameInfo* const info, WebPPicture* const curr) { |  | ||||||
|   const GifImageDesc image_desc = gif->Image; |   const GifImageDesc image_desc = gif->Image; | ||||||
|   uint32_t* dst = NULL; |   uint32_t* dst = NULL; | ||||||
|   uint8_t* tmp = NULL; |   uint8_t* tmp = NULL; | ||||||
| @@ -229,6 +235,19 @@ static int ReadSubImage(GifFileType* gif, WebPPicture* const prev, | |||||||
|       Remap(tmp, gif, dst + y * sub_image->argb_stride, rect.width); |       Remap(tmp, gif, dst + y * sub_image->argb_stride, rect.width); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |   ok = 1; | ||||||
|  |  | ||||||
|  |  End: | ||||||
|  |   free(tmp); | ||||||
|  |   return ok; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Optimize the image frame for WebP. | ||||||
|  | static void OptimizeFrame(const WebPFrameRect* const gif_rect, | ||||||
|  |                           WebPPicture* const curr, WebPPicture* const prev, | ||||||
|  |                           WebPPicture* const sub_image, | ||||||
|  |                           WebPMuxFrameInfo* const info) { | ||||||
|  |   WebPFrameRect rect = *gif_rect; | ||||||
|  |  | ||||||
|   // Snap to even offsets (and adjust dimensions if needed). |   // Snap to even offsets (and adjust dimensions if needed). | ||||||
|   rect.width += (rect.x_offset & 1); |   rect.width += (rect.x_offset & 1); | ||||||
| @@ -237,17 +256,17 @@ static int ReadSubImage(GifFileType* gif, WebPPicture* const prev, | |||||||
|   rect.y_offset &= ~1; |   rect.y_offset &= ~1; | ||||||
|  |  | ||||||
|   // Make this a key frame if needed. |   // Make this a key frame if needed. | ||||||
|   if (is_first_frame || IsKeyFrame(curr, &rect, prev)) { |   if (IsKeyFrame(curr, &rect, prev)) { | ||||||
|     *count_since_key_frame = 0; |     kParams.count_since_key_frame = 0; | ||||||
|   } else { |   } else { | ||||||
|     ++*count_since_key_frame; |     ++kParams.count_since_key_frame; | ||||||
|     if (*count_since_key_frame > key_frame_interval) { |     if (kParams.count_since_key_frame > kParams.key_frame_interval) { | ||||||
|       ConvertToKeyFrame(curr, prev, &rect); |       ConvertToKeyFrame(curr, prev, &rect); | ||||||
|       *count_since_key_frame = 0; |       kParams.count_since_key_frame = 0; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (*count_since_key_frame == 0) { |   if (kParams.count_since_key_frame == 0) { | ||||||
|     info->blend_method = WEBP_MUX_NO_BLEND;  // Key frame, so no need to blend. |     info->blend_method = WEBP_MUX_NO_BLEND;  // Key frame, so no need to blend. | ||||||
|   } else { |   } else { | ||||||
|     info->blend_method = WEBP_MUX_BLEND;  // The blending method in GIF. |     info->blend_method = WEBP_MUX_BLEND;  // The blending method in GIF. | ||||||
| @@ -266,16 +285,52 @@ static int ReadSubImage(GifFileType* gif, WebPPicture* const prev, | |||||||
|                   sub_image); |                   sub_image); | ||||||
|   info->x_offset = rect.x_offset; |   info->x_offset = rect.x_offset; | ||||||
|   info->y_offset = rect.y_offset; |   info->y_offset = rect.y_offset; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int EncodeFrame(const WebPConfig* const config, | ||||||
|  |                        WebPPicture* const sub_image, | ||||||
|  |                        WebPData* const encoded_data) { | ||||||
|  |   int ok = 0; | ||||||
|  |   WebPMemoryWriter memory; | ||||||
|  |   if (!config->lossless) { | ||||||
|  |     // We need to call BGRA variant because of the way we do Remap(). Note | ||||||
|  |     // that 'sub_image' will no longer be a view and own some memory. | ||||||
|  |     if (!WebPPictureImportBGRA( | ||||||
|  |             sub_image, (uint8_t*)sub_image->argb, | ||||||
|  |             sub_image->argb_stride * sizeof(*sub_image->argb))) { | ||||||
|  |       goto End; | ||||||
|  |     } | ||||||
|  |     sub_image->use_argb = 0; | ||||||
|  |   } else { | ||||||
|  |     sub_image->use_argb = 1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   sub_image->writer = WebPMemoryWrite; | ||||||
|  |   sub_image->custom_ptr = &memory; | ||||||
|  |   WebPMemoryWriterInit(&memory); | ||||||
|  |   if (!WebPEncode(config, sub_image)) goto End; | ||||||
|  |  | ||||||
|  |   encoded_data->bytes = memory.mem; | ||||||
|  |   encoded_data->size  = memory.size; | ||||||
|   ok = 1; |   ok = 1; | ||||||
|  |  | ||||||
|  End: |  End: | ||||||
|   free(tmp); |  | ||||||
|   return ok; |   return ok; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static void DisposeFrame(WebPMuxAnimDispose dispose_method, | ||||||
|  |                          const WebPFrameRect* const gif_rect, | ||||||
|  |                          WebPPicture* const frame, WebPPicture* const canvas) { | ||||||
|  |   if (dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) { | ||||||
|  |     ClearPicture(frame, NULL); | ||||||
|  |     ClearPicture(canvas, gif_rect); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| static int GetBackgroundColor(const ColorMapObject* const color_map, | static int GetBackgroundColor(const ColorMapObject* const color_map, | ||||||
|                               GifWord bgcolor_idx, uint32_t* const bgcolor) { |                               GifWord bgcolor_idx, uint32_t* const bgcolor) { | ||||||
|   if (transparent_index != -1 && bgcolor_idx == transparent_index) { |   if (kParams.transparent_index != -1 && | ||||||
|  |       bgcolor_idx == kParams.transparent_index) { | ||||||
|     *bgcolor = TRANSPARENT_COLOR;  // Special case. |     *bgcolor = TRANSPARENT_COLOR;  // Special case. | ||||||
|     return 1; |     return 1; | ||||||
|   } else if (color_map == NULL || color_map->Colors == NULL |   } else if (color_map == NULL || color_map->Colors == NULL | ||||||
| @@ -358,9 +413,6 @@ int main(int argc, const char *argv[]) { | |||||||
|   WebPMuxFrameInfo frame; |   WebPMuxFrameInfo frame; | ||||||
|   WebPMuxAnimParams anim = { WHITE_COLOR, 0 }; |   WebPMuxAnimParams anim = { WHITE_COLOR, 0 }; | ||||||
|  |  | ||||||
|   int is_first_frame = 1; |  | ||||||
|   size_t count_since_key_frame = 0;  // Frames seen since the last key frame. |  | ||||||
|   size_t key_frame_interval = 9;  // Max distance between key frames. |  | ||||||
|   int done; |   int done; | ||||||
|   int c; |   int c; | ||||||
|   int quiet = 0; |   int quiet = 0; | ||||||
| @@ -370,6 +422,12 @@ int main(int argc, const char *argv[]) { | |||||||
|   int stored_icc = 0;  // Whether we have already stored an ICC profile. |   int stored_icc = 0;  // Whether we have already stored an ICC profile. | ||||||
|   int stored_xmp = 0; |   int stored_xmp = 0; | ||||||
|  |  | ||||||
|  |   // Initialize global variables. | ||||||
|  |   kParams.transparent_index = -1;  // No transparency by default. | ||||||
|  |   kParams.is_first_frame = 1; | ||||||
|  |   kParams.key_frame_interval = 9; | ||||||
|  |   kParams.count_since_key_frame = 0; | ||||||
|  |  | ||||||
|   memset(&frame, 0, sizeof(frame)); |   memset(&frame, 0, sizeof(frame)); | ||||||
|   frame.id = WEBP_CHUNK_ANMF; |   frame.id = WEBP_CHUNK_ANMF; | ||||||
|   frame.dispose_method = WEBP_MUX_DISPOSE_BACKGROUND; |   frame.dispose_method = WEBP_MUX_DISPOSE_BACKGROUND; | ||||||
| @@ -400,8 +458,8 @@ int main(int argc, const char *argv[]) { | |||||||
|     } else if (!strcmp(argv[c], "-m") && c < argc - 1) { |     } else if (!strcmp(argv[c], "-m") && c < argc - 1) { | ||||||
|       config.method = strtol(argv[++c], NULL, 0); |       config.method = strtol(argv[++c], NULL, 0); | ||||||
|     } else if (!strcmp(argv[c], "-kmax") && c < argc - 1) { |     } else if (!strcmp(argv[c], "-kmax") && c < argc - 1) { | ||||||
|       key_frame_interval = strtoul(argv[++c], NULL, 0); |       kParams.key_frame_interval = strtoul(argv[++c], NULL, 0); | ||||||
|       if (key_frame_interval == 0) key_frame_interval = ~0; |       if (kParams.key_frame_interval == 0) kParams.key_frame_interval = ~0; | ||||||
|     } else if (!strcmp(argv[c], "-f") && c < argc - 1) { |     } else if (!strcmp(argv[c], "-f") && c < argc - 1) { | ||||||
|       config.filter_strength = strtol(argv[++c], NULL, 0); |       config.filter_strength = strtol(argv[++c], NULL, 0); | ||||||
|     } else if (!strcmp(argv[c], "-version")) { |     } else if (!strcmp(argv[c], "-version")) { | ||||||
| @@ -467,31 +525,16 @@ int main(int argc, const char *argv[]) { | |||||||
|       case IMAGE_DESC_RECORD_TYPE: { |       case IMAGE_DESC_RECORD_TYPE: { | ||||||
|         WebPPicture sub_image; |         WebPPicture sub_image; | ||||||
|         WebPFrameRect gif_rect; |         WebPFrameRect gif_rect; | ||||||
|         WebPMemoryWriter memory; |  | ||||||
|  |  | ||||||
|         if (!DGifGetImageDesc(gif)) goto End; |         if (!DGifGetImageDesc(gif)) goto End; | ||||||
|         if (!ReadSubImage(gif, &previous, is_first_frame, key_frame_interval, |         if (!ReadFrame(gif, &gif_rect, &sub_image, ¤t)) { | ||||||
|                           &count_since_key_frame, &gif_rect, &sub_image, &frame, |  | ||||||
|                           ¤t)) { |  | ||||||
|           goto End; |           goto End; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (!config.lossless) { |         OptimizeFrame(&gif_rect, ¤t, &previous, &sub_image, &frame); | ||||||
|           // We need to call BGRA variant because of the way we do Remap(). Note |  | ||||||
|           // that 'sub_image' will no longer be a view and own some memory. |  | ||||||
|           WebPPictureImportBGRA( |  | ||||||
|               &sub_image, (uint8_t*)sub_image.argb, |  | ||||||
|               sub_image.argb_stride * sizeof(*sub_image.argb)); |  | ||||||
|           sub_image.use_argb = 0; |  | ||||||
|         } else { |  | ||||||
|           sub_image.use_argb = 1; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         sub_image.writer = WebPMemoryWrite; |         if (!EncodeFrame(&config, &sub_image, &frame.bitstream)) { | ||||||
|         sub_image.custom_ptr = &memory; |           fprintf(stderr, "Error! Cannot encode frame as WebP\n"); | ||||||
|         WebPMemoryWriterInit(&memory); |  | ||||||
|         if (!WebPEncode(&config, &sub_image)) { |  | ||||||
|           fprintf(stderr, "Error! Cannot encode current as WebP\n"); |  | ||||||
|           fprintf(stderr, "Error code: %d\n", sub_image.error_code); |           fprintf(stderr, "Error code: %d\n", sub_image.error_code); | ||||||
|           goto End; |           goto End; | ||||||
|         } |         } | ||||||
| @@ -499,8 +542,6 @@ int main(int argc, const char *argv[]) { | |||||||
|         // Now we have all the info about the frame, as a Graphic Control |         // Now we have all the info about the frame, as a Graphic Control | ||||||
|         // Extension Block always appears before the Image Descriptor Block. |         // Extension Block always appears before the Image Descriptor Block. | ||||||
|         // So add the frame to mux. |         // So add the frame to mux. | ||||||
|         frame.bitstream.bytes = memory.mem; |  | ||||||
|         frame.bitstream.size = memory.size; |  | ||||||
|         err = WebPMuxPushFrame(mux, &frame, 1); |         err = WebPMuxPushFrame(mux, &frame, 1); | ||||||
|         if (err != WEBP_MUX_OK) { |         if (err != WEBP_MUX_OK) { | ||||||
|           fprintf(stderr, "ERROR (%s): Could not add animation frame.\n", |           fprintf(stderr, "ERROR (%s): Could not add animation frame.\n", | ||||||
| @@ -513,16 +554,14 @@ int main(int argc, const char *argv[]) { | |||||||
|                  frame.x_offset, frame.y_offset, |                  frame.x_offset, frame.y_offset, | ||||||
|                  frame.duration); |                  frame.duration); | ||||||
|           printf("dispose:%d blend:%d transparent index:%d\n", |           printf("dispose:%d blend:%d transparent index:%d\n", | ||||||
|                  frame.dispose_method, frame.blend_method, transparent_index); |                  frame.dispose_method, frame.blend_method, | ||||||
|  |                  kParams.transparent_index); | ||||||
|         } |         } | ||||||
|         WebPDataClear(&frame.bitstream); |         WebPDataClear(&frame.bitstream); | ||||||
|         WebPPictureFree(&sub_image); |         WebPPictureFree(&sub_image); | ||||||
|         is_first_frame = 0; |         kParams.is_first_frame = 0; | ||||||
|  |  | ||||||
|         if (frame.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) { |         DisposeFrame(frame.dispose_method, &gif_rect, ¤t, &previous); | ||||||
|           ClearPicture(¤t, NULL); |  | ||||||
|           ClearPicture(&previous, &gif_rect); |  | ||||||
|         } |  | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
|       case EXTENSION_RECORD_TYPE: { |       case EXTENSION_RECORD_TYPE: { | ||||||
| @@ -551,8 +590,9 @@ int main(int argc, const char *argv[]) { | |||||||
|                   (dispose == 2) ? WEBP_MUX_DISPOSE_BACKGROUND |                   (dispose == 2) ? WEBP_MUX_DISPOSE_BACKGROUND | ||||||
|                                  : WEBP_MUX_DISPOSE_NONE; |                                  : WEBP_MUX_DISPOSE_NONE; | ||||||
|             } |             } | ||||||
|             transparent_index = (flags & GIF_TRANSPARENT_MASK) ? data[4] : -1; |             kParams.transparent_index = | ||||||
|             if (is_first_frame) { |                 (flags & GIF_TRANSPARENT_MASK) ? data[4] : -1; | ||||||
|  |             if (kParams.is_first_frame) { | ||||||
|               if (!GetBackgroundColor(gif->SColorMap, gif->SBackGroundColor, |               if (!GetBackgroundColor(gif->SColorMap, gif->SBackGroundColor, | ||||||
|                                       &anim.bgcolor)) { |                                       &anim.bgcolor)) { | ||||||
|                 fprintf(stderr, "GIF decode warning: invalid background color " |                 fprintf(stderr, "GIF decode warning: invalid background color " | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user