20130420
[gdash.git] / src / gfx / cellrenderer.cpp
blob9cd8a5392f7c71e15fea198d547f7f4239d3244c
1 /*
2 * Copyright (c) 2007-2013, Czirkos Zoltan http://code.google.com/p/gdash/
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
19 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
20 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 #include "config.h"
26 #include <memory>
28 #include "gfx/cellrenderer.hpp"
30 #include "misc/logger.hpp"
31 #include "gfx/pixbuf.hpp"
32 #include "gfx/pixbuffactory.hpp"
33 #include "gfx/screen.hpp"
36 /* data */
37 #include "c64_gfx.cpp"
39 CellRenderer::CellRenderer(Screen &screen, const std::string &theme_file)
40 : PixmapStorage(screen),
41 loaded(0),
42 cells_all(0),
43 is_c64_colored(false),
44 cell_size(0),
45 cells_pixbufs(),
46 cells(),
47 color0(GD_GDASH_BLACK),
48 color1(GD_GDASH_MIDDLEBLUE),
49 color2(GD_GDASH_LIGHTRED),
50 color3(GD_GDASH_WHITE),
51 color4(GD_GDASH_WHITE),
52 color5(GD_GDASH_WHITE),
53 screen(screen) {
54 load_theme_file(theme_file);
58 CellRenderer::~CellRenderer() {
59 remove_cached();
60 delete loaded;
61 delete cells_all;
64 /** Remove colored Pixbufs and Pixmaps created. */
65 void CellRenderer::remove_cached() {
66 for (unsigned i = 0; i < G_N_ELEMENTS(cells_pixbufs); ++i) {
67 delete cells_pixbufs[i];
68 cells_pixbufs[i] = NULL;
70 CellRenderer::release_pixmaps();
71 if (is_c64_colored) {
72 delete cells_all;
73 cells_all = NULL;
78 void CellRenderer::release_pixmaps() {
79 for (unsigned i = 0; i < G_N_ELEMENTS(cells); ++i) {
80 delete cells[i];
81 cells[i] = 0;
87 Pixbuf &CellRenderer::cell_pixbuf(unsigned i) {
88 g_assert(i < G_N_ELEMENTS(cells_pixbufs));
89 if (cells_all == NULL)
90 create_colorized_cells();
91 g_assert(cells_all != NULL);
92 if (cells_pixbufs[i] == NULL)
93 cells_pixbufs[i] = screen.pixbuf_factory.create_subpixbuf(*cells_all, (i % NUM_OF_CELLS_X) * cell_size, (i / NUM_OF_CELLS_X) * cell_size, cell_size, cell_size);
94 return *cells_pixbufs[i];
97 Pixmap &CellRenderer::cell(unsigned i) {
98 g_assert(i < G_N_ELEMENTS(cells));
99 if (cells[i] == NULL) {
100 int type = i / NUM_OF_CELLS; // 0=normal, 1=colored1, 2=colored2
101 int index = i % NUM_OF_CELLS;
102 Pixbuf &pb = cell_pixbuf(index); // this is to be rendered as a pixmap, but may be colored
104 switch (type) {
105 case 0:
106 cells[i] = screen.create_scaled_pixmap_from_pixbuf(pb, false);
107 break;
108 case 1: {
109 std::auto_ptr<Pixbuf> colored(screen.pixbuf_factory.create_composite_color(pb, gd_flash_color));
110 cells[i] = screen.create_scaled_pixmap_from_pixbuf(*colored, false);
112 break;
113 case 2: {
114 std::auto_ptr<Pixbuf> colored(screen.pixbuf_factory.create_composite_color(pb, gd_select_color));
115 cells[i] = screen.create_scaled_pixmap_from_pixbuf(*colored, false);
117 break;
118 default:
119 g_assert_not_reached();
120 break;
123 return *cells[i];
126 /* check if given surface is ok to be a gdash theme. */
127 bool CellRenderer::is_pixbuf_ok_for_theme(const Pixbuf &surface) {
128 if ((surface.get_width() % NUM_OF_CELLS_X != 0)
129 || (surface.get_height() % NUM_OF_CELLS_Y != 0)
130 || (surface.get_width() / NUM_OF_CELLS_X != surface.get_height() / NUM_OF_CELLS_Y)) {
131 gd_critical(CPrintf("Image should contain %d cells in a row and %d in a column!") % int(NUM_OF_CELLS_X) % int(NUM_OF_CELLS_Y));
132 return false;
134 if (surface.get_width() / NUM_OF_CELLS_X < 16) {
135 gd_critical("The image should contain cells which are at least 16x16 pixels in size!");
138 return true; /* passed checks */
141 bool CellRenderer::is_image_ok_for_theme(PixbufFactory &pixbuf_factory, const char *filename) {
142 try {
143 SetLoggerContextForFunction scf(filename);
144 std::auto_ptr<Pixbuf> image(pixbuf_factory.create_from_file(filename));
145 return is_pixbuf_ok_for_theme(*image);
146 } catch (...) {
147 return false;
151 /* load theme from image file. */
152 /* return true if successful. */
153 bool CellRenderer::loadcells_image(Pixbuf *image) {
154 /* do some checks. if those fail, the error is already reported by the function */
155 if (!is_pixbuf_ok_for_theme(*image)) {
156 delete image;
157 return false;
160 /* remove old stuff */
161 remove_cached();
162 delete loaded;
163 loaded = NULL;
165 /* load new stuff */
166 cell_size = image->get_width() / NUM_OF_CELLS_X;
167 loaded = image;
169 if (check_if_pixbuf_c64_png(*loaded)) {
170 /* c64 pixbuf with a small number of colors which can be changed */
171 cells_all = NULL;
172 is_c64_colored = true;
173 } else {
174 /* normal, "truecolor" pixbuf */
175 cells_all = loaded;
176 loaded = NULL;
177 is_c64_colored = false;
179 return true;
183 /* load theme from image file. */
184 /* return true if successful. */
185 bool CellRenderer::loadcells_file(const std::string &filename) {
186 /* load cell graphics */
187 /* load from file */
188 try {
189 Pixbuf *image = screen.pixbuf_factory.create_from_file(filename.c_str());
190 return loadcells_image(image);
191 } catch (std::exception &e) {
192 gd_critical(CPrintf("%s: unable to load image (%s)") % filename % e.what());
193 return false;
197 /* load the theme specified in theme_file. */
198 /* if successful, ok. */
199 /* if fails, or no theme specified, load the builtin */
200 void CellRenderer::load_theme_file(const std::string &theme_file) {
201 if (theme_file != "" && loadcells_file(theme_file)) {
202 /* loaded from png file */
203 } else {
204 Pixbuf *image = screen.pixbuf_factory.create_from_inline(sizeof(c64_gfx), c64_gfx);
205 loadcells_image(image);
209 int CellRenderer::get_cell_size() {
210 return cell_size * screen.get_pixmap_scale();
214 void CellRenderer::select_pixbuf_colors(GdColor c0, GdColor c1, GdColor c2, GdColor c3, GdColor c4, GdColor c5) {
215 if (c0 != color0 || c1 != color1 || c2 != color2 || c3 != color3 || c4 != color4 || c5 != color5) {
216 /* if not the same colors as requested before */
217 color0 = c0;
218 color1 = c1;
219 color2 = c2;
220 color3 = c3;
221 color4 = c4;
222 color5 = c5;
223 if (is_c64_colored)
224 remove_cached();
230 static inline int
231 c64_color_index(int h, int s, int v, int a) {
232 if (a < 0x80)
233 return 8; /* transparent */
234 if (v < 0x10)
235 return 0; /* black */
236 if (s < 0x10)
237 return 7; /* editor white, arrows & etc */
239 if (h < 30 || h >= 330) /* around 0 */
240 return 1; /* red - foreg1 */
241 if (h >= 270 && h < 330) /* around 300 */
242 return 2; /* purple - foreg2 */
243 if (h >= 30 && h < 90) /* around 60 */
244 return 3; /* yellow - foreg3 */
245 if (h >= 90 && h < 150) /* around 120 */
246 return 4; /* green - amoeba */
247 if (h >= 210 && h < 270) /* around 240 */
248 return 5; /* slime */
250 if (h >= 150 && h < 210) /* around 180 */
251 return 6; /* cyan - editor black */
253 return 0; /* should be unreachable */
257 /* returns true, if the given pixbuf seems to be a c64 imported image. */
258 bool CellRenderer::check_if_pixbuf_c64_png(Pixbuf const &image) {
259 int wx = image.get_width() * 4; // 4 bytes/pixel
260 int h = image.get_height();
262 bool c64_png = true;
263 for (int y = 0; y < h; y++) {
264 const unsigned char *p = (const unsigned char *) image.get_row(y);
265 for (int x = 0; x < wx; x++)
266 if (p[x] != 0 && p[x] != 255)
267 c64_png = false;
270 return c64_png;
274 /** This function takes the loaded image, and transforms it using the selected
275 * cave colors, to create cells_all.
277 * The process is as follows. All pixels are converted to HSV (hue, saturation,
278 * value). The hues of the pixels should be 0 (red), 60 (yellow), 120 (green)
279 * etc, n*60. This way will the routine recognize the colors.
280 * After converting the pixels to HSV, the hue selects the cave color to use.
281 * The resulting color will use the hue of the selected cave color, the product
282 * of the saturations of the cave color and the original color, and the
283 * product of the values:
285 * Hresult = Hcave
286 * Sresult = Scave * Sloadedimage
287 * Vresult = Vcave * Vloadedimage.
289 * This allows for modulating the cave colors in saturation and value. If the
290 * loaded image contains a dark purple color instead of RGB(255;0;255) purple,
291 * the cave color will also be darkened at that pixel and so on. */
292 void CellRenderer::create_colorized_cells() {
293 g_assert(is_c64_colored);
294 g_assert(loaded != NULL);
296 if (cells_all)
297 delete cells_all;
299 GdColor cols[9]; /* holds rgba for color indexes internally used */
301 cols[0] = color0.to_hsv(); /* c64 background */
302 cols[1] = color1.to_hsv(); /* foreg1 */
303 cols[2] = color2.to_hsv(); /* foreg2 */
304 cols[3] = color3.to_hsv(); /* foreg3 */
305 cols[4] = color4.to_hsv(); /* amoeba */
306 cols[5] = color5.to_hsv(); /* slime */
307 cols[6] = GdColor::from_hsv(0, 0, 0); /* black, opaque */
308 cols[7] = GdColor::from_hsv(0, 0, 100); /* white, opaque */
309 cols[8] = GdColor::from_hsv(0, 0, 0); /* for the transparent */
311 int w = loaded->get_width(), h = loaded->get_height();
312 cells_all = screen.pixbuf_factory.create(w, h);
314 for (int y = 0; y < h; y++) {
315 const guint32 *p = loaded->get_row(y);
316 guint32 *to = cells_all->get_row(y);
317 for (int x = 0; x < w; x++) {
318 /* rgb values found in image */
319 unsigned r = (p[x] & loaded->rmask) >> loaded->rshift;
320 unsigned g = (p[x] & loaded->gmask) >> loaded->gshift;
321 unsigned b = (p[x] & loaded->bmask) >> loaded->bshift;
322 unsigned a = (p[x] & loaded->amask) >> loaded->ashift;
323 GdColor in = GdColor::from_rgb(r, g, b).to_hsv();
324 unsigned h = in.get_h();
325 unsigned s = in.get_s();
326 unsigned v = in.get_v();
328 /* the color code from the original image (essentially the hue) will select the color index */
329 unsigned index = c64_color_index(h, s, v, a);
330 GdColor newcol;
331 if (index == 0 || index >= 6) {
332 /* for the background and the editor colors, no shading is used */
333 newcol = cols[index];
334 } else {
335 /* otherwise the saturation and value from the original image will modify it */
336 unsigned s_multiplier = s;
337 unsigned v_multiplier = v;
338 newcol = GdColor::from_hsv(
339 cols[index].get_h(),
340 cols[index].get_s() * s_multiplier / 100,
341 cols[index].get_v() * v_multiplier / 100).to_rgb();
344 guint32 newcolword =
345 newcol.get_r() << cells_all->rshift |
346 newcol.get_g() << cells_all->gshift |
347 newcol.get_b() << cells_all->bshift |
348 a << cells_all->ashift;
350 to[x] = newcolword;