1 /* mainwindow.c
2 Copyright (C) 2004-2021 Mark Tyler and Dmitry Groshev
3
4 This file is part of mtPaint.
5
6 mtPaint is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
10
11 mtPaint is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with mtPaint in the file COPYING.
18 */
19
20 #include "global.h"
21 #undef _
22 #define _(X) X
23
24 #include "mygtk.h"
25 #include "memory.h"
26 #include "vcode.h"
27 #include "ani.h"
28 #include "png.h"
29 #include "mainwindow.h"
30 #include "viewer.h"
31 #include "otherwindow.h"
32 #include "inifile.h"
33 #include "canvas.h"
34 #include "polygon.h"
35 #include "layer.h"
36 #include "info.h"
37 #include "prefs.h"
38 #include "channels.h"
39 #include "toolbar.h"
40 #include "csel.h"
41 #include "shifter.h"
42 #include "spawn.h"
43 #include "font.h"
44 #include "icons.h"
45 #include "thread.h"
46
47
48 typedef struct {
49 int idx_c, nidx_c, cnt_c;
50 int cline_d, settings_d, layers_d;
51 int impmode;
52 int *strs_c;
53 void **drop, **clip;
54 void **clipboard;
55 void **dockpage1;
56 void **keyslot;
57 } main_dd;
58
59 #define GREY_W 153
60 #define GREY_B 102
61 const unsigned char greyz[2] = {GREY_W, GREY_B}; // For opacity squares
62
63 char *channames[NUM_CHANNELS + 1], *allchannames[NUM_CHANNELS + 1];
64 char *cspnames[NUM_CSPACES];
65
66 char *channames_[NUM_CHANNELS + 1] =
67 { _("Image"), _("Alpha"), _("Selection"), _("Mask"), NULL };
68 char *cspnames_[NUM_CSPACES] =
69 { _("RGB"), _("sRGB"), "LXN" };
70
71 /// INIFILE ENTRY LISTS
72
73 typedef struct {
74 char *name;
75 int *var;
76 int defv;
77 } inilist;
78
79 static inilist ini_bool[] = {
80 { "layermainToggle", &show_layers_main, FALSE },
81 { "sharperReduce", &sharper_reduce, FALSE },
82 { "tga565", &tga_565, FALSE },
83 { "tgaDefdir", &tga_defdir, FALSE },
84 { "tgaRLE", &tga_RLE, FALSE },
85 { "lbmPBM", &lbm_pbm, FALSE },
86 { "disableTransparency", &opaque_view, FALSE },
87 { "smudgeOpacity", &smudge_mode, FALSE },
88 { "showMenuIcons", &show_menu_icons, FALSE },
89 { "showTileGrid", &show_tile_grid, FALSE },
90 { "applyICC", &apply_icc, FALSE },
91 { "scrollwheelZOOM", &scroll_zoom, FALSE },
92 { "cursorZoom", &cursor_zoom, FALSE },
93 { "layerOverlay", &layer_overlay, FALSE },
94 { "paintGamma", &paint_gamma, FALSE },
95 { "patternB", &pattern_B, FALSE },
96 { "arrowScroll", &arrow_scroll, FALSE },
97 { "tablet_use_size", tablet_tool_use + 0, FALSE },
98 { "tablet_use_flow", tablet_tool_use + 1, FALSE },
99 { "tablet_use_opacity", tablet_tool_use + 2, FALSE },
100 { "pasteCommit", &paste_commit, TRUE },
101 { "couple_RGBA", &RGBA_mode, TRUE },
102 { "gridToggle", &mem_show_grid, TRUE },
103 { "optimizeChequers", &chequers_optimize, TRUE },
104 { "quitToggle", &q_quit, TRUE },
105 { "continuousPainting", &mem_continuous, TRUE },
106 { "opacityToggle", &mem_undo_opacity, TRUE },
107 { "imageCentre", &canvas_image_centre, TRUE },
108 { "view_focus", &vw_focus_on, TRUE },
109 { "pasteToggle", &show_paste, TRUE },
110 { "cursorToggle", &cursor_tool, TRUE },
111 { "autopreviewToggle", &brcosa_auto, TRUE },
112 { "colorGrid", &color_grid, TRUE },
113 { "defaultGamma", &use_gamma, TRUE },
114 { "undoableLoad", &undo_load, TRUE },
115 { "tiffPredictor", &tiff_predictor, TRUE },
116 { "lbmPack", &lbm_pack, TRUE },
117 { "lbmIgnoreTrans", &lbm_untrans, TRUE },
118 #if STATUS_ITEMS != 5
119 #error Wrong number of "status?Toggle" inifile items defined
120 #endif
121 { "status0Toggle", status_on + 0, TRUE },
122 { "status1Toggle", status_on + 1, TRUE },
123 { "status2Toggle", status_on + 2, TRUE },
124 { "status3Toggle", status_on + 3, TRUE },
125 { "status4Toggle", status_on + 4, TRUE },
126 #if TOOLBAR_MAX != 6
127 #error Wrong number of "toolbar?" inifile items defined
128 #endif
129 { "toolbar1", toolbar_status + 1, TRUE },
130 { "toolbar2", toolbar_status + 2, TRUE },
131 { "toolbar3", toolbar_status + 3, TRUE },
132 { "toolbar4", toolbar_status + 4, TRUE },
133 { "toolbar5", toolbar_status + 5, TRUE },
134 { "fontAntialias0", &font_aa, TRUE },
135 { "fontAntialias1", &font_bk, FALSE },
136 { "fontAntialias2", &font_r, FALSE },
137 #ifdef U_FREETYPE
138 { "fontAntialias3", &font_obl, FALSE },
139 { "ftSetDPI", &ft_setdpi, TRUE },
140 #endif
141 { "fontSetDPI", &font_setdpi, FALSE },
142 { NULL, NULL }
143 };
144
145 static inilist ini_int[] = {
146 { "jpegQuality", &jpeg_quality, 85 },
147 { "pngCompression", &png_compression, 9 },
148 { "jpeg2000Rate", &jp2_rate, 1 },
149 { "lzmaPreset", &lzma_preset, 9 },
150 { "zstdLevel", &zstd_level, 9 },
151 { "webpPreset", &webp_preset, 1 },
152 { "webpQuality", &webp_quality, 90 },
153 { "webpCompression", &webp_compression, 9 },
154 { "lbmMask", &lbm_mask, CHN_MASK },
155 { "silence_limit", &silence_limit, 18 },
156 { "gradientOpacity", &grad_opacity, 128 },
157 { "gridMin", &mem_grid_min, 8 },
158 { "undoMBlimit", &mem_undo_limit, 0 },
159 { "undoCommon", &mem_undo_common, 25 },
160 { "maxThreads", &maxthreads, 0 },
161 { "kpixThreads", &kpix_threads, 256 },
162 { "backgroundGrey", &mem_background, 180 },
163 { "pixelNudge", &mem_nudge, 8 },
164 { "recentFiles", &recent_files, 10 },
165 { "lastspalType", &spal_mode, 2 },
166 { "posterizeMode", &def_bcsp.pmode, 0 },
167 { "panSize", &max_pan, 128 },
168 { "undoDepth", &mem_undo_depth, DEF_UNDO },
169 { "tileWidth", &tgrid_dx, 32 },
170 { "tileHeight", &tgrid_dy, 32 },
171 { "gridRGB", grid_rgb + GRID_NORMAL, RGB_2_INT( 50, 50, 50) },
172 { "gridBorder", grid_rgb + GRID_BORDER, RGB_2_INT( 0, 219, 0) },
173 { "gridTrans", grid_rgb + GRID_TRANS, RGB_2_INT( 0, 109, 109) },
174 { "gridTile", grid_rgb + GRID_TILE, RGB_2_INT(170, 170, 170) },
175 { "gridSegment", grid_rgb + GRID_SEGMENT,RGB_2_INT(219, 219, 0) },
176 { "palAB", &mem_pal_ab_c, RGB_2_INT(53, 53, 162) },
177 { "palIndex", &mem_pal_id_c, RGB_2_INT(200, 200, 200) },
178 { "tablet_value_size", tablet_tool_factor + 0, MAX_TF },
179 { "tablet_value_flow", tablet_tool_factor + 1, MAX_TF },
180 { "tablet_value_opacity", tablet_tool_factor + 2,MAX_TF },
181 { "fontBackground", &font_bkg, 0 },
182 { "fontAngle", &font_angle, 0 },
183 { "fontAlign", &font_align, 0 },
184 { "fontDPI", &font_dpi, 72 },
185 { "fontSpacing", &font_spacing, 0 },
186 #ifdef U_FREETYPE
187 { "fontSizeBitmap", &font_bmsize, 1 },
188 { "fontSize", &font_size, 12 },
189 { "font_dirs", &font_dirs, 0 },
190 #endif
191 { NULL, NULL }
192 };
193
194
195 void **main_window_, **main_keys, **settings_dock, **layers_dock, **main_split,
196 **drawing_canvas, **scrolledwindow_canvas,
197 **menu_slots[TOTAL_MENU_IDS];
198
199 static void **dock_area, **dock_book, **main_menubar;
200
201 int view_image_only, viewer_mode, drag_index, q_quit, cursor_tool;
202 int show_menu_icons, paste_commit, scroll_zoom, arrow_scroll;
203 int drag_index_vals[2], cursor_corner, use_gamma, view_vsplit;
204 int files_passed, cmd_mode, tablet_working;
205 char **file_args, **script_cmds;
206
207 static int show_dock;
208 static int mouse_left_canvas, is_tracking;
209 static int cvxy[2]; // canvas window position
210
211 typedef struct {
212 int mode; // (tool_type + 1) if drawn, 0 if not
213 int x, y, s; // Top left corner and size
214 int cx, cy; // Clone perimeter offset
215 } perim_info;
216
217 static perim_info perim_state; // Tool perimeter
218 int perim_wx, perim_wy; // Cursor position
219
220 #define perim_status perim_state.mode
221 #define perim_x perim_state.x
222 #define perim_y perim_state.y
223 #define perim_s perim_state.s
224 #define perim_cx perim_state.cx
225 #define perim_cy perim_state.cy
226
227 static void repaint_perim(rgbcontext *ctx); // Redraw perimeter around mouse cursor
228 static void clear_perim(perim_info *p); // Clear perimeter around mouse cursor
229 static void move_perim(int x, int y); // Move perimeter to a new location
230
clear_perim_real(int x0,int y0,int s)231 static void clear_perim_real(int x0, int y0, int s)
232 {
233 int x1, y1, zoom = 1, scale = 1;
234
235
236 /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
237 if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
238 else scale = rint(can_zoom);
239
240 x1 = margin_main_x + ((x0 + s - 1) * scale) / zoom + scale - 1;
241 y1 = margin_main_y + ((y0 + s - 1) * scale) / zoom + scale - 1;
242 x0 = margin_main_x + (x0 * scale) / zoom;
243 y0 = margin_main_y + (y0 * scale) / zoom;
244
245 repaint_canvas(x0, y0, 1, y1 - y0 + 1);
246 repaint_canvas(x1, y0, 1, y1 - y0 + 1);
247 repaint_canvas(x0 + 1, y0, x1 - x0 - 1, 1);
248 repaint_canvas(x0 + 1, y1, x1 - x0 - 1, 1);
249 }
250
pressed_load_recent(int item)251 static void pressed_load_recent(int item)
252 {
253 if ((layers_total ? check_layers_for_changes() :
254 check_for_changes()) == 1) return;
255
256 do_a_load(recent_filenames[item - 1], undo_load); // Load requested file
257 }
258
pressed_crop()259 static void pressed_crop()
260 {
261 int res, rect[4];
262
263
264 if ( marq_status != MARQUEE_DONE ) return;
265 marquee_at(rect);
266 if ((rect[0] == 0) && (rect[2] >= mem_width) &&
267 (rect[1] == 0) && (rect[3] >= mem_height)) return;
268
269 res = mem_image_resize(rect[2], rect[3], -rect[0], -rect[1], 0);
270
271 if (!res)
272 {
273 pressed_select(FALSE);
274 change_to_tool(DEFAULT_TOOL_ICON);
275 update_stuff(UPD_GEOM);
276 }
277 else memory_errors(res);
278 }
279
280 static multi_ext *script_rect(int *rect);
281 static void select_poly(multi_ext *mx);
282
pressed_select(int all)283 void pressed_select(int all)
284 {
285 int i = 0;
286
287 /* Remove old selection */
288 if (marq_status != MARQUEE_NONE)
289 {
290 i = UPD_SEL;
291 if (marq_status >= MARQUEE_PASTE) i = UPD_SEL | CF_DRAW;
292 else paint_marquee(MARQ_HIDE, 0, 0, NULL);
293 marq_status = MARQUEE_NONE;
294 }
295 if ((tool_type == TOOL_POLYGON) && (poly_status != POLY_NONE))
296 {
297 poly_points = 0;
298 poly_status = POLY_NONE;
299 i = UPD_SEL | CF_DRAW; // Have to erase polygon
300 }
301 /* And deal with selection persistence too */
302 marq_x1 = marq_y1 = marq_x2 = marq_y2 = -1;
303
304 while (all) /* Select entire canvas */
305 {
306 multi_ext *mx;
307 int rxy[4];
308
309 if ((mx = script_rect(rxy))) // Script wants a polygon
310 {
311 change_to_tool(TTB_POLY);
312 select_poly(mx);
313 free(mx);
314 break;
315 }
316 clip(rxy, 0, 0, mem_width - 1, mem_height - 1, rxy);
317 /* We are selecting an area, so block inside-out selections */
318 if ((rxy[0] > rxy[2]) || (rxy[1] > rxy[3])) break;
319 i |= UPD_SEL;
320 copy4(marq_xy, rxy);
321 if (tool_type != TOOL_SELECT)
322 {
323 /* Switch tool, and let that & marquee persistence
324 * do all the rest except full redraw */
325 change_to_tool(TTB_SELECT);
326 i &= CF_DRAW;
327 break;
328 }
329 marq_status = MARQUEE_DONE;
330 if (i & CF_DRAW) break; // Full redraw will draw marquee too
331 paint_marquee(MARQ_SHOW, 0, 0, NULL);
332 break;
333 }
334 if (i) update_stuff(i);
335 }
336
pressed_remove_unused()337 static void pressed_remove_unused()
338 {
339 if (mem_remove_unused_check() <= 0)
340 {
341 if (!script_cmds) alert_box(_("Error"),
342 _("There were no unused colours to remove!"), NULL);
343 }
344 else
345 {
346 spot_undo(UNDO_XPAL);
347
348 mem_remove_unused();
349 mem_undo_prepare();
350
351 update_stuff(UPD_TPAL);
352 }
353 }
354
pressed_default_pal()355 static void pressed_default_pal()
356 {
357 spot_undo(UNDO_PAL);
358 mem_pal_copy( mem_pal, mem_pal_def );
359 mem_cols = mem_pal_def_i;
360 update_stuff(UPD_PAL);
361 }
362
pressed_remove_duplicates()363 static void pressed_remove_duplicates()
364 {
365 char *mess;
366 int dups = scan_duplicates();
367
368 if (!dups)
369 {
370 if (!script_cmds) alert_box(_("Error"),
371 _("The palette does not contain 2 colours that have identical RGB values"), NULL);
372 return;
373 }
374 mess = g_strdup_printf(__("The palette contains %i colours that have identical RGB values. Do you really want to merge them into one index and realign the canvas?"), dups);
375 if (script_cmds || (alert_box(_("Warning"), mess, _("Yes"), _("No"), NULL) == 1))
376 {
377 spot_undo(UNDO_XPAL);
378
379 remove_duplicates();
380 mem_undo_prepare();
381 update_stuff(UPD_PAL);
382 }
383 g_free(mess);
384 }
385
pressed_dither_A()386 static void pressed_dither_A()
387 {
388 mem_find_dither(mem_col_A24.red, mem_col_A24.green, mem_col_A24.blue);
389 update_stuff(UPD_ABP);
390 }
391
392 // System clipboard import
393
394 static clipform_dd clip_formats[] = {
395 { "application/x-mtpaint-pmm", (void *)(FT_PMM | FTM_EXTEND) },
396 { "application/x-mtpaint-clipboard", (void *)(FT_PNG | FTM_EXTEND) },
397 { "image/png", (void *)(FT_PNG) },
398 { "image/bmp", (void *)(FT_BMP) },
399 { "image/x-bmp", (void *)(FT_BMP) },
400 { "image/x-MS-bmp", (void *)(FT_BMP) },
401 #ifdef U_TIFF
402 { "image/tiff", (void *)(FT_TIFF) },
403 #endif
404 #ifdef HAVE_PIXMAPS
405 /* These two don't make sense without X */
406 { "PIXMAP", (void *)(FT_PIXMAP), sizeof(XID_type), 32 },
407 { "BITMAP", (void *)(FT_PIXMAP), sizeof(XID_type), 32 },
408 /* !!! BITMAP requests are handled same as PIXMAP - because it is only
409 * done to appease buggy XPaint which requests both and crashes if
410 * receiving only one - WJ */
411 #endif
412 };
413 #define CLIP_TARGETS (sizeof(clip_formats) / sizeof(clip_formats[0]))
414
415 /* Seems it'll be better to prefer BMP when talking to the likes of GIMP -
416 * they send PNGs really slowly (likely, compressed to the max); but not
417 * everyone supports alpha in BMPs. */
418
clipboard_import_fn(main_dd * dt,void ** wdata,int what,void ** where,copy_ext * cdata)419 static int clipboard_import_fn(main_dd *dt, void **wdata, int what, void **where,
420 copy_ext *cdata)
421 {
422 int form = (int)cdata->format->id;
423 if ((dt->impmode == FS_PNG_LOAD) && undo_load) form |= FTM_UNDO;
424 return (load_mem_image(cdata->data, cdata->len, dt->impmode, form) == 1);
425 }
426
import_clipboard(int mode)427 int import_clipboard(int mode)
428 {
429 main_dd *dt = GET_DDATA(main_window_);
430 if (cmd_mode) return (FALSE); // !!! Needs active GTK+ to work
431 dt->impmode = mode;
432 return (cmd_checkv(dt->clipboard, CLIP_PROCESS));
433 }
434
setup_clip_save(ls_settings * settings)435 static void setup_clip_save(ls_settings *settings)
436 {
437 init_ls_settings(settings, NULL);
438 memcpy(settings->img, mem_clip.img, sizeof(chanlist));
439 settings->pal = mem_pal;
440 settings->width = mem_clip_w;
441 settings->height = mem_clip_h;
442 settings->bpp = mem_clip_bpp;
443 settings->colors = mem_cols;
444 }
445
clipboard_export_fn(main_dd * dt,void ** wdata,int what,void ** where,copy_ext * cdata)446 static void clipboard_export_fn(main_dd *dt, void **wdata, int what, void **where,
447 copy_ext *cdata)
448 {
449 ls_settings settings;
450 unsigned char *buf, *pp[2];
451 int res, len, type;
452
453 if (!cdata->format) return; // Someone else stole system clipboard
454 if (!mem_clipboard) return; // Our own clipboard got emptied
455
456 /* Prepare settings */
457 setup_clip_save(&settings);
458 settings.mode = FS_CLIPBOARD;
459 settings.ftype = type = (int)cdata->format->id;
460 settings.png_compression = 1; // Speed is of the essence
461
462 res = save_mem_image(&buf, &len, &settings);
463 if (res) return; // No luck creating in-memory image
464
465 pp[1] = (pp[0] = buf) + len;
466 cmd_setv(where, pp, COPY_DATA);
467 free(buf);
468 }
469
export_clipboard()470 static int export_clipboard()
471 {
472 main_dd *dt = GET_DDATA(main_window_);
473 if (!mem_clipboard) return (FALSE);
474 return (cmd_checkv(dt->clipboard, CLIP_OFFER));
475 }
476
gui_save(char * filename,ls_settings * settings)477 int gui_save(char *filename, ls_settings *settings)
478 {
479 int res = -2, fflags = file_formats[settings->ftype].flags;
480 char *mess = NULL, *f8;
481
482 /* Mismatched format - raise an error right here */
483 if ((fflags & FF_NOSAVE) || !(fflags & FF_SAVE_MASK))
484 {
485 int maxc = 0;
486 char *fform = NULL, *fname = file_formats[settings->ftype].name;
487
488 /* RGB to indexed (or to unsaveable) */
489 if (mem_img_bpp == 3) fform = __("RGB");
490 /* Indexed to RGB, or to unsaveable format */
491 else if (!(fflags & FF_IDX) || (fflags & FF_NOSAVE))
492 fform = __("indexed");
493 /* More than 16 colors */
494 else if (fflags & FF_16) maxc = 16;
495 /* More than 2 colors */
496 else maxc = 2;
497 /* Build message */
498 if (fform) mess = g_strdup_printf(__("You are trying to save an %s image to an %s file which is not possible. I would suggest you save with a PNG extension."),
499 fform, fname);
500 else mess = g_strdup_printf(__("You are trying to save an %s file with a palette of more than %d colours. Either use another format or reduce the palette to %d colours."),
501 fname, maxc, maxc);
502 }
503 else
504 {
505 /* Commit paste if required */
506 if (!script_cmds && paste_commit && (marq_status >= MARQUEE_PASTE))
507 {
508 commit_paste(FALSE, NULL);
509 pen_down = 0;
510 mem_undo_prepare();
511 pressed_select(FALSE);
512 }
513
514 /* Prepare to save image */
515 memcpy(settings->img, mem_img, sizeof(chanlist));
516 settings->pal = mem_pal;
517 settings->width = mem_width;
518 settings->height = mem_height;
519 settings->bpp = mem_img_bpp;
520 settings->colors = mem_cols;
521
522 res = save_image(filename, settings);
523 }
524 if (res < 0)
525 {
526 if (res == -1)
527 {
528 f8 = gtkuncpy(NULL, filename, 0);
529 mess = g_strdup_printf(__("Unable to save file: %s"), f8);
530 g_free(f8);
531 }
532 if (mess)
533 {
534 alert_box(_("Error"), mess, NULL);
535 g_free(mess);
536 }
537 }
538 else
539 {
540 notify_unchanged(filename);
541 register_file(filename);
542 }
543
544 return res;
545 }
546
pressed_save_file()547 static void pressed_save_file()
548 {
549 ls_settings settings;
550
551 while (mem_filename)
552 {
553 init_ls_settings(&settings, NULL);
554 settings.ftype = file_type_by_ext(mem_filename, FF_IMAGE);
555 if (settings.ftype == FT_NONE) break;
556 settings.mode = FS_PNG_SAVE;
557 if (gui_save(mem_filename, &settings) < 0) break;
558 return;
559 }
560 file_selector(FS_PNG_SAVE);
561 }
562
563 char mem_clip_file[PATHBUF];
564
load_clip(int item)565 static void load_clip(int item)
566 {
567 char clip[PATHBUF];
568 int i;
569
570 if (item == -1) // System clipboard
571 i = import_clipboard(FS_CLIPBOARD);
572 else // Disk file
573 {
574 snprintf(clip, PATHBUF, "%s%i", mem_clip_file, item);
575 i = load_image(clip, FS_CLIP_FILE, FT_PNG) == 1;
576 }
577
578 if (!i) alert_box(_("Error"), _("Unable to load clipboard"), NULL);
579
580 update_stuff(UPD_XCOPY);
581 if (i && (MEM_BPP >= mem_clip_bpp)) pressed_paste(TRUE);
582 }
583
save_clip(int item)584 static void save_clip(int item)
585 {
586 ls_settings settings;
587 char clip[PATHBUF];
588 int i;
589
590 if (item == -1) // Exporting clipboard
591 {
592 export_clipboard();
593 return;
594 }
595
596 /* Prepare settings */
597 setup_clip_save(&settings);
598 settings.mode = FS_CLIP_FILE;
599 settings.ftype = FT_PNG;
600
601 snprintf(clip, PATHBUF, "%s%i", mem_clip_file, item);
602 i = save_image(clip, &settings);
603
604 if (i) alert_box(_("Error"), _("Unable to save clipboard"), NULL);
605 }
606
pressed_opacity(int opacity)607 void pressed_opacity(int opacity)
608 {
609 if (IS_INDEXED) opacity = 255;
610 tool_opacity = opacity < 1 ? 1 : opacity > 255 ? 255 : opacity;
611 update_stuff(UPD_OPAC);
612 }
613
toggle_view()614 static void toggle_view()
615 {
616 view_image_only = !view_image_only;
617
618 cmd_showhide(main_menubar, !view_image_only);
619 if (view_image_only)
620 {
621 int i;
622
623 for (i = TOOLBAR_MAIN; i < TOOLBAR_MAX; i++)
624 if (toolbar_boxes[i]) cmd_showhide(toolbar_boxes[i], FALSE);
625 }
626 else toolbar_showhide(); // Switch toolbar/status/palette on if needed
627 }
628
zoom_grid(int state)629 static void zoom_grid(int state)
630 {
631 mem_show_grid = state;
632 update_stuff(UPD_RENDER);
633 }
634
635 static void delete_event(main_dd *dt, void **wdata);
636
quit_all(int mode)637 static void quit_all(int mode)
638 {
639 if (mode || q_quit) delete_event(GET_DDATA(main_window_), main_window_);
640 }
641
642 /* Scroll canvas under cursor instead of moving the cursor */
try_scroll(int * dxy)643 static int try_scroll(int *dxy)
644 {
645 int bx, by, mx[2];
646
647 if (!arrow_scroll || mouse_left_canvas) return (TRUE); // Let cursor go
648
649 cmd_read(scrolledwindow_canvas, NULL);
650 cmd_peekv(scrolledwindow_canvas, mx, sizeof(mx), CSCROLL_LIMITS);
651 dxy[0] -= (bx = bounded(cvxy[0] + dxy[0], 0, mx[0])) - cvxy[0];
652 dxy[1] -= (by = bounded(cvxy[1] + dxy[1], 0, mx[1])) - cvxy[1];
653 if ((bx ^ cvxy[0]) | (by ^ cvxy[1]))
654 {
655 cvxy[0] = bx;
656 cvxy[1] = by;
657 cmd_reset(scrolledwindow_canvas, NULL);
658 }
659 return (dxy[0] | dxy[1]);
660 }
661
662 /* Forward declaration */
663 static void mouse_event(mouse_ext *m, int mflag, int dx, int dy);
664
665 /* For "dual" mouse control */
666 static int unreal_move, lastdx, lastdy;
667
move_mouse(int dx,int dy,int button)668 static void move_mouse(int dx, int dy, int button)
669 {
670 static unsigned int bmasks[4] = { 0, _B1mask, _B2mask, _B3mask };
671 mouse_ext m;
672 int dxy[2], zoom = 1, scale = 1;
673
674 if (!unreal_move) lastdx = lastdy = 0;
675 if (!mem_img[CHN_IMAGE]) return;
676 dx += lastdx; dy += lastdy;
677
678 cmd_peekv(drawing_canvas, &m, sizeof(m), CANVAS_FIND_MOUSE);
679
680 if (button) /* Clicks simulated without extra movements */
681 {
682 m.button = button;
683 m.count = 1; // press
684 mouse_event(&m, 1, dx, dy);
685 m.state |= bmasks[button]; // Shows state _prior_ to event
686 m.count = -1; // release
687 mouse_event(&m, 1, dx, dy);
688 return;
689 }
690
691 /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
692 if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
693 else scale = rint(can_zoom);
694
695 if (zoom > 1) /* Fine control required */
696 {
697 lastdx = dx; lastdy = dy;
698 mouse_event(&m, 1, dx, dy); // motion
699 unreal_move = 2;
700
701 /* Nudge cursor when needed */
702 if ((abs(lastdx) >= zoom) || (abs(lastdy) >= zoom))
703 {
704 lastdx -= (dxy[0] = lastdx * can_zoom) * zoom;
705 lastdy -= (dxy[1] = lastdy * can_zoom) * zoom;
706 if (try_scroll(dxy))
707 {
708 unreal_move = 3;
709 /* Event can be delayed or lost */
710 cmd_setv(drawing_canvas, dxy, CANVAS_BMOVE_MOUSE);
711 }
712 }
713 }
714 else /* Real mouse is precise enough */
715 {
716 unreal_move = 1;
717
718 /* Simulate movement unless actually moved mouse */
719 dxy[0] = dx * scale;
720 dxy[1] = dy * scale;
721 while (TRUE)
722 {
723 if (try_scroll(dxy))
724 {
725 cmd_setv(drawing_canvas, dxy, CANVAS_BMOVE_MOUSE);
726 if (!(dxy[0] | dxy[1])) break; // Moved OK
727 lastdx = dxy[0] / scale;
728 lastdy = dxy[1] / scale;
729 }
730 mouse_event(&m, 1, dx, dy); // motion
731 break;
732 }
733 }
734 }
735
stop_line()736 void stop_line()
737 {
738 int i = line_status == LINE_LINE;
739
740 line_status = LINE_NONE;
741 if (i) repaint_line(NULL);
742 }
743
check_zoom_keys_real(int act_m)744 int check_zoom_keys_real(int act_m)
745 {
746 int action = act_m >> 16;
747
748 if ((action == ACT_ZOOM) || (action == ACT_VIEW) || (action == ACT_VWZOOM))
749 {
750 action_dispatch(action, (act_m & 0xFFFF) - 0x8000, 0, TRUE);
751 return (TRUE);
752 }
753 return (FALSE);
754 }
755
check_zoom_keys(int act_m)756 int check_zoom_keys(int act_m)
757 {
758 int action = act_m >> 16;
759
760 if (check_zoom_keys_real(act_m)) return (TRUE);
761 if ((action == ACT_DOCK) || (action == ACT_QUIT) ||
762 (action == DLG_BRCOSA) || (action == ACT_PAN) ||
763 (action == ACT_CROP) || (action == ACT_SWAP_AB) ||
764 (action == DLG_CHOOSER) || (action == ACT_TOOL))
765 action_dispatch(action, (act_m & 0xFFFF) - 0x8000, 0, TRUE);
766 else return (FALSE);
767
768 return (TRUE);
769 }
770
771 static void menu_action(void *dt, void **wdata, int what, void **where);
772
773 static void *keylist_code[] = {
774 uMENUBAR(menu_action),
775 uMENUITEM(_("Zoom in"), ACTMOD(ACT_ZOOM, 0)),
776 SHORTCUT(plus, 0), SHORTCUT(KP_Add, 0),
777 uMENUITEM(_("Zoom out"), ACTMOD(ACT_ZOOM, -1)),
778 SHORTCUT(minus, 0), SHORTCUT(KP_Subtract, 0),
779 uMENUITEM(_("10% zoom"), ACTMOD(ACT_ZOOM, -10)),
780 SHORTCUT(1, 0), SHORTCUT(KP_1, 0),
781 uMENUITEM(_("25% zoom"), ACTMOD(ACT_ZOOM, -4)),
782 SHORTCUT(2, 0), SHORTCUT(KP_2, 0),
783 uMENUITEM(_("50% zoom"), ACTMOD(ACT_ZOOM, -2)),
784 SHORTCUT(3, 0), SHORTCUT(KP_3, 0),
785 uMENUITEM(_("100% zoom"), ACTMOD(ACT_ZOOM, 1)),
786 SHORTCUT(4, 0), SHORTCUT(KP_4, 0),
787 uMENUITEM(_("400% zoom"), ACTMOD(ACT_ZOOM, 4)),
788 SHORTCUT(5, 0), SHORTCUT(KP_5, 0),
789 uMENUITEM(_("600% zoom"), ACTMOD(ACT_ZOOM, 6)),
790 SHORTCUT0,
791 uMENUITEM(_("800% zoom"), ACTMOD(ACT_ZOOM, 8)),
792 SHORTCUT(6, 0), SHORTCUT(KP_6, 0),
793 uMENUITEM(_("1000% zoom"), ACTMOD(ACT_ZOOM, 10)),
794 SHORTCUT0,
795 uMENUITEM(_("1200% zoom"), ACTMOD(ACT_ZOOM, 12)),
796 SHORTCUT(7, 0), SHORTCUT(KP_7, 0),
797 uMENUITEM(_("1600% zoom"), ACTMOD(ACT_ZOOM, 16)),
798 SHORTCUT(8, 0), SHORTCUT(KP_8, 0),
799 uMENUITEM(_("2000% zoom"), ACTMOD(ACT_ZOOM, 20)),
800 SHORTCUT(9, 0), SHORTCUT(KP_9, 0),
801 uMENUITEM(_("4000% zoom"), ACTMOD(ACT_ZOOM, 40)),
802 SHORTCUT0,
803 uMENUITEM(_("8000% zoom"), ACTMOD(ACT_ZOOM, 80)),
804 SHORTCUT0,
805 uMENUITEM(_("10% opacity"), ACTMOD(ACT_OPAC, 1)),
806 SHORTCUT(1, C), SHORTCUT(KP_1, C),
807 uMENUITEM(_("20% opacity"), ACTMOD(ACT_OPAC, 2)),
808 SHORTCUT(2, C), SHORTCUT(KP_2, C),
809 uMENUITEM(_("30% opacity"), ACTMOD(ACT_OPAC, 3)),
810 SHORTCUT(3, C), SHORTCUT(KP_3, C),
811 uMENUITEM(_("40% opacity"), ACTMOD(ACT_OPAC, 4)),
812 SHORTCUT(4, C), SHORTCUT(KP_4, C),
813 uMENUITEM(_("50% opacity"), ACTMOD(ACT_OPAC, 5)),
814 SHORTCUT(5, C), SHORTCUT(KP_5, C),
815 uMENUITEM(_("60% opacity"), ACTMOD(ACT_OPAC, 6)),
816 SHORTCUT(6, C), SHORTCUT(KP_6, C),
817 uMENUITEM(_("70% opacity"), ACTMOD(ACT_OPAC, 7)),
818 SHORTCUT(7, C), SHORTCUT(KP_7, C),
819 uMENUITEM(_("80% opacity"), ACTMOD(ACT_OPAC, 8)),
820 SHORTCUT(8, C), SHORTCUT(KP_8, C),
821 uMENUITEM(_("90% opacity"), ACTMOD(ACT_OPAC, 9)),
822 SHORTCUT(9, C), SHORTCUT(KP_9, C),
823 uMENUITEM(_("100% opacity"), ACTMOD(ACT_OPAC, 10)),
824 SHORTCUT(0, C), SHORTCUT(KP_0, C),
825 uMENUITEM(_("Increase opacity"), ACTMOD(ACT_OPAC, 0)),
826 SHORTCUT(plus, C), SHORTCUT(KP_Add, C),
827 uMENUITEM(_("Decrease opacity"), ACTMOD(ACT_OPAC, -1)),
828 SHORTCUT(minus, C), SHORTCUT(KP_Subtract, C),
829 uMENUITEM(_("Draw open arrow head"), ACTMOD(ACT_ARROW, 2)),
830 SHORTCUT(a, 0),
831 uMENUITEM(_("Draw closed arrow head"), ACTMOD(ACT_ARROW, 3)),
832 SHORTCUT(s, 0),
833 uMENUITEM(_("Previous colour A"), ACTMOD(ACT_A, -1)),
834 SHORTCUT(bracketleft, 0),
835 uMENUITEM(_("Next colour A"), ACTMOD(ACT_A, 1)),
836 SHORTCUT(bracketright, 0),
837 uMENUITEM(_("Previous colour B"), ACTMOD(ACT_B, -1)),
838 SHORTCUT(bracketleft, S), SHORTCUT(braceleft, S),
839 uMENUITEM(_("Next colour B"), ACTMOD(ACT_B, 1)),
840 SHORTCUT(bracketright, S), SHORTCUT(braceright, S),
841 uMENUITEM(_("Previous pattern"), ACTMOD(ACT_PAT, -1)),
842 SHORTCUT0,
843 uMENUITEM(_("Next pattern"), ACTMOD(ACT_PAT, 1)),
844 SHORTCUT0,
845 uMENUITEM(_("Larger brush"), ACTMOD(ACT_SIZE, 1)),
846 SHORTCUT0,
847 uMENUITEM(_("Smaller brush"), ACTMOD(ACT_SIZE, -1)),
848 SHORTCUT0,
849 uMENUITEM(_("View window - Zoom in"), ACTMOD(ACT_VWZOOM, 0)),
850 SHORTCUT(plus, S), SHORTCUT(KP_Add, S),
851 uMENUITEM(_("View window - Zoom out"), ACTMOD(ACT_VWZOOM, -1)),
852 SHORTCUT(minus, S), SHORTCUT(KP_Subtract, S),
853 /// FIXED KEYS
854 uMENUITEM(NULL, ACTMOD(ACT_QUIT, 0)),
855 SHORTCUT(q, 0), SHORTCUT(q, S), SHORTCUT(q, A),
856 SHORTCUT(q, CS), SHORTCUT(q, CA),
857 SHORTCUT(q, SA), SHORTCUT(q, CSA),
858 uMENUITEM(NULL, ACTMOD(ACT_SEL_MOVE, 5)),
859 SHORTCUT(Left, S), SHORTCUT(KP_Left, S),
860 uMENUITEM(NULL, ACTMOD(ACT_SEL_MOVE, 7)),
861 SHORTCUT(Right, S), SHORTCUT(KP_Right, S),
862 uMENUITEM(NULL, ACTMOD(ACT_SEL_MOVE, 3)),
863 SHORTCUT(Down, S), SHORTCUT(KP_Down, S),
864 uMENUITEM(NULL, ACTMOD(ACT_SEL_MOVE, 9)),
865 SHORTCUT(Up, S), SHORTCUT(KP_Up, S),
866 uMENUITEM(NULL, ACTMOD(ACT_SEL_MOVE, 4)),
867 SHORTCUT(Left, 0), SHORTCUT(KP_Left, 0),
868 uMENUITEM(NULL, ACTMOD(ACT_SEL_MOVE, 6)),
869 SHORTCUT(Right, 0), SHORTCUT(KP_Right, 0),
870 uMENUITEM(NULL, ACTMOD(ACT_SEL_MOVE, 2)),
871 SHORTCUT(Down, 0), SHORTCUT(KP_Down, 0),
872 uMENUITEM(NULL, ACTMOD(ACT_SEL_MOVE, 8)),
873 SHORTCUT(Up, 0), SHORTCUT(KP_Up, 0),
874 uMENUITEM(NULL, ACTMOD(ACT_LR_MOVE, 5)),
875 SHORTCUT(Left, CS), SHORTCUT(KP_Left, CS),
876 uMENUITEM(NULL, ACTMOD(ACT_LR_MOVE, 7)),
877 SHORTCUT(Right, CS), SHORTCUT(KP_Right, CS),
878 uMENUITEM(NULL, ACTMOD(ACT_LR_MOVE, 3)),
879 SHORTCUT(Down, CS), SHORTCUT(KP_Down, CS),
880 uMENUITEM(NULL, ACTMOD(ACT_LR_MOVE, 9)),
881 SHORTCUT(Up, CS), SHORTCUT(KP_Up, CS),
882 uMENUITEM(NULL, ACTMOD(ACT_LR_MOVE, 4)),
883 SHORTCUT(Left, C), SHORTCUT(KP_Left, C),
884 uMENUITEM(NULL, ACTMOD(ACT_LR_MOVE, 6)),
885 SHORTCUT(Right, C), SHORTCUT(KP_Right, C),
886 uMENUITEM(NULL, ACTMOD(ACT_LR_MOVE, 2)),
887 SHORTCUT(Down, C), SHORTCUT(KP_Down, C),
888 uMENUITEM(NULL, ACTMOD(ACT_LR_MOVE, 8)),
889 SHORTCUT(Up, C), SHORTCUT(KP_Up, C),
890 uMENUITEM(NULL, ACTMOD(ACT_ESC, 0)),
891 SHORTCUT(Escape, 0), SHORTCUT(Escape, A),
892 uMENUITEM(NULL, ACTMOD(ACT_COMMIT, 0)),
893 SHORTCUT(Return, 0), SHORTCUT(Return, C),
894 SHORTCUT(Return, A), SHORTCUT(Return, CA),
895 SHORTCUT(KP_Enter, 0), SHORTCUT(KP_Enter, C),
896 SHORTCUT(KP_Enter, A), SHORTCUT(KP_Enter, CA),
897 uMENUITEM(NULL, ACTMOD(ACT_COMMIT, 1)),
898 SHORTCUT(Return, S), SHORTCUT(Return, CS),
899 SHORTCUT(Return, SA), SHORTCUT(Return, CSA),
900 SHORTCUT(KP_Enter, S), SHORTCUT(KP_Enter, CS),
901 SHORTCUT(KP_Enter, SA), SHORTCUT(KP_Enter, CSA),
902 uMENUITEM(NULL, ACTMOD(ACT_RCLICK, 0)),
903 SHORTCUT(BackSpace, 0), SHORTCUT(BackSpace, C),
904 SHORTCUT(BackSpace, S), SHORTCUT(BackSpace, A),
905 SHORTCUT(BackSpace, CS), SHORTCUT(BackSpace, CA),
906 SHORTCUT(BackSpace, SA), SHORTCUT(BackSpace, CSA),
907 RET
908 };
909
key_action(key_ext * key,int toggle)910 int key_action(key_ext *key, int toggle)
911 {
912 main_dd *dt = GET_DDATA(main_window_);
913 void *v, **slot;
914 int act_m;
915
916 cmd_setv(main_keys, key, KEYMAP_KEY);
917 slot = dt->keyslot;
918 // Leave unmapped key to be handled elsewhere
919 if (!slot) act_m = 0;
920 // Do nothing if slot is insensitive
921 else if (!cmd_checkv(slot, SLOT_SENSITIVE)) act_m = ACTMOD_DUMMY;
922 // Activate toggleable widget if allowed
923 else if (toggle && (v = slot_data(slot, dt)))
924 {
925 cmd_set(slot, cmd_checkv(slot, SLOT_RADIO) || !*(int *)v);
926 act_m = ACTMOD_DUMMY;
927 }
928 else act_m = TOOL_ID(slot);
929 return (act_m);
930 }
931
dock_focused()932 int dock_focused()
933 {
934 return (cmd_checkv(dock_book, SLOT_FOCUSED));
935 }
936
handle_keypress(main_dd * dt,void ** wdata,int what,void ** where,key_ext * keydata)937 static int handle_keypress(main_dd *dt, void **wdata, int what, void **where,
938 key_ext *keydata)
939 {
940 int act_m = key_action(keydata, TRUE);
941
942 if (!act_m) return (FALSE);
943
944 if (act_m != ACTMOD_DUMMY)
945 action_dispatch(act_m >> 16, (act_m & 0xFFFF) - 0x8000, 0, TRUE);
946 return (TRUE);
947 }
948
draw_arrow(int mode)949 static void draw_arrow(int mode)
950 {
951 int i, xa1, xa2, ya1, ya2, minx, maxx, miny, maxy, w, h;
952 double uvx, uvy; // Line length & unit vector lengths
953 int oldmode = mem_undo_opacity;
954 grad_info svgrad;
955
956
957 if (!((tool_type == TOOL_LINE) && (line_status != LINE_NONE) &&
958 ((line_x1 != line_x2) || (line_y1 != line_y2)))) return;
959 svgrad = gradient[mem_channel];
960 line_to_gradient();
961
962 // Calculate 2 coords for arrow corners
963 uvy = sqrt((line_x1 - line_x2) * (line_x1 - line_x2) +
964 (line_y1 - line_y2) * (line_y1 - line_y2));
965 uvx = (line_x1 - line_x2) / uvy;
966 uvy = (line_y1 - line_y2) / uvy;
967
968 xa1 = rint(line_x2 + tool_flow * (uvx - uvy * 0.5));
969 xa2 = rint(line_x2 + tool_flow * (uvx + uvy * 0.5));
970 ya1 = rint(line_y2 + tool_flow * (uvy + uvx * 0.5));
971 ya2 = rint(line_y2 + tool_flow * (uvy - uvx * 0.5));
972
973 // !!! Call this, or let undo engine do it?
974 // mem_undo_prepare();
975 pen_down = 0;
976 do_tool_action(TC_LINE_ARROW, line_x2, line_y2, MAX_PRESSURE);
977 line_status = LINE_LINE;
978
979 // Draw arrow lines & circles
980 mem_undo_opacity = TRUE;
981 f_circle(xa1, ya1, tool_size);
982 f_circle(xa2, ya2, tool_size);
983 tline(xa1, ya1, line_x2, line_y2, tool_size);
984 tline(xa2, ya2, line_x2, line_y2, tool_size);
985
986 if (mode == 3)
987 {
988 // Draw 3rd line and fill arrowhead
989 tline(xa1, ya1, xa2, ya2, tool_size );
990 poly_points = 0;
991 poly_add(line_x2, line_y2);
992 poly_add(xa1, ya1);
993 poly_add(xa2, ya2);
994 poly_paint();
995 poly_points = 0;
996 }
997 mem_undo_opacity = oldmode;
998 gradient[mem_channel] = svgrad;
999 mem_undo_prepare();
1000 pen_down = 0;
1001
1002 // Update screen areas
1003 minx = xa1 < xa2 ? xa1 : xa2;
1004 if (minx > line_x2) minx = line_x2;
1005 maxx = xa1 > xa2 ? xa1 : xa2;
1006 if (maxx < line_x2) maxx = line_x2;
1007
1008 miny = ya1 < ya2 ? ya1 : ya2;
1009 if (miny > line_y2) miny = line_y2;
1010 maxy = ya1 > ya2 ? ya1 : ya2;
1011 if (maxy < line_y2) maxy = line_y2;
1012
1013 i = (tool_size + 1) >> 1;
1014 minx -= i; miny -= i; maxx += i; maxy += i;
1015
1016 w = maxx - minx + 1;
1017 h = maxy - miny + 1;
1018
1019 update_stuff(UPD_IMGP);
1020 lr_update_area(layer_selected, minx, miny, w, h);
1021 }
1022
check_for_changes()1023 int check_for_changes() // 1=STOP, 2=IGNORE, -10=NOT CHANGED
1024 {
1025 if (!mem_changed) return (-10);
1026 return (alert_box(_("Warning"),
1027 _("This canvas/palette contains changes that have not been saved. Do you really want to lose these changes?"),
1028 _("Cancel Operation"), _("Lose Changes"), NULL));
1029 }
1030
var_init()1031 void var_init()
1032 {
1033 inilist *ilp;
1034
1035 /* Load listed settings */
1036 for (ilp = ini_bool; ilp->name; ilp++)
1037 *(ilp->var) = inifile_get_gboolean(ilp->name, ilp->defv);
1038 for (ilp = ini_int; ilp->name; ilp++)
1039 *(ilp->var) = inifile_get_gint32(ilp->name, ilp->defv);
1040
1041 /* Initialize undo memory space */
1042 if (mem_undo_limit <= 0)
1043 {
1044 unsigned mem = sys_mem_size();
1045 /* Limit usable space to 2 Gb on 32-bit systems */
1046 if ((sizeof(void *) <= 4) && (mem > 2048)) mem = 2048;
1047 /* Take 1/4 of memory space, rounded up to nearest 32 Mb */
1048 mem_undo_limit = ((mem / 4) + 31) & ~31;
1049 /* But no less than 32 Mb */
1050 if (!mem_undo_limit) mem_undo_limit = 32;
1051 }
1052
1053 #ifdef U_TIFF
1054 /* Load TIFF types */
1055 {
1056 int i, tr, ti, tb;
1057 tr = inifile_get_gint32("tiffTypeRGB", 1 /* COMPRESSION_NONE */);
1058 ti = inifile_get_gint32("tiffTypeI", 1 /* COMPRESSION_NONE */);
1059 tb = inifile_get_gint32("tiffTypeBW", 1 /* COMPRESSION_NONE */);
1060 init_tiff_formats();
1061 for (i = 0; tiff_formats[i].name; i++)
1062 {
1063 if (tiff_formats[i].id == tr) tiff_rtype = i;
1064 if (tiff_formats[i].id == ti) tiff_itype = i;
1065 if (tiff_formats[i].id == tb) tiff_btype = i;
1066 }
1067 }
1068 #endif
1069 }
1070
string_init()1071 void string_init()
1072 {
1073 int i;
1074
1075 for (i = 0; i < NUM_CHANNELS + 1; i++)
1076 allchannames[i] = channames[i] = __(channames_[i]);
1077 channames[CHN_IMAGE] = "";
1078 for (i = 0; i < NUM_CSPACES; i++)
1079 cspnames[i] = __(cspnames_[i]);
1080 }
1081
1082 static void toggle_dock(int state);
1083
delete_event(main_dd * dt,void ** wdata)1084 static void delete_event(main_dd *dt, void **wdata)
1085 {
1086 inilist *ilp;
1087 int i;
1088
1089 i = layers_total ? check_layers_for_changes() : check_for_changes();
1090 if (i == -10)
1091 {
1092 i = 2;
1093 if (inifile_get_gboolean("exitToggle", FALSE))
1094 i = alert_box(MT_VERSION, _("Do you really want to quit?"),
1095 _("NO"), _("YES"), NULL);
1096 }
1097 if (i != 2) return; // Cancel quitting
1098
1099 /* Store listed settings */
1100 for (ilp = ini_bool; ilp->name; ilp++)
1101 inifile_set_gboolean(ilp->name, *(ilp->var));
1102 for (ilp = ini_int; ilp->name; ilp++)
1103 inifile_set_gint32(ilp->name, *(ilp->var));
1104
1105 #ifdef U_TIFF
1106 /* Store TIFF types */
1107 inifile_set_gint32("tiffTypeRGB", tiff_formats[tiff_rtype].id);
1108 inifile_set_gint32("tiffTypeI", tiff_formats[tiff_itype].id);
1109 inifile_set_gint32("tiffTypeBW", tiff_formats[tiff_btype].id);
1110 #endif
1111
1112 update_recent_files(TRUE); // Store the list
1113
1114 if (files_passed <= 1) inifile_set_gboolean("showDock", show_dock);
1115 toggle_dock(FALSE); // To remember dock size
1116
1117 // Get rid of extra windows + remember positions
1118 delete_layers_window();
1119
1120 // Remember the toolbar settings
1121 toolbar_settings_exit(NULL, NULL);
1122
1123 run_destroy(wdata);
1124 }
1125
canvas_scroll(main_dd * dt,void ** wdata,int what,void ** where,scroll_ext * scroll)1126 static void canvas_scroll(main_dd *dt, void **wdata, int what, void **where,
1127 scroll_ext *scroll)
1128 {
1129 if (scroll_zoom)
1130 {
1131 action_dispatch(origin_slot(where) == drawing_canvas ?
1132 ACT_ZOOM : ACT_VWZOOM, scroll->yscroll > 0 ? -1 : 0,
1133 FALSE, TRUE);
1134 scroll->xscroll = scroll->yscroll = 0;
1135 }
1136 else if (scroll->state & _Cmask) /* Convert up-down into left-right */
1137 {
1138 scroll->xscroll = scroll->yscroll;
1139 scroll->yscroll = 0;
1140 }
1141 }
1142
1143
grad_stroke(int x,int y)1144 void grad_stroke(int x, int y)
1145 {
1146 grad_info *grad = gradient + mem_channel;
1147 double d, stroke;
1148 int i, j;
1149
1150 /* No usable gradient */
1151 if (grad->wmode == GRAD_MODE_NONE) return;
1152
1153 if (!pen_down || (tool_type > TOOL_SPRAY)) /* Begin stroke */
1154 {
1155 grad_path = grad->xv = grad->yv = 0.0;
1156 }
1157 else /* Continue stroke */
1158 {
1159 i = x - tool_ox; j = y - tool_oy;
1160 stroke = sqrt(i * i + j * j);
1161 /* First step - anchor rear end */
1162 if (grad_path == 0.0)
1163 {
1164 d = tool_size * 0.5 / stroke;
1165 grad_x0 = tool_ox - i * d;
1166 grad_y0 = tool_oy - j * d;
1167 }
1168 /* Scalar product */
1169 d = (tool_ox - grad_x0) * (x - tool_ox) +
1170 (tool_oy - grad_y0) * (y - tool_oy);
1171 if (d < 0.0) /* Going backward - flip rear */
1172 {
1173 d = tool_size * 0.5 / stroke;
1174 grad_x0 = x - i * d;
1175 grad_y0 = y - j * d;
1176 grad_path += tool_size + stroke;
1177 }
1178 else /* Going forward or sideways - drag rear */
1179 {
1180 stroke = sqrt((x - grad_x0) * (x - grad_x0) +
1181 (y - grad_y0) * (y - grad_y0));
1182 d = tool_size * 0.5 / stroke;
1183 grad_x0 = x + (grad_x0 - x) * d;
1184 grad_y0 = y + (grad_y0 - y) * d;
1185 grad_path += stroke - tool_size * 0.5;
1186 }
1187 d = 2.0 / (double)tool_size;
1188 grad->xv = (x - grad_x0) * d;
1189 grad->yv = (y - grad_y0) * d;
1190 }
1191 }
1192
1193 #define GF_UPDATE 1
1194 #define GF_REDRAW 2
1195 #define GF_DRAW 4
1196
do_grad_action(int cmd,int x,int y)1197 static void do_grad_action(int cmd, int x, int y)
1198 {
1199 grad_info *grad = gradient + mem_channel;
1200 int old[4], prev = grad->status, upd = 0;
1201
1202 copy4(old, grad->xy);
1203 switch (cmd)
1204 {
1205 case TC_GRAD_START:
1206 grad->xy[0] = grad->xy[2] = x;
1207 grad->xy[1] = grad->xy[3] = y;
1208 grad->status = GRAD_END;
1209 upd = GF_UPDATE | GF_DRAW;
1210 break;
1211 case TC_GRAD_PICK0:
1212 grad->status = GRAD_START;
1213 // Fallthrough
1214 case TC_GRAD_SET0:
1215 grad->xy[0] = x;
1216 grad->xy[1] = y;
1217 if (cmd == TC_GRAD_SET0) prev = grad->status = GRAD_DONE;
1218 upd = GF_UPDATE | GF_REDRAW;
1219 break;
1220 case TC_GRAD_PICK1:
1221 grad->status = GRAD_END;
1222 // Fallthrough
1223 case TC_GRAD_SET1:
1224 grad->xy[2] = x;
1225 grad->xy[3] = y;
1226 if (cmd == TC_GRAD_SET1) prev = grad->status = GRAD_DONE;
1227 upd = GF_UPDATE | GF_REDRAW;
1228 break;
1229 case TC_GRAD_DRAG0: case TC_GRAD_DRAG1:
1230 {
1231 int *xy = grad->xy + (cmd == TC_GRAD_DRAG0 ? 0 : 2);
1232 if ((xy[0] != x) || (xy[1] != y))
1233 {
1234 xy[0] = x;
1235 xy[1] = y;
1236 upd = GF_UPDATE | GF_REDRAW;
1237 }
1238 break;
1239 }
1240 case TC_GRAD_CLEAR:
1241 grad->status = GRAD_NONE;
1242 upd = GF_UPDATE | GF_DRAW;
1243 break;
1244 }
1245
1246 if (upd & GF_UPDATE) grad_update(grad);
1247 if (upd & (GF_REDRAW | GF_DRAW))
1248 {
1249 if ((prev == GRAD_DONE) && grad_opacity)
1250 cmd_repaint(drawing_canvas);
1251 else repaint_grad(upd & GF_REDRAW ? old : NULL);
1252 }
1253 }
1254
grad_action(int count,int button,int x,int y)1255 static int grad_action(int count, int button, int x, int y)
1256 {
1257 grad_info *grad = gradient + mem_channel;
1258 int i, j, cmd = TC_NONE;
1259
1260 /* Left click sets points and picks them up again */
1261 if ((count == 1) && (button == 1))
1262 {
1263 /* Start anew */
1264 if (grad->status == GRAD_NONE) cmd = TC_GRAD_START;
1265 /* Place starting point */
1266 else if (grad->status == GRAD_START) cmd = TC_GRAD_SET0;
1267 /* Place end point */
1268 else if (grad->status == GRAD_END) cmd = TC_GRAD_SET1;
1269 /* Pick up nearest end */
1270 else if (grad->status == GRAD_DONE)
1271 {
1272 i = (x - grad->xy[0]) * (x - grad->xy[0]) +
1273 (y - grad->xy[1]) * (y - grad->xy[1]);
1274 j = (x - grad->xy[2]) * (x - grad->xy[2]) +
1275 (y - grad->xy[3]) * (y - grad->xy[3]);
1276 cmd = i < j ? TC_GRAD_PICK0 : TC_GRAD_PICK1;
1277 }
1278 }
1279
1280 /* Everything but left click is irrelevant when no gradient */
1281 else if (grad->status == GRAD_NONE);
1282
1283 /* Right click deletes the gradient */
1284 else if (count == 1) cmd = TC_GRAD_CLEAR; /* button != 1 */
1285
1286 /* Motion is irrelevant with gradient in place */
1287 else if (grad->status == GRAD_DONE);
1288
1289 /* Motion drags points around */
1290 else if (!count)
1291 cmd = grad->status == GRAD_START ? TC_GRAD_DRAG0 : TC_GRAD_DRAG1;
1292
1293 return (cmd);
1294 }
1295
1296 /* Pick color A/B from canvas, or use one given */
pick_color(int ox,int oy,int ab,int area[4],int pixel)1297 static int pick_color(int ox, int oy, int ab, int area[4], int pixel)
1298 {
1299 int rgba = RGBA_mode && (mem_channel == CHN_IMAGE);
1300 int upd = 0, alpha = 255; // opaque
1301
1302 if (pixel < 0) // If no pixel value is given
1303 {
1304 /* Default alpha */
1305 alpha = channel_col_[ab][CHN_ALPHA];
1306 /* Average brush or selection area on double click */
1307 while (area)
1308 {
1309 int rect[4];
1310
1311 /* Clip to image */
1312 copy4(rect, area);
1313 rect[2] += rect[0];
1314 rect[3] += rect[1];
1315 if (!clip(rect, 0, 0, mem_width, mem_height, rect)) break;
1316 /* Average utility channel */
1317 if (mem_channel != CHN_IMAGE) pixel = average_channel(
1318 mem_img[mem_channel], mem_width, rect);
1319 if (MEM_BPP != 3) break;
1320 /* Average alpha if needed */
1321 if (rgba && mem_img[CHN_ALPHA]) alpha = average_channel(
1322 mem_img[CHN_ALPHA], mem_width, rect);
1323 /* Average image channel as RGBA or RGB */
1324 pixel = average_pixels(mem_img[CHN_IMAGE],
1325 rgba && alpha && !channel_dis[CHN_ALPHA] ?
1326 mem_img[CHN_ALPHA] : NULL, mem_width, rect);
1327 break;
1328 }
1329 /* Failing that, just pick color from image */
1330 if (pixel < 0)
1331 {
1332 pixel = get_pixel(ox, oy);
1333 if (mem_img[CHN_ALPHA])
1334 alpha = mem_img[CHN_ALPHA][mem_width * oy + ox];
1335 }
1336 }
1337
1338 if (rgba)
1339 {
1340 upd = channel_col_[ab][CHN_ALPHA] ^ alpha;
1341 channel_col_[ab][CHN_ALPHA] = alpha;
1342 }
1343
1344 if (mem_channel != CHN_IMAGE)
1345 {
1346 upd |= channel_col_[ab][mem_channel] ^ pixel;
1347 channel_col_[ab][mem_channel] = pixel;
1348 }
1349 else if (mem_img_bpp == 1)
1350 {
1351 upd |= mem_col_[ab] ^ pixel;
1352 mem_col_[ab] = pixel;
1353 mem_col_24[ab] = mem_pal[pixel];
1354 }
1355 else
1356 {
1357 png_color *col = mem_col_24 + ab;
1358
1359 upd |= PNG_2_INT(*col) ^ pixel;
1360 col->red = INT_2_R(pixel);
1361 col->green = INT_2_G(pixel);
1362 col->blue = INT_2_B(pixel);
1363 }
1364 return (upd);
1365 }
1366
tool_done()1367 static void tool_done()
1368 {
1369 tint_mode[2] = 0; // Paranoia
1370 clone_status &= CLONE_ABS;
1371 pen_down = 0;
1372 if (col_reverse)
1373 {
1374 col_reverse = FALSE;
1375 mem_swap_cols(FALSE);
1376 }
1377
1378 mem_undo_prepare();
1379 }
1380
1381 static int get_bkg(int xc, int yc, int dclick);
1382
xmmod(int x,int y)1383 static inline int xmmod(int x, int y)
1384 {
1385 return (x - (x % y));
1386 }
1387
1388 /* Mouse event from button/motion on the canvas */
mouse_event(mouse_ext * m,int mflag,int dx,int dy)1389 static void mouse_event(mouse_ext *m, int mflag, int dx, int dy)
1390 {
1391 static int tool_fix, tool_fixv; // Fixate on axis
1392 int new_cursor;
1393 int i, x0, y0, ox, oy;
1394 int x, y, cmd;
1395 int zoom = 1, scale = 1;
1396
1397
1398 /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
1399 if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
1400 else scale = rint(can_zoom);
1401
1402 x0 = floor_div((m->x - margin_main_x) * zoom, scale) + dx;
1403 y0 = floor_div((m->y - margin_main_y) * zoom, scale) + dy;
1404
1405 ox = x0 < 0 ? 0 : x0 >= mem_width ? mem_width - 1 : x0;
1406 oy = y0 < 0 ? 0 : y0 >= mem_height ? mem_height - 1 : y0;
1407
1408 if (!mflag) /* Coordinate fixation */
1409 {
1410 if (!(m->state & _Smask)) tool_fix = 0;
1411 else if (!(m->state & _Cmask)) /* Shift */
1412 {
1413 if (tool_fix != 1) tool_fixv = x0;
1414 tool_fix = 1;
1415 }
1416 else /* Ctrl+Shift */
1417 {
1418 if (tool_fix != 2) tool_fixv = y0;
1419 tool_fix = 2;
1420 }
1421 }
1422 /* No use when moving cursor by keyboard */
1423 else if (!m->count) tool_fix = 0;
1424
1425 if (!tool_fix);
1426 /* For rectangular selection it makes sense to fix its width/height */
1427 else if ((tool_type == TOOL_SELECT) && (m->button == 1) &&
1428 ((marq_status == MARQUEE_DONE) || (marq_status == MARQUEE_SELECTING)))
1429 {
1430 if (tool_fix == 1) x0 = marq_x2;
1431 else y0 = marq_y2;
1432 }
1433 else if (tool_fix == 1) x0 = tool_fixv;
1434 else /* if (tool_fix == 2) */ y0 = tool_fixv;
1435
1436 x = x0; y = y0;
1437 if (tgrid_snap) /* Snap to grid */
1438 {
1439 int xy[2] = { x0, y0 };
1440
1441 /* For everything but rectangular selection, snap with rounding
1442 * feels more natural than with flooring - WJ */
1443 if (tool_type != TOOL_SELECT)
1444 {
1445 xy[0] += tgrid_dx >> 1;
1446 xy[1] += tgrid_dy >> 1;
1447 }
1448 snap_xy(xy);
1449 if ((x = x0 = xy[0]) < 0) x += xmmod(tgrid_dx - x - 1, tgrid_dx);
1450 if (x >= mem_width) x -= xmmod(tgrid_dx + x - mem_width, tgrid_dx);
1451 if ((y = y0 = xy[1]) < 0) y += xmmod(tgrid_dy - y - 1, tgrid_dy);
1452 if (y >= mem_height) y -= xmmod(tgrid_dy + y - mem_height, tgrid_dy);
1453 }
1454
1455 x = x < 0 ? 0 : x >= mem_width ? mem_width - 1 : x;
1456 y = y < 0 ? 0 : y >= mem_height ? mem_height - 1 : y;
1457
1458 /* ****** Release-event-specific code ****** */
1459
1460 if (m->count < 0)
1461 {
1462 cmd = TC_NONE;
1463 if ((tool_type == TOOL_LINE) && (m->button == 1) &&
1464 (line_status == LINE_START)) cmd = TC_LINE_NEXT;
1465
1466 if (((tool_type == TOOL_SELECT) || (tool_type == TOOL_POLYGON)) &&
1467 (m->button == 1) && ((marq_status == MARQUEE_SELECTING) ||
1468 (marq_status == MARQUEE_PASTE_DRAG))) cmd = TC_SEL_STOP;
1469
1470 // Finish off dragged polygon selection
1471 if ((tool_type == TOOL_POLYGON) && (poly_status == POLY_DRAGGING))
1472 cmd = TC_POLY_CLOSE;
1473
1474 if (cmd != TC_NONE) do_tool_action(cmd, x, y, 0);
1475
1476 tool_done();
1477 update_menus();
1478
1479 return;
1480 }
1481
1482 /* ****** Common click/motion handling code ****** */
1483
1484 if ((m->state & _CSmask) == _Cmask)
1485 {
1486 /* Delete point from polygon */
1487 if ((m->button == 3) && (tool_type == TOOL_POLYGON))
1488 {
1489 if (m->count == 1) do_tool_action(TC_POLY_DEL, x, y, 0);
1490 }
1491 else if (m->button == 2) /* Auto-dither */
1492 {
1493 if ((mem_channel == CHN_IMAGE) && (mem_img_bpp == 3))
1494 pressed_dither_A();
1495 }
1496 /* Snap clone source to point */
1497 else if ((m->button == 1) && (tool_type == TOOL_CLONE))
1498 {
1499 clone_x = x;
1500 clone_y = y;
1501 clone_dx = clone_dy = 0;
1502 move_perim(x, y);
1503 }
1504 /* Set colour A/B */
1505 else if ((m->button == 1) || (m->button == 3))
1506 {
1507 int pix, rect[4];
1508
1509 /* Pick color from tracing image when possible */
1510 pix = get_bkg(m->x + dx * scale, m->y + dy * scale,
1511 m->count == 2);
1512
1513 /* Double click averages an area */
1514 rect[2] = 0;
1515 if (pix >= 0); // Got it from tracing image
1516 else if (m->count != 2); // No double click
1517 // Have brush square
1518 else if (!NO_PERIM(tool_type))
1519 {
1520 int ts2 = tool_size >> 1;
1521 rect[0] = ox - ts2; rect[1] = oy - ts2;
1522 rect[2] = rect[3] = tool_size;
1523 }
1524 // Have selection marquee
1525 else if ((marq_status > MARQUEE_NONE) &&
1526 (marq_status < MARQUEE_PASTE)) marquee_at(rect);
1527
1528 if (pick_color(ox, oy,
1529 m->button == 3, // A for left, B for right
1530 rect[2] ? rect : NULL, // Area to average
1531 pix)) update_stuff(UPD_CAB);
1532 }
1533 }
1534
1535 else if ((m->button == 2) || ((m->button == 3) && (m->state & _Smask)))
1536 set_zoom_centre(ox, oy);
1537
1538 else if (tool_type == TOOL_GRADIENT)
1539 {
1540 cmd = grad_action(m->count, m->button, x0, y0);
1541 if (cmd != TC_NONE) do_grad_action(cmd, x0, y0);
1542 }
1543
1544 /* Pure moves are handled elsewhere */
1545 else if (m->button)
1546 {
1547 cmd = tool_action(m->count, m->button, x, y);
1548 if (cmd != TC_NONE)
1549 do_tool_action(cmd | TCF_ONCE, x, y, m->pressure);
1550 }
1551
1552
1553 /* ****** Now to mouse-move-specific part ****** */
1554
1555 /* A focus change caused by a click results in leave and enter events
1556 * immediately followed by a click event, so the task to restore the
1557 * tracking state then by necessity falls to that click - WJ */
1558 if (!m->count || !is_tracking)
1559 {
1560 if ((poly_status == POLY_SELECTING) && !m->button)
1561 stretch_poly_line(x, y);
1562
1563 if ((tool_type == TOOL_SELECT) || (tool_type == TOOL_POLYGON))
1564 {
1565 if (marq_status == MARQUEE_DONE)
1566 {
1567 i = close_to(x, y);
1568 if (i != cursor_corner) // Stops excessive CPU/flickering
1569 set_cursor(corner_cursor[cursor_corner = i]);
1570 }
1571 if (marq_status >= MARQUEE_PASTE)
1572 {
1573 new_cursor = (x >= marq_x1) && (x <= marq_x2) &&
1574 (y >= marq_y1) && (y <= marq_y2); // Normal/4way
1575 if (new_cursor != cursor_corner) // Stops flickering on slow hardware
1576 set_cursor((cursor_corner = new_cursor) ?
1577 move_cursor : NULL);
1578 }
1579 }
1580 update_xy_bar(x, y);
1581
1582 /* TOOL PERIMETER BOX UPDATES */
1583
1584 if ((tool_type == TOOL_CLONE) && !(clone_status & CLONE_DRAG))
1585 {
1586 if ((clone_status == CLONE_ABS) ||
1587 (clone_status == (CLONE_REL | CLONE_TRACK)))
1588 {
1589 /* Source stays put */
1590 clone_dx = clone_x - x;
1591 clone_dy = clone_y - y;
1592 }
1593 else
1594 {
1595 /* Source follows cursor */
1596 clone_x = x + clone_dx;
1597 clone_y = y + clone_dy;
1598 }
1599 clone_status = (!m->button && (m->state & _Cmask) ?
1600 CLONE_TRACK : 0) | (clone_status & CLONE_ABS);
1601 }
1602
1603 move_perim(x, y);
1604
1605 /* LINE UPDATES */
1606
1607 if ((tool_type == TOOL_LINE) && (line_status == LINE_LINE) &&
1608 ((line_x2 != x) || (line_y2 != y)))
1609 {
1610 int old[4];
1611
1612 copy4(old, line_xy);
1613 line_x2 = x;
1614 line_y2 = y;
1615 repaint_line(old);
1616 }
1617
1618 is_tracking = TRUE;
1619 }
1620
1621 update_sel_bar(FALSE);
1622 }
1623
canvas_mouse(main_dd * dt,void ** wdata,int what,void ** where,mouse_ext * mouse)1624 static int canvas_mouse(main_dd *dt, void **wdata, int what, void **where,
1625 mouse_ext *mouse)
1626 {
1627 int rm = unreal_move;
1628
1629 mouse_left_canvas = FALSE;
1630
1631 /* Steal focus from dock window */
1632 if ((mouse->count > 0) && dock_focused())
1633 cmd_setv(main_window_, NULL, WINDOW_FOCUS);
1634
1635 /* Skip synthetic mouse moves */
1636 if (!mouse->count)
1637 {
1638 if (unreal_move == 3)
1639 {
1640 unreal_move = 2;
1641 return (TRUE);
1642 }
1643 unreal_move = 0;
1644 }
1645
1646 /* Do nothing if no image */
1647 if ((mouse->count >= 0) && !mem_img[CHN_IMAGE]) return (TRUE);
1648
1649 /* If cursor got warped, will have another movement event to handle */
1650 if (!mouse->count && mouse->button && (tool_type == TOOL_SELECT) &&
1651 cmd_checkv(where, MOUSE_BOUND)) return (TRUE);
1652
1653 mouse_event(mouse, rm & 1, 0, 0);
1654
1655 return (mouse->count >= 0);
1656 }
1657
1658 // Mouse enters/leaves the canvas
canvas_enter_leave(main_dd * dt,void ** wdata,int what,void ** where,void * enter)1659 static void canvas_enter_leave(main_dd *dt, void **wdata, int what, void **where,
1660 void *enter)
1661 {
1662 if (enter)
1663 {
1664 mouse_left_canvas = FALSE;
1665 return;
1666 }
1667 /* If leaving canvas */
1668 /* Only do this if we have an image */
1669 if (!mem_img[CHN_IMAGE]) return;
1670 mouse_left_canvas = TRUE;
1671 if (status_on[STATUS_CURSORXY])
1672 cmd_setv(label_bar[STATUS_CURSORXY], "", LABEL_VALUE);
1673 if (status_on[STATUS_PIXELRGB])
1674 cmd_setv(label_bar[STATUS_PIXELRGB], "", LABEL_VALUE);
1675 if (perim_status > 0) clear_perim(&perim_state);
1676
1677 clone_status &= ~CLONE_TRACK; // No tracking w/o perimeter
1678
1679 if (tool_type == TOOL_GRADIENT)
1680 {
1681 /* Let leave hide the dragged line */
1682 grad_info *grad = gradient + mem_channel;
1683 if ((grad->status == GRAD_START) || (grad->status == GRAD_END))
1684 repaint_grad(NULL);
1685 }
1686
1687 if (((tool_type == TOOL_POLYGON) && (poly_status == POLY_SELECTING)) ||
1688 ((tool_type == TOOL_LINE) && (line_status == LINE_LINE)))
1689 repaint_line(NULL);
1690
1691 is_tracking = FALSE;
1692 }
1693
render_background(unsigned char * rgb,int x0,int y0,int wid,int hgt,int fwid)1694 static int render_background(unsigned char *rgb, int x0, int y0, int wid, int hgt, int fwid)
1695 {
1696 int i, j, k, scale, dx, dy, step, ii, jj, ii0, px, py;
1697 int xwid = 0, xhgt = 0, wid3 = wid * 3;
1698
1699 /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
1700 if (!chequers_optimize) step = 8;
1701 else if (can_zoom < 1.0) step = 6;
1702 else
1703 {
1704 scale = rint(can_zoom);
1705 step = scale < 4 ? 6 : scale == 4 ? 8 : scale;
1706 }
1707 dx = x0 % step;
1708 dy = y0 % step;
1709
1710 py = (x0 / step + y0 / step) & 1;
1711 if (hgt + dy > step)
1712 {
1713 jj = step - dy;
1714 xhgt = (hgt + dy) % step;
1715 if (!xhgt) xhgt = step;
1716 hgt -= xhgt;
1717 xhgt -= step;
1718 }
1719 else jj = hgt--;
1720 if (wid + dx > step)
1721 {
1722 ii0 = step - dx;
1723 xwid = (wid + dx) % step;
1724 if (!xwid) xwid = step;
1725 wid -= xwid;
1726 xwid -= step;
1727 }
1728 else ii0 = wid--;
1729
1730 for (j = 0; ; jj += step)
1731 {
1732 if (j >= hgt)
1733 {
1734 if (j > hgt) break;
1735 jj += xhgt;
1736 }
1737 px = py;
1738 ii = ii0;
1739 for (i = 0; ; ii += step)
1740 {
1741 if (i >= wid)
1742 {
1743 if (i > wid) break;
1744 ii += xwid;
1745 }
1746 k = (ii - i) * 3;
1747 memset(rgb, greyz[px], k);
1748 rgb += k;
1749 px ^= 1;
1750 i = ii;
1751 }
1752 rgb += fwid - wid3;
1753 for(j++; j < jj; j++)
1754 {
1755 memcpy(rgb, rgb - fwid, wid3);
1756 rgb += fwid;
1757 }
1758 py ^= 1;
1759 }
1760
1761 return (!chequers_optimize); // Request async_bk
1762 }
1763
1764 /// TRACING IMAGE
1765
1766 unsigned char *bkg_rgb;
1767 int bkg_x, bkg_y, bkg_w, bkg_h, bkg_scale, bkg_flag;
1768
config_bkg(int src)1769 int config_bkg(int src)
1770 {
1771 image_info *img;
1772 int l;
1773
1774 if (!src) return (TRUE); // No change
1775
1776 // Remove old
1777 free(bkg_rgb);
1778 bkg_rgb = NULL;
1779 bkg_w = bkg_h = 0;
1780
1781 img = src == 2 ? &mem_image : src == 3 ? &mem_clip : NULL;
1782 if (!img || !img->img[CHN_IMAGE]) return (TRUE); // No image
1783
1784 l = img->width * img->height;
1785 bkg_rgb = malloc(l * 3);
1786 if (!bkg_rgb) return (FALSE);
1787
1788 if (img->bpp == 1)
1789 do_convert_rgb(0, 1, l, bkg_rgb, img->img[CHN_IMAGE], mem_pal);
1790 else memcpy(bkg_rgb, img->img[CHN_IMAGE], l * 3);
1791 bkg_w = img->width;
1792 bkg_h = img->height;
1793 return (TRUE);
1794 }
1795
render_bkg(rgbcontext * ctx)1796 static int render_bkg(rgbcontext *ctx)
1797 {
1798 unsigned char *src, *dest;
1799 int i, x0, x, y, ty, w3, l3, d0, dd, adj, bs, rxy[4];
1800 int zoom = 1, scale = 1;
1801
1802
1803 /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
1804 if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
1805 else scale = rint(can_zoom);
1806
1807 bs = bkg_scale * zoom;
1808 adj = bs - scale > 0 ? bs - scale : 0;
1809 if (!clip(rxy, floor_div(bkg_x * scale + adj, bs) + margin_main_x,
1810 floor_div(bkg_y * scale + adj, bs) + margin_main_y,
1811 floor_div((bkg_x + bkg_w) * scale + adj, bs) + margin_main_x,
1812 floor_div((bkg_y + bkg_h) * scale + adj, bs) + margin_main_y,
1813 ctx->xy)) return (FALSE);
1814
1815 w3 = (ctx->xy[2] - ctx->xy[0]) * 3;
1816 dest = ctx->rgb + (rxy[1] - ctx->xy[1]) * w3 + (rxy[0] - ctx->xy[0]) * 3;
1817 l3 = (rxy[2] - rxy[0]) * 3;
1818
1819 d0 = (rxy[0] - margin_main_x) * bs;
1820 x0 = floor_div(d0, scale);
1821 d0 -= x0 * scale;
1822 x0 -= bkg_x;
1823
1824 for (ty = -1 , i = rxy[1]; i < rxy[3]; i++)
1825 {
1826 y = floor_div((i - margin_main_y) * bs, scale) - bkg_y;
1827 if (y != ty)
1828 {
1829 src = bkg_rgb + (y * bkg_w + x0) * 3;
1830 for (dd = d0 , x = rxy[0]; x < rxy[2]; x++ , dest += 3)
1831 {
1832 dest[0] = src[0];
1833 dest[1] = src[1];
1834 dest[2] = src[2];
1835 for (dd += bs; dd >= scale; dd -= scale)
1836 src += 3;
1837 }
1838 ty = y;
1839 dest += w3 - l3;
1840 }
1841 else
1842 {
1843 memcpy(dest, dest - w3, l3);
1844 dest += w3;
1845 }
1846 }
1847
1848 return (scale > 1); // Request async_bk
1849 }
1850
get_bkg(int xc,int yc,int dclick)1851 static int get_bkg(int xc, int yc, int dclick)
1852 {
1853 int xb, yb, xi, yi, x, scale;
1854
1855 /* No background / not RGB / wrong scale */
1856 if (!bkg_flag || (mem_channel != CHN_IMAGE) || (mem_img_bpp != 3) ||
1857 (can_zoom < 1.0)) return (-1);
1858 scale = rint(can_zoom);
1859 xi = floor_div(xc - margin_main_x, scale);
1860 yi = floor_div(yc - margin_main_y, scale);
1861 /* Inside image */
1862 if ((xi >= 0) && (xi < mem_width) && (yi >= 0) && (yi < mem_height))
1863 {
1864 /* Pixel must be transparent */
1865 x = mem_width * yi + xi;
1866 if (mem_img[CHN_ALPHA] && !channel_dis[CHN_ALPHA] &&
1867 !mem_img[CHN_ALPHA][x]); // Alpha transparency
1868 else if (mem_xpm_trans < 0) return (-1);
1869 else if (x *= 3 , MEM_2_INT(mem_img[CHN_IMAGE], x) !=
1870 PNG_2_INT(mem_pal[mem_xpm_trans])) return (-1);
1871
1872 /* Double click averages background under image pixel */
1873 if (dclick)
1874 {
1875 int vxy[4];
1876
1877 vxy[2] = (vxy[0] = xi * bkg_scale - bkg_x) + bkg_scale;
1878 vxy[3] = (vxy[1] = yi * bkg_scale - bkg_y) + bkg_scale;
1879
1880 return (clip(vxy, 0, 0, bkg_w, bkg_h, vxy) ?
1881 average_pixels(bkg_rgb, NULL, bkg_w, vxy) : -1);
1882 }
1883 }
1884 xb = floor_div((xc - margin_main_x) * bkg_scale, scale) - bkg_x;
1885 yb = floor_div((yc - margin_main_y) * bkg_scale, scale) - bkg_y;
1886 /* Outside of background */
1887 if ((xb < 0) || (xb >= bkg_w) || (yb < 0) || (yb >= bkg_h)) return (-1);
1888 x = (bkg_w * yb + xb) * 3;
1889 return (MEM_2_INT(bkg_rgb, x));
1890 }
1891
1892 /* This is set when background is at different scale than image */
1893 int async_bk;
1894
setup_row(renderstate * r,int x0,int width,int zoom,int scale,int mw,int xpm,int opac,int bpp,png_color * pal)1895 void setup_row(renderstate *r, int x0, int width, int zoom, int scale, int mw,
1896 int xpm, int opac, int bpp, png_color *pal)
1897 {
1898 renderstate rr;
1899
1900 /* Horizontal zoom */
1901 rr.zoom = zoom;
1902 rr.scale = scale;
1903 x0 %= rr.scale;
1904 if (x0 < 0) x0 += rr.scale;
1905
1906 if (width + x0 > rr.scale)
1907 {
1908 rr.dx = rr.scale - x0;
1909 x0 = (width + x0) % rr.scale;
1910 if (!x0) x0 = rr.scale;
1911 width -= x0;
1912 rr.xwid = x0 - rr.scale;
1913 }
1914 else
1915 {
1916 rr.dx = width--;
1917 rr.xwid = 0;
1918 }
1919 rr.width = width;
1920 rr.mw = mw;
1921
1922 if ((xpm > -1) && (bpp == 3)) xpm = PNG_2_INT(pal[xpm]);
1923 rr.xpm = xpm;
1924 rr.opac = opac;
1925
1926 rr.bpp = bpp;
1927 rr.pal = pal;
1928 rr.cmask = 0;
1929
1930 *r = rr;
1931 }
1932
render_row(renderstate * r,unsigned char * rgb,chanlist base_img,int x,int y,chanlist xtra_img)1933 void render_row(renderstate *r, unsigned char *rgb, chanlist base_img,
1934 int x, int y, chanlist xtra_img)
1935 {
1936 renderstate rr = *r;
1937 int alpha_blend = !overlay_alpha;
1938 unsigned char *src = NULL, *dest, *alpha = NULL, px, beta = 255;
1939 int i, j, k, ii, ds = rr.zoom * 3, da = 0;
1940 int w_bpp = rr.bpp, w_xpm = rr.xpm;
1941
1942 if (xtra_img)
1943 {
1944 src = xtra_img[CHN_IMAGE];
1945 alpha = xtra_img[CHN_ALPHA];
1946 }
1947 if (rr.cmask & CMASK_ALPHA) alpha = β /* Ignore alpha if disabled */
1948 if (!src) src = base_img[CHN_IMAGE] + (rr.mw * y + x) * rr.bpp;
1949 if (!alpha) alpha = base_img[CHN_ALPHA] ? base_img[CHN_ALPHA] +
1950 rr.mw * y + x : β
1951 if (alpha != &beta) da = rr.zoom;
1952 dest = rgb;
1953 ii = rr.dx;
1954
1955 /* Substitute non-transparent "image overlay" colour */
1956 if (rr.cmask & CMASK_IMAGE)
1957 {
1958 w_bpp = 3;
1959 w_xpm = -1;
1960 ds = 0;
1961 src = channel_rgb[CHN_IMAGE];
1962 }
1963 if (!da && (w_xpm < 0) && (rr.opac == 255)) alpha_blend = FALSE;
1964
1965 /* Indexed fully opaque */
1966 if ((w_bpp == 1) && !alpha_blend)
1967 {
1968 for (i = 0; ; ii += rr.scale)
1969 {
1970 if (i >= rr.width)
1971 {
1972 if (i > rr.width) break;
1973 ii += rr.xwid;
1974 }
1975 px = *src;
1976 src += rr.zoom;
1977 for(; i < ii; i++)
1978 {
1979 dest[0] = rr.pal[px].red;
1980 dest[1] = rr.pal[px].green;
1981 dest[2] = rr.pal[px].blue;
1982 dest += 3;
1983 }
1984 }
1985 }
1986
1987 /* Indexed transparent */
1988 else if (w_bpp == 1)
1989 {
1990 for (i = 0; ; ii += rr.scale , alpha += da)
1991 {
1992 if (i >= rr.width)
1993 {
1994 if (i > rr.width) break;
1995 ii += rr.xwid;
1996 }
1997 px = *src;
1998 src += rr.zoom;
1999 if (!*alpha || (px == w_xpm))
2000 {
2001 dest += (ii - i) * 3;
2002 i = ii;
2003 continue;
2004 }
2005 rr2_as:
2006 if (rr.opac == 255)
2007 {
2008 dest[0] = rr.pal[px].red;
2009 dest[1] = rr.pal[px].green;
2010 dest[2] = rr.pal[px].blue;
2011 }
2012 else
2013 {
2014 j = 255 * dest[0] + rr.opac * (rr.pal[px].red - dest[0]);
2015 dest[0] = (j + (j >> 8) + 1) >> 8;
2016 j = 255 * dest[1] + rr.opac * (rr.pal[px].green - dest[1]);
2017 dest[1] = (j + (j >> 8) + 1) >> 8;
2018 j = 255 * dest[2] + rr.opac * (rr.pal[px].blue - dest[2]);
2019 dest[2] = (j + (j >> 8) + 1) >> 8;
2020 }
2021 rr2_s:
2022 dest += 3;
2023 if (++i >= ii) continue;
2024 if (async_bk) goto rr2_as;
2025 dest[0] = *(dest - 3);
2026 dest[1] = *(dest - 2);
2027 dest[2] = *(dest - 1);
2028 goto rr2_s;
2029 }
2030 }
2031
2032 /* RGB fully opaque */
2033 else if (!alpha_blend)
2034 {
2035 for (i = 0; ; ii += rr.scale , src += ds)
2036 {
2037 if (i >= rr.width)
2038 {
2039 if (i > rr.width) break;
2040 ii += rr.xwid;
2041 }
2042 for(; i < ii; i++)
2043 {
2044 dest[0] = src[0];
2045 dest[1] = src[1];
2046 dest[2] = src[2];
2047 dest += 3;
2048 }
2049 }
2050 }
2051
2052 /* RGB transparent */
2053 else
2054 {
2055 for (i = 0; ; ii += rr.scale , src += ds , alpha += da)
2056 {
2057 if (i >= rr.width)
2058 {
2059 if (i > rr.width) break;
2060 ii += rr.xwid;
2061 }
2062 if (!*alpha || (MEM_2_INT(src, 0) == w_xpm))
2063 {
2064 dest += (ii - i) * 3;
2065 i = ii;
2066 continue;
2067 }
2068 k = rr.opac * alpha[0];
2069 k = (k + (k >> 8) + 1) >> 8;
2070 rr4_as:
2071 if (k == 255)
2072 {
2073 dest[0] = src[0];
2074 dest[1] = src[1];
2075 dest[2] = src[2];
2076 }
2077 else
2078 {
2079 j = 255 * dest[0] + k * (src[0] - dest[0]);
2080 dest[0] = (j + (j >> 8) + 1) >> 8;
2081 j = 255 * dest[1] + k * (src[1] - dest[1]);
2082 dest[1] = (j + (j >> 8) + 1) >> 8;
2083 j = 255 * dest[2] + k * (src[2] - dest[2]);
2084 dest[2] = (j + (j >> 8) + 1) >> 8;
2085 }
2086 rr4_s:
2087 dest += 3;
2088 if (++i >= ii) continue;
2089 if (async_bk) goto rr4_as;
2090 dest[0] = *(dest - 3);
2091 dest[1] = *(dest - 2);
2092 dest[2] = *(dest - 1);
2093 goto rr4_s;
2094 }
2095 }
2096 }
2097
overlay_row(renderstate * r,unsigned char * rgb,chanlist base_img,int x,int y,chanlist xtra_img)2098 void overlay_row(renderstate *r, unsigned char *rgb, chanlist base_img,
2099 int x, int y, chanlist xtra_img)
2100 {
2101 renderstate rr = *r;
2102 unsigned char *alpha, *sel, *mask, *dest;
2103 int i, j, k, ii, dw, opA, opS, opM, t0, t1, t2, t3;
2104
2105 if (xtra_img)
2106 {
2107 alpha = xtra_img[CHN_ALPHA];
2108 sel = xtra_img[CHN_SEL];
2109 mask = xtra_img[CHN_MASK];
2110 }
2111 else alpha = sel = mask = NULL;
2112 j = rr.mw * y + x;
2113 if (!alpha && base_img[CHN_ALPHA]) alpha = base_img[CHN_ALPHA] + j;
2114 if (!sel && base_img[CHN_SEL]) sel = base_img[CHN_SEL] + j;
2115 if (!mask && base_img[CHN_MASK]) mask = base_img[CHN_MASK] + j;
2116
2117 /* Prepare channel weights (256-based) */
2118 k = rr.cmask & CMASK_IMAGE ? 256 : 256 - channel_opacity[CHN_IMAGE] -
2119 (channel_opacity[CHN_IMAGE] >> 7);
2120 opA = alpha && overlay_alpha && !(rr.cmask & CMASK_ALPHA) ?
2121 channel_opacity[CHN_ALPHA] : 0;
2122 opS = sel && !(rr.cmask & CMASK_SEL) ? channel_opacity[CHN_SEL] : 0;
2123 opM = mask && !(rr.cmask & CMASK_MASK) ? channel_opacity[CHN_MASK] : 0;
2124
2125 /* Nothing to do - don't waste time then */
2126 j = opA + opS + opM;
2127 if (!k || !j) return;
2128
2129 opA = (k * opA) / j;
2130 opS = (k * opS) / j;
2131 opM = (k * opM) / j;
2132 if (!(opA + opS + opM)) return;
2133
2134 dest = rgb;
2135 ii = rr.dx;
2136 for (i = dw = 0; ; ii += rr.scale , dw += rr.zoom)
2137 {
2138 if (i >= rr.width)
2139 {
2140 if (i > rr.width) break;
2141 ii += rr.xwid;
2142 }
2143 t0 = t1 = t2 = t3 = 0;
2144 if (opA)
2145 {
2146 j = opA * (alpha[dw] ^ channel_inv[CHN_ALPHA]);
2147 t0 += j;
2148 t1 += j * channel_rgb[CHN_ALPHA][0];
2149 t2 += j * channel_rgb[CHN_ALPHA][1];
2150 t3 += j * channel_rgb[CHN_ALPHA][2];
2151 }
2152 if (opS)
2153 {
2154 j = opS * (sel[dw] ^ channel_inv[CHN_SEL]);
2155 t0 += j;
2156 t1 += j * channel_rgb[CHN_SEL][0];
2157 t2 += j * channel_rgb[CHN_SEL][1];
2158 t3 += j * channel_rgb[CHN_SEL][2];
2159 }
2160 if (opM)
2161 {
2162 j = opM * (mask[dw] ^ channel_inv[CHN_MASK]);
2163 t0 += j;
2164 t1 += j * channel_rgb[CHN_MASK][0];
2165 t2 += j * channel_rgb[CHN_MASK][1];
2166 t3 += j * channel_rgb[CHN_MASK][2];
2167 }
2168 j = (256 * 255) - t0;
2169 or_as:
2170 k = t1 + j * dest[0];
2171 dest[0] = (k + (k >> 8) + 0x100) >> 16;
2172 k = t2 + j * dest[1];
2173 dest[1] = (k + (k >> 8) + 0x100) >> 16;
2174 k = t3 + j * dest[2];
2175 dest[2] = (k + (k >> 8) + 0x100) >> 16;
2176 or_s:
2177 dest += 3;
2178 if (++i >= ii) continue;
2179 if (async_bk) goto or_as;
2180 dest[0] = *(dest - 3);
2181 dest[1] = *(dest - 2);
2182 dest[2] = *(dest - 1);
2183 goto or_s;
2184 }
2185 }
2186
2187 /* Specialized renderer for irregular overlays */
overlay_preview(renderstate * r,unsigned char * rgb,unsigned char * map,int col,int opacity)2188 void overlay_preview(renderstate *r, unsigned char *rgb, unsigned char *map,
2189 int col, int opacity)
2190 {
2191 renderstate rr = *r;
2192 unsigned char *dest, crgb[3] = {INT_2_R(col), INT_2_G(col), INT_2_B(col)};
2193 int i, j, k, ii, dw;
2194
2195 dest = rgb;
2196 ii = rr.dx;
2197 for (i = dw = 0; ; ii += rr.scale , dw += rr.zoom)
2198 {
2199 if (i >= rr.width)
2200 {
2201 if (i > rr.width) break;
2202 ii += rr.xwid;
2203 }
2204 k = opacity * map[dw];
2205 k = (k + (k >> 8) + 1) >> 8;
2206 op_as:
2207 j = 255 * dest[0] + k * (crgb[0] - dest[0]);
2208 dest[0] = (j + (j >> 8) + 1) >> 8;
2209 j = 255 * dest[1] + k * (crgb[1] - dest[1]);
2210 dest[1] = (j + (j >> 8) + 1) >> 8;
2211 j = 255 * dest[2] + k * (crgb[2] - dest[2]);
2212 dest[2] = (j + (j >> 8) + 1) >> 8;
2213 op_s:
2214 dest += 3;
2215 if (++i >= ii) continue;
2216 if (async_bk) goto op_as;
2217 dest[0] = *(dest - 3);
2218 dest[1] = *(dest - 2);
2219 dest[2] = *(dest - 1);
2220 goto op_s;
2221 }
2222 }
2223
2224 typedef struct {
2225 int rgb_s; // For when need RGB with 1bpp channel
2226 int mask_s; // For most everything
2227 int overlay_s; // For extra overlay
2228 int channel_s; // For composited channel
2229 int alpha_s; // For composited alpha
2230 int n_channel_s; // For generated/transformed channel
2231 int n_alpha_s; // For generated alpha
2232 int n_opacity_s; // For generated opacity
2233 unsigned char *rgb,
2234 *mask,
2235 *overlay,
2236 *channel,
2237 *alpha,
2238 *n_channel,
2239 *n_alpha,
2240 *n_opacity; // Pointers to same
2241 } render_mem_req;
2242
2243 typedef struct {
2244 unsigned char *wmask, *gmask, *walpha, *galpha;
2245 unsigned char *wimg, *gimg, *rgb;
2246 int opac, len, bpp;
2247 } grad_render_state;
2248
grad_render_req(render_mem_req * mr,int len)2249 static int grad_render_req(render_mem_req *mr, int len)
2250 {
2251 int bpp;
2252
2253 // !!! Only the "slow path" for now
2254 if (gradient[mem_channel].status != GRAD_DONE) return (FALSE);
2255
2256 bpp = MEM_BPP;
2257 mr->mask_s = mr->n_opacity_s = len;
2258 mr->channel_s = mr->n_channel_s = len * bpp;
2259 if ((mem_channel == CHN_IMAGE) && RGBA_mode && mem_img[CHN_ALPHA])
2260 mr->alpha_s = mr->n_alpha_s = len;
2261 if (IS_INDEXED && (grad_opacity < 255)) mr->rgb_s = len * 3;
2262 return (TRUE);
2263 }
2264
init_grad_render(render_mem_req * mr,grad_render_state * g,int len,chanlist tlist)2265 static void init_grad_render(render_mem_req *mr, grad_render_state *g, int len,
2266 chanlist tlist)
2267 {
2268 memset(g, 0, sizeof(grad_render_state));
2269
2270 g->wmask = mr->mask; /* Mask */
2271 g->gmask = mr->n_opacity; /* Gradient opacity */
2272 g->gimg = mr->n_channel; /* Gradient image */
2273 g->wimg = mr->channel; /* Resulting image */
2274 g->galpha = mr->n_alpha; /* Gradient alpha */
2275 g->walpha = mr->alpha; /* Resulting alpha */
2276 g->rgb = mr->rgb; /* Indexed to RGB */
2277
2278 tlist[CHN_ALPHA] = g->walpha;
2279 tlist[mem_channel] = g->wimg;
2280 if (g->rgb) tlist[CHN_IMAGE] = g->rgb;
2281 g->opac = IS_INDEXED ? 0 : grad_opacity;
2282 g->len = len;
2283 g->bpp = MEM_BPP;
2284 }
2285
grad_render(int start,int step,int cnt,int x,int y,unsigned char * mask0,grad_render_state * g)2286 static void grad_render(int start, int step, int cnt, int x, int y,
2287 unsigned char *mask0, grad_render_state *g)
2288 {
2289 int l = mem_width * y + x, li = l * mem_img_bpp;
2290
2291 prep_mask(start, step, cnt, g->wmask, mask0, mem_img[CHN_IMAGE] + li);
2292
2293 grad_pixels(start, step, cnt, x, y, g->wmask, g->gmask, g->gimg, g->galpha);
2294
2295 if (g->walpha) memcpy(g->walpha, mem_img[CHN_ALPHA] + l, g->len);
2296 process_mask(start, step, cnt, g->wmask, g->walpha, mem_img[CHN_ALPHA] + l,
2297 g->galpha, g->gmask, g->opac, channel_dis[CHN_ALPHA]);
2298
2299 memcpy(g->wimg, mem_img[mem_channel] + l * g->bpp, g->len * g->bpp);
2300 process_img(start, step, cnt, g->wmask, g->wimg, g->wimg, g->gimg,
2301 g->gimg, g->bpp, 0);
2302
2303 if (g->rgb) blend_indexed(start, step, cnt, g->rgb, mem_img[CHN_IMAGE] + l,
2304 g->wimg, mem_img[CHN_ALPHA] + l, g->walpha, grad_opacity);
2305 }
2306
2307 typedef struct {
2308 chanlist tlist; // Channel overrides
2309 unsigned char *mask0; // Active mask channel
2310 unsigned char *pvi; // Xform/xhold render: temp image row
2311 unsigned char *pvm; // Xform/xhold render: temp mask row
2312 int rxy[4]; // Clipped area
2313 int dx; // Image-space X offset
2314 int lx; // Allocated row length
2315 int pww; // Logical row length
2316 int zoom; // Decimation factor
2317 int scale; // Replication factor
2318 int lop; // Base opacity
2319 int xpm; // Transparent color
2320 } main_render_state;
2321
2322 typedef struct {
2323 chanlist tlist; // Channel overrides for rendering clipboard
2324 unsigned char *clip_alpha; // Pasted into alpha channel
2325 unsigned char *t_alpha; // Fake pasted alpha
2326 unsigned char *pix, *alpha; // Destinations for the above
2327 unsigned char *mask, *wmask; // Temp mask: one we use, other we init
2328 unsigned char *mask0; // Image mask channel to use
2329 unsigned char *xbuf; // Extra buffer for process_img()
2330 int opacity, bpp; // Just that
2331 int pixf; // Flag: need current channel override filled
2332 int dx; // Memory-space X offset
2333 int lx; // Allocated row length
2334 int pww; // Logical row length
2335 int xform; // Flag: do color transform
2336 int maskf; // Flag: need mask filled
2337 } paste_render_state;
2338
2339 /* !!! If ever combined with more than color transform, must not unconditionally
2340 * !!! assign potentially shorter (paste area sized) lengths to buffers */
paste_render_req(render_mem_req * mr,paste_render_state * p,main_render_state * r)2341 static int paste_render_req(render_mem_req *mr, paste_render_state *p,
2342 main_render_state *r)
2343 {
2344 int rxy[4], bpp, scale = r->scale, zoom = r->zoom;
2345
2346
2347 /* Clip paste area to update area */
2348 if (!clip(rxy, (marq_x1 * scale + zoom - 1) / zoom,
2349 (marq_y1 * scale + zoom - 1) / zoom,
2350 (marq_x2 * scale) / zoom + scale,
2351 (marq_y2 * scale) / zoom + scale, r->rxy)) return (FALSE);
2352
2353 /* Setup row position and size */
2354 p->dx = (rxy[0] * zoom) / scale;
2355 p->pww = xy_span(rxy, scale, 0);
2356 p->lx = (p->pww - 1) * zoom + 1;
2357
2358 if ((mem_channel == CHN_IMAGE) && mem_img[CHN_ALPHA] &&
2359 !channel_dis[CHN_ALPHA])
2360 {
2361 // Need temp alpha
2362 if (mem_clip_alpha || RGBA_mode) mr->alpha_s = r->lx;
2363 // Need fake alpha
2364 if (!mem_clip_alpha && RGBA_mode) mr->n_alpha_s = p->lx;
2365 }
2366
2367 bpp = p->bpp = MEM_BPP;
2368 // Need temp mask
2369 if ((p->maskf = !mr->mask_s)) mr->mask_s = p->lx;
2370 // Need temp image
2371 p->pixf = !mr->channel_s; /* Need it prefilled if no override data incoming */
2372 mr->channel_s = r->lx * bpp;
2373 // Need xform buffer
2374 p->xform = mem_preview_clip && (bpp == 3) && (mem_clip_bpp == 3);
2375 if (p->xform || NEED_XBUF_PASTE) mr->n_channel_s = p->lx * bpp;
2376
2377 /* Setup opacity mode */
2378 if (!IS_INDEXED) p->opacity = tool_opacity;
2379
2380 return (TRUE);
2381 }
2382
2383 /* !!! This function copies existing override set to build its own modified
2384 * !!! one, so override set must not be changed after calling it */
init_paste_render(render_mem_req * mr,paste_render_state * p,main_render_state * r)2385 static void init_paste_render(render_mem_req *mr, paste_render_state *p,
2386 main_render_state *r)
2387 {
2388 int ddx = p->dx - r->dx;
2389
2390
2391 memcpy(p->tlist, r->tlist, sizeof(chanlist));
2392
2393 if ((mem_channel == CHN_IMAGE) && !channel_dis[CHN_ALPHA])
2394 p->clip_alpha = mem_clip_alpha;
2395
2396 p->tlist[CHN_ALPHA] = mr->alpha;
2397 p->tlist[mem_channel] = mr->channel;
2398 p->mask = mr->mask;
2399 p->t_alpha = mr->n_alpha;
2400 p->xbuf = mr->n_channel;
2401
2402 /* Setup "image" (current) channel override */
2403 p->pix = p->tlist[mem_channel] + ddx * p->bpp;
2404
2405 /* Setup alpha channel override */
2406 if (mr->alpha) p->alpha = p->tlist[CHN_ALPHA] + ddx;
2407
2408 /* Setup mask */
2409 if (mem_channel <= CHN_ALPHA) p->mask0 = r->mask0;
2410 if (p->maskf) p->wmask = p->mask;
2411 else
2412 {
2413 p->mask += ddx;
2414 if (r->mask0 != p->mask0)
2415 /* Mask has wrong data - reuse memory but refill values */
2416 p->wmask = p->mask;
2417 }
2418
2419 /* Setup fake alpha */
2420 if (p->t_alpha) memset(p->t_alpha, channel_col_A[CHN_ALPHA], p->lx);
2421 }
2422
paste_render(int start,int step,int y,paste_render_state * p)2423 static void paste_render(int start, int step, int y, paste_render_state *p)
2424 {
2425 int ld = mem_width * y + p->dx;
2426 int dc = mem_clip_w * (y - marq_y1) + p->dx - marq_x1;
2427 int bpp = p->bpp;
2428 int cnt = p->pww;
2429 unsigned char *clip_src = mem_clipboard + dc * mem_clip_bpp;
2430
2431 if (p->wmask) prep_mask(start, step, cnt, p->wmask, p->mask0 ?
2432 p->mask0 + ld : NULL, mem_img[CHN_IMAGE] + ld * mem_img_bpp);
2433 process_mask(start, step, cnt, p->mask, p->alpha, mem_img[CHN_ALPHA] + ld,
2434 p->clip_alpha ? p->clip_alpha + dc : p->t_alpha,
2435 mem_clip_mask ? mem_clip_mask + dc : NULL, p->opacity, 0);
2436 if (p->xform) /* Apply color transform if preview requested */
2437 {
2438 do_transform(start, step, cnt, p->mask, p->xbuf, clip_src, 0);
2439 clip_src = p->xbuf;
2440 }
2441 if (mem_clip_bpp < bpp)
2442 {
2443 /* Convert paletted clipboard to RGB */
2444 do_convert_rgb(start, step, cnt, p->xbuf, clip_src,
2445 mem_clip_paletted ? mem_clip_pal : mem_pal);
2446 clip_src = p->xbuf;
2447 }
2448 process_img(start, step, cnt, p->mask, p->pix, p->pix, clip_src,
2449 p->xbuf, bpp, 0);
2450 }
2451
2452 typedef struct {
2453 main_render_state r;
2454 paste_render_state p;
2455 render_mem_req m;
2456 int tflag, xflag, gflag, pflag, lr;
2457 int pw;
2458 int cxy[4];
2459 unsigned char *rgb, *irgb;
2460 threaddata *tdata; // For simplicity
2461 } u_render_state;
2462
2463 /* Filter preview modes */
2464 #define XF_NONE 0
2465 #define XF_XHOLD 1
2466 #define XF_NOISE 2
2467
main_render_req(u_render_state * u)2468 static void main_render_req(u_render_state *u)
2469 {
2470 main_render_state r = u->r;
2471
2472
2473 if (!channel_dis[CHN_MASK]) r.mask0 = mem_img[CHN_MASK];
2474
2475 r.xpm = mem_xpm_trans;
2476 r.lop = 255;
2477 if (u->lr && layer_selected)
2478 r.lop = (layer_table_p[layer_selected].opacity * 255 + 50) / 100;
2479
2480 /* Setup row position and size */
2481 r.dx = (r.rxy[0] * r.zoom) / r.scale;
2482 r.pww = xy_span(r.rxy, r.scale, 0);
2483 r.lx = (r.pww - 1) * r.zoom + 1;
2484
2485 /* ****** Memory request phase ****** */
2486
2487 /* Color transform preview */
2488 if ((u->tflag = mem_preview && (mem_img_bpp == 3)))
2489 {
2490 u->m.mask_s = r.lx;
2491 if (mem_channel == CHN_IMAGE) u->m.channel_s = r.lx * 3;
2492 else u->m.rgb_s = r.lx * 3;
2493 }
2494
2495 /* Filter preview */
2496 else if ((u->xflag = xhold_preview ? XF_XHOLD : noise_preview ? XF_NOISE : 0))
2497 {
2498 u->m.mask_s = r.lx;
2499 u->m.channel_s = r.lx * MEM_BPP;
2500 }
2501
2502 /* Color selective mode preview */
2503 else if (csel_overlay) u->m.overlay_s = r.lx;
2504
2505 /* Gradient preview */
2506 else if ((tool_type == TOOL_GRADIENT) && grad_opacity)
2507 u->gflag = grad_render_req(&u->m, r.lx);
2508
2509 /* Paste preview - can only coexist with transform */
2510 if (show_paste && (marq_status >= MARQUEE_PASTE) &&
2511 !u->xflag && !u->m.overlay_s && !u->gflag)
2512 u->pflag = paste_render_req(&u->m, &u->p, &r);
2513
2514 /* Pass the data */
2515 u->r = r;
2516 }
2517
main_render(u_render_state * u,int py,int ph)2518 static void main_render(u_render_state *u, int py, int ph)
2519 {
2520 main_render_state r = u->r;
2521 grad_render_state grstate;
2522 renderstate rs;
2523 unsigned char *rgb, **tlist = r.tlist, *overlay = u->m.overlay;
2524 int j, jj, j0, l, pw2, pw;
2525
2526 /* ****** Init phase ****** */
2527
2528 /* Color transform preview */
2529 if (u->tflag)
2530 {
2531 r.pvm = u->m.mask;
2532 r.tlist[CHN_IMAGE] = r.pvi = u->m.rgb ? u->m.rgb : u->m.channel;
2533 }
2534
2535 /* Threshold preview */
2536 if (u->xflag)
2537 {
2538 if (mem_channel > CHN_ALPHA) r.mask0 = NULL;
2539 r.pvm = u->m.mask;
2540 r.tlist[mem_channel] = r.pvi = u->m.channel;
2541 }
2542
2543 /* Gradient preview */
2544 if (u->gflag)
2545 {
2546 if (mem_channel > CHN_ALPHA) r.mask0 = NULL;
2547 init_grad_render(&u->m, &grstate, r.lx, r.tlist);
2548 }
2549
2550 /* Paste preview */
2551 if (u->pflag) init_paste_render(&u->m, &u->p, &r);
2552
2553 /* Start rendering */
2554 pw2 = r.rxy[2] - r.rxy[0];
2555 setup_row(&rs, r.rxy[0], pw2, r.zoom, r.scale, mem_width, r.xpm, r.lop,
2556 u->gflag && grstate.rgb ? 3 : mem_img_bpp, mem_pal);
2557 rs.cmask = (hide_image ? CMASK_IMAGE : 0) |
2558 (channel_dis[CHN_ALPHA] ? CMASK_ALPHA : 0) |
2559 (channel_dis[CHN_SEL] ? CMASK_SEL : 0) |
2560 (channel_dis[CHN_MASK] ? CMASK_MASK : 0);
2561 j0 = -1; pw2 *= 3;
2562 rgb = u->irgb + (py - r.rxy[1]) * (pw = u->pw);
2563 for (jj = 0; jj < ph; jj++ , rgb += pw)
2564 {
2565 j = ((py + jj) * r.zoom) / r.scale;
2566 if (j != j0)
2567 {
2568 j0 = j;
2569 l = mem_width * j + r.dx;
2570 tlist = r.tlist; /* Default override */
2571
2572 /* Color transform/threshold preview */
2573 if (u->tflag | u->xflag)
2574 {
2575 unsigned char *src, *img;
2576 int bpp = mem_img_bpp;
2577
2578 src = img = mem_img[CHN_IMAGE] + l * bpp;
2579
2580 prep_mask(0, r.zoom, r.pww, r.pvm,
2581 r.mask0 ? r.mask0 + l : NULL, img);
2582 if (u->tflag) do_transform(0, r.zoom, r.pww,
2583 r.pvm, r.pvi, src, 255);
2584 else if (u->xflag == XF_XHOLD)
2585 {
2586 bpp = MEM_BPP;
2587 src = mem_img[mem_channel] + l * bpp;
2588 do_xhold(0, r.zoom, r.pww, r.pvm, r.pvi, src);
2589 }
2590 else /* if (u->xflag == XF_NOISE) */
2591 do_perlin(0, r.zoom, r.pww, r.pvm, r.pvi, r.dx, j);
2592 process_img(0, r.zoom, r.pww, r.pvm,
2593 r.pvi, r.pvi, src, NULL, bpp, BLENDF_SET);
2594 }
2595 /* Color selective mode preview */
2596 else if (overlay)
2597 {
2598 memset(overlay, 0, r.lx);
2599 csel_scan(0, r.zoom, r.pww, overlay,
2600 mem_img[CHN_IMAGE] + l * mem_img_bpp,
2601 csel_data);
2602 }
2603 /* Gradient preview */
2604 else if (u->gflag) grad_render(0, r.zoom, r.pww, r.dx, j,
2605 r.mask0 ? r.mask0 + l : NULL, &grstate);
2606
2607 /* Paste preview - should be after transform */
2608 if (u->pflag && (j >= marq_y1) && (j <= marq_y2))
2609 {
2610 tlist = u->p.tlist; /* Paste-area override */
2611 if (u->p.alpha) memcpy(tlist[CHN_ALPHA],
2612 mem_img[CHN_ALPHA] + l, r.lx);
2613 if (u->p.pixf) memcpy(tlist[mem_channel],
2614 mem_img[mem_channel] + l * u->p.bpp,
2615 r.lx * u->p.bpp);
2616 paste_render(0, r.zoom, j, &u->p);
2617 }
2618 }
2619 else if (!async_bk)
2620 {
2621 memcpy(rgb, rgb - pw, pw2);
2622 continue;
2623 }
2624 render_row(&rs, rgb, mem_img, r.dx, j, tlist);
2625 if (!overlay) overlay_row(&rs, rgb, mem_img, r.dx, j, tlist);
2626 else overlay_preview(&rs, rgb, overlay, csel_preview, csel_preview_a);
2627 }
2628 }
2629
2630 /// GRID
2631
2632 int grid_rgb[GRID_MAX]; // Grid colors to use
2633 int mem_show_grid; // Boolean show toggle
2634 int mem_grid_min; // Minimum zoom to show it at
2635 int color_grid; // If to use grid coloring
2636 int show_tile_grid; // Tile grid toggle
2637 int tgrid_x0, tgrid_y0; // Tile grid origin
2638 int tgrid_dx, tgrid_dy; // Tile grid spacing
2639 int tgrid_snap; // Coordinates snap toggle
2640
2641 /* Snap coordinate pair to tile grid (floored) */
snap_xy(int * xy)2642 void snap_xy(int *xy)
2643 {
2644 int dx, dy;
2645
2646 dx = (xy[0] - tgrid_x0) % tgrid_dx;
2647 xy[0] -= dx + (dx < 0 ? tgrid_dx : 0);
2648 dy = (xy[1] - tgrid_y0) % tgrid_dy;
2649 xy[1] -= dy + (dy < 0 ? tgrid_dy : 0);
2650 }
2651
2652 /* Buffer stores interleaved transparency bits for two pixel rows; current
2653 * row in bits 0, 2, etc., previous one in bits 1, 3, etc. */
scan_trans(unsigned char * dest,int delta,int y,int x,int w)2654 static void scan_trans(unsigned char *dest, int delta, int y, int x, int w)
2655 {
2656 static unsigned char beta = 255;
2657 unsigned char *src, *srca = β
2658 int i, ofs, bit, buf, xpm, da = 0, bpp = mem_img_bpp;
2659
2660
2661 delta += delta; dest += delta >> 3; delta &= 7;
2662 if (y >= mem_height) // Clear
2663 {
2664 for (i = 0; i < w; i++ , dest++) *dest = (*dest & 0x55) << 1;
2665 return;
2666 }
2667
2668 xpm = mem_xpm_trans < 0 ? -1 : bpp == 1 ? mem_xpm_trans :
2669 PNG_2_INT(mem_pal[mem_xpm_trans]);
2670 ofs = y * mem_width + x;
2671 src = mem_img[CHN_IMAGE] + ofs * bpp;
2672 if (mem_img[CHN_ALPHA]) srca = mem_img[CHN_ALPHA] + ofs , da = 1;
2673 bit = 1 << delta;
2674 buf = (*dest & 0x55) << 1;
2675 for (i = 0; i < w; i++ , src += bpp , srca += da)
2676 {
2677 if (*srca && ((bpp == 1 ? *src : MEM_2_INT(src, 0)) != xpm))
2678 buf |= bit;
2679 if ((bit <<= 2) < 0x100) continue;
2680 *dest++ = buf; buf = (*dest & 0x55) << 1; bit = 1;
2681 }
2682 *dest = buf;
2683 }
2684
2685 /* Draw grid on rgb memory */
draw_grid(unsigned char * rgb,int x,int y,int w,int h,int l)2686 static void draw_grid(unsigned char *rgb, int x, int y, int w, int h, int l)
2687 {
2688 /* !!! This limit IN THEORY can be violated by a sufficiently huge screen, if
2689 * clipping to image isn't enforced in some fashion; this code can be made to
2690 * detect the violation and do the clipping (on X axis) for itself - really
2691 * colored is only the image proper + 1 pixel to bottom & right of it */
2692 unsigned char lbuf[(MAX_WIDTH * 2 + 2 + 7) / 8 + 2];
2693 int dx, dy, step = can_zoom;
2694 int i, j, k, yy, wx, ww, x0, xw;
2695
2696
2697 l *= 3;
2698 dx = (x - margin_main_x) % step;
2699 if (dx <= 0) dx += step;
2700 dy = (y - margin_main_y) % step;
2701 if (dy <= 0) dy += step;
2702
2703 /* Use dumb code for uncolored grid */
2704 if (!color_grid || (x + w <= margin_main_x) || (y + h <= margin_main_y) ||
2705 (x > margin_main_x + mem_width * step) ||
2706 (y > margin_main_y + mem_height * step))
2707 {
2708 unsigned char *tmp;
2709 int i, j, k, step3, tc;
2710
2711 tc = grid_rgb[color_grid ? GRID_TRANS : GRID_NORMAL];
2712 dx = (step - dx) * 3;
2713 w *= 3;
2714
2715 for (k = dy , i = 0; i < h; i++ , k++)
2716 {
2717 tmp = rgb + i * l;
2718 if (k == step) /* Filled line */
2719 {
2720 j = k = 0; step3 = 3;
2721 }
2722 else /* Spaced dots */
2723 {
2724 j = dx; step3 = step * 3;
2725 }
2726 for (tmp += j; j < w; j += step3 , tmp += step3)
2727 {
2728 tmp[0] = INT_2_R(tc);
2729 tmp[1] = INT_2_G(tc);
2730 tmp[2] = INT_2_B(tc);
2731 }
2732 }
2733 return;
2734 }
2735
2736 wx = floor_div(x - margin_main_x - 1, step);
2737 ww = (w + dx + step - 1) / step + 1;
2738 memset(lbuf, 0, (ww + ww + 7 + 16) >> 3); // Init to transparent
2739
2740 x0 = wx < 0 ? 0 : wx;
2741 xw = (wx + ww < mem_width ? wx + ww : mem_width) - x0;
2742 wx = x0 - wx;
2743 yy = floor_div(y - margin_main_y, step);
2744
2745 /* Initial row fill */
2746 if (dy == step) yy--;
2747 if ((yy >= 0) && (yy < mem_height)) // Else it stays cleared
2748 scan_trans(lbuf, wx, yy, x0, xw);
2749
2750 for (k = dy , i = 0; i < h; i++ , k++)
2751 {
2752 unsigned char *tmp = rgb + i * l, *buf = lbuf + 2;
2753
2754 // Horizontal span
2755 if (k == step)
2756 {
2757 int nv, tc, kk;
2758
2759 yy++; k = 0;
2760 // Fill one more row
2761 if ((yy >= 0) && (yy <= mem_height + 1))
2762 scan_trans(lbuf, wx, yy, x0, xw);
2763 nv = (lbuf[0] + (lbuf[1] << 8)) ^ 0x2FFFF; // Invert
2764 tc = grid_rgb[((nv & 3) + 1) >> 1];
2765 for (kk = dx , j = 0; j < w; j++ , kk++ , tmp += 3)
2766 {
2767 if (kk != step) // Span
2768 {
2769 tmp[0] = INT_2_R(tc);
2770 tmp[1] = INT_2_G(tc);
2771 tmp[2] = INT_2_B(tc);
2772 continue;
2773 }
2774 // Intersection
2775 /* 0->0, 15->2, in-between remains between */
2776 tc = grid_rgb[((nv & 0xF) * 9 + 0x79) >> 7];
2777 tmp[0] = INT_2_R(tc);
2778 tmp[1] = INT_2_G(tc);
2779 tmp[2] = INT_2_B(tc);
2780 nv >>= 2;
2781 if (nv < 0x400)
2782 nv ^= (*buf++ << 8) ^ 0x2FD00; // Invert
2783 tc = grid_rgb[((nv & 3) + 1) >> 1];
2784 kk = 0;
2785 }
2786 }
2787 // Vertical spans
2788 else
2789 {
2790 int nv = (lbuf[0] + (lbuf[1] << 8)) ^ 0x2FFFF; // Invert
2791 j = step - dx; tmp += j * 3;
2792 for (; j < w; j += step , tmp += step * 3)
2793 {
2794 /* 0->0, 5->2, in-between remains between */
2795 int tc = grid_rgb[((nv & 5) + 3) >> 2];
2796 tmp[0] = INT_2_R(tc);
2797 tmp[1] = INT_2_G(tc);
2798 tmp[2] = INT_2_B(tc);
2799 nv >>= 2;
2800 if (nv < 0x400)
2801 nv ^= (*buf++ << 8) ^ 0x2FD00; // Invert
2802 }
2803 }
2804 }
2805 }
2806
2807 /* Draw tile grid on rgb memory */
draw_tgrid(unsigned char * rgb,int x,int y,int w,int h,int l)2808 static void draw_tgrid(unsigned char *rgb, int x, int y, int w, int h, int l)
2809 {
2810 unsigned char *tmp, *tm2;
2811 int i, j, k, dx, dy, nx, ny, xx, yy, tc, zoom = 1, scale = 1;
2812
2813
2814 /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
2815 if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
2816 else scale = rint(can_zoom);
2817 if ((tgrid_dx < zoom * 2) || (tgrid_dy < zoom * 2)) return; // Too dense
2818
2819 dx = tgrid_x0 ? tgrid_dx - tgrid_x0 : 0;
2820 nx = (x * zoom - dx) / (tgrid_dx * scale);
2821 if (nx < 0) nx = 0; nx++; dx++;
2822 dy = tgrid_y0 ? tgrid_dy - tgrid_y0 : 0;
2823 ny = (y * zoom - dy) / (tgrid_dy * scale);
2824 if (ny < 0) ny = 0; ny++; dy++;
2825 xx = ((nx * tgrid_dx - dx) * scale) / zoom + scale - 1;
2826 yy = ((ny * tgrid_dy - dy) * scale) / zoom + scale - 1;
2827 if ((xx >= x + w) && (yy >= y + h)) return; // Entirely inside grid cell
2828
2829 l *= 3; tc = grid_rgb[GRID_TILE];
2830 for (i = 0; i < h; i++)
2831 {
2832 tmp = rgb + l * i;
2833 if (y + i == yy) /* Filled line */
2834 {
2835 for (j = 0; j < w; j++ , tmp += 3)
2836 {
2837 tmp[0] = INT_2_R(tc);
2838 tmp[1] = INT_2_G(tc);
2839 tmp[2] = INT_2_B(tc);
2840 }
2841 yy = ((++ny * tgrid_dy - dy) * scale) / zoom + scale - 1;
2842 continue;
2843 }
2844 /* Spaced dots */
2845 for (k = xx , j = nx + 1; k < x + w; j++)
2846 {
2847 tm2 = tmp + (k - x) * 3;
2848 tm2[0] = INT_2_R(tc);
2849 tm2[1] = INT_2_G(tc);
2850 tm2[2] = INT_2_B(tc);
2851 k = ((j * tgrid_dx - dx) * scale) / zoom + scale - 1;
2852 }
2853 }
2854 }
2855
2856 /* Draw segmentation contours on rgb memory */
draw_segments(unsigned char * rgb,int x,int y,int w,int h,int l)2857 static void draw_segments(unsigned char *rgb, int x, int y, int w, int h, int l)
2858 {
2859 unsigned char lbuf[(MAX_WIDTH * 2 + 7) / 8];
2860 int i, j, k, j0, kk, dx, dy, wx, ww, yy, vf, tc, zoom = 1, scale = 1;
2861
2862
2863 /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
2864 if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
2865 else scale = rint(can_zoom);
2866
2867 l *= 3;
2868 dx = x % scale;
2869 wx = x / scale;
2870 ww = (w + dx + scale - 1) / scale;
2871
2872 dy = y % scale;
2873 yy = y / scale;
2874
2875 /* Initial row fill */
2876 mem_seg_scan(lbuf, yy, wx, ww, zoom, seg_preview);
2877
2878 tc = grid_rgb[GRID_SEGMENT];
2879 j0 = !!dx;
2880 vf = (scale == 1) | 2; // Draw both edges in one pixel if no zoom
2881 for (k = dy , i = 0; i < h; i++ , k++)
2882 {
2883 unsigned char *tmp, *buf;
2884 int nv;
2885
2886 if (k == scale)
2887 {
2888 mem_seg_scan(lbuf, ++yy, wx, ww, zoom, seg_preview);
2889 k = 0;
2890 }
2891
2892 /* Horizontal lines */
2893 if (!k && (scale > 1))
2894 {
2895 tmp = rgb + i * l;
2896 buf = lbuf;
2897 nv = *buf++ + 0x100;
2898 for (kk = dx, j = 0; j < w; j++ , tmp += 3)
2899 {
2900 if (nv & 1) // Draw grid line
2901 {
2902 tmp[0] = INT_2_R(tc);
2903 tmp[1] = INT_2_G(tc);
2904 tmp[2] = INT_2_B(tc);
2905 }
2906 if (++kk == scale)
2907 {
2908 if ((nv >>= 2) == 1) nv = *buf++ + 0x100;
2909 kk = 0;
2910 }
2911 }
2912 }
2913
2914 /* Vertical/mixed lines */
2915 tmp = rgb + i * l + (j0 * scale - dx) * 3;
2916 buf = lbuf;
2917 nv = (*buf++ + 0x100) >> (j0 * 2);
2918 for (j = j0; j < ww; j++ , tmp += scale * 3)
2919 {
2920 if (nv & vf)
2921 {
2922 tmp[0] = INT_2_R(tc);
2923 tmp[1] = INT_2_G(tc);
2924 tmp[2] = INT_2_B(tc);
2925 }
2926 if ((nv >>= 2) == 1) nv = *buf++ + 0x100;
2927 }
2928 }
2929 }
2930
2931 /* Draw dashed line to RGB memory or straight to canvas */
draw_dash(int c0,int c1,int ofs,int x,int y,int w,int h,rgbcontext * ctx)2932 void draw_dash(int c0, int c1, int ofs, int x, int y, int w, int h, rgbcontext *ctx)
2933 {
2934 rgbcontext cw;
2935 unsigned char *dest;
2936 int i, k, l, ww, step, cc[2] = { c0, c1 };
2937
2938 if (!ctx)
2939 {
2940 cw.xy[2] = (cw.xy[0] = x) + w;
2941 cw.xy[3] = (cw.xy[1] = y) + h;
2942 cw.rgb = NULL;
2943 cmd_setv(drawing_canvas, ctx = &cw, CANVAS_PAINT);
2944 if (!cw.rgb) return;
2945 }
2946 else if (!clip(cw.xy, x, y, x + w, y + h, ctx->xy)) return;
2947
2948 ofs += cw.xy[0] + cw.xy[1] - x - y; // offset in pattern
2949 ww = (ctx->xy[2] - ctx->xy[0]) * 3;
2950 if (w == 1) // Vertical
2951 {
2952 step = ww;
2953 l = cw.xy[3] - cw.xy[1];
2954 }
2955 else // Horizontal
2956 {
2957 step = 3;
2958 l = cw.xy[2] - cw.xy[0];
2959 }
2960 dest = ctx->rgb + (cw.xy[1] - ctx->xy[1]) * ww +
2961 (cw.xy[0] - ctx->xy[0]) * 3;
2962
2963 for (i = 0; i < l; i++)
2964 {
2965 k = cc[((i + ofs) / 3) & 1];
2966 dest[0] = INT_2_R(k);
2967 dest[1] = INT_2_G(k);
2968 dest[2] = INT_2_B(k);
2969 dest += step;
2970 }
2971
2972 if (ctx == &cw) cmd_setv(drawing_canvas, ctx, CANVAS_PAINT);
2973 }
2974
2975 /* Polygon drawing to RGB memory */
draw_poly(int * xy,int cnt,int shift,int x00,int y00,rgbcontext * ctx)2976 void draw_poly(int *xy, int cnt, int shift, int x00, int y00, rgbcontext *ctx)
2977 {
2978 linedata line;
2979 unsigned char *rgb;
2980 int i, x0, y0, x1, y1, dx, dy, a0, a, w, vxy[4];
2981
2982 copy4(vxy, ctx->xy);
2983 w = vxy[2] - vxy[0];
2984 --vxy[2]; --vxy[3];
2985
2986 x1 = x00 + *xy++; y1 = y00 + *xy++;
2987 a = x1 < vxy[0] ? 1 : x1 > vxy[2] ? 2:
2988 y1 < vxy[1] ? 3 : y1 > vxy[3] ? 4 : 5;
2989 for (i = 1; i < cnt; i++)
2990 {
2991 x0 = x1; y0 = y1; a0 = a;
2992 x1 = x00 + *xy++; y1 = y00 + *xy++;
2993 dx = abs(x1 - x0); dy = abs(y1 - y0);
2994 if (dx < dy) dx = dy; shift += dx;
2995 switch (a0) // Basic clipping
2996 {
2997 // Already visible - skip if same point
2998 case 0: if (!dx) continue; break;
2999 // Left of window - skip if still there
3000 case 1: if (x1 < vxy[0]) continue; break;
3001 // Right of window
3002 case 2: if (x1 > vxy[2]) continue; break;
3003 // Top of window
3004 case 3: if (y1 < vxy[1]) continue; break;
3005 // Bottom of window
3006 case 4: if (y1 > vxy[3]) continue; break;
3007 // First point - never skip
3008 case 5: a0 = 0; break;
3009 }
3010 // May be visible - find where the other end goes
3011 a = x1 < vxy[0] ? 1 : x1 > vxy[2] ? 2 :
3012 y1 < vxy[1] ? 3 : y1 > vxy[3] ? 4 : 0;
3013 line_init(line, x0, y0, x1, y1);
3014 if (a0 + a) // If both ends inside area, no clipping needed
3015 {
3016 if (line_clip(line, vxy, &a0) < 0) continue;
3017 dx -= a0;
3018 }
3019 // Draw to RGB
3020 for (dx = shift - dx; line[2] >= 0; line_step(line) , dx++)
3021 {
3022 rgb = ctx->rgb + ((line[1] - ctx->xy[1]) * w +
3023 (line[0] - ctx->xy[0])) * 3;
3024 rgb[0] = rgb[1] = rgb[2] = ((~dx >> 2) & 1) * 255;
3025 }
3026 }
3027 }
3028
3029 /* Clip area to image & align rgb pointer with it */
clip_to_image(int * rect,unsigned char * rgb,int * vxy)3030 static unsigned char *clip_to_image(int *rect, unsigned char *rgb, int *vxy)
3031 {
3032 int rxy[4], mw, mh;
3033
3034 /* Clip update area to image bounds */
3035 xy_origin(rxy, vxy, margin_main_x, margin_main_y);
3036 canvas_size(&mw, &mh);
3037 if (!clip(rect, 0, 0, mw, mh, rxy)) return (NULL);
3038
3039 /* Align buffer with image */
3040 rgb += ((rxy[2] - rxy[0]) * (rect[1] - rxy[1]) + (rect[0] - rxy[0])) * 3;
3041 return (rgb);
3042 }
3043
3044 /* Map clipping rectangle to line-space, for use with line_clip() */
prepare_line_clip(int * lxy,int * vxy,int scale)3045 void prepare_line_clip(int *lxy, int *vxy, int scale)
3046 {
3047 int i;
3048
3049 for (i = 0; i < 4; i++)
3050 lxy[i] = floor_div(vxy[i] - margin_main_xy[i & 1] - (i >> 1), scale);
3051 }
3052
canvas_render(u_render_state * u,int py,int ph)3053 static void canvas_render(u_render_state *u, int py, int ph)
3054 {
3055 int cxy[4], rxy[4], pw = u->pw;
3056 unsigned char *rgb = NULL;
3057
3058 /* Render underlying layers */
3059 if (u->lr)
3060 {
3061 copy4(cxy, u->cxy);
3062 rgb = u->rgb + (py - cxy[1]) * pw;
3063 cxy[3] = (cxy[1] = py) + ph;
3064 render_layers(rgb, cxy, pw, u->r.zoom, u->r.scale,
3065 0, layer_selected - 1, FALSE);
3066 }
3067
3068 /* Render canvas image */
3069 if (u->irgb && clip(rxy, 0, py, INT_MAX, py + ph, u->r.rxy))
3070 main_render(u, rxy[1], rxy[3] - rxy[1]);
3071
3072 /* Render overlying layers */
3073 if (u->lr) render_layers(rgb, cxy, pw, u->r.zoom, u->r.scale,
3074 layer_selected + 1, layers_total, FALSE);
3075 }
3076
3077 #ifdef U_THREADS
3078
do_canvas_render(tcb * thread)3079 static void do_canvas_render(tcb *thread)
3080 {
3081 u_render_state *u = thread->data;
3082 int *rr = u->lr ? u->cxy : u->r.rxy;
3083 int y0 = rr[1], scale = u->r.scale, h = thread->nsteps * scale;
3084 int d = floor_mod(y0, scale);
3085
3086 if (thread->step0) y0 += thread->step0 * scale - d;
3087 else h -= d;
3088 if (h > rr[3] - y0) h = rr[3] - y0;
3089
3090 canvas_render(u, y0, h);
3091 }
3092
3093 #endif
3094
3095 int kpix_threads; // Min kpixels per render thread
3096
paint_canvas(void * dt,void ** wdata,int what,void ** where,rgbcontext * ctx)3097 static int paint_canvas(void *dt, void **wdata, int what, void **where,
3098 rgbcontext *ctx)
3099 {
3100 u_render_state u;
3101 unsigned char *irgb, *rgb = ctx->rgb;
3102 int rect[4], vxy[4];
3103 int i, px, py, pw, ph, zoom = 1, scale = 1, paste_f = FALSE;
3104
3105 pw = ctx->xy[2] - (px = ctx->xy[0]);
3106 ph = ctx->xy[3] - (py = ctx->xy[1]);
3107 memset(rgb, mem_background, pw * ph * 3);
3108
3109 /* Find out which part is image */
3110 irgb = clip_to_image(rect, rgb, ctx->xy);
3111
3112 /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
3113 if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
3114 else scale = rint(can_zoom);
3115
3116 /* Prepare data for renderer */
3117 memset(&u, 0, sizeof(u));
3118 u.r.zoom = zoom;
3119 u.r.scale = scale;
3120 u.rgb = rgb;
3121 u.irgb = irgb;
3122 u.pw = pw * 3;
3123 xy_origin(u.cxy, ctx->xy, margin_main_x, margin_main_y);
3124
3125 u.lr = layers_total && show_layers_main;
3126
3127 /* Set up image for renderer */
3128 if (irgb)
3129 {
3130 copy4(u.r.rxy, rect);
3131 main_render_req(&u);
3132 paste_f = u.pflag;
3133 }
3134
3135 if (bkg_flag && bkg_rgb) async_bk = render_bkg(ctx); /* Tracing image */
3136 else if (!u.lr) /* Render default background if no layers shown */
3137 {
3138 if (irgb && ((mem_xpm_trans >= 0) ||
3139 (!overlay_alpha && mem_img[CHN_ALPHA] && !channel_dis[CHN_ALPHA])))
3140 async_bk = render_background(irgb, rect[0], rect[1],
3141 rect[2] - rect[0], rect[3] - rect[1], pw * 3);
3142 }
3143
3144 while (irgb || u.lr)
3145 {
3146 #ifdef U_THREADS
3147 int nt, nt2, pww = 0, wh = 0;
3148 size_t vpix = 0;
3149
3150 /* Calculate amount of work for threads */
3151 if (irgb)
3152 {
3153 wh = xy_span(rect, scale, 1);
3154 pww = xy_span(rect, scale, 0);
3155 vpix = pww * wh;
3156 }
3157 if (u.lr)
3158 {
3159 size_t vp2 = render_layers(NULL, u.cxy, 0, zoom, scale,
3160 0, layers_total, FALSE);
3161 if (!vp2) break; // Nothing here, move along
3162 // !!! Heuristic weight; maybe 1/8 would be better?
3163 vpix += vp2 / 4 - vpix / 4;
3164
3165 u.rgb += ((u.cxy[1] - py + margin_main_y) * pw +
3166 u.cxy[0] - px + margin_main_x) * 3;
3167
3168 wh = xy_span(u.cxy, scale, 1);
3169 pww = xy_span(u.cxy, async_bk ? 1 : scale, 0);
3170 }
3171
3172 nt = image_threads(pww, wh);
3173 nt2 = ceil_div(vpix, kpix_threads * 1024);
3174 if (nt2 > nt) nt2 = nt;
3175
3176 #ifndef HAVE__SFA
3177 /* csel_scan() takes 50%+ of per-row time, so when it cannot share
3178 * its color cache, 2 threads can interleave calls to it but more
3179 * than 2 only waste even more time on lock contention - WJ */
3180 if ((nt2 > 2) && (u.m.overlay_s || (mem_cselect &&
3181 (u.tflag | u.xflag | u.gflag | u.pflag)))) nt2 = 2;
3182 #endif
3183
3184 u.tdata = talloc(MA_SKIP_ZEROSIZE | MA_FLAG_NONE, nt2,
3185 &u, sizeof(u), NULL,
3186 #else /* ifndef U_THREADS */
3187 u.tdata = multialloc(MA_SKIP_ZEROSIZE | MA_FLAG_NONE,
3188 #endif
3189 &u.m.rgb, u.m.rgb_s,
3190 &u.m.mask, u.m.mask_s,
3191 &u.m.overlay, u.m.overlay_s,
3192 &u.m.channel, u.m.channel_s,
3193 &u.m.alpha, u.m.alpha_s,
3194 &u.m.n_channel, u.m.n_channel_s,
3195 &u.m.n_alpha, u.m.n_alpha_s,
3196 &u.m.n_opacity, u.m.n_opacity_s,
3197 NULL);
3198
3199 #ifdef U_THREADS
3200 if (u.tdata && (u.tdata != MEM_NONE)) // Threads w/allocation
3201 {
3202 /* !!! do_transform(), and any other preview code that
3203 * self-initializes global tables, should get an init
3204 * call here to avoid a thread clash - WJ */
3205 if (u.tflag) do_transform(0, 0, 0, NULL, NULL, NULL, 255);
3206
3207 nt /= u.tdata->count;
3208 if (nt > MAX_TH_STRIPS) nt = MAX_TH_STRIPS;
3209 u.tdata->chunks = nt;
3210 u.tdata->silent = TRUE;
3211 launch_threads(do_canvas_render, u.tdata, NULL, wh);
3212 }
3213 else
3214 #endif
3215 if (u.tdata) // Single thread
3216 {
3217 int *rr = u.lr ? u.cxy : rect;
3218 canvas_render(&u, rr[1], rr[3] - rr[1]);
3219 }
3220 if (u.tdata != MEM_NONE) free(u.tdata);
3221 break;
3222 }
3223
3224 /* No grid at all */
3225 if (!mem_show_grid || (scale < mem_grid_min));
3226 /* No paste - single area */
3227 else if (!paste_f) draw_grid(rgb, px, py, pw, ph, pw);
3228 /* With paste - zero to four areas */
3229 else
3230 {
3231 int n, x0, y0, w0, h0, rect04[5 * 4], *p = rect04;
3232 unsigned char *r;
3233
3234 w0 = (marq_x2 < mem_width ? marq_x2 + 1 : mem_width) * scale;
3235 x0 = marq_x1 > 0 ? marq_x1 * scale : 0;
3236 w0 -= x0; x0 += margin_main_x;
3237 h0 = (marq_y2 < mem_height ? marq_y2 + 1 : mem_height) * scale;
3238 y0 = marq_y1 > 0 ? marq_y1 * scale : 0;
3239 h0 -= y0; y0 += margin_main_y;
3240
3241 n = clip4(rect04, px, py, pw, ph, x0, y0, w0, h0);
3242 while (n--)
3243 {
3244 p += 4;
3245 r = rgb + ((p[1] - py) * pw + (p[0] - px)) * 3;
3246 draw_grid(r, p[0], p[1], p[2], p[3], pw);
3247 }
3248 }
3249
3250 /* Tile grid */
3251 if (show_tile_grid && irgb) draw_tgrid(irgb, rect[0], rect[1],
3252 rect[2] - rect[0], rect[3] - rect[1], pw);
3253
3254 /* Segmentation preview */
3255 if (seg_preview && irgb) draw_segments(irgb, rect[0], rect[1],
3256 rect[2] - rect[0], rect[3] - rect[1], pw);
3257
3258 async_bk = FALSE;
3259
3260 /* !!! All other over-the-image things have to be redrawn here as well !!! */
3261 prepare_line_clip(vxy, ctx->xy, scale);
3262 /* Redraw gradient line if needed */
3263 i = gradient[mem_channel].status;
3264 if ((mem_gradient || (tool_type == TOOL_GRADIENT)) &&
3265 (mouse_left_canvas ? (i == GRAD_DONE) : (i != GRAD_NONE)))
3266 refresh_line(3, vxy, ctx);
3267
3268 /* Draw marquee as we may have drawn over it */
3269 if ((marq_status != MARQUEE_NONE) && irgb)
3270 paint_marquee(MARQ_SHOW, 0, 0, ctx);
3271 if ((tool_type == TOOL_POLYGON) && poly_points)
3272 paint_poly_marquee(ctx);
3273
3274 /* Redraw line if needed */
3275 if ((((tool_type == TOOL_POLYGON) && (poly_status == POLY_SELECTING)) ||
3276 ((tool_type == TOOL_LINE) && (line_status == LINE_LINE))) &&
3277 !mouse_left_canvas)
3278 refresh_line(tool_type == TOOL_LINE ? 1 : 2, vxy, ctx);
3279
3280 /* Redraw perimeter if needed */
3281 if (perim_status) repaint_perim(ctx);
3282
3283 return (TRUE); // now draw this
3284 }
3285
repaint_canvas(int px,int py,int pw,int ph)3286 void repaint_canvas(int px, int py, int pw, int ph)
3287 {
3288 rgbcontext ctx = { { px, py, px + pw, py + ph }, NULL };
3289
3290 cmd_setv(drawing_canvas, &ctx, CANVAS_PAINT);
3291 if (!ctx.rgb) return;
3292 paint_canvas(NULL, NULL, 0, NULL, &ctx);
3293 cmd_setv(drawing_canvas, &ctx, CANVAS_PAINT);
3294 }
3295
3296 /* Update x,y,w,h area of current image */
main_update_area(int x,int y,int w,int h)3297 void main_update_area(int x, int y, int w, int h)
3298 {
3299 int zoom, scale, rxy[4];
3300
3301 if (can_zoom < 1.0)
3302 {
3303 zoom = rint(1.0 / can_zoom);
3304 w += x;
3305 h += y;
3306 x = floor_div(x + zoom - 1, zoom);
3307 y = floor_div(y + zoom - 1, zoom);
3308 w = (w - x * zoom + zoom - 1) / zoom;
3309 h = (h - y * zoom + zoom - 1) / zoom;
3310 if ((w <= 0) || (h <= 0)) return;
3311 }
3312 else
3313 {
3314 scale = rint(can_zoom);
3315 x *= scale;
3316 y *= scale;
3317 w *= scale;
3318 h *= scale;
3319 if (color_grid && mem_show_grid && (scale >= mem_grid_min))
3320 w++ , h++; // Redraw grid lines bordering the area
3321 }
3322
3323 rxy[2] = (rxy[0] = x + margin_main_x) + w;
3324 rxy[3] = (rxy[1] = y + margin_main_y) + h;
3325 cmd_setv(drawing_canvas, rxy, CANVAS_REPAINT);
3326 }
3327
3328 /* Get zoomed canvas size */
canvas_size(int * w,int * h)3329 void canvas_size(int *w, int *h)
3330 {
3331 int zoom = 1, scale = 1;
3332
3333 /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
3334 if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
3335 else scale = rint(can_zoom);
3336
3337 *w = (mem_width * scale + zoom - 1) / zoom;
3338 *h = (mem_height * scale + zoom - 1) / zoom;
3339 }
3340
clear_perim(perim_info * p)3341 static void clear_perim(perim_info *p)
3342 {
3343 int ps = p->mode;
3344 p->mode = 0; /* Cleared */
3345 clear_perim_real(p->x, p->y, p->s);
3346 if (ps == TOOL_CLONE + 1)
3347 clear_perim_real(p->x + p->cx, p->y + p->cy, p->s);
3348 }
3349
repaint_perim_real(int c,int ox,int oy,rgbcontext * ctx)3350 static void repaint_perim_real(int c, int ox, int oy, rgbcontext *ctx)
3351 {
3352 int w, h, x0, y0, x1, y1, zoom = 1, scale = 1;
3353
3354
3355 /* !!! This uses the fact that zoom factor is either N or 1/N !!! */
3356 if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
3357 else scale = rint(can_zoom);
3358
3359 ox += perim_x; oy += perim_y;
3360 x0 = margin_main_x + (ox * scale) / zoom;
3361 y0 = margin_main_y + (oy * scale) / zoom;
3362 x1 = margin_main_x + ((ox + perim_s - 1) * scale) / zoom + scale - 1;
3363 y1 = margin_main_y + ((oy + perim_s - 1) * scale) / zoom + scale - 1;
3364
3365 w = x1 - x0 + 1;
3366 h = y1 - y0 + 1;
3367
3368 draw_dash(c, RGB_2_INT(0, 0, 0), 0, x0, y0, 1, h, ctx);
3369 draw_dash(c, RGB_2_INT(0, 0, 0), 0, x1, y0, 1, h, ctx);
3370
3371 draw_dash(c, RGB_2_INT(0, 0, 0), 0, x0 + 1, y0, w - 2, 1, ctx);
3372 draw_dash(c, RGB_2_INT(0, 0, 0), 0, x0 + 1, y1, w - 2, 1, ctx);
3373 }
3374
repaint_perim(rgbcontext * ctx)3375 static void repaint_perim(rgbcontext *ctx)
3376 {
3377 repaint_perim_real(RGB_2_INT(255, 255, 255), 0, 0, ctx);
3378 if (perim_status == TOOL_CLONE + 1)
3379 repaint_perim_real(RGB_2_INT(255, 0, 0), perim_cx, perim_cy, ctx);
3380 }
3381
move_perim(int x,int y)3382 static void move_perim(int x, int y)
3383 {
3384 perim_info p = perim_state;
3385
3386 perim_status = 0; /* Clear */
3387 perim_wx = x; /* Remember */
3388 perim_wy = y;
3389 if ((tool_size * can_zoom > 4) && !NO_PERIM(tool_type) && !mouse_left_canvas)
3390 {
3391 perim_x = x - (tool_size >> 1);
3392 perim_y = y - (tool_size >> 1);
3393 perim_s = tool_size;
3394 perim_cx = clone_dx;
3395 perim_cy = clone_dy;
3396 perim_status = tool_type + 1; /* Draw */
3397 }
3398 if (p.mode) clear_perim(&p);
3399 if (perim_status) repaint_perim(NULL);
3400 }
3401
configure_canvas()3402 static void configure_canvas()
3403 {
3404 int new_margin_x = 0, new_margin_y = 0;
3405
3406 if (canvas_image_centre)
3407 {
3408 int w, h, wh[2];
3409 cmd_peekv(drawing_canvas, wh, sizeof(wh), CANVAS_SIZE);
3410 canvas_size(&w, &h);
3411 if ((wh[0] -= w) > 0) new_margin_x = wh[0] >> 1;
3412 if ((wh[1] -= h) > 0) new_margin_y = wh[1] >> 1;
3413 }
3414
3415 if ((new_margin_x != margin_main_x) || (new_margin_y != margin_main_y))
3416 {
3417 margin_main_x = new_margin_x;
3418 margin_main_y = new_margin_y;
3419 cmd_repaint(drawing_canvas);
3420 // Force redraw of whole canvas as the margin has shifted
3421 }
3422 vw_realign(); // Update the view window as needed
3423 }
3424
force_main_configure()3425 void force_main_configure()
3426 {
3427 if (cmd_mode) return;
3428 if (drawing_canvas) configure_canvas();
3429 if (view_showing && vw_drawing) vw_configure();
3430 }
3431
set_cursor(void ** what)3432 void set_cursor(void **what) // Set mouse cursor
3433 {
3434 cmd_cursor(drawing_canvas, !cursor_tool ? NULL : what ? what :
3435 m_cursor[tool_type]);
3436 }
3437
change_to_tool(int icon)3438 void change_to_tool(int icon)
3439 {
3440 grad_info *grad;
3441 void *var, **slot = icon_buttons[icon];
3442 int i, t, update = UPD_SEL;
3443
3444 if (!cmd_checkv(slot, SLOT_SENSITIVE)) return; // Blocked
3445 // !!! Unnecessarily complicated approach - better add a peek operation
3446 var = cmd_read(slot, NULL);
3447 if (*(int *)var != TOOL_ID(slot)) cmd_set(slot, TRUE);
3448
3449 switch (icon)
3450 {
3451 case TTB_PAINT:
3452 t = brush_type; break;
3453 case TTB_SHUFFLE:
3454 t = TOOL_SHUFFLE; break;
3455 case TTB_FLOOD:
3456 t = TOOL_FLOOD; break;
3457 case TTB_LINE:
3458 t = TOOL_LINE; break;
3459 case TTB_SMUDGE:
3460 t = TOOL_SMUDGE; break;
3461 case TTB_CLONE:
3462 t = TOOL_CLONE; break;
3463 case TTB_SELECT:
3464 t = TOOL_SELECT; break;
3465 case TTB_POLY:
3466 t = TOOL_POLYGON; break;
3467 case TTB_GRAD:
3468 t = TOOL_GRADIENT; break;
3469 default: return;
3470 }
3471
3472 /* Tool hasn't changed (likely, recursion changed it from under us) */
3473 if (t == tool_type) return;
3474
3475 /* Make sure tool release actions are done */
3476 tool_done();
3477
3478 if (perim_status) clear_perim(&perim_state);
3479 i = tool_type;
3480 tool_type = t;
3481
3482 grad = gradient + mem_channel;
3483 if (i == TOOL_LINE) stop_line();
3484 if ((i == TOOL_GRADIENT) && (grad->status != GRAD_NONE))
3485 {
3486 if (grad->status != GRAD_DONE) grad->status = GRAD_NONE;
3487 else if (grad_opacity) update |= CF_DRAW;
3488 else if (!mem_gradient) repaint_grad(NULL);
3489 }
3490 if ( marq_status != MARQUEE_NONE)
3491 {
3492 if (!script_cmds && paste_commit && (marq_status >= MARQUEE_PASTE))
3493 {
3494 commit_paste(FALSE, NULL);
3495 pen_down = 0;
3496 mem_undo_prepare();
3497 }
3498
3499 marq_status = MARQUEE_NONE; // Marquee is on so lose it!
3500 update |= CF_DRAW; // Needed to clear selection
3501 }
3502 if ( poly_status != POLY_NONE)
3503 {
3504 poly_status = POLY_NONE; // Marquee is on so lose it!
3505 poly_points = 0;
3506 update |= CF_DRAW; // Needed to clear selection
3507 }
3508 if (tool_type == TOOL_CLONE) init_clone();
3509 /* Persistent selection frame */
3510 // !!! To NOT show selection frame while placing gradient
3511 // if ((tool_type == TOOL_SELECT)
3512 if (((tool_type == TOOL_SELECT) || (tool_type == TOOL_GRADIENT))
3513 && (marq_x1 >= 0) && (marq_y1 >= 0)
3514 && (marq_x2 >= 0) && (marq_y2 >= 0))
3515 {
3516 marq_status = MARQUEE_DONE;
3517 paint_marquee(MARQ_SHOW, 0, 0, NULL);
3518 }
3519 if ((tool_type == TOOL_GRADIENT) && (grad->status != GRAD_NONE))
3520 {
3521 if (grad_opacity) update |= CF_DRAW;
3522 else repaint_grad(NULL);
3523 }
3524 update_stuff(update);
3525 move_perim(perim_wx, perim_wy); // New perimeter in old location
3526 }
3527
pressed_view_hori(int state)3528 static void pressed_view_hori(int state)
3529 {
3530 view_vsplit = !!state;
3531 if (view_showing) view_show(); // rearrange
3532 }
3533
set_image(int state)3534 void set_image(int state)
3535 {
3536 static int depth = 0;
3537
3538 if (state ? --depth : depth++) return;
3539
3540 cmd_showhide(main_split, state);
3541 }
3542
read_hex_dub(char * in)3543 static char read_hex_dub(char *in) // Read hex double
3544 {
3545 static const char chars[] = "0123456789abcdef0123456789ABCDEF";
3546 const char *p1, *p2;
3547
3548 p1 = strchr(chars, in[0]);
3549 p2 = strchr(chars, in[1]);
3550 return (p1 && p2 ? (((p1 - chars) & 15) << 4) + ((p2 - chars) & 15) : '?');
3551 }
3552
parse_drag(main_dd * dt,void ** wdata,int what,void ** where,drag_ext * drag)3553 static void parse_drag(main_dd *dt, void **wdata, int what, void **where,
3554 drag_ext *drag)
3555 {
3556 #ifdef WIN32
3557 char fname[PATHTXT];
3558 #else
3559 char fname[PATHBUF];
3560 #endif
3561 char ch, *tp, *tp2, *txt = drag->data;
3562 int i, j, nlayer = TRUE;
3563
3564
3565 if (drag->len <= 0) return;
3566
3567 set_image(FALSE);
3568
3569 tp = txt;
3570 while ((layers_total < MAX_LAYERS) && (tp2 = strstr(tp, "file:")))
3571 {
3572 tp = tp2 + 5;
3573 while (*tp == '/') tp++;
3574 #ifndef WIN32
3575 // If not windows keep a leading slash
3576 tp--;
3577 // If windows strip away all leading slashes
3578 #endif
3579 i = 0;
3580 while ((ch = *tp++) > 31) // Copy filename
3581 {
3582 if (ch == '%') // Weed out those ghastly % substitutions
3583 {
3584 ch = read_hex_dub(tp);
3585 tp += 2;
3586 }
3587 fname[i++] = ch;
3588 if (i >= sizeof(fname) - 1) break;
3589 }
3590 fname[i] = 0;
3591 tp--;
3592
3593 #ifdef WIN32
3594 /* !!! GTK+ uses UTF-8 encoding for URIs on Windows */
3595 gtkncpy(fname, fname, PATHBUF);
3596 reseparate(fname);
3597 #endif
3598 j = detect_image_format(fname);
3599 if ((j > 0) && (j != FT_NONE) && (j != FT_LAYERS1))
3600 {
3601 if (!nlayer || layer_add(0, 0, 1, 0, mem_pal_def, 0))
3602 nlayer = load_image(fname, FS_LAYER_LOAD, j) == 1;
3603 if (nlayer) set_new_filename(layers_total, fname);
3604 }
3605 }
3606 if (!nlayer) layer_delete(layers_total);
3607
3608 layer_refresh_list();
3609 layer_choose(layers_total);
3610 layers_notify_changed();
3611 if (layers_total) view_show();
3612 set_image(TRUE);
3613 }
3614
3615
3616 static clipform_dd uri_list = { "text/uri-list" };
3617
3618
3619 static void pressed_pal_copy();
3620 static void pressed_pal_paste();
3621 static void pressed_sel_ramp(int vert);
3622
3623 static const signed char arrow_dx[4] = { 0, -1, 1, 0 },
3624 arrow_dy[4] = { 1, 0, 0, -1 };
3625
move_marquee(int action,int * xy,int change,int dir)3626 static void move_marquee(int action, int *xy, int change, int dir)
3627 {
3628 int dx = tgrid_dx, dy = tgrid_dy;
3629 if (!tgrid_snap) dx = dy = change;
3630 paint_marquee(action,
3631 xy[0] + dx * arrow_dx[dir], xy[1] + dy * arrow_dy[dir], NULL);
3632 update_stuff(UPD_SGEOM);
3633 }
3634
3635 /// SCRIPTING
3636
3637 /* Number of args is ((char **)res[0] - res - 1); res[] is NULL-terminated */
wj_parse_argv(char * src)3638 char **wj_parse_argv(char *src)
3639 {
3640 char c, q, q0, *dest, *tmp, **v;
3641 int n = 0, l = strlen(src);
3642
3643 dest = tmp = malloc(l + 1);
3644 q0 = q = ' '; // Start outside word
3645 while ((c = *src++))
3646 {
3647 /* Various quoted states */
3648 if (q == '#')
3649 {
3650 if (c == '\n') q = ' ';
3651 }
3652 else if (q == '\\')
3653 {
3654 q = q0;
3655 if (q == '"')
3656 {
3657 if ((c != '"') && (c != '\\') && (c != '`') &&
3658 (c != '$') && (c != '\n')) *dest++ = '\\';
3659 *dest++ = c;
3660 }
3661 else if (c != '\n')
3662 {
3663 *dest++ = c;
3664 q = 0;
3665 }
3666 }
3667 else if (q == '"')
3668 {
3669 if (c == '\\') q0 = q , q = c;
3670 else if (c == '"') q = 0;
3671 else *dest++ = c;
3672 }
3673 else if (q == '\'')
3674 {
3675 if (c == '\'') q = 0;
3676 else *dest++ = c;
3677 }
3678 /* Unquoted state - in a word or in between */
3679 else if ((c == '\n') || (c == ' ') || (c == '\t'))
3680 {
3681 if (!q) *dest++ = 0 , n++;
3682 q = ' ';
3683 }
3684 else if (c == '\\') q0 = q , q = c;
3685 else if ((c == '#') && q) q = c;
3686 else if ((c == '"') || (c == '\'')) q = c;
3687 else
3688 {
3689 *dest++ = c;
3690 q = 0;
3691 }
3692 }
3693 /* Final state */
3694 if (!q) *dest++ = 0 , n++;
3695 else if ((q == '\\') || (q == '"') || (q == '\'')) n = -1; // Error
3696 l = dest - tmp;
3697 if ((n > 0) && (v = realloc(tmp, sizeof(*v) * (n + 1) + l)))
3698 {
3699 tmp = (void *)(v + n + 1);
3700 memmove(tmp, v, l);
3701 for (l = 0; l < n; l++)
3702 {
3703 v[l] = tmp;
3704 tmp += strlen(tmp) + 1;
3705 }
3706 v[n] = NULL;
3707 return (v);
3708 }
3709 /* Syntax error, or empty string */
3710 free(tmp);
3711 return (NULL);
3712 }
3713
command_slot(char * cmd)3714 static void **command_slot(char *cmd)
3715 {
3716 void **slot;
3717 int l, m;
3718
3719 l = strspn(++cmd, "/");
3720 l += strcspn(cmd + l, "=/");
3721 slot = find_slot(NEXT_SLOT(main_menubar), cmd, l,
3722 (cmd[0] != '/') && (cmd[l] == '/'));
3723 m = 2;
3724 while (slot && (cmd[l] == '/'))
3725 {
3726 cmd += l + 1;
3727 l = strcspn(cmd, "=/");
3728 slot = find_slot(NEXT_SLOT(slot), cmd, l, m++);
3729 }
3730 return (slot);
3731 }
3732
3733 #define MAX_NESTING 16 /* Defuse recursion bombs */
3734
run_script(char ** res)3735 int run_script(char **res)
3736 {
3737 static int level;
3738 void **slot;
3739 char **cur, *str = NULL, *err = NULL;
3740 int n;
3741
3742 level++;
3743 if (!res || !res[0]) err = _("Empty string or broken quoting");
3744 else if (res[0][0] != '-') err = _("Script must begin with a command");
3745 else if (level > MAX_NESTING) err = _("Script nesting limit exceeded");
3746 else
3747 {
3748 user_break = 0;
3749 for (cur = res; cur[0]; cur++)
3750 {
3751 if (cur[0][0] != '-') continue; // Skip to next command
3752 if (!strcmp(cur[0], "--")) break; // End marker
3753 slot = command_slot(cur[0]);
3754 if (!slot) str = _("'%s' does not match any item");
3755 else if (!cmd_checkv(slot, SLOT_SCRIPTABLE))
3756 str = _("'%s' matches a non-scriptable item");
3757 else if (!cmd_checkv(slot, SLOT_SENSITIVE))
3758 str = _("'%s' matches a disabled item");
3759 else
3760 {
3761 char *chain[3], *tmp = strchr(cur[0], '=');
3762
3763 script_cmds = cur + 1;
3764 /* Send default value on, if no toggle-item */
3765 if (tmp && !slot_data(slot, NULL))
3766 {
3767 chain[0] = tmp;
3768 chain[1] = (void *)&chain[1];
3769 chain[2] = (void *)script_cmds;
3770 script_cmds = chain;
3771 }
3772
3773 /* Activate the item */
3774 n = cmd_setstr(slot, tmp + !!tmp); // skip "="
3775 script_cmds = NULL;
3776
3777 if (n < 0) str = _("'%s' value does not fit item");
3778 else if (!user_break) continue;
3779 }
3780 break; // Terminate on error
3781 }
3782 if (str) err = str = g_strdup_printf(__(str), cur[0]);
3783 update_stuff(CF_NOW); // Do cumulative update
3784 }
3785 level--;
3786 if (err) alert_box(_("Error"), err, NULL);
3787 if (str) g_free(str);
3788 return (!err ? 1 : str ? -1 : 0); // 1 = clean, -1 = buggy, 0 = wrong
3789 }
3790
3791 #define SCRIPT_ITEMS 10
3792 #define SCRIPTS_MAX FACTION_ROWS_TOTAL
3793 #define MAXNAMELEN 2048
3794
3795 #define SCRIPT1_NAME "# Resharpen image"
3796 #define SCRIPT1_CODE "# Resharpen image after rescaling\n" \
3797 "-effect/unsharp r=1 am=0.4 -effect/unsharp r=30 am=0.1"
3798
3799 static char *script_ini[2] = { "scriptName%d", "script%d" };
3800
launch_script(int n)3801 static void launch_script(int n)
3802 {
3803 char txt[64], **res;
3804
3805 sprintf(txt, script_ini[1], n);
3806 res = wj_parse_argv(inifile_get(txt, ""));
3807 run_script(res);
3808 free(res);
3809 }
3810
update_script_menu()3811 static void update_script_menu() // Populate menu
3812 {
3813 int i, v, items = 0;
3814 char txt[64], *nm, *sm, *ns = SCRIPT1_NAME, *ss = SCRIPT1_CODE;
3815 void **slot;
3816
3817 /* Show valid slots in menu */
3818 for (i = 1; i <= SCRIPT_ITEMS; i++)
3819 {
3820 sprintf(txt, script_ini[0], i);
3821 nm = inifile_get(txt, ns);
3822
3823 sprintf(txt, script_ini[1], i);
3824 sm = inifile_get(txt, ss);
3825
3826 slot = menu_slots[MENU_SCRIPT1 - 1 + i];
3827
3828 if ((v = nm && nm[0] && (nm[0] != '#') && sm && sm[0]))
3829 cmd_setv(slot, nm, LABEL_VALUE);
3830
3831 cmd_showhide(slot, v); // Hide by default
3832 cmd_sensitive(slot, v); // Make insensitive for shortcuts
3833 items += v;
3834 ns = ss = "";
3835 }
3836
3837 /* Hide submenu if no valid slots */
3838 cmd_showhide(menu_slots[MENU_SCRIPT_M], items);
3839 cmd_showhide(menu_slots[MENU_SCRIPT], !items);
3840 }
3841
3842 typedef struct {
3843 char *rows[SCRIPTS_MAX][4];
3844 char *script, *name;
3845 void **list, **nm, **tx, **exec;
3846 int nidx, idx, cnt, lock, changed;
3847 } script_dd;
3848
update_text(script_dd * dt)3849 static void update_text(script_dd *dt)
3850 {
3851 if (dt->changed)
3852 {
3853 char **rp = dt->rows[dt->idx];
3854 cmd_read(dt->tx, dt);
3855 if (rp[1] != rp[3]) free(rp[1]);
3856 rp[1] = strdup(dt->script);
3857 dt->changed = FALSE;
3858 }
3859 }
3860
script_click(script_dd * dt,void ** wdata,int what,void ** where)3861 static void script_click(script_dd *dt, void **wdata, int what, void **where)
3862 {
3863 char txt[64], **rp;
3864 int i, idx[SCRIPTS_MAX];
3865
3866 if (what == op_EVT_DESTROY) // Finalize
3867 {
3868 cmd_sensitive(menu_slots[MENU_SCRIPT], TRUE);
3869 cmd_sensitive(menu_slots[MENU_SCRIPTC], TRUE);
3870 return;
3871 }
3872
3873 if (what != op_EVT_CANCEL) update_text(dt);
3874
3875 if (origin_slot(where) == dt->exec) // Run script
3876 {
3877 char **res = wj_parse_argv(dt->rows[dt->idx][1]);
3878 run_script(res);
3879 free(res);
3880 return;
3881 }
3882
3883 cmd_showhide(wdata, FALSE);
3884 if (what != op_EVT_CANCEL) // Store scripts
3885 {
3886 cmd_peekv(dt->list, idx, sizeof(idx), LISTC_ORDER);
3887 /* Copy out reordered strings */
3888 for (i = 0; i < SCRIPTS_MAX; i++)
3889 {
3890 if (idx[i] == i) continue;
3891 rp = dt->rows[i];
3892 if (rp[0] == rp[2]) rp[0] = strdup(rp[0]);
3893 if (rp[1] == rp[3]) rp[1] = strdup(rp[1]);
3894 }
3895 /* Store into inifile */
3896 for (i = 0; i < SCRIPTS_MAX; i++)
3897 {
3898 rp = dt->rows[i];
3899 sprintf(txt, script_ini[0], idx[i] + 1);
3900 if (rp[0] != rp[2]) inifile_set(txt, rp[0]);
3901 sprintf(txt, script_ini[1], idx[i] + 1);
3902 if (rp[1] != rp[3]) inifile_set(txt, rp[1]);
3903 }
3904 /* Display in menu */
3905 update_script_menu();
3906 }
3907
3908 for (i = 0; i < SCRIPTS_MAX; i++) // Release string memory
3909 {
3910 rp = dt->rows[i];
3911 if (rp[0] != rp[2]) free(rp[0]);
3912 if (rp[1] != rp[3]) free(rp[1]);
3913 }
3914
3915 run_destroy(wdata);
3916 }
3917
script_changed(script_dd * dt,void ** wdata,int what,void ** where)3918 static void script_changed(script_dd *dt, void **wdata, int what, void **where)
3919 {
3920 char **rp;
3921
3922 if (dt->lock) return;
3923 if (origin_slot(where) == dt->tx) // Text entry
3924 {
3925 dt->changed = TRUE; // Read it later
3926 return;
3927 }
3928
3929 cmd_read(where, dt); // Name entry
3930 rp = dt->rows[dt->idx];
3931 if (rp[0] == rp[2]) rp[0] = malloc(MAXNAMELEN);
3932 strncpy0(rp[0], dt->name, MAXNAMELEN);
3933 cmd_setv(dt->list, (void *)dt->idx, LISTC_RESET_ROW);
3934 }
3935
script_select_row(script_dd * dt,void ** wdata,int what,void ** where)3936 static void script_select_row(script_dd *dt, void **wdata, int what, void **where)
3937 {
3938 char **rp;
3939
3940 cmd_read(where, dt);
3941
3942 if (dt->nidx == dt->idx) return; // no change
3943 dt->lock = TRUE;
3944
3945 /* Update outgoing row */
3946 update_text(dt);
3947
3948 /* Get fields from array for incoming row */
3949 rp = dt->rows[dt->idx = dt->nidx];
3950 cmd_setv(dt->nm, rp[0], ENTRY_VALUE);
3951 cmd_setv(dt->tx, rp[1], TEXT_VALUE);
3952
3953 dt->lock = FALSE;
3954 }
3955
3956 #define WBbase script_dd
3957 static void *script_code[] = {
3958 WINDOW(_("Script")), // nonmodal
3959 // WXYWH("script", 400, 400),
3960 DEFSIZE(400, 400),
3961 EVENT(DESTROY, script_click),
3962 HSEP,
3963 XVBOXB,
3964 BORDER(SCROLL, 0), BORDER(FRAME, 0), BORDER(ENTRY, 0),
3965 VSPLIT,
3966 XSCROLL(1, 1), // auto/auto; 1st page
3967 WLIST,
3968 PTXTCOLUMN(rows[0][0], WBsizeof(rows[0]), 200, 0),
3969 REF(list), LISTCd(nidx, cnt, script_select_row), TRIGGER,
3970 VBOXS, // 2nd page
3971 FHBOXB(_("Action")),
3972 REF(nm), XENTRY(name), EVENT(CHANGE, script_changed),
3973 WDONE, // FHBOX
3974 REF(tx), TEXT(script), EVENT(CHANGE, script_changed),
3975 WDONE, // VBOX
3976 WDONE, // VSPLIT
3977 WDONE, // XVBOX
3978 HSEP,
3979 EQBOX, CANCELBTN(_("Cancel"), script_click),
3980 REF(exec), BUTTON(_("Execute"), script_click),
3981 BUTTON(_("OK"), script_click), FOCUS,
3982 WSHOW
3983 };
3984 #undef WBbase
3985
pressed_script()3986 static void pressed_script()
3987 {
3988 script_dd tdata;
3989 char txt[64], *ns = SCRIPT1_NAME, *ss = SCRIPT1_CODE;
3990 int i;
3991
3992 // Make sure the user can only open 1 script window
3993 cmd_sensitive(menu_slots[MENU_SCRIPT], FALSE);
3994 cmd_sensitive(menu_slots[MENU_SCRIPTC], FALSE);
3995
3996 memset(&tdata, 0, sizeof(tdata));
3997 for (i = 0; i < SCRIPTS_MAX; i++)
3998 {
3999 sprintf(txt, script_ini[0], i + 1);
4000 tdata.rows[i][0] = tdata.rows[i][2] = inifile_get(txt, ns);
4001 sprintf(txt, script_ini[1], i + 1);
4002 tdata.rows[i][1] = tdata.rows[i][3] = inifile_get(txt, ss);
4003 ns = ss = "";
4004 }
4005
4006 tdata.idx = 1;
4007 tdata.cnt = SCRIPTS_MAX;
4008 run_create(script_code, &tdata, sizeof(tdata));
4009 }
4010
4011 typedef struct {
4012 char *name;
4013 } name_dd;
4014
4015 #define WBbase name_dd
4016 static void *name_code[] = {
4017 TOPVBOX, uPATHSTR(name), WSHOW
4018 };
4019 #undef WBbase
4020
load_script()4021 static void load_script()
4022 {
4023 name_dd *dt, tdata = { NULL };
4024 char *txt, *tx2 = NULL, **res = NULL;
4025 void **wdata;
4026
4027 wdata = run_create_(name_code, &tdata, sizeof(tdata), script_cmds);
4028 run_query(wdata);
4029 dt = GET_DDATA(wdata);
4030 txt = slurp_file(dt->name, 0);
4031 run_destroy(wdata);
4032 if (!txt)
4033 {
4034 alert_box(_("Error"), _("Unable to load script"), NULL);
4035 return;
4036 }
4037
4038 /* Stepping through arguments is done within run_create_(), so here
4039 * script_cmds is done with and free for overloading - WJ */
4040 if (!cmd_mode) tx2 = gtkuncpy(NULL, txt, 0); // Files are in system encoding
4041 res = wj_parse_argv(tx2 ? tx2 : txt);
4042 run_script(res);
4043 free(res);
4044 free(tx2);
4045 free(txt);
4046 }
4047
do_script(int what)4048 static void do_script(int what)
4049 {
4050 void **where = what == TOOLBAR_LAYERS ? layers_dock :
4051 what == TOOLBAR_SETTINGS ? settings_dock :
4052 /* what == TOOLBAR_TOOLS */ toolbar_boxes[TOOLBAR_TOOLS];
4053 cmd_run_script(where, script_cmds);
4054 }
4055
4056 /* Script mode image info */
4057 typedef struct {
4058 char *str;
4059 int dest;
4060 } sinf_dd;
4061
4062 static char *info_dest[] = { "stdout", "standard output",
4063 "stderr", "standard error", "clipboard" };
4064 #define DEST_STDERR 2
4065 #define DEST_CLIP 4
4066
4067 #define WBbase sinf_dd
4068 static void *sinf_code[] = {
4069 TOPVBOX,
4070 XENTRY(str),
4071 UNLESSv(cmd_mode), OPT(info_dest, DEST_CLIP + 1, dest),
4072 IFv(cmd_mode), OPT(info_dest, DEST_CLIP, dest),
4073 FLATTEN,
4074 WSHOW
4075 };
4076 #undef WBbase
4077
interpolate_info()4078 static void interpolate_info()
4079 {
4080 static sinf_dd tdata = { "", 0 };
4081 sinf_dd *dt;
4082 void **res;
4083
4084 if (!script_cmds) return;
4085 res = run_create_(sinf_code, &tdata, sizeof(tdata), script_cmds);
4086 run_query(res);
4087 dt = GET_DDATA(res);
4088 if (dt->str[0]) // Do nothing for empty string
4089 {
4090 char *s = interpolate_line(dt->str, FALSE);
4091 if (!s); // Do nothing if could not interpolate
4092 else if (dt->dest < DEST_CLIP)
4093 {
4094 FILE *f = dt->dest < DEST_STDERR ? stdout : stderr;
4095 fputs(s, f);
4096 fflush(f); // In case no LF at end of line
4097 }
4098 else cmd_setv(((main_dd *)GET_DDATA(main_window_))->clipboard,
4099 s, CLIP_TEXT);
4100 free(s);
4101 }
4102 run_destroy(res);
4103 }
4104
4105 /* Script mode color picker */
pick_pixel(multi_ext * mx,void ** where,int ab)4106 static int pick_pixel(multi_ext *mx, void **where, int ab)
4107 {
4108 int area[4], *row = mx->rows[0] + 1;
4109
4110 /* Sanity check */
4111 if ((mx->mincols < 2) || (mx->fractcol >= 0) || (mx->nrows > 2))
4112 return (0);
4113
4114 /* 2 corners */
4115 if ((mx->nrows == 2) && (mx->ncols == 2))
4116 {
4117 area[0] = row[0];
4118 area[1] = row[1];
4119 row = mx->rows[1] + 1;
4120 area[2] = row[0] + 1;
4121 area[3] = row[1] + 1;
4122 }
4123 /* 1 point */
4124 else if (mx->ncols == 2)
4125 {
4126 area[2] = (area[0] = row[0]) + 1;
4127 area[3] = (area[1] = row[1]) + 1;
4128 }
4129 /* Area with size */
4130 else if (mx->ncols == 4)
4131 {
4132 area[2] = (area[0] = row[0]) + row[2];
4133 area[3] = (area[1] = row[1]) + row[3];
4134 }
4135 /* Error */
4136 else return (0);
4137
4138 /* Drop previous regular value */
4139 cmd_set(origin_slot(where), -1);
4140
4141 if (clip(area, 0, 0, mem_width, mem_height, area))
4142 {
4143 int sz = (area[2] -= area[0]) | (area[3] -= area[1]);
4144 pick_color(area[0], area[1], ab, sz != 1 ? area : NULL, -1);
4145 }
4146 return (1);
4147 }
4148
4149 typedef struct {
4150 int n, mode;
4151 } idx_dd;
4152
idx_multi_evt(idx_dd * dt,void ** wdata,int what,void ** where,multi_ext * mx)4153 static int idx_multi_evt(idx_dd *dt, void **wdata, int what, void **where,
4154 multi_ext *mx)
4155 {
4156 /* A/B mode */
4157 if (dt->mode < 0x100) return (pick_pixel(mx, where, dt->mode));
4158 /* Unmask/mask mode */
4159 if ((mx->fractcol >= 0) || (mx->nrows > 1)) return (0); // Error
4160 cmd_set(origin_slot(where), 256); // Drop previous regular value
4161 mem_mask_setv(mx->rows[0] + 1, mx->rows[0][0], dt->mode - 0x100);
4162 return (1);
4163 }
4164
4165 #define WBbase idx_dd
4166 static void *idx_code[] = {
4167 TOPVBOX, SPIN(n, -1, 256), EVENT(MULTI, idx_multi_evt), WSHOW
4168 };
4169 #undef WBbase
4170
script_idx(int mode)4171 static int script_idx(int mode)
4172 {
4173 idx_dd tdata;
4174 void **res;
4175 int n;
4176
4177 if (!script_cmds) return (-1);
4178 tdata.n = -1;
4179 tdata.mode = mode;
4180 res = run_create_(idx_code, &tdata, sizeof(tdata), script_cmds);
4181 run_query(res);
4182 n = ((idx_dd *)GET_DDATA(res))->n;
4183 run_destroy(res);
4184 return (n);
4185 }
4186
4187 typedef struct {
4188 int a, b;
4189 } ab_dd;
4190
ab_pick_pixel(ab_dd * dt,void ** wdata,int what,void ** where,multi_ext * mx)4191 static int ab_pick_pixel(ab_dd *dt, void **wdata, int what, void **where,
4192 multi_ext *mx)
4193 {
4194 return (pick_pixel(mx, where, cmd_read(where, dt) == &dt->b));
4195 }
4196
4197 #define WBbase ab_dd
4198 static void *ab_code[] = {
4199 TOPVBOX,
4200 SPIN(a, -1, 256), ALTNAME("A"), EVENT(MULTI, ab_pick_pixel),
4201 SPIN(b, -1, 256), OPNAME("B"), EVENT(MULTI, ab_pick_pixel),
4202 WSHOW
4203 };
4204 #undef WBbase
4205
script_ab()4206 static void script_ab()
4207 {
4208 static ab_dd tdata = { -1, -1 };
4209 ab_dd *dt;
4210 void **res;
4211
4212 res = run_create_(ab_code, &tdata, sizeof(tdata), script_cmds);
4213 run_query(res);
4214 dt = GET_DDATA(res);
4215 if ((dt->a >= 0) && (dt->a < mem_cols))
4216 mem_col_A24 = mem_pal[mem_col_A = dt->a];
4217 if ((dt->b >= 0) && (dt->b < mem_cols))
4218 mem_col_B24 = mem_pal[mem_col_B = dt->b];
4219 run_destroy(res);
4220 update_stuff(UPD_AB);
4221 }
4222
4223 typedef struct {
4224 int x[3], y[3], n[3], b[3], brush;
4225 void **nspin;
4226 } nxy_dd;
4227
bp_evt(nxy_dd * dt,void ** wdata,int what,void ** where)4228 static void bp_evt(nxy_dd *dt, void **wdata, int what, void **where)
4229 {
4230 void *var = cmd_read(where, dt);
4231 int w = dt->x[2] + 1;
4232
4233 if (var == dt->n)
4234 {
4235 int n = dt->n[0];
4236 dt->x[0] = n % w;
4237 dt->y[0] = n / w;
4238 if (dt->brush) mem_set_brush(n);
4239 else mem_tool_pat = n;
4240 }
4241 else if (var == dt->b) mem_tool_pat_B = dt->b[0];
4242 else if ((what == op_EVT_SCRIPT) && (dt->x[0] >= 0) && (dt->y[0] >= 0))
4243 cmd_set(dt->nspin, dt->y[0] * w + dt->x[0]);
4244 }
4245
bp_pick(nxy_dd * dt,void ** wdata,int what,void ** where,multi_ext * mx)4246 static int bp_pick(nxy_dd *dt, void **wdata, int what, void **where,
4247 multi_ext *mx)
4248 {
4249 /* Sanity check */
4250 if (mx->fractcol >= 0) return (0); // Error
4251
4252 /* Grid position */
4253 if ((mx->nrows == 1) && (mx->ncols == 2))
4254 {
4255 int *row = mx->rows[0] + 1, x = row[0], y = row[1];
4256 cmd_set(where, bounded(y, 0, dt->y[2]) * (dt->x[2] + 1) +
4257 bounded(x, 0, dt->x[2]));
4258 return (1);
4259 }
4260
4261 return (0); // Error
4262 }
4263
4264 static char *brush_txt[TOOL_SPRAY + 1] = { "Square", "Circle",
4265 "Horizontal", "Vertical", "Slash", "Backslash", "Spray" };
4266
4267 #define WBbase nxy_dd
4268 static void *bp_code[] = {
4269 TOPVBOX,
4270 REF(nspin), SPINa(n), EVENT(CHANGE, bp_evt), EVENT(MULTI, bp_pick),
4271 UNLESSx(brush, 1), ALTNAME("A"), IFvx(pattern_B, 1), /* && */
4272 SPINa(b), EVENT(CHANGE, bp_evt), EVENT(MULTI, bp_pick), OPNAME("B"),
4273 ENDIF(1),
4274 SPINa(x), EVENT(SCRIPT, bp_evt), OPNAME("X"),
4275 SPINa(y), EVENT(SCRIPT, bp_evt), OPNAME("Y"),
4276 IFx(brush, 1),
4277 RPACKv(brush_txt, TOOL_SPRAY + 1, 1, brush_type),
4278 EVENT(SELECT, bp_evt), OPNAME("Type"),
4279 SPINv(tool_size, 1, 255), EVENT(CHANGE, bp_evt), OPNAME("Size"),
4280 SPINv(tool_flow, 1, 255), EVENT(CHANGE, bp_evt), OPNAME("Flow"),
4281 ENDIF(1),
4282 WSHOW
4283 };
4284 #undef WBbase
4285
script_bp(int mode)4286 static void script_bp(int mode)
4287 {
4288 nxy_dd tdata;
4289
4290 memset(&tdata, 0, sizeof(tdata));
4291 tdata.x[1] = tdata.y[1] = -1;
4292 if (mode == CHOOSE_BRUSH)
4293 {
4294 tdata.x[2] = BRUSH_GRID_W - 1;
4295 tdata.y[2] = BRUSH_GRID_H - 1;
4296 tdata.x[0] = tdata.y[0] = -1;
4297 tdata.brush = TRUE;
4298 }
4299 else
4300 {
4301 tdata.x[2] = patterns_grid_w - 1;
4302 tdata.y[2] = patterns_grid_h - 1;
4303 tdata.n[0] = mem_tool_pat;
4304 tdata.b[0] = mem_tool_pat_B;
4305 tdata.x[0] = mem_tool_pat % patterns_grid_w;
4306 tdata.y[0] = mem_tool_pat / patterns_grid_w;
4307 tdata.n[1] = tdata.b[1] = -DEF_PATTERNS;
4308 }
4309 tdata.n[2] = tdata.b[2] = (tdata.x[2] + 1) * (tdata.y[2] + 1) - 1;
4310 run_destroy(run_create_(bp_code, &tdata, sizeof(tdata), script_cmds));
4311 if (mode == CHOOSE_BRUSH)
4312 {
4313 change_to_tool(TTB_PAINT);
4314 update_stuff(UPD_BRUSH);
4315 }
4316 else update_stuff(UPD_PAT);
4317 }
4318
4319 typedef struct {
4320 int w, h, rxy[4];
4321 multi_ext *mx;
4322 void **group;
4323 } rect_dd;
4324
make_select(rect_dd * dt,void ** wdata,int what,void ** where,multi_ext * mx)4325 static int make_select(rect_dd *dt, void **wdata, int what, void **where,
4326 multi_ext *mx)
4327 {
4328 /* Drop previous polygon if any */
4329 free(dt->mx);
4330 dt->mx = NULL;
4331
4332 /* Sanity check */
4333 if ((mx->mincols < 2) || (mx->fractcol >= 0)) return (0);
4334 if ((mx->ncols != 2) && ((mx->nrows != 1) || (mx->ncols != 4)))
4335 return (0);
4336
4337 /* Point/rectangle selection */
4338 if (mx->nrows <= 2)
4339 {
4340 int *row = mx->rows[0] + 1;
4341 dt->rxy[0] = row[0];
4342 dt->rxy[1] = row[1];
4343 if (mx->nrows > 1) row = mx->rows[1] + 1;
4344 dt->rxy[2] = row[0];
4345 dt->rxy[3] = row[1];
4346 if (mx->ncols > 2)
4347 {
4348 dt->rxy[2] += row[2] - 1;
4349 dt->rxy[3] += row[3] - 1;
4350 }
4351 cmd_reset(dt->group, dt); // Update widgets
4352 return (1);
4353 }
4354
4355 /* Polygon selection */
4356 dt->mx = mx;
4357 return (-1); // Keep the data
4358 }
4359
4360 #define WBbase rect_dd
4361 static void *rect_code[] = {
4362 TOPVBOX, EVENT(MULTI, make_select), OPNAME(""),
4363 REF(group), GROUPR,
4364 SPIN(rxy[0], 0, MAX_WIDTH - 1), OPNAME("x0"),
4365 SPIN(rxy[1], 0, MAX_HEIGHT - 1), OPNAME("y0"),
4366 SPIN(rxy[2], 0, MAX_WIDTH - 1), OPNAME("x1"),
4367 SPIN(rxy[3], 0, MAX_HEIGHT - 1), OPNAME("y1"),
4368 SPIN(w, 0, MAX_WIDTH), OPNAME("width"),
4369 SPIN(h, 0, MAX_HEIGHT), OPNAME("height"),
4370 WSHOW
4371 };
4372 #undef WBbase
4373
script_rect(int * rect)4374 static multi_ext *script_rect(int *rect)
4375 {
4376 static rect_dd tdata = { 0, 0, { 0, 0, MAX_WIDTH - 1, MAX_HEIGHT - 1 } };
4377 rect_dd *dt;
4378 multi_ext *mx;
4379 void **res;
4380
4381 copy4(rect, tdata.rxy);
4382 if (!script_cmds) return (NULL);
4383
4384 res = run_create_(rect_code, &tdata, sizeof(tdata), script_cmds);
4385 run_query(res);
4386 dt = GET_DDATA(res);
4387 mx = dt->mx;
4388 if (dt->w) dt->rxy[2] = dt->rxy[0] + dt->w - 1;
4389 if (dt->h) dt->rxy[3] = dt->rxy[1] + dt->h - 1;
4390 copy4(rect, dt->rxy);
4391 run_destroy(res);
4392 return (mx);
4393 }
4394
4395 typedef struct {
4396 int swap, mx, dx, dy, align, x, y;
4397 } paste_dd;
4398
4399 static char *paste_align[] = { "Left", "Right", "Top", "Bottom", "Centre" };
4400
align_evt(paste_dd * dt,void ** wdata,int what,void ** where)4401 static void align_evt(paste_dd *dt, void **wdata, int what, void **where)
4402 {
4403 cmd_read(where, dt);
4404 switch (dt->align)
4405 {
4406 case 0: // Left
4407 dt->dx = 0; break;
4408 case 1: // Right
4409 dt->dx = mem_clip_w - 1; break;
4410 case 2: // Top
4411 dt->dy = 0; break;
4412 case 3: // Bottom
4413 dt->dy = mem_clip_h - 1; break;
4414 case 4: // Center
4415 dt->dx = mem_clip_w / 2; dt->dy = mem_clip_h / 2; break;
4416 }
4417 }
4418
do_paste(paste_dd * dt,void ** wdata,int what,void ** where,multi_ext * mx)4419 static int do_paste(paste_dd *dt, void **wdata, int what, void **where,
4420 multi_ext *mx)
4421 {
4422 int i, fr, mode, rows, dx = dt->dx, dy = dt->dy, p = MAX_PRESSURE;
4423
4424 /* Sanity check */
4425 if ((mx->ncols > 3) || (mx->mincols < 2) ||
4426 ((mx->fractcol >= 0) && (mx->fractcol != 2))) return (0); // Error
4427
4428 do_tool_action(TC_PASTE_DRAG, marq_x1, marq_y1, 0);
4429
4430 mode = dt->swap ? TC_PASTE_PSWAP | TCF_PRES | TCF_ONCE :
4431 TC_PASTE_PAINT | TCF_PRES | TCF_ONCE;
4432 fr = mx->fractcol == 2;
4433 rows = mx->nrows;
4434 for (i = 0; i < rows; i++)
4435 {
4436 int *row = mx->rows[i];
4437 if (row[0] > 2) p = bounded(fr ? row[3] :
4438 (row[3] * MAX_PRESSURE) / 100, 0, MAX_PRESSURE);
4439 do_tool_action(mode, row[1] - dx, row[2] - dy, p);
4440 }
4441
4442 do_tool_action(TC_SEL_STOP, marq_x1, marq_y1, 0);
4443 tool_done();
4444 dt->mx = TRUE;
4445 return (1);
4446 }
4447
4448 #define WBbase paste_dd
4449 static void *paste_code[] = {
4450 TOPVBOX,
4451 OPT(paste_align, 5, align), EVENT(SCRIPT, align_evt),
4452 ALTNAME("Align"), FLATTEN, EVENT(MULTI, do_paste),
4453 CHECK("swap", swap), EVENT(CHANGE, align_evt),
4454 SPIN(x, -MAX_WIDTH, MAX_WIDTH), OPNAME("x0"),
4455 SPIN(y, -MAX_HEIGHT, MAX_HEIGHT), OPNAME("y0"),
4456 WSHOW
4457 };
4458 #undef WBbase
4459
script_paste(int centre)4460 void script_paste(int centre)
4461 {
4462 paste_dd tdata, *dt;
4463 void **res;
4464
4465 memset(&tdata, 0, sizeof(tdata));
4466 tdata.x = marq_x1;
4467 tdata.y = marq_y1;
4468 if (centre)
4469 {
4470 tdata.x += tdata.dx = mem_clip_w / 2;
4471 tdata.y += tdata.dy = mem_clip_h / 2;
4472 tdata.align = 4;
4473 }
4474 res = run_create_(paste_code, &tdata, sizeof(tdata), script_cmds);
4475 run_query(res);
4476 dt = GET_DDATA(res);
4477 if (!dt->mx) // No commits yet - paste at x & y
4478 {
4479 int x = dt->x - dt->dx, y = dt->y - dt->dy;
4480 if ((marq_x1 ^ x) | (marq_y1 ^ y)) // Moved
4481 paint_marquee(MARQ_MOVE, x, y, NULL);
4482 commit_paste(dt->swap, NULL);
4483 tool_done();
4484 }
4485 run_destroy(res);
4486 update_stuff(UPD_SGEOM);
4487 }
4488
4489 typedef struct {
4490 int n[3];
4491 } inrange_dd;
4492
4493 #define WBbase inrange_dd
4494 static void *inrange_code[] = {
4495 TOPVBOX, SPINa(n), WSHOW
4496 };
4497 #undef WBbase
4498
script_inrange(int n0,int nmin,int nmax)4499 static int script_inrange(int n0, int nmin, int nmax)
4500 {
4501 if (script_cmds)
4502 {
4503 inrange_dd tdata = { { n0, nmin, nmax } };
4504 void **res = run_create_(inrange_code, &tdata, sizeof(tdata), script_cmds);
4505 run_query(res);
4506 n0 = ((inrange_dd *)GET_DDATA(res))->n[0];
4507 run_destroy(res);
4508 }
4509 return (n0);
4510 }
4511
do_act_esc()4512 static void do_act_esc()
4513 {
4514 if ((tool_type == TOOL_SELECT) || (tool_type == TOOL_POLYGON))
4515 pressed_select(FALSE);
4516 else if (tool_type == TOOL_LINE)
4517 {
4518 stop_line();
4519 update_sel_bar(FALSE);
4520 }
4521 else if ((tool_type == TOOL_GRADIENT) &&
4522 (gradient[mem_channel].status != GRAD_NONE))
4523 {
4524 do_grad_action(TC_GRAD_CLEAR, 0, 0);
4525 update_sel_bar(FALSE);
4526 }
4527 }
4528
select_poly(multi_ext * mx)4529 static void select_poly(multi_ext *mx)
4530 {
4531 int i, rows = mx->nrows;
4532
4533 for (i = 0; i < rows; i++)
4534 {
4535 int *row = mx->rows[i] + 1;
4536 do_tool_action(i ? TC_POLY_ADD | TCF_ONCE : TC_POLY_START,
4537 bounded(row[0], 0, mem_width - 1),
4538 bounded(row[1], 0, mem_height - 1), 0);
4539 }
4540 do_tool_action(TC_POLY_CLOSE, 0, 0, 0);
4541 }
4542
tool_command(main_dd * dt,void ** wdata,int what,void ** where,multi_ext * mx)4543 static int tool_command(main_dd *dt, void **wdata, int what, void **where,
4544 multi_ext *mx)
4545 {
4546 int *row;
4547 int i, p, fr, rows = mx->nrows, res = 0;
4548
4549 do_act_esc();
4550 switch (tool_type)
4551 {
4552 case TOOL_GRADIENT:
4553 if ((rows == 1) && (mx->ncols < 3)) res = 1; // Valid clear op
4554 if ((rows != 2) || (mx->ncols != 2) || (mx->mincols != 2) ||
4555 (mx->fractcol >= 0)) break; // Error
4556 row = mx->rows[0] + 1;
4557 do_grad_action(TC_GRAD_START,
4558 bounded(row[0], -MAX_WIDTH, MAX_WIDTH),
4559 bounded(row[1], -MAX_HEIGHT, MAX_HEIGHT));
4560 row = mx->rows[1] + 1;
4561 do_grad_action(TC_GRAD_SET1,
4562 bounded(row[0], -MAX_WIDTH, MAX_WIDTH),
4563 bounded(row[1], -MAX_HEIGHT, MAX_HEIGHT));
4564 res = 1;
4565 break;
4566 case TOOL_LINE:
4567 if ((rows < 2) || (mx->ncols != 2) || (mx->mincols != 2) ||
4568 (mx->fractcol >= 0)) break; // Error
4569 for (i = 0; i < rows; i++)
4570 {
4571 row = mx->rows[i] + 1;
4572 do_tool_action(TC_LINE_START,
4573 bounded(row[0], 0, mem_width - 1),
4574 bounded(row[1], 0, mem_height - 1), MAX_PRESSURE);
4575 line_status = LINE_LINE;
4576 }
4577 stop_line();
4578 res = 1;
4579 break;
4580 case TOOL_SELECT:
4581 if ((rows > 2) || (mx->ncols != 2) || (mx->mincols != 2) ||
4582 (mx->fractcol >= 0)) break; // Error
4583 row = mx->rows[0] + 1;
4584 do_tool_action(TC_SEL_START,
4585 bounded(row[0], 0, mem_width - 1),
4586 bounded(row[1], 0, mem_height - 1), 0);
4587 if (rows > 1) row = mx->rows[1] + 1;
4588 do_tool_action(TC_SEL_TO,
4589 bounded(row[0], 0, mem_width - 1),
4590 bounded(row[1], 0, mem_height - 1), 0);
4591 do_tool_action(TC_SEL_STOP, 0, 0, 0);
4592 res = 1;
4593 break;
4594 case TOOL_POLYGON:
4595 if ((mx->ncols != 2) || (mx->mincols != 2) ||
4596 (mx->fractcol >= 0)) break; // Error
4597 select_poly(mx);
4598 res = 1;
4599 break;
4600 default: /* Regular painting tools */
4601 if ((mx->ncols > 3) || (mx->mincols < 2) ||
4602 ((mx->fractcol >= 0) && (mx->fractcol != 2))) break; // Error
4603 fr = mx->fractcol == 2;
4604 p = MAX_PRESSURE;
4605 for (i = 0; i < rows; i++)
4606 {
4607 row = mx->rows[i];
4608 if (row[0] > 2) p = bounded(fr ? row[3] :
4609 (row[3] * MAX_PRESSURE) / 100, 0, MAX_PRESSURE);
4610 do_tool_action(TC_PAINT | TCF_PRES | TCF_ONCE,
4611 bounded(row[1], 0, mem_width - 1),
4612 bounded(row[2], 0, mem_height - 1), p);
4613 }
4614 res = 1;
4615 break;
4616 }
4617
4618 if (res) /* Do as for button release */
4619 {
4620 tool_done();
4621 update_menus();
4622 }
4623 return (res);
4624 }
4625
4626 void *scriptbar_code[] = {
4627 EVENT(MULTI, tool_command), ALTNAME(""), // for tools toolbar
4628 RET
4629 };
4630
new_zoom(int mode,float zoom)4631 static float new_zoom(int mode, float zoom)
4632 {
4633 return (!mode ? // Zoom in
4634 (zoom >= 1 ? zoom + 1 : 1.0 / (rint(1.0 / zoom) - 1)) :
4635 mode == -1 ? // Zoom out
4636 (zoom > 1 ? zoom - 1 : 1.0 / (rint(1.0 / zoom) + 1)) :
4637 mode > 0 ? mode : -1.0 / mode); // Zoom to given factor/divisor
4638 }
4639
action_dispatch(int action,int mode,int state,int kbd)4640 void action_dispatch(int action, int mode, int state, int kbd)
4641 {
4642 int change = mode & 1 ? mem_nudge : 1, dir = (mode >> 1) - 1;
4643
4644 switch (action)
4645 {
4646 case ACT_QUIT:
4647 quit_all(mode); break;
4648 case ACT_ZOOM:
4649 align_size(new_zoom(mode, can_zoom));
4650 break;
4651 case ACT_VIEW:
4652 toggle_view(); break;
4653 case ACT_PAN:
4654 pressed_pan(); break;
4655 case ACT_CROP:
4656 pressed_crop(); break;
4657 case ACT_SWAP_AB:
4658 mem_swap_cols(TRUE); break;
4659 case ACT_TOOL:
4660 if (state || kbd) change_to_tool(mode); // Ignore DEactivating buttons
4661 break;
4662 case ACT_SEL_MOVE:
4663 /* Gradient tool has precedence over selection */
4664 if ((tool_type != TOOL_GRADIENT) && (marq_status > MARQUEE_NONE))
4665 move_marquee(MARQ_MOVE, marq_xy, change, dir);
4666 else move_mouse(change * arrow_dx[dir], change * arrow_dy[dir], 0);
4667 break;
4668 case ACT_OPAC:
4669 pressed_opacity(mode > 0 ? (255 * mode) / 10 :
4670 tool_opacity + 1 + mode + mode);
4671 break;
4672 case ACT_LR_MOVE:
4673 /* User is selecting so allow CTRL+arrow keys to resize the
4674 * marquee; for consistency, gradient tool blocks this */
4675 if ((tool_type != TOOL_GRADIENT) && (marq_status == MARQUEE_DONE))
4676 move_marquee(MARQ_SIZE, marq_xy + 2, change, dir);
4677 else if (bkg_flag && !layer_selected)
4678 {
4679 /* !!! Later, maybe localize redraw to the changed part */
4680 bkg_x += change * arrow_dx[dir];
4681 bkg_y += change * arrow_dy[dir];
4682 update_stuff(UPD_RENDER);
4683 }
4684 else if (layers_total) move_layer_relative(layer_selected,
4685 change * arrow_dx[dir], change * arrow_dy[dir]);
4686 break;
4687 case ACT_ESC: do_act_esc(); break;
4688 case ACT_COMMIT:
4689 if (marq_status >= MARQUEE_PASTE)
4690 {
4691 commit_paste(mode, NULL);
4692 pen_down = 0; // Ensure each press of enter is a new undo level
4693 mem_undo_prepare();
4694 }
4695 else move_mouse(0, 0, 1);
4696 break;
4697 case ACT_RCLICK:
4698 if (marq_status < MARQUEE_PASTE) move_mouse(0, 0, 3);
4699 break;
4700 case ACT_ARROW: draw_arrow(mode); break;
4701 case ACT_A:
4702 case ACT_B:
4703 action = action == ACT_B;
4704 dir = script_idx(action);
4705 if (!mode && (dir < 0)); // Nothing to do
4706 else if (mem_channel == CHN_IMAGE)
4707 {
4708 mode = mode ? mode + mem_col_[action] : dir;
4709 if ((mode >= 0) && (mode < mem_cols))
4710 mem_col_[action] = mode;
4711 mem_col_24[action] = mem_pal[mem_col_[action]];
4712 }
4713 else
4714 {
4715 mode = mode ? mode + channel_col_[action][mem_channel] : dir;
4716 if ((mode >= 0) && (mode <= 255))
4717 channel_col_[action][mem_channel] = mode;
4718 }
4719 update_stuff(UPD_CAB);
4720 break;
4721 case ACT_SIZE:
4722 tool_size = bounded(tool_size + mode, 1, 255);
4723 update_stuff(UPD_BRUSH);
4724 break;
4725 case ACT_PAT:
4726 mem_tool_pat = bounded(mem_tool_pat + mode, -DEF_PATTERNS,
4727 patterns_grid_w * patterns_grid_h - 1);
4728 update_stuff(UPD_PAT);
4729 break;
4730 case ACT_CHANNEL:
4731 if (kbd) state = TRUE;
4732 if (mode < 0) pressed_channel_create(mode);
4733 else pressed_channel_edit(state, mode);
4734 break;
4735 case ACT_VWZOOM:
4736 vw_align_size(new_zoom(mode, vw_zoom));
4737 break;
4738 case ACT_SAVE:
4739 pressed_save_file(); break;
4740 case ACT_FACTION:
4741 pressed_file_action(mode); break;
4742 case ACT_LOAD_RECENT:
4743 pressed_load_recent(mode); break;
4744 case ACT_DO_UNDO:
4745 pressed_do_undo(mode, script_inrange(1, 1,
4746 mode ? mem_undo_redo : mem_undo_done));
4747 break;
4748 case ACT_COPY:
4749 pressed_copy(mode); break;
4750 case ACT_PASTE:
4751 pressed_paste(mode);
4752 if (script_cmds && (marq_status >= MARQUEE_PASTE))
4753 script_paste(mode); // Move and commit
4754 break;
4755 case ACT_COPY_PAL:
4756 pressed_pal_copy(); break;
4757 case ACT_PASTE_PAL:
4758 pressed_pal_paste(); break;
4759 case ACT_LOAD_CLIP:
4760 load_clip(mode); break;
4761 case ACT_SAVE_CLIP:
4762 save_clip(mode); break;
4763 case ACT_TBAR:
4764 pressed_toolbar_toggle(state, mode); break;
4765 case ACT_DOCK:
4766 if (!kbd) toggle_dock(show_dock = state);
4767 else if (cmd_checkv(menu_slots[MENU_DOCK], SLOT_SENSITIVE))
4768 cmd_set(menu_slots[MENU_DOCK], !show_dock);
4769 break;
4770 case ACT_CENTER:
4771 pressed_centralize(state); break;
4772 case ACT_GRID:
4773 zoom_grid(state); break;
4774 case ACT_SNAP:
4775 tgrid_snap = state;
4776 break;
4777 case ACT_VWWIN:
4778 if (state) view_show();
4779 else view_hide();
4780 break;
4781 case ACT_VWSPLIT:
4782 pressed_view_hori(state); break;
4783 case ACT_VWFOCUS:
4784 pressed_view_focus(state); break;
4785 case ACT_FLIP_V:
4786 pressed_flip_image_v(); break;
4787 case ACT_FLIP_H:
4788 pressed_flip_image_h(); break;
4789 case ACT_ROTATE:
4790 pressed_rotate_image(mode); break;
4791 case ACT_SELECT:
4792 pressed_select(mode); break;
4793 case ACT_LASSO:
4794 pressed_lasso(mode); break;
4795 case ACT_OUTLINE:
4796 pressed_rectangle(mode); break;
4797 case ACT_ELLIPSE:
4798 pressed_ellipse(mode); break;
4799 case ACT_SEL_FLIP_V:
4800 pressed_flip_sel_v(); break;
4801 case ACT_SEL_FLIP_H:
4802 pressed_flip_sel_h(); break;
4803 case ACT_SEL_ROT:
4804 pressed_rotate_sel(mode); break;
4805 case ACT_RAMP:
4806 pressed_sel_ramp(mode); break;
4807 case ACT_SEL_ALPHA_AB:
4808 pressed_clip_alpha_scale(); break;
4809 case ACT_SEL_ALPHAMASK:
4810 pressed_clip_alphamask(); break;
4811 case ACT_SEL_MASK_AB:
4812 pressed_clip_mask(mode); break;
4813 case ACT_SEL_MASK:
4814 if (!mode) pressed_clip_mask_all();
4815 else pressed_clip_mask_clear();
4816 break;
4817 case ACT_PAL_DEF:
4818 pressed_default_pal(); break;
4819 case ACT_PAL_MASK:
4820 mem_mask_setv(NULL, script_idx(mode + 0x100), mode);
4821 update_stuff(UPD_CMASK);
4822 break;
4823 case ACT_DITHER_A:
4824 pressed_dither_A(); break;
4825 case ACT_PAL_MERGE:
4826 pressed_remove_duplicates(); break;
4827 case ACT_PAL_CLEAN:
4828 pressed_remove_unused(); break;
4829 case ACT_ISOMETRY:
4830 iso_trans(mode); break;
4831 case ACT_CHN_DIS:
4832 pressed_channel_disable(state, mode); break;
4833 case ACT_SET_RGBA:
4834 pressed_RGBA_toggle(state); break;
4835 case ACT_SET_OVERLAY:
4836 pressed_channel_toggle(state, mode); break;
4837 case ACT_LR_SAVE:
4838 layer_press_save(); break;
4839 case ACT_LR_ADD:
4840 if (mode == LR_NEW) generic_new_window(1);
4841 else if (mode == LR_DUP) layer_press_duplicate();
4842 else if (mode == LR_PASTE) pressed_paste_layer();
4843 else /* if (mode == LR_COMP) */ layer_add_composite();
4844 break;
4845 case ACT_LR_DEL:
4846 if (!mode) layer_press_delete();
4847 else layer_press_remove_all();
4848 break;
4849 case ACT_DOCS:
4850 show_html(inifile_get(HANDBOOK_BROWSER_INI, NULL),
4851 inifile_get(HANDBOOK_LOCATION_INI, NULL));
4852 break;
4853 case ACT_REBIND_KEYS:
4854 /* "Tool of last resort" for when shortcuts don't work */
4855 cmd_reset(main_keys, NULL); break;
4856 case ACT_MODE:
4857 mode_change(mode, state); break;
4858 case ACT_LR_SHIFT:
4859 shift_layer(mode); break;
4860 case ACT_LR_CENTER:
4861 layer_press_centre(); break;
4862 case ACT_SCRIPT:
4863 do_script(mode); break;
4864 case ACT_RUN_SCRIPT:
4865 if (!mode) load_script();
4866 else launch_script(mode);
4867 break;
4868 case DLG_BRCOSA:
4869 pressed_brcosa(NULL); break;
4870 case DLG_CHOOSER:
4871 if (!script_cmds) choose_pattern(mode);
4872 else if (mode == CHOOSE_COLOR) script_ab();
4873 else script_bp(mode);
4874 break;
4875 case DLG_SCALE:
4876 pressed_scale_size(TRUE); break;
4877 case DLG_SIZE:
4878 pressed_scale_size(FALSE); break;
4879 case DLG_NEW:
4880 generic_new_window(0); break;
4881 case DLG_FSEL:
4882 file_selector(mode); break;
4883 case DLG_FACTIONS:
4884 pressed_file_configure(); break;
4885 case DLG_TEXT:
4886 pressed_text(); break;
4887 case DLG_TEXT_FT:
4888 pressed_mt_text(); break;
4889 case DLG_LAYERS:
4890 if (mode) cmd_set(menu_slots[MENU_LAYER], FALSE); // Closed by toolbar
4891 else if (state) pressed_layers();
4892 else delete_layers_window();
4893 break;
4894 case DLG_INDEXED:
4895 pressed_quantize(mode); break;
4896 case DLG_ROTATE:
4897 pressed_rotate_free(); break;
4898 case DLG_INFO:
4899 if (script_cmds) interpolate_info();
4900 else pressed_information();
4901 break;
4902 case DLG_PREFS:
4903 pressed_preferences(); break;
4904 case DLG_COLORS:
4905 colour_selector(mode); break;
4906 case DLG_PAL_SIZE:
4907 pressed_add_cols(); break;
4908 case DLG_PAL_SORT:
4909 pressed_sort_pal(); break;
4910 case DLG_PAL_SHIFTER:
4911 pressed_shifter(); break;
4912 case DLG_CHN_DEL:
4913 pressed_channel_delete(); break;
4914 case DLG_ANI:
4915 pressed_animate_window(); break;
4916 case DLG_ANI_VIEW:
4917 ani_but_preview(NULL); break;
4918 case DLG_ANI_KEY:
4919 pressed_set_key_frame(); break;
4920 case DLG_ANI_KILLKEY:
4921 pressed_remove_key_frames(); break;
4922 case DLG_ABOUT:
4923 pressed_help(); break;
4924 case DLG_SKEW:
4925 pressed_skew(); break;
4926 case DLG_FLOOD:
4927 flood_settings(); break;
4928 case DLG_SMUDGE:
4929 smudge_settings(); break;
4930 case DLG_CLONE:
4931 clone_settings(); break;
4932 case DLG_GRAD:
4933 gradient_setup(mode); break;
4934 case DLG_STEP:
4935 step_settings(); break;
4936 case DLG_FILT:
4937 blend_settings(); break;
4938 case DLG_TRACE:
4939 bkg_setup(); break;
4940 case DLG_PICK_GRAD:
4941 pressed_pick_gradient(); break;
4942 case DLG_SEGMENT:
4943 pressed_segment(); break;
4944 case DLG_SCRIPT:
4945 pressed_script(); break;
4946 case DLG_LASSO:
4947 lasso_settings(); break;
4948 case DLG_KEYS:
4949 keys_selector(); break;
4950 case DLG_XHOLD:
4951 pressed_xhold(); break;
4952 case DLG_NOISE:
4953 pressed_noise(); break;
4954 case FILT_2RGB:
4955 if (mem_img_bpp == 1) pressed_convert_rgb();
4956 // Allow a noop in script mode
4957 else if (script_cmds) spot_undo(UNDO_PAL);
4958 break;
4959 case FILT_INVERT:
4960 case FILT_NORM:
4961 pressed_inv(action); break;
4962 case FILT_GREY:
4963 pressed_greyscale(mode); break;
4964 case FILT_EDGE:
4965 pressed_edge_detect(); break;
4966 case FILT_DOG:
4967 pressed_dog(); break;
4968 case FILT_SHARPEN:
4969 pressed_sharpen(); break;
4970 case FILT_UNSHARP:
4971 pressed_unsharp(); break;
4972 case FILT_SOFTEN:
4973 pressed_soften(); break;
4974 case FILT_GAUSS:
4975 pressed_gauss(); break;
4976 case FILT_FX:
4977 pressed_fx(mode); break;
4978 case FILT_BACT:
4979 pressed_bacteria(); break;
4980 case FILT_MAP:
4981 pressed_map(); break;
4982 case FILT_THRES:
4983 pressed_threshold(); break;
4984 case FILT_UALPHA:
4985 pressed_unassociate(); break;
4986 case FILT_KUWAHARA:
4987 pressed_kuwahara(); break;
4988 }
4989 }
4990
menu_action(void * dt,void ** wdata,int what,void ** where)4991 static void menu_action(void *dt, void **wdata, int what, void **where)
4992 {
4993 int act_m = TOOL_ID(where), res = TRUE;
4994 void *cause = cmd_read(where, dt);
4995 // Good for radioitems too, as all action codes are nonzero
4996 if (cause) res = *(int *)cause;
4997
4998 action_dispatch(act_m >> 16, (act_m & 0xFFFF) - 0x8000, res, FALSE);
4999 }
5000
do_pal_copy(png_color * tpal,unsigned char * img,unsigned char * alpha,unsigned char * mask,unsigned char * mask2,png_color * wpal,int w,int h,int bpp,int step)5001 static int do_pal_copy(png_color *tpal, unsigned char *img,
5002 unsigned char *alpha, unsigned char *mask,
5003 unsigned char *mask2, png_color *wpal,
5004 int w, int h, int bpp, int step)
5005 {
5006 int i, j, n;
5007
5008 mem_pal_copy(tpal, mem_pal);
5009 for (n = i = 0; i < h; i++)
5010 {
5011 for (j = 0; j < w; j++ , img += bpp)
5012 {
5013 /* Skip empty parts */
5014 if ((mask2 && !mask2[j]) || (mask && !mask[j]) ||
5015 (alpha && !alpha[j])) continue;
5016 if (bpp == 1) tpal[n] = wpal[*img];
5017 else
5018 {
5019 tpal[n].red = img[0];
5020 tpal[n].green = img[1];
5021 tpal[n].blue = img[2];
5022 }
5023 if (++n >= 256) return (256);
5024 }
5025 img += (step - w) * bpp;
5026 if (alpha) alpha += step;
5027 if (mask) mask += step;
5028 if (mask2) mask2 += w;
5029 }
5030 return (n);
5031 }
5032
pressed_pal_copy()5033 static void pressed_pal_copy()
5034 {
5035 png_color tpal[256];
5036 int n = 0;
5037
5038 /* Source is selection */
5039 if ((marq_status == MARQUEE_DONE) || (poly_status == POLY_DONE))
5040 {
5041 unsigned char *mask2 = NULL;
5042 int i, bpp, rect[4];
5043
5044 marquee_at(rect);
5045 if ((poly_status == POLY_DONE) &&
5046 (mask2 = calloc(1, rect[2] * rect[3])))
5047 poly_draw(TRUE, mask2, rect[2]);
5048
5049 i = rect[1] * mem_width + rect[0];
5050 bpp = MEM_BPP;
5051 n = do_pal_copy(tpal, mem_img[mem_channel] + i * bpp,
5052 (mem_channel == CHN_IMAGE) && mem_img[CHN_ALPHA] ?
5053 mem_img[CHN_ALPHA] + i : NULL,
5054 (mem_channel <= CHN_ALPHA) && mem_img[CHN_SEL] ?
5055 mem_img[CHN_SEL] + i : NULL,
5056 mask2, mem_pal,
5057 rect[2], rect[3], bpp, mem_width);
5058
5059 if (mask2) free(mask2);
5060 }
5061 /* Source is clipboard */
5062 else if (mem_clipboard) n = do_pal_copy(tpal, mem_clipboard,
5063 mem_clip_alpha, mem_clip_mask,
5064 NULL, mem_clip_paletted ? mem_clip_pal : mem_pal,
5065 mem_clip_w, mem_clip_h, mem_clip_bpp, mem_clip_w);
5066
5067 if (!n) return; // Empty set - ignore
5068 spot_undo(UNDO_PAL);
5069 mem_pal_copy(mem_pal, tpal);
5070 mem_cols = n;
5071 update_stuff(UPD_PAL);
5072 }
5073
pressed_pal_paste()5074 static void pressed_pal_paste()
5075 {
5076 unsigned char *dest;
5077 int i, h, w = mem_cols;
5078
5079 // If selection exists, use it to set the width of the clipboard
5080 if ((tool_type == TOOL_SELECT) && (marq_status == MARQUEE_DONE))
5081 {
5082 w = abs(marq_x1 - marq_x2) + 1;
5083 if (w > mem_cols) w = mem_cols;
5084 }
5085 h = (mem_cols + w - 1) / w;
5086
5087 mem_clip_new(w, h, 1, CMASK_IMAGE, NULL);
5088 if (!mem_clipboard)
5089 {
5090 memory_errors(1);
5091 return;
5092 }
5093 mem_clip_paletted = 1;
5094 mem_pal_copy(mem_clip_pal, mem_pal);
5095 memset(dest = mem_clipboard, 0, w * h);
5096 for (i = 0; i < mem_cols; i++) dest[i] = i;
5097
5098 pressed_paste(TRUE);
5099 }
5100
5101 /// DOCK AREA
5102
toggle_dock(int state)5103 static void toggle_dock(int state)
5104 {
5105 cmd_set(dock_area, state);
5106 }
5107
pressed_sel_ramp(int vert)5108 static void pressed_sel_ramp(int vert)
5109 {
5110 unsigned char *c0, *c1, *img, *dest, *buf, *mask;
5111 int i, j, k, a, b, v, w, h, ww, gcor, opacity, bpp = MEM_BPP, rect[4];
5112
5113 if (marq_status != MARQUEE_DONE) return;
5114
5115 marquee_at(rect);
5116 w = rect[2]; h = rect[3];
5117 k = (vert ? h : w) - 1;
5118 if (k < 2) return; // Nothing to do
5119
5120 ww = w * bpp;
5121 buf = multialloc(MA_ALIGN_DEFAULT, &buf, ww, &mask, w, NULL);
5122 if (!buf)
5123 {
5124 memory_errors(1);
5125 return;
5126 }
5127
5128 spot_undo(UNDO_FILT);
5129
5130 opacity = IS_INDEXED ? 0 : tool_opacity;
5131 gcor = paint_gamma && (bpp == 3);
5132 img = mem_img[mem_channel] + (rect[1] * mem_width + rect[0]) * bpp;
5133 c0 = img;
5134 c1 = img + (h - 1) * mem_width * bpp;
5135 v = 0x3F; /* Vertical ramp: channel step 1, ramp step 0 */
5136 vert = !!vert;
5137 /* Horizontal ramp: for 1bpp channel step 0, ramp step 1;
5138 * for 3 bpp channel step (1, 1, -2), ramp step (0, 0, 1) */
5139 if (!vert) ww -= bpp , v = bpp == 3 ? 0x0F : 0x2A;
5140 /* Render the ramp row by row */
5141 for (i = vert; i < h - vert; i++)
5142 {
5143 dest = img + i * mem_width * bpp;
5144 if (vert) j = 0 , b = i;
5145 else j = bpp , b = 1 , c0 = dest , c1 = dest + ww;
5146 for (a = 0; j < ww; j++)
5147 {
5148 buf[j] = gcor ? UNGAMMA256((gamma256[c0[a]] * (k - b) +
5149 gamma256[c1[a]] * b) / k) :
5150 (c0[a] * (k - b) + c1[a] * b) / k;
5151 a += (v & 3) - 2; // Step on channels
5152 b += 1 - (v & 1); // Step on ramp
5153 v = (v + ((v & 3) << 6)) >> 2; // Next step
5154 }
5155 row_protected(rect[0], rect[1] + i, w, mask);
5156 if (!vert) mask[0] = mask[k] = 255; // Leave source pixels alone
5157 process_mask(0, 1, w, mask, NULL, NULL, NULL, NULL, opacity, 0);
5158 process_img(0, 1, w, mask, dest, dest, buf, buf, bpp, 0);
5159 }
5160
5161 free(buf);
5162 mem_undo_prepare();
5163 update_stuff(UPD_IMG);
5164 }
5165
5166 static int menu_view, menu_layer;
5167 static int menu_chan = ACTMOD(ACT_CHANNEL, CHN_IMAGE); // initial state
5168
5169 static void *main_menu_code[] = {
5170 REFv(main_menubar), SMARTMENU(menu_action),
5171 SSUBMENU(_("/_File")),
5172 MENUTEAR, //
5173 MENUITEMis(_("//New"), ACTMOD(DLG_NEW, 0), XPM_ICON(new)),
5174 SHORTCUT(n, C),
5175 MENUITEMis(_("//Open ..."), ACTMOD(DLG_FSEL, FS_PNG_LOAD), XPM_ICON(open)),
5176 SHORTCUT(o, C),
5177 MENUITEMis(_("//Save"), ACTMOD(ACT_SAVE, 0), XPM_ICON(save)),
5178 SHORTCUT(s, C),
5179 MENUITEMs(_("//Save As ..."), ACTMOD(DLG_FSEL, FS_PNG_SAVE)),
5180 MENUSEP, //
5181 MENUITEMs(_("//Export Undo Images ..."), ACTMOD(DLG_FSEL, FS_EXPORT_UNDO)),
5182 ACTMAP(NEED_UNDO),
5183 MENUITEMs(_("//Export Undo Images (reversed) ..."), ACTMOD(DLG_FSEL, FS_EXPORT_UNDO2)),
5184 ACTMAP(NEED_UNDO),
5185 MENUITEMs(_("//Export ASCII Art ..."), ACTMOD(DLG_FSEL, FS_EXPORT_ASCII)),
5186 ACTMAP(NEED_IDX),
5187 MENUITEMs(_("//Export Animated GIF ..."), ACTMOD(DLG_FSEL, FS_EXPORT_GIF)),
5188 ACTMAP(NEED_IDX),
5189 MENUSEP, //
5190 SUBMENU(_("//Actions")),
5191 MENUTEAR, ///
5192 REFv(menu_slots[MENU_FACTION1]),
5193 MENUITEM("///1", ACTMOD(ACT_FACTION, 1)),
5194 REFv(menu_slots[MENU_FACTION2]),
5195 MENUITEM("///2", ACTMOD(ACT_FACTION, 2)),
5196 REFv(menu_slots[MENU_FACTION3]),
5197 MENUITEM("///3", ACTMOD(ACT_FACTION, 3)),
5198 REFv(menu_slots[MENU_FACTION4]),
5199 MENUITEM("///4", ACTMOD(ACT_FACTION, 4)),
5200 REFv(menu_slots[MENU_FACTION5]),
5201 MENUITEM("///5", ACTMOD(ACT_FACTION, 5)),
5202 REFv(menu_slots[MENU_FACTION6]),
5203 MENUITEM("///6", ACTMOD(ACT_FACTION, 6)),
5204 REFv(menu_slots[MENU_FACTION7]),
5205 MENUITEM("///7", ACTMOD(ACT_FACTION, 7)),
5206 REFv(menu_slots[MENU_FACTION8]),
5207 MENUITEM("///8", ACTMOD(ACT_FACTION, 8)),
5208 REFv(menu_slots[MENU_FACTION9]),
5209 MENUITEM("///9", ACTMOD(ACT_FACTION, 9)),
5210 REFv(menu_slots[MENU_FACTION10]),
5211 MENUITEM("///10", ACTMOD(ACT_FACTION, 10)),
5212 REFv(menu_slots[MENU_FACTION11]),
5213 MENUITEM("///11", ACTMOD(ACT_FACTION, 11)),
5214 REFv(menu_slots[MENU_FACTION12]),
5215 MENUITEM("///12", ACTMOD(ACT_FACTION, 12)),
5216 REFv(menu_slots[MENU_FACTION13]),
5217 MENUITEM("///13", ACTMOD(ACT_FACTION, 13)),
5218 REFv(menu_slots[MENU_FACTION14]),
5219 MENUITEM("///14", ACTMOD(ACT_FACTION, 14)),
5220 REFv(menu_slots[MENU_FACTION15]),
5221 MENUITEM("///15", ACTMOD(ACT_FACTION, 15)),
5222 REFv(menu_slots[MENU_FACTION_S]),
5223 MENUSEPr, ///
5224 MENUITEM(_("///Configure"), ACTMOD(DLG_FACTIONS, 0)),
5225 WDONE,
5226 REFv(menu_slots[MENU_RECENT_S]),
5227 MENUSEPr, //
5228 REFv(menu_slots[MENU_RECENT1]),
5229 MENUITEM("//1", ACTMOD(ACT_LOAD_RECENT, 1)),
5230 SHORTCUT(F1, CS),
5231 REFv(menu_slots[MENU_RECENT2]),
5232 MENUITEM("//2", ACTMOD(ACT_LOAD_RECENT, 2)),
5233 SHORTCUT(F2, CS),
5234 REFv(menu_slots[MENU_RECENT3]),
5235 MENUITEM("//3", ACTMOD(ACT_LOAD_RECENT, 3)),
5236 SHORTCUT(F3, CS),
5237 REFv(menu_slots[MENU_RECENT4]),
5238 MENUITEM("//4", ACTMOD(ACT_LOAD_RECENT, 4)),
5239 SHORTCUT(F4, CS),
5240 REFv(menu_slots[MENU_RECENT5]),
5241 MENUITEM("//5", ACTMOD(ACT_LOAD_RECENT, 5)),
5242 SHORTCUT(F5, CS),
5243 REFv(menu_slots[MENU_RECENT6]),
5244 MENUITEM("//6", ACTMOD(ACT_LOAD_RECENT, 6)),
5245 SHORTCUT(F6, CS),
5246 REFv(menu_slots[MENU_RECENT7]),
5247 MENUITEM("//7", ACTMOD(ACT_LOAD_RECENT, 7)),
5248 SHORTCUT(F7, CS),
5249 REFv(menu_slots[MENU_RECENT8]),
5250 MENUITEM("//8", ACTMOD(ACT_LOAD_RECENT, 8)),
5251 SHORTCUT(F8, CS),
5252 REFv(menu_slots[MENU_RECENT9]),
5253 MENUITEM("//9", ACTMOD(ACT_LOAD_RECENT, 9)),
5254 SHORTCUT(F9, CS),
5255 REFv(menu_slots[MENU_RECENT10]),
5256 MENUITEM("//10", ACTMOD(ACT_LOAD_RECENT, 10)),
5257 SHORTCUT(F10, CS),
5258 REFv(menu_slots[MENU_RECENT11]),
5259 MENUITEM("//11", ACTMOD(ACT_LOAD_RECENT, 11)),
5260 REFv(menu_slots[MENU_RECENT12]),
5261 MENUITEM("//12", ACTMOD(ACT_LOAD_RECENT, 12)),
5262 REFv(menu_slots[MENU_RECENT13]),
5263 MENUITEM("//13", ACTMOD(ACT_LOAD_RECENT, 13)),
5264 REFv(menu_slots[MENU_RECENT14]),
5265 MENUITEM("//14", ACTMOD(ACT_LOAD_RECENT, 14)),
5266 REFv(menu_slots[MENU_RECENT15]),
5267 MENUITEM("//15", ACTMOD(ACT_LOAD_RECENT, 15)),
5268 REFv(menu_slots[MENU_RECENT16]),
5269 MENUITEM("//16", ACTMOD(ACT_LOAD_RECENT, 16)),
5270 REFv(menu_slots[MENU_RECENT17]),
5271 MENUITEM("//17", ACTMOD(ACT_LOAD_RECENT, 17)),
5272 REFv(menu_slots[MENU_RECENT18]),
5273 MENUITEM("//18", ACTMOD(ACT_LOAD_RECENT, 18)),
5274 REFv(menu_slots[MENU_RECENT19]),
5275 MENUITEM("//19", ACTMOD(ACT_LOAD_RECENT, 19)),
5276 REFv(menu_slots[MENU_RECENT20]),
5277 MENUITEM("//20", ACTMOD(ACT_LOAD_RECENT, 20)),
5278 MENUSEP, //
5279 MENUITEMs(_("//Quit"), ACTMOD(ACT_QUIT, 1)),
5280 SHORTCUT(q, C),
5281 WDONE,
5282 SSUBMENU(_("/_Edit")),
5283 MENUTEAR, //
5284 MENUITEMis(_("//Undo"), ACTMOD(ACT_DO_UNDO, 0), XPM_ICON(undo)),
5285 ACTMAP(NEED_UNDO), SHORTCUT(z, C),
5286 MENUITEMis(_("//Redo"), ACTMOD(ACT_DO_UNDO, 1), XPM_ICON(redo)),
5287 ACTMAP(NEED_REDO), SHORTCUT(r, C),
5288 MENUSEP, //
5289 MENUITEMis(_("//Cut"), ACTMOD(ACT_COPY, 1), XPM_ICON(cut)),
5290 ACTMAP(NEED_SEL2), SHORTCUT(x, C),
5291 MENUITEMis(_("//Copy"), ACTMOD(ACT_COPY, 0), XPM_ICON(copy)),
5292 ACTMAP(NEED_SEL2), SHORTCUT(c, C),
5293 MENUITEMs(_("//Copy To Palette"), ACTMOD(ACT_COPY_PAL, 0)),
5294 ACTMAP(NEED_PSEL),
5295 MENUITEMis(_("//Paste To Centre"), ACTMOD(ACT_PASTE, 1), XPM_ICON(paste)),
5296 ACTMAP(NEED_CLIP), SHORTCUT(v, C),
5297 MENUITEMs(_("//Paste To New Layer"), ACTMOD(ACT_LR_ADD, LR_PASTE)),
5298 ACTMAP(NEED_PCLIP), SHORTCUT(v, CS),
5299 MENUITEMs(_("//Paste"), ACTMOD(ACT_PASTE, 0)),
5300 ACTMAP(NEED_CLIP), SHORTCUT(k, C),
5301 MENUITEMis(_("//Paste Text"), ACTMOD(DLG_TEXT, 0), XPM_ICON(text)),
5302 SHORTCUT(t, S),
5303 IFvx(cmd_mode, 1), // Disable GUI-only text renderer
5304 UNLESSv(texteng_con), ACTMAP(NEED_SKIP),
5305 ENDIF(1),
5306 #ifdef U_FREETYPE
5307 MENUITEMs(_("//Paste Text (FreeType)"), ACTMOD(DLG_TEXT_FT, 0)),
5308 SHORTCUT(t, 0),
5309 #endif
5310 MENUITEMs(_("//Paste Palette"), ACTMOD(ACT_PASTE_PAL, 0)),
5311 MENUSEP, //
5312 SUBMENU(_("//Load Clipboard")),
5313 MENUTEAR, ///
5314 MENUITEMs("///1", ACTMOD(ACT_LOAD_CLIP, 1)),
5315 SHORTCUT(F1, S),
5316 MENUITEMs("///2", ACTMOD(ACT_LOAD_CLIP, 2)),
5317 SHORTCUT(F2, S),
5318 MENUITEMs("///3", ACTMOD(ACT_LOAD_CLIP, 3)),
5319 SHORTCUT(F3, S),
5320 MENUITEMs("///4", ACTMOD(ACT_LOAD_CLIP, 4)),
5321 SHORTCUT(F4, S),
5322 MENUITEMs("///5", ACTMOD(ACT_LOAD_CLIP, 5)),
5323 SHORTCUT(F5, S),
5324 MENUITEMs("///6", ACTMOD(ACT_LOAD_CLIP, 6)),
5325 SHORTCUT(F6, S),
5326 MENUITEMs("///7", ACTMOD(ACT_LOAD_CLIP, 7)),
5327 SHORTCUT(F7, S),
5328 MENUITEMs("///8", ACTMOD(ACT_LOAD_CLIP, 8)),
5329 SHORTCUT(F8, S),
5330 MENUITEMs("///9", ACTMOD(ACT_LOAD_CLIP, 9)),
5331 SHORTCUT(F9, S),
5332 MENUITEMs("///10", ACTMOD(ACT_LOAD_CLIP, 10)),
5333 SHORTCUT(F10, S),
5334 MENUITEMs("///11", ACTMOD(ACT_LOAD_CLIP, 11)),
5335 SHORTCUT(F11, S),
5336 MENUITEMs("///12", ACTMOD(ACT_LOAD_CLIP, 12)),
5337 SHORTCUT(F12, S),
5338 WDONE,
5339 SUBMENU(_("//Save Clipboard")),
5340 MENUTEAR, ///
5341 MENUITEMs("///1", ACTMOD(ACT_SAVE_CLIP, 1)),
5342 ACTMAP(NEED_PCLIP), SHORTCUT(F1, C),
5343 MENUITEMs("///2", ACTMOD(ACT_SAVE_CLIP, 2)),
5344 ACTMAP(NEED_PCLIP), SHORTCUT(F2, C),
5345 MENUITEMs("///3", ACTMOD(ACT_SAVE_CLIP, 3)),
5346 ACTMAP(NEED_PCLIP), SHORTCUT(F3, C),
5347 MENUITEMs("///4", ACTMOD(ACT_SAVE_CLIP, 4)),
5348 ACTMAP(NEED_PCLIP), SHORTCUT(F4, C),
5349 MENUITEMs("///5", ACTMOD(ACT_SAVE_CLIP, 5)),
5350 ACTMAP(NEED_PCLIP), SHORTCUT(F5, C),
5351 MENUITEMs("///6", ACTMOD(ACT_SAVE_CLIP, 6)),
5352 ACTMAP(NEED_PCLIP), SHORTCUT(F6, C),
5353 MENUITEMs("///7", ACTMOD(ACT_SAVE_CLIP, 7)),
5354 ACTMAP(NEED_PCLIP), SHORTCUT(F7, C),
5355 MENUITEMs("///8", ACTMOD(ACT_SAVE_CLIP, 8)),
5356 ACTMAP(NEED_PCLIP), SHORTCUT(F8, C),
5357 MENUITEMs("///9", ACTMOD(ACT_SAVE_CLIP, 9)),
5358 ACTMAP(NEED_PCLIP), SHORTCUT(F9, C),
5359 MENUITEMs("///10", ACTMOD(ACT_SAVE_CLIP, 10)),
5360 ACTMAP(NEED_PCLIP), SHORTCUT(F10, C),
5361 MENUITEMs("///11", ACTMOD(ACT_SAVE_CLIP, 11)),
5362 ACTMAP(NEED_PCLIP), SHORTCUT(F11, C),
5363 MENUITEMs("///12", ACTMOD(ACT_SAVE_CLIP, 12)),
5364 ACTMAP(NEED_PCLIP), SHORTCUT(F12, C),
5365 WDONE,
5366 MENUITEM(_("//Import Clipboard from System"), ACTMOD(ACT_LOAD_CLIP, -1)),
5367 MENUITEM(_("//Export Clipboard to System"), ACTMOD(ACT_SAVE_CLIP, -1)),
5368 ACTMAP(NEED_PCLIP),
5369 MENUSEP, //
5370 MENUITEMs(_("//Choose Pattern ..."), ACTMOD(DLG_CHOOSER, CHOOSE_PATTERN)),
5371 SHORTCUT(F2, 0),
5372 MENUITEMs(_("//Choose Brush ..."), ACTMOD(DLG_CHOOSER, CHOOSE_BRUSH)),
5373 SHORTCUT(F3, 0),
5374 MENUITEMs(_("//Choose Colour ..."), ACTMOD(DLG_CHOOSER, CHOOSE_COLOR)),
5375 SHORTCUT(e, 0),
5376 // for scripting
5377 uMENUITEMs("//layers", ACTMOD(ACT_SCRIPT, TOOLBAR_LAYERS)),
5378 uMENUITEMs("//settings", ACTMOD(ACT_SCRIPT, TOOLBAR_SETTINGS)),
5379 uMENUITEMs("//tools", ACTMOD(ACT_SCRIPT, TOOLBAR_TOOLS)),
5380 WDONE,
5381 SSUBMENU(_("/_View")),
5382 MENUTEAR, //
5383 MENUCHECKv(_("//Show Main Toolbar"), ACTMOD(ACT_TBAR, TOOLBAR_MAIN),
5384 toolbar_status[TOOLBAR_MAIN]), SHORTCUT(F5, 0),
5385 MENUCHECKv(_("//Show Tools Toolbar"), ACTMOD(ACT_TBAR, TOOLBAR_TOOLS),
5386 toolbar_status[TOOLBAR_TOOLS]), SHORTCUT(F6, 0),
5387 REFv(menu_slots[MENU_TBSET]),
5388 MENUCHECKv(_("//Show Settings Toolbar"), ACTMOD(ACT_TBAR, TOOLBAR_SETTINGS),
5389 toolbar_status[TOOLBAR_SETTINGS]), SHORTCUT(F7, 0),
5390 REFv(menu_slots[MENU_DOCK]),
5391 MENUCHECKv(_("//Show Dock"), ACTMOD(ACT_DOCK, 0), show_dock),
5392 SHORTCUT(F12, 0), MTRIGGER(menu_action), // to show dock initially
5393 MENUCHECKv(_("//Show Palette"), ACTMOD(ACT_TBAR, TOOLBAR_PALETTE),
5394 toolbar_status[TOOLBAR_PALETTE]), SHORTCUT(F8, 0),
5395 MENUCHECKv(_("//Show Status Bar"), ACTMOD(ACT_TBAR, TOOLBAR_STATUS),
5396 toolbar_status[TOOLBAR_STATUS]),
5397 MENUSEP, //
5398 MENUITEM(_("//Toggle Image View"), ACTMOD(ACT_VIEW, 0)),
5399 SHORTCUT(Home, 0), SHORTCUT(Home, C), SHORTCUT(Home, S),
5400 SHORTCUT(Home, A), SHORTCUT(Home, CS), SHORTCUT(Home, CA),
5401 SHORTCUT(Home, SA), SHORTCUT(Home, CSA),
5402 MENUCHECKv(_("//Centralize Image"), ACTMOD(ACT_CENTER, 0), canvas_image_centre),
5403 MENUCHECKv(_("//Show Zoom Grid"), ACTMOD(ACT_GRID, 0), mem_show_grid),
5404 MENUCHECKvs(_("//Snap To Tile Grid"), ACTMOD(ACT_SNAP, 0), tgrid_snap),
5405 SHORTCUT(b, 0),
5406 MENUITEM(_("//Configure Grid ..."), ACTMOD(DLG_COLORS, COLSEL_GRID)),
5407 MENUITEM(_("//Tracing Image ..."), ACTMOD(DLG_TRACE, 0)),
5408 MENUSEP, //
5409 REFv(menu_slots[MENU_VIEW]),
5410 MENUCHECKv(_("//View Window"), ACTMOD(ACT_VWWIN, 0), menu_view),
5411 SHORTCUT(v, 0),
5412 MENUCHECKv(_("//Horizontal Split"), ACTMOD(ACT_VWSPLIT, 0), view_vsplit),
5413 SHORTCUT(h, 0),
5414 MENUCHECKv(_("//Focus View Window"), ACTMOD(ACT_VWFOCUS, 0), vw_focus_on),
5415 MENUSEP, //
5416 MENUITEMi(_("//Pan Window"), ACTMOD(ACT_PAN, 0), XPM_ICON(pan)),
5417 SHORTCUT(End, 0),
5418 REFv(menu_slots[MENU_LAYER]),
5419 MENUCHECKv(_("//Layers Window"), ACTMOD(DLG_LAYERS, 0), menu_layer),
5420 SHORTCUT(l, 0),
5421 WDONE,
5422 SSUBMENU(_("/_Image")),
5423 MENUTEAR, //
5424 uMENUITEMs("//Convert To RGB", ACTMOD(FILT_2RGB, 0)), // for scripting
5425 MENUITEM(_("//Convert To RGB"), ACTMOD(FILT_2RGB, 0)),
5426 ACTMAP(NEED_IDX),
5427 MENUITEMs(_("//Convert To Indexed ..."), ACTMOD(DLG_INDEXED, 0)),
5428 ACTMAP(NEED_24),
5429 MENUSEP, //
5430 MENUITEMs(_("//Scale Canvas ..."), ACTMOD(DLG_SCALE, 0)),
5431 SHORTCUT(Page_Up, 0),
5432 MENUITEMs(_("//Resize Canvas ..."), ACTMOD(DLG_SIZE, 0)),
5433 SHORTCUT(Page_Down, 0),
5434 MENUITEMs(_("//Crop"), ACTMOD(ACT_CROP, 0)),
5435 ACTMAP(NEED_CROP), SHORTCUT(x, CS), SHORTCUT(Delete, 0),
5436 MENUSEP, //
5437 MENUITEMs(_("//Flip Vertically"), ACTMOD(ACT_FLIP_V, 0)),
5438 MENUITEMs(_("//Flip Horizontally"), ACTMOD(ACT_FLIP_H, 0)),
5439 SHORTCUT(m, C),
5440 uMENUITEMs("//rotate", ACTMOD(DLG_ROTATE, 0)), // for scripting
5441 MENUITEMs(_("//Rotate Clockwise"), ACTMOD(ACT_ROTATE, 0)),
5442 MENUITEMs(_("//Rotate Anti-Clockwise"), ACTMOD(ACT_ROTATE, 1)),
5443 MENUITEMs(_("//Free Rotate ..."), ACTMOD(DLG_ROTATE, 0)),
5444 MENUITEMs(_("//Skew ..."), ACTMOD(DLG_SKEW, 0)),
5445 MENUSEP, //
5446 // !!! Maybe support indexed mode too, later
5447 MENUITEMs(_("//Segment ..."), ACTMOD(DLG_SEGMENT, 0)),
5448 ACTMAP(NEED_24),
5449 uMENUITEMs("//Script ...", ACTMOD(ACT_RUN_SCRIPT, 0)), // for scripting
5450 REFv(menu_slots[MENU_SCRIPT]),
5451 MENUITEM(_("//Script ..."), ACTMOD(DLG_SCRIPT, 0)),
5452 REFv(menu_slots[MENU_SCRIPT_M]),
5453 SUBMENU(_("//Scripts")),
5454 MENUTEAR, ///
5455 REFv(menu_slots[MENU_SCRIPT1]),
5456 MENUITEMs("///1", ACTMOD(ACT_RUN_SCRIPT, 1)),
5457 REFv(menu_slots[MENU_SCRIPT2]),
5458 MENUITEMs("///2", ACTMOD(ACT_RUN_SCRIPT, 2)),
5459 REFv(menu_slots[MENU_SCRIPT3]),
5460 MENUITEMs("///3", ACTMOD(ACT_RUN_SCRIPT, 3)),
5461 REFv(menu_slots[MENU_SCRIPT4]),
5462 MENUITEMs("///4", ACTMOD(ACT_RUN_SCRIPT, 4)),
5463 REFv(menu_slots[MENU_SCRIPT5]),
5464 MENUITEMs("///5", ACTMOD(ACT_RUN_SCRIPT, 5)),
5465 REFv(menu_slots[MENU_SCRIPT6]),
5466 MENUITEMs("///6", ACTMOD(ACT_RUN_SCRIPT, 6)),
5467 REFv(menu_slots[MENU_SCRIPT7]),
5468 MENUITEMs("///7", ACTMOD(ACT_RUN_SCRIPT, 7)),
5469 REFv(menu_slots[MENU_SCRIPT8]),
5470 MENUITEMs("///8", ACTMOD(ACT_RUN_SCRIPT, 8)),
5471 REFv(menu_slots[MENU_SCRIPT9]),
5472 MENUITEMs("///9", ACTMOD(ACT_RUN_SCRIPT, 9)),
5473 REFv(menu_slots[MENU_SCRIPT10]),
5474 MENUITEMs("///10", ACTMOD(ACT_RUN_SCRIPT, 10)),
5475 MENUSEP, ///
5476 REFv(menu_slots[MENU_SCRIPTC]),
5477 MENUITEM(_("///Configure"), ACTMOD(DLG_SCRIPT, 0)),
5478 WDONE,
5479 MENUSEP, //
5480 MENUITEMs(_("//Information ..."), ACTMOD(DLG_INFO, 0)),
5481 SHORTCUT(i, C),
5482 REFv(menu_slots[MENU_PREFS]),
5483 MENUITEMs(_("//Preferences ..."), ACTMOD(DLG_PREFS, 0)),
5484 SHORTCUT(p, C),
5485 WDONE,
5486 SSUBMENU(_("/_Selection")),
5487 MENUTEAR, //
5488 MENUITEMs(_("//Select All"), ACTMOD(ACT_SELECT, 1)),
5489 SHORTCUT(a, C),
5490 MENUITEMs(_("//Select None (Esc)"), ACTMOD(ACT_SELECT, 0)),
5491 ACTMAP(NEED_MARQ), SHORTCUT(a, CS),
5492 MENUITEMis(_("//Lasso Selection"), ACTMOD(ACT_LASSO, 0), XPM_ICON(lasso)),
5493 ACTMAP(NEED_LAS2), SHORTCUT(j, 0),
5494 MENUITEMs(_("//Lasso Selection Cut"), ACTMOD(ACT_LASSO, 1)),
5495 ACTMAP(NEED_LASSO),
5496 MENUSEP, //
5497 MENUITEMis(_("//Outline Selection"), ACTMOD(ACT_OUTLINE, 0), XPM_ICON(rect1)),
5498 ACTMAP(NEED_SEL2), SHORTCUT(t, C),
5499 MENUITEMis(_("//Fill Selection"), ACTMOD(ACT_OUTLINE, 1), XPM_ICON(rect2)),
5500 ACTMAP(NEED_SEL2), SHORTCUT(t, CS),
5501 MENUITEMis(_("//Outline Ellipse"), ACTMOD(ACT_ELLIPSE, 0), XPM_ICON(ellipse2)),
5502 ACTMAP(NEED_SEL), SHORTCUT(l, C),
5503 MENUITEMis(_("//Fill Ellipse"), ACTMOD(ACT_ELLIPSE, 1), XPM_ICON(ellipse)),
5504 ACTMAP(NEED_SEL), SHORTCUT(l, CS),
5505 MENUSEP, //
5506 MENUITEMis(_("//Flip Vertically"), ACTMOD(ACT_SEL_FLIP_V, 0), XPM_ICON(flip_vs)),
5507 ACTMAP(NEED_PCLIP),
5508 MENUITEMis(_("//Flip Horizontally"), ACTMOD(ACT_SEL_FLIP_H, 0), XPM_ICON(flip_hs)),
5509 ACTMAP(NEED_PCLIP),
5510 MENUITEMis(_("//Rotate Clockwise"), ACTMOD(ACT_SEL_ROT, 0), XPM_ICON(rotate_cs)),
5511 ACTMAP(NEED_PCLIP),
5512 MENUITEMis(_("//Rotate Anti-Clockwise"), ACTMOD(ACT_SEL_ROT, 1), XPM_ICON(rotate_as)),
5513 ACTMAP(NEED_PCLIP),
5514 MENUSEP, //
5515 MENUITEMs(_("//Horizontal Ramp"), ACTMOD(ACT_RAMP, 0)),
5516 ACTMAP(NEED_SEL),
5517 MENUITEMs(_("//Vertical Ramp"), ACTMOD(ACT_RAMP, 1)),
5518 ACTMAP(NEED_SEL),
5519 MENUSEP, //
5520 MENUITEMs(_("//Alpha Blend A,B"), ACTMOD(ACT_SEL_ALPHA_AB, 0)),
5521 ACTMAP(NEED_ACLIP),
5522 MENUITEMs(_("//Move Alpha to Mask"), ACTMOD(ACT_SEL_ALPHAMASK, 0)),
5523 ACTMAP(NEED_PCLIP),
5524 MENUITEMs(_("//Mask Colour A,B"), ACTMOD(ACT_SEL_MASK_AB, 0)),
5525 ACTMAP(NEED_CLIP),
5526 MENUITEMs(_("//Unmask Colour A,B"), ACTMOD(ACT_SEL_MASK_AB, 255)),
5527 ACTMAP(NEED_CLIP),
5528 MENUITEMs(_("//Mask All Colours"), ACTMOD(ACT_SEL_MASK, 0)),
5529 ACTMAP(NEED_PCLIP),
5530 MENUITEMs(_("//Clear Mask"), ACTMOD(ACT_SEL_MASK, 255)),
5531 ACTMAP(NEED_PCLIP),
5532 WDONE,
5533 SSUBMENU(_("/_Palette")),
5534 uMENUITEMs("//a", ACTMOD(ACT_A, 0)), // for scripting
5535 uMENUITEMs("//b", ACTMOD(ACT_B, 0)), // for scripting
5536 MENUTEAR, //
5537 MENUITEMis(_("//Load ..."), ACTMOD(DLG_FSEL, FS_PALETTE_LOAD), XPM_ICON(open)),
5538 MENUITEMis(_("//Save As ..."), ACTMOD(DLG_FSEL, FS_PALETTE_SAVE), XPM_ICON(save)),
5539 MENUITEMs(_("//Load Default"), ACTMOD(ACT_PAL_DEF, 0)),
5540 MENUSEP, //
5541 MENUITEMs(_("//Mask All"), ACTMOD(ACT_PAL_MASK, 1)),
5542 MENUITEMs(_("//Mask None"), ACTMOD(ACT_PAL_MASK, 0)),
5543 MENUSEP, //
5544 MENUITEMs(_("//Swap A & B"), ACTMOD(ACT_SWAP_AB, 0)),
5545 SHORTCUT(x, 0),
5546 MENUITEMs(_("//Edit Colour A & B ..."), ACTMOD(DLG_COLORS, COLSEL_EDIT_AB)),
5547 SHORTCUT(e, C),
5548 MENUITEMs(_("//Dither A"), ACTMOD(ACT_DITHER_A, 0)),
5549 ACTMAP(NEED_24),
5550 MENUITEMs(_("//Palette Editor ..."), ACTMOD(DLG_COLORS, COLSEL_EDIT_ALL)),
5551 SHORTCUT(w, C),
5552 MENUITEMs(_("//Set Palette Size ..."), ACTMOD(DLG_PAL_SIZE, 0)),
5553 MENUITEMs(_("//Merge Duplicate Colours"), ACTMOD(ACT_PAL_MERGE, 0)),
5554 ACTMAP(NEED_IDX),
5555 MENUITEMs(_("//Remove Unused Colours"), ACTMOD(ACT_PAL_CLEAN, 0)),
5556 ACTMAP(NEED_IDX),
5557 MENUSEP, //
5558 MENUITEMs(_("//Create Quantized ..."), ACTMOD(DLG_INDEXED, 1)),
5559 ACTMAP(NEED_24),
5560 MENUSEP, //
5561 MENUITEMs(_("//Sort Colours ..."), ACTMOD(DLG_PAL_SORT, 0)),
5562 MENUITEMs(_("//Palette Shifter ..."), ACTMOD(DLG_PAL_SHIFTER, 0)),
5563 MENUITEMs(_("//Pick Gradient ..."), ACTMOD(DLG_PICK_GRAD, 0)),
5564 WDONE,
5565 SSUBMENU(_("/Effe_cts")),
5566 MENUTEAR, //
5567 MENUITEMis(_("//Transform Colour ..."), ACTMOD(DLG_BRCOSA, 0), XPM_ICON(brcosa)),
5568 SHORTCUT(c, CS), SHORTCUT(Insert, 0),
5569 MENUITEMs(_("//Invert"), ACTMOD(FILT_INVERT, 0)),
5570 SHORTCUT(i, CS),
5571 MENUITEMs(_("//Greyscale"), ACTMOD(FILT_GREY, 0)),
5572 SHORTCUT(g, C),
5573 MENUITEMs(_("//Greyscale (Gamma corrected)"), ACTMOD(FILT_GREY, 1)),
5574 SHORTCUT(g, CS),
5575 MENUITEMs(_("//Normalize"), ACTMOD(FILT_NORM, 0)),
5576 MENUITEMs(_("//Threshold ..."), ACTMOD(DLG_XHOLD, 0)),
5577 MENUITEMs(_("//Map ..."), ACTMOD(FILT_MAP, 0)),
5578 ACTMAP(NEED_24),
5579 SUBMENU(_("//Isometric Transformation")),
5580 MENUTEAR, ///
5581 MENUITEMs(_("///Left Side Down"), ACTMOD(ACT_ISOMETRY, 0)),
5582 MENUITEMs(_("///Right Side Down"), ACTMOD(ACT_ISOMETRY, 1)),
5583 MENUITEMs(_("///Top Side Right"), ACTMOD(ACT_ISOMETRY, 2)),
5584 MENUITEMs(_("///Bottom Side Right"), ACTMOD(ACT_ISOMETRY, 3)),
5585 WDONE,
5586 MENUSEP, //
5587 MENUITEMs(_("//Edge Detect ..."), ACTMOD(FILT_EDGE, 0)),
5588 ACTMAP(NEED_NOIDX),
5589 MENUITEMs(_("//Difference of Gaussians ..."), ACTMOD(FILT_DOG, 0)),
5590 ACTMAP(NEED_NOIDX),
5591 MENUITEMs(_("//Sharpen ..."), ACTMOD(FILT_SHARPEN, 0)),
5592 ACTMAP(NEED_NOIDX),
5593 MENUITEMs(_("//Unsharp Mask ..."), ACTMOD(FILT_UNSHARP, 0)),
5594 ACTMAP(NEED_NOIDX),
5595 MENUITEMs(_("//Soften ..."), ACTMOD(FILT_SOFTEN, 0)),
5596 ACTMAP(NEED_NOIDX),
5597 MENUITEMs(_("//Gaussian Blur ..."), ACTMOD(FILT_GAUSS, 0)),
5598 ACTMAP(NEED_NOIDX),
5599 MENUITEMs(_("//Kuwahara-Nagao Blur ..."), ACTMOD(FILT_KUWAHARA, 0)),
5600 ACTMAP(NEED_24),
5601 MENUITEMs(_("//Emboss"), ACTMOD(FILT_FX, FX_EMBOSS)),
5602 ACTMAP(NEED_NOIDX),
5603 MENUITEMs(_("//Dilate"), ACTMOD(FILT_FX, FX_DILATE)),
5604 ACTMAP(NEED_NOIDX),
5605 MENUITEMs(_("//Erode"), ACTMOD(FILT_FX, FX_ERODE)),
5606 ACTMAP(NEED_NOIDX),
5607 MENUSEP, //
5608 MENUITEMs(_("//Solid Noise ..."), ACTMOD(DLG_NOISE, 0)),
5609 MENUITEMs(_("//Bacteria ..."), ACTMOD(FILT_BACT, 0)),
5610 ACTMAP(NEED_IDX),
5611 WDONE,
5612 SSUBMENU(_("/Cha_nnels")),
5613 MENUTEAR, //
5614 MENUITEMs(_("//New ..."), ACTMOD(ACT_CHANNEL, -1)),
5615 MENUITEMis(_("//Load ..."), ACTMOD(DLG_FSEL, FS_CHANNEL_LOAD), XPM_ICON(open)),
5616 MENUITEMis(_("//Save As ..."), ACTMOD(DLG_FSEL, FS_CHANNEL_SAVE), XPM_ICON(save)),
5617 MENUITEMs(_("//Delete ..."), ACTMOD(DLG_CHN_DEL, -1)),
5618 ACTMAP(NEED_CHAN),
5619 MENUSEP, //
5620 REFv(menu_slots[MENU_CHAN0]),
5621 MENURITEMvs(_("//Edit Image"), ACTMOD(ACT_CHANNEL, CHN_IMAGE), menu_chan),
5622 SHORTCUT(1, S), SHORTCUT(KP_1, S),
5623 REFv(menu_slots[MENU_CHAN1]),
5624 MENURITEMvs(_("//Edit Alpha"), ACTMOD(ACT_CHANNEL, CHN_ALPHA), menu_chan),
5625 SHORTCUT(2, S), SHORTCUT(KP_2, S),
5626 REFv(menu_slots[MENU_CHAN2]),
5627 MENURITEMvs(_("//Edit Selection"), ACTMOD(ACT_CHANNEL, CHN_SEL), menu_chan),
5628 SHORTCUT(3, S), SHORTCUT(KP_3, S),
5629 REFv(menu_slots[MENU_CHAN3]),
5630 MENURITEMvs(_("//Edit Mask"), ACTMOD(ACT_CHANNEL, CHN_MASK), menu_chan),
5631 SHORTCUT(4, S), SHORTCUT(KP_4, S),
5632 MENUSEP, //
5633 REFv(menu_slots[MENU_DCHAN0]),
5634 MENUCHECKv(_("//Hide Image"), ACTMOD(ACT_SET_OVERLAY, 1), hide_image),
5635 SHORTCUT(h, C),
5636 REFv(menu_slots[MENU_DCHAN1]),
5637 MENUCHECKvs(_("//Disable Alpha"), ACTMOD(ACT_CHN_DIS, CHN_ALPHA),
5638 channel_dis[CHN_ALPHA]),
5639 REFv(menu_slots[MENU_DCHAN2]),
5640 MENUCHECKvs(_("//Disable Selection"), ACTMOD(ACT_CHN_DIS, CHN_SEL),
5641 channel_dis[CHN_SEL]),
5642 REFv(menu_slots[MENU_DCHAN3]),
5643 MENUCHECKvs(_("//Disable Mask"), ACTMOD(ACT_CHN_DIS, CHN_MASK),
5644 channel_dis[CHN_MASK]),
5645 MENUSEP, //
5646 MENUCHECKvs(_("//Couple RGBA Operations"), ACTMOD(ACT_SET_RGBA, 0), RGBA_mode),
5647 MENUITEMs(_("//Threshold ..."), ACTMOD(FILT_THRES, 0)),
5648 MENUITEMs(_("//Unassociate Alpha"), ACTMOD(FILT_UALPHA, 0)),
5649 ACTMAP(NEED_RGBA),
5650 MENUSEP, //
5651 REFv(menu_slots[MENU_OALPHA]),
5652 MENUCHECKv(_("//View Alpha as an Overlay"), ACTMOD(ACT_SET_OVERLAY, 0), overlay_alpha),
5653 MENUITEM(_("//Configure Overlays ..."), ACTMOD(DLG_COLORS, COLSEL_OVERLAYS)),
5654 WDONE,
5655 SSUBMENU(_("/_Layers")),
5656 MENUTEAR, //
5657 MENUITEMis(_("//New Layer"), ACTMOD(ACT_LR_ADD, LR_NEW), XPM_ICON(new)),
5658 MENUITEMis(_("//Save"), ACTMOD(ACT_LR_SAVE, 0), XPM_ICON(save)),
5659 SHORTCUT(s, CS),
5660 MENUITEMs(_("//Save As ..."), ACTMOD(DLG_FSEL, FS_LAYER_SAVE)),
5661 MENUITEMs(_("//Save Composite Image ..."), ACTMOD(DLG_FSEL, FS_COMPOSITE_SAVE)),
5662 MENUITEMs(_("//Composite to New Layer"), ACTMOD(ACT_LR_ADD, LR_COMP)),
5663 MENUITEMs(_("//Remove All Layers"), ACTMOD(ACT_LR_DEL, 1)),
5664 MENUSEP, //
5665 MENUITEM(_("//Configure Animation ..."), ACTMOD(DLG_ANI, 0)),
5666 MENUITEM(_("//Preview Animation ..."), ACTMOD(DLG_ANI_VIEW, 0)),
5667 MENUITEM(_("//Set Key Frame ..."), ACTMOD(DLG_ANI_KEY, 0)),
5668 MENUITEM(_("//Remove All Key Frames ..."), ACTMOD(DLG_ANI_KILLKEY, 0)),
5669 WDONE,
5670 SSUBMENU(_("/More...")),
5671 WDONE,
5672 ESUBMENU(_("/_Help")),
5673 MENUITEM(_("//Documentation"), ACTMOD(ACT_DOCS, 0)),
5674 REFv(menu_slots[MENU_HELP]),
5675 MENUITEM(_("//About"), ACTMOD(DLG_ABOUT, 0)),
5676 SHORTCUT(F1, 0),
5677 MENUSEP, //
5678 MENUITEM(_("//Keyboard Shortcuts ..."), ACTMOD(DLG_KEYS, 0)),
5679 MENUITEM(_("//Rebind Shortcut Keycodes"), ACTMOD(ACT_REBIND_KEYS, 0)),
5680 WDONE,
5681 WDONE, // smartmenu
5682 ENDSCRIPT,
5683 RET
5684 };
5685
dock_esc(main_dd * dt,void ** wdata,int what,void ** where,key_ext * keydata)5686 static int dock_esc(main_dd *dt, void **wdata, int what, void **where,
5687 key_ext *keydata)
5688 {
5689 /* Pressing Escape moves focus out of dock - to nowhere */
5690 if (keydata->key == KEY(Escape))
5691 {
5692 cmd_setv(main_window_, NULL, WINDOW_FOCUS);
5693 return (TRUE);
5694 }
5695 return (FALSE);
5696 }
5697
cline_keypress(main_dd * dt,void ** wdata,int what,void ** where,key_ext * keydata)5698 static int cline_keypress(main_dd *dt, void **wdata, int what, void **where,
5699 key_ext *keydata)
5700 {
5701 return (check_zoom_keys(wtf_pressed(keydata))); // Check HOME/zoom keys
5702 }
5703
cline_select(main_dd * dt,void ** wdata,int what,void ** where)5704 static void cline_select(main_dd *dt, void **wdata, int what, void **where)
5705 {
5706 cmd_read(where, dt);
5707
5708 if (dt->idx_c == dt->nidx_c) return; // no change
5709 if ((layers_total ? check_layers_for_changes() : check_for_changes()) == 1)
5710 cmd_set(where, dt->idx_c); // Go back
5711 // Load requested file
5712 else do_a_load(file_args[dt->idx_c = dt->nidx_c], undo_load);
5713 }
5714
dock_undock_evt(main_dd * dt,void ** wdata,int what,void ** where)5715 static void dock_undock_evt(main_dd *dt, void **wdata, int what, void **where)
5716 {
5717 int dstate;
5718
5719 cmd_read(where, dt);
5720 dstate = dt->settings_d | dt->layers_d;
5721
5722 /* Hide settings + layers page if it's empty */
5723 cmd_showhide(dt->dockpage1, dstate);
5724
5725 /* Show tabs only when it makes sense */
5726 cmd_setv(dock_book, (void *)(dt->cline_d && dstate), NBOOK_TABS);
5727
5728 /* Close dock if nothing left in it */
5729 dstate |= dt->cline_d;
5730 if (!dstate) cmd_set(menu_slots[MENU_DOCK], FALSE);
5731 cmd_sensitive(menu_slots[MENU_DOCK], dstate);
5732 }
5733
5734 #define REPAINT_CANVAS_COST 512
5735
5736 #define WBbase main_dd
5737 static void *main_code[] = {
5738 /// MAIN WINDOW
5739 MAINWINDOW(MT_VERSION, icon_xpm, 100, 100), EVENT(CANCEL, delete_event),
5740 EVENT(KEY, handle_keypress),
5741 REF(drop), CLIPFORM(uri_list, 1),
5742 /* !!! Konqueror needs GDK_ACTION_MOVE to do a drop; we never accept
5743 * move as a move, so have to do some non-default processing - WJ */
5744 DRAGDROPm(drop, NULL, parse_drag),
5745 WXYWH("window", 630, 400),
5746 /// KEYMAP
5747 REFv(main_keys), KEYMAP(keyslot, "main"),
5748 CALL(keylist_code),
5749 REFv(dock_area), DOCK("dockSize"),
5750 /// MENU
5751 CALL(main_menu_code),
5752 /// TOOLBARS
5753 CALL(toolbar_code),
5754 XHBOX,
5755 /// PALETTE
5756 CALL(toolbar_palette_code),
5757 XVBOX,
5758 /// DRAWING AREA
5759 REFv(main_split), HVSPLIT,
5760 // MAIN WINDOW
5761 REFv(scrolledwindow_canvas), CSCROLLv(cvxy), EVENT(CHANGE, vw_focus_idle),
5762 REFv(drawing_canvas), CANVAS(48, 48, REPAINT_CANVAS_COST, paint_canvas),
5763 EVENT(CHANGE, configure_canvas),
5764 EVENT(XMOUSE, canvas_mouse), EVENT(RXMOUSE, canvas_mouse),
5765 EVENT(MXMOUSE, canvas_mouse), EVENT(CROSS, canvas_enter_leave),
5766 EVENT(SCROLL, canvas_scroll),
5767 // VIEW WINDOW
5768 CALL(init_view_code),
5769 EVENT(SCROLL, canvas_scroll), // for vw_drawing widget
5770 WDONE, // hvsplit
5771 //// STATUS BAR
5772 REFv(toolbar_boxes[TOOLBAR_STATUS]),
5773 STATUSBAR, UNLESSv(toolbar_status[TOOLBAR_STATUS]), HIDDEN,
5774 /* Labels visibility is set later by init_status_bar() */
5775 REFv(label_bar[STATUS_GEOMETRY]), STLABEL(0, 0),
5776 REFv(label_bar[STATUS_CURSORXY]), STLABEL(90, 1),
5777 REFv(label_bar[STATUS_PIXELRGB]), STLABEL(0, 0),
5778 REFv(label_bar[STATUS_SELEGEOM]), STLABELe(0, 0),
5779 REFv(label_bar[STATUS_UNDOREDO]), STLABELe(70, 1),
5780 WDONE, // statusbar
5781 WDONE, // xvbox
5782 WDONE, // xhbox
5783 WDONE, // left pane
5784 BORDER(NBOOK, 0),
5785 REFv(dock_book), NBOOKr, KEEPWIDTH, WANTKEYS(dock_esc),
5786 IFx(cline_d, 1),
5787 PAGEi(XPM_ICON(cline), 0),
5788 BORDER(SCROLL, 0),
5789 XSCROLL(1, 1), // auto/auto
5790 WLIST,
5791 RTXTCOLUMNDi(0, 0),
5792 COLUMNDATA(strs_c, sizeof(int)), CLEANUP(strs_c),
5793 LISTCu(nidx_c, cnt_c, cline_select), FOCUS,
5794 EVENT(KEY, cline_keypress),
5795 WDONE,
5796 ENDIF(1),
5797 REF(dockpage1), PAGEir(XPM_ICON(layers), 5),
5798 REFv(settings_dock),
5799 MOUNT(settings_d, create_settings_box, dock_undock_evt),
5800 HSEPt,
5801 REFv(layers_dock),
5802 PMOUNT(layers_d, create_layers_box, dock_undock_evt, "layers_h", 400),
5803 TRIGGER,
5804 WDONE, // page
5805 WDONE, // nbook
5806 WDONE, // right pane
5807 REF(clip), CLIPFORM(clip_formats, CLIP_TARGETS),
5808 REF(clipboard), CLIPBOARD(clip, 3, clipboard_export_fn, clipboard_import_fn),
5809 RAISED, WEND
5810 };
5811 #undef WBbase
5812
main_init()5813 void main_init()
5814 {
5815 main_dd tdata;
5816 char *tdev, txt[PATHTXT];
5817
5818 memset(&tdata, 0, sizeof(tdata));
5819 /* Prepare commandline list */
5820 if ((show_dock = tdata.cline_d = files_passed > 1))
5821 {
5822 memx2 mem;
5823 int i, *p;
5824
5825 memset(&mem, 0, sizeof(mem));
5826 getmemx2(&mem, 4000); // default size
5827 mem.here += getmemx2(&mem, files_passed * sizeof(int)); // minsize
5828 for (i = 0; i < files_passed; i++)
5829 {
5830 p = (int *)mem.buf + i;
5831 *p = mem.here - ((char *)p - mem.buf);
5832 /* Convert name string (to UTF-8 in GTK+2) and store it */
5833 gtkuncpy(txt, file_args[i], sizeof(txt));
5834 addstr(&mem, txt, 0);
5835 }
5836 tdata.cnt_c = files_passed;
5837 tdata.strs_c = (int *)mem.buf;
5838 }
5839 show_dock |= inifile_get_gboolean("showDock", FALSE);
5840 main_window_ = run_create_(main_code, &tdata, sizeof(tdata), script_cmds);
5841
5842 recent_files = bounded(recent_files, 0, MAX_RECENT);
5843 update_recent_files(FALSE);
5844
5845 if (!tdata.cline_d) // Stops first icon in toolbar being selected
5846 cmd_setv(main_window_, NULL, WINDOW_FOCUS);
5847
5848 init_status_bar();
5849 init_factions(); // Initialize file action menu
5850 update_script_menu();
5851
5852 file_in_homedir(txt, ".clipboard", PATHBUF);
5853 strncpy0(mem_clip_file, inifile_get("clipFilename", txt), PATHBUF);
5854
5855 set_cursor(NULL);
5856 change_to_tool(DEFAULT_TOOL_ICON);
5857
5858 /* Skip the GUI-specific updates in commandline mode */
5859 if (cmd_mode) return;
5860
5861 cmd_peekv(main_window_, &tdev, sizeof(tdev), WDATA_TABLET); // Check tablet state
5862 tablet_working = !!tdev;
5863
5864 cmd_showhide(main_window_, TRUE);
5865
5866 if (viewer_mode) toggle_view();
5867 else toolbar_showhide();
5868 }
5869
spot_undo(int mode)5870 void spot_undo(int mode)
5871 {
5872 mem_undo_next(mode); // Do memory stuff for undo
5873 update_menus(); // Update menu undo issues
5874 }
5875
5876 #ifdef U_NLS
5877 #include <locale.h>
setup_language()5878 void setup_language() // Change language
5879 {
5880 char *txt = inifile_get( "languageSETTING", "system" ), txt2[64];
5881
5882 if (strcmp("system", txt))
5883 {
5884 snprintf( txt2, 60, "LANGUAGE=%s", txt );
5885 putenv( txt2 );
5886 snprintf( txt2, 60, "LANG=%s", txt );
5887 putenv( txt2 );
5888 snprintf( txt2, 60, "LC_ALL=%s", txt );
5889 putenv( txt2 );
5890 }
5891 else txt = "";
5892
5893
5894 #if GTK_MAJOR_VERSION > 1
5895 if (cmd_mode)
5896 #endif
5897 setlocale(LC_ALL, txt);
5898 #if GTK_MAJOR_VERSION <= 2
5899 /* !!! Slow or not, but NLS is *really* broken on GTK+1 without it - WJ */
5900 // GTK+1 hates this - it really slows things down
5901 if (!cmd_mode) gtk_set_locale();
5902 #endif
5903 }
5904 #endif
5905
update_titlebar()5906 void update_titlebar() // Update filename in titlebar
5907 {
5908 static int changed = -1;
5909 static char *name = "";
5910 char txt[300], txt2[PATHTXT];
5911
5912
5913 /* Don't send needless updates */
5914 if (!main_window_ || ((mem_changed == changed) && (mem_filename == name)))
5915 return;
5916 changed = mem_changed;
5917 name = mem_filename;
5918
5919 snprintf(txt, 290, "%s %s %s", MT_VERSION,
5920 changed ? __("(Modified)") : "-",
5921 name ? gtkuncpy(txt2, name, PATHTXT) : __("Untitled"));
5922
5923 cmd_setv(main_window_, txt, WINDOW_TITLE);
5924 }
5925
notify_changed()5926 void notify_changed() // Image/palette has just changed - update vars as needed
5927 {
5928 mem_tempfiles = NULL;
5929 if (!mem_changed)
5930 {
5931 mem_changed = TRUE;
5932 update_titlebar();
5933 }
5934 }
5935
5936 /* Image has just been unchanged: saved to file, or loaded if "filename" is NULL */
notify_unchanged(char * filename)5937 void notify_unchanged(char *filename)
5938 {
5939 if (mem_changed)
5940 {
5941 if (filename) mem_file_modified(filename);
5942 mem_changed = FALSE;
5943 update_titlebar();
5944 }
5945 }
5946
5947