Skip to content

Commit e55dd1b

Browse files
fix: WebView support for GIF assets (#2389)
1 parent 7c8bb5d commit e55dd1b

2 files changed

Lines changed: 160 additions & 10 deletions

File tree

webview/src/view.cpp

Lines changed: 148 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include <QNetworkReply>
99
#include <QImage>
1010
#include <QPainter>
11+
#include <QMovie>
12+
#include <QBuffer>
1113

1214
#include "view.h"
1315

@@ -28,6 +30,19 @@ View::View(QWidget* parent) : QWidget(parent)
2830
networkManager = new QNetworkAccessManager(this);
2931
currentImage = QImage();
3032
nextImage = QImage();
33+
movie = nullptr;
34+
animationTimer = new QTimer(this);
35+
isAnimatedImage = false;
36+
37+
connect(animationTimer, &QTimer::timeout, this, &View::updateMovieFrame);
38+
}
39+
40+
View::~View()
41+
{
42+
if (movie) {
43+
movie->stop();
44+
delete movie;
45+
}
3146
}
3247

3348
void View::loadPage(const QString &uri)
@@ -61,6 +76,15 @@ void View::loadImage(const QString &preUri)
6176
{
6277
qDebug() << "Type: Image";
6378

79+
// Stop any existing animation
80+
if (movie) {
81+
movie->stop();
82+
delete movie;
83+
movie = nullptr;
84+
}
85+
animationTimer->stop();
86+
isAnimatedImage = false;
87+
6488
QFileInfo fileInfo = QFileInfo(preUri);
6589
QString src;
6690

@@ -99,19 +123,15 @@ void View::loadImage(const QString &preUri)
99123

100124
connect(reply, &QNetworkReply::finished, this, [=]() {
101125
if (reply->error() == QNetworkReply::NoError) {
102-
QImage newImage;
103126
QByteArray data = reply->readAll();
104127
qDebug() << "Received image data size:" << data.size();
105128

106-
if (newImage.loadFromData(data)) {
107-
qDebug() << "Successfully loaded image. Size:" << newImage.size();
108-
nextImage = newImage;
109-
// Only hide web view and update current image after the new image is loaded
110-
webView->setVisible(false);
111-
currentImage = nextImage;
112-
update();
129+
if (tryLoadAsAnimatedGif(data)) {
130+
// Successfully loaded as animated GIF
131+
return;
113132
} else {
114-
qDebug() << "Failed to load image from data";
133+
// Load as static image
134+
loadAsStaticImage(data);
115135
}
116136
} else {
117137
qDebug() << "Network error:" << reply->errorString();
@@ -125,13 +145,109 @@ void View::loadImage(const QString &preUri)
125145
});
126146
}
127147

148+
bool View::tryLoadAsAnimatedGif(const QByteArray& data)
149+
{
150+
// Try to load as QMovie first to check if it's an animated GIF
151+
QBuffer* testBuffer = new QBuffer();
152+
testBuffer->setData(data);
153+
testBuffer->open(QIODevice::ReadOnly);
154+
155+
// Create QMovie to test if it's animated
156+
QMovie testMovie(testBuffer, QByteArray(), this);
157+
158+
if (testMovie.isValid() && testMovie.frameCount() > 1) {
159+
// This is an animated GIF
160+
qDebug() << "Detected animated GIF with" << testMovie.frameCount() << "frames";
161+
delete testBuffer; // Clean up test buffer
162+
163+
// Create a new buffer for the actual movie
164+
QBuffer* buffer = new QBuffer();
165+
buffer->setData(data);
166+
buffer->open(QIODevice::ReadOnly);
167+
168+
// Create the actual movie for animation
169+
movie = new QMovie(buffer, QByteArray(), this);
170+
171+
if (movie->isValid()) {
172+
qDebug() << "GIF animation loaded successfully. Frame count:" << movie->frameCount();
173+
qDebug() << "Animation speed:" << movie->speed() << "ms per frame";
174+
175+
setupAnimation();
176+
return true;
177+
} else {
178+
qDebug() << "Failed to load GIF as animation, falling back to static image";
179+
delete movie;
180+
movie = nullptr;
181+
delete buffer;
182+
183+
// Fall back to static image loading - this preserves original behavior
184+
loadAsStaticImage(data);
185+
return true; // Return true to prevent double loading
186+
}
187+
} else {
188+
// This is a static image (including single-frame GIFs)
189+
delete testBuffer;
190+
return false;
191+
}
192+
}
193+
194+
void View::loadAsStaticImage(const QByteArray& data)
195+
{
196+
QImage newImage;
197+
if (newImage.loadFromData(data)) {
198+
qDebug() << "Successfully loaded static image. Size:" << newImage.size();
199+
nextImage = newImage;
200+
webView->setVisible(false);
201+
currentImage = nextImage;
202+
update();
203+
} else {
204+
qDebug() << "Failed to load image from data";
205+
}
206+
}
207+
208+
void View::updateMovieFrame()
209+
{
210+
if (movie && isAnimatedImage && movie->state() == QMovie::Running) {
211+
// Try to advance to the next frame
212+
if (movie->jumpToNextFrame()) {
213+
QImage newFrame = movie->currentImage();
214+
if (!newFrame.isNull()) {
215+
currentImage = newFrame;
216+
update();
217+
}
218+
}
219+
220+
// Schedule next frame update
221+
scheduleNextFrame();
222+
}
223+
}
224+
225+
void View::scheduleNextFrame()
226+
{
227+
int frameDelay = movie->nextFrameDelay();
228+
if (frameDelay > 0) {
229+
animationTimer->start(frameDelay);
230+
} else {
231+
// If no delay specified, try to get it from the movie speed
232+
frameDelay = movie->speed();
233+
if (frameDelay > 0) {
234+
animationTimer->start(frameDelay);
235+
} else {
236+
animationTimer->start(100); // Default delay
237+
}
238+
}
239+
}
240+
128241
void View::paintEvent(QPaintEvent*)
129242
{
130243
QPainter painter(this);
131244
painter.fillRect(rect(), Qt::black);
132245

133246
if (!currentImage.isNull()) {
134-
qDebug() << "Painting image. Size:" << currentImage.size();
247+
// Only log for static images to avoid spam during animation
248+
if (!isAnimatedImage) {
249+
qDebug() << "Painting image. Size:" << currentImage.size();
250+
}
135251
QSize scaledSize = currentImage.size();
136252
scaledSize.scale(size(), Qt::KeepAspectRatio);
137253
QRect targetRect = QRect(QPoint(0, 0), size());
@@ -157,3 +273,25 @@ void View::handleAuthRequest(QNetworkReply* reply, QAuthenticator* auth)
157273
Q_UNUSED(auth)
158274
webView->load(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::AppDataLocation, "res/access_denied.html")));
159275
}
276+
277+
void View::setupAnimation()
278+
{
279+
isAnimatedImage = true;
280+
webView->setVisible(false);
281+
282+
// Start the animation
283+
movie->start();
284+
285+
// Set up timer for frame updates
286+
int frameDelay = movie->nextFrameDelay();
287+
if (frameDelay > 0) {
288+
animationTimer->start(frameDelay);
289+
} else {
290+
// Default to 100ms if no delay specified
291+
animationTimer->start(100);
292+
}
293+
294+
// Get the first frame
295+
currentImage = movie->currentImage();
296+
update();
297+
}

webview/src/view.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
#include <QNetworkReply>
88
#include <QNetworkAccessManager>
99
#include <QImage>
10+
#include <QMovie>
11+
#include <QTimer>
1012

1113
class View : public QWidget
1214
{
1315
Q_OBJECT
1416

1517
public:
1618
explicit View(QWidget* parent);
19+
~View();
1720
QWebEngineView* webView; // Made public for MainWindow access
1821

1922
void loadPage(const QString &uri);
@@ -25,11 +28,20 @@ class View : public QWidget
2528

2629
private slots:
2730
void handleAuthRequest(QNetworkReply*, QAuthenticator*);
31+
void updateMovieFrame();
2832

2933
private:
34+
bool tryLoadAsAnimatedGif(const QByteArray& data);
35+
void loadAsStaticImage(const QByteArray& data);
36+
void scheduleNextFrame();
37+
void setupAnimation();
38+
3039
QWebEnginePage* pre_loader;
3140
QEventLoop pre_loader_loop;
3241
QNetworkAccessManager* networkManager;
3342
QImage currentImage;
3443
QImage nextImage;
44+
QMovie* movie;
45+
QTimer* animationTimer;
46+
bool isAnimatedImage;
3547
};

0 commit comments

Comments
 (0)