]> git.draconx.ca Git - liblbx.git/blob - src/gui/image.c
lbxgui: Render LBX directly to Cairo surfaces.
[liblbx.git] / src / gui / image.c
1 /*
2  *  2ooM: The Master of Orion II Reverse Engineering Project
3  *  Rendering routines for Cairo surfaces.
4  *  Copyright © 2014 Nick Bowler
5  *
6  *  This program is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <assert.h>
25 #include <limits.h>
26 #include <cairo/cairo.h>
27 #include <glib.h>
28
29 #include "lbxgui.h"
30
31 static const cairo_user_data_key_t last_frame_key;
32
33 /*
34  * Scale 6-bit colour values (0-63) to 8-bit (0-255) as evenly as possible.
35  */
36 static inline guint32 scale6to8(unsigned x)
37 {
38         assert(x <= 0x3f);
39
40         return x*0xff / 0x3f;
41 }
42
43 /*
44  * Output a single row of pixel data in cairo ARGB32 format.
45  */
46 static void write_argb(unsigned char *argb, unsigned char *index,
47                        unsigned n, unsigned x, unsigned y, unsigned stride,
48                        const struct lbx_colour *palette)
49 {
50         argb += (unsigned long) y * stride;
51         argb += 4ul * x;
52
53         for (unsigned i = 0; i < n; i++) {
54                 const struct lbx_colour *c;
55                 guint32 px = 0xffff00ff;
56
57                 c = palette+index[i];
58                 if (c->active) {
59                         px = 0xff000000
60                            | (scale6to8(c->red)   << 16)
61                            | (scale6to8(c->green) <<  8)
62                            |  scale6to8(c->blue);
63                 }
64
65                 memcpy(argb + 4ul*i, &px, 4);
66         }
67 }
68
69 /*
70  * Update a cairo RGBA32 surface according to the drawing commands in the
71  * specified frame.
72  */
73 static int render_argb(cairo_surface_t *dst, struct lbx_image *img,
74                        unsigned frame, const struct lbx_colour *palette)
75 {
76         unsigned char *row, *sdata;
77         unsigned x, y, stride;
78         int ret = 0;
79         long rc;
80
81         g_return_val_if_fail(cairo_surface_get_type(dst) == CAIRO_SURFACE_TYPE_IMAGE, -1);
82         g_return_val_if_fail(cairo_image_surface_get_format(dst) == CAIRO_FORMAT_ARGB32, -1);
83         g_return_val_if_fail(cairo_image_surface_get_width(dst) == img->width, -1);
84         g_return_val_if_fail(cairo_image_surface_get_height(dst) == img->height, -1);
85
86         sdata = cairo_image_surface_get_data(dst);
87         stride = cairo_image_surface_get_stride(dst);
88
89         row = malloc(img->width);
90         if (!row)
91                 return -1;
92
93         rc = lbx_img_seek(img, frame);
94         if (rc < 0) {
95                 ret = -1;
96                 goto out;
97         }
98
99         cairo_surface_flush(dst);
100         while ((rc = lbx_img_read_row_header(img, &x, &y)) != 0) {
101                 if (rc < 0) {
102                         ret = -1;
103                         break;
104                 }
105
106                 rc = lbx_img_read_row_data(img, row);
107                 if (rc < 0) {
108                         ret = -1;
109                         break;
110                 }
111
112                 write_argb(sdata, row, rc, x, y, stride, palette);
113         }
114         cairo_surface_mark_dirty(dst);
115 out:
116         free(row);
117         return ret;
118 }
119
120 static int get_last_frame(cairo_surface_t *s)
121 {
122         int *data = cairo_surface_get_user_data(s, &last_frame_key);
123
124         return data ? *data : -1;
125 }
126
127 static void set_last_frame(cairo_surface_t *s, int frame)
128 {
129         int *data = cairo_surface_get_user_data(s, &last_frame_key);
130
131         if (!data) {
132                 cairo_status_t rc;
133
134                 data = malloc(sizeof *data);
135                 if (!data)
136                         return;
137
138                 rc = cairo_surface_set_user_data(s, &last_frame_key,
139                                                  data, free);
140                 if (rc != CAIRO_STATUS_SUCCESS) {
141                         free(data);
142                         return;
143                 }
144         }
145
146         *data = frame;
147 }
148
149 /*
150  * Render the specified frame onto a cairo surface.  If this is the first
151  * time rendering a frame onto this surface, it is not assumed to contain
152  * any particular data.  Otherwise, unless there was an intervening call to
153  * lbxgui_render_restart, the surface is assumed to contain valid frame data
154  * from the most recent call to this function.
155  */
156 int lbxgui_render_argb(cairo_surface_t *dst, struct lbx_image *img,
157                        unsigned frame, const struct lbx_colour *palette)
158 {
159         int last_frame = get_last_frame(dst), ref_frame = 0;
160
161         g_return_val_if_fail(frame < img->frames, -1);
162         g_return_val_if_fail(dst, -1);
163
164         if (img->chunk)
165                 ref_frame = (frame / img->chunk) * img->chunk;
166
167         if (last_frame < ref_frame || last_frame > frame) {
168                 cairo_t *cr = cairo_create(dst);
169                 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
170                 cairo_paint(cr);
171                 cairo_destroy(cr);
172
173                 last_frame = -1;
174         }
175
176         for (unsigned i = MAX(last_frame+1, ref_frame); i <= frame; i++) {
177                 int rc = render_argb(dst, img, i, palette);
178                 if (rc < 0) {
179                         set_last_frame(dst, -1);
180                         return rc;
181                 }
182
183                 set_last_frame(dst, i);
184         }
185
186         return 0;
187 }
188
189 /*
190  * "Forget" the last rendered frame on the specified surface so that the next
191  * image will be redrawn from scratch.
192  */
193 void lbxgui_render_restart(cairo_surface_t *dst)
194 {
195         g_return_if_fail(dst);
196
197         set_last_frame(dst, -1);
198 }
199
200 /*
201  * Copies the active elements in the src palette over the corresponding
202  * elements in the dst palette.
203  */
204 void lbxgui_stack_palette(struct lbx_colour *dst, const struct lbx_colour *src)
205 {
206         for (unsigned i = 0; i < 256; i++) {
207                 if (src[i].active)
208                         dst[i] = src[i];
209         }
210 }