1 /* vim:expandtab:ts=2 sw=2:
2 */
3 /*  Grafx2 - The Ultimate 256-color bitmap paint program
4 
5 	Copyright owned by various GrafX2 authors, see COPYRIGHT.txt for details.
6 
7     Grafx2 is free software; you can redistribute it and/or
8     modify it under the terms of the GNU General Public License
9     as published by the Free Software Foundation; version 2
10     of the License.
11 
12     Grafx2 is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15     GNU General Public License for more details.
16 
17     You should have received a copy of the GNU General Public License
18     along with Grafx2; if not, see <http://www.gnu.org/licenses/>
19 */
20 
21 #include <stdlib.h>
22 #include <string.h>
23 
24 #include "struct.h"
25 #include "global.h"
26 #include "graph.h"
27 #include "screen.h"
28 #include "engine.h"
29 #include "windows.h"
30 #include "input.h"
31 #include "misc.h"
32 #include "osdep.h"
33 #include "tiles.h"
34 
35 // These helpers are only needed internally at the moment
36 #define TILE_FOR_COORDS(x,y) (((y)-Snap_offset_Y)/Snap_height*Main.tilemap_width+((x)-Snap_offset_X)/Snap_width)
37 #define TILE_AT(x,y) (y)*Main.tilemap_width+(x)
38 #define TILE_X(t) (((t)%Main.tilemap_width)*Snap_width+Snap_offset_X)
39 #define TILE_Y(t) (((t)/Main.tilemap_width)*Snap_height+Snap_offset_Y)
40 
41 enum TILE_FLIPPED
42 {
43   TILE_FLIPPED_NONE = 0,
44   TILE_FLIPPED_X = 1,
45   TILE_FLIPPED_Y = 2,
46   TILE_FLIPPED_XY = 3, // needs be TILE_FLIPPED_X|TILE_FLIPPED_Y
47 };
48 
49 // globals
50 
51 ///
52 /// Draw a pixel while Tilemap mode is active : This will paint on all
53 /// similar tiles of the layer, visible on the screen or not.
Tilemap_draw(word x,word y,byte color)54 void Tilemap_draw(word x, word y, byte color)
55 {
56   int tile, first_tile;
57   int rel_x, rel_y;
58 
59   if (x < Snap_offset_X
60    || y < Snap_offset_Y
61    || x >= Snap_offset_X + Main.tilemap_width*Snap_width
62    || y >= Snap_offset_Y + Main.tilemap_height*Snap_height)
63     return;
64 
65   tile = first_tile = TILE_FOR_COORDS(x,y);
66 
67   rel_x = (x - Snap_offset_X + Snap_width) % Snap_width;
68   rel_y = (y - Snap_offset_Y + Snap_height) % Snap_height;
69   do
70   {
71     int xx,yy;
72     switch(Main.tilemap[tile].Flipped ^ Main.tilemap[first_tile].Flipped)
73     {
74       case 0: // no
75       default:
76         xx = (tile % Main.tilemap_width)*Snap_width+Snap_offset_X + rel_x;
77         yy = (tile / Main.tilemap_width)*Snap_height+Snap_offset_Y + rel_y;
78         break;
79       case 1: // horizontal
80         xx = (tile % Main.tilemap_width)*Snap_width+Snap_offset_X + Snap_width-rel_x-1;
81         yy = (tile / Main.tilemap_width)*Snap_height+Snap_offset_Y + rel_y;
82         break;
83       case 2: // vertical
84         xx = (tile % Main.tilemap_width)*Snap_width+Snap_offset_X + rel_x;
85         yy = (tile / Main.tilemap_width)*Snap_height+Snap_offset_Y + Snap_height - rel_y - 1;
86         break;
87       case 3: // both
88         xx = (tile % Main.tilemap_width)*Snap_width+Snap_offset_X + Snap_width-rel_x-1;
89         yy = (tile / Main.tilemap_width)*Snap_height+Snap_offset_Y + Snap_height - rel_y - 1;
90         break;
91     }
92     if (yy>=Limit_top&&yy<=Limit_bottom&&xx>=Limit_left&&xx<=Limit_right)
93       Pixel_in_current_screen_with_preview(xx,yy,color);
94     else
95       Pixel_in_current_screen(xx,yy,color);
96 
97     tile = Main.tilemap[tile].Next;
98   } while (tile != first_tile);
99 
100   Update_rect(0,0,0,0);
101 }
102 
103 ///
Tile_is_same(int t1,int t2)104 int Tile_is_same(int t1, int t2)
105 {
106   byte *bmp1,*bmp2;
107   int y;
108 
109   bmp1 = Main.backups->Pages->Image[Main.current_layer].Pixels+(TILE_Y(t1))*Main.image_width+(TILE_X(t1));
110   bmp2 = Main.backups->Pages->Image[Main.current_layer].Pixels+(TILE_Y(t2))*Main.image_width+(TILE_X(t2));
111 
112   for (y=0; y < Snap_height; y++)
113   {
114     if (memcmp(bmp1+y*Main.image_width, bmp2+y*Main.image_width, Snap_width))
115       return 0;
116   }
117   return 1;
118 }
119 
120 ///
Tile_is_same_flipped_x(int t1,int t2)121 int Tile_is_same_flipped_x(int t1, int t2)
122 {
123   byte *bmp1,*bmp2;
124   int y, x;
125 
126   bmp1 = Main.backups->Pages->Image[Main.current_layer].Pixels+(TILE_Y(t1))*Main.image_width+(TILE_X(t1));
127   bmp2 = Main.backups->Pages->Image[Main.current_layer].Pixels+(TILE_Y(t2))*Main.image_width+(TILE_X(t2)+Snap_width-1);
128 
129   for (y=0; y < Snap_height; y++)
130   {
131     for (x=0; x < Snap_width; x++)
132       if (*(bmp1+y*Main.image_width+x) != *(bmp2+y*Main.image_width-x))
133         return 0;
134   }
135   return 1;
136 }
137 
138 ///
Tile_is_same_flipped_y(int t1,int t2)139 int Tile_is_same_flipped_y(int t1, int t2)
140 {
141   byte *bmp1,*bmp2;
142   int y;
143 
144   bmp1 = Main.backups->Pages->Image[Main.current_layer].Pixels+(TILE_Y(t1))*Main.image_width+(TILE_X(t1));
145   bmp2 = Main.backups->Pages->Image[Main.current_layer].Pixels+(TILE_Y(t2)+Snap_height-1)*Main.image_width+(TILE_X(t2));
146 
147   for (y=0; y < Snap_height; y++)
148   {
149     if (memcmp(bmp1+y*Main.image_width, bmp2-y*Main.image_width, Snap_width))
150       return 0;
151   }
152   return 1;
153 }
154 
155 
156 ///
Tile_is_same_flipped_xy(int t1,int t2)157 int Tile_is_same_flipped_xy(int t1, int t2)
158 {
159   byte *bmp1,*bmp2;
160   int y, x;
161 
162   bmp1 = Main.backups->Pages->Image[Main.current_layer].Pixels+(TILE_Y(t1))*Main.image_width+(TILE_X(t1));
163   bmp2 = Main.backups->Pages->Image[Main.current_layer].Pixels+(TILE_Y(t2)+Snap_height-1)*Main.image_width+(TILE_X(t2)+Snap_width-1);
164 
165   for (y=0; y < Snap_height; y++)
166   {
167     for (x=0; x < Snap_width; x++)
168       if (*(bmp1+y*Main.image_width+x) != *(bmp2-y*Main.image_width-x))
169         return 0;
170   }
171   return 1;
172 }
173 
174 /// Create or update a tilemap based on current screen (layer)'s pixels.
Tilemap_update(void)175 void Tilemap_update(void)
176 {
177   int width;
178   int height;
179   int tile;
180   int count=1;
181   T_Tile * tile_ptr;
182 
183   int wait_window=0;
184   byte old_cursor=0;
185 
186   if (!Main.tilemap_mode)
187     return;
188 
189   width=(Main.image_width-Snap_offset_X)/Snap_width;
190   height=(Main.image_height-Snap_offset_Y)/Snap_height;
191 
192   if (width<1 || height<1 || width*height>1000000l
193    || (tile_ptr=(T_Tile *)malloc(width*height*sizeof(T_Tile))) == NULL)
194   {
195     // Cannot enable tilemap because either the image is too small
196     // for the grid settings (and I don't want to implement partial tiles)
197     // Or the number of tiles seems unreasonable (one million) : This can
198     // happen if you set grid 1x1 for example.
199 
200     Disable_tilemap(&Main);
201     return;
202   }
203 
204   if (Main.tilemap)
205   {
206     // Recycle existing tilemap
207     free(Main.tilemap);
208     Main.tilemap=NULL;
209   }
210   Main.tilemap=tile_ptr;
211 
212   Main.tilemap_width=width;
213   Main.tilemap_height=height;
214 
215   if (width*height > 1000 || Config.Tilemap_show_count)
216   {
217     wait_window=1;
218     old_cursor=Cursor_shape;
219     Open_window(180,36,"Creating tileset");
220     Print_in_window(26, 20, "Please wait...",MC_Black,MC_Light);
221     Cursor_shape=CURSOR_SHAPE_HOURGLASS;
222     Update_window_area(0,0,Window_width, Window_height);
223     Display_cursor();
224     Get_input(0);
225   }
226 
227   // Initialize array with all tiles unique by default
228   for (tile=0; tile<width*height; tile++)
229   {
230     Main.tilemap[tile].Previous = tile;
231     Main.tilemap[tile].Next = tile;
232     Main.tilemap[tile].Flipped = 0;
233   }
234 
235   // Now find similar tiles and link them in circular linked list
236   //It will be used to modify all tiles whenever you draw on one.
237   for (tile=1; tile<width*height; tile++)
238   {
239     int ref_tile;
240 
241     // Try normal comparison
242 
243     for (ref_tile=0; ref_tile<tile; ref_tile++)
244     {
245       if (Main.tilemap[ref_tile].Previous<=ref_tile && Tile_is_same(ref_tile, tile))
246       {
247         break; // stop loop on ref_tile
248       }
249     }
250     if (ref_tile<tile)
251     {
252       // New occurrence of a known tile
253       // Insert at the end. classic doubly-linked-list.
254       int last_tile=Main.tilemap[ref_tile].Previous;
255       Main.tilemap[tile].Previous=last_tile;
256       Main.tilemap[tile].Next=ref_tile;
257       Main.tilemap[tile].Flipped=Main.tilemap[ref_tile].Flipped;
258       Main.tilemap[ref_tile].Previous=tile;
259       Main.tilemap[last_tile].Next=tile;
260       continue; // next tile
261     }
262 
263     // Try flipped-y comparison
264     if (Config.Tilemap_allow_flipped_y)
265     {
266       for (ref_tile=0; ref_tile<tile; ref_tile++)
267       {
268         if (Main.tilemap[ref_tile].Previous<=ref_tile && Tile_is_same_flipped_y(ref_tile, tile))
269         {
270           break; // stop loop on ref_tile
271         }
272       }
273       if (ref_tile<tile)
274       {
275         // New occurrence of a known tile
276         // Insert at the end. classic doubly-linked-list.
277         int last_tile=Main.tilemap[ref_tile].Previous;
278         Main.tilemap[tile].Previous=last_tile;
279         Main.tilemap[tile].Next=ref_tile;
280         Main.tilemap[tile].Flipped=Main.tilemap[ref_tile].Flipped ^ TILE_FLIPPED_Y;
281         Main.tilemap[ref_tile].Previous=tile;
282         Main.tilemap[last_tile].Next=tile;
283         continue; // next tile
284       }
285     }
286 
287     // Try flipped-x comparison
288     if (Config.Tilemap_allow_flipped_x)
289     {
290       for (ref_tile=0; ref_tile<tile; ref_tile++)
291       {
292         if (Main.tilemap[ref_tile].Previous<=ref_tile && Tile_is_same_flipped_x(ref_tile, tile))
293         {
294           break; // stop loop on ref_tile
295         }
296       }
297       if (ref_tile<tile)
298       {
299         // New occurrence of a known tile
300         // Insert at the end. classic doubly-linked-list.
301         int last_tile=Main.tilemap[ref_tile].Previous;
302         Main.tilemap[tile].Previous=last_tile;
303         Main.tilemap[tile].Next=ref_tile;
304         Main.tilemap[tile].Flipped=Main.tilemap[ref_tile].Flipped ^ TILE_FLIPPED_X;
305         Main.tilemap[ref_tile].Previous=tile;
306         Main.tilemap[last_tile].Next=tile;
307         continue; // next tile
308       }
309     }
310 
311     // Try flipped-xy comparison
312     if (Config.Tilemap_allow_flipped_x && Config.Tilemap_allow_flipped_y)
313     {
314       for (ref_tile=0; ref_tile<tile; ref_tile++)
315       {
316         if (Main.tilemap[ref_tile].Previous<=ref_tile && Tile_is_same_flipped_xy(ref_tile, tile))
317         {
318           break; // stop loop on ref_tile
319         }
320       }
321       if (ref_tile<tile)
322       {
323         // New occurrence of a known tile
324         // Insert at the end. classic doubly-linked-list.
325         int last_tile=Main.tilemap[ref_tile].Previous;
326         Main.tilemap[tile].Previous=last_tile;
327         Main.tilemap[tile].Next=ref_tile;
328         Main.tilemap[tile].Flipped=Main.tilemap[ref_tile].Flipped ^ TILE_FLIPPED_XY;
329         Main.tilemap[ref_tile].Previous=tile;
330         Main.tilemap[last_tile].Next=tile;
331         continue; // next tile
332       }
333     }
334 
335     // This tile is really unique.
336     // Nothing to do at this time: the initialization
337     // has already set the right data for Main.tilemap[tile].
338     count++;
339   }
340 
341   if (wait_window)
342   {
343     char   str[8];
344     dword end;
345 
346     if (Config.Tilemap_show_count)
347     {
348       Num2str(count,str,7);
349       Hide_cursor();
350       Print_in_window(6, 20, "Unique tiles: ",MC_Black,MC_Light);
351       Print_in_window(6+8*14,20,str,MC_Black,MC_Light);
352       Display_cursor();
353 
354       // Wait a moment to display count
355       end = GFX2_GetTicks()+750;
356       do
357       {
358         Get_input(20);
359       } while (GFX2_GetTicks()<end);
360     }
361 
362     Close_window();
363     Cursor_shape=old_cursor;
364     Display_cursor();
365   }
366 }
367 
368 ///
369 /// Clears all tilemap data and settings
370 /// Safe to call again.
Disable_tilemap(T_Document * doc)371 void Disable_tilemap(T_Document * doc)
372 {
373   if (doc->tilemap)
374   {
375     // Recycle existing tilemap
376     free(doc->tilemap);
377     doc->tilemap=NULL;
378   }
379   doc->tilemap_width=0;
380   doc->tilemap_height=0;
381   doc->tilemap_mode=0;
382 }
383