Skip to content

Commit d9a139f

Browse files
authored
Merge pull request opencv#25608 from sturkmen72:animated_webp_support
Animated WebP Support opencv#25608 related issues opencv#24855 opencv#22569 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake
1 parent 0903061 commit d9a139f

File tree

15 files changed

+1002
-119
lines changed

15 files changed

+1002
-119
lines changed

cmake/OpenCVFindLibsGrfmt.cmake

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,19 +196,34 @@ if(WITH_WEBP AND NOT WEBP_FOUND
196196
endif()
197197

198198
if(NOT WEBP_VERSION AND WEBP_INCLUDE_DIR)
199-
ocv_clear_vars(ENC_MAJ_VERSION ENC_MIN_VERSION ENC_REV_VERSION)
200-
if(EXISTS "${WEBP_INCLUDE_DIR}/enc/vp8enci.h")
201-
ocv_parse_header("${WEBP_INCLUDE_DIR}/enc/vp8enci.h" WEBP_VERSION_LINES ENC_MAJ_VERSION ENC_MIN_VERSION ENC_REV_VERSION)
202-
set(WEBP_VERSION "${ENC_MAJ_VERSION}.${ENC_MIN_VERSION}.${ENC_REV_VERSION}")
203-
elseif(EXISTS "${WEBP_INCLUDE_DIR}/webp/encode.h")
199+
if(EXISTS "${WEBP_INCLUDE_DIR}/webp/encode.h")
204200
file(STRINGS "${WEBP_INCLUDE_DIR}/webp/encode.h" WEBP_ENCODER_ABI_VERSION REGEX "#define[ \t]+WEBP_ENCODER_ABI_VERSION[ \t]+([x0-9a-f]+)" )
205201
if(WEBP_ENCODER_ABI_VERSION MATCHES "#define[ \t]+WEBP_ENCODER_ABI_VERSION[ \t]+([x0-9a-f]+)")
206202
set(WEBP_ENCODER_ABI_VERSION "${CMAKE_MATCH_1}")
207-
set(WEBP_VERSION "encoder: ${WEBP_ENCODER_ABI_VERSION}")
208203
else()
209204
unset(WEBP_ENCODER_ABI_VERSION)
210205
endif()
211206
endif()
207+
208+
if(EXISTS "${WEBP_INCLUDE_DIR}/webp/decode.h")
209+
file(STRINGS "${WEBP_INCLUDE_DIR}/webp/decode.h" WEBP_DECODER_ABI_VERSION REGEX "#define[ \t]+WEBP_DECODER_ABI_VERSION[ \t]+([x0-9a-f]+)" )
210+
if(WEBP_DECODER_ABI_VERSION MATCHES "#define[ \t]+WEBP_DECODER_ABI_VERSION[ \t]+([x0-9a-f]+)")
211+
set(WEBP_DECODER_ABI_VERSION "${CMAKE_MATCH_1}")
212+
else()
213+
unset(WEBP_DECODER_ABI_VERSION)
214+
endif()
215+
endif()
216+
217+
if(EXISTS "${WEBP_INCLUDE_DIR}/webp/demux.h")
218+
file(STRINGS "${WEBP_INCLUDE_DIR}/webp/demux.h" WEBP_DEMUX_ABI_VERSION REGEX "#define[ \t]+WEBP_DEMUX_ABI_VERSION[ \t]+([x0-9a-f]+)" )
219+
if(WEBP_DEMUX_ABI_VERSION MATCHES "#define[ \t]+WEBP_DEMUX_ABI_VERSION[ \t]+([x0-9a-f]+)")
220+
set(WEBP_DEMUX_ABI_VERSION "${CMAKE_MATCH_1}")
221+
else()
222+
unset(WEBP_DEMUX_ABI_VERSION)
223+
endif()
224+
endif()
225+
226+
set(WEBP_VERSION "decoder: ${WEBP_DECODER_ABI_VERSION}, encoder: ${WEBP_ENCODER_ABI_VERSION}, demux: ${WEBP_DEMUX_ABI_VERSION}")
212227
endif()
213228

214229
# --- libopenjp2 (optional, check before libjasper) ---

cmake/OpenCVFindWebP.cmake

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010

1111
# Look for the header file.
1212

13-
unset(WEBP_FOUND)
14-
1513
FIND_PATH(WEBP_INCLUDE_DIR NAMES webp/decode.h)
1614

1715
if(NOT WEBP_INCLUDE_DIR)
@@ -21,13 +19,14 @@ else()
2119

2220
# Look for the library.
2321
FIND_LIBRARY(WEBP_LIBRARY NAMES webp)
24-
MARK_AS_ADVANCED(WEBP_LIBRARY)
22+
FIND_LIBRARY(WEBP_MUX_LIBRARY NAMES webpmux)
23+
FIND_LIBRARY(WEBP_DEMUX_LIBRARY NAMES webpdemux)
2524

2625
# handle the QUIETLY and REQUIRED arguments and set WEBP_FOUND to TRUE if
2726
# all listed variables are TRUE
2827
INCLUDE(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
2928
FIND_PACKAGE_HANDLE_STANDARD_ARGS(WebP DEFAULT_MSG WEBP_LIBRARY WEBP_INCLUDE_DIR)
3029

31-
SET(WEBP_LIBRARIES ${WEBP_LIBRARY})
30+
SET(WEBP_LIBRARIES ${WEBP_LIBRARY} ${WEBP_MUX_LIBRARY} ${WEBP_DEMUX_LIBRARY})
3231
SET(WEBP_INCLUDE_DIRS ${WEBP_INCLUDE_DIR})
3332
endif()
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
Handling Animated Image Files {#tutorial_animations}
2+
===========================
3+
4+
@tableofcontents
5+
6+
| | |
7+
| -: | :- |
8+
| Original author | Suleyman Turkmen (with help of ChatGPT) |
9+
| Compatibility | OpenCV >= 4.11 |
10+
11+
Goal
12+
----
13+
In this tutorial, you will learn how to:
14+
15+
- Use `cv::imreadanimation` to load frames from animated image files.
16+
- Understand the structure and parameters of the `cv::Animation` structure.
17+
- Display individual frames from an animation.
18+
- Use `cv::imwriteanimation` to write `cv::Animation` to a file.
19+
20+
Source Code
21+
-----------
22+
23+
@add_toggle_cpp
24+
- **Downloadable code**: Click
25+
[here](https://github.com/opencv/opencv/tree/4.x/samples/cpp/tutorial_code/imgcodecs/animations.cpp)
26+
27+
- **Code at a glance:**
28+
@include samples/cpp/tutorial_code/imgcodecs/animations.cpp
29+
@end_toggle
30+
31+
@add_toggle_python
32+
- **Downloadable code**: Click
33+
[here](https://github.com/opencv/opencv/tree/4.x/samples/python/tutorial_code/imgcodecs/animations.py)
34+
35+
- **Code at a glance:**
36+
@include samples/python/tutorial_code/imgcodecs/animations.py
37+
@end_toggle
38+
39+
Explanation
40+
-----------
41+
42+
## Initializing the Animation Structure
43+
44+
Initialize a `cv::Animation` structure to hold the frames from the animated image file.
45+
46+
@add_toggle_cpp
47+
@snippet cpp/tutorial_code/imgcodecs/animations.cpp init_animation
48+
@end_toggle
49+
50+
@add_toggle_python
51+
@snippet python/tutorial_code/imgcodecs/animations.py init_animation
52+
@end_toggle
53+
54+
## Loading Frames
55+
56+
Use `cv::imreadanimation` to load frames from the specified file. Here, we load all frames from an animated WebP image.
57+
58+
@add_toggle_cpp
59+
@snippet cpp/tutorial_code/imgcodecs/animations.cpp read_animation
60+
@end_toggle
61+
62+
@add_toggle_python
63+
@snippet python/tutorial_code/imgcodecs/animations.py read_animation
64+
@end_toggle
65+
66+
## Displaying Frames
67+
68+
Each frame in the `animation.frames` vector can be displayed as a standalone image. This loop iterates through each frame, displaying it in a window with a short delay to simulate the animation.
69+
70+
@add_toggle_cpp
71+
@snippet cpp/tutorial_code/imgcodecs/animations.cpp show_animation
72+
@end_toggle
73+
74+
@add_toggle_python
75+
@snippet python/tutorial_code/imgcodecs/animations.py show_animation
76+
@end_toggle
77+
78+
## Saving Animation
79+
80+
@add_toggle_cpp
81+
@snippet cpp/tutorial_code/imgcodecs/animations.cpp write_animation
82+
@end_toggle
83+
84+
@add_toggle_python
85+
@snippet python/tutorial_code/imgcodecs/animations.py write_animation
86+
@end_toggle
87+
88+
## Summary
89+
90+
The `cv::imreadanimation` and `cv::imwriteanimation` functions make it easy to work with animated image files by loading frames into a `cv::Animation` structure, allowing frame-by-frame processing.
91+
With these functions, you can load, process, and save frames from animated image files like GIF, AVIF, APNG, and WebP.

doc/tutorials/app/table_of_content_app.markdown

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ Application utils (highgui, imgcodecs, videoio modules) {#tutorial_table_of_cont
1010
- @subpage tutorial_orbbec_uvc
1111
- @subpage tutorial_intelperc
1212
- @subpage tutorial_wayland_ubuntu
13+
- @subpage tutorial_animations

modules/imgcodecs/include/opencv2/imgcodecs.hpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,36 @@ enum ImwriteGIFCompressionFlags {
236236

237237
//! @} imgcodecs_flags
238238

239+
/** @brief Represents an animation with multiple frames.
240+
The `Animation` struct is designed to store and manage data for animated sequences such as those from animated formats (e.g., GIF, AVIF, APNG, WebP).
241+
It provides support for looping, background color settings, frame timing, and frame storage.
242+
*/
243+
struct CV_EXPORTS_W_SIMPLE Animation
244+
{
245+
//! Number of times the animation should loop. 0 means infinite looping.
246+
CV_PROP_RW int loop_count;
247+
//! Background color of the animation in BGRA format.
248+
CV_PROP_RW Scalar bgcolor;
249+
//! Duration for each frame in milliseconds.
250+
CV_PROP_RW std::vector<int> durations;
251+
//! Vector of frames, where each Mat represents a single frame.
252+
CV_PROP_RW std::vector<Mat> frames;
253+
254+
/** @brief Constructs an Animation object with optional loop count and background color.
255+
256+
@param loopCount An integer representing the number of times the animation should loop:
257+
- `0` (default) indicates infinite looping, meaning the animation will replay continuously.
258+
- Positive values denote finite repeat counts, allowing the animation to play a limited number of times.
259+
- If a negative value or a value beyond the maximum of `0xffff` (65535) is provided, it is reset to `0`
260+
(infinite looping) to maintain valid bounds.
261+
262+
@param bgColor A `Scalar` object representing the background color in BGRA format:
263+
- Defaults to `Scalar()`, indicating an empty color (usually transparent if supported).
264+
- This background color provides a solid fill behind frames that have transparency, ensuring a consistent display appearance.
265+
*/
266+
Animation(int loopCount = 0, Scalar bgColor = Scalar());
267+
};
268+
239269
/** @brief Loads an image from a file.
240270
241271
@anchor imread
@@ -323,6 +353,38 @@ The function imreadmulti loads a specified range from a multi-page image from th
323353
*/
324354
CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector<Mat>& mats, int start, int count, int flags = IMREAD_ANYCOLOR);
325355

356+
/** @example samples/cpp/tutorial_code/imgcodecs/animations.cpp
357+
An example to show usage of cv::imreadanimation and cv::imwriteanimation functions.
358+
Check @ref tutorial_animations "the corresponding tutorial" for more details
359+
*/
360+
361+
/** @brief Loads frames from an animated image file into an Animation structure.
362+
363+
The function imreadanimation loads frames from an animated image file (e.g., GIF, AVIF, APNG, WEBP) into the provided Animation struct.
364+
365+
@param filename A string containing the path to the file.
366+
@param animation A reference to an Animation structure where the loaded frames will be stored. It should be initialized before the function is called.
367+
@param start The index of the first frame to load. This is optional and defaults to 0.
368+
@param count The number of frames to load. This is optional and defaults to 32767.
369+
370+
@return Returns true if the file was successfully loaded and frames were extracted; returns false otherwise.
371+
*/
372+
CV_EXPORTS_W bool imreadanimation(const String& filename, CV_OUT Animation& animation, int start = 0, int count = INT16_MAX);
373+
374+
/** @brief Saves an Animation to a specified file.
375+
376+
The function imwriteanimation saves the provided Animation data to the specified file in an animated format.
377+
Supported formats depend on the implementation and may include formats like GIF, AVIF, APNG, or WEBP.
378+
379+
@param filename The name of the file where the animation will be saved. The file extension determines the format.
380+
@param animation A constant reference to an Animation struct containing the frames and metadata to be saved.
381+
@param params Optional format-specific parameters encoded as pairs (paramId_1, paramValue_1, paramId_2, paramValue_2, ...).
382+
These parameters are used to specify additional options for the encoding process. Refer to `cv::ImwriteFlags` for details on possible parameters.
383+
384+
@return Returns true if the animation was successfully saved; returns false otherwise.
385+
*/
386+
CV_EXPORTS_W bool imwriteanimation(const String& filename, const Animation& animation, const std::vector<int>& params = std::vector<int>());
387+
326388
/** @brief Returns the number of images inside the given file
327389
328390
The function imcount returns the number of pages in a multi-page image (e.g. TIFF), the number of frames in an animation (e.g. AVIF), and 1 otherwise.

modules/imgcodecs/src/grfmt_avif.cpp

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <memory>
1212

1313
#include <opencv2/core/utils/configuration.private.hpp>
14+
#include <opencv2/core/utils/logger.hpp>
1415
#include "opencv2/imgproc.hpp"
1516
#include "grfmt_avif.hpp"
1617

@@ -242,6 +243,8 @@ bool AvifDecoder::readData(Mat &img) {
242243
return false;
243244
}
244245

246+
m_animation.durations.push_back(decoder_->imageTiming.durationInTimescales);
247+
245248
if (decoder_->image->exif.size > 0) {
246249
m_exif.parseExif(decoder_->image->exif.data, decoder_->image->exif.size);
247250
}
@@ -297,16 +300,26 @@ bool AvifEncoder::isFormatSupported(int depth) const {
297300

298301
bool AvifEncoder::write(const Mat &img, const std::vector<int> &params) {
299302
std::vector<Mat> img_vec(1, img);
300-
return writeToOutput(img_vec, params);
303+
return writemulti(img_vec, params);
301304
}
302305

303306
bool AvifEncoder::writemulti(const std::vector<Mat> &img_vec,
304307
const std::vector<int> &params) {
305-
return writeToOutput(img_vec, params);
308+
309+
CV_LOG_INFO(NULL, "Multi page image will be written as animation with 1 second frame duration.");
310+
311+
Animation animation;
312+
animation.frames = img_vec;
313+
314+
for (size_t i = 0; i < animation.frames.size(); i++)
315+
{
316+
animation.durations.push_back(1000);
317+
}
318+
return writeanimation(animation, params);
306319
}
307320

308-
bool AvifEncoder::writeToOutput(const std::vector<Mat> &img_vec,
309-
const std::vector<int> &params) {
321+
bool AvifEncoder::writeanimation(const Animation& animation,
322+
const std::vector<int> &params) {
310323
int bit_depth = 8;
311324
int speed = AVIF_SPEED_FASTEST;
312325
for (size_t i = 0; i < params.size(); i += 2) {
@@ -340,12 +353,12 @@ bool AvifEncoder::writeToOutput(const std::vector<Mat> &img_vec,
340353
#endif
341354
encoder_->speed = speed;
342355

343-
const avifAddImageFlags flag = (img_vec.size() == 1)
356+
const avifAddImageFlags flag = (animation.frames.size() == 1)
344357
? AVIF_ADD_IMAGE_FLAG_SINGLE
345358
: AVIF_ADD_IMAGE_FLAG_NONE;
346359
std::vector<AvifImageUniquePtr> images;
347360
std::vector<cv::Mat> imgs_scaled;
348-
for (const cv::Mat &img : img_vec) {
361+
for (const cv::Mat &img : animation.frames) {
349362
CV_CheckType(
350363
img.type(),
351364
(bit_depth == 8 && img.depth() == CV_8U) ||
@@ -358,13 +371,15 @@ bool AvifEncoder::writeToOutput(const std::vector<Mat> &img_vec,
358371

359372
images.emplace_back(ConvertToAvif(img, do_lossless, bit_depth));
360373
}
361-
for (const AvifImageUniquePtr &image : images) {
374+
375+
for (size_t i = 0; i < images.size(); i++)
376+
{
362377
OPENCV_AVIF_CHECK_STATUS(
363-
avifEncoderAddImage(encoder_, image.get(), /*durationInTimescale=*/1,
364-
flag),
378+
avifEncoderAddImage(encoder_, images[i].get(), animation.durations[i], flag),
365379
encoder_);
366380
}
367381

382+
encoder_->timescale = 1000;
368383
OPENCV_AVIF_CHECK_STATUS(avifEncoderFinish(encoder_, output.get()), encoder_);
369384

370385
if (m_buf) {

modules/imgcodecs/src/grfmt_avif.hpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,11 @@ class AvifEncoder CV_FINAL : public BaseImageEncoder {
4646

4747
bool writemulti(const std::vector<Mat>& img_vec,
4848
const std::vector<int>& params) CV_OVERRIDE;
49+
bool writeanimation(const Animation& animation, const std::vector<int>& params) CV_OVERRIDE;
4950

5051
ImageEncoder newEncoder() const CV_OVERRIDE;
5152

5253
private:
53-
bool writeToOutput(const std::vector<Mat>& img_vec,
54-
const std::vector<int>& params);
5554
avifEncoder* encoder_;
5655
};
5756

modules/imgcodecs/src/grfmt_base.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ bool BaseImageEncoder::writemulti(const std::vector<Mat>&, const std::vector<int
144144
return false;
145145
}
146146

147+
bool BaseImageEncoder::writeanimation(const Animation&, const std::vector<int>& )
148+
{
149+
return false;
150+
}
151+
147152
ImageEncoder BaseImageEncoder::newEncoder() const
148153
{
149154
return ImageEncoder();

modules/imgcodecs/src/grfmt_base.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ class BaseImageDecoder {
133133
*/
134134
virtual bool checkSignature(const String& signature) const;
135135

136+
const Animation& animation() const { return m_animation; };
137+
136138
/**
137139
* @brief Create and return a new instance of the derived image decoder.
138140
* @return A new ImageDecoder object.
@@ -151,6 +153,7 @@ class BaseImageDecoder {
151153
bool m_use_rgb; ///< Flag indicating whether to decode the image in RGB order.
152154
ExifReader m_exif; ///< Object for reading EXIF metadata from the image.
153155
size_t m_frame_count; ///< Number of frames in the image (for animations and multi-page images).
156+
Animation m_animation;
154157
};
155158

156159

@@ -215,6 +218,8 @@ class BaseImageEncoder {
215218
*/
216219
virtual bool writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params);
217220

221+
virtual bool writeanimation(const Animation& animation, const std::vector<int>& params);
222+
218223
/**
219224
* @brief Get a description of the image encoder (e.g., the format it supports).
220225
* @return A string describing the encoder.

0 commit comments

Comments
 (0)