Repository navigation
Expand file tree
/
Copy pathwebp-decoder.cpp
More file actions
141 lines (113 loc) · 3.65 KB
/
Copy pathwebp-decoder.cpp
File metadata and controls
141 lines (113 loc) · 3.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include "webp-decoder.h"
#include <util/platform.h>
#include <vector>
#include <webp/decode.h>
#include <webp/demux.h>
#define BLOG(level, format, ...) blog(level, "[WebP-Decoder] " format, ##__VA_ARGS__)
WebPDecoder::WebPDecoder() {}
WebPDecoder::~WebPDecoder() {
VerifyFree();
}
void WebPDecoder::VerifyFree() {
obs_enter_graphics();
for (auto& frame : frames) {
if (frame.texture) {
gs_texture_destroy(frame.texture);
}
}
obs_leave_graphics();
frames.clear();
is_animated = false;
width = 0;
height = 0;
total_duration = 0;
}
bool WebPDecoder::Load(const char* path) {
VerifyFree();
if (!path || !*path) return false;
FILE* f = os_fopen(path, "rb");
if (!f) {
BLOG(LOG_WARNING, "Failed to open file: %s", path);
return false;
}
int64_t size = os_fgetsize(f);
if (size <= 0) {
fclose(f);
return false;
}
std::vector<uint8_t> buffer((size_t)size);
size_t read_bytes = fread(buffer.data(), 1, (size_t)size, f);
fclose(f);
if (read_bytes != (size_t)size) {
return false;
}
return DecodeData(buffer.data(), (size_t)size);
}
bool WebPDecoder::DecodeData(const uint8_t* data, size_t size) {
WebPData webp_data;
webp_data.bytes = data;
webp_data.size = size;
WebPAnimDecoderOptions dec_options;
WebPAnimDecoderOptionsInit(&dec_options);
dec_options.color_mode = MODE_RGBA;
WebPAnimDecoder* dec = WebPAnimDecoderNew(&webp_data, &dec_options);
if (dec == NULL) {
BLOG(LOG_WARNING, "Failed to create WebPAnimDecoder");
return false;
}
WebPAnimInfo anim_info;
if (!WebPAnimDecoderGetInfo(dec, &anim_info)) {
BLOG(LOG_WARNING, "Failed to get animation info");
WebPAnimDecoderDelete(dec);
return false;
}
width = anim_info.canvas_width;
height = anim_info.canvas_height;
loop_count = anim_info.loop_count;
is_animated = (anim_info.frame_count > 1);
BLOG(LOG_INFO, "Decoding WebP: %dx%d, Frames: %d, Loops: %d", width, height, anim_info.frame_count, loop_count);
int prev_timestamp = 0;
// We must decode ALL frames to get correct blending
obs_enter_graphics();
while (WebPAnimDecoderHasMoreFrames(dec)) {
uint8_t* buf;
int timestamp;
if (!WebPAnimDecoderGetNext(dec, &buf, ×tamp)) {
break;
}
// Create texture for this frame
gs_texture_t* tex = gs_texture_create(width, height, GS_RGBA, 1, (const uint8_t**)&buf, GS_DYNAMIC);
if (!tex) {
BLOG(LOG_WARNING, "Failed to create texture for frame");
continue;
}
WebPFrame frame;
frame.texture = tex;
frame.timestamp_ms = timestamp;
frame.duration_ms = timestamp - prev_timestamp;
frames.push_back(frame);
prev_timestamp = timestamp;
}
obs_leave_graphics();
WebPAnimDecoderDelete(dec);
total_duration = prev_timestamp;
return !frames.empty();
}
gs_texture_t* WebPDecoder::GetTextureForTime(uint64_t time_ms) {
if (frames.empty()) return NULL;
if (!is_animated) return frames[0].texture;
// Handle looping
if (total_duration > 0) {
time_ms %= total_duration;
}
// Find the frame that covers this timestamp.
// timestamp_ms is the END time of the frame.
// So we want the first frame where frame.timestamp_ms > time_ms
for (const auto& frame : frames) {
if (frame.timestamp_ms > (int)time_ms) {
return frame.texture;
}
}
// Fallback to last frame (should be covered by loop logic, but just in case)
return frames.back().texture;
}