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