479 lines
13 KiB
C
479 lines
13 KiB
C
/* blib - Library of useful things to hack the Blinkenlights
|
|
*
|
|
* Copyright (c) 2001-2002 The Blinkenlights Crew
|
|
* Sven Neumann <sven@gimp.org>
|
|
* Michael Natterer <mitch@gimp.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <glib-object.h>
|
|
|
|
#include "btypes.h"
|
|
#include "bmovie.h"
|
|
#include "bmovie-bml-parser.h"
|
|
#include "bparser.h"
|
|
#include "butils.h"
|
|
|
|
enum
|
|
{
|
|
PARSER_IN_BLM = B_PARSER_STATE_USER,
|
|
PARSER_IN_HEADER,
|
|
PARSER_IN_TITLE,
|
|
PARSER_IN_DESCRIPTION,
|
|
PARSER_IN_CREATOR,
|
|
PARSER_IN_AUTHOR,
|
|
PARSER_IN_EMAIL,
|
|
PARSER_IN_URL,
|
|
PARSER_IN_DURATION,
|
|
PARSER_IN_LOOP,
|
|
PARSER_IN_FRAME,
|
|
PARSER_IN_ROW,
|
|
PARSER_IN_SEGMENTS,
|
|
PARSER_FINISH,
|
|
};
|
|
|
|
typedef struct _ParserData ParserData;
|
|
|
|
struct _ParserData
|
|
{
|
|
gint bits;
|
|
gint channels;
|
|
|
|
gint frame_duration;
|
|
guchar *frame_data;
|
|
gint frame_next_row;
|
|
|
|
BMovie *movie;
|
|
gboolean lazy;
|
|
};
|
|
|
|
static BParserState parser_start_element (BParserState state,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
gpointer user_data,
|
|
GError **error);
|
|
static BParserState parser_end_element (BParserState state,
|
|
const gchar *element_name,
|
|
const gchar *cdata,
|
|
gsize cdata_len,
|
|
gpointer user_data,
|
|
GError **error);
|
|
|
|
static gboolean parse_blm_attributes (ParserData *data,
|
|
const gchar **names,
|
|
const gchar **values);
|
|
static gboolean parse_frame_attributes (ParserData *data,
|
|
const gchar **names,
|
|
const gchar **values);
|
|
|
|
|
|
gboolean
|
|
b_movie_bml_parse_bml (BMovie *movie,
|
|
GIOChannel *io,
|
|
gboolean lazy,
|
|
GError **error)
|
|
{
|
|
BParser *parser;
|
|
ParserData data;
|
|
gboolean retval;
|
|
|
|
data.movie = movie;
|
|
data.lazy = lazy;
|
|
data.frame_data = NULL;
|
|
|
|
parser = b_parser_new (parser_start_element, parser_end_element, &data);
|
|
|
|
retval = b_parser_parse_io_channel (parser, io, FALSE, error);
|
|
|
|
if (retval && b_parser_get_state (parser) != PARSER_FINISH)
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"This doesn't look like Blinkenlights Markup Language");
|
|
retval = FALSE;
|
|
}
|
|
|
|
b_parser_free (parser);
|
|
|
|
g_free (data.frame_data);
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
/* parser functions */
|
|
|
|
static BParserState
|
|
parser_start_element (BParserState state,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
ParserData *data = (ParserData *) user_data;
|
|
|
|
switch (state)
|
|
{
|
|
case B_PARSER_STATE_TOPLEVEL:
|
|
if (! strcmp (element_name, "blm"))
|
|
{
|
|
if (! parse_blm_attributes (data, attribute_names, attribute_values))
|
|
{
|
|
g_set_error (error,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"Invalid attributes for blm element");
|
|
break;
|
|
}
|
|
|
|
return PARSER_IN_BLM;
|
|
}
|
|
break;
|
|
|
|
case PARSER_IN_BLM:
|
|
if (! strcmp (element_name, "header"))
|
|
return PARSER_IN_HEADER;
|
|
|
|
if (data->lazy)
|
|
return B_PARSER_STATE_UNKNOWN;
|
|
|
|
if (! strcmp (element_name, "frame"))
|
|
{
|
|
if (! parse_frame_attributes (data,
|
|
attribute_names, attribute_values))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"Invalid attributes for frame element number %d",
|
|
data->movie->n_frames);
|
|
break;
|
|
}
|
|
|
|
if (data->frame_data)
|
|
memset (data->frame_data, 0,
|
|
data->movie->width * data->movie->height);
|
|
else
|
|
data->frame_data = g_new0 (guchar, (data->movie->width *
|
|
data->movie->height));
|
|
|
|
data->frame_next_row = 0;
|
|
|
|
return PARSER_IN_FRAME;
|
|
}
|
|
break;
|
|
|
|
case PARSER_IN_HEADER:
|
|
if (! strcmp (element_name, "title"))
|
|
return PARSER_IN_TITLE;
|
|
|
|
if (! strcmp (element_name, "description"))
|
|
return PARSER_IN_DESCRIPTION;
|
|
|
|
if (! strcmp (element_name, "creator"))
|
|
return PARSER_IN_CREATOR;
|
|
|
|
if (! strcmp (element_name, "author"))
|
|
return PARSER_IN_AUTHOR;
|
|
|
|
if (! strcmp (element_name, "email"))
|
|
return PARSER_IN_EMAIL;
|
|
|
|
if (! strcmp (element_name, "url"))
|
|
return PARSER_IN_URL;
|
|
|
|
/* only parse duration if we are lazy-loading ! */
|
|
if (! strcmp (element_name, "duration") && data->lazy)
|
|
return PARSER_IN_DURATION;
|
|
|
|
if (! strcmp (element_name, "loop"))
|
|
return PARSER_IN_LOOP;
|
|
|
|
break;
|
|
|
|
case PARSER_IN_FRAME:
|
|
if (! strcmp (element_name, "row"))
|
|
{
|
|
if (data->frame_next_row == data->movie->height)
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"Too many rows in frame number %d",
|
|
data->movie->n_frames);
|
|
break;
|
|
}
|
|
|
|
return PARSER_IN_ROW;
|
|
}
|
|
else if(! strcmp (element_name, "segments"))
|
|
{
|
|
return PARSER_IN_SEGMENTS;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return B_PARSER_STATE_UNKNOWN;
|
|
}
|
|
|
|
static BParserState
|
|
parser_end_element (BParserState state,
|
|
const gchar *element_name,
|
|
const gchar *cdata,
|
|
gsize cdata_len,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
ParserData *data = (ParserData *) user_data;
|
|
|
|
switch (state)
|
|
{
|
|
case PARSER_IN_BLM:
|
|
return PARSER_FINISH;
|
|
|
|
case PARSER_IN_HEADER:
|
|
return PARSER_IN_BLM;
|
|
|
|
case PARSER_IN_TITLE:
|
|
if (!data->movie->title)
|
|
data->movie->title = g_strdup (cdata);
|
|
return PARSER_IN_HEADER;
|
|
|
|
case PARSER_IN_DESCRIPTION:
|
|
if (!data->movie->description)
|
|
data->movie->description = g_strdup (cdata);
|
|
return PARSER_IN_HEADER;
|
|
|
|
case PARSER_IN_CREATOR:
|
|
if (!data->movie->creator)
|
|
data->movie->creator = g_strdup (cdata);
|
|
return PARSER_IN_HEADER;
|
|
|
|
case PARSER_IN_AUTHOR:
|
|
if (!data->movie->author)
|
|
data->movie->author = g_strdup (cdata);
|
|
return PARSER_IN_HEADER;
|
|
|
|
case PARSER_IN_EMAIL:
|
|
if (!data->movie->email)
|
|
data->movie->email = g_strdup (cdata);
|
|
return PARSER_IN_HEADER;
|
|
|
|
case PARSER_IN_URL:
|
|
if (!data->movie->url)
|
|
data->movie->url = g_strdup (cdata);
|
|
return PARSER_IN_HEADER;
|
|
|
|
case PARSER_IN_DURATION:
|
|
b_parse_int (cdata, & data->movie->duration);
|
|
return PARSER_IN_HEADER;
|
|
|
|
case PARSER_IN_LOOP:
|
|
/* for backward compat, ignore loop if it has a value of "no" */
|
|
if (!cdata_len || g_ascii_tolower (*cdata) != 'n')
|
|
data->movie->loop = TRUE;
|
|
return PARSER_IN_HEADER;
|
|
|
|
case PARSER_IN_FRAME:
|
|
if (data->frame_next_row != data->movie->height)
|
|
{
|
|
g_set_error (error,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"Too few rows in frame number %d",
|
|
data->movie->n_frames);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
b_movie_prepend_frame (data->movie,
|
|
data->frame_duration, data->frame_data);
|
|
}
|
|
return PARSER_IN_BLM;
|
|
|
|
case PARSER_IN_ROW:
|
|
{
|
|
gchar *row;
|
|
gchar *src;
|
|
guchar *dest;
|
|
gint bpp_src;
|
|
gint i, x = 0;
|
|
|
|
row = g_strdup (cdata);
|
|
|
|
if ((data->bits <= 4 &&
|
|
cdata_len != data->movie->width * data->channels) ||
|
|
(data->bits > 4 &&
|
|
cdata_len != data->movie->width * data->channels * 2))
|
|
{
|
|
g_set_error (error,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"Invalid row length in frame number %d",
|
|
data->movie->n_frames);
|
|
goto row_done;
|
|
}
|
|
|
|
bpp_src = data->bits <= 4 ? 1 : 2;
|
|
|
|
src = row;
|
|
dest = data->frame_data + (data->movie->width * data->frame_next_row);
|
|
|
|
for (; x < data->movie->width; x++, src += bpp_src, dest += 1)
|
|
{
|
|
*dest = 0;
|
|
|
|
for (i = 0; i < bpp_src; i++)
|
|
{
|
|
*dest <<= 4;
|
|
|
|
src[i] = g_ascii_tolower (src[i]);
|
|
|
|
switch (src[i])
|
|
{
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
*dest += src[i] - '0';
|
|
break;
|
|
|
|
case 'a':
|
|
case 'b':
|
|
case 'c':
|
|
case 'd':
|
|
case 'e':
|
|
case 'f':
|
|
*dest += 10 + src[i] - 'a';
|
|
break;
|
|
|
|
default:
|
|
g_set_error (error,
|
|
G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"Invalid row data in frame number %d",
|
|
data->movie->n_frames);
|
|
goto row_done;
|
|
}
|
|
}
|
|
|
|
if (*dest > data->movie->maxval)
|
|
{
|
|
g_set_error (error,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"Row data exceeds maxval (%d) in frame number %d",
|
|
data->movie->maxval, data->movie->n_frames);
|
|
goto row_done;
|
|
}
|
|
}
|
|
|
|
row_done:
|
|
g_free (row);
|
|
data->frame_next_row++;
|
|
|
|
if (x != data->movie->width)
|
|
return B_PARSER_STATE_UNKNOWN;
|
|
}
|
|
return PARSER_IN_FRAME;
|
|
|
|
case PARSER_IN_SEGMENTS:
|
|
{
|
|
/* parse the segment information */
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return B_PARSER_STATE_UNKNOWN;
|
|
}
|
|
|
|
static gboolean
|
|
parse_blm_attributes (ParserData *data,
|
|
const gchar **names,
|
|
const gchar **values)
|
|
{
|
|
gint i;
|
|
gint width = 0;
|
|
gint height = 0;
|
|
gint bits = 1;
|
|
gint channels = 1;
|
|
|
|
for (i = 0; names[i] && values[i]; i++)
|
|
{
|
|
if (strcmp (names[i], "width") == 0)
|
|
b_parse_int (values[i], &width);
|
|
else if (strcmp (names[i], "height") == 0)
|
|
b_parse_int (values[i], &height);
|
|
else if (strcmp (names[i], "bits") == 0)
|
|
b_parse_int (values[i], &bits);
|
|
else if (strcmp (names[i], "channels") == 0)
|
|
b_parse_int (values[i], &channels);
|
|
}
|
|
|
|
if (width > 0 && height > 0 &&
|
|
(bits >= 1 && bits <= 8) && (channels == 1)) /* channels == 3 */
|
|
{
|
|
data->movie->width = width;
|
|
data->movie->height = height;
|
|
data->movie->maxval = (1 << bits) - 1;
|
|
data->movie->channels = channels;
|
|
|
|
data->bits = bits;
|
|
data->channels = channels;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_frame_attributes (ParserData *data,
|
|
const gchar **names,
|
|
const gchar **values)
|
|
{
|
|
gint i;
|
|
gint duration = 0;
|
|
|
|
for (i = 0; names[i] && values[i]; i++)
|
|
{
|
|
if (strcmp (names[i], "duration") == 0)
|
|
b_parse_int (values[i], &duration);
|
|
}
|
|
|
|
if (duration < B_MOVIE_MIN_DELAY)
|
|
{
|
|
g_printerr ("Frame with %d ms duration, using %d ms instead\n",
|
|
duration, B_MOVIE_DEFAULT_DELAY);
|
|
duration = B_MOVIE_DEFAULT_DELAY;
|
|
}
|
|
|
|
data->frame_duration = duration;
|
|
|
|
return TRUE;
|
|
}
|