/*
* 2ooM: The Master of Orion II Reverse Engineering Project
* Rendering routines for Cairo surfaces.
* Copyright © 2014 Nick Bowler
*
* 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 3 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, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "lbxgui.h"
static const cairo_user_data_key_t last_frame_key;
/*
* Scale 6-bit colour values (0-63) to 8-bit (0-255) as evenly as possible.
*/
static inline guint32 scale6to8(unsigned x)
{
assert(x <= 0x3f);
return x*0xff / 0x3f;
}
/*
* Output a single row of pixel data in cairo ARGB32 format.
*/
static void write_argb(unsigned char *argb, unsigned char *index,
unsigned n, unsigned x, unsigned y, unsigned stride,
const struct lbx_colour *palette)
{
argb += (unsigned long) y * stride;
argb += 4ul * x;
for (unsigned i = 0; i < n; i++) {
const struct lbx_colour *c;
guint32 px = 0xffff00ff;
c = palette+index[i];
if (c->active) {
px = 0xff000000
| (scale6to8(c->red) << 16)
| (scale6to8(c->green) << 8)
| scale6to8(c->blue);
}
memcpy(argb + 4ul*i, &px, 4);
}
}
/*
* Update a cairo RGBA32 surface according to the drawing commands in the
* specified frame.
*/
static int render_argb(cairo_surface_t *dst, struct lbx_image *img,
unsigned frame, const struct lbx_colour *palette)
{
unsigned char *row, *sdata;
unsigned x, y, stride;
int ret = 0;
long rc;
g_return_val_if_fail(cairo_surface_get_type(dst) == CAIRO_SURFACE_TYPE_IMAGE, -1);
g_return_val_if_fail(cairo_image_surface_get_format(dst) == CAIRO_FORMAT_ARGB32, -1);
g_return_val_if_fail(cairo_image_surface_get_width(dst) == img->width, -1);
g_return_val_if_fail(cairo_image_surface_get_height(dst) == img->height, -1);
sdata = cairo_image_surface_get_data(dst);
stride = cairo_image_surface_get_stride(dst);
row = malloc(img->width);
if (!row)
return -1;
rc = lbx_img_seek(img, frame);
if (rc < 0) {
ret = -1;
goto out;
}
cairo_surface_flush(dst);
while ((rc = lbx_img_read_row_header(img, &x, &y)) != 0) {
if (rc < 0) {
ret = -1;
break;
}
rc = lbx_img_read_row_data(img, row);
if (rc < 0) {
ret = -1;
break;
}
write_argb(sdata, row, rc, x, y, stride, palette);
}
cairo_surface_mark_dirty(dst);
out:
free(row);
return ret;
}
static int get_last_frame(cairo_surface_t *s)
{
int *data = cairo_surface_get_user_data(s, &last_frame_key);
return data ? *data : -1;
}
static void set_last_frame(cairo_surface_t *s, int frame)
{
int *data = cairo_surface_get_user_data(s, &last_frame_key);
if (!data) {
cairo_status_t rc;
data = malloc(sizeof *data);
if (!data)
return;
rc = cairo_surface_set_user_data(s, &last_frame_key,
data, free);
if (rc != CAIRO_STATUS_SUCCESS) {
free(data);
return;
}
}
*data = frame;
}
/*
* Render the specified frame onto a cairo surface. If this is the first
* time rendering a frame onto this surface, it is not assumed to contain
* any particular data. Otherwise, unless there was an intervening call to
* lbxgui_render_restart, the surface is assumed to contain valid frame data
* from the most recent call to this function.
*/
int lbxgui_render_argb(cairo_surface_t *dst, struct lbx_image *img,
unsigned frame, const struct lbx_colour *palette)
{
int last_frame = get_last_frame(dst), ref_frame = 0;
g_return_val_if_fail(frame < img->frames, -1);
g_return_val_if_fail(dst, -1);
if (img->chunk)
ref_frame = (frame / img->chunk) * img->chunk;
if (last_frame < ref_frame || last_frame > frame) {
cairo_t *cr = cairo_create(dst);
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_paint(cr);
cairo_destroy(cr);
last_frame = -1;
}
for (unsigned i = MAX(last_frame+1, ref_frame); i <= frame; i++) {
int rc = render_argb(dst, img, i, palette);
if (rc < 0) {
set_last_frame(dst, -1);
return rc;
}
set_last_frame(dst, i);
}
return 0;
}
/*
* "Forget" the last rendered frame on the specified surface so that the next
* image will be redrawn from scratch.
*/
void lbxgui_render_restart(cairo_surface_t *dst)
{
g_return_if_fail(dst);
set_last_frame(dst, -1);
}
/*
* Copies the active elements in the src palette over the corresponding
* elements in the dst palette.
*/
void lbxgui_stack_palette(struct lbx_colour *dst, const struct lbx_colour *src)
{
for (unsigned i = 0; i < 256; i++) {
if (src[i].active)
dst[i] = src[i];
}
}