forked from KolibriOS/kolibrios
470 lines
18 KiB
C
470 lines
18 KiB
C
|
/*
|
||
|
SDL_flic - renders FLIC animations
|
||
|
Copyright (C) 2003 Andre de Leiradella
|
||
|
|
||
|
This library is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU Lesser General Public
|
||
|
License as published by the Free Software Foundation; either
|
||
|
version 2.1 of the License, or (at your option) any later version.
|
||
|
|
||
|
This library is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
Lesser General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU Lesser General Public
|
||
|
License along with this library; if not, write to the Free Software
|
||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
|
|
||
|
For information about SDL_flic contact leiradella@bigfoot.com
|
||
|
|
||
|
Version 1.0: first public release.
|
||
|
Version 1.1: fixed bug to set *error to FLI_OK when returning successfully from FLI_Open
|
||
|
added function FLI_Reset to reset the animation to the first frame
|
||
|
Version 1.2: added function FLI_Skip to skip the current frame without rendering
|
||
|
FLI_Animation->surface is now correctly locked and unlocked
|
||
|
the rwops stream is now part of the FLI_Animation structure and is closed inside FLI_Close
|
||
|
renamed FLI_Reset to FLI_Rewind
|
||
|
added function FLI_Version that returns the library version
|
||
|
*/
|
||
|
#include <SDL_flic.h>
|
||
|
#include <setjmp.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <mem.h>
|
||
|
|
||
|
/* Library version. */
|
||
|
#define FLI_MAJOR 1
|
||
|
#define FLI_MINOR 2
|
||
|
|
||
|
/* Chunk types. */
|
||
|
#define FLI_COLOR256 4
|
||
|
#define FLI_SS2 7
|
||
|
#define FLI_COLOR 11
|
||
|
#define FLI_LC 12
|
||
|
#define FLI_BLACK 13
|
||
|
#define FLI_BRUN 15
|
||
|
#define FLI_COPY 16
|
||
|
#define FLI_PSTAMP 18
|
||
|
|
||
|
typedef struct {
|
||
|
Uint32 size, type, numchunks;
|
||
|
} FLI_Frame;
|
||
|
|
||
|
typedef struct {
|
||
|
Uint32 size, type, index;
|
||
|
} FLI_Chunk;
|
||
|
|
||
|
static INLINE void readbuffer(FLI_Animation *flic, void *buffer, int size) {
|
||
|
if (SDL_RWread(flic->rwops, buffer, 1, size) != size)
|
||
|
longjmp(flic->error, FLI_READERROR);
|
||
|
}
|
||
|
|
||
|
static INLINE Uint8 readu8(FLI_Animation *flic) {
|
||
|
Uint8 b;
|
||
|
|
||
|
readbuffer(flic, &b, 1);
|
||
|
return b;
|
||
|
}
|
||
|
|
||
|
static INLINE Uint16 readu16(FLI_Animation *flic) {
|
||
|
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
||
|
Uint16 hi, lo;
|
||
|
|
||
|
readbuffer(flic, &lo, 1);
|
||
|
readbuffer(flic, &hi, 1);
|
||
|
return hi << 8 | lo;
|
||
|
#else
|
||
|
Uint16 w;
|
||
|
|
||
|
readbuffer(flic, &w, 2);
|
||
|
return w;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static INLINE Uint32 readu32(FLI_Animation *flic) {
|
||
|
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
||
|
Uint32 hi;
|
||
|
|
||
|
hi = readu16(flic);
|
||
|
return hi << 16 | readu16(flic);
|
||
|
#else
|
||
|
Uint32 u;
|
||
|
|
||
|
readbuffer(flic, &u, 4);
|
||
|
return u;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static void readheader(FLI_Animation *flic) {
|
||
|
/* Skip size, we don't need it. */
|
||
|
SDL_RWseek(flic->rwops, 4, SEEK_CUR);
|
||
|
/* Read and check magic. */
|
||
|
flic->format = readu16(flic);
|
||
|
if (flic->format != FLI_FLI && flic->format != FLI_FLC)
|
||
|
longjmp(flic->error, FLI_CORRUPTEDFILE);
|
||
|
/* Read number of frames, maximum is 4000 for FLI and FLC files. */
|
||
|
flic->numframes = readu16(flic);
|
||
|
if (flic->numframes > 4000)
|
||
|
longjmp(flic->error, FLI_CORRUPTEDFILE);
|
||
|
/* Read width and height, must be 320x200 for FLI files. */
|
||
|
flic->width = readu16(flic);
|
||
|
flic->height = readu16(flic);
|
||
|
if (flic->format == FLI_FLI && (flic->width != 320 || flic->height != 200))
|
||
|
longjmp(flic->error, FLI_CORRUPTEDFILE);
|
||
|
/* Read color depth, must be 8 for FLI and FLC files. */
|
||
|
flic->depth = readu16(flic);
|
||
|
if (flic->depth != 8)
|
||
|
longjmp(flic->error, FLI_CORRUPTEDFILE);
|
||
|
/* Skip the flags, it doesn't look like it follows the specs. */
|
||
|
readu16(flic);
|
||
|
/* Read the delay between frames. */
|
||
|
flic->delay = (flic->format == FLI_FLI) ? readu16(flic) : readu32(flic);
|
||
|
/* Skip rest of the header. */
|
||
|
SDL_RWseek(flic->rwops, (flic->format == FLI_FLI) ? 110 : 108, SEEK_CUR);
|
||
|
}
|
||
|
|
||
|
static INLINE void readframe(FLI_Animation *flic, FLI_Frame *frame) {
|
||
|
/* Read the size of the frame, must be less than or equal to 64k in FLI files. */
|
||
|
frame->size = readu32(flic);
|
||
|
if (flic->format == FLI_FLI && frame->size > 65536)
|
||
|
longjmp(flic->error, FLI_CORRUPTEDFILE);
|
||
|
/* Read the type of the frame, must be 0xF1FA in FLI files or 0xF1FA or 0xF100 in FLC files. */
|
||
|
frame->type = readu16(flic);
|
||
|
if (frame->type != 0xF1FA && (flic->format == FLI_FLC && frame->type != 0xF100))
|
||
|
longjmp(flic->error, FLI_CORRUPTEDFILE);
|
||
|
/* Read the number of chunks in this frame. */
|
||
|
frame->numchunks = readu16(flic);
|
||
|
/* Skip rest of the data. */
|
||
|
SDL_RWseek(flic->rwops, 8, SEEK_CUR);
|
||
|
}
|
||
|
|
||
|
static INLINE void readchunk(FLI_Animation *flic, FLI_Chunk *chunk) {
|
||
|
/* Read the chunk size. */
|
||
|
chunk->size = readu32(flic);
|
||
|
/* Read the chunk type. */
|
||
|
chunk->type = readu16(flic);
|
||
|
}
|
||
|
|
||
|
static void handlecolor(FLI_Animation *flic, FLI_Chunk *chunk) {
|
||
|
int numpackets, index, count;
|
||
|
SDL_Color color;
|
||
|
|
||
|
(void)chunk;
|
||
|
/* Number of packets. */
|
||
|
numpackets = readu16(flic);
|
||
|
/* Color index that will be changed. */
|
||
|
index = 0;
|
||
|
while (numpackets-- > 0) {
|
||
|
/* Skip some colors. */
|
||
|
index += readu8(flic);
|
||
|
/* And change some others. */
|
||
|
count = readu8(flic);
|
||
|
if (count == 0)
|
||
|
count = 256;
|
||
|
while (count-- > 0) {
|
||
|
/* r, g and b are in the range [0..63]. */
|
||
|
color.r = ((Uint32)readu8(flic)) * 255 / 63;
|
||
|
color.g = ((Uint32)readu8(flic)) * 255 / 63;
|
||
|
color.b = ((Uint32)readu8(flic)) * 255 / 63;
|
||
|
SDL_SetColors(flic->surface, &color, index++, 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void handlelc(FLI_Animation *flic, FLI_Chunk *chunk) {
|
||
|
int numlines, numpackets, size;
|
||
|
Uint8 *line, *p;
|
||
|
|
||
|
(void)chunk;
|
||
|
/* Skip lines at the top of the image. */
|
||
|
line = (Uint8 *)flic->surface->pixels + readu16(flic) * flic->surface->pitch;
|
||
|
/* numlines lines will change. */
|
||
|
numlines = readu16(flic);
|
||
|
while (numlines-- > 0) {
|
||
|
p = line;
|
||
|
line += flic->surface->pitch;
|
||
|
/* Each line has numpackets changes. */
|
||
|
numpackets = readu8(flic);
|
||
|
while (numpackets-- > 0) {
|
||
|
/* Skip pixels at the beginning of the line. */
|
||
|
p += readu8(flic);
|
||
|
/* size pixels will change. */
|
||
|
size = (Sint8)readu8(flic);
|
||
|
if (size >= 0) {
|
||
|
/* Pixels follow. */
|
||
|
readbuffer(flic, (void *)p, size);
|
||
|
} else {
|
||
|
size = -size;
|
||
|
/* One pixel to be repeated follow. */
|
||
|
memset((void *)p, readu8(flic), size);
|
||
|
}
|
||
|
p += size;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void handleblack(FLI_Animation *flic, FLI_Chunk *chunk) {
|
||
|
(void)chunk;
|
||
|
/* Fill the surface with color 0. */
|
||
|
if (SDL_FillRect(flic->surface, NULL, 0) != 0)
|
||
|
longjmp(flic->error, FLI_SDLERROR);
|
||
|
}
|
||
|
|
||
|
static void handlebrun(FLI_Animation *flic, FLI_Chunk *chunk) {
|
||
|
int numlines, size;
|
||
|
Uint8 *p, *next;
|
||
|
|
||
|
(void)chunk;
|
||
|
/* Begin at the top of the image. */
|
||
|
p = (Uint8 *)flic->surface->pixels;
|
||
|
/* All lines will change. */
|
||
|
numlines = flic->height;
|
||
|
while (numlines-- > 0) {
|
||
|
/* The number of packages is ignored, packets run until the next line is reached. */
|
||
|
readu8(flic);
|
||
|
next = p + flic->surface->pitch;
|
||
|
while (p < next) {
|
||
|
/* size pixels will change. */
|
||
|
size = (Sint8)readu8(flic);
|
||
|
if (size < 0) {
|
||
|
size = -size;
|
||
|
/* Pixels follow. */
|
||
|
readbuffer(flic, (void *)p, size);
|
||
|
} else {
|
||
|
/* One pixel to be repeated follow. */
|
||
|
memset((void *)p, readu8(flic), size);
|
||
|
}
|
||
|
p += size;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void handlecopy(FLI_Animation *flic, FLI_Chunk *chunk) {
|
||
|
(void)chunk;
|
||
|
/* Read the entire image from the stream. */
|
||
|
readbuffer(flic, (void *)flic->surface->pixels, flic->width * flic->height);
|
||
|
}
|
||
|
|
||
|
static void handlecolor256(FLI_Animation *flic, FLI_Chunk *chunk) {
|
||
|
int numpackets, index, count;
|
||
|
SDL_Color color;
|
||
|
|
||
|
(void)chunk;
|
||
|
if (flic->format == FLI_FLI)
|
||
|
longjmp(flic->error, FLI_CORRUPTEDFILE);
|
||
|
/* Number of packets. */
|
||
|
numpackets = readu16(flic);
|
||
|
/* Color index that will be changed. */
|
||
|
index = 0;
|
||
|
while (numpackets-- > 0) {
|
||
|
/* Skip some colors. */
|
||
|
index += readu8(flic);
|
||
|
/* And change some others. */
|
||
|
count = readu8(flic);
|
||
|
if (count == 0)
|
||
|
count = 256;
|
||
|
while (count-- > 0) {
|
||
|
/* r, g and b are in the range [0..255]. */
|
||
|
color.r = readu8(flic);
|
||
|
color.g = readu8(flic);
|
||
|
color.b = readu8(flic);
|
||
|
SDL_SetColors(flic->surface, &color, index++, 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void handless2(FLI_Animation *flic, FLI_Chunk *chunk) {
|
||
|
int numlines, y, code, size;
|
||
|
Uint8 *p, c;
|
||
|
|
||
|
(void)chunk;
|
||
|
if (flic->format == FLI_FLI)
|
||
|
longjmp(flic->error, FLI_CORRUPTEDFILE);
|
||
|
/* numlines lines will change. */
|
||
|
numlines = readu16(flic);
|
||
|
y = 0;
|
||
|
while (numlines > 0) {
|
||
|
/* Read the code. */
|
||
|
code = readu16(flic);
|
||
|
switch ((code >> 14) & 0x03) {
|
||
|
case 0x00:
|
||
|
p = (Uint8 *)flic->surface->pixels + flic->surface->pitch * y;
|
||
|
while (code-- > 0) {
|
||
|
/* Skip some pixels. */
|
||
|
p += readu8(flic);
|
||
|
size = ((Sint8)readu8(flic)) * 2;
|
||
|
if (size >= 0) {
|
||
|
/* Pixels follow. */
|
||
|
readbuffer(flic, (void *)p, size);
|
||
|
} else {
|
||
|
size = -size;
|
||
|
readu8(flic);
|
||
|
/* One pixel to be repeated follow. */
|
||
|
memset((void *)p, readu8(flic), size);
|
||
|
}
|
||
|
p += size;
|
||
|
}
|
||
|
y++;
|
||
|
numlines--;
|
||
|
break;
|
||
|
case 0x01:
|
||
|
longjmp(flic->error, FLI_CORRUPTEDFILE);
|
||
|
case 0x02:
|
||
|
/* Last pixel of the line. */
|
||
|
p = (Uint8 *)flic->surface->pixels + flic->surface->pitch * (y + 1);
|
||
|
p[-1] = code & 0xFF;
|
||
|
break;
|
||
|
case 0x03:
|
||
|
/* Skip some lines. */
|
||
|
y += (code ^ 0xFFFF) + 1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int FLI_Version(void) {
|
||
|
return FLI_MAJOR << 16 | FLI_MINOR;
|
||
|
}
|
||
|
|
||
|
FLI_Animation *FLI_Open(SDL_RWops *rwops, int *error) {
|
||
|
FLI_Animation *flic;
|
||
|
FLI_Frame frame;
|
||
|
int err;
|
||
|
|
||
|
/* Alloc animation. */
|
||
|
flic = (FLI_Animation *)malloc(sizeof(FLI_Animation));
|
||
|
if (flic == NULL) {
|
||
|
if (error != NULL) *error = FLI_OUTOFMEMORY;
|
||
|
return NULL;
|
||
|
}
|
||
|
flic->rwops = rwops;
|
||
|
flic->surface = NULL;
|
||
|
/* Error handling. */
|
||
|
err = setjmp(flic->error);
|
||
|
if (err != 0) {
|
||
|
if (error != NULL) *error = err;
|
||
|
FLI_Close(flic);
|
||
|
return NULL;
|
||
|
}
|
||
|
/* Read the header. */
|
||
|
readheader(flic);
|
||
|
/* Create a buffer to hold the rendered frame. */
|
||
|
flic->surface = SDL_CreateRGBSurface(SDL_SWSURFACE, flic->width, flic->height, 8, 0, 0, 0, 0);
|
||
|
if (flic->surface == NULL)
|
||
|
longjmp(flic->error, FLI_SDLERROR);
|
||
|
/* Read the first frame. */
|
||
|
flic->offframe1 = SDL_RWtell(rwops);
|
||
|
readframe(flic, &frame);
|
||
|
/* If it's a prefix frame, skip it. */
|
||
|
if (frame.type == 0xF100) {
|
||
|
SDL_RWseek(rwops, frame.size - 16, SEEK_CUR);
|
||
|
flic->offframe1 = SDL_RWtell(rwops);
|
||
|
flic->numframes--;
|
||
|
}
|
||
|
flic->offnextframe = flic->offframe1;
|
||
|
flic->nextframe = 1;
|
||
|
if (error != NULL) *error = FLI_OK;
|
||
|
return flic;
|
||
|
}
|
||
|
|
||
|
void FLI_Close(FLI_Animation *flic) {
|
||
|
if (flic != NULL) {
|
||
|
if (flic->rwops != NULL)
|
||
|
SDL_RWclose(flic->rwops);
|
||
|
if (flic->surface != NULL)
|
||
|
SDL_FreeSurface(flic->surface);
|
||
|
free(flic);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int FLI_NextFrame(FLI_Animation *flic) {
|
||
|
FLI_Frame frame;
|
||
|
FLI_Chunk chunk;
|
||
|
int error, locked;
|
||
|
Uint32 i;
|
||
|
|
||
|
/* Flag to tell if the surface is locked. */
|
||
|
locked = 0;
|
||
|
/* Error handling. */
|
||
|
error = setjmp(flic->error);
|
||
|
if (error != 0) {
|
||
|
if (locked)
|
||
|
SDL_UnlockSurface(flic->surface);
|
||
|
return error;
|
||
|
}
|
||
|
/* Seek to the current frame. */
|
||
|
SDL_RWseek(flic->rwops, flic->offnextframe, SEEK_SET);
|
||
|
/* Read the current frame. */
|
||
|
readframe(flic, &frame);
|
||
|
/* Read and process each of the chunks of this frame. */
|
||
|
SDL_LockSurface(flic->surface);
|
||
|
locked = 1;
|
||
|
(void)locked;
|
||
|
for (i = frame.numchunks; i != 0; i--) {
|
||
|
readchunk(flic, &chunk);
|
||
|
switch (chunk.type) {
|
||
|
case FLI_COLOR:
|
||
|
handlecolor(flic, &chunk);
|
||
|
break;
|
||
|
case FLI_LC:
|
||
|
handlelc(flic, &chunk);
|
||
|
break;
|
||
|
case FLI_BLACK:
|
||
|
handleblack(flic, &chunk);
|
||
|
break;
|
||
|
case FLI_BRUN:
|
||
|
handlebrun(flic, &chunk);
|
||
|
break;
|
||
|
case FLI_COPY:
|
||
|
handlecopy(flic, &chunk);
|
||
|
break;
|
||
|
case FLI_COLOR256:
|
||
|
handlecolor256(flic, &chunk);
|
||
|
break;
|
||
|
case FLI_SS2:
|
||
|
handless2(flic, &chunk);
|
||
|
break;
|
||
|
case FLI_PSTAMP:
|
||
|
/* Ignore this chunk. */
|
||
|
break;
|
||
|
default:
|
||
|
longjmp(flic->error, FLI_CORRUPTEDFILE);
|
||
|
}
|
||
|
}
|
||
|
SDL_UnlockSurface(flic->surface);
|
||
|
/* Setup the number and position of next frame. If it wraps, go to the first one. */
|
||
|
if (++flic->nextframe > flic->numframes) {
|
||
|
flic->offnextframe = flic->offframe1;
|
||
|
flic->nextframe = 1;
|
||
|
} else
|
||
|
flic->offnextframe += frame.size;
|
||
|
return FLI_OK;
|
||
|
}
|
||
|
|
||
|
int FLI_Rewind(FLI_Animation *flic) {
|
||
|
flic->offnextframe = flic->offframe1;
|
||
|
flic->nextframe = 1;
|
||
|
return FLI_OK;
|
||
|
}
|
||
|
|
||
|
int FLI_Skip(FLI_Animation *flic) {
|
||
|
FLI_Frame frame;
|
||
|
int error;
|
||
|
|
||
|
/* Error handling. */
|
||
|
error = setjmp(flic->error);
|
||
|
if (error != 0)
|
||
|
return error;
|
||
|
/* Seek to the current frame. */
|
||
|
SDL_RWseek(flic->rwops, flic->offnextframe, SEEK_SET);
|
||
|
/* Read the current frame. */
|
||
|
readframe(flic, &frame);
|
||
|
/* Skip to the next frame without rendering. */
|
||
|
if (++flic->nextframe > flic->numframes) {
|
||
|
flic->offnextframe = flic->offframe1;
|
||
|
flic->nextframe = 1;
|
||
|
} else
|
||
|
flic->offnextframe += frame.size;
|
||
|
return FLI_OK;
|
||
|
}
|