mirror of
https://github.com/mkxp-z/mkxp-z.git
synced 2025-08-08 16:05:34 +02:00
Support animated GIFs
This commit is contained in:
parent
0ee00d1137
commit
f98b91ff9a
12 changed files with 2514 additions and 16 deletions
|
@ -436,6 +436,131 @@ RB_METHOD(bitmapGetMega){
|
|||
return rb_bool_new(b->isMega());
|
||||
}
|
||||
|
||||
RB_METHOD(bitmapGetAnimated){
|
||||
RB_UNUSED_PARAM;
|
||||
|
||||
rb_check_argc(argc, 0);
|
||||
|
||||
Bitmap *b = getPrivateData<Bitmap>(self);
|
||||
|
||||
return rb_bool_new(b->isAnimated());
|
||||
}
|
||||
|
||||
RB_METHOD(bitmapGetPlaying){
|
||||
RB_UNUSED_PARAM;
|
||||
|
||||
rb_check_argc(argc, 0);
|
||||
|
||||
Bitmap *b = getPrivateData<Bitmap>(self);
|
||||
|
||||
return rb_bool_new(b->isPlaying());
|
||||
}
|
||||
|
||||
RB_METHOD(bitmapSetPlaying){
|
||||
RB_UNUSED_PARAM;
|
||||
|
||||
bool play;
|
||||
|
||||
rb_get_args(argc, argv, "b", &play RB_ARG_END);
|
||||
|
||||
Bitmap *b = getPrivateData<Bitmap>(self);
|
||||
|
||||
(play) ? b->play() : b->stop();
|
||||
|
||||
return RUBY_Qnil;
|
||||
}
|
||||
|
||||
RB_METHOD(bitmapGotoStop){
|
||||
RB_UNUSED_PARAM;
|
||||
|
||||
int frame;
|
||||
|
||||
rb_get_args(argc, argv, "i", &frame RB_ARG_END);
|
||||
|
||||
Bitmap *b = getPrivateData<Bitmap>(self);
|
||||
|
||||
b->gotoAndStop(frame);
|
||||
|
||||
return RUBY_Qnil;
|
||||
}
|
||||
|
||||
RB_METHOD(bitmapGotoPlay){
|
||||
RB_UNUSED_PARAM;
|
||||
|
||||
int frame;
|
||||
|
||||
rb_get_args(argc, argv, "i", &frame RB_ARG_END);
|
||||
|
||||
Bitmap *b = getPrivateData<Bitmap>(self);
|
||||
|
||||
b->gotoAndPlay(frame);
|
||||
|
||||
return RUBY_Qnil;
|
||||
}
|
||||
|
||||
RB_METHOD(bitmapFrames){
|
||||
RB_UNUSED_PARAM;
|
||||
|
||||
rb_check_argc(argc, 0);
|
||||
|
||||
Bitmap *b = getPrivateData<Bitmap>(self);
|
||||
|
||||
return INT2NUM(b->numFrames());
|
||||
}
|
||||
|
||||
RB_METHOD(bitmapCurrentFrame){
|
||||
RB_UNUSED_PARAM;
|
||||
|
||||
rb_check_argc(argc, 0);
|
||||
|
||||
Bitmap *b = getPrivateData<Bitmap>(self);
|
||||
|
||||
return INT2NUM(b->currentFrameI());
|
||||
}
|
||||
|
||||
RB_METHOD(bitmapSetFPS){
|
||||
RB_UNUSED_PARAM;
|
||||
|
||||
float fps;
|
||||
rb_get_args(argc, argv, "f", &fps RB_ARG_END);
|
||||
|
||||
Bitmap *b = getPrivateData<Bitmap>(self);
|
||||
|
||||
b->setAnimationFPS(fps);
|
||||
|
||||
return RUBY_Qnil;
|
||||
}
|
||||
|
||||
RB_METHOD(bitmapGetFPS){
|
||||
RB_UNUSED_PARAM;
|
||||
|
||||
rb_check_argc(argc, 0);
|
||||
|
||||
Bitmap *b = getPrivateData<Bitmap>(self);
|
||||
return rb_float_new(b->getAnimationFPS());
|
||||
}
|
||||
|
||||
RB_METHOD(bitmapSetLooping){
|
||||
RB_UNUSED_PARAM;
|
||||
|
||||
bool loop;
|
||||
rb_get_args(argc, argv, "b", &loop RB_ARG_END);
|
||||
|
||||
Bitmap *b = getPrivateData<Bitmap>(self);
|
||||
b->setLooping(loop);
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
RB_METHOD(bitmapGetLooping){
|
||||
RB_UNUSED_PARAM;
|
||||
|
||||
rb_check_argc(argc, 0);
|
||||
|
||||
Bitmap *b = getPrivateData<Bitmap>(self);
|
||||
return rb_bool_new(b->getLooping());
|
||||
}
|
||||
|
||||
RB_METHOD(bitmapGetMaxSize){
|
||||
RB_UNUSED_PARAM;
|
||||
|
||||
|
@ -500,6 +625,20 @@ void bitmapBindingInit() {
|
|||
|
||||
_rb_define_method(klass, "mega?", bitmapGetMega);
|
||||
rb_define_singleton_method(klass, "max_size", RUBY_METHOD_FUNC(bitmapGetMaxSize), -1);
|
||||
|
||||
_rb_define_method(klass, "animated?", bitmapGetAnimated);
|
||||
_rb_define_method(klass, "playing", bitmapGetPlaying);
|
||||
_rb_define_method(klass, "playing=", bitmapSetPlaying);
|
||||
_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, "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);
|
||||
|
||||
INIT_PROP_BIND(Bitmap, Font, "font");
|
||||
}
|
||||
|
|
|
@ -286,6 +286,14 @@
|
|||
3B97F77625E6182100A569B5 /* libSDL2_sound.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B97F77525E6182100A569B5 /* libSDL2_sound.a */; };
|
||||
3B97F77725E6182100A569B5 /* libSDL2_sound.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B97F77525E6182100A569B5 /* libSDL2_sound.a */; };
|
||||
3BA08E9B256638C900449CFF /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BD2B46925651C1B003DAD8A /* AudioToolbox.framework */; };
|
||||
3BA69454263DAB53004194EB /* libnsgif.c in Sources */ = {isa = PBXBuildFile; fileRef = 3BA6944E263DAB53004194EB /* libnsgif.c */; };
|
||||
3BA69455263DAB53004194EB /* libnsgif.c in Sources */ = {isa = PBXBuildFile; fileRef = 3BA6944E263DAB53004194EB /* libnsgif.c */; };
|
||||
3BA69456263DAB53004194EB /* libnsgif.c in Sources */ = {isa = PBXBuildFile; fileRef = 3BA6944E263DAB53004194EB /* libnsgif.c */; };
|
||||
3BA69457263DAB53004194EB /* libnsgif.c in Sources */ = {isa = PBXBuildFile; fileRef = 3BA6944E263DAB53004194EB /* libnsgif.c */; };
|
||||
3BA69458263DAB53004194EB /* lzw.c in Sources */ = {isa = PBXBuildFile; fileRef = 3BA6944F263DAB53004194EB /* lzw.c */; };
|
||||
3BA69459263DAB53004194EB /* lzw.c in Sources */ = {isa = PBXBuildFile; fileRef = 3BA6944F263DAB53004194EB /* lzw.c */; };
|
||||
3BA6945A263DAB53004194EB /* lzw.c in Sources */ = {isa = PBXBuildFile; fileRef = 3BA6944F263DAB53004194EB /* lzw.c */; };
|
||||
3BA6945B263DAB53004194EB /* lzw.c in Sources */ = {isa = PBXBuildFile; fileRef = 3BA6944F263DAB53004194EB /* lzw.c */; };
|
||||
3BC65CCD2584EDC60063AFF1 /* tilemapvx.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3B10ED7D2568E95D00372D13 /* tilemapvx.cpp */; };
|
||||
3BC65CCF2584EDC60063AFF1 /* rgssad.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3B10ED382568E95D00372D13 /* rgssad.cpp */; };
|
||||
3BC65CD02584EDC60063AFF1 /* input.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3B10ED462568E95D00372D13 /* input.cpp */; };
|
||||
|
@ -972,6 +980,11 @@
|
|||
3B97F77525E6182100A569B5 /* libSDL2_sound.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libSDL2_sound.a; path = "Dependencies/build-macosx-x86_64/lib/libSDL2_sound.a"; sourceTree = "<group>"; };
|
||||
3BA08EA4256641ED00449CFF /* Assets.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Assets.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3BA08EA6256641EE00449CFF /* Assets.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Assets.plist; sourceTree = "<group>"; };
|
||||
3BA6944E263DAB53004194EB /* libnsgif.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = libnsgif.c; sourceTree = "<group>"; };
|
||||
3BA6944F263DAB53004194EB /* lzw.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lzw.c; sourceTree = "<group>"; };
|
||||
3BA69451263DAB53004194EB /* log.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = log.h; sourceTree = "<group>"; };
|
||||
3BA69452263DAB53004194EB /* lzw.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lzw.h; sourceTree = "<group>"; };
|
||||
3BA69453263DAB53004194EB /* libnsgif.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libnsgif.h; sourceTree = "<group>"; };
|
||||
3BC65D442584EDC60063AFF1 /* Z.arm.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Z.arm.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3BC65D4A2584EED10063AFF1 /* libvorbis.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libvorbis.a; path = "Dependencies/build-macosx-arm64/lib/libvorbis.a"; sourceTree = "<group>"; };
|
||||
3BC65D4B2584EED10063AFF1 /* libpixman-1.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libpixman-1.a"; path = "Dependencies/build-macosx-arm64/lib/libpixman-1.a"; sourceTree = "<group>"; };
|
||||
|
@ -1407,6 +1420,7 @@
|
|||
3B10ED6F2568E95D00372D13 /* display */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3BA6944D263DAB53004194EB /* libnsgif */,
|
||||
3B10ED7E2568E95D00372D13 /* gl */,
|
||||
3B10ED702568E95D00372D13 /* tilemap.h */,
|
||||
3B10ED712568E95D00372D13 /* tilemap-common.h */,
|
||||
|
@ -1605,6 +1619,26 @@
|
|||
path = misc;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3BA6944D263DAB53004194EB /* libnsgif */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3BA6944E263DAB53004194EB /* libnsgif.c */,
|
||||
3BA6944F263DAB53004194EB /* lzw.c */,
|
||||
3BA69450263DAB53004194EB /* utils */,
|
||||
3BA69452263DAB53004194EB /* lzw.h */,
|
||||
3BA69453263DAB53004194EB /* libnsgif.h */,
|
||||
);
|
||||
path = libnsgif;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3BA69450263DAB53004194EB /* utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3BA69451263DAB53004194EB /* log.h */,
|
||||
);
|
||||
path = utils;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3BC65D492584EE690063AFF1 /* ARM64 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -2041,6 +2075,7 @@
|
|||
3B1C239425A19C600075EF5D /* etc.cpp in Sources */,
|
||||
3B1C239525A19C600075EF5D /* shader.cpp in Sources */,
|
||||
3B1C239625A19C600075EF5D /* tilemap.cpp in Sources */,
|
||||
3BA6945B263DAB53004194EB /* lzw.c in Sources */,
|
||||
3B1C239825A19C600075EF5D /* window.cpp in Sources */,
|
||||
3B1C239A25A19C600075EF5D /* input-binding.cpp in Sources */,
|
||||
3B1C239B25A19C600075EF5D /* keybindings.cpp in Sources */,
|
||||
|
@ -2054,6 +2089,7 @@
|
|||
3B1C23A525A19C600075EF5D /* tilemapvx-binding.cpp in Sources */,
|
||||
3B1C23A625A19C600075EF5D /* window-binding.cpp in Sources */,
|
||||
3B1C23A725A19C600075EF5D /* midisource.cpp in Sources */,
|
||||
3BA69457263DAB53004194EB /* libnsgif.c in Sources */,
|
||||
3B1C23A825A19C600075EF5D /* graphics-binding.cpp in Sources */,
|
||||
3B1C23A925A19C600075EF5D /* plane.cpp in Sources */,
|
||||
3B1C23AA25A19C600075EF5D /* tilequad.cpp in Sources */,
|
||||
|
@ -2093,6 +2129,7 @@
|
|||
3B522DDE259C1E53003301C4 /* http-binding.cpp in Sources */,
|
||||
3BC65CCD2584EDC60063AFF1 /* tilemapvx.cpp in Sources */,
|
||||
3BC65CCF2584EDC60063AFF1 /* rgssad.cpp in Sources */,
|
||||
3BA6945A263DAB53004194EB /* lzw.c in Sources */,
|
||||
3BC65CD02584EDC60063AFF1 /* input.cpp in Sources */,
|
||||
3BC65CD12584EDC60063AFF1 /* tilemap-binding.cpp in Sources */,
|
||||
3BC65CD22584EDC60063AFF1 /* audio.cpp in Sources */,
|
||||
|
@ -2105,6 +2142,7 @@
|
|||
3BC65CD82584EDC60063AFF1 /* bitmap-binding.cpp in Sources */,
|
||||
3BC65CD92584EDC60063AFF1 /* vorbissource.cpp in Sources */,
|
||||
3BC65CDB2584EDC60063AFF1 /* filesystem-binding.cpp in Sources */,
|
||||
3BA69456263DAB53004194EB /* libnsgif.c in Sources */,
|
||||
3BC65CDD2584EDC60063AFF1 /* glstate.cpp in Sources */,
|
||||
3BC65CDE2584EDC60063AFF1 /* gl-fun.cpp in Sources */,
|
||||
3BC65CDF2584EDC60063AFF1 /* sprite-binding.cpp in Sources */,
|
||||
|
@ -2169,6 +2207,7 @@
|
|||
3B522DDC259C1E53003301C4 /* http-binding.cpp in Sources */,
|
||||
3BC65D8E2584F3AD0063AFF1 /* tilemapvx.cpp in Sources */,
|
||||
3BC65D902584F3AD0063AFF1 /* rgssad.cpp in Sources */,
|
||||
3BA69458263DAB53004194EB /* lzw.c in Sources */,
|
||||
3BC65D912584F3AD0063AFF1 /* input.cpp in Sources */,
|
||||
3BC65D922584F3AD0063AFF1 /* tilemap-binding.cpp in Sources */,
|
||||
3BC65D932584F3AD0063AFF1 /* audio.cpp in Sources */,
|
||||
|
@ -2181,6 +2220,7 @@
|
|||
3BC65D992584F3AD0063AFF1 /* bitmap-binding.cpp in Sources */,
|
||||
3BC65D9A2584F3AD0063AFF1 /* vorbissource.cpp in Sources */,
|
||||
3BC65D9C2584F3AD0063AFF1 /* filesystem-binding.cpp in Sources */,
|
||||
3BA69454263DAB53004194EB /* libnsgif.c in Sources */,
|
||||
3BC65D9E2584F3AD0063AFF1 /* glstate.cpp in Sources */,
|
||||
3BC65D9F2584F3AD0063AFF1 /* gl-fun.cpp in Sources */,
|
||||
3BC65DA02584F3AD0063AFF1 /* sprite-binding.cpp in Sources */,
|
||||
|
@ -2245,6 +2285,7 @@
|
|||
3B522DDD259C1E53003301C4 /* http-binding.cpp in Sources */,
|
||||
3B10EDC22568E95E00372D13 /* tilemapvx.cpp in Sources */,
|
||||
3B10EDA72568E95E00372D13 /* rgssad.cpp in Sources */,
|
||||
3BA69459263DAB53004194EB /* lzw.c in Sources */,
|
||||
3B10EDA82568E95E00372D13 /* input.cpp in Sources */,
|
||||
3B10EE022568E96A00372D13 /* tilemap-binding.cpp in Sources */,
|
||||
3B10EDB72568E95E00372D13 /* audio.cpp in Sources */,
|
||||
|
@ -2257,6 +2298,7 @@
|
|||
3B10EDFF2568E96A00372D13 /* bitmap-binding.cpp in Sources */,
|
||||
3B10EDBA2568E95E00372D13 /* vorbissource.cpp in Sources */,
|
||||
3B10EDF62568E96A00372D13 /* filesystem-binding.cpp in Sources */,
|
||||
3BA69455263DAB53004194EB /* libnsgif.c in Sources */,
|
||||
3B10EDC92568E95E00372D13 /* glstate.cpp in Sources */,
|
||||
3B10EDCC2568E95E00372D13 /* gl-fun.cpp in Sources */,
|
||||
3B10EDFB2568E96A00372D13 /* sprite-binding.cpp in Sources */,
|
||||
|
|
|
@ -43,14 +43,40 @@
|
|||
#include "filesystem.h"
|
||||
#include "font.h"
|
||||
#include "eventthread.h"
|
||||
#include "graphics.h"
|
||||
#include "system.h"
|
||||
#include "util/util.h"
|
||||
|
||||
#include "debugwriter.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <algorithm>
|
||||
|
||||
extern "C" {
|
||||
#include "libnsgif/libnsgif.h"
|
||||
}
|
||||
|
||||
#define GUARD_MEGA \
|
||||
{ \
|
||||
if (p->megaSurface) \
|
||||
throw Exception(Exception::MKXPError, \
|
||||
"Operation not supported for mega surfaces"); \
|
||||
"Operation not supported for mega surfaces / animated bitmaps"); \
|
||||
}
|
||||
|
||||
#define GUARD_ANIMATED \
|
||||
{ \
|
||||
if (p->animation.enabled) \
|
||||
throw Exception(Exception::MKXPError, \
|
||||
"Operation not supported for animated bitmaps"); \
|
||||
}
|
||||
|
||||
#define GUARD_UNANIMATED \
|
||||
{ \
|
||||
if (!p->animation.enabled) \
|
||||
throw Exception(Exception::MKXPError, \
|
||||
"Operation not supported for static bitmaps"); \
|
||||
}
|
||||
|
||||
#define OUTLINE_SIZE 1
|
||||
|
||||
/* Normalize (= ensure width and
|
||||
|
@ -74,10 +100,97 @@ static IntRect normalizedRect(const IntRect &rect)
|
|||
return norm;
|
||||
}
|
||||
|
||||
|
||||
// libnsgif loading callbacks, taken pretty much straight from their tests
|
||||
|
||||
static void *gif_bitmap_create(int width, int height)
|
||||
{
|
||||
/* ensure a stupidly large bitmap is not created */
|
||||
return calloc(width * height, 4);
|
||||
}
|
||||
|
||||
|
||||
static void gif_bitmap_set_opaque(void *bitmap, bool opaque)
|
||||
{
|
||||
(void) opaque; /* unused */
|
||||
(void) bitmap; /* unused */
|
||||
assert(bitmap);
|
||||
}
|
||||
|
||||
|
||||
static bool gif_bitmap_test_opaque(void *bitmap)
|
||||
{
|
||||
(void) bitmap; /* unused */
|
||||
assert(bitmap);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static unsigned char *gif_bitmap_get_buffer(void *bitmap)
|
||||
{
|
||||
assert(bitmap);
|
||||
return (unsigned char *)bitmap;
|
||||
}
|
||||
|
||||
|
||||
static void gif_bitmap_destroy(void *bitmap)
|
||||
{
|
||||
assert(bitmap);
|
||||
free(bitmap);
|
||||
}
|
||||
|
||||
|
||||
static void gif_bitmap_modified(void *bitmap)
|
||||
{
|
||||
(void) bitmap; /* unused */
|
||||
assert(bitmap);
|
||||
return;
|
||||
}
|
||||
|
||||
// --------------------
|
||||
|
||||
struct BitmapPrivate
|
||||
{
|
||||
Bitmap *self;
|
||||
|
||||
struct {
|
||||
int width;
|
||||
int height;
|
||||
|
||||
bool enabled;
|
||||
bool playing;
|
||||
bool loop;
|
||||
std::vector<TEXFBO> frames;
|
||||
float fps;
|
||||
int lastFrame;
|
||||
unsigned long long startTime;
|
||||
|
||||
inline int currentFrameI() {
|
||||
if (!playing) return lastFrame;
|
||||
int i = lastFrame + ((shState->runTime() - startTime) / ((1 / fps) * 1000000));
|
||||
int r = (loop) ? fmod(i, frames.size()) : (i > frames.size() - 1) ? frames.size() - 1 : i;
|
||||
return r;
|
||||
}
|
||||
|
||||
TEXFBO ¤tFrame() {
|
||||
return frames[currentFrameI()];
|
||||
}
|
||||
|
||||
void play() {
|
||||
playing = true;
|
||||
startTime = shState->runTime();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
lastFrame = currentFrameI();
|
||||
playing = false;
|
||||
}
|
||||
|
||||
void seek(int frame) {
|
||||
lastFrame = clamp(frame, 0, (int)frames.size());
|
||||
}
|
||||
} animation;
|
||||
|
||||
TEXFBO gl;
|
||||
|
||||
Font *font;
|
||||
|
@ -108,6 +221,15 @@ struct BitmapPrivate
|
|||
surface(0)
|
||||
{
|
||||
format = SDL_AllocFormat(SDL_PIXELFORMAT_ABGR8888);
|
||||
|
||||
animation.width = -1;
|
||||
animation.height = -1;
|
||||
animation.enabled = false;
|
||||
animation.playing = false;
|
||||
animation.loop = true;
|
||||
animation.startTime = 0;
|
||||
animation.fps = -1;
|
||||
animation.lastFrame = 0;
|
||||
|
||||
font = &shState->defaultFont();
|
||||
pixman_region_init(&tainted);
|
||||
|
@ -168,13 +290,19 @@ struct BitmapPrivate
|
|||
|
||||
void bindTexture(ShaderBase &shader)
|
||||
{
|
||||
if (animation.enabled) {
|
||||
TEXFBO cframe = animation.currentFrame();
|
||||
TEX::bind(cframe.tex);
|
||||
shader.setTexSize(Vec2i(cframe.width, cframe.height));
|
||||
return;
|
||||
}
|
||||
TEX::bind(gl.tex);
|
||||
shader.setTexSize(Vec2i(gl.width, gl.height));
|
||||
}
|
||||
|
||||
void bindFBO()
|
||||
{
|
||||
FBO::bind(gl.fbo);
|
||||
FBO::bind((animation.enabled) ? animation.currentFrame().fbo : gl.fbo);
|
||||
}
|
||||
|
||||
void pushSetViewport(ShaderBase &shader) const
|
||||
|
@ -235,16 +363,90 @@ struct BitmapPrivate
|
|||
|
||||
struct BitmapOpenHandler : FileSystem::OpenHandler
|
||||
{
|
||||
SDL_Surface *surf;
|
||||
std::vector<SDL_Surface*> surfaces;
|
||||
float animation_rate;
|
||||
|
||||
// Filled if errors from GIF reading are needed
|
||||
std::string error;
|
||||
|
||||
BitmapOpenHandler()
|
||||
: surf(0)
|
||||
: animation_rate(-1)
|
||||
{}
|
||||
|
||||
bool tryRead(SDL_RWops &ops, const char *ext)
|
||||
{
|
||||
surf = IMG_LoadTyped_RW(&ops, 1, ext);
|
||||
return surf != 0;
|
||||
if (IMG_isGIF(&ops)) {
|
||||
// Use libnsgif to initialise the gif data
|
||||
gif_animation gif {};
|
||||
|
||||
gif_bitmap_callback_vt gif_bitmap_callbacks = {
|
||||
gif_bitmap_create,
|
||||
gif_bitmap_destroy,
|
||||
gif_bitmap_get_buffer,
|
||||
gif_bitmap_set_opaque,
|
||||
gif_bitmap_test_opaque,
|
||||
gif_bitmap_modified
|
||||
};
|
||||
|
||||
gif_create(&gif, &gif_bitmap_callbacks);
|
||||
|
||||
size_t data_size = ops.size(&ops);
|
||||
|
||||
auto data = new unsigned char[data_size];
|
||||
ops.seek(&ops, 0, RW_SEEK_SET);
|
||||
ops.read(&ops, data, data_size, 1);
|
||||
|
||||
int status;
|
||||
do {
|
||||
status = gif_initialise(&gif, data_size, data);
|
||||
if (status != GIF_OK && status != GIF_WORKING) {
|
||||
gif_finalise(&gif);
|
||||
delete data;
|
||||
error = "Failed to initialize GIF (Error " + std::to_string(status) + ")";
|
||||
return false;
|
||||
}
|
||||
} while (status != GIF_OK);
|
||||
|
||||
int image_width = -1;
|
||||
int image_height = -1;
|
||||
|
||||
// Read every frame
|
||||
for (int i = 0; i < gif.frame_count; i++) {
|
||||
int status = gif_decode_frame(&gif, i);
|
||||
if (status != GIF_OK && status != GIF_WORKING) {
|
||||
error = "Failed to read GIF frame " + std::to_string(i + 1) + " (Error " + std::to_string(status) + ")";
|
||||
for (SDL_Surface *s : surfaces)
|
||||
SDL_FreeSurface(s);
|
||||
break;
|
||||
}
|
||||
|
||||
if (image_width == -1 || !image_height == -1) {
|
||||
image_width = gif.width;
|
||||
image_height = gif.height;
|
||||
}
|
||||
else if (gif.width != image_width || gif.height != image_height) {
|
||||
error = "Failed to read GIF (Varying frame size)";
|
||||
for (SDL_Surface *s : surfaces)
|
||||
SDL_FreeSurface(s);
|
||||
break;
|
||||
}
|
||||
|
||||
if (animation_rate == -1 && gif.frames[gif.decoded_frame].frame_delay) {
|
||||
animation_rate = 1 / ((float)gif.frames[gif.decoded_frame].frame_delay / 100);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
gif_finalise(&gif);
|
||||
delete data;
|
||||
} else {
|
||||
surfaces.push_back(IMG_LoadTyped_RW(&ops, 1, ext));
|
||||
}
|
||||
return (surfaces.size() > 0 && error.empty());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -252,11 +454,59 @@ Bitmap::Bitmap(const char *filename)
|
|||
{
|
||||
BitmapOpenHandler handler;
|
||||
shState->fileSystem().openRead(handler, filename);
|
||||
SDL_Surface *imgSurf = handler.surf;
|
||||
|
||||
if (!handler.error.empty()) {
|
||||
// Not loaded with SDL, but I want it to be caught with the same exception type
|
||||
throw Exception(Exception::SDLError, "Error loading image '%s': %s", filename, handler.error.c_str());
|
||||
}
|
||||
else if (handler.surfaces.size() < 1) {
|
||||
throw Exception(Exception::SDLError, "Error loading image '%s': %s",
|
||||
filename, SDL_GetError());
|
||||
}
|
||||
|
||||
if (handler.surfaces.size() > 1) {
|
||||
p = new BitmapPrivate(this);
|
||||
p->animation.enabled = true;
|
||||
p->animation.width = handler.surfaces[0]->w;
|
||||
p->animation.height = handler.surfaces[0]->h;
|
||||
|
||||
if (p->animation.width >= glState.caps.maxTexSize || p->animation.height > glState.caps.maxTexSize)
|
||||
{
|
||||
throw new Exception(Exception::MKXPError, "Animation too large (%ix%i, max %ix%i)",
|
||||
p->animation.width, p->animation.height, glState.caps.maxTexSize, glState.caps.maxTexSize);
|
||||
}
|
||||
|
||||
p->animation.fps = (handler.animation_rate == -1) ? shState->graphics().getFrameRate() : handler.animation_rate;
|
||||
|
||||
for (SDL_Surface* s : handler.surfaces)
|
||||
{
|
||||
TEXFBO texfbo;
|
||||
try {
|
||||
texfbo = shState->texPool().request(p->animation.width, p->animation.height);
|
||||
}
|
||||
catch (const Exception &e)
|
||||
{
|
||||
for (SDL_Surface *s : handler.surfaces)
|
||||
SDL_FreeSurface(s);
|
||||
|
||||
throw e;
|
||||
}
|
||||
TEX::bind(texfbo.tex);
|
||||
TEX::uploadImage(p->animation.width, p->animation.height, s->pixels, GL_RGBA);
|
||||
|
||||
p->animation.frames.push_back(texfbo);
|
||||
}
|
||||
|
||||
for (SDL_Surface *s : handler.surfaces)
|
||||
SDL_FreeSurface(s);
|
||||
|
||||
p->addTaintedArea(rect());
|
||||
p->animation.play();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!imgSurf)
|
||||
throw Exception(Exception::SDLError, "Error loading image '%s': %s",
|
||||
filename, SDL_GetError());
|
||||
SDL_Surface *imgSurf = handler.surfaces[0];
|
||||
|
||||
|
||||
p->ensureFormat(imgSurf, SDL_PIXELFORMAT_ABGR8888);
|
||||
|
||||
|
@ -356,6 +606,7 @@ Bitmap::Bitmap(void *pixeldata, int width, int height)
|
|||
Bitmap::Bitmap(const Bitmap &other)
|
||||
{
|
||||
other.ensureNonMega();
|
||||
other.ensureNonAnimated();
|
||||
|
||||
p = new BitmapPrivate(this);
|
||||
|
||||
|
@ -375,6 +626,9 @@ int Bitmap::width() const
|
|||
|
||||
if (p->megaSurface)
|
||||
return p->megaSurface->w;
|
||||
|
||||
if (p->animation.enabled)
|
||||
return p->animation.width;
|
||||
|
||||
return p->gl.width;
|
||||
}
|
||||
|
@ -385,6 +639,9 @@ int Bitmap::height() const
|
|||
|
||||
if (p->megaSurface)
|
||||
return p->megaSurface->h;
|
||||
|
||||
if (p->animation.enabled)
|
||||
return p->animation.height;
|
||||
|
||||
return p->gl.height;
|
||||
}
|
||||
|
@ -392,7 +649,13 @@ int Bitmap::height() const
|
|||
bool Bitmap::isMega() const{
|
||||
guardDisposed();
|
||||
|
||||
return p->megaSurface;
|
||||
return p->megaSurface || p->animation.enabled;
|
||||
}
|
||||
|
||||
bool Bitmap::isAnimated() const {
|
||||
guardDisposed();
|
||||
|
||||
return p->animation.enabled;
|
||||
}
|
||||
|
||||
IntRect Bitmap::rect() const
|
||||
|
@ -431,7 +694,10 @@ void Bitmap::stretchBlt(const IntRect &destRect,
|
|||
{
|
||||
guardDisposed();
|
||||
|
||||
GUARD_MEGA;
|
||||
// Don't need this, right? This function is fine with megasurfaces it seems
|
||||
//GUARD_MEGA;
|
||||
|
||||
GUARD_ANIMATED;
|
||||
|
||||
if (source.isDisposed())
|
||||
return;
|
||||
|
@ -581,6 +847,7 @@ void Bitmap::fillRect(const IntRect &rect, const Vec4 &color)
|
|||
guardDisposed();
|
||||
|
||||
GUARD_MEGA;
|
||||
GUARD_ANIMATED;
|
||||
|
||||
p->fillRect(rect, color);
|
||||
|
||||
|
@ -609,6 +876,7 @@ void Bitmap::gradientFillRect(const IntRect &rect,
|
|||
guardDisposed();
|
||||
|
||||
GUARD_MEGA;
|
||||
GUARD_ANIMATED;
|
||||
|
||||
SimpleColorShader &shader = shState->shaders().simpleColor;
|
||||
shader.bind();
|
||||
|
@ -655,6 +923,7 @@ void Bitmap::clearRect(const IntRect &rect)
|
|||
guardDisposed();
|
||||
|
||||
GUARD_MEGA;
|
||||
GUARD_ANIMATED;
|
||||
|
||||
p->fillRect(rect, Vec4());
|
||||
|
||||
|
@ -666,6 +935,7 @@ void Bitmap::blur()
|
|||
guardDisposed();
|
||||
|
||||
GUARD_MEGA;
|
||||
GUARD_ANIMATED;
|
||||
|
||||
Quad &quad = shState->gpQuad();
|
||||
FloatRect rect(0, 0, width(), height());
|
||||
|
@ -711,6 +981,7 @@ void Bitmap::radialBlur(int angle, int divisions)
|
|||
guardDisposed();
|
||||
|
||||
GUARD_MEGA;
|
||||
GUARD_ANIMATED;
|
||||
|
||||
angle = clamp<int>(angle, 0, 359);
|
||||
divisions = clamp<int>(divisions, 2, 100);
|
||||
|
@ -806,6 +1077,7 @@ void Bitmap::clear()
|
|||
guardDisposed();
|
||||
|
||||
GUARD_MEGA;
|
||||
GUARD_ANIMATED;
|
||||
|
||||
p->bindFBO();
|
||||
|
||||
|
@ -833,6 +1105,7 @@ Color Bitmap::getPixel(int x, int y) const
|
|||
guardDisposed();
|
||||
|
||||
GUARD_MEGA;
|
||||
GUARD_ANIMATED;
|
||||
|
||||
if (x < 0 || y < 0 || x >= width() || y >= height())
|
||||
return Vec4();
|
||||
|
@ -863,6 +1136,7 @@ void Bitmap::setPixel(int x, int y, const Color &color)
|
|||
guardDisposed();
|
||||
|
||||
GUARD_MEGA;
|
||||
GUARD_ANIMATED;
|
||||
|
||||
uint8_t pixel[] =
|
||||
{
|
||||
|
@ -896,6 +1170,7 @@ bool Bitmap::getRaw(void *output, int output_size)
|
|||
guardDisposed();
|
||||
|
||||
GUARD_MEGA;
|
||||
GUARD_ANIMATED;
|
||||
|
||||
FBO::bind(p->gl.fbo);
|
||||
glReadPixels(0,0,width(),height(),GL_RGBA,GL_UNSIGNED_BYTE,output);
|
||||
|
@ -910,6 +1185,7 @@ void Bitmap::replaceRaw(void *pixel_data, int size)
|
|||
if (size != w*h*4) return;
|
||||
|
||||
GUARD_MEGA;
|
||||
GUARD_ANIMATED;
|
||||
|
||||
TEX::bind(p->gl.tex);
|
||||
TEX::uploadImage(w, h, pixel_data, GL_RGBA);
|
||||
|
@ -923,6 +1199,7 @@ void Bitmap::saveToFile(const char *filename)
|
|||
guardDisposed();
|
||||
|
||||
GUARD_MEGA;
|
||||
GUARD_ANIMATED;
|
||||
|
||||
SDL_Surface *surf = SDL_CreateRGBSurface(0, width(), height(),p->format->BitsPerPixel, p->format->Rmask,p->format->Gmask,p->format->Bmask,p->format->Amask);
|
||||
|
||||
|
@ -972,6 +1249,7 @@ void Bitmap::hueChange(int hue)
|
|||
guardDisposed();
|
||||
|
||||
GUARD_MEGA;
|
||||
GUARD_ANIMATED;
|
||||
|
||||
if ((hue % 360) == 0)
|
||||
return;
|
||||
|
@ -1119,6 +1397,7 @@ void Bitmap::drawText(const IntRect &rect, const char *str, int align)
|
|||
guardDisposed();
|
||||
|
||||
GUARD_MEGA;
|
||||
GUARD_ANIMATED;
|
||||
|
||||
std::string fixed = fixupString(str);
|
||||
str = fixed.c_str();
|
||||
|
@ -1380,6 +1659,7 @@ IntRect Bitmap::textSize(const char *str)
|
|||
guardDisposed();
|
||||
|
||||
GUARD_MEGA;
|
||||
GUARD_ANIMATED;
|
||||
|
||||
TTF_Font *font = p->font->getSdlFont();
|
||||
|
||||
|
@ -1415,7 +1695,7 @@ void Bitmap::setInitFont(Font *value)
|
|||
|
||||
TEXFBO &Bitmap::getGLTypes()
|
||||
{
|
||||
return p->gl;
|
||||
return (p->animation.enabled) ? p->animation.currentFrame() : p->gl;
|
||||
}
|
||||
|
||||
SDL_Surface *Bitmap::megaSurface() const
|
||||
|
@ -1431,6 +1711,94 @@ void Bitmap::ensureNonMega() const
|
|||
GUARD_MEGA;
|
||||
}
|
||||
|
||||
void Bitmap::ensureNonAnimated() const
|
||||
{
|
||||
if (isDisposed())
|
||||
return;
|
||||
|
||||
GUARD_ANIMATED;
|
||||
}
|
||||
|
||||
void Bitmap::stop()
|
||||
{
|
||||
GUARD_UNANIMATED;
|
||||
if (!p->animation.playing) return;
|
||||
|
||||
p->animation.stop();
|
||||
}
|
||||
|
||||
void Bitmap::play()
|
||||
{
|
||||
GUARD_UNANIMATED;
|
||||
if (p->animation.playing) return;
|
||||
p->animation.play();
|
||||
}
|
||||
|
||||
bool Bitmap::isPlaying()
|
||||
{
|
||||
GUARD_UNANIMATED;
|
||||
return (p->animation.playing);
|
||||
}
|
||||
|
||||
void Bitmap::gotoAndStop(int frame)
|
||||
{
|
||||
GUARD_UNANIMATED;
|
||||
|
||||
p->animation.stop();
|
||||
p->animation.seek(frame);
|
||||
}
|
||||
void Bitmap::gotoAndPlay(int frame)
|
||||
{
|
||||
GUARD_UNANIMATED;
|
||||
|
||||
p->animation.stop();
|
||||
p->animation.seek(frame);
|
||||
p->animation.play();
|
||||
}
|
||||
|
||||
int Bitmap::numFrames()
|
||||
{
|
||||
GUARD_UNANIMATED;
|
||||
return p->animation.frames.size();
|
||||
}
|
||||
|
||||
int Bitmap::currentFrameI() const
|
||||
{
|
||||
GUARD_UNANIMATED;
|
||||
return p->animation.currentFrameI();
|
||||
}
|
||||
|
||||
void Bitmap::setAnimationFPS(float FPS)
|
||||
{
|
||||
GUARD_UNANIMATED;
|
||||
|
||||
bool restart = p->animation.playing;
|
||||
p->animation.stop();
|
||||
p->animation.fps = FPS;
|
||||
if (restart) p->animation.play();
|
||||
}
|
||||
|
||||
float Bitmap::getAnimationFPS()
|
||||
{
|
||||
GUARD_UNANIMATED;
|
||||
|
||||
return p->animation.fps;
|
||||
}
|
||||
|
||||
void Bitmap::setLooping(bool loop)
|
||||
{
|
||||
GUARD_UNANIMATED;
|
||||
|
||||
p->animation.loop = loop;
|
||||
}
|
||||
|
||||
bool Bitmap::getLooping()
|
||||
{
|
||||
GUARD_UNANIMATED;
|
||||
|
||||
return p->animation.loop;
|
||||
}
|
||||
|
||||
void Bitmap::bindTex(ShaderBase &shader)
|
||||
{
|
||||
p->bindTexture(shader);
|
||||
|
@ -1449,6 +1817,10 @@ void Bitmap::releaseResources()
|
|||
{
|
||||
if (p->megaSurface)
|
||||
SDL_FreeSurface(p->megaSurface);
|
||||
else if (p->animation.enabled) {
|
||||
for (TEXFBO &tex : p->animation.frames)
|
||||
shState->texPool().release(tex);
|
||||
}
|
||||
else
|
||||
shState->texPool().release(p->gl);
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ public:
|
|||
int width() const;
|
||||
int height() const;
|
||||
bool isMega() const;
|
||||
bool isAnimated() const;
|
||||
|
||||
IntRect rect() const;
|
||||
|
||||
|
@ -116,6 +117,22 @@ public:
|
|||
TEXFBO &getGLTypes();
|
||||
SDL_Surface *megaSurface() const;
|
||||
void ensureNonMega() const;
|
||||
void ensureNonAnimated() const;
|
||||
|
||||
// GIF functions
|
||||
void stop();
|
||||
void play();
|
||||
bool isPlaying();
|
||||
void gotoAndStop(int frame);
|
||||
void gotoAndPlay(int frame);
|
||||
int numFrames();
|
||||
int currentFrameI() const;
|
||||
|
||||
void setAnimationFPS(float FPS);
|
||||
float getAnimationFPS();
|
||||
|
||||
void setLooping(bool loop);
|
||||
bool getLooping();
|
||||
|
||||
/* Binds the backing texture and sets the correct
|
||||
* texture size uniform in shader */
|
||||
|
|
1228
src/display/libnsgif/libnsgif.c
Normal file
1228
src/display/libnsgif/libnsgif.c
Normal file
File diff suppressed because it is too large
Load diff
192
src/display/libnsgif/libnsgif.h
Normal file
192
src/display/libnsgif/libnsgif.h
Normal file
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* Copyright 2004 Richard Wilson <richard.wilson@netsurf-browser.org>
|
||||
* Copyright 2008 Sean Fox <dyntryx@gmail.com>
|
||||
*
|
||||
* This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
|
||||
* Licenced under the MIT License,
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Interface to progressive animated GIF file decoding.
|
||||
*/
|
||||
|
||||
#ifndef _LIBNSGIF_H_
|
||||
#define _LIBNSGIF_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
/* Error return values */
|
||||
typedef enum {
|
||||
GIF_WORKING = 1,
|
||||
GIF_OK = 0,
|
||||
GIF_INSUFFICIENT_FRAME_DATA = -1,
|
||||
GIF_FRAME_DATA_ERROR = -2,
|
||||
GIF_INSUFFICIENT_DATA = -3,
|
||||
GIF_DATA_ERROR = -4,
|
||||
GIF_INSUFFICIENT_MEMORY = -5,
|
||||
GIF_FRAME_NO_DISPLAY = -6,
|
||||
GIF_END_OF_FRAME = -7
|
||||
} gif_result;
|
||||
|
||||
/** GIF frame data */
|
||||
typedef struct gif_frame {
|
||||
/** whether the frame should be displayed/animated */
|
||||
bool display;
|
||||
/** delay (in cs) before animating the frame */
|
||||
unsigned int frame_delay;
|
||||
|
||||
/* Internal members are listed below */
|
||||
|
||||
/** offset (in bytes) to the GIF frame data */
|
||||
unsigned int frame_pointer;
|
||||
/** whether the frame has previously been used */
|
||||
bool virgin;
|
||||
/** whether the frame is totally opaque */
|
||||
bool opaque;
|
||||
/** whether a forcable screen redraw is required */
|
||||
bool redraw_required;
|
||||
/** how the previous frame should be disposed; affects plotting */
|
||||
unsigned char disposal_method;
|
||||
/** whether we acknoledge transparency */
|
||||
bool transparency;
|
||||
/** the index designating a transparent pixel */
|
||||
unsigned char transparency_index;
|
||||
/** x co-ordinate of redraw rectangle */
|
||||
unsigned int redraw_x;
|
||||
/** y co-ordinate of redraw rectangle */
|
||||
unsigned int redraw_y;
|
||||
/** width of redraw rectangle */
|
||||
unsigned int redraw_width;
|
||||
/** height of redraw rectangle */
|
||||
unsigned int redraw_height;
|
||||
} gif_frame;
|
||||
|
||||
/* API for Bitmap callbacks */
|
||||
typedef void* (*gif_bitmap_cb_create)(int width, int height);
|
||||
typedef void (*gif_bitmap_cb_destroy)(void *bitmap);
|
||||
typedef unsigned char* (*gif_bitmap_cb_get_buffer)(void *bitmap);
|
||||
typedef void (*gif_bitmap_cb_set_opaque)(void *bitmap, bool opaque);
|
||||
typedef bool (*gif_bitmap_cb_test_opaque)(void *bitmap);
|
||||
typedef void (*gif_bitmap_cb_modified)(void *bitmap);
|
||||
|
||||
/** Bitmap callbacks function table */
|
||||
typedef struct gif_bitmap_callback_vt {
|
||||
/** Create a bitmap. */
|
||||
gif_bitmap_cb_create bitmap_create;
|
||||
/** Free a bitmap. */
|
||||
gif_bitmap_cb_destroy bitmap_destroy;
|
||||
/** Return a pointer to the pixel data in a bitmap. */
|
||||
gif_bitmap_cb_get_buffer bitmap_get_buffer;
|
||||
|
||||
/* Members below are optional */
|
||||
|
||||
/** Sets whether a bitmap should be plotted opaque. */
|
||||
gif_bitmap_cb_set_opaque bitmap_set_opaque;
|
||||
/** Tests whether a bitmap has an opaque alpha channel. */
|
||||
gif_bitmap_cb_test_opaque bitmap_test_opaque;
|
||||
/** The bitmap image has changed, so flush any persistant cache. */
|
||||
gif_bitmap_cb_modified bitmap_modified;
|
||||
} gif_bitmap_callback_vt;
|
||||
|
||||
/** GIF animation data */
|
||||
typedef struct gif_animation {
|
||||
/** LZW decode context */
|
||||
void *lzw_ctx;
|
||||
/** callbacks for bitmap functions */
|
||||
gif_bitmap_callback_vt bitmap_callbacks;
|
||||
/** pointer to GIF data */
|
||||
unsigned char *gif_data;
|
||||
/** width of GIF (may increase during decoding) */
|
||||
unsigned int width;
|
||||
/** heigth of GIF (may increase during decoding) */
|
||||
unsigned int height;
|
||||
/** number of frames decoded */
|
||||
unsigned int frame_count;
|
||||
/** number of frames partially decoded */
|
||||
unsigned int frame_count_partial;
|
||||
/** decoded frames */
|
||||
gif_frame *frames;
|
||||
/** current frame decoded to bitmap */
|
||||
int decoded_frame;
|
||||
/** currently decoded image; stored as bitmap from bitmap_create callback */
|
||||
void *frame_image;
|
||||
/** number of times to loop animation */
|
||||
int loop_count;
|
||||
|
||||
/* Internal members are listed below */
|
||||
|
||||
/** current index into GIF data */
|
||||
unsigned int buffer_position;
|
||||
/** total number of bytes of GIF data available */
|
||||
unsigned int buffer_size;
|
||||
/** current number of frame holders */
|
||||
unsigned int frame_holders;
|
||||
/** index in the colour table for the background colour */
|
||||
unsigned int background_index;
|
||||
/** image aspect ratio (ignored) */
|
||||
unsigned int aspect_ratio;
|
||||
/** size of colour table (in entries) */
|
||||
unsigned int colour_table_size;
|
||||
/** whether the GIF has a global colour table */
|
||||
bool global_colours;
|
||||
/** global colour table */
|
||||
unsigned int *global_colour_table;
|
||||
/** local colour table */
|
||||
unsigned int *local_colour_table;
|
||||
|
||||
/** previous frame for GIF_FRAME_RESTORE */
|
||||
void *prev_frame;
|
||||
/** previous frame index */
|
||||
int prev_index;
|
||||
/** previous frame width */
|
||||
unsigned prev_width;
|
||||
/** previous frame height */
|
||||
unsigned prev_height;
|
||||
} gif_animation;
|
||||
|
||||
/**
|
||||
* Initialises necessary gif_animation members.
|
||||
*/
|
||||
void gif_create(gif_animation *gif, gif_bitmap_callback_vt *bitmap_callbacks);
|
||||
|
||||
/**
|
||||
* Initialises any workspace held by the animation and attempts to decode
|
||||
* any information that hasn't already been decoded.
|
||||
* If an error occurs, all previously decoded frames are retained.
|
||||
*
|
||||
* @return Error return value.
|
||||
* - GIF_FRAME_DATA_ERROR for GIF frame data error
|
||||
* - GIF_INSUFFICIENT_FRAME_DATA for insufficient data to process
|
||||
* any more frames
|
||||
* - GIF_INSUFFICIENT_MEMORY for memory error
|
||||
* - GIF_DATA_ERROR for GIF error
|
||||
* - GIF_INSUFFICIENT_DATA for insufficient data to do anything
|
||||
* - GIF_OK for successful decoding
|
||||
* - GIF_WORKING for successful decoding if more frames are expected
|
||||
*/
|
||||
gif_result gif_initialise(gif_animation *gif, size_t size, unsigned char *data);
|
||||
|
||||
/**
|
||||
* Decodes a GIF frame.
|
||||
*
|
||||
* @return Error return value. If a frame does not contain any image data,
|
||||
* GIF_OK is returned and gif->current_error is set to
|
||||
* GIF_FRAME_NO_DISPLAY
|
||||
* - GIF_FRAME_DATA_ERROR for GIF frame data error
|
||||
* - GIF_INSUFFICIENT_FRAME_DATA for insufficient data to complete the frame
|
||||
* - GIF_DATA_ERROR for GIF error (invalid frame header)
|
||||
* - GIF_INSUFFICIENT_DATA for insufficient data to do anything
|
||||
* - GIF_INSUFFICIENT_MEMORY for insufficient memory to process
|
||||
* - GIF_OK for successful decoding
|
||||
*/
|
||||
gif_result gif_decode_frame(gif_animation *gif, unsigned int frame);
|
||||
|
||||
/**
|
||||
* Releases any workspace held by a gif
|
||||
*/
|
||||
void gif_finalise(gif_animation *gif);
|
||||
|
||||
#endif
|
377
src/display/libnsgif/lzw.c
Normal file
377
src/display/libnsgif/lzw.c
Normal file
|
@ -0,0 +1,377 @@
|
|||
/*
|
||||
* This file is part of NetSurf's LibNSGIF, http://www.netsurf-browser.org/
|
||||
* Licensed under the MIT License,
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* Copyright 2017 Michael Drake <michael.drake@codethink.co.uk>
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "lzw.h"
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief LZW decompression (implementation)
|
||||
*
|
||||
* Decoder for GIF LZW data.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Context for reading LZW data.
|
||||
*
|
||||
* LZW data is split over multiple sub-blocks. Each sub-block has a
|
||||
* byte at the start, which says the sub-block size, and then the data.
|
||||
* Zero-size sub-blocks have no data, and the biggest sub-block size is
|
||||
* 255, which means there are 255 bytes of data following the sub-block
|
||||
* size entry.
|
||||
*
|
||||
* Note that an individual LZW code can be split over up to three sub-blocks.
|
||||
*/
|
||||
struct lzw_read_ctx {
|
||||
const uint8_t *data; /**< Pointer to start of input data */
|
||||
uint32_t data_len; /**< Input data length */
|
||||
uint32_t data_sb_next; /**< Offset to sub-block size */
|
||||
|
||||
const uint8_t *sb_data; /**< Pointer to current sub-block in data */
|
||||
uint32_t sb_bit; /**< Current bit offset in sub-block */
|
||||
uint32_t sb_bit_count; /**< Bit count in sub-block */
|
||||
};
|
||||
|
||||
/**
|
||||
* LZW dictionary entry.
|
||||
*
|
||||
* Records in the dictionary are composed of 1 or more entries.
|
||||
* Entries point to previous entries which can be followed to compose
|
||||
* the complete record. To compose the record in reverse order, take
|
||||
* the `last_value` from each entry, and move to the previous entry.
|
||||
* If the previous_entry's index is < the current clear_code, then it
|
||||
* is the last entry in the record.
|
||||
*/
|
||||
struct lzw_dictionary_entry {
|
||||
uint8_t last_value; /**< Last value for record ending at entry. */
|
||||
uint8_t first_value; /**< First value for entry's record. */
|
||||
uint16_t previous_entry; /**< Offset in dictionary to previous entry. */
|
||||
};
|
||||
|
||||
/**
|
||||
* LZW decompression context.
|
||||
*/
|
||||
struct lzw_ctx {
|
||||
/** Input reading context */
|
||||
struct lzw_read_ctx input;
|
||||
|
||||
uint32_t previous_code; /**< Code read from input previously. */
|
||||
uint32_t previous_code_first; /**< First value of previous code. */
|
||||
|
||||
uint32_t initial_code_size; /**< Starting LZW code size. */
|
||||
uint32_t current_code_size; /**< Current LZW code size. */
|
||||
uint32_t current_code_size_max; /**< Max code value for current size. */
|
||||
|
||||
uint32_t clear_code; /**< Special Clear code value */
|
||||
uint32_t eoi_code; /**< Special End of Information code value */
|
||||
|
||||
uint32_t current_entry; /**< Next position in table to fill. */
|
||||
|
||||
/** Output value stack. */
|
||||
uint8_t stack_base[1 << LZW_CODE_MAX];
|
||||
|
||||
/** LZW decode dictionary. Generated during decode. */
|
||||
struct lzw_dictionary_entry table[1 << LZW_CODE_MAX];
|
||||
};
|
||||
|
||||
|
||||
/* Exported function, documented in lzw.h */
|
||||
lzw_result lzw_context_create(struct lzw_ctx **ctx)
|
||||
{
|
||||
struct lzw_ctx *c = malloc(sizeof(*c));
|
||||
if (c == NULL) {
|
||||
return LZW_NO_MEM;
|
||||
}
|
||||
|
||||
*ctx = c;
|
||||
return LZW_OK;
|
||||
}
|
||||
|
||||
|
||||
/* Exported function, documented in lzw.h */
|
||||
void lzw_context_destroy(struct lzw_ctx *ctx)
|
||||
{
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Advance the context to the next sub-block in the input data.
|
||||
*
|
||||
* \param[in] ctx LZW reading context, updated on success.
|
||||
* \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise.
|
||||
*/
|
||||
static lzw_result lzw__block_advance(struct lzw_read_ctx *ctx)
|
||||
{
|
||||
uint32_t block_size;
|
||||
uint32_t next_block_pos = ctx->data_sb_next;
|
||||
const uint8_t *data_next = ctx->data + next_block_pos;
|
||||
|
||||
if (next_block_pos >= ctx->data_len) {
|
||||
return LZW_NO_DATA;
|
||||
}
|
||||
|
||||
block_size = *data_next;
|
||||
|
||||
if ((next_block_pos + block_size) >= ctx->data_len) {
|
||||
return LZW_NO_DATA;
|
||||
}
|
||||
|
||||
ctx->sb_bit = 0;
|
||||
ctx->sb_bit_count = block_size * 8;
|
||||
|
||||
if (block_size == 0) {
|
||||
ctx->data_sb_next += 1;
|
||||
return LZW_OK_EOD;
|
||||
}
|
||||
|
||||
ctx->sb_data = data_next + 1;
|
||||
ctx->data_sb_next += block_size + 1;
|
||||
|
||||
return LZW_OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the next LZW code of given size from the raw input data.
|
||||
*
|
||||
* Reads codes from the input data stream coping with GIF data sub-blocks.
|
||||
*
|
||||
* \param[in] ctx LZW reading context, updated.
|
||||
* \param[in] code_size Size of LZW code to get from data.
|
||||
* \param[out] code_out Returns an LZW code on success.
|
||||
* \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise.
|
||||
*/
|
||||
static inline lzw_result lzw__next_code(
|
||||
struct lzw_read_ctx *ctx,
|
||||
uint8_t code_size,
|
||||
uint32_t *code_out)
|
||||
{
|
||||
uint32_t code = 0;
|
||||
uint8_t current_bit = ctx->sb_bit & 0x7;
|
||||
uint8_t byte_advance = (current_bit + code_size) >> 3;
|
||||
|
||||
assert(byte_advance <= 2);
|
||||
|
||||
if (ctx->sb_bit + code_size <= ctx->sb_bit_count) {
|
||||
/* Fast path: code fully inside this sub-block */
|
||||
const uint8_t *data = ctx->sb_data + (ctx->sb_bit >> 3);
|
||||
switch (byte_advance) {
|
||||
case 2: code |= data[2] << 16; /* Fall through */
|
||||
case 1: code |= data[1] << 8; /* Fall through */
|
||||
case 0: code |= data[0] << 0;
|
||||
}
|
||||
ctx->sb_bit += code_size;
|
||||
} else {
|
||||
/* Slow path: code spans sub-blocks */
|
||||
uint8_t byte = 0;
|
||||
uint8_t bits_remaining_0 = (code_size < (8 - current_bit)) ?
|
||||
code_size : (8 - current_bit);
|
||||
uint8_t bits_remaining_1 = code_size - bits_remaining_0;
|
||||
uint8_t bits_used[3] = {
|
||||
[0] = bits_remaining_0,
|
||||
[1] = bits_remaining_1 < 8 ? bits_remaining_1 : 8,
|
||||
[2] = bits_remaining_1 - 8,
|
||||
};
|
||||
|
||||
while (true) {
|
||||
const uint8_t *data = ctx->sb_data;
|
||||
lzw_result res;
|
||||
|
||||
/* Get any data from end of this sub-block */
|
||||
while (byte <= byte_advance &&
|
||||
ctx->sb_bit < ctx->sb_bit_count) {
|
||||
code |= data[ctx->sb_bit >> 3] << (byte << 3);
|
||||
ctx->sb_bit += bits_used[byte];
|
||||
byte++;
|
||||
}
|
||||
|
||||
/* Check if we have all we need */
|
||||
if (byte > byte_advance) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Move to next sub-block */
|
||||
res = lzw__block_advance(ctx);
|
||||
if (res != LZW_OK) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*code_out = (code >> current_bit) & ((1 << code_size) - 1);
|
||||
return LZW_OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear LZW code dictionary.
|
||||
*
|
||||
* \param[in] ctx LZW reading context, updated.
|
||||
* \param[out] stack_pos_out Returns current stack position.
|
||||
* \return LZW_OK or error code.
|
||||
*/
|
||||
static lzw_result lzw__clear_codes(
|
||||
struct lzw_ctx *ctx,
|
||||
const uint8_t ** const stack_pos_out)
|
||||
{
|
||||
uint32_t code;
|
||||
uint8_t *stack_pos;
|
||||
|
||||
/* Reset dictionary building context */
|
||||
ctx->current_code_size = ctx->initial_code_size + 1;
|
||||
ctx->current_code_size_max = (1 << ctx->current_code_size) - 1;;
|
||||
ctx->current_entry = (1 << ctx->initial_code_size) + 2;
|
||||
|
||||
/* There might be a sequence of clear codes, so process them all */
|
||||
do {
|
||||
lzw_result res = lzw__next_code(&ctx->input,
|
||||
ctx->current_code_size, &code);
|
||||
if (res != LZW_OK) {
|
||||
return res;
|
||||
}
|
||||
} while (code == ctx->clear_code);
|
||||
|
||||
/* The initial code must be from the initial dictionary. */
|
||||
if (code > ctx->clear_code) {
|
||||
return LZW_BAD_ICODE;
|
||||
}
|
||||
|
||||
/* Record this initial code as "previous" code, needed during decode. */
|
||||
ctx->previous_code = code;
|
||||
ctx->previous_code_first = code;
|
||||
|
||||
/* Reset the stack, and add first non-clear code added as first item. */
|
||||
stack_pos = ctx->stack_base;
|
||||
*stack_pos++ = code;
|
||||
|
||||
*stack_pos_out = stack_pos;
|
||||
return LZW_OK;
|
||||
}
|
||||
|
||||
|
||||
/* Exported function, documented in lzw.h */
|
||||
lzw_result lzw_decode_init(
|
||||
struct lzw_ctx *ctx,
|
||||
const uint8_t *compressed_data,
|
||||
uint32_t compressed_data_len,
|
||||
uint32_t compressed_data_pos,
|
||||
uint8_t code_size,
|
||||
const uint8_t ** const stack_base_out,
|
||||
const uint8_t ** const stack_pos_out)
|
||||
{
|
||||
struct lzw_dictionary_entry *table = ctx->table;
|
||||
|
||||
/* Initialise the input reading context */
|
||||
ctx->input.data = compressed_data;
|
||||
ctx->input.data_len = compressed_data_len;
|
||||
ctx->input.data_sb_next = compressed_data_pos;
|
||||
|
||||
ctx->input.sb_bit = 0;
|
||||
ctx->input.sb_bit_count = 0;
|
||||
|
||||
/* Initialise the dictionary building context */
|
||||
ctx->initial_code_size = code_size;
|
||||
|
||||
ctx->clear_code = (1 << code_size) + 0;
|
||||
ctx->eoi_code = (1 << code_size) + 1;
|
||||
|
||||
/* Initialise the standard dictionary entries */
|
||||
for (uint32_t i = 0; i < ctx->clear_code; ++i) {
|
||||
table[i].first_value = i;
|
||||
table[i].last_value = i;
|
||||
}
|
||||
|
||||
*stack_base_out = ctx->stack_base;
|
||||
return lzw__clear_codes(ctx, stack_pos_out);
|
||||
}
|
||||
|
||||
|
||||
/* Exported function, documented in lzw.h */
|
||||
lzw_result lzw_decode(struct lzw_ctx *ctx,
|
||||
const uint8_t ** const stack_pos_out)
|
||||
{
|
||||
lzw_result res;
|
||||
uint32_t code_new;
|
||||
uint32_t code_out;
|
||||
uint8_t last_value;
|
||||
uint8_t *stack_pos = ctx->stack_base;
|
||||
uint32_t clear_code = ctx->clear_code;
|
||||
uint32_t current_entry = ctx->current_entry;
|
||||
struct lzw_dictionary_entry * const table = ctx->table;
|
||||
|
||||
/* Get a new code from the input */
|
||||
res = lzw__next_code(&ctx->input, ctx->current_code_size, &code_new);
|
||||
if (res != LZW_OK) {
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Handle the new code */
|
||||
if (code_new == clear_code) {
|
||||
/* Got Clear code */
|
||||
return lzw__clear_codes(ctx, stack_pos_out);
|
||||
|
||||
} else if (code_new == ctx->eoi_code) {
|
||||
/* Got End of Information code */
|
||||
return LZW_EOI_CODE;
|
||||
|
||||
} else if (code_new > current_entry) {
|
||||
/* Code is invalid */
|
||||
return LZW_BAD_CODE;
|
||||
|
||||
} else if (code_new < current_entry) {
|
||||
/* Code is in table */
|
||||
code_out = code_new;
|
||||
last_value = table[code_new].first_value;
|
||||
} else {
|
||||
/* Code not in table */
|
||||
*stack_pos++ = ctx->previous_code_first;
|
||||
code_out = ctx->previous_code;
|
||||
last_value = ctx->previous_code_first;
|
||||
}
|
||||
|
||||
/* Add to the dictionary, only if there's space */
|
||||
if (current_entry < (1 << LZW_CODE_MAX)) {
|
||||
struct lzw_dictionary_entry *entry = table + current_entry;
|
||||
entry->last_value = last_value;
|
||||
entry->first_value = ctx->previous_code_first;
|
||||
entry->previous_entry = ctx->previous_code;
|
||||
ctx->current_entry++;
|
||||
}
|
||||
|
||||
/* Ensure code size is increased, if needed. */
|
||||
if (current_entry == ctx->current_code_size_max) {
|
||||
if (ctx->current_code_size < LZW_CODE_MAX) {
|
||||
ctx->current_code_size++;
|
||||
ctx->current_code_size_max =
|
||||
(1 << ctx->current_code_size) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Store details of this code as "previous code" to the context. */
|
||||
ctx->previous_code_first = table[code_new].first_value;
|
||||
ctx->previous_code = code_new;
|
||||
|
||||
/* Put rest of data for this code on output stack.
|
||||
* Note, in the case of "code not in table", the last entry of the
|
||||
* current code has already been placed on the stack above. */
|
||||
while (code_out > clear_code) {
|
||||
struct lzw_dictionary_entry *entry = table + code_out;
|
||||
*stack_pos++ = entry->last_value;
|
||||
code_out = entry->previous_entry;
|
||||
}
|
||||
*stack_pos++ = table[code_out].last_value;
|
||||
|
||||
*stack_pos_out = stack_pos;
|
||||
return LZW_OK;
|
||||
}
|
105
src/display/libnsgif/lzw.h
Normal file
105
src/display/libnsgif/lzw.h
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* This file is part of NetSurf's LibNSGIF, http://www.netsurf-browser.org/
|
||||
* Licensed under the MIT License,
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* Copyright 2017 Michael Drake <michael.drake@codethink.co.uk>
|
||||
*/
|
||||
|
||||
#ifndef LZW_H_
|
||||
#define LZW_H_
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief LZW decompression (interface)
|
||||
*
|
||||
* Decoder for GIF LZW data.
|
||||
*/
|
||||
|
||||
|
||||
/** Maximum LZW code size in bits */
|
||||
#define LZW_CODE_MAX 12
|
||||
|
||||
|
||||
/* Declare lzw internal context structure */
|
||||
struct lzw_ctx;
|
||||
|
||||
|
||||
/** LZW decoding response codes */
|
||||
typedef enum lzw_result {
|
||||
LZW_OK, /**< Success */
|
||||
LZW_OK_EOD, /**< Success; reached zero-length sub-block */
|
||||
LZW_NO_MEM, /**< Error: Out of memory */
|
||||
LZW_NO_DATA, /**< Error: Out of data */
|
||||
LZW_EOI_CODE, /**< Error: End of Information code */
|
||||
LZW_BAD_ICODE, /**< Error: Bad initial LZW code */
|
||||
LZW_BAD_CODE, /**< Error: Bad LZW code */
|
||||
} lzw_result;
|
||||
|
||||
|
||||
/**
|
||||
* Create an LZW decompression context.
|
||||
*
|
||||
* \param[out] ctx Returns an LZW decompression context. Caller owned,
|
||||
* free with lzw_context_destroy().
|
||||
* \return LZW_OK on success, or appropriate error code otherwise.
|
||||
*/
|
||||
lzw_result lzw_context_create(
|
||||
struct lzw_ctx **ctx);
|
||||
|
||||
/**
|
||||
* Destroy an LZW decompression context.
|
||||
*
|
||||
* \param[in] ctx The LZW decompression context to destroy.
|
||||
*/
|
||||
void lzw_context_destroy(
|
||||
struct lzw_ctx *ctx);
|
||||
|
||||
/**
|
||||
* Initialise an LZW decompression context for decoding.
|
||||
*
|
||||
* Caller owns neither `stack_base_out` or `stack_pos_out`.
|
||||
*
|
||||
* \param[in] ctx The LZW decompression context to initialise.
|
||||
* \param[in] compressed_data The compressed data.
|
||||
* \param[in] compressed_data_len Byte length of compressed data.
|
||||
* \param[in] compressed_data_pos Start position in data. Must be position
|
||||
* of a size byte at sub-block start.
|
||||
* \param[in] code_size The initial LZW code size to use.
|
||||
* \param[out] stack_base_out Returns base of decompressed data stack.
|
||||
* \param[out] stack_pos_out Returns current stack position.
|
||||
* There are `stack_pos_out - stack_base_out`
|
||||
* current stack entries.
|
||||
* \return LZW_OK on success, or appropriate error code otherwise.
|
||||
*/
|
||||
lzw_result lzw_decode_init(
|
||||
struct lzw_ctx *ctx,
|
||||
const uint8_t *compressed_data,
|
||||
uint32_t compressed_data_len,
|
||||
uint32_t compressed_data_pos,
|
||||
uint8_t code_size,
|
||||
const uint8_t ** const stack_base_out,
|
||||
const uint8_t ** const stack_pos_out);
|
||||
|
||||
/**
|
||||
* Fill the LZW stack with decompressed data
|
||||
*
|
||||
* Ensure anything on the stack is used before calling this, as anything
|
||||
* on the stack before this call will be trampled.
|
||||
*
|
||||
* Caller does not own `stack_pos_out`.
|
||||
*
|
||||
* \param[in] ctx LZW reading context, updated.
|
||||
* \param[out] stack_pos_out Returns current stack position.
|
||||
* Use with `stack_base_out` value from previous
|
||||
* lzw_decode_init() call.
|
||||
* There are `stack_pos_out - stack_base_out`
|
||||
* current stack entries.
|
||||
* \return LZW_OK on success, or appropriate error code otherwise.
|
||||
*/
|
||||
lzw_result lzw_decode(
|
||||
struct lzw_ctx *ctx,
|
||||
const uint8_t ** const stack_pos_out);
|
||||
|
||||
|
||||
#endif
|
21
src/display/libnsgif/utils/log.h
Normal file
21
src/display/libnsgif/utils/log.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2003 James Bursa <bursa@users.sourceforge.net>
|
||||
* Copyright 2004 John Tytgat <John.Tytgat@aaug.net>
|
||||
*
|
||||
* This file is part of NetSurf, http://www.netsurf-browser.org/
|
||||
* Licenced under the MIT License,
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef _LIBNSGIF_LOG_H_
|
||||
#define _LIBNSGIF_LOG_H_
|
||||
|
||||
#ifdef NDEBUG
|
||||
# define LOG(x) ((void) 0)
|
||||
#else
|
||||
# define LOG(x) do { fprintf(stderr, x), fputc('\n', stderr); } while (0)
|
||||
#endif /* NDEBUG */
|
||||
|
||||
#endif /* _LIBNSGIF_LOG_H_ */
|
|
@ -575,7 +575,7 @@ void Sprite::draw()
|
|||
|
||||
p->bitmap->bindTex(*base);
|
||||
|
||||
if (p->wave.active)
|
||||
if (p->wave.active)
|
||||
p->wave.qArray.draw();
|
||||
else
|
||||
p->quad.draw();
|
||||
|
|
|
@ -521,7 +521,8 @@ struct TilemapPrivate
|
|||
/* Assembles atlas from tileset and autotile bitmaps */
|
||||
void buildAtlas()
|
||||
{
|
||||
updateAutotileInfo();
|
||||
updateAutotileInfo();
|
||||
tileset->ensureNonAnimated();
|
||||
|
||||
TileAtlas::BlitVec blits = TileAtlas::calcBlits(atlas.efTilesetH, atlas.size);
|
||||
|
||||
|
@ -542,6 +543,7 @@ struct TilemapPrivate
|
|||
{
|
||||
const uint8_t atInd = atlas.usableATs[i];
|
||||
Bitmap *autotile = autotiles[atInd];
|
||||
autotile->ensureNonAnimated();
|
||||
|
||||
int atW = autotile->width();
|
||||
int atH = autotile->height();
|
||||
|
|
|
@ -68,7 +68,7 @@ global_args += '-DMKXPZ_ALCDEVICE=' + alcdev_struct
|
|||
global_include_dirs += include_directories('.',
|
||||
'audio',
|
||||
'crypto',
|
||||
'display', 'display/gl',
|
||||
'display', 'display/gl', 'display/libnsgif', 'display/libnsgif/utils',
|
||||
'etc',
|
||||
'filesystem', 'filesystem/ghc',
|
||||
'input',
|
||||
|
@ -130,6 +130,9 @@ main_source = files(
|
|||
'display/window.cpp',
|
||||
'display/windowvx.cpp',
|
||||
|
||||
'display/libnsgif/libnsgif.c',
|
||||
'display/libnsgif/lzw.c',
|
||||
|
||||
'display/gl/gl-debug.cpp',
|
||||
'display/gl/gl-fun.cpp',
|
||||
'display/gl/gl-meta.cpp',
|
||||
|
|
Loading…
Add table
Reference in a new issue