I decided to implement animated graphics playback as a video provider.
It makes the most sense. My video provider is attached. I tried to
reuse as much code from the GIF image provider as possible. It is still
lacking some error checking. And it won't work with optimized GIF
animations. ie. those with only partial graphics on a frame. I'll
release my flash video provider once I get it working with all flash 5
animations. The current one wasn't a very complete implementation.
/*
(c) Copyright 2000-2002 convergence integrated media GmbH.
(c) Copyright 2002 convergence GmbH.
All rights reserved.
Written by Denis Oliver Kropp <[EMAIL PROTECTED]>,
Andreas Hundt <[EMAIL PROTECTED]> and
Sven Neumann <[EMAIL PROTECTED]>.
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 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.
*/
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <pthread.h>
#include <directfb.h>
#include <directfb_internals.h>
#include <media/idirectfbvideoprovider.h>
#include <media/idirectfbdatabuffer.h>
#include <core/coredefs.h>
#include <core/coretypes.h>
#include <core/layers.h>
#include <core/surfaces.h>
#include <display/idirectfbsurface.h>
#include <misc/gfx_util.h>
#include <misc/util.h>
#include <misc/mem.h>
#include <misc/memcpy.h>
#define MAX_COLORMAP_SIZE 256
#define CM_RED 0
#define CM_GREEN 1
#define CM_BLUE 2
#define MAX_LWZ_BITS 12
#define SOURCE "DirectFB/VideoProvider_GIF"
#define LM_to_uint(a,b) (((b)<<8)|(a))
static DFBResult
Probe(IDirectFBVideoProvider_ProbeContext *ctx);
static DFBResult
Construct(IDirectFBVideoProvider *thiz,
const char *filename);
#include <interface_implementation.h>
DFB_INTERFACE_IMPLEMENTATION(IDirectFBVideoProvider, GIF)
typedef struct
{
int ref; /* reference counter */
pthread_t thread;
int width;
int height;
int bpp;
int depth;
int bg;
int aspect_ratio;
__u8 colormap[3][MAX_COLORMAP_SIZE];
IDirectFBSurface *destination;
DFBRectangle dest_rect;
DVFrameCallback callback;
void *ctx;
int greyscale;
int transparent;
int delay;
int inputflag;
int disposal;
int set_code_size;
int code_size;
int clear_code;
int end_code;
int max_code_size;
int max_code;
int curbit;
int lastbit;
int done;
int last_byte;
int fresh;
int firstcode;
int oldcode;
__u8 buf[280];
IDirectFBDataBuffer *buffer;
CoreSurface *source;
int table[2][(1<< MAX_LWZ_BITS)];
int stack[(1<<(MAX_LWZ_BITS))*2], *sp;
} IDirectFBVideoProvider_GIF_data;
static int
__lzw_read(IDirectFBVideoProvider_GIF_data *data, int flag, int input_code_size);
static __u32
__find_colorkey(int colors, __u8 colormap[3][MAX_COLORMAP_SIZE]);
static int
__read_block(IDirectFBDataBuffer *buffer, __u8 *buf);
static int
__read_colormap(IDirectFBDataBuffer *buffer, int number, __u8 buf[3][MAX_COLORMAP_SIZE]);
static int
__read(IDirectFBDataBuffer *buffer, void *data, unsigned int length);
static void
debug(char *src, char *format, ...)
{
va_list args;
if (src && *src)
fprintf(stdout, "(%s) ", src);
va_start(args, format);
vfprintf(stdout, format, args);
va_end(args);
fflush(stdout);
}
static int
GetCode(IDirectFBVideoProvider_GIF_data *data, int code_size, int flag)
{
int i, j, ret;
unsigned char count;
if (flag)
{
data->curbit = 0;
data->lastbit = 0;
data->done = false;
return 0;
}
if ((data->curbit+code_size) >= data->lastbit)
{
if (data->done) {
if (data->curbit >= data->lastbit) {
debug(SOURCE, "ran off end of bits\n");
}
return -1;
}
data->buf[0] = data->buf[data->last_byte-2];
data->buf[1] = data->buf[data->last_byte-1];
if ((count = __read_block( data->buffer, &data->buf[2] )) == 0) {
data->done = true;
}
data->last_byte = 2 + count;
data->curbit = (data->curbit - data->lastbit) + 16;
data->lastbit = (2+count) * 8;
}
ret = 0;
for (i = data->curbit, j = 0; j < code_size; ++i, ++j) {
ret |= ((data->buf[ i / 8 ] & (1 << (i % 8))) != 0) << j;
}
data->curbit += code_size;
return ret;
}
static int
__lzw_read(IDirectFBVideoProvider_GIF_data *data, int flag, int input_code_size)
{
int code, incode;
int i;
if (flag) {
data->set_code_size = input_code_size;
data->code_size = data->set_code_size+1;
data->clear_code = 1 << data->set_code_size ;
data->end_code = data->clear_code + 1;
data->max_code_size = 2*data->clear_code;
data->max_code = data->clear_code+2;
GetCode(data, 0, true);
data->fresh = true;
for (i = 0; i < data->clear_code; ++i) {
data->table[0][i] = 0;
data->table[1][i] = i;
}
for (; i < (1<<MAX_LWZ_BITS); ++i) {
data->table[0][i] = data->table[1][0] = 0;
}
data->sp = data->stack;
return 0;
}
else if (data->fresh) {
data->fresh = false;
do {
data->firstcode = data->oldcode = GetCode( data, data->code_size, false );
} while (data->firstcode == data->clear_code);
return data->firstcode;
}
if (data->sp > data->stack) {
return *--data->sp;
}
while ((code = GetCode( data, data->code_size, false )) >= 0) {
if (code == data->clear_code) {
for (i = 0; i < data->clear_code; ++i) {
data->table[0][i] = 0;
data->table[1][i] = i;
}
for (; i < (1<<MAX_LWZ_BITS); ++i) {
data->table[0][i] = data->table[1][i] = 0;
}
data->code_size = data->set_code_size+1;
data->max_code_size = 2*data->clear_code;
data->max_code = data->clear_code+2;
data->sp = data->stack;
data->firstcode = data->oldcode = GetCode( data, data->code_size, false );
return data->firstcode;
}
else if (code == data->end_code) {
int count;
__u8 buf[260];
while ((count = __read_block( data->buffer, buf )) > 0)
;
if (count != 0)
printf("missing EOD in data stream "
"(common occurence)\n");
return -2;
}
incode = code;
if (code >= data->max_code) {
*data->sp++ = data->firstcode;
code = data->oldcode;
}
while (code >= data->clear_code) {
*data->sp++ = data->table[1][code];
if (code == data->table[0][code]) {
printf("circular table entry BIG ERROR\n");
}
code = data->table[0][code];
}
*data->sp++ = data->firstcode = data->table[1][code];
if ((code = data->max_code) <(1<<MAX_LWZ_BITS)) {
data->table[0][code] = data->oldcode;
data->table[1][code] = data->firstcode;
++data->max_code;
if ((data->max_code >= data->max_code_size)
&& (data->max_code_size < (1<<MAX_LWZ_BITS)))
{
data->max_code_size *= 2;
++data->code_size;
}
}
data->oldcode = incode;
if (data->sp > data->stack) {
return *--data->sp;
}
}
return code;
}
static __u32 *
__read_image(IDirectFBVideoProvider_GIF_data *data, int width, int height,
__u8 cmap[3][MAX_COLORMAP_SIZE], __u32 key_rgb, int interlace)
{
__u8 c;
int v;
int xpos = 0;
int ypos = 0;
int pass = 0;
__u32 *image = 0;
__read(data->buffer, &c, 1);
if (__lzw_read(data, 1, c) < 0)
{
debug(SOURCE, "error reading image\n");
return 0;
}
if (!(image = DFBMALLOC(width * height * 4)))
{
debug(SOURCE, "cannot allocate image\n");
return 0;
}
//debug(SOURCE, "reading %dx%d GIF image\n",
//width, height);
while ((v = __lzw_read(data, 0, c)) >= 0)
{
__u32 *dst = image + (ypos * width + xpos);
if (v == data->transparent)
*dst++ = key_rgb;
else
{
*dst++ =
(0xff000000 |
cmap[CM_RED][v] << 16 |
cmap[CM_GREEN][v] << 8 |
cmap[CM_BLUE][v]);
}
++xpos;
if (xpos == width)
{
xpos = 0;
if (interlace)
{
switch (pass)
{
case 0:
case 1:
xpos +=8;
break;
case 2:
ypos += 4;
break;
case 3:
ypos +=2;
break;
}
if (ypos >= height)
{
++pass;
switch (pass)
{
case 1:
ypos = 4;
break;
case 2:
ypos = 2;
break;
case 3:
ypos = 1;
break;
default:
goto read_image_done;
}
}
}
else
{
++ypos;
}
}
if (ypos >= height)
break;
}
read_image_done:
if (__lzw_read(data, 0, c) >= 0)
{
debug(SOURCE, "too much input data. ignoring extra\n");
}
return image;
}
static int
__sort_colors(const void *a, const void *b)
{
return (*((const __u8 *) a) - *((const __u8 *) b));
}
static __u32
__find_colorkey( int n_colors, __u8 cmap[3][MAX_COLORMAP_SIZE] )
{
__u32 color = 0xFF000000;
__u8 csort[MAX_COLORMAP_SIZE];
int i, j, index, d;
if (n_colors < 1)
return color;
DFB_ASSERT( n_colors <= MAX_COLORMAP_SIZE );
for (i = 0; i < 3; i++) {
dfb_memcpy( csort, cmap[i], n_colors );
qsort( csort, n_colors, 1, __sort_colors );
for (j = 1, index = 0, d = 0; j < n_colors; j++) {
if (csort[j] - csort[j-1] > d) {
d = csort[j] - csort[j-1];
index = j;
}
}
if ((csort[0] - 0x0) > d) {
d = csort[0] - 0x0;
index = n_colors;
}
if (0xFF - (csort[n_colors - 1]) > d) {
index = n_colors + 1;
}
if (index < n_colors)
csort[0] = csort[index] - (d/2);
else if (index == n_colors)
csort[0] = 0x0;
else
csort[0] = 0xFF;
color |= (csort[0] << (8 * (2 - i)));
}
return color;
}
static int
__read_block(IDirectFBDataBuffer *buffer, __u8 *buf)
{
unsigned char count;
if (!(__read(buffer, &count, 1)))
{
debug(SOURCE, "error getting block size\n");
return -1;
}
if ((count != 0) && (!(__read(buffer, buf, count))))
{
debug(SOURCE, "error reading data block\n");
return -1;
}
return count;
}
static int
__read_colormap(IDirectFBDataBuffer *buffer, int number, __u8 buf[3][MAX_COLORMAP_SIZE])
{
int i;
__u8 rgb[3];
for (i=0; i<number; ++i)
{
if (!(__read(buffer, rgb, sizeof(rgb))))
{
debug(SOURCE, "bad colormap\n");
return 1;
}
buf[CM_RED][i] = rgb[0];
buf[CM_GREEN][i] = rgb[1];
buf[CM_BLUE][i] = rgb[2];
}
return 0;
}
static int
__read(IDirectFBDataBuffer *buffer, void *data, unsigned int length)
{
DFBResult ret;
if ((ret = buffer->WaitForData(buffer, length)))
return 0;
if ((ret = buffer->GetData(buffer, length, data, 0)))
return 0;
return 1;
}
static int
__read_gif_header(IDirectFBVideoProvider_GIF_data *data,
int *width, int *height, int *transparency)
{
__u8 buf[16];
if (data->buffer->SeekTo(data->buffer, 0))
{
debug(SOURCE, "unable to seek\n");
return -1;
}
if (!(__read(data->buffer, buf, 6)))
{
debug(SOURCE, "error reading magic number\n");
return -1;
}
if (strncmp((char *)buf, "GIF", 3))
{
debug(SOURCE, "not a GIF file\n");
return -1;
}
if (strncmp((char *)buf+3, "87a", 3) &&
strncmp((char *)buf+3, "89a", 3))
{
debug(SOURCE, "invalid GIF version\n");
return -1;
}
if (!(__read(data->buffer, buf, 7)))
{
debug(SOURCE, "failed to read screen descriptor\n");
return -1;
}
*width = LM_to_uint(buf[0], buf[1]);
*height = LM_to_uint(buf[2], buf[3]);
return 0;
}
static void *
FrameThread(void *ctx)
{
DFBResult ret;
__u8 buf[256], c;
int image_start = 0;
int colorkey, transparent, bpp, alpha;
__u32 key_rgb = 0;
__u8 local_colormap[3][MAX_COLORMAP_SIZE];
__u32 *image_data;
int g_colormap;
IDirectFBVideoProvider_GIF_data *data = (IDirectFBVideoProvider_GIF_data *)ctx;
ret = data->buffer->SeekTo(data->buffer, 0);
if (ret)
{
debug(SOURCE, "unable to seek\n");
return 0;
}
if (!(__read(data->buffer, buf, 6)))
debug(SOURCE, "error reading magic number\n");
if (strncmp((char *)buf, "GIF", 3))
debug(SOURCE, "probably not a GIF\n");
if (strncmp((char *)(buf+3), "87a", 3) &&
strncmp((char *)(buf+3), "89a", 3))
{
debug(SOURCE, "bad version number, not '87a' or '89a'\n");
}
if (!(__read(data->buffer, buf, 7)))
{
debug(SOURCE, "failed to read screen descriptor\n");
return 0;
}
data->width = LM_to_uint(buf[0], buf[1]);
data->height = LM_to_uint(buf[2], buf[3]);
data->bpp = 2 << (buf[4] & 0x07);
data->depth = (((buf[4] & 0x70) >> 3) + 1);
data->bg = buf[5];
data->aspect_ratio = buf[6];
if (buf[4] & 0x80)
{
if (__read_colormap(data->buffer, data->bpp, data->colormap))
debug(SOURCE, "error reading colormap\n");
}
if (data->aspect_ratio && data->aspect_ratio != 49)
debug(SOURCE, "non-square pixels\n");
data->transparent = -1;
data->delay = -1;
data->inputflag = -1;
data->disposal = 0;
data->buffer->GetPosition(data->buffer, &image_start);
for (;;)
{
if (!(__read(data->buffer, &c, 1)))
debug(SOURCE, "EOF/read error on image data\n");
if (c == ';') // GIF terminator
{
data->buffer->SeekTo(data->buffer, image_start);
continue;
}
if (c == '!') // GIF extension
{
__read(data->buffer, &c, 1);
switch (c)
{
case 0x01: // plain text extension
break;
case 0xff: // application extension
break;
case 0xfe: // comment
while (__read_block(data->buffer, (__u8 *)buf))
{
//debug("", "%s\n", buf);
}
break;
case 0xf9: // graphic control extension
__read_block(data->buffer, (__u8 *)buf);
data->disposal = (buf[0] >> 2) & 0x07;
data->inputflag = (buf[0] >> 1) & 0x01;
data->delay = LM_to_uint(buf[1], buf[2]);
if ((buf[0] & 0x01))
data->transparent = buf[3];
break;
default:
debug(SOURCE, "unknown extension 0x%02x\n", c);
break;
}
}
if (c != ',')
continue;
// image block
{
alpha = 0;
__read(data->buffer, buf, 9);
transparent = (data->transparent != -1);
g_colormap = !(buf[8] & 0x80);
if (g_colormap)
{
if (transparent)
colorkey = __find_colorkey(data->bpp, data->colormap);
}
else
{
bpp = 2 << (buf[8] & 0x07);
if (__read_colormap(data->buffer, data->bpp, local_colormap))
debug(SOURCE, "error reading local colormap\n");
if (transparent)
colorkey = __find_colorkey(bpp, local_colormap);
}
if (key_rgb)
key_rgb = colorkey;
if (alpha)
colorkey = 0x00ffffff;
image_data = __read_image(data, data->width, data->height,
(g_colormap) ? data->colormap : local_colormap,
colorkey, (buf[8] & 0x40));
if (image_data)
{
DFBRectangle rect, drect;
void *dst;
int pitch;
rect.x = 0;
rect.y = 0;
rect.w = (int) data->width;
rect.h = (int) data->height;
drect = data->dest_rect;
data->destination->Lock(data->destination, DSLF_WRITE, &dst, &pitch);
dfb_scale_linear_32(image_data, data->width, data->height,
dst, pitch, &drect, data->source);
data->destination->Unlock(data->destination);
if (data->callback)
data->callback (data->ctx);
DFBFREE(image_data);
// delay frame time
usleep(data->delay * 10000);
}
}
}
}
static void
IDirectFBVideoProvider_GIF_Destruct(IDirectFBVideoProvider *thiz)
{
IDirectFBVideoProvider_GIF_data *data;
data = (IDirectFBVideoProvider_GIF_data*)thiz->priv;
thiz->Stop(thiz);
dfb_surface_unref(data->source);
DFB_DEALLOCATE_INTERFACE(thiz);
}
static DFBResult
IDirectFBVideoProvider_GIF_AddRef(IDirectFBVideoProvider *thiz)
{
INTERFACE_GET_DATA(IDirectFBVideoProvider_GIF)
data->ref++;
return DFB_OK;
}
static DFBResult
IDirectFBVideoProvider_GIF_Release(IDirectFBVideoProvider *thiz)
{
INTERFACE_GET_DATA(IDirectFBVideoProvider_GIF)
if (--data->ref == 0)
{
IDirectFBVideoProvider_GIF_Destruct(thiz);
}
return DFB_OK;
}
static DFBResult
IDirectFBVideoProvider_GIF_GetCapabilities(IDirectFBVideoProvider *thiz,
DFBVideoProviderCapabilities *caps)
{
INTERFACE_GET_DATA(IDirectFBVideoProvider_GIF)
if (!caps)
return DFB_INVARG;
*caps = DVCAPS_BASIC | DVCAPS_SCALE;
return DFB_OK;
}
static DFBResult
IDirectFBVideoProvider_GIF_GetSurfaceDescription(IDirectFBVideoProvider *thiz,
DFBSurfaceDescription *desc)
{
int width = 0;
int height = 0;
int transparency;
INTERFACE_GET_DATA(IDirectFBVideoProvider_GIF)
if (!desc)
return DFB_INVARG;
//__read_gif_header(data, &width, &height, &transparency);
memset(desc, 0, sizeof(DFBSurfaceDescription));
desc->flags = (DFBSurfaceDescriptionFlags)
(DSDESC_WIDTH | DSDESC_HEIGHT | DSDESC_PIXELFORMAT);
desc->width = (int) data->width;
desc->height = (int) data->height;
desc->pixelformat = dfb_primary_layer_pixelformat();
return DFB_OK;
}
static DFBResult
IDirectFBVideoProvider_GIF_PlayTo(IDirectFBVideoProvider *thiz,
IDirectFBSurface *destination,
const DFBRectangle *dstrect,
DVFrameCallback callback,
void *ctx )
{
DFBRectangle rect;
IDirectFBSurface_data *dst_data;
INTERFACE_GET_DATA(IDirectFBVideoProvider_GIF)
if (!destination)
return DFB_INVARG;
dst_data = (IDirectFBSurface_data *)destination->priv;
if (!dst_data)
return DFB_DEAD;
/* build the destination rectangle */
if (dstrect)
{
if (dstrect->w < 1 || dstrect->h < 1)
return DFB_INVARG;
rect = *dstrect;
rect.x += dst_data->area.wanted.x;
rect.y += dst_data->area.wanted.y;
}
else
rect = dst_data->area.wanted;
/* save for later blitting operation */
data->dest_rect = rect;
/* build the clip rectangle */
if (!dfb_rectangle_intersect( &rect, &dst_data->area.current ))
return DFB_INVARG;
if (data->destination)
{
data->destination->Release(data->destination);
data->destination = NULL; /* FIXME: remove listener */
}
destination->AddRef(destination);
data->destination = destination; /* FIXME: install listener */
data->callback = callback;
data->ctx = ctx;
if (data->thread == -1)
pthread_create(&data->thread, NULL, FrameThread, data);
return DFB_OK;
}
static DFBResult
IDirectFBVideoProvider_GIF_Stop(IDirectFBVideoProvider *thiz )
{
INTERFACE_GET_DATA(IDirectFBVideoProvider_GIF)
if (data->thread != -1)
{
pthread_cancel( data->thread );
pthread_join( data->thread, NULL );
data->thread = -1;
}
if (data->destination)
{
data->destination->Release(data->destination);
data->destination = NULL; /* FIXME: remove listener */
}
return DFB_OK;
}
static DFBResult
IDirectFBVideoProvider_GIF_SeekTo(IDirectFBVideoProvider *thiz,
double seconds)
{
INTERFACE_GET_DATA(IDirectFBVideoProvider_GIF)
return DFB_UNIMPLEMENTED;
}
static DFBResult
IDirectFBVideoProvider_GIF_GetPos(IDirectFBVideoProvider *thiz,
double *seconds)
{
INTERFACE_GET_DATA(IDirectFBVideoProvider_GIF)
*seconds = 0.0;
return DFB_UNIMPLEMENTED;
}
static DFBResult
IDirectFBVideoProvider_GIF_GetLength(IDirectFBVideoProvider *thiz,
double *seconds)
{
INTERFACE_GET_DATA(IDirectFBVideoProvider_GIF)
*seconds = 0.0;
return DFB_UNIMPLEMENTED;
}
static DFBResult
IDirectFBVideoProvider_GIF_GetColorAdjustment(IDirectFBVideoProvider *thiz,
DFBColorAdjustment *adj)
{
INTERFACE_GET_DATA(IDirectFBVideoProvider_GIF)
if (!adj)
return DFB_INVARG;
return DFB_UNIMPLEMENTED;
}
static DFBResult
IDirectFBVideoProvider_GIF_SetColorAdjustment(IDirectFBVideoProvider *thiz,
DFBColorAdjustment *adj)
{
INTERFACE_GET_DATA(IDirectFBVideoProvider_GIF)
if (!adj)
return DFB_INVARG;
return DFB_UNIMPLEMENTED;
}
/* exported symbols */
static DFBResult
Probe(IDirectFBVideoProvider_ProbeContext *ctx)
{
char *ext;
if ((ext = strrchr(ctx->filename, '.')) &&
!(strcasecmp(ext, ".gif")))
{
return DFB_OK;
}
return DFB_UNSUPPORTED;
}
static DFBResult
Construct(IDirectFBVideoProvider *thiz, const char *filename)
{
IDirectFBDataBuffer *buffer = 0;
DFB_ALLOCATE_INTERFACE_DATA(thiz, IDirectFBVideoProvider_GIF)
// setup data buffer so we can reuse some of the
// code from the GIF image provider.
buffer = calloc(1, sizeof(IDirectFBDataBuffer));
IDirectFBDataBuffer_File_Construct(buffer, filename);
data->ref = 1;
data->buffer = buffer;
buffer->AddRef(buffer);
data->greyscale = -1;
data->transparent = -1;
data->delay = -1;
__read_gif_header(data, &data->width, &data->height, &data->transparent);
dfb_surface_create(data->width, data->height,
dfb_primary_layer_pixelformat(),
CSP_SYSTEMONLY, DSCAPS_SYSTEMONLY, NULL,
&(data->source));
data->thread = -1;
thiz->AddRef = IDirectFBVideoProvider_GIF_AddRef;
thiz->Release = IDirectFBVideoProvider_GIF_Release;
thiz->GetCapabilities = IDirectFBVideoProvider_GIF_GetCapabilities;
thiz->GetSurfaceDescription =
IDirectFBVideoProvider_GIF_GetSurfaceDescription;
thiz->PlayTo = IDirectFBVideoProvider_GIF_PlayTo;
thiz->Stop = IDirectFBVideoProvider_GIF_Stop;
thiz->SeekTo = IDirectFBVideoProvider_GIF_SeekTo;
thiz->GetPos = IDirectFBVideoProvider_GIF_GetPos;
thiz->GetLength = IDirectFBVideoProvider_GIF_GetLength;
thiz->GetColorAdjustment =
IDirectFBVideoProvider_GIF_GetColorAdjustment;
thiz->SetColorAdjustment =
IDirectFBVideoProvider_GIF_SetColorAdjustment;
return DFB_OK;
}