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