Skip to content

Commit 4db5350

Browse files
committed
fix(webp): improve error handling
Avoid silently swallowing encoder failures and stop double-encoding last frame when animation device closes. Prefer stack over heap allocation. Remove dontrun in docs, as it seems to break web docs.
1 parent f606c63 commit 4db5350

File tree

5 files changed

+54
-48
lines changed

5 files changed

+54
-48
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,5 @@ Config/testthat/edition: 3
4242
Config/usethis/last-upkeep: 2025-04-25
4343
Encoding: UTF-8
4444
Roxygen: list(markdown = TRUE)
45-
RoxygenNote: 7.3.2
45+
RoxygenNote: 7.3.3
4646
SystemRequirements: freetype2, libpng, libtiff, libjpeg, libwebp, libwebpmux

R/agg_dev.R

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,15 +603,13 @@ agg_webp <- function(
603603
#' @export
604604
#'
605605
#' @examples
606-
#' \dontrun{
607606
#' file <- tempfile(fileext = '.webp')
608607
#' agg_webp_anim(file, delay = 100, loop = 0)
609608
#' for(i in 1:10) {
610609
#' plot(sin(1:100 + i/10), type = 'l', ylim = c(-1, 1))
611610
#' dev.flush()
612611
#' }
613612
#' dev.off()
614-
#' }
615613
agg_webp_anim <- function(
616614
filename = 'Ranim.webp',
617615
width = 480,

man/agg_webp_anim.Rd

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/AggDeviceWebPAnim.h

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,14 @@
77
#include <vector>
88
#include <memory>
99
#include <stdexcept>
10+
#include <string>
1011

1112
#include "AggDevice.h"
1213
#include "files.h"
1314
#include "ragg.h"
1415

15-
using WebPPicturePtr = std::unique_ptr<WebPPicture, void(*)(WebPPicture*)>;
16-
using WebPDataPtr = std::unique_ptr<WebPData, void(*)(WebPData*)>;
1716
using FilePtr = std::unique_ptr<FILE, int(*)(FILE*)>;
1817

19-
inline WebPPicturePtr make_webp_picture() {
20-
WebPPicture* pic = new WebPPicture;
21-
if (!WebPPictureInit(pic)) {
22-
delete pic;
23-
throw std::runtime_error("WebPPictureInit failed");
24-
}
25-
return WebPPicturePtr(pic, WebPPictureFree);
26-
}
27-
28-
inline WebPDataPtr make_webp_data() {
29-
WebPData* data = new WebPData;
30-
return WebPDataPtr(data, WebPDataClear);
31-
}
32-
3318
inline FilePtr make_file(const char* filename, const char* mode) {
3419
FILE* f = unicode_fopen(filename, mode);
3520
if (!f) {
@@ -92,17 +77,22 @@ class AggDeviceWebPAnim : public AggDevice<PIXFMT> {
9277
demultiply<PIXFMT>(this->pixf);
9378

9479
try {
95-
auto pic = make_webp_picture();
80+
WebPPicture pic;
81+
if (!WebPPictureInit(&pic)) {
82+
Rf_warning("WebPPictureInit failed");
83+
return false;
84+
}
85+
auto pic_guard = std::unique_ptr<WebPPicture, decltype(&WebPPictureFree)>(&pic, WebPPictureFree);
9686

9787
WebPMemoryWriter wr;
9888
WebPMemoryWriterInit(&wr);
9989
auto cleanup = [](WebPMemoryWriter* w) { WebPMemoryWriterClear(w); };
10090
std::unique_ptr<WebPMemoryWriter, decltype(cleanup)> wr_guard(&wr, cleanup);
10191

102-
pic->width = this->width;
103-
pic->height = this->height;
104-
pic->writer = WebPMemoryWrite;
105-
pic->custom_ptr = &wr;
92+
pic.width = this->width;
93+
pic.height = this->height;
94+
pic.writer = WebPMemoryWrite;
95+
pic.custom_ptr = &wr;
10696

10797
WebPConfig config;
10898
if (!WebPConfigInit(&config)) {
@@ -116,13 +106,13 @@ class AggDeviceWebPAnim : public AggDevice<PIXFMT> {
116106
const auto importer = (PIXFMT::num_components == 3) ? WebPPictureImportRGB
117107
: WebPPictureImportRGBA;
118108

119-
if (!importer(pic.get(), reinterpret_cast<const uint8_t*>(this->buffer), stride)) {
120-
Rf_warning("WebPPictureImport failed: %s", webp_error_name(pic->error_code));
109+
if (!importer(&pic, reinterpret_cast<const uint8_t*>(this->buffer), stride)) {
110+
Rf_warning("WebPPictureImport failed: %s", webp_error_name(pic.error_code));
121111
return false;
122112
}
123113

124-
if (!WebPEncode(&config, pic.get())) {
125-
Rf_warning("WebPEncode failed: %s", webp_error_name(pic->error_code));
114+
if (!WebPEncode(&config, &pic)) {
115+
Rf_warning("WebPEncode failed: %s", webp_error_name(pic.error_code));
126116
return false;
127117
}
128118

@@ -141,7 +131,13 @@ class AggDeviceWebPAnim : public AggDevice<PIXFMT> {
141131
}
142132

143133
void close() {
144-
savePage();
134+
if (!savePage()) {
135+
throw std::runtime_error("Failed to encode final WebP frame");
136+
}
137+
138+
if (frames.empty()) {
139+
throw std::runtime_error("WebP animation has no frames to write");
140+
}
145141

146142
for (size_t i = 0; i < frames.size(); ++i) {
147143
const auto& raw = frames[i];
@@ -157,29 +153,35 @@ class AggDeviceWebPAnim : public AggDevice<PIXFMT> {
157153
finfo.blend_method = WEBP_MUX_BLEND;
158154
WebPMuxError frame_err = WebPMuxPushFrame(mux.get(), &finfo, 1);
159155
if (frame_err != WEBP_MUX_OK) {
160-
Rf_error("Failed to push WebP frame %zu/%zu: %s (%zu bytes)",
161-
i + 1, frames.size(), mux_error_name(frame_err), raw.size());
156+
std::string msg = "Failed to push WebP frame " +
157+
std::to_string(i + 1) + "/" +
158+
std::to_string(frames.size()) + ": " +
159+
mux_error_name(frame_err) + " (" +
160+
std::to_string(raw.size()) + " bytes)";
161+
throw std::runtime_error(msg);
162162
}
163163
}
164164

165-
try {
166-
auto out = make_webp_data();
167-
WebPMuxError mux_err = WebPMuxAssemble(mux.get(), out.get());
168-
if (mux_err != WEBP_MUX_OK) {
169-
Rf_error("WebP mux assemble failed: %s (%zu frames)", mux_error_name(mux_err), frames.size());
170-
}
165+
WebPData out;
166+
WebPDataInit(&out);
167+
auto out_guard = std::unique_ptr<WebPData, decltype(&WebPDataClear)>(&out, WebPDataClear);
171168

172-
auto fd = make_file(this->file.c_str(), "wb");
173-
if (fwrite(out->bytes, 1, out->size, fd.get()) != out->size) {
174-
Rf_error("Failed to write WebP animation file");
175-
}
169+
WebPMuxError mux_err = WebPMuxAssemble(mux.get(), &out);
170+
if (mux_err != WEBP_MUX_OK) {
171+
std::string msg = "WebP mux assemble failed: " +
172+
std::string(mux_error_name(mux_err)) + " (" +
173+
std::to_string(frames.size()) + " frames)";
174+
throw std::runtime_error(msg);
175+
}
176176

177-
} catch (...) {
178-
AggDevice<PIXFMT>::close();
179-
throw;
177+
auto fd = make_file(this->file.c_str(), "wb");
178+
if (fwrite(out.bytes, 1, out.size, fd.get()) != out.size) {
179+
throw std::runtime_error("Failed to write WebP animation file");
180180
}
181181

182-
AggDevice<PIXFMT>::close();
182+
if (this->pageno == 0) {
183+
this->pageno = 1;
184+
}
183185
}
184186

185187
private:

src/init_device.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "ragg.h"
44
#include <cstddef>
5+
#include <memory>
56

67
template<class T>
78
void agg_metric_info(int c, const pGEcontext gc, double* ascent,
@@ -43,8 +44,15 @@ void agg_close(pDevDesc dd) {
4344
T * device = (T *) dd->deviceSpecific;
4445

4546
BEGIN_CPP
47+
auto deleter = [dd](T* ptr) {
48+
if (dd != NULL) {
49+
dd->deviceSpecific = NULL;
50+
}
51+
delete ptr;
52+
};
53+
std::unique_ptr<T, decltype(deleter)> guard(device, deleter);
4654
device->close();
47-
delete device;
55+
guard.reset();
4856
END_CPP
4957

5058
return;

0 commit comments

Comments
 (0)