From 1219fb4ab92e2f4d20be55a1e40db2f749137ef3 Mon Sep 17 00:00:00 2001 From: Struma Date: Sun, 2 May 2021 20:36:28 -0400 Subject: [PATCH] Support manual animation of Bitmaps --- binding/bitmap-binding.cpp | 69 ++++++++++- macos/mkxp-z.xcodeproj/project.pbxproj | 2 - src/display/bitmap.cpp | 156 +++++++++++++++++++++---- src/display/bitmap.h | 10 +- 4 files changed, 207 insertions(+), 30 deletions(-) diff --git a/binding/bitmap-binding.cpp b/binding/bitmap-binding.cpp index 23dd4d67..285350c7 100644 --- a/binding/bitmap-binding.cpp +++ b/binding/bitmap-binding.cpp @@ -470,6 +470,28 @@ RB_METHOD(bitmapSetPlaying){ return RUBY_Qnil; } +RB_METHOD(bitmapPlay){ + RB_UNUSED_PARAM; + + rb_check_argc(argc, 0); + Bitmap *b = getPrivateData(self); + + b->play(); + + return RUBY_Qnil; +} + +RB_METHOD(bitmapPause){ + RB_UNUSED_PARAM; + + rb_check_argc(argc, 0); + Bitmap *b = getPrivateData(self); + + b->stop(); + + return RUBY_Qnil; +} + RB_METHOD(bitmapGotoStop){ RB_UNUSED_PARAM; @@ -518,6 +540,47 @@ RB_METHOD(bitmapCurrentFrame){ return INT2NUM(b->currentFrameI()); } +RB_METHOD(bitmapAddFrame){ + RB_UNUSED_PARAM; + + VALUE srcBitmap; + VALUE position; + + rb_scan_args(argc, argv, "11", &srcBitmap, &position); + + Bitmap *src = getPrivateDataCheck(srcBitmap, BitmapType); + + Bitmap *b = getPrivateData(self); + + int pos = -1; + if (position != Qnil) { + pos = NUM2INT(position); + if (pos < 0) pos = 0; + } + + return INT2NUM(b->addFrame(*src, pos)); +} + +RB_METHOD(bitmapRemoveFrame){ + RB_UNUSED_PARAM; + + VALUE position; + + rb_scan_args(argc, argv, "01", &position); + + Bitmap *b = getPrivateData(self); + + int pos = -1; + if (position != Qnil) { + pos = NUM2INT(position); + if (pos < 0) pos = 0; + } + + b->removeFrame(pos); + + return RUBY_Qnil; +} + RB_METHOD(bitmapSetFPS){ RB_UNUSED_PARAM; @@ -634,13 +697,15 @@ void bitmapBindingInit() { _rb_define_method(klass, "animated?", bitmapGetAnimated); _rb_define_method(klass, "playing", bitmapGetPlaying); _rb_define_method(klass, "playing=", bitmapSetPlaying); + _rb_define_method(klass, "play", bitmapPlay); + _rb_define_method(klass, "pause", bitmapPause); _rb_define_method(klass, "goto_and_stop", bitmapGotoStop); _rb_define_method(klass, "goto_and_play", bitmapGotoPlay); _rb_define_method(klass, "frame_count", bitmapFrames); _rb_define_method(klass, "current_frame", bitmapCurrentFrame); + _rb_define_method(klass, "add_frame", bitmapAddFrame); + _rb_define_method(klass, "remove_frame", bitmapRemoveFrame); _rb_define_method(klass, "frame_rate", bitmapGetFPS); - - // For some reason Ruby says "screw this function in particular" _rb_define_method(klass, "frame_rate=", bitmapSetFPS); _rb_define_method(klass, "looping", bitmapGetLooping); _rb_define_method(klass, "looping=", bitmapSetLooping); diff --git a/macos/mkxp-z.xcodeproj/project.pbxproj b/macos/mkxp-z.xcodeproj/project.pbxproj index f1ca5e6e..bd20119c 100644 --- a/macos/mkxp-z.xcodeproj/project.pbxproj +++ b/macos/mkxp-z.xcodeproj/project.pbxproj @@ -813,7 +813,6 @@ 3B10ED512568E95D00372D13 /* sharedstate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = sharedstate.cpp; sourceTree = ""; }; 3B10ED532568E95D00372D13 /* filesystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = filesystem.h; sourceTree = ""; }; 3B10ED542568E95D00372D13 /* filesystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = filesystem.cpp; sourceTree = ""; }; - 3B10ED552568E95D00372D13 /* meson.build */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = meson.build; sourceTree = ""; }; 3B10ED562568E95D00372D13 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; 3B10ED5A2568E95D00372D13 /* fake-api.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "fake-api.cpp"; sourceTree = ""; }; 3B10ED5C2568E95D00372D13 /* fake-api.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "fake-api.h"; sourceTree = ""; }; @@ -1307,7 +1306,6 @@ 3B5A84052569B56F00BAF2E5 /* config.cpp */, 3B10ED502568E95D00372D13 /* settingsmenu.h */, 3B10ED512568E95D00372D13 /* sharedstate.cpp */, - 3B10ED552568E95D00372D13 /* meson.build */, 3B10ED562568E95D00372D13 /* main.cpp */, 3B10ED6E2568E95D00372D13 /* settingsmenu.cpp */, 3B10EDA42568E95E00372D13 /* sharedstate.h */, diff --git a/src/display/bitmap.cpp b/src/display/bitmap.cpp index f07532c2..e4d19209 100644 --- a/src/display/bitmap.cpp +++ b/src/display/bitmap.cpp @@ -77,6 +77,13 @@ extern "C" { "Operation not supported for static bitmaps"); \ } +#define GUARD_PLAYING \ +{ \ + if (p->animation.playing) \ + throw Exception(Exception::MKXPError, \ + "Operation not supported while bitmap's animation is being played"); \ +} + #define OUTLINE_SIZE 1 /* Normalize (= ensure width and @@ -166,7 +173,7 @@ struct BitmapPrivate unsigned long long startTime; inline int currentFrameI() { - if (!playing) return lastFrame; + if (!playing || fps <= 0) return lastFrame; int i = lastFrame + ((shState->runTime() - startTime) / ((1 / fps) * 1000000)); return (loop) ? fmod(i, frames.size()) : (i > frames.size() - 1) ? frames.size() - 1 : i; } @@ -221,13 +228,13 @@ struct BitmapPrivate { format = SDL_AllocFormat(SDL_PIXELFORMAT_ABGR8888); - animation.width = -1; - animation.height = -1; + animation.width = 0; + animation.height = 0; animation.enabled = false; animation.playing = false; animation.loop = true; animation.startTime = 0; - animation.fps = -1; + animation.fps = 0; animation.lastFrame = 0; font = &shState->defaultFont(); @@ -435,7 +442,6 @@ struct BitmapOpenHandler : FileSystem::OpenHandler } SDL_Surface *s = SDL_CreateRGBSurfaceWithFormat(0, gif.width, gif.height, 32, SDL_PIXELFORMAT_ABGR8888); - SDL_SetSurfaceBlendMode(s, SDL_BLENDMODE_NONE); memcpy(s->pixels, gif.frame_image, gif.width * gif.height * 4); surfaces.push_back(s); } @@ -500,7 +506,6 @@ Bitmap::Bitmap(const char *filename) SDL_FreeSurface(s); p->addTaintedArea(rect()); - p->animation.play(); return; } @@ -605,7 +610,6 @@ Bitmap::Bitmap(void *pixeldata, int width, int height) Bitmap::Bitmap(const Bitmap &other) { other.ensureNonMega(); - other.ensureNonAnimated(); p = new BitmapPrivate(this); @@ -696,7 +700,7 @@ void Bitmap::stretchBlt(const IntRect &destRect, // Don't need this, right? This function is fine with megasurfaces it seems //GUARD_MEGA; - GUARD_ANIMATED; + GUARD_PLAYING; if (source.isDisposed()) return; @@ -763,7 +767,7 @@ void Bitmap::stretchBlt(const IntRect &destRect, SDL_BlitScaled(srcSurf, &srcRect, blitTemp, 0); - TEX::bind(p->gl.tex); + TEX::bind(getGLTypes().tex); if (bltRect.w == dstRect.w && bltRect.h == dstRect.h) { @@ -789,8 +793,8 @@ void Bitmap::stretchBlt(const IntRect &destRect, if (opacity == 255 && !p->touchesTaintedArea(destRect)) { /* Fast blit */ - GLMeta::blitBegin(p->gl); - GLMeta::blitSource(source.p->gl); + GLMeta::blitBegin(getGLTypes()); + GLMeta::blitSource(getGLTypes()); GLMeta::blitRectangle(sourceRect, destRect); GLMeta::blitEnd(); } @@ -802,7 +806,7 @@ void Bitmap::stretchBlt(const IntRect &destRect, TEXFBO &gpTex = shState->gpTexFBO(destRect.w, destRect.h); GLMeta::blitBegin(gpTex); - GLMeta::blitSource(p->gl); + GLMeta::blitSource(getGLTypes()); GLMeta::blitRectangle(destRect, Vec2i()); GLMeta::blitEnd(); @@ -1168,11 +1172,16 @@ bool Bitmap::getRaw(void *output, int output_size) guardDisposed(); - GUARD_MEGA; - GUARD_ANIMATED; + GUARD_PLAYING; - FBO::bind(p->gl.fbo); - glReadPixels(0,0,width(),height(),GL_RGBA,GL_UNSIGNED_BYTE,output); + if (!p->animation.enabled && (p->surface || p->megaSurface)) { + void *src = (p->megaSurface) ? p->megaSurface->pixels : p->surface->pixels; + memcpy(output, src, output_size); + } + else { + FBO::bind(getGLTypes().fbo); + gl.ReadPixels(0,0,width(),height(),GL_RGBA,GL_UNSIGNED_BYTE,output); + } return true; } @@ -1198,12 +1207,12 @@ void Bitmap::saveToFile(const char *filename) guardDisposed(); GUARD_MEGA; - GUARD_ANIMATED; + GUARD_PLAYING; SDL_Surface *surf = SDL_CreateRGBSurface(0, width(), height(),p->format->BitsPerPixel, p->format->Rmask,p->format->Gmask,p->format->Bmask,p->format->Amask); if (!surf) - throw Exception(Exception::SDLError, "Failed to save bitmap: %s", SDL_GetError()); + throw Exception(Exception::SDLError, "Failed to prepare bitmap for saving: %s", SDL_GetError()); getRaw(surf->pixels, surf->w * surf->h * 4); @@ -1697,6 +1706,11 @@ TEXFBO &Bitmap::getGLTypes() return (p->animation.enabled) ? p->animation.currentFrame() : p->gl; } +SDL_Surface *Bitmap::surface() const +{ + return p->surface; +} + SDL_Surface *Bitmap::megaSurface() const { return p->megaSurface; @@ -1718,6 +1732,14 @@ void Bitmap::ensureNonAnimated() const GUARD_ANIMATED; } +void Bitmap::ensureNotPlaying() const +{ + if (isDisposed()) + return; + + GUARD_PLAYING; +} + void Bitmap::stop() { GUARD_UNANIMATED; @@ -1735,7 +1757,6 @@ void Bitmap::play() bool Bitmap::isPlaying() { - GUARD_UNANIMATED; return (p->animation.playing); } @@ -1757,19 +1778,104 @@ void Bitmap::gotoAndPlay(int frame) int Bitmap::numFrames() { - GUARD_UNANIMATED; + if (!p->animation.enabled) return 1; return p->animation.frames.size(); } int Bitmap::currentFrameI() const { - GUARD_UNANIMATED; + if (!p->animation.enabled) return 1; return p->animation.currentFrameI(); } +int Bitmap::addFrame(Bitmap &source, int position) +{ + GUARD_MEGA; + + source.ensureNotPlaying(); + + if (source.height() != height() || source.width() != width()) + throw Exception(Exception::MKXPError, "Animations with varying dimensions are not supported (%ix%i vs %ix%i)", + source.width(), source.height(), width(), height()); + + TEXFBO newframe = shState->texPool().request(source.width(), source.height()); + + // Convert the bitmap into an animated bitmap if it isn't already one + if (!p->animation.enabled) { + p->animation.width = p->gl.width; + p->animation.height = p->gl.height; + p->animation.enabled = true; + p->animation.lastFrame = 0; + + if (p->animation.fps <= 0) + p->animation.fps = shState->graphics().getFrameRate(); + + p->animation.frames.push_back(p->gl); + + if (p->surface) + SDL_FreeSurface(p->surface); + p->gl = TEXFBO(); + } + + if (source.surface()) { + TEX::bind(newframe.tex); + TEX::uploadImage(source.width(), source.height(), source.surface()->pixels, GL_RGBA); + SDL_FreeSurface(p->surface); + p->surface = 0; + } + else { + // FIXME: gotta see if I can copy textures directly from one TEXFBO to the other + // I'm an idiot so I don't already know + auto pixels = new char[source.width() * source.height() * 4]; + FBO::bind(source.getGLTypes().fbo); + gl.ReadPixels(0,0,source.width(),source.height(),GL_RGBA,GL_UNSIGNED_BYTE,pixels); + TEX::bind(newframe.tex); + TEX::uploadImage(newframe.width, newframe.height, pixels, GL_RGBA); + delete pixels; + } + + int ret; + + if (position < 0) { + p->animation.frames.push_back(newframe); + ret = p->animation.frames.size(); + } + else { + p->animation.frames.insert(p->animation.frames.begin() + position, newframe); + ret = position; + } + + return ret; +} + +void Bitmap::removeFrame(int position) { + int pos = (position < 0) ? p->animation.frames.size() - 1 : clamp(position, 0, (int)(p->animation.frames.size() - 1)); + TEXFBO frame = p->animation.frames[pos]; + shState->texPool().release(p->animation.frames[pos]); + p->animation.frames.erase(p->animation.frames.begin() + pos); + + // Change the animated bitmap back to a normal one if there's only one frame left + if (p->animation.frames.size() == 1) { + + p->animation.enabled = false; + p->animation.playing = false; + p->animation.width = 0; + p->animation.height = 0; + p->animation.lastFrame = 0; + + p->gl = p->animation.frames[0]; + p->animation.frames.erase(p->animation.frames.begin()); + + p->allocSurface(); + + FBO::bind(p->gl.fbo); + gl.ReadPixels(0,0,p->gl.width, p->gl.height, GL_RGBA, GL_UNSIGNED_BYTE, p->surface->pixels); + } +} + void Bitmap::setAnimationFPS(float FPS) { - GUARD_UNANIMATED; + GUARD_MEGA; bool restart = p->animation.playing; p->animation.stop(); @@ -1779,21 +1885,21 @@ void Bitmap::setAnimationFPS(float FPS) float Bitmap::getAnimationFPS() { - GUARD_UNANIMATED; + GUARD_MEGA; return p->animation.fps; } void Bitmap::setLooping(bool loop) { - GUARD_UNANIMATED; + GUARD_MEGA; p->animation.loop = loop; } bool Bitmap::getLooping() { - GUARD_UNANIMATED; + GUARD_MEGA; return p->animation.loop; } diff --git a/src/display/bitmap.h b/src/display/bitmap.h index 9b35c02d..802cfe25 100644 --- a/src/display/bitmap.h +++ b/src/display/bitmap.h @@ -115,11 +115,12 @@ public: /* */ TEXFBO &getGLTypes(); + SDL_Surface *surface() const; SDL_Surface *megaSurface() const; void ensureNonMega() const; void ensureNonAnimated() const; - // GIF functions + // Animation functions void stop(); void play(); bool isPlaying(); @@ -128,12 +129,19 @@ public: int numFrames(); int currentFrameI() const; + int addFrame(Bitmap &source, int position = -1); + + void removeFrame(int position = -1); + void setAnimationFPS(float FPS); float getAnimationFPS(); void setLooping(bool loop); bool getLooping(); + void ensureNotPlaying() const; + // ---------- + /* Binds the backing texture and sets the correct * texture size uniform in shader */ void bindTex(ShaderBase &shader);