Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // GifViewer.cpp — minimal animated GIF viewer for Haiku (giflib + BeAPI)
- // Fixes: create BMessageRunner in AttachedToWindow(), avoid unused-parameter warning,
- // clamp frame delay, recreate runner each frame.
- #include <Application.h>
- #include <Bitmap.h>
- #include <Message.h>
- #include <MessageRunner.h>
- #include <Window.h>
- #include <View.h>
- #include <stdio.h>
- #include <string.h>
- #include <stdexcept>
- extern "C" {
- #include <gif_lib.h>
- }
- static const uint32 kMsgTick = 'tick';
- // --- GifView -----------------------------------------------------------------
- class GifView : public BView {
- public:
- GifView(BRect frame, GifFileType* gif)
- : BView(frame, "gifview", B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS),
- fGif(gif),
- fCanvas(nullptr),
- fRunner(nullptr),
- fFrame(0),
- fLastDisposal(0),
- fHasLastRect(false),
- fLastDelayCS(10) // centiseconds (0.1s) default
- {
- SetViewColor(B_TRANSPARENT_32_BIT);
- _PrepareCanvas();
- _RenderFrame(0); // draw the first frame onto the canvas (no runner yet)
- }
- ~GifView() override {
- delete fRunner;
- delete fCanvas;
- }
- // create the runner only when attached (target messenger will be valid)
- void AttachedToWindow() override {
- BView::AttachedToWindow();
- _ScheduleNextTick();
- }
- void DetachedFromWindow() override {
- delete fRunner;
- fRunner = nullptr;
- BView::DetachedFromWindow();
- }
- void Draw(BRect update) override {
- (void)update; // silence unused-parameter warning
- if (fCanvas && fCanvas->IsValid()) {
- DrawBitmap(fCanvas, BPoint(0, 0));
- }
- }
- void MessageReceived(BMessage* msg) override {
- if (msg->what == kMsgTick) {
- // printf("Tick: advancing from frame %d\n", fFrame);
- _Advance();
- Invalidate();
- } else {
- BView::MessageReceived(msg);
- }
- }
- private:
- void _PrepareCanvas() {
- delete fCanvas;
- BRect rect(0, 0, fGif->SWidth - 1, fGif->SHeight - 1);
- fCanvas = new BBitmap(rect, B_RGBA32, true);
- if (!fCanvas || !fCanvas->IsValid())
- throw std::runtime_error("Failed to allocate canvas bitmap");
- // start fully transparent (or you can fill with background color if desired)
- memset(fCanvas->Bits(), 0, fCanvas->BitsLength());
- }
- void _ClearRectToBG(const GifImageDesc& idesc) {
- if (!fCanvas) return;
- uint8* bits = (uint8*)fCanvas->Bits();
- const int32 bpr = fCanvas->BytesPerRow();
- for (int y = 0; y < idesc.Height; y++) {
- uint8* row = bits + (idesc.Top + y) * bpr + (idesc.Left * 4);
- for (int x = 0; x < idesc.Width; x++) {
- row[0] = 0; // blue
- row[1] = 0; // green
- row[2] = 0; // red
- row[3] = 0; // alpha
- row += 4;
- }
- }
- }
- void _BlitFrame(const SavedImage& si, const GraphicsControlBlock& gcb) {
- ColorMapObject* cmap = si.ImageDesc.ColorMap ? si.ImageDesc.ColorMap : fGif->SColorMap;
- if (!cmap) return;
- uint8* bits = (uint8*)fCanvas->Bits();
- const int32 bpr = fCanvas->BytesPerRow();
- const GifByteType* src = si.RasterBits;
- const int transparent = gcb.TransparentColor; // -1 means none
- for (int y = 0; y < si.ImageDesc.Height; y++) {
- uint8* row = bits + (si.ImageDesc.Top + y) * bpr + (si.ImageDesc.Left * 4);
- for (int x = 0; x < si.ImageDesc.Width; x++) {
- int idx = *src++;
- if (idx == transparent) {
- // leave underlying pixel as-is
- } else if (idx >= 0 && idx < cmap->ColorCount) {
- const GifColorType& col = cmap->Colors[idx];
- row[0] = col.Blue;
- row[1] = col.Green;
- row[2] = col.Red;
- row[3] = 255;
- }
- row += 4;
- }
- }
- }
- void _RenderFrame(int index) {
- if (!fCanvas || index < 0 || index >= fGif->ImageCount) return;
- // apply disposal of previous frame BEFORE drawing this one
- if (fHasLastRect && fLastDisposal == DISPOSE_BACKGROUND) {
- _ClearRectToBG(fLastRect);
- }
- // get GCB for this frame (safe default if call fails)
- GraphicsControlBlock gcb;
- memset(&gcb, 0, sizeof(gcb));
- if (DGifSavedExtensionToGCB(fGif, index, &gcb) == GIF_ERROR) {
- // leave gcb as zeros (delay = 0 etc.)
- }
- _BlitFrame(fGif->SavedImages[index], gcb);
- // store for next disposal handling
- fLastRect = fGif->SavedImages[index].ImageDesc;
- fHasLastRect = true;
- fLastDisposal = gcb.DisposalMode;
- fLastDelayCS = (gcb.DelayTime > 0 ? gcb.DelayTime : 10); // default 0.1s
- }
- void _Advance() {
- fFrame = (fFrame + 1) % fGif->ImageCount;
- _RenderFrame(fFrame);
- _ScheduleNextTick();
- }
- void _ScheduleNextTick() {
- // centiseconds -> microseconds
- const bigtime_t usec = (bigtime_t)fLastDelayCS * 10000LL;
- // recreate runner so SetInterval quirks don't bite older Haiku builds
- delete fRunner;
- fRunner = nullptr;
- BMessage tick(kMsgTick);
- // create runner targeting this view (now valid because we only call this in AttachedToWindow)
- BMessenger target(this);
- // enforce a reasonable minimum interval (0.05s or 0.1s) to avoid very tiny values
- const bigtime_t chosen = (usec >= 20000LL) ? usec : 100000LL;
- fRunner = new BMessageRunner(target, &tick, chosen);
- // debug
- // printf("Scheduled runner: interval = %lld us (frame %d)\n",
- // (long long)chosen, fFrame);
- }
- private:
- GifFileType* fGif;
- BBitmap* fCanvas;
- BMessageRunner* fRunner;
- int fFrame;
- GifImageDesc fLastRect;
- int fLastDisposal;
- bool fHasLastRect;
- int fLastDelayCS; // centiseconds
- };
- // --- Window + app -----------------------------------------------------------
- class GifWindow : public BWindow {
- public:
- GifWindow(GifFileType* gif)
- : BWindow(BRect(100, 100, 100 + gif->SWidth, 100 + gif->SHeight),
- "GIF Viewer",
- B_TITLED_WINDOW, B_NOT_ZOOMABLE | B_NOT_RESIZABLE)
- {
- AddChild(new GifView(Bounds(), gif));
- Show();
- }
- bool QuitRequested() override {
- be_app->PostMessage(B_QUIT_REQUESTED);
- return true;
- }
- };
- class GifApp : public BApplication {
- public:
- GifApp(const char* gifPath) : BApplication("application/x-vnd.example-gifview"), fGif(nullptr) {
- _OpenGif(gifPath);
- }
- ~GifApp() override {
- if (fGif) {
- int err = 0;
- #if GIFLIB_MAJOR >= 5
- DGifCloseFile(fGif, &err);
- #else
- DGifCloseFile(fGif);
- #endif
- fGif = nullptr;
- }
- }
- void ReadyToRun() override {
- new GifWindow(fGif);
- }
- private:
- void _OpenGif(const char* path) {
- int gifErr = 0;
- #if GIFLIB_MAJOR >= 5
- fGif = DGifOpenFileName(path, &gifErr);
- #else
- fGif = DGifOpenFileName(path);
- #endif
- if (!fGif) {
- fprintf(stderr, "Failed to open GIF: %s (err=%d)\n", path, gifErr);
- throw std::runtime_error("DGifOpenFileName failed");
- }
- if (DGifSlurp(fGif) == GIF_ERROR) {
- throw std::runtime_error("DGifSlurp failed");
- }
- if (fGif->ImageCount <= 0) throw std::runtime_error("GIF has no frames");
- }
- GifFileType* fGif;
- };
- // --- main ------------------------------------------------------------------
- int main(int argc, char** argv)
- {
- if (argc < 2) {
- fprintf(stderr, "Usage: %s /path/to/animation.gif\n", argv[0]);
- return 1;
- }
- try {
- GifApp app(argv[1]);
- app.Run();
- } catch (const std::exception& e) {
- fprintf(stderr, "Error: %s\n", e.what());
- return 1;
- }
- return 0;
- }
Advertisement
Add Comment
Please, Sign In to add comment