Support animated GIFs

This commit is contained in:
Struma 2021-05-01 21:50:39 -04:00 committed by Roza
parent 0ee00d1137
commit f98b91ff9a
12 changed files with 2514 additions and 16 deletions

View file

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

View file

@ -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 */,

View file

@ -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 &currentFrame() {
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);

View file

@ -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 */

File diff suppressed because it is too large Load diff

View 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
View 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
View 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

View 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_ */

View file

@ -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();

View file

@ -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();

View file

@ -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',