1 // This file is part of Golly.
2 // See docs/License.html for the copyright notice.
3
4 /* -------------------- Some notes on Golly's display code ---------------------
5
6 The rectangular area used to display patterns is called the viewport.
7 It's represented by a window of class PatternView defined in wxview.h.
8 The global viewptr points to a PatternView window which is created in
9 MainFrame::MainFrame() in wxmain.cpp.
10
11 All drawing in the viewport is done in this module using OpenGL 1.
12
13 The main rendering routine is DrawView() -- see the end of this module.
14 DrawView() is called from PatternView::OnPaint(), the update event handler
15 for the viewport window. Update events are created automatically by the
16 wxWidgets event dispatcher, or they can be created manually by calling
17 Refresh().
18
19 DrawView() does the following tasks:
20
21 - Fills the entire viewport with the state 0 color.
22 - Calls currlayer->algo->draw() to draw the current pattern. It passes
23 in renderer, an instance of golly_render (derived from liferender) which
24 has these methods:
25 - pixblit() draws a pixmap containing at least one live cell.
26 - getcolors() provides access to the current layer's color arrays.
27
28 Note that currlayer->algo->draw() does all the hard work of figuring
29 out which parts of the viewport are dead and building all the pixmaps
30 for the live parts. The pixmaps contain suitably shrunken images
31 when the scale is < 1:1 (ie. mag < 0).
32
33 Each lifealgo needs to implement its own draw() method; for example,
34 hlifealgo::draw() in hlifedraw.cpp.
35
36 - Calls DrawGridLines() to overlay grid lines if they are visible.
37
38 - Calls DrawGridBorder() to draw border around a bounded universe.
39
40 - Calls DrawSelection() to overlay a translucent selection rectangle
41 if a selection exists and any part of it is visible.
42
43 - Calls DrawStackedLayers() to overlay multiple layers using the current
44 layer's scale and location.
45
46 - If the user is doing a paste, DrawPasteImage() creates a suitable
47 viewport and draws the paste pattern (stored in pastelayer).
48
49 - Calls DrawControls() if the translucent controls need to be drawn.
50
51 - Calls DrawOverlay() if the current overlay needs to be drawn.
52
53 ----------------------------------------------------------------------------- */
54
55 #include <string.h> // for memcpy
56
57 #include "wx/wxprec.h" // for compilers that support precompilation
58 #ifndef WX_PRECOMP
59 #include "wx/wx.h" // for all others include the necessary headers
60 #endif
61
62 #include "wx/rawbmp.h" // for wxAlphaPixelData
63
64 #include "bigint.h"
65 #include "lifealgo.h"
66 #include "viewport.h"
67
68 #include "wxgolly.h" // for viewptr, bigview, statusptr
69 #include "wxutils.h" // for Warning
70 #include "wxprefs.h" // for showgridlines, mingridmag, swapcolors, etc
71 #include "wxstatus.h" // for statusptr->...
72 #include "wxview.h" // for viewptr->...
73 #include "wxlayer.h" // currlayer, GetLayer, etc
74 #include "wxoverlay.h" // curroverlay, overlay_position
75 #include "wxrender.h"
76
77 // -----------------------------------------------------------------------------
78
79 // local data used in golly_render routines
80
81 int currwd, currht; // current width and height of viewport, in pixels
82 int scalefactor; // current scale factor (1, 2, 4, 8 or 16)
83 unsigned char dead_alpha = 255; // alpha value for dead pixels
84 unsigned char live_alpha = 255; // alpha value for live pixels
85 GLuint rgbatexture = 0; // texture name for drawing RGBA bitmaps
86 GLuint icontexture = 0; // texture name for drawing icons
87 GLuint celltexture = 0; // texture name for drawing magnified cells
88 GLuint tiletexture = 0; // texture name for tiled drawing
89 unsigned char* iconatlas = NULL; // pointer to texture atlas for current set of icons
90 unsigned char* cellatlas = NULL; // pointer to texture atlas for current set of magnified cells
91 unsigned char* itemgrid = NULL; // pointer to buffer for 16x16 cell/icon grid
92 const int itemgridwidth = 512; // item grid width in pixels
93 const int itemgridheight = 512; // item grid height in pixels
94 const int itemgridrows = 16; // number of item rows
95 const int itemgridcols = 16; // number of item columns
96 float iconscalew = 1.0;
97 float iconscaleh = 1.0;
98 unsigned char* cellatlasPOT = NULL; // pointer to power of 2 texture atlas for magnified cells
99 unsigned char* tilebuffer = NULL; // pointer to tile texture buffer (will be power of 2 in size)
100 float cellscalew = 1.0;
101 float cellscaleh = 1.0;
102
103 // cellatlas needs to be rebuilt if any of these parameters change
104 int prevnum = 0; // previous number of live states
105 int prevsize = 0; // previous cell size
106 unsigned char prevalpha; // previous alpha component
107 unsigned char prevr[256]; // previous red component of each state
108 unsigned char prevg[256]; // previous green component of each state
109 unsigned char prevb[256]; // previous blue component of each state
110 bool prevborders = false; // previous cell borders setting
111
112 // fixed texture coordinates used by glTexCoordPointer
113 static const GLshort texture_coordinates[] = { 0,0, 1,0, 0,1, 1,1 };
114
115 // for drawing paste pattern
116 Layer* pastelayer; // layer containing the paste pattern
117 wxRect pastebbox; // bounding box in cell coords (not necessarily minimal)
118 unsigned char* modedata[4] = {NULL}; // RGBA data for drawing current paste mode (And, Copy, Or, Xor)
119 const int modewd = 32; // width of each modedata
120 const int modeht = 16; // height of each modedata
121
122 // for drawing translucent controls
123 unsigned char* ctrlsbitmap = NULL; // RGBA data for controls bitmap
124 unsigned char* darkbutt = NULL; // RGBA data for darkening one button
125 int controlswd; // width of ctrlsbitmap
126 int controlsht; // height of ctrlsbitmap
127
128 // include controls_xpm (XPM data for controls bitmap)
129 #include "bitmaps/controls.xpm"
130
131 // these constants must match image dimensions in bitmaps/controls.xpm
132 const int buttborder = 6; // size of outer border
133 const int buttsize = 22; // size of each button
134 const int buttsperrow = 3; // # of buttons in each row
135 const int numbutts = 15; // # of buttons
136 const int rowgap = 4; // vertical gap after first 2 rows
137
138 // currently clicked control
139 control_id currcontrol = NO_CONTROL;
140
141 // dynamic vertex buffer for grid lines
142 // start size is enough for 4096x4096 screen resolution at zoom 4
143 long vertexsize = 8192 * sizeof(GLfloat);
144 GLfloat *vertexbuffer = (GLfloat *)malloc(vertexsize);
145
146 // fixed vertex and texture coordinate buffers for drawing magnified cells or icons
147 // for speed cells are drawn in blocks of 4096
148 // size is enough for 4096 cells and must be a multiple of 12
149 long magbuffersize = 4096 * 12 * sizeof(GLfloat);
150 GLfloat *magvertexbuffer = (GLfloat *)malloc(magbuffersize);
151 GLfloat *magtextcoordbuffer = (GLfloat *)malloc(magbuffersize);
152
153 #ifdef __WXMSW__
154 // this constant isn't defined in the OpenGL headers on Windows (XP at least)
155 #define GL_CLAMP_TO_EDGE 0x812F
156 #endif
157
158 // -----------------------------------------------------------------------------
159
SetColor(int r,int g,int b,int a)160 static void SetColor(int r, int g, int b, int a)
161 {
162 glColor4ub(r, g, b, a);
163 }
164
165 // -----------------------------------------------------------------------------
166
FillRect(int x,int y,int wd,int ht)167 static void FillRect(int x, int y, int wd, int ht)
168 {
169 GLfloat rect[] = {
170 (float)x, (float)y+ht, // left, bottom
171 (float)x+wd, (float)y+ht, // right, bottom
172 (float)x+wd, (float)y, // right, top
173 (float)x, (float)y, // left, top
174 };
175 glVertexPointer(2, GL_FLOAT, 0, rect);
176 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
177 }
178
179 // -----------------------------------------------------------------------------
180
EnableTextures()181 static void EnableTextures()
182 {
183 if (!glIsEnabled(GL_TEXTURE_2D)) {
184 // restore texture color and enable textures
185 SetColor(255, 255, 255, 255);
186 glEnable(GL_TEXTURE_2D);
187 }
188 }
189
190 // -----------------------------------------------------------------------------
191
DisableTextures()192 static void DisableTextures()
193 {
194 if (glIsEnabled(GL_TEXTURE_2D)) {
195 glDisable(GL_TEXTURE_2D);
196 };
197 }
198
199 // -----------------------------------------------------------------------------
200
CreateTranslucentControls()201 void CreateTranslucentControls()
202 {
203 wxImage image(controls_xpm);
204 controlswd = image.GetWidth();
205 controlsht = image.GetHeight();
206
207 // create ctrlsbitmap and initialize its RGBA data based on pixels in image
208 ctrlsbitmap = (unsigned char*) malloc(controlswd * controlsht * 4);
209 if (ctrlsbitmap) {
210 int p = 0;
211 for ( int y = 0; y < controlsht; y++ ) {
212 for ( int x = 0; x < controlswd; x++ ) {
213 unsigned char r = image.GetRed(x,y);
214 unsigned char g = image.GetGreen(x,y);
215 unsigned char b = image.GetBlue(x,y);
216 if (r == 0 && g == 0 && b == 0) {
217 // make black pixel 100% transparent
218 ctrlsbitmap[p++] = 0;
219 ctrlsbitmap[p++] = 0;
220 ctrlsbitmap[p++] = 0;
221 ctrlsbitmap[p++] = 0;
222 } else {
223 // make all non-black pixels translucent
224 ctrlsbitmap[p++] = r;
225 ctrlsbitmap[p++] = g;
226 ctrlsbitmap[p++] = b;
227 ctrlsbitmap[p++] = 192; // 75% opaque
228 }
229 }
230 }
231 }
232
233 // allocate bitmap for darkening a clicked button
234 darkbutt = (unsigned char*) malloc(buttsize * buttsize * 4);
235
236 // create bitmaps for drawing each paste mode
237 paste_mode savemode = pmode;
238 for (int i = 0; i < 4; i++) {
239 pmode = (paste_mode) i;
240 wxString pmodestr = wxString(GetPasteMode(),wxConvLocal); // uses pmode
241
242 wxBitmap modemap(modewd, modeht, 32);
243 wxMemoryDC dc;
244 dc.SelectObject(modemap);
245
246 wxRect r(0, 0, modewd, modeht);
247 wxBrush brush(*wxWHITE);
248 FillRect(dc, r, brush);
249
250 dc.SetFont(*statusptr->GetStatusFont());
251 dc.SetBackgroundMode(wxSOLID);
252 dc.SetTextBackground(*wxWHITE);
253 dc.SetTextForeground(*wxBLACK);
254 dc.SetPen(*wxBLACK);
255
256 int textwd, textht;
257 dc.GetTextExtent(pmodestr, &textwd, &textht);
258 textwd += 4;
259 dc.DrawText(pmodestr, 2, modeht - statusptr->GetTextAscent() - 4);
260
261 dc.SelectObject(wxNullBitmap);
262
263 // now convert modemap data into RGBA data suitable for passing into DrawRGBAData
264 modedata[i] = (unsigned char*) malloc(modewd * modeht * 4);
265 if (modedata[i]) {
266 int j = 0;
267 unsigned char* m = modedata[i];
268 wxAlphaPixelData data(modemap);
269 if (data) {
270 wxAlphaPixelData::Iterator p(data);
271 for (int y = 0; y < modeht; y++) {
272 wxAlphaPixelData::Iterator rowstart = p;
273 for (int x = 0; x < modewd; x++) {
274 if (x > textwd) {
275 m[j++] = 0;
276 m[j++] = 0;
277 m[j++] = 0;
278 m[j++] = 0;
279 } else {
280 m[j++] = p.Red();
281 m[j++] = p.Green();
282 m[j++] = p.Blue();
283 m[j++] = 255;
284 }
285 p++;
286 }
287 p = rowstart;
288 p.OffsetY(data, 1);
289 }
290 }
291 }
292 }
293 pmode = savemode;
294 }
295
296 // -----------------------------------------------------------------------------
297
DestroyDrawingData()298 void DestroyDrawingData()
299 {
300 if (cellatlas) free(cellatlas);
301 if (cellatlasPOT) {
302 free(cellatlasPOT);
303 cellatlasPOT = NULL;
304 }
305 if (itemgrid) {
306 free(itemgrid);
307 itemgrid = NULL;
308 }
309 if (tilebuffer) {
310 free(tilebuffer);
311 tilebuffer = NULL;
312 }
313 if (ctrlsbitmap) free(ctrlsbitmap);
314 if (darkbutt) free(darkbutt);
315 for (int i = 0; i < 4; i++) {
316 if (modedata[i]) free(modedata[i]);
317 }
318 }
319
320 // -----------------------------------------------------------------------------
321
GetTexture(unsigned char * data,int * w,int * h,GLuint * texturename,float * scalew,float * scaleh)322 unsigned char* GetTexture(unsigned char *data, int *w, int *h, GLuint *texturename, float *scalew, float *scaleh)
323 {
324 // enable textures
325 EnableTextures();
326
327 // create the texture name once
328 if (*texturename == 0) glGenTextures(1, texturename);
329 glBindTexture(GL_TEXTURE_2D, *texturename);
330
331 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
332 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
333
334 // initially assume texture width and height same as data
335 int texturew = *w;
336 int textureh = *h;
337 unsigned char* pow2data = NULL;
338
339 // set scale to 1 if specified
340 if (scalew) *scalew = 1.0;
341 if (scaleh) *scaleh = 1.0;
342
343 // OpenGL versions earlier than 2.0 require textures to be a power of 2 in size (POT)
344 if (glMajor < 2) {
345 // check if width is a POT
346 if (texturew & (texturew - 1)) {
347 texturew = 1;
348 while (texturew < *w) texturew <<= 1;
349 }
350
351 // check if height is a POT
352 if (textureh & (textureh - 1)) {
353 textureh = 1;
354 while (textureh < *h) textureh <<= 1;
355 }
356
357 // check if texture size is now different than the data size
358 if (*w != texturew || *h != textureh) {
359 // allocate memory for POT texture using calloc so pixels outside supplied data will be transparent
360 pow2data = (unsigned char*)calloc(texturew * textureh, 4);
361 if (!pow2data) Fatal(_("Cound not allocate POT buffer!"));
362
363 // copy the data to the top left of the POT texture
364 unsigned char* srcptr = data;
365 unsigned char* dstptr = pow2data;
366 for (int y = 0; y < *h; y++) {
367 memcpy(dstptr, srcptr, *w * 4);
368 srcptr += *w * 4;
369 dstptr += texturew * 4;
370 }
371
372 // compute the width and height scale factors for the potentially resized texture
373 if (scalew) *scalew = (float)*w / texturew;
374 if (scaleh) *scaleh = (float)*h / textureh;
375
376 // return the new size
377 *w = texturew;
378 *h = textureh;
379
380 // use the POT buffer for the texture
381 data = pow2data;
382 }
383 }
384
385 // create the texture
386 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texturew, textureh, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
387
388
389 // return the POT buffer (or NULL if not needed/allocated)
390 return pow2data;
391 }
392
393 // -----------------------------------------------------------------------------
394
DrawRGBAData(unsigned char * rgbadata,int x,int y,int w,int h)395 void DrawRGBAData(unsigned char* rgbadata, int x, int y, int w, int h)
396 {
397 // check if data fits in single texture
398 if (w <= glMaxTextureSize && h <= glMaxTextureSize) {
399 // convert texture to POT if needed
400 int texturew = w;
401 int textureh = h;
402 unsigned char* pow2data = GetTexture(rgbadata, &texturew, &textureh, &rgbatexture, NULL, NULL);
403
404 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // avoids edge effects when scaling
405 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // ditto
406 glTexCoordPointer(2, GL_SHORT, 0, texture_coordinates);
407
408 GLfloat vertices[] = {
409 (float)x, (float)y,
410 (float)x + texturew, (float)y,
411 (float)x, (float)y + textureh,
412 (float)x + texturew, (float)y + textureh,
413 };
414 glVertexPointer(2, GL_FLOAT, 0, vertices);
415
416 // draw the texture
417 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
418
419 // free the POT buffer if allocated
420 if (pow2data) {
421 free(pow2data);
422 }
423 }
424 else {
425 // use the maximum texture size as the tile size
426 int tilesize = glMaxTextureSize;
427
428 // compute number of tiles in the x and y direction
429 int tilex = ((w - 1) / tilesize) + 1;
430 int tiley = ((h - 1) / tilesize) + 1;
431
432 // create the buffer for the tile once (this will be a power of 2)
433 if (tilebuffer == 0) tilebuffer = (unsigned char*)calloc(tilesize * tilesize, 4);
434 if (!tilebuffer) Fatal(_("Could not allocate tile buffer!"));
435
436 // enable textures
437 EnableTextures();
438
439 // create the texture name and texture once
440 if (tiletexture == 0) {
441 // generate the texture name
442 glGenTextures(1, &tiletexture);
443
444 // make the named texture current
445 glBindTexture(GL_TEXTURE_2D, tiletexture);
446
447 // setup the texture paramters
448 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
449 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
450 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // avoids edge effects when scaling
451 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // ditto
452
453 // create the texture and have OpenGL allocate the memory
454 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tilesize, tilesize, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
455 }
456 else {
457 // make the named texture current
458 glBindTexture(GL_TEXTURE_2D, tiletexture);
459 }
460
461 // draw each row
462 for (int iy = 0; iy < tiley; iy++) {
463 // compute the tile height for tiles in this row
464 int tileh = h - (iy * tilesize);
465 if (tileh > tilesize) tileh = tilesize;
466
467 // draw each tile in the row
468 for (int ix = 0; ix < tilex ; ix++) {
469 // compute the tile width
470 int tilew = w - (ix * tilesize);
471 if (tilew > tilesize) tilew = tilesize;
472
473 // copy the relevant data top left into the texture buffer
474 // no need to clear unused portion since it isn't rendered below
475 unsigned char* srcptr = rgbadata + (ix * tilesize * 4) + (iy * tilesize * w * 4);
476 unsigned char* dstptr = tilebuffer;
477
478 for (int r = 0; r < tileh; r++) {
479 // copy data
480 memcpy(dstptr, srcptr, tilew * 4);
481
482 // next row
483 srcptr += w * 4;
484 dstptr += tilew * 4;
485 }
486
487 // update the texture with the tile data
488 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tilew, tileh, GL_RGBA, GL_UNSIGNED_BYTE, tilebuffer);
489
490 // set the vertices for the texture position
491 GLfloat vertices[] = {
492 (float)(x + (ix * tilesize)), (float)(y + (iy * tilesize)),
493 (float)(x + (ix * tilesize) + tilew), (float)(y + (iy * tilesize)),
494 (float)(x + (ix * tilesize)), (float)(y + (iy * tilesize) + tileh),
495 (float)(x + (ix * tilesize) + tilew), (float)(y + (iy * tilesize) + tileh),
496 };
497 glVertexPointer(2, GL_FLOAT, 0, vertices);
498
499 // set the part of the texture to draw
500 GLfloat xscale = (double)tilew / tilesize;
501 GLfloat yscale = (double)tileh / tilesize;
502 GLfloat coordinates[] = { 0, 0, xscale, 0, 0, yscale, xscale, yscale };
503 glTexCoordPointer(2, GL_FLOAT, 0, coordinates);
504
505 // draw the tile
506 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
507 }
508 }
509 }
510 }
511
512 // -----------------------------------------------------------------------------
513
Create16x16Grid(unsigned char * itemrow,int itemsize,int numitems)514 void Create16x16Grid(unsigned char* itemrow, int itemsize, int numitems) {
515 // item width in bytes
516 int itemwidth = itemsize * 4;
517
518 // source pixel row size in bytes
519 int sourcerow = itemwidth * numitems;
520
521 // destination pixel row size in bytes
522 int destrow = itemgridwidth * 4;
523
524 // destination item row size in bytes
525 int destitemrow = destrow * itemsize;
526
527 // allocate the 16x16 item grid if not already allocated
528 if (!itemgrid) {
529 // maximum item size is 32x32 RGBA pixels
530 itemgrid = (unsigned char*)malloc(itemgridrows * itemgridcols * 4 * 32 * 32);
531 }
532
533 // copy the items into the 16x16 buffer
534 for (int i = 0; i < numitems; i++) {
535 // get the source item
536 unsigned char* source = itemrow + i * itemwidth;
537
538 // get the destination position
539 unsigned char* dest = itemgrid + (i / itemgridcols) * destitemrow + (i & (itemgridcols - 1)) * itemwidth;
540
541 // copy item into destination
542 for (int y = 0; y < itemsize; y++) {
543 memcpy(dest, source, itemwidth);
544 source += sourcerow;
545 dest += destrow;
546 }
547 }
548 }
549
550 // -----------------------------------------------------------------------------
551
LoadIconAtlas(int iconsize,int numicons)552 static void LoadIconAtlas(int iconsize, int numicons)
553 {
554 // convert row to 2D grid of icons
555 Create16x16Grid(iconatlas, iconsize, numicons);
556
557 // enable textures
558 EnableTextures();
559
560 // create the texture name once
561 if (icontexture == 0) glGenTextures(1, &icontexture);
562 glBindTexture(GL_TEXTURE_2D, icontexture);
563 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
564 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
565
566 // put the icons in the texture
567 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, itemgridwidth, itemgridheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, itemgrid);
568 }
569
570 // -----------------------------------------------------------------------------
571
DrawOneIcon(wxDC & dc,int x,int y,wxBitmap * icon,unsigned char deadr,unsigned char deadg,unsigned char deadb,unsigned char liver,unsigned char liveg,unsigned char liveb,bool multicolor)572 void DrawOneIcon(wxDC& dc, int x, int y, wxBitmap* icon,
573 unsigned char deadr, unsigned char deadg, unsigned char deadb,
574 unsigned char liver, unsigned char liveg, unsigned char liveb,
575 bool multicolor)
576 {
577 // draw a single icon (either multi-color or grayscale) outside the viewport,
578 // so we don't use OpenGL calls in here
579
580 int wd = icon->GetWidth();
581 int ht = icon->GetHeight();
582 wxBitmap pixmap(wd, ht, 32);
583
584 wxAlphaPixelData pxldata(pixmap);
585 if (pxldata) {
586 #if defined(__WXGTK__) && !wxCHECK_VERSION(2,9,0)
587 pxldata.UseAlpha();
588 #endif
589 wxAlphaPixelData::Iterator p(pxldata);
590 wxAlphaPixelData icondata(*icon);
591 if (icondata) {
592 wxAlphaPixelData::Iterator iconpxl(icondata);
593 for (int i = 0; i < ht; i++) {
594 wxAlphaPixelData::Iterator pixmaprow = p;
595 wxAlphaPixelData::Iterator iconrow = iconpxl;
596 for (int j = 0; j < wd; j++) {
597 if (iconpxl.Red() || iconpxl.Green() || iconpxl.Blue()) {
598 if (multicolor) {
599 // use non-black pixel in multi-colored icon
600 if (swapcolors) {
601 p.Red() = 255 - iconpxl.Red();
602 p.Green() = 255 - iconpxl.Green();
603 p.Blue() = 255 - iconpxl.Blue();
604 } else {
605 p.Red() = iconpxl.Red();
606 p.Green() = iconpxl.Green();
607 p.Blue() = iconpxl.Blue();
608 }
609 } else {
610 // grayscale icon
611 if (iconpxl.Red() == 255) {
612 // replace white pixel with live cell color
613 p.Red() = liver;
614 p.Green() = liveg;
615 p.Blue() = liveb;
616 } else {
617 // replace gray pixel with appropriate shade between
618 // live and dead cell colors
619 float frac = (float)(iconpxl.Red()) / 255.0;
620 p.Red() = (int)(deadr + frac * (liver - deadr) + 0.5);
621 p.Green() = (int)(deadg + frac * (liveg - deadg) + 0.5);
622 p.Blue() = (int)(deadb + frac * (liveb - deadb) + 0.5);
623 }
624 }
625 } else {
626 // replace black pixel with dead cell color
627 p.Red() = deadr;
628 p.Green() = deadg;
629 p.Blue() = deadb;
630 }
631 #if defined(__WXGTK__) || wxCHECK_VERSION(2,9,0)
632 p.Alpha() = 255;
633 #endif
634 p++;
635 iconpxl++;
636 }
637 // move to next row of pixmap
638 p = pixmaprow;
639 p.OffsetY(pxldata, 1);
640 // move to next row of icon bitmap
641 iconpxl = iconrow;
642 iconpxl.OffsetY(icondata, 1);
643 }
644 }
645 }
646 dc.DrawBitmap(pixmap, x, y);
647 }
648
649 // -----------------------------------------------------------------------------
650
ChangeCellAtlas(int cellsize,int numcells,unsigned char alpha,bool borders)651 static bool ChangeCellAtlas(int cellsize, int numcells, unsigned char alpha, bool borders)
652 {
653 if (numcells != prevnum) return true;
654 if (cellsize != prevsize) return true;
655 if (alpha != prevalpha) return true;
656 if (borders != prevborders) return true;
657
658 for (int state = 1; state <= numcells; state++) {
659 if (currlayer->cellr[state] != prevr[state]) return true;
660 if (currlayer->cellg[state] != prevg[state]) return true;
661 if (currlayer->cellb[state] != prevb[state]) return true;
662 }
663
664 return false; // no need to change cellatlas
665 }
666
667 // -----------------------------------------------------------------------------
668
LoadCellAtlas(int cellsize,int numcells,unsigned char alpha)669 static void LoadCellAtlas(int cellsize, int numcells, unsigned char alpha)
670 {
671 // cellatlas might need to be (re)created
672 if (ChangeCellAtlas(cellsize, numcells, alpha, cellborders)) {
673 prevnum = numcells;
674 prevsize = cellsize;
675 prevalpha = alpha;
676 prevborders = cellborders;
677 for (int state = 1; state <= numcells; state++) {
678 prevr[state] = currlayer->cellr[state];
679 prevg[state] = currlayer->cellg[state];
680 prevb[state] = currlayer->cellb[state];
681 }
682
683 if (cellatlas) free(cellatlas);
684
685 // allocate enough memory for texture atlas to store RGBA pixels for a row of cells
686 // (note that we use calloc so all alpha bytes are initially 0)
687 int rowbytes = numcells * cellsize * 4;
688 cellatlas = (unsigned char*) calloc(rowbytes * cellsize, 1);
689
690 if (cellatlas) {
691 // determine whether zoomed cells have a border
692 bool haveborder = (cellborders && cellsize > 2);
693
694 // set pixels in top row
695 int tpos = 0;
696 for (int state = 1; state <= numcells; state++) {
697 unsigned char r = currlayer->cellr[state];
698 unsigned char g = currlayer->cellg[state];
699 unsigned char b = currlayer->cellb[state];
700
701 // if the cell size is > 2 and cellborders are on then leave a 1 pixel gap at right
702 // and bottom edge of each cell
703 int cellwd = cellsize - (haveborder ? 1 : 0);
704
705 for (int i = 0; i < cellwd; i++) {
706 cellatlas[tpos++] = r;
707 cellatlas[tpos++] = g;
708 cellatlas[tpos++] = b;
709 cellatlas[tpos++] = alpha;
710 }
711 if (haveborder) tpos += 4; // skip transparent pixel at right edge of cell
712 }
713 // copy top row to remaining rows
714 int remrows = cellsize - (haveborder ? 2 : 1);
715 for (int i = 1; i <= remrows; i++) {
716 memcpy(&cellatlas[i * rowbytes], cellatlas, rowbytes);
717 }
718 }
719 }
720
721 // convert row to 2D grid of icons
722 Create16x16Grid(cellatlas, cellsize, numcells);
723
724 // enable textures
725 EnableTextures();
726
727 // create the texture name once
728 if (celltexture == 0) glGenTextures(1, &celltexture);
729 glBindTexture(GL_TEXTURE_2D, celltexture);
730 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
731 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
732
733 // put the cells in the texture
734 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, itemgridwidth, itemgridheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, itemgrid);
735 }
736
737 // -----------------------------------------------------------------------------
738
DrawCells(unsigned char * statedata,int x,int y,int w,int h,int pmscale,int stride,int numstates,GLuint texture)739 void DrawCells(unsigned char* statedata, int x, int y, int w, int h, int pmscale, int stride, int numstates, GLuint texture)
740 {
741 // called from golly_render::pixblit to draw cells magnified by pmscale (2, 4, ... 2^MAX_MAG)
742 // uses the supplied texture to draw solid cells or icons
743 //
744 // one cell = 2 triangles = 6 vertices = 12 coordinates for GL_TRIANGLES
745
746 int v = 0;
747 int t = 0;
748 int max = magbuffersize / sizeof(*magvertexbuffer);
749
750 float tscale = (float)pmscale / itemgridwidth;
751
752 EnableTextures();
753 glBindTexture(GL_TEXTURE_2D, texture);
754
755 for (int row = 0; row < h; row++) {
756 for (int col = 0; col < w; col++) {
757 unsigned char state = statedata[row*stride + col];
758 if (state > 0) {
759 // add cell to buffer
760 int xpos = x + col * pmscale;
761 int ypos = y + row * pmscale;
762
763 magvertexbuffer[v++] = xpos;
764 magvertexbuffer[v++] = ypos;
765 magvertexbuffer[v++] = xpos + pmscale;
766 magvertexbuffer[v++] = ypos;
767 magvertexbuffer[v++] = xpos;
768 magvertexbuffer[v++] = ypos + pmscale;
769
770 magvertexbuffer[v++] = xpos + pmscale;
771 magvertexbuffer[v++] = ypos;
772 magvertexbuffer[v++] = xpos;
773 magvertexbuffer[v++] = ypos + pmscale;
774 magvertexbuffer[v++] = xpos + pmscale;
775 magvertexbuffer[v++] = ypos + pmscale;
776
777 int tx = (state - 1) & (itemgridcols - 1);
778 int ty = (state - 1) / itemgridcols;
779
780 magtextcoordbuffer[t++] = (float)tx * tscale;
781 magtextcoordbuffer[t++] = (float)ty * tscale;
782 magtextcoordbuffer[t++] = (float)(tx + 1) * tscale;
783 magtextcoordbuffer[t++] = (float)ty * tscale;
784 magtextcoordbuffer[t++] = (float)tx * tscale;
785 magtextcoordbuffer[t++] = (float)(ty + 1) * tscale;
786
787 magtextcoordbuffer[t++] = (float)(tx + 1) * tscale;
788 magtextcoordbuffer[t++] = (float)ty * tscale;
789 magtextcoordbuffer[t++] = (float)tx * tscale;
790 magtextcoordbuffer[t++] = (float)(ty + 1) * tscale;
791 magtextcoordbuffer[t++] = (float)(tx + 1) * tscale;
792 magtextcoordbuffer[t++] = (float)(ty + 1) * tscale;
793
794 // check if buffer is full
795 if (v == max) {
796 // draw cells in buffer
797 glTexCoordPointer(2, GL_FLOAT, 0, magtextcoordbuffer);
798 glVertexPointer(2, GL_FLOAT, 0, magvertexbuffer);
799 glDrawArrays(GL_TRIANGLES, 0, v / 2);
800
801 // reset buffer
802 v = 0;
803 t = 0;
804 }
805 }
806 }
807 }
808
809 // draw any remaining cells in buffer
810 if (v > 0) {
811 glTexCoordPointer(2, GL_FLOAT, 0, magtextcoordbuffer);
812 glVertexPointer(2, GL_FLOAT, 0, magvertexbuffer);
813 glDrawArrays(GL_TRIANGLES, 0, v / 2);
814 }
815 }
816
817 // -----------------------------------------------------------------------------
818
819 class golly_render : public liferender
820 {
821 public:
golly_render()822 golly_render() {}
~golly_render()823 virtual ~golly_render() {}
824 virtual void pixblit(int x, int y, int w, int h, unsigned char* pm, int pmscale);
825 virtual void getcolors(unsigned char** r, unsigned char** g, unsigned char** b,
826 unsigned char* deada, unsigned char* livea);
827 };
828
829 golly_render renderer; // create instance
830
831 // -----------------------------------------------------------------------------
832
pixblit(int x,int y,int w,int h,unsigned char * pmdata,int pmscale)833 void golly_render::pixblit(int x, int y, int w, int h, unsigned char* pmdata, int pmscale)
834 {
835 if (x >= currwd || y >= currht) return;
836 if (x + w <= 0 || y + h <= 0) return;
837
838 // stride is the horizontal pixel width of the image data
839 int stride = w/pmscale;
840
841 // clip data outside viewport
842 if (pmscale > 1) {
843 // pmdata contains 1 byte per `pmscale' pixels, so we must be careful
844 // and adjust x, y, w and h by multiples of `pmscale' only.
845 if (x < 0) {
846 int dx = -x/pmscale*pmscale;
847 pmdata += dx/pmscale;
848 w -= dx;
849 x += dx;
850 }
851 if (y < 0) {
852 int dy = -y/pmscale*pmscale;
853 pmdata += dy/pmscale*stride;
854 h -= dy;
855 y += dy;
856 }
857 if (x + w >= currwd + pmscale) w = (currwd - x + pmscale - 1)/pmscale*pmscale;
858 if (y + h >= currht + pmscale) h = (currht - y + pmscale - 1)/pmscale*pmscale;
859 }
860
861 if (pmscale == 1) {
862 // draw RGBA pixel data at scale 1:1
863 DrawRGBAData(pmdata, x, y, w, h);
864
865 } else if (showicons && pmscale > 4 && iconatlas) {
866 // draw icons at scales 1:8 and above
867 DrawCells(pmdata, x, y, w/pmscale, h/pmscale, pmscale, stride, currlayer->numicons, icontexture);
868
869 } else {
870 // draw magnified cells, assuming pmdata contains (w/pmscale)*(h/pmscale) bytes
871 // where each byte contains a cell state
872 DrawCells(pmdata, x, y, w/pmscale, h/pmscale, pmscale, stride, currlayer->numicons, celltexture);
873 }
874 }
875
876 // -----------------------------------------------------------------------------
877
getcolors(unsigned char ** r,unsigned char ** g,unsigned char ** b,unsigned char * deada,unsigned char * livea)878 void golly_render::getcolors(unsigned char** r, unsigned char** g, unsigned char** b,
879 unsigned char* deada, unsigned char* livea)
880 {
881 *r = currlayer->cellr;
882 *g = currlayer->cellg;
883 *b = currlayer->cellb;
884 *deada = dead_alpha;
885 *livea = live_alpha;
886 }
887
888 // -----------------------------------------------------------------------------
889
DrawSelection(wxRect & rect,bool active)890 void DrawSelection(wxRect& rect, bool active)
891 {
892 // draw semi-transparent rectangle
893 DisableTextures();
894 if (active) {
895 SetColor(selectrgb->Red(), selectrgb->Green(), selectrgb->Blue(), 128);
896 } else {
897 // use light gray to indicate an inactive selection
898 SetColor(160, 160, 160, 128);
899 }
900 FillRect(rect.x, rect.y, rect.width, rect.height);
901 }
902
903 // -----------------------------------------------------------------------------
904
InitPaste(Layer * player,wxRect & bbox)905 void InitPaste(Layer* player, wxRect& bbox)
906 {
907 // set globals used in DrawPasteImage
908 pastelayer = player;
909 pastebbox = bbox;
910 }
911
912 // -----------------------------------------------------------------------------
913
PixelsToCells(int pixels,int mag)914 int PixelsToCells(int pixels, int mag) {
915 // convert given # of screen pixels to corresponding # of cells
916 if (mag >= 0) {
917 int cellsize = 1 << mag;
918 return (pixels + cellsize - 1) / cellsize;
919 } else {
920 // mag < 0; no need to worry about overflow
921 return pixels << -mag;
922 }
923 }
924
925 // -----------------------------------------------------------------------------
926
DrawPasteImage()927 void DrawPasteImage()
928 {
929 int pastemag = currlayer->view->getmag();
930
931 // note that viewptr->pasterect.width > 0
932 int prectwd = viewptr->pasterect.width;
933 int prectht = viewptr->pasterect.height;
934
935 // calculate size of paste image; we could just set it to pasterect size
936 // but that would be slow and wasteful for large pasterects, so we use
937 // the following code (the only tricky bit is when plocation = Middle)
938 int pastewd = prectwd;
939 int pasteht = prectht;
940
941 wxRect cellbox = pastebbox;
942 if (pastewd > currlayer->view->getwidth() || pasteht > currlayer->view->getheight()) {
943 if (plocation == Middle) {
944 // temporary viewport may need to be TWICE size of current viewport
945 if (pastewd > 2 * currlayer->view->getwidth())
946 pastewd = 2 * currlayer->view->getwidth();
947 if (pasteht > 2 * currlayer->view->getheight())
948 pasteht = 2 * currlayer->view->getheight();
949 if (pastemag > 0) {
950 // make sure pastewd/ht don't have partial cells
951 int cellsize = 1 << pastemag;
952 if ((pastewd + 1) % cellsize > 0)
953 pastewd += cellsize - ((pastewd + 1) % cellsize);
954 if ((pasteht + 1) % cellsize != 0)
955 pasteht += cellsize - ((pasteht + 1) % cellsize);
956 }
957 if (prectwd > pastewd) {
958 // make sure prectwd - pastewd is an even number of *cells*
959 if (pastemag > 0) {
960 int cellsize = 1 << pastemag;
961 int celldiff = (prectwd - pastewd) / cellsize;
962 if (celldiff & 1) pastewd += cellsize;
963 } else {
964 if ((prectwd - pastewd) & 1) pastewd++;
965 }
966 }
967 if (prectht > pasteht) {
968 // make sure prectht - pasteht is an even number of *cells*
969 if (pastemag > 0) {
970 int cellsize = 1 << pastemag;
971 int celldiff = (prectht - pasteht) / cellsize;
972 if (celldiff & 1) pasteht += cellsize;
973 } else {
974 if ((prectht - pasteht) & 1) pasteht++;
975 }
976 }
977 } else {
978 // plocation is at a corner of pasterect so temporary viewport
979 // may need to be size of current viewport
980 if (pastewd > currlayer->view->getwidth())
981 pastewd = currlayer->view->getwidth();
982 if (pasteht > currlayer->view->getheight())
983 pasteht = currlayer->view->getheight();
984 if (pastemag > 0) {
985 // make sure pastewd/ht don't have partial cells
986 int cellsize = 1 << pastemag;
987 int gap = 1; // gap between cells
988 if (pastemag == 1) gap = 0; // no gap at scale 1:2
989 if ((pastewd + gap) % cellsize > 0)
990 pastewd += cellsize - ((pastewd + gap) % cellsize);
991 if ((pasteht + gap) % cellsize != 0)
992 pasteht += cellsize - ((pasteht + gap) % cellsize);
993 }
994 cellbox.width = PixelsToCells(pastewd, pastemag);
995 cellbox.height = PixelsToCells(pasteht, pastemag);
996 if (plocation == TopLeft) {
997 // show top left corner of pasterect
998 cellbox.x = pastebbox.x;
999 cellbox.y = pastebbox.y;
1000 } else if (plocation == TopRight) {
1001 // show top right corner of pasterect
1002 cellbox.x = pastebbox.x + pastebbox.width - cellbox.width;
1003 cellbox.y = pastebbox.y;
1004 } else if (plocation == BottomRight) {
1005 // show bottom right corner of pasterect
1006 cellbox.x = pastebbox.x + pastebbox.width - cellbox.width;
1007 cellbox.y = pastebbox.y + pastebbox.height - cellbox.height;
1008 } else { // plocation == BottomLeft
1009 // show bottom left corner of pasterect
1010 cellbox.x = pastebbox.x;
1011 cellbox.y = pastebbox.y + pastebbox.height - cellbox.height;
1012 }
1013 }
1014 }
1015
1016 wxRect r = viewptr->pasterect;
1017 if (r.width > pastewd || r.height > pasteht) {
1018 // paste image is smaller than pasterect (which can't fit in viewport)
1019 // so shift image depending on plocation
1020 switch (plocation) {
1021 case TopLeft:
1022 // no need to do any shifting
1023 break;
1024 case TopRight:
1025 // shift image to top right corner of pasterect
1026 r.x += r.width - pastewd;
1027 break;
1028 case BottomRight:
1029 // shift image to bottom right corner of pasterect
1030 r.x += r.width - pastewd;
1031 r.y += r.height - pasteht;
1032 break;
1033 case BottomLeft:
1034 // shift image to bottom left corner of pasterect
1035 r.y += r.height - pasteht;
1036 break;
1037 case Middle:
1038 // shift image to middle of pasterect; note that above code
1039 // has ensured (r.width - pastewd) and (r.height - pasteht)
1040 // are an even number of *cells* if pastemag > 0
1041 r.x += (r.width - pastewd) / 2;
1042 r.y += (r.height - pasteht) / 2;
1043 break;
1044 }
1045 }
1046
1047 // set up viewport for drawing paste pattern
1048 pastelayer->view->resize(pastewd, pasteht);
1049 int midx, midy;
1050 if (pastemag > 1) {
1051 // allow for gap between cells
1052 midx = cellbox.x + (cellbox.width - 1) / 2;
1053 midy = cellbox.y + (cellbox.height - 1) / 2;
1054 } else {
1055 midx = cellbox.x + cellbox.width / 2;
1056 midy = cellbox.y + cellbox.height / 2;
1057 }
1058 pastelayer->view->setpositionmag(midx, midy, pastemag);
1059
1060 // temporarily turn off grid lines
1061 bool saveshow = showgridlines;
1062 showgridlines = false;
1063
1064 // dead pixels will be 100% transparent and live pixels 100% opaque
1065 dead_alpha = 0;
1066 live_alpha = 255;
1067
1068 currwd = pastelayer->view->getwidth();
1069 currht = pastelayer->view->getheight();
1070
1071 glTranslatef(r.x, r.y, 0);
1072
1073 // temporarily set currlayer to pastelayer so golly_render routines
1074 // will use the paste pattern's color and icons
1075 Layer* savelayer = currlayer;
1076 currlayer = pastelayer;
1077
1078 if (showicons && pastemag > 2) {
1079 // only show icons at scales 1:8 and above
1080 if (pastemag == 3) {
1081 iconatlas = currlayer->atlas7x7;
1082 LoadIconAtlas(8, currlayer->numicons);
1083 } else if (pastemag == 4) {
1084 iconatlas = currlayer->atlas15x15;
1085 LoadIconAtlas(16, currlayer->numicons);
1086 } else {
1087 iconatlas = currlayer->atlas31x31;
1088 LoadIconAtlas(32, currlayer->numicons);
1089 }
1090 } else if (pastemag > 0) {
1091 LoadCellAtlas(1 << pastemag, currlayer->numicons, 255);
1092 }
1093
1094 if (scalefactor > 1) {
1095 // change scale to 1:1 and increase its size by scalefactor
1096 pastelayer->view->setmag(0);
1097 currwd = currwd * scalefactor;
1098 currht = currht * scalefactor;
1099 pastelayer->view->resize(currwd, currht);
1100
1101 glPushMatrix();
1102 glScalef(1.0/scalefactor, 1.0/scalefactor, 1.0);
1103
1104 pastelayer->algo->draw(*pastelayer->view, renderer);
1105
1106 // restore viewport settings
1107 currwd = currwd / scalefactor;
1108 currht = currht / scalefactor;
1109
1110 // restore OpenGL scale
1111 glPopMatrix();
1112
1113 } else {
1114 // no scaling
1115 pastelayer->algo->draw(*pastelayer->view, renderer);
1116 }
1117
1118 currlayer = savelayer;
1119 showgridlines = saveshow;
1120
1121 glTranslatef(-r.x, -r.y, 0);
1122
1123 // overlay translucent rect to show paste area
1124 DisableTextures();
1125 SetColor(pastergb->Red(), pastergb->Green(), pastergb->Blue(), 64);
1126 r = viewptr->pasterect;
1127 FillRect(r.x, r.y, r.width, r.height);
1128
1129 // show current paste mode
1130 if (r.y > 0 && modedata[(int)pmode]) {
1131 DrawRGBAData(modedata[(int)pmode], r.x, r.y - modeht - 1, modewd, modeht);
1132 }
1133 }
1134
1135 // -----------------------------------------------------------------------------
1136
WhichControl(int x,int y)1137 control_id WhichControl(int x, int y)
1138 {
1139 // determine which button is at x,y in controls bitmap
1140 int col, row;
1141
1142 x -= buttborder;
1143 y -= buttborder;
1144 if (x < 0 || y < 0) return NO_CONTROL;
1145
1146 // allow for vertical gap after first 2 rows
1147 if (y < (buttsize + rowgap)) {
1148 if (y > buttsize) return NO_CONTROL; // in 1st gap
1149 row = 1;
1150 } else if (y < 2*(buttsize + rowgap)) {
1151 if (y > 2*buttsize + rowgap) return NO_CONTROL; // in 2nd gap
1152 row = 2;
1153 } else {
1154 row = 3 + (y - 2*(buttsize + rowgap)) / buttsize;
1155 }
1156
1157 col = 1 + x / buttsize;
1158 if (col < 1 || col > buttsperrow) return NO_CONTROL;
1159 if (row < 1 || row > numbutts/buttsperrow) return NO_CONTROL;
1160
1161 int control = (row - 1) * buttsperrow + col;
1162 return (control_id) control;
1163 }
1164
1165 // -----------------------------------------------------------------------------
1166
DrawControls(wxRect & rect)1167 void DrawControls(wxRect& rect)
1168 {
1169 if (ctrlsbitmap) {
1170 DrawRGBAData(ctrlsbitmap, rect.x, rect.y, controlswd, controlsht);
1171
1172 if (currcontrol > NO_CONTROL && darkbutt) {
1173 // show clicked control
1174 int i = (int)currcontrol - 1;
1175 int x = buttborder + (i % buttsperrow) * buttsize;
1176 int y = buttborder + (i / buttsperrow) * buttsize;
1177
1178 // allow for vertical gap after first 2 rows
1179 if (i < buttsperrow) {
1180 // y is correct
1181 } else if (i < 2*buttsperrow) {
1182 y += rowgap;
1183 } else {
1184 y += 2*rowgap;
1185 }
1186
1187 // draw one darkened button
1188 int p = 0;
1189 for ( int row = 0; row < buttsize; row++ ) {
1190 for ( int col = 0; col < buttsize; col++ ) {
1191 unsigned char alpha = ctrlsbitmap[((row + y) * controlswd + col + x) * 4 + 3];
1192 if (alpha == 0) {
1193 // pixel is transparent
1194 darkbutt[p++] = 0;
1195 darkbutt[p++] = 0;
1196 darkbutt[p++] = 0;
1197 darkbutt[p++] = 0;
1198 } else {
1199 // pixel is part of button so use a very dark gray
1200 darkbutt[p++] = 20;
1201 darkbutt[p++] = 20;
1202 darkbutt[p++] = 20;
1203 darkbutt[p++] = 128; // 50% opaque
1204 }
1205 }
1206 }
1207 DrawRGBAData(darkbutt, rect.x + x, rect.y + y, buttsize, buttsize);
1208 }
1209 }
1210 }
1211
1212 // -----------------------------------------------------------------------------
1213
DrawGridLines(int wd,int ht)1214 void DrawGridLines(int wd, int ht)
1215 {
1216 int cellsize = 1 << currlayer->view->getmag();
1217 int h, v, i, topbold, leftbold;
1218
1219 // compute the vertex buffer size
1220 long neededBufferSize = ((((wd + ht) * 4) / cellsize) + 1) * sizeof(*vertexbuffer);
1221
1222 // check if the current buffer is big enough
1223 if (neededBufferSize > vertexsize) {
1224 // grow the buffer to the required size
1225 vertexbuffer = (GLfloat *)realloc(vertexbuffer, neededBufferSize);
1226 vertexsize = neededBufferSize;
1227 }
1228
1229 // get the vertex buffer
1230 GLfloat *points = vertexbuffer;
1231 int numPoints = 0;
1232
1233 if (showboldlines) {
1234 // ensure that origin cell stays next to bold lines;
1235 // ie. bold lines scroll when pattern is scrolled
1236 pair<bigint, bigint> lefttop = currlayer->view->at(0, 0);
1237 leftbold = lefttop.first.mod_smallint(boldspacing);
1238 topbold = lefttop.second.mod_smallint(boldspacing);
1239 if (currlayer->originx != bigint::zero) {
1240 leftbold -= currlayer->originx.mod_smallint(boldspacing);
1241 }
1242 if (currlayer->originy != bigint::zero) {
1243 topbold -= currlayer->originy.mod_smallint(boldspacing);
1244 }
1245 if (mathcoords) topbold--; // show origin cell above bold line
1246 } else {
1247 // avoid gcc warning
1248 topbold = leftbold = 0;
1249 }
1250
1251 DisableTextures();
1252 glLineWidth(1.0);
1253
1254 // set the stroke color depending on current bg color
1255 int r = currlayer->cellr[0];
1256 int g = currlayer->cellg[0];
1257 int b = currlayer->cellb[0];
1258 int gray = (int) ((r + g + b) / 3.0);
1259 if (gray > 127) {
1260 // darker lines
1261 SetColor(r > 32 ? r - 32 : 0,
1262 g > 32 ? g - 32 : 0,
1263 b > 32 ? b - 32 : 0, 255);
1264 } else {
1265 // lighter lines
1266 SetColor(r + 32 < 256 ? r + 32 : 255,
1267 g + 32 < 256 ? g + 32 : 255,
1268 b + 32 < 256 ? b + 32 : 255, 255);
1269 }
1270
1271 // draw all plain lines first;
1272 // note that we need to add/subtract 0.5 from coordinates to avoid uneven spacing
1273
1274 i = showboldlines ? topbold : 1;
1275 v = 0;
1276 while (true) {
1277 v += cellsize;
1278 if (v > ht) break;
1279 if (showboldlines) i++;
1280 if (i % boldspacing != 0) {
1281 points[numPoints++] = -0.5f;
1282 points[numPoints++] = (float)v - 0.5f;
1283 points[numPoints++] = (float)wd + 0.5f;
1284 points[numPoints++] = (float)v - 0.5f;
1285 }
1286 }
1287
1288 i = showboldlines ? leftbold : 1;
1289 h = 0;
1290 while (true) {
1291 h += cellsize;
1292 if (h > wd) break;
1293 if (showboldlines) i++;
1294 if (i % boldspacing != 0) {
1295 points[numPoints++] = (float)h - 0.5f;
1296 points[numPoints++] = -0.5f;
1297 points[numPoints++] = (float)h - 0.5f;
1298 points[numPoints++] = (float)ht + 0.5;
1299 }
1300 }
1301
1302 // draw all plain lines
1303 glVertexPointer(2, GL_FLOAT, 0, points);
1304 glDrawArrays(GL_LINES, 0, numPoints / 2);
1305
1306 if (showboldlines) {
1307 // draw bold lines in slightly darker/lighter color
1308 numPoints = 0;
1309 if (gray > 127) {
1310 // darker lines
1311 SetColor(r > 64 ? r - 64 : 0,
1312 g > 64 ? g - 64 : 0,
1313 b > 64 ? b - 64 : 0, 255);
1314 } else {
1315 // lighter lines
1316 SetColor(r + 64 < 256 ? r + 64 : 255,
1317 g + 64 < 256 ? g + 64 : 255,
1318 b + 64 < 256 ? b + 64 : 255, 255);
1319 }
1320
1321 i = topbold;
1322 v = 0;
1323 while (true) {
1324 v += cellsize;
1325 if (v > ht) break;
1326 i++;
1327 if (i % boldspacing == 0) {
1328 points[numPoints++] = -0.5f;
1329 points[numPoints++] = (float)v - 0.5f;
1330 points[numPoints++] = (float)wd + 0.5f;
1331 points[numPoints++] = (float)v - 0.5f;
1332 }
1333 }
1334
1335 i = leftbold;
1336 h = 0;
1337 while (true) {
1338 h += cellsize;
1339 if (h > wd) break;
1340 i++;
1341 if (i % boldspacing == 0) {
1342 points[numPoints++] = (float)h - 0.5f;
1343 points[numPoints++] = -0.5f;
1344 points[numPoints++] = (float)h - 0.5f;
1345 points[numPoints++] = (float)ht + 0.5f;
1346 }
1347 }
1348
1349 // draw all bold lines
1350 glVertexPointer(2, GL_FLOAT, 0, points);
1351 glDrawArrays(GL_LINES, 0, numPoints / 2);
1352 }
1353 }
1354
1355 // -----------------------------------------------------------------------------
1356
DrawGridBorder(int wd,int ht)1357 void DrawGridBorder(int wd, int ht)
1358 {
1359 // universe is bounded so draw any visible border regions
1360 pair<int,int> ltpxl = currlayer->view->screenPosOf(currlayer->algo->gridleft,
1361 currlayer->algo->gridtop,
1362 currlayer->algo);
1363 pair<int,int> rbpxl = currlayer->view->screenPosOf(currlayer->algo->gridright,
1364 currlayer->algo->gridbottom,
1365 currlayer->algo);
1366 int left = ltpxl.first;
1367 int top = ltpxl.second;
1368 int right = rbpxl.first;
1369 int bottom = rbpxl.second;
1370 if (currlayer->algo->gridwd == 0) {
1371 left = 0;
1372 right = wd-1;
1373 }
1374 if (currlayer->algo->gridht == 0) {
1375 top = 0;
1376 bottom = ht-1;
1377 }
1378
1379 // note that right and/or bottom might be INT_MAX so avoid adding to cause overflow
1380 if (currlayer->view->getmag() > 0) {
1381 // move to bottom right pixel of cell at gridright,gridbottom
1382 if (right < wd) right += (1 << currlayer->view->getmag()) - 1;
1383 if (bottom < ht) bottom += (1 << currlayer->view->getmag()) - 1;
1384 if (currlayer->view->getmag() == 1) {
1385 // there are no gaps at scale 1:2
1386 if (right < wd) right++;
1387 if (bottom < ht) bottom++;
1388 }
1389 } else {
1390 if (right < wd) right++;
1391 if (bottom < ht) bottom++;
1392 }
1393
1394 if (left < 0 && right >= wd && top < 0 && bottom >= ht) {
1395 // border isn't visible (ie. grid fills viewport)
1396 return;
1397 }
1398
1399 DisableTextures();
1400 SetColor(borderrgb->Red(), borderrgb->Green(), borderrgb->Blue(), 255);
1401
1402 if (left >= wd || right < 0 || top >= ht || bottom < 0) {
1403 // no part of grid is visible so fill viewport with border
1404 FillRect(0, 0, wd, ht);
1405 return;
1406 }
1407
1408 // avoid drawing overlapping rects below
1409 int rtop = 0;
1410 int rheight = ht;
1411
1412 if (currlayer->algo->gridht > 0) {
1413 if (top > 0) {
1414 // top border is visible
1415 FillRect(0, 0, wd, top);
1416 // reduce size of rect below
1417 rtop = top;
1418 rheight -= top;
1419 }
1420 if (bottom < ht) {
1421 // bottom border is visible
1422 FillRect(0, bottom, wd, ht - bottom);
1423 // reduce size of rect below
1424 rheight -= ht - bottom;
1425 }
1426 }
1427
1428 if (currlayer->algo->gridwd > 0) {
1429 if (left > 0) {
1430 // left border is visible
1431 FillRect(0, rtop, left, rheight);
1432 }
1433 if (right < wd) {
1434 // right border is visible
1435 FillRect(right, rtop, wd - right, rheight);
1436 }
1437 }
1438 }
1439
1440 // -----------------------------------------------------------------------------
1441
ReplaceAlpha(int iconsize,int numicons,unsigned char oldalpha,unsigned char newalpha)1442 void ReplaceAlpha(int iconsize, int numicons, unsigned char oldalpha, unsigned char newalpha)
1443 {
1444 if (iconatlas) {
1445 int numbytes = numicons * iconsize * iconsize * 4;
1446 int i = 3;
1447 while (i < numbytes) {
1448 if (iconatlas[i] == oldalpha) iconatlas[i] = newalpha;
1449 i += 4;
1450 }
1451 }
1452 }
1453
1454 // -----------------------------------------------------------------------------
1455
DrawOneLayer()1456 void DrawOneLayer()
1457 {
1458 // dead pixels will be 100% transparent, and live pixels will use opacity setting
1459 dead_alpha = 0;
1460 live_alpha = int(2.55 * opacity);
1461
1462 int iconsize = 0;
1463 int currmag = currlayer->view->getmag();
1464
1465 if (showicons && currmag > 2) {
1466 // only show icons at scales 1:8 and above
1467 if (currmag == 3) {
1468 iconatlas = currlayer->atlas7x7;
1469 iconsize = 8;
1470 } else if (currmag == 4) {
1471 iconatlas = currlayer->atlas15x15;
1472 iconsize = 16;
1473 } else {
1474 iconatlas = currlayer->atlas31x31;
1475 iconsize = 32;
1476 }
1477
1478 // DANGER: we're making assumptions about what CreateIconAtlas does in wxlayer.cpp
1479
1480 if (live_alpha < 255) {
1481 // this is ugly, but we need to replace the alpha 255 values in iconatlas
1482 // with live_alpha so that LoadIconAtlas will load translucent icons
1483 ReplaceAlpha(iconsize, currlayer->numicons, 255, live_alpha);
1484 }
1485
1486 // load iconatlas for use by DrawCells
1487 LoadIconAtlas(iconsize, currlayer->numicons);
1488 } else if (currmag > 0) {
1489 LoadCellAtlas(1 << currmag, currlayer->numicons, live_alpha);
1490 }
1491
1492 if (scalefactor > 1) {
1493 // temporarily change viewport scale to 1:1 and increase its size by scalefactor
1494 currlayer->view->setmag(0);
1495 currwd = currwd * scalefactor;
1496 currht = currht * scalefactor;
1497 currlayer->view->resize(currwd, currht);
1498
1499 glPushMatrix();
1500 glScalef(1.0/scalefactor, 1.0/scalefactor, 1.0);
1501
1502 currlayer->algo->draw(*currlayer->view, renderer);
1503
1504 // restore viewport settings
1505 currwd = currwd / scalefactor;
1506 currht = currht / scalefactor;
1507 currlayer->view->resize(currwd, currht);
1508 currlayer->view->setmag(currmag);
1509
1510 // restore OpenGL scale
1511 glPopMatrix();
1512
1513 } else {
1514 currlayer->algo->draw(*currlayer->view, renderer);
1515 }
1516
1517 if (showicons && currmag > 2 && live_alpha < 255) {
1518 // restore alpha values in iconatlas
1519 ReplaceAlpha(iconsize, currlayer->numicons, live_alpha, 255);
1520 }
1521 }
1522
1523 // -----------------------------------------------------------------------------
1524
DrawStackedLayers()1525 void DrawStackedLayers()
1526 {
1527 // temporarily turn off grid lines
1528 bool saveshow = showgridlines;
1529 showgridlines = false;
1530
1531 // overlay patterns in layers 1..numlayers-1
1532 for ( int i = 1; i < numlayers; i++ ) {
1533 Layer* savelayer = currlayer;
1534 currlayer = GetLayer(i);
1535
1536 // use real current layer's viewport
1537 viewport* saveview = currlayer->view;
1538 currlayer->view = savelayer->view;
1539
1540 if ( !currlayer->algo->isEmpty() ) {
1541 DrawOneLayer();
1542 }
1543
1544 // draw this layer's selection if necessary
1545 wxRect r;
1546 if ( currlayer->currsel.Visible(&r) ) {
1547 DrawSelection(r, i == currindex);
1548 }
1549
1550 // restore viewport and currlayer
1551 currlayer->view = saveview;
1552 currlayer = savelayer;
1553 }
1554
1555 showgridlines = saveshow;
1556 }
1557
1558 // -----------------------------------------------------------------------------
1559
DrawTileFrame(wxRect & trect,int wd)1560 void DrawTileFrame(wxRect& trect, int wd)
1561 {
1562 trect.Inflate(wd);
1563 wxRect r = trect;
1564
1565 r.height = wd;
1566 FillRect(r.x, r.y, r.width, r.height); // top edge
1567
1568 r.y += trect.height - wd;
1569 FillRect(r.x, r.y, r.width, r.height); // bottom edge
1570
1571 r = trect;
1572 r.width = wd;
1573 FillRect(r.x, r.y, r.width, r.height); // left edge
1574
1575 r.x += trect.width - wd;
1576 FillRect(r.x, r.y, r.width, r.height); // right edge
1577 }
1578
1579 // -----------------------------------------------------------------------------
1580
DrawTileBorders()1581 void DrawTileBorders()
1582 {
1583 if (tileborder <= 0) return; // no borders
1584
1585 // draw tile borders in bigview window
1586 int wd, ht;
1587 bigview->GetClientSize(&wd, &ht);
1588 if (wd < 1 || ht < 1) return;
1589
1590 // most people will choose either a very light or very dark color for dead cells,
1591 // so draw mid gray border around non-current tiles
1592 DisableTextures();
1593 SetColor(144, 144, 144, 255);
1594 wxRect trect;
1595 for ( int i = 0; i < numlayers; i++ ) {
1596 if (i != currindex) {
1597 trect = GetLayer(i)->tilerect;
1598 DrawTileFrame(trect, tileborder);
1599 }
1600 }
1601
1602 // draw green border around current tile
1603 trect = GetLayer(currindex)->tilerect;
1604 SetColor(0, 255, 0, 255);
1605 DrawTileFrame(trect, tileborder);
1606 }
1607
1608 // -----------------------------------------------------------------------------
1609
DrawOverlay()1610 void DrawOverlay()
1611 {
1612 unsigned char* overlaydata = curroverlay->GetOverlayData();
1613
1614 if (overlaydata) {
1615 int wd = curroverlay->GetOverlayWidth();
1616 int ht = curroverlay->GetOverlayHeight();
1617 int x = 0;
1618 int y = 0;
1619 switch (curroverlay->GetOverlayPosition()) {
1620 case topleft:
1621 break;
1622 case topright:
1623 x = currwd - wd;
1624 break;
1625 case bottomright:
1626 x = currwd - wd;
1627 y = currht - ht;
1628 break;
1629 case bottomleft:
1630 y = currht - ht;
1631 break;
1632 case middle:
1633 x = (currwd - wd) / 2;
1634 y = (currht - ht) / 2;
1635 break;
1636 }
1637
1638 // render the overlay
1639 DrawRGBAData(overlaydata, x, y, wd, ht);
1640
1641 // the cursor might need to be changed
1642 curroverlay->CheckCursor();
1643 }
1644 }
1645
1646 // -----------------------------------------------------------------------------
1647
DrawView(int tileindex)1648 void DrawView(int tileindex)
1649 {
1650 if (curroverlay->OnlyDrawOverlay()) {
1651 DrawOverlay();
1652 return;
1653 }
1654
1655 wxRect r;
1656 Layer* savelayer = NULL;
1657 viewport* saveview0 = NULL;
1658 int colorindex;
1659 int currmag = currlayer->view->getmag();
1660
1661 if (viewptr->GridVisible()) {
1662 // draw a tiny pixmap before calling DrawGridLines to avoid crash in NVIDIA driver
1663 unsigned char data[] = "RGBARGBARGBARGBA";
1664 DrawRGBAData(data, 0, 0, 2, 2); // data has 4 pixels
1665 }
1666
1667 // if grid is bounded then ensure viewport's central cell is not outside grid edges
1668 if ( currlayer->algo->gridwd > 0) {
1669 if ( currlayer->view->x < currlayer->algo->gridleft )
1670 currlayer->view->setpositionmag(currlayer->algo->gridleft,
1671 currlayer->view->y,
1672 currmag);
1673 else if ( currlayer->view->x > currlayer->algo->gridright )
1674 currlayer->view->setpositionmag(currlayer->algo->gridright,
1675 currlayer->view->y,
1676 currmag);
1677 }
1678 if ( currlayer->algo->gridht > 0) {
1679 if ( currlayer->view->y < currlayer->algo->gridtop )
1680 currlayer->view->setpositionmag(currlayer->view->x,
1681 currlayer->algo->gridtop,
1682 currmag);
1683 else if ( currlayer->view->y > currlayer->algo->gridbottom )
1684 currlayer->view->setpositionmag(currlayer->view->x,
1685 currlayer->algo->gridbottom,
1686 currmag);
1687 }
1688
1689 if ( viewptr->nopattupdate ) {
1690 // don't draw incomplete pattern, just fill background
1691 glClearColor(currlayer->cellr[0]/255.0,
1692 currlayer->cellg[0]/255.0,
1693 currlayer->cellb[0]/255.0,
1694 1.0);
1695 glClear(GL_COLOR_BUFFER_BIT);
1696 // might as well draw grid lines and border
1697 currwd = currlayer->view->getwidth();
1698 currht = currlayer->view->getheight();
1699 if ( viewptr->GridVisible() ) {
1700 DrawGridLines(currwd, currht);
1701 }
1702 if ( currlayer->algo->gridwd > 0 || currlayer->algo->gridht > 0 ) {
1703 DrawGridBorder(currwd, currht);
1704 }
1705 return;
1706 }
1707
1708 if ( numlayers > 1 && tilelayers ) {
1709 if ( tileindex < 0 ) {
1710 DrawTileBorders();
1711 // there's no need to fill bigview's background
1712 return;
1713 }
1714 // tileindex >= 0 so temporarily change some globals to draw this tile
1715 if ( syncviews && tileindex != currindex ) {
1716 // make sure this layer uses same location and scale as current layer
1717 GetLayer(tileindex)->view->setpositionmag(currlayer->view->x,
1718 currlayer->view->y,
1719 currmag);
1720 }
1721 savelayer = currlayer;
1722 currlayer = GetLayer(tileindex);
1723 currmag = currlayer->view->getmag(); // possibly changed if not syncviews
1724 viewptr = currlayer->tilewin;
1725 colorindex = tileindex;
1726 } else if ( numlayers > 1 && stacklayers ) {
1727 // draw all layers starting with layer 0 but using current layer's viewport
1728 savelayer = currlayer;
1729 if ( currindex != 0 ) {
1730 // change currlayer to layer 0
1731 currlayer = GetLayer(0);
1732 saveview0 = currlayer->view;
1733 currlayer->view = savelayer->view;
1734 }
1735 colorindex = 0;
1736 } else {
1737 // just draw the current layer
1738 colorindex = currindex;
1739 }
1740
1741 // fill the background with the current state 0 color
1742 // (note that currlayer might have changed)
1743 glClearColor(currlayer->cellr[0]/255.0,
1744 currlayer->cellg[0]/255.0,
1745 currlayer->cellb[0]/255.0,
1746 1.0);
1747 glClear(GL_COLOR_BUFFER_BIT);
1748
1749 if (showicons && currmag > 2) {
1750 // only show icons at scales 1:8 and above
1751 if (currmag == 3) {
1752 iconatlas = currlayer->atlas7x7;
1753 LoadIconAtlas(8, currlayer->numicons);
1754 } else if (currmag == 4) {
1755 iconatlas = currlayer->atlas15x15;
1756 LoadIconAtlas(16, currlayer->numicons);
1757 } else {
1758 iconatlas = currlayer->atlas31x31;
1759 LoadIconAtlas(32, currlayer->numicons);
1760 }
1761 } else if (currmag > 0) {
1762 LoadCellAtlas(1 << currmag, currlayer->numicons, 255);
1763 }
1764
1765 currwd = currlayer->view->getwidth();
1766 currht = currlayer->view->getheight();
1767
1768 // all pixels are initially opaque
1769 dead_alpha = 255;
1770 live_alpha = 255;
1771
1772 // draw pattern using a sequence of pixblit calls
1773 if (smartscale && currmag <= -1 && currmag >= -4) {
1774 // current scale is from 2^1:1 to 2^4:1
1775 scalefactor = 1 << (-currmag);
1776
1777 // temporarily change viewport scale to 1:1 and increase its size by scalefactor
1778 currlayer->view->setmag(0);
1779 currwd = currwd * scalefactor;
1780 currht = currht * scalefactor;
1781 currlayer->view->resize(currwd, currht);
1782
1783 glPushMatrix();
1784 glScalef(1.0/scalefactor, 1.0/scalefactor, 1.0);
1785
1786 currlayer->algo->draw(*currlayer->view, renderer);
1787
1788 // restore viewport settings
1789 currwd = currwd / scalefactor;
1790 currht = currht / scalefactor;
1791 currlayer->view->resize(currwd, currht);
1792 currlayer->view->setmag(currmag);
1793
1794 // restore OpenGL scale
1795 glPopMatrix();
1796
1797 } else {
1798 // no scaling
1799 scalefactor = 1;
1800 currlayer->algo->draw(*currlayer->view, renderer);
1801 }
1802
1803 if ( viewptr->GridVisible() ) {
1804 DrawGridLines(currwd, currht);
1805 }
1806
1807 // if universe is bounded then draw border regions (if visible)
1808 if ( currlayer->algo->gridwd > 0 || currlayer->algo->gridht > 0 ) {
1809 DrawGridBorder(currwd, currht);
1810 }
1811
1812 if ( currlayer->currsel.Visible(&r) ) {
1813 DrawSelection(r, colorindex == currindex);
1814 }
1815
1816 if ( numlayers > 1 && stacklayers ) {
1817 // must restore currlayer before we call DrawStackedLayers
1818 currlayer = savelayer;
1819 if ( saveview0 ) {
1820 // restore layer 0's viewport
1821 GetLayer(0)->view = saveview0;
1822 }
1823 // draw layers 1, 2, ... numlayers-1
1824 DrawStackedLayers();
1825 }
1826
1827 if ( viewptr->waitingforclick && viewptr->pasterect.width > 0 ) {
1828 DrawPasteImage();
1829 }
1830
1831 if (viewptr->showcontrols) {
1832 DrawControls(viewptr->controlsrect);
1833 }
1834
1835 if (showoverlay) {
1836 if (numlayers > 1 && tilelayers) {
1837 // only display overlay above current tile
1838 if (tileindex == currindex) DrawOverlay();
1839 } else {
1840 DrawOverlay();
1841 }
1842 }
1843
1844 if ( numlayers > 1 && tilelayers ) {
1845 // restore globals changed above
1846 currlayer = savelayer;
1847 viewptr = currlayer->tilewin;
1848 }
1849 }
1850