Support manual animation of Bitmaps

This commit is contained in:
Struma 2021-05-02 20:36:28 -04:00 committed by Roza
parent 2c188f944a
commit 1219fb4ab9
4 changed files with 207 additions and 30 deletions

View file

@ -470,6 +470,28 @@ RB_METHOD(bitmapSetPlaying){
return RUBY_Qnil;
}
RB_METHOD(bitmapPlay){
RB_UNUSED_PARAM;
rb_check_argc(argc, 0);
Bitmap *b = getPrivateData<Bitmap>(self);
b->play();
return RUBY_Qnil;
}
RB_METHOD(bitmapPause){
RB_UNUSED_PARAM;
rb_check_argc(argc, 0);
Bitmap *b = getPrivateData<Bitmap>(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<Bitmap>(srcBitmap, BitmapType);
Bitmap *b = getPrivateData<Bitmap>(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<Bitmap>(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);

View file

@ -813,7 +813,6 @@
3B10ED512568E95D00372D13 /* sharedstate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = sharedstate.cpp; sourceTree = "<group>"; };
3B10ED532568E95D00372D13 /* filesystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = filesystem.h; sourceTree = "<group>"; };
3B10ED542568E95D00372D13 /* filesystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = filesystem.cpp; sourceTree = "<group>"; };
3B10ED552568E95D00372D13 /* meson.build */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = meson.build; sourceTree = "<group>"; };
3B10ED562568E95D00372D13 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = "<group>"; };
3B10ED5A2568E95D00372D13 /* fake-api.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "fake-api.cpp"; sourceTree = "<group>"; };
3B10ED5C2568E95D00372D13 /* fake-api.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "fake-api.h"; sourceTree = "<group>"; };
@ -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 */,

View file

@ -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;
}

View file

@ -115,11 +115,12 @@ public:
/* <internal> */
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);