1
/**************************************************************************
3
Fotoxx edit photos and manage collections
5
Copyright 2007 2008 2009 2010 2011 Michael Cornelison
6
Source URL: http://kornelix.squarespace.com/fotoxx
7
Contact: kornelix2@googlemail.com
9
This program is free software: you can redistribute it and/or modify
10
it under the terms of the GNU General Public License as published by
11
the Free Software Foundation, either version 3 of the License, or
12
(at your option) any later version.
14
This program is distributed in the hope that it will be useful,
15
but WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
GNU General Public License for more details.
19
You should have received a copy of the GNU General Public License
20
along with this program. If not, see http://www.gnu.org/licenses/.
22
***************************************************************************/
24
#define EX // disable extern declarations
27
#define fversion "Fotoxx v.11.11.1" // this fotoxx version
28
#define flicense "Free software - GNU General Public License v.3"
29
#define fhomepage "http://kornelix.squarespace.com/fotoxx"
30
#define ftranslators \
31
"Translators: Jie Luo, Eugenio Baldi, Justa, Peter Landgren, \n" \
32
"Alexander Krasnopolsky, Arthur Kalverboer, André Campos Rodovalho, \n" \
33
"Stanislas Zeller, Doriano Blengino"
34
#define fcredits "Software used: GTK, libtiff, ufraw-batch, exiftool"
35
#define fcontact "Bug reports: kornelix2@googlemail.com"
38
char *initial_file = 0; // initial file on command line
41
// fotoxx main program
43
int main(int argc, char *argv[])
45
using namespace zfuncs;
47
char lang[8] = "", *pp;
50
int Fclone=0, cloxx=0, cloyy=0, cloww=0, clohh=0;
54
printf(fversion "\n"); // print fotoxx version
55
if (argc > 1 && strEqu(argv[1],"-v")) return 0; // no more is wanted
57
// initialize externals
60
for (int ii = 0; ii < 8; ii++) wtnx[ii] = ii;
61
strcpy(jpeg_quality,def_jpeg_quality);
62
mwgeom[0] = mwgeom[1] = 100; // default main window geometry v.11.07
63
mwgeom[2] = 700; mwgeom[3] = 500;
69
batchresize[0] = 1600;
70
batchresize[1] = 1200;
73
gridcount[0] = gridcount[1] = 5; // set default grid line counts
76
tbar_style = "both"; // default toolbar style
77
Ffirsttime = 1; // first time startup v.11.11
78
fsync_lock = "fotoxx_syncfiles";
82
gtk_init(&argc,&argv); // initz. GTK & strip GTK command line params
84
for (int ii = 1; ii < argc; ii++) // command line parameters
87
if (strcmpv(pp,"-debug","-d",null)) // -debug
89
else if (strcmpv(pp,"-lang","-l",null) && argc > ii+1) // -lang lc_RC (language/region code)
90
strncpy0(lang,argv[++ii],7);
91
else if (strcmpv(pp,"-clone1","-c1",null)) // -clone1 (clone1/2 not user options)
93
else if (strcmpv(pp,"-clone2","-c2",null)) { // -clone2 xpos ypos v.11.07
95
cloxx = atoi(argv[ii+1]); // window position and size
96
cloyy = atoi(argv[ii+2]); // passed from parent instance
97
cloww = atoi(argv[ii+3]);
98
clohh = atoi(argv[ii+4]);
101
else if (strcmpv(pp,"-slideshow","-ss",null) && argc > ii+1) // -slideshow /image/file.jpg v.11.04
102
SS_imagefile = strdupz(argv[++ii]);
103
else if (strcmpv(pp,"-music","-m",null) && argc > ii+1) // -music /music/file.ogg v.11.04
104
SS_musicfile = strdupz(argv[++ii]);
105
else if (strcmpv(pp,"-recent","-r",null)) // recent files gallery v.11.07
107
else if (strcmpv(pp,"-previous","-prev","-p",null)) // open previous file v.11.07
109
else if (strcmpv(pp,"-syncfiles",null)) { // spawned sync files process v.11.11
112
if (strEqu(pp,"auto")) Fautosync = 1; // set auto or manual sync
113
if (strEqu(pp,"manual")) Fmansync = 1;
115
else initial_file = strdupz(pp); // must be initial file or directory
118
zinitapp("fotoxx",null); // get app directories
120
ZTXinit(lang); // setup locale and translations
123
strncatv(topdirk_file,199,get_zuserdir(),"/top_directory",null); // /home/user/.fotoxx/top_directory
125
*search_index_file = 0;
126
strncatv(search_index_file,199,get_zuserdir(),"/search_index",null); // /home/user/.fotoxx/search_index
128
*tags_defined_file = 0;
129
strncatv(tags_defined_file,199,get_zuserdir(),"/tags_defined",null); // /home/user/.fotoxx/tags_defined
131
*recentfiles_file = 0;
132
strncatv(recentfiles_file,199,get_zuserdir(),"/recent_files",null); // /home/user/.fotoxx/recent_files
134
*saved_areas_dirk = 0;
135
strncatv(saved_areas_dirk,199,get_zuserdir(),"/saved_areas/",null); // /home/user/.fotoxx/saved_areas/
136
err = stat(saved_areas_dirk,&statb);
137
if (err) mkdir(saved_areas_dirk,0750);
139
*collections_dirk = 0;
140
strncatv(collections_dirk,199,get_zuserdir(),"/collections/",null); // /home/user/.fotoxx/collections/
141
err = stat(collections_dirk,&statb);
142
if (err) mkdir(collections_dirk,0750);
144
*saved_curves_dirk = 0;
145
strncatv(saved_curves_dirk,199,get_zuserdir(),"/saved_curves/",null); // /home/user/.fotoxx/saved_curves/
146
err = stat(saved_curves_dirk,&statb);
147
if (err) mkdir(saved_curves_dirk,0750);
149
*annotations_dirk = 0;
150
strncatv(annotations_dirk,199,get_zuserdir(),"/annotations/",null); // /home/user/.fotoxx/annotations/
151
err = stat(annotations_dirk,&statb);
152
if (err) mkdir(annotations_dirk,0750);
154
load_params(); // restore parameters from last session
156
if (Fsyncfiles) { // this is spawned sync process v.11.11
157
gtk_init_add((GtkFunction) syncfiles_func,0); // do the sync function only
162
if (! Fsyncfiles && ! Fclone) { // this is the user GUI process
163
printf("spawn sync files subprocess \n");
164
err = system("fotoxx -syncfiles auto &"); // spawn process for sync files
166
printf("error: %s \n",wstrerror(err)); // quit if failed
171
mWin = gtk_window_new(GTK_WINDOW_TOPLEVEL); // create main window
172
gtk_window_set_title(MWIN,fversion);
174
mVbox = gtk_vbox_new(0,0); // add vert. packing box
175
gtk_container_add(GTK_CONTAINER(mWin),mVbox);
177
mMbar = create_menubar(mVbox); // add menus / sub-menus
179
GtkWidget *mFile = add_menubar_item(mMbar,ZTX("File"),topmenufunc);
180
add_submenu_item(mFile, ZTX("Image Gallery"), m_gallery);
181
add_submenu_item(mFile, ZTX("Clone 50/50"), m_clone1);
182
add_submenu_item(mFile, ZTX("Clone Overlay"), m_clone2);
183
add_submenu_item(mFile, ZTX("Open Image File"), m_open);
184
add_submenu_item(mFile, ZTX("Open in New Window"), m_open_newin);
185
add_submenu_item(mFile, ZTX("Open Previous File"), m_previous);
186
add_submenu_item(mFile, ZTX("Open Recent File"), m_recent);
187
add_submenu_item(mFile, ZTX("Save to Same File"), m_save);
188
add_submenu_item(mFile, ZTX("Save to New Version"), m_savevers);
189
add_submenu_item(mFile, ZTX("Save to New File"), m_saveas);
190
add_submenu_item(mFile, ZTX("Create Blank Image"), m_create);
191
add_submenu_item(mFile, ZTX("Trash Image File"), m_trash);
192
add_submenu_item(mFile, ZTX("Rename Image File"), m_rename);
193
add_submenu_item(mFile, ZTX("Batch Rename Files"), m_batchrename);
194
add_submenu_item(mFile, ZTX("Print Image File"), m_print);
195
add_submenu_item(mFile, ZTX("Quit fotoxx"), m_quit);
197
GtkWidget *mTools = add_menubar_item(mMbar,ZTX("Tools"),topmenufunc);
198
add_submenu_item(mTools, ZTX("Manage Collections"), m_manage_collections);
199
add_submenu_item(mTools, ZTX("Move Collections"), m_move_collections);
200
add_submenu_item(mTools, ZTX("Check Monitor"), m_moncheck);
201
add_submenu_item(mTools, ZTX("Monitor Gamma"), m_mongamma);
202
add_submenu_item(mTools, ZTX("Brightness Graph"), m_histogram);
203
add_submenu_item(mTools, ZTX("Slide Show"), m_slideshow);
204
add_submenu_item(mTools, ZTX("Show RGB"), m_show_RGB);
205
add_submenu_item(mTools, ZTX("Grid Lines"), m_gridlines);
206
add_submenu_item(mTools, ZTX("Lens Parameters"), m_lensparms);
207
add_submenu_item(mTools, ZTX("Change Language"), m_lang);
208
add_submenu_item(mTools, "Edit Translations", m_translate);
209
add_submenu_item(mTools, ZTX("Add Menu and Launcher"), m_menu_launcher);
210
add_submenu_item(mTools, ZTX("Convert RAW files"), m_conv_raw);
211
add_submenu_item(mTools, ZTX("Burn Images to CD/DVD"), m_burn);
212
add_submenu_item(mTools, ZTX("E-mail Images"), m_email);
213
add_submenu_item(mTools, ZTX("Synchronize Files"), m_syncfiles);
214
add_submenu_item(mTools, ZTX("Toolbar Style"), m_tbar_style);
215
add_submenu_item(mTools, ZTX("Memory Usage"), m_memory_usage);
217
GtkWidget *mInfo = add_menubar_item(mMbar,"Info",topmenufunc);
218
add_submenu_item(mInfo, ZTX("Edit Caption/Comments"), m_edit_cctext);
219
add_submenu_item(mInfo, ZTX("Edit Tags"), m_edit_tags);
220
add_submenu_item(mInfo, ZTX("Manage Tags"), m_manage_tags);
221
add_submenu_item(mInfo, ZTX("Batch Add Tags"), m_batchAddTags);
222
add_submenu_item(mInfo, ZTX("Batch Delete Tag"), m_batchDelTag);
223
add_submenu_item(mInfo, ZTX("View Info (short)"), m_info_view_short);
224
add_submenu_item(mInfo, ZTX("View Info (long)"), m_info_view_long);
225
add_submenu_item(mInfo, ZTX("Edit Info"), m_info_edit);
226
add_submenu_item(mInfo, ZTX("Delete Info"), m_info_delete);
227
add_submenu_item(mInfo, ZTX("Search Images"), m_search_images);
229
GtkWidget *mSelect = add_menubar_item(mMbar,ZTX("Select"),topmenufunc);
230
add_submenu_item(mSelect, ZTX("Select"), m_select);
231
add_submenu_item(mSelect, ZTX("Show"), m_select_show);
232
add_submenu_item(mSelect, ZTX("Hide"), m_select_hide);
233
add_submenu_item(mSelect, ZTX("Enable"), m_select_enable);
234
add_submenu_item(mSelect, ZTX("Disable"), m_select_disable);
235
add_submenu_item(mSelect, ZTX("Invert"), m_select_invert);
236
add_submenu_item(mSelect, ZTX("Unselect"), m_select_unselect);
237
add_submenu_item(mSelect, ZTX("Copy"), m_select_copy);
238
add_submenu_item(mSelect, ZTX("Paste"), m_select_paste);
239
add_submenu_item(mSelect, ZTX("Open"), m_select_open);
240
add_submenu_item(mSelect, ZTX("Save"), m_select_save);
241
add_submenu_item(mSelect, ZTX("Select Whole Image"), m_select_whole_image);
242
add_submenu_item(mSelect, ZTX("Select and Edit"), m_select_edit);
244
GtkWidget *mTransf = add_menubar_item(mMbar,ZTX("Transform"),topmenufunc);
245
add_submenu_item(mTransf, ZTX("Rotate Image"), m_rotate);
246
add_submenu_item(mTransf, ZTX("Trim Image"), m_trim);
247
add_submenu_item(mTransf, ZTX("Resize Image"), m_resize);
248
add_submenu_item(mTransf, ZTX("Batch Resize/Export"), m_batchresize);
249
add_submenu_item(mTransf, ZTX("Annotate Image"), m_annotate);
250
add_submenu_item(mTransf, ZTX("Flip Image"), m_flip);
251
add_submenu_item(mTransf, ZTX("Make Negative"), m_negate);
252
add_submenu_item(mTransf, ZTX("Unbend Image"), m_unbend);
253
add_submenu_item(mTransf, ZTX("Keystone Correction"), m_keystone);
254
add_submenu_item(mTransf, ZTX("Warp Image (area)"), m_warp_area);
255
add_submenu_item(mTransf, ZTX("Warp Image (curved)"), m_warp_curved);
256
add_submenu_item(mTransf, ZTX("Warp Image (linear)"), m_warp_linear);
257
add_submenu_item(mTransf, ZTX("Warp Image (affine)"), m_warp_affine);
259
GtkWidget *mRetouch = add_menubar_item(mMbar,ZTX("Retouch"),topmenufunc);
260
add_submenu_item(mRetouch, ZTX("Brightness/Color"), m_tune);
261
add_submenu_item(mRetouch, ZTX("Gamma Curves"), m_gamma);
262
add_submenu_item(mRetouch, ZTX("Expand Brightness"), m_xbrange);
263
add_submenu_item(mRetouch, ZTX("Flatten Brightness"), m_flatten);
264
add_submenu_item(mRetouch, ZTX("Brightness Ramp"), m_brightramp);
265
add_submenu_item(mRetouch, ZTX("Tone Mapping"), m_tonemap);
266
add_submenu_item(mRetouch, ZTX("White Balance"), m_whitebal);
267
add_submenu_item(mRetouch, ZTX("Match Colors"), m_match_color);
268
add_submenu_item(mRetouch, "DRGB", m_DRGB);
269
add_submenu_item(mRetouch, ZTX("Revise RGB"), m_revise_RGB);
270
add_submenu_item(mRetouch, ZTX("Red Eyes"), m_redeye);
271
add_submenu_item(mRetouch, ZTX("Blur Image"), m_blur);
272
add_submenu_item(mRetouch, ZTX("Sharpen Image"), m_sharpen);
273
add_submenu_item(mRetouch, ZTX("Reduce Noise"), m_denoise);
274
add_submenu_item(mRetouch, ZTX("Smart Erase"), m_smart_erase);
275
add_submenu_item(mRetouch, ZTX("Remove Dust"), m_dust);
276
add_submenu_item(mRetouch, ZTX("Edit Pixels"), m_pixedit);
278
GtkWidget *mArt = add_menubar_item(mMbar,ZTX("Art"),topmenufunc);
279
add_submenu_item(mArt, ZTX("Color Depth"), m_colordep);
280
add_submenu_item(mArt, ZTX("Drawing"), m_draw);
281
add_submenu_item(mArt, ZTX("Outlines"), m_outlines);
282
add_submenu_item(mArt, ZTX("Embossing"), m_emboss);
283
add_submenu_item(mArt, ZTX("Tiles"), m_tiles);
284
add_submenu_item(mArt, ZTX("Dots"), m_dots);
285
add_submenu_item(mArt, ZTX("Painting"), m_painting);
287
GtkWidget *mComb = add_menubar_item(mMbar,ZTX("Combine"),topmenufunc);
288
add_submenu_item(mComb, ZTX("High Dynamic Range"), m_HDR);
289
add_submenu_item(mComb, ZTX("High Depth of Field"), m_HDF);
290
add_submenu_item(mComb, ZTX("Stack / Paint"), m_STP);
291
add_submenu_item(mComb, ZTX("Stack / Noise"), m_STN);
292
add_submenu_item(mComb, ZTX("Panorama"), m_pano);
293
add_submenu_item(mComb, ZTX("Vertical Panorama"), m_vpano);
295
GtkWidget *mPlugins = add_menubar_item(mMbar,"Plugins",topmenufunc); // build plugin menu v.11.03
296
add_submenu_item(mPlugins,ZTX("Edit Plugins"),m_edit_plugins);
297
for (int ii = 0; ii < Nplugins; ii++) {
298
pp = strstr(plugins[ii]," = ");
301
add_submenu_item(mPlugins,plugins[ii],m_run_plugin);
305
GtkWidget *mHelp = add_menubar_item(mMbar,ZTX("Help"),topmenufunc);
306
add_submenu_item(mHelp, ZTX("About"), m_help);
307
add_submenu_item(mHelp, ZTX("User Guide"), m_help);
308
add_submenu_item(mHelp, ZTX("User Guide Changes"), m_help);
309
add_submenu_item(mHelp, ZTX("Edit Functions Summary"), m_help);
310
add_submenu_item(mHelp, ZTX("Change Log"), m_help);
311
add_submenu_item(mHelp, ZTX("Translations"), m_help);
312
add_submenu_item(mHelp, ZTX("Home Page"), m_help);
313
add_submenu_item(mHelp, "README", m_help);
315
mTbar = create_toolbar(mVbox); // toolbar buttons
316
add_toolbar_button(mTbar, ZTX("Gallery"), ZTX("Image Gallery"), "gallery.png", m_gallery);
317
add_toolbar_button(mTbar, ZTX("Open"), ZTX("Open Image File"), "open.png", m_open);
318
add_toolbar_button(mTbar, ZTX("Prev"), ZTX("Open Previous File"), "prev.png", m_prev);
319
add_toolbar_button(mTbar, ZTX("Next"), ZTX("Open Next File"), "next.png", m_next);
320
add_toolbar_button(mTbar, "Zoom+", ZTX("Zoom-in (bigger)"), "zoom+.png", m_zoom);
321
add_toolbar_button(mTbar, "Zoom-", ZTX("Zoom-out (smaller)"), "zoom-.png", m_zoom);
322
add_toolbar_button(mTbar, ZTX("Undo"), ZTX("Undo One Edit"), "undo.png", m_undo);
323
add_toolbar_button(mTbar, ZTX("Redo"), ZTX("Redo One Edit"), "redo.png", m_redo);
324
add_toolbar_button(mTbar, "separator", null, null, null);
325
add_toolbar_button(mTbar, "separator", null, null, null);
326
add_toolbar_button(mTbar, ZTX("Save"), ZTX("Save to Same File"), "save.png", m_save);
327
add_toolbar_button(mTbar, ZTX("Save+V"), ZTX("Save to New Version"), "save+V.png", m_savevers);
328
add_toolbar_button(mTbar, ZTX("Save+F"), ZTX("Save to New File"), "save+F.png", m_saveas);
329
add_toolbar_button(mTbar, ZTX("Trash"), ZTX("Move Image to Trash"), "trash.png", m_trash);
330
add_toolbar_button(mTbar, "separator", null, null, null);
331
add_toolbar_button(mTbar, "separator", null, null, null);
332
add_toolbar_button(mTbar, ZTX("Quit"), ZTX("Quit fotoxx"), "quit.png", m_quit);
333
add_toolbar_button(mTbar, ZTX("Help"), ZTX("Fotoxx Essentials"), "help.png", m_help);
335
tbar_style_set(null); // recall last toolbar style v.11.06
337
drWin = gtk_drawing_area_new(); // add drawing window
338
gtk_box_pack_start(GTK_BOX(mVbox),drWin,1,1,0);
340
STbar = create_stbar(mVbox); // add status bar
342
G_SIGNAL(mWin,"delete_event",delete_event,0) // connect signals
343
G_SIGNAL(mWin,"destroy",destroy_event,0)
344
G_SIGNAL(drWin,"expose-event",mwpaint1,0)
346
gtk_widget_add_events(drWin,GDK_BUTTON_PRESS_MASK); // connect mouse events
347
gtk_widget_add_events(drWin,GDK_BUTTON_RELEASE_MASK);
348
gtk_widget_add_events(drWin,GDK_BUTTON_MOTION_MASK); // motion with button pressed
349
gtk_widget_add_events(drWin,GDK_POINTER_MOTION_MASK); // pointer motion
350
G_SIGNAL(drWin,"button-press-event",mouse_event,0)
351
G_SIGNAL(drWin,"button-release-event",mouse_event,0)
352
G_SIGNAL(drWin,"motion-notify-event",mouse_event,0)
354
G_SIGNAL(mWin,"key-press-event",KBpress,0) // connect KB events
355
G_SIGNAL(mWin,"key-release-event",KBrelease,0)
357
drag_drop_connect(drWin,m_open_drag); // connect drag-drop event
359
gtk_window_move(MWIN,mwgeom[0],mwgeom[1]); // main window geometry
360
gtk_window_set_default_size(MWIN,mwgeom[2],mwgeom[3]); // (defaults or last session params)
361
gtk_widget_show_all(mWin); // show all widgets
363
if (Fclone == 1) { // clone1: left half of desktop v.10.12
364
screen = gdk_screen_get_default();
365
cloww = gdk_screen_get_width(screen);
366
clohh = gdk_screen_get_height(screen);
367
cloww = cloww / 2 - 20;
369
gtk_window_move(MWIN,10,10); // v.11.07
370
gtk_window_resize(MWIN,cloww,clohh);
373
if (Fclone == 2) { // clone2: open new window v.11.07
374
gtk_window_move(MWIN,cloxx+20,cloyy+30); // slightly offset from old window
375
gtk_window_resize(MWIN,cloww,clohh);
378
gdkgc = gdk_gc_new(drWin->window); // initz. graphics context
380
black.red = black.green = black.blue = 0; // set up colors
381
white.red = white.green = white.blue = maxcolor;
382
lgray.red = lgray.green = lgray.blue = 0.9 * maxcolor;
383
dgray.red = dgray.green = dgray.blue = 0.5 * maxcolor;
384
red.red = maxcolor; red.green = red.blue = 0;
385
green.green = maxcolor; green.red = green.blue = 0;
387
colormap = gtk_widget_get_colormap(drWin);
388
gdk_rgb_find_color(colormap,&black);
389
gdk_rgb_find_color(colormap,&white);
390
gdk_rgb_find_color(colormap,&lgray);
391
gdk_rgb_find_color(colormap,&dgray);
392
gdk_rgb_find_color(colormap,&red);
393
gdk_rgb_find_color(colormap,&green);
395
gdk_gc_set_foreground(gdkgc,&black);
396
gdk_gc_set_background(gdkgc,&lgray);
397
gdk_window_set_background(drWin->window,&lgray); // v.11.01
400
gdk_gc_set_line_attributes(gdkgc,1,LINEATTRIBUTES);
402
arrowcursor = gdk_cursor_new(GDK_TOP_LEFT_ARROW); // cursor for selection
403
dragcursor = gdk_cursor_new(GDK_CROSSHAIR); // cursor for dragging
404
drawcursor = gdk_cursor_new(GDK_PENCIL); // cursor for drawing lines
406
gtk_init_add((GtkFunction) gtkinitfunc,0); // set initz. call from gtk_main()
407
gtk_main(); // start processing window events
412
/**************************************************************************/
414
// initial function called from gtk_main() at startup
416
int gtkinitfunc(void *data)
419
int ftype, fdirk = 0;
420
char procfile[20], *ppv;
424
menulock(1); // block menus until initz. done v.11.04
426
printf("install location: %s \n",get_zinstalloc()); // install location v.11.05
428
if (Ffirsttime) { // first time startup v.11.11
429
printf("%s \n",ZTX("first time startup"));
430
zmessageACK(mWin,"User Guide is available in Help menu"); // inform user of user guide
435
Nwt = sysconf(_SC_NPROCESSORS_ONLN); // get SMP CPU count
436
if (Nwt <= 0) Nwt = 1;
437
if (Nwt > max_threads) Nwt = max_threads; // compile time limit
438
printf("using %d threads \n",Nwt);
440
err = system("which exiftool"); // check for exiftool v.11.05
441
if (! err) Fexiftool = 1;
442
err = system("which xdg-open"); // check for xdg-open
443
if (! err) Fxdgopen = 1;
444
err = system("which ufraw-batch"); // check for ufraw-batch
445
if (! err) Fufraw = 1;
447
undo_files = zmalloc(200);
448
*undo_files = 0; // look for orphaned undo files
449
strncatv(undo_files,199,get_zuserdir(),"/*_undo_*",null); // /home/user/.fotoxx/*_undo_*
451
while ((pp = SearchWild(undo_files,flag)))
453
pp2 = strstr(pp,".fotoxx/");
455
npid = atoi(pp2+8); // pid of file owner
456
snprintf(procfile,19,"/proc/%d",npid);
457
err = stat(procfile,&statb);
458
if (! err) continue; // pid is active, keep file
459
printf("orphaned undo file deleted: %s \n",pp);
460
err = remove(pp); // delete file v.11.03
463
*undo_files = 0; // setup undo filespec template
464
strncatv(undo_files,199,get_zuserdir(),"/",PIDstring,"_undo_nn",null); // /home/user/.fotoxx/pppppp_undo_nn
466
editlog = pvlist_create(maxedits); // history log of image edits done
467
for (int ii = 0; ii < maxedits; ii++) // pre-load all pvlist entries v.10.2
468
pvlist_append(editlog,"nothing");
470
mutex_init(&Fpixmap_lock,0); // setup lock for edit pixmaps
471
TIFFSetWarningHandler(tiffwarninghandler); // intercept TIFF warning messages
472
zdialog_positions("load"); // load saved dialog positions v.11.07
474
// set up current file and directory from saved parameters or command line
476
if (topdirk) curr_dirk = strdupz(topdirk); // use top directory if defined v.11.07
478
ppv = getcwd(command,ccc); // or use current directory v.11.09
479
if (ppv) curr_dirk = strdupz(ppv);
482
if (initial_file) { // from command line or previous session
483
ppv = realpath(initial_file,0); // prepend directory if needed
486
initial_file = strdupz(ppv);
490
printf("invalid file: %s \n",initial_file); // invalid file path
497
ftype = image_file_type(initial_file);
498
if (ftype == 0) { // non-existent file
499
printf("invalid file: %s \n",initial_file);
503
else if (ftype == 1) { // is a directory
504
if (curr_dirk) zfree(curr_dirk);
505
curr_dirk = initial_file;
509
else if (ftype == 2) { // supported image file type
510
if (curr_dirk) zfree(curr_dirk);
511
curr_dirk = strdupz(initial_file); // set current directory from file
512
ppv = strrchr(curr_dirk,'/');
516
printf("unsupported file type: %s \n",initial_file); // unsupported file type
522
if (curr_dirk) err = chdir(curr_dirk); // set current directory
524
menulock(0); // enable menus v.11.04
526
g_timeout_add(200,gtimefunc,0); // start periodic function (200 ms) v.11.06
528
if (SS_imagefile) { // start slide show if wanted v.11.04
529
f_open(SS_imagefile,1);
531
sprintf(command,"xdg-open \"%s\" ",SS_musicfile); // start music if wanted
532
printf("command: %s \n",command);
533
err = system(command);
539
if (initial_file) // open initial file
540
f_open(initial_file,1);
542
else if (fdirk) { // open gallery for initial directory
543
image_navi::thumbsize = 128;
544
m_gallery(0,0); // v.11.08
547
else if (Fprev) // start with previous file v.11.08
551
image_navi::thumbsize = 180; // start with gallery of recent files
552
m_recent(0,0); // v.11.07
555
return 0; // start with blank window
559
/**************************************************************************/
562
// Avoid any thread usage of gtk/gdk functions.
564
int gtimefunc(void *arg)
566
static zdialog *zdmessage = 0;
569
if (Wrepaint) { // window update needed
574
update_statusbar(); // update every cycle v.11.03
576
if (Fslideshow) slideshow_next("timer"); // show next image if time is up
579
paint_text(Dww/2-50,Dhh-20,"BUSY","monospace 10"); // write BUSY on window v.11.07
581
if (threadmessage && ! zdmessage) // display message for thread process
582
zdmessage = zmessage_post(mWin,0,threadmessage); // (avoid use of GTK in threads) v.11.03
584
if (zdmessage && ! threadmessage) // message terminated by thread
585
zdialog_free(zdmessage);
587
if (zdmessage && zdmessage->sentinel != zdsentinel) { // popup window closed by user
592
syncfd = global_lock(fsync_lock); // check for sync files process
593
if (syncfd >= 0) { // not busy
594
global_unlock(syncfd);
595
if (Fsyncbusy) threadmessage = 0; // it was busy, now done v.11.11
598
else Fsyncbusy = 1; // sync files still busy
604
/**************************************************************************/
606
// update status bar with image data and status
607
// called from timer function
609
void update_statusbar()
611
static double time1 = 0, time2, cpu1 = 0, cpu2, cpuload;
612
char text1[300], text2[100];
613
int ww, hh, scale, bpc, done;
614
double file_MB = 1.0 / mega * curr_file_size;
618
time2 = get_seconds(); // compute process cpu load
619
if (time2 - time1 > 1.0) { // at 1 second intervals
620
cpu2 = jobtime(); // v.11.09
621
cpuload = 100.0 * (cpu2 - cpu1) / (time2 - time1);
626
sprintf(text1,"CPU %03.0f%c",cpuload,'%'); // CPU 023%
628
if (curr_file) // v.11.04
646
snprintf(text2,99," %dx%dx%d",ww,hh,bpc); // 2345x1234x16 (preview) 1.56MB 45%
648
if (Fpreview) strcat(text1," (preview)");
649
sprintf(text2," %.2fMB",file_MB);
651
scale = Mscale * 100 + 0.5;
652
sprintf(text2," %d%c",scale,'%');
655
if (Pundo) { // edit undo stack depth
656
snprintf(text2,99," edits: %d",Pundo);
661
if (Fmenulock) strcat(text1," menu locked");
662
if (Fsyncbusy) strcat(text1," file sync busy"); // v.11.11
663
if (Factivearea) strcat(text1," area active"); // v.11.01
664
if (zfuncs::zdialog_busy) strcat(text1," dialog open"); // v.11.11
666
if (SB_goal) { // progress monitor v.9.6
667
done = 100.0 * SB_done / SB_goal; // v.11.06
668
snprintf(text2,99," progress %d%c",done,'%');
672
if (*SB_text) { // application text v.10.7
674
strcat(text1,SB_text);
677
stbar_message(STbar,text1); // write to status bar
683
/**************************************************************************/
685
// functions for main window event signals
687
int delete_event() // main window closed
689
printf("main window delete event \n");
690
if (mod_keep()) return 1; // allow user bailout
691
Fshutdown++; // shutdown in progress
692
save_params(); // save parameters for next session
693
zdialog_positions("save"); // save dialog positions too v.11.07
694
free_resources(); // delete undo files
698
void destroy_event() // main window destroyed
700
printf("main window destroy event \n");
701
exit(1); // instead of gtk_main_quit();
706
// process top-level menu entry
708
void topmenufunc(GtkWidget *, cchar *menu)
710
topmenu = (char *) menu; // remember top-level menu in case
711
return; // this is needed somewhere
715
/**************************************************************************/
717
// Paint window when created, exposed, resized, or modified (edited).
718
// This is a full window update. May NOT be called from threads.
723
int incrx, incry; // mouse drag
724
static int pincrx = 0, pincry = 0; // prior mouse drag
725
static int pdww = 0, pdhh = 0; // prior window size
726
static int piorgx = 0, piorgy = 0; // prior image origin in window
727
static double pscale = 1; // prior scale
728
double wscale, hscale;
729
int refresh = 0; // flag, image refesh needed
732
if (Fshutdown) return 1; // shutdown underway
733
if (! Fpxm8) return 1; // no image
735
Dww = drWin->allocation.width; // (new) drawing window size
736
Dhh = drWin->allocation.height;
737
if (Dww < 20 || Dhh < 20) return 1; // too small
739
if (mutex_trylock(&Fpixmap_lock) != 0) { // lock pixmaps
740
Wrepaint++; // cannot, come back later
744
if (Frefresh) { // image was updated
749
if (Dww != pdww || Dhh != pdhh) { // window size changed
750
refresh++; // image refresh needed
755
if (E3pxm16) { // get image size
757
Ihh = E3hh; // edit in progress
760
Iww = Fww; // no edit
764
if (Fzoom == 0) { // scale to fit window
765
wscale = 1.0 * Dww / Iww;
766
hscale = 1.0 * Dhh / Ihh;
767
if (wscale < hscale) Mscale = wscale; // use greatest ww/hh ratio
768
else Mscale = hscale;
769
if (Iww < Dww && Ihh < Dhh && ! Fblowup) Mscale = 1.0; // small image 1x unless Fblowup
772
else Mscale = Fzoom; // scale to Fzoom level
774
if (Mscale != pscale) refresh++; // scale changed, image refresh needed
776
if (Mscale > pscale) { // zoom increased
777
Iorgx += iww * 0.5 * (1.0 - pscale / Mscale); // keep current image center
778
Iorgy += ihh * 0.5 * (1.0 - pscale / Mscale);
782
iww = Dww / Mscale; // image space fitting in window
783
if (iww > Iww) iww = Iww;
785
if (ihh > Ihh) ihh = Ihh;
787
if (zoomx || zoomy) { // req. zoom center
788
Iorgx = zoomx - 0.5 * iww; // corresp. image origin
789
Iorgy = zoomy - 0.5 * ihh;
793
if ((Mxdrag || Mydrag) && ! Mcapture) { // scroll via mouse drag
794
incrx = (Mxdrag - Mxdown) * 1.3 * Iww / iww; // scale
795
incry = (Mydrag - Mydown) * 1.3 * Ihh / ihh;
796
if (pincrx > 0 && incrx < 0) incrx = 0; // stop bounce at extremes
797
if (pincrx < 0 && incrx > 0) incrx = 0;
799
if (pincry > 0 && incry < 0) incry = 0;
800
if (pincry < 0 && incry > 0) incry = 0;
802
Iorgx += incrx; // new image origin after scroll
804
Mxdown = Mxdrag + incrx; // new drag origin
805
Mydown = Mydrag + incry;
809
if (iww == Iww) { // scaled image <= window width
810
Iorgx = 0; // center image in window
811
Dorgx = 0.5 * (Dww - Iww * Mscale);
813
else Dorgx = 0; // image > window, use entire window
815
if (ihh == Ihh) { // same for image height
817
Dorgy = 0.5 * (Dhh - Ihh * Mscale);
821
if (Iorgx + iww > Iww) Iorgx = Iww - iww; // set limits
822
if (Iorgy + ihh > Ihh) Iorgy = Ihh - ihh;
823
if (Iorgx < 0) Iorgx = 0;
824
if (Iorgy < 0) Iorgy = 0;
826
if (Iorgx != piorgx || Iorgy != piorgy) { // image origin changed
827
refresh++; // image refresh needed
832
if (refresh) // image refresh is needed
834
if (E3pxm16) { // edit in progress
836
Dpxm16 = PXM_copy_area(E3pxm16,Iorgx,Iorgy,iww,ihh); // copy E3 image area, PXM-16
837
pxmtemp = PXM_convbpc(Dpxm16); // convert to PXM-8
839
else pxmtemp = PXM_copy_area(Fpxm8,Iorgx,Iorgy,iww,ihh); // no edit, copy PXM-8 image area
841
dww = iww * Mscale; // scale to window
844
Dpxm8 = PXM_rescale(pxmtemp,dww,dhh);
848
wrect.x = wrect.y = 0; // stop flicker
851
gdk_window_begin_paint_rect(drWin->window,&wrect);
853
gdk_draw_rgb_image(drWin->window, gdkgc, Dorgx, Dorgy, dww, dhh, // draw scaled image to window
854
NODITHER, (uint8 *) Dpxm8->bmp, dww*3);
856
if (Ntoplines) paint_toplines(1); // draw line overlays
857
if (Ftoparc) paint_toparc(1); // draw arc overlay
858
if (Fgrid) paint_gridlines(); // draw grid lines
859
if (Ntoptext) paint_toptext(); // draw text strings v.11.07
860
if (Fshowarea && ! Fpreview) sa_show(1); // draw select area outline
862
gdk_window_end_paint(drWin->window); // release all window updates
864
mutex_unlock(&Fpixmap_lock); // unlock pixmaps
865
Wpainted++; // notify edit function of repaint
866
histogram_paint(); // update brightness histogram if exists
871
/**************************************************************************/
873
// Cause (modified) output image to get repainted soon.
874
// The entire image is repainted (that part within the window).
875
// MAY be called from threads.
880
Wrepaint++; // req. repaint by periodic function
885
// Repaint a rectangular part of the image being edited.
886
// px3, py3, ww3, hh3: modified area within E3pxm16 to be repainted
887
// Dpxm16: 1x copy of E3pxm16 area currently visible in main window
888
// px2, py2, ww2, hh2: E3pxm16 area copied into Dpxm16
889
// Dpxm8: window image, Dpxm16 scaled to window, converted to 8 bits
890
// May NOT be called from threads.
892
void mwpaint3(int px3, int py3, int ww3, int hh3) // overhauled v.10.11
894
int px2, py2, ww2, hh2, dx, dy;
898
if (! Dpxm16) zappcrash("mwpaint3() no Dpxm16"); // v.10.12
900
px2 = px3 - Iorgx; // map modified area into Dpxm16
905
if (px2 < 0) { // if beyond Dpxm16 edge, reduce
910
if (px2 + ww2 > iww) ww2 = iww - px2;
917
if (py2 + hh2 > ihh) hh2 = ihh - py2;
919
if (ww2 <= 0 || hh2 <= 0) return; // completely off Dpxm16 image
921
for (dy = 0; dy < hh2; dy++) // copy pixels from E3pxm16 to Dpxm16
922
for (dx = 0; dx < ww2; dx++)
924
pix3 = PXMpix(E3pxm16,px3+dx,py3+dy);
925
pix2 = PXMpix(Dpxm16,px2+dx,py2+dy);
931
PXM_update(Dpxm16,Dpxm8,px2,py2,ww2,hh2); // copy/rescale Dpxm16 area into Dpxm8 area
933
px2 = px2 * Mscale - 1; // Dpxm8 area impacted v.10.12
934
py2 = py2 * Mscale - 1;
935
ww2 = ww2 * Mscale + 1 / Mscale + 2;
936
hh2 = hh2 * Mscale + 1 / Mscale + 2;
938
if (px2 < 0) px2 = 0; // stay within image edge
939
if (py2 < 0) py2 = 0;
940
if (px2 + ww2 > dww) ww2 = dww - px2;
941
if (py2 + hh2 > dhh) hh2 = dhh - py2;
942
if (ww2 <= 0 || hh2 <= 0) return; // completely off Dpxm8 image
944
pxm8area = (uint8 *) Dpxm8->bmp + 3 * (dww * py2 + px2); // origin of area to copy
946
gdk_draw_rgb_image(drWin->window, gdkgc, px2 + Dorgx, py2 + Dorgy, // copy into window
947
ww2, hh2, NODITHER, pxm8area, dww*3);
952
/**************************************************************************/
954
// mouse event function - capture buttons and drag movements
956
void mouse_event(GtkWidget *, GdkEventButton *event, void *)
958
void mouse_convert(int &xpos, int &ypos);
960
static int bdtime = 0, butime = 0, mbusy = 0;
961
int button, time, type;
964
button = event->button; // button, 1/3 = left/right
966
Mxposn = event->x; // mouse position in window
969
mouse_convert(Mxposn,Myposn); // convert to image space
971
if (type == GDK_MOTION_NOTIFY) {
972
if (mbusy) return; // discard excess motion events
978
if (type == GDK_BUTTON_PRESS) { // button down
979
bdtime = time; // time of button down
980
Mxdown = Mxposn; // position at button down time
983
Mdrag++; // possible drag start
989
if (type == GDK_BUTTON_RELEASE) { // button up
990
Mxclick = Myclick = 0; // reset click status
991
butime = time; // time of button up
992
if (butime - bdtime < 400) // less than 0.4 secs down
993
if (Mxposn == Mxdown && Myposn == Mydown) { // and not moving
994
if (Mbutton == 1) LMclick++; // left mouse click
995
if (Mbutton == 3) RMclick++; // right mouse click
996
Mxclick = Mxdown; // click = button down position
999
Mxdown = Mydown = Mxdrag = Mydrag = Mdrag = Mbutton = 0; // forget buttons and drag
1002
if (type == GDK_MOTION_NOTIFY && Mdrag) { // drag underway
1007
if (mouseCBfunc) { // pass to handler function
1009
mbusy++; // stop re-entrance v.10.8
1015
if (LMclick && ! Mcapture) { // left click = zoom request
1017
zoomx = Mxclick; // zoom center = mouse
1019
m_zoom(null, (char *) "+");
1022
if (RMclick && ! Mcapture) { // right click
1025
zoomx = zoomy = 0; // reset zoomed image
1026
m_zoom(null, (char *) "-");
1028
else if (edit_coll_name && curr_file) // pass file to edit collection
1029
edit_coll_popmenu(null,curr_file); // v.11.11
1032
if ((Mxdrag || Mydrag) && ! Mcapture) mwpaint1(); // drag = scroll
1037
// convert mouse position from window space to image space
1039
void mouse_convert(int &xpos, int &ypos)
1041
xpos = (xpos - Dorgx) / Mscale + Iorgx + 0.5;
1042
ypos = (ypos - Dorgy) / Mscale + Iorgy + 0.5;
1044
if (xpos < 0) xpos = 0; // if outside image put at edge
1045
if (ypos < 0) ypos = 0;
1048
if (xpos >= E3ww) xpos = E3ww-1;
1049
if (ypos >= E3hh) ypos = E3hh-1;
1052
if (xpos >= Fww) xpos = Fww-1;
1053
if (ypos >= Fhh) ypos = Fhh-1;
1060
/**************************************************************************/
1062
// keyboard event function - some toolbar buttons have KB equivalents
1063
// GDK key symbols: /usr/include/gtk-2.0/gdk/gdkkeysyms.h
1065
int KBzmalloclog = 0;
1070
int KBpress(GtkWidget *win, GdkEventKey *event, void *) // prevent propagation of key-press
1071
{ // events to toolbar buttons
1072
KBkey = event->keyval;
1074
if (KBkey == GDK_Control_L || // Ctrl key is pressed v.11.07
1075
KBkey == GDK_Control_R) KBcontrolkey = 1;
1077
if (KBkey == GDK_Shift_L || // Shift key is pressed v.11.07
1078
KBkey == GDK_Shift_R) KBshiftkey = 1;
1080
if (KBkey == GDK_a || // 'A' key is pressed v.11.11
1081
KBkey == GDK_A) KB_A_key = 1; // (undo/redo --> undo/redo ALL)
1086
int KBrelease(GtkWidget *win, GdkEventKey *event, void *)
1091
KBkey = event->keyval;
1093
if (KBcapture) return 1; // let function handle it
1095
if (KBkey == GDK_Control_L || // Ctrl key released v.11.07
1096
KBkey == GDK_Control_R) KBcontrolkey = 0;
1098
if (KBkey == GDK_Shift_L || // Shift key released v.11.07
1099
KBkey == GDK_Shift_R) KBshiftkey = 0;
1101
if (KBkey == GDK_a || // 'A' key is released v.11.11
1102
KBkey == GDK_A) KB_A_key = 0;
1105
if (KBkey == GDK_s) m_save(0,0); // Ctrl-* shortcuts
1106
if (KBkey == GDK_S) m_saveas(0,0);
1107
if (KBkey == GDK_v) m_savevers(0,0);
1108
if (KBkey == GDK_V) m_savevers(0,0);
1109
if (KBkey == GDK_q) m_quit(0,0);
1110
if (KBkey == GDK_Q) m_quit(0,0);
1113
if (KBkey == GDK_G || KBkey == GDK_g) // toggle grid lines v.11.11
1116
if (Fslideshow) { // v.11.10
1117
if (KBkey == GDK_Left) slideshow_next("prev"); // arrow keys = prev/next image
1118
if (KBkey == GDK_Right) slideshow_next("next");
1119
if (KBkey == GDK_space) slideshow_next("pause"); // spacebar = pause/resume
1120
if (KBkey == GDK_Escape) m_slideshow(0,0); // escape = exit slideshow
1123
if (KBkey == GDK_Left) m_prev(0,0); // arrow keys = prev/next image
1124
if (KBkey == GDK_Right) m_next(0,0);
1127
if (KBkey == GDK_plus) m_zoom(null, (char *) "+"); // +/- keys >> zoom in/out
1128
if (KBkey == GDK_equal) m_zoom(null, (char *) "+"); // = key: same as +
1129
if (KBkey == GDK_minus) m_zoom(null, (char *) "-");
1130
if (KBkey == GDK_KP_Add) m_zoom(null, (char *) "+"); // keypad +
1131
if (KBkey == GDK_KP_Subtract) m_zoom(null, (char *) "-"); // keypad -
1133
if (KBkey == GDK_Delete) m_trash(0,0); // delete >> trash
1135
if (KBkey == GDK_F1) // F1 >> user guide
1136
showz_userguide(zfuncs::F1_help_topic); // show topic if there, or page 1
1138
if (! E1pxm16) { // if no edit in progress,
1139
if (KBkey == GDK_R || KBkey == GDK_r) angle = 90; // allow manual rotations
1140
if (KBkey == GDK_L || KBkey == GDK_l) angle = -90;
1142
mutex_lock(&Fpixmap_lock); // v.10.5
1143
pxm = PXM_rotate(Fpxm8,angle);
1148
mutex_unlock(&Fpixmap_lock);
1150
m_save(0,"nowarn"); // auto save uprighted image v.10.12
1154
if (KBcontrolkey && KBkey == GDK_m) { // zmalloc() activity log
1155
KBzmalloclog = 1 - KBzmalloclog; // Ctrl+M = toggle on/off
1156
if (KBzmalloclog) zmalloc_log(999999);
1157
else zmalloc_log(0);
1158
zmalloc_report(); // v.10.8
1161
if (KBkey == GDK_T || KBkey == GDK_t) m_trim(0,0); // Key T = Trim function v.10.3.1
1162
if (KBkey == GDK_B || KBkey == GDK_b) m_tune(0,0); // Key B = brightness/color v.10.3.1
1163
if (KBkey == GDK_N || KBkey == GDK_n) m_rename(0,0); // Key N = rename file v.11.11
1165
if (KBkey == GDK_z || KBkey == GDK_Z) { // Z key
1166
if (! KBcontrolkey) m_zoom(null, (char *) "Z"); // if no control key, toggle zoom 1x
1167
else { // if control key, v.11.11
1168
if (KBshiftkey) m_redo(0,0); // with shift: redo edit
1169
else m_undo(0,0); // without shift: undo edit
1178
/**************************************************************************/
1180
// paint a grid of horizontal and vertical lines
1182
void paint_gridlines() // revised v.10.10.2
1184
int px, py, stepx, stepy, startx, starty;
1186
if (! Fpxm8) return; // no image
1187
if (! Fgrid) return; // grid lines off v.11.11
1189
stepx = gridspace[0]; // space between grid lines
1190
stepy = gridspace[1];
1192
if (gridcount[0]) stepx = dww / (1 + gridcount[0]); // if line counts specified, v.10.11
1193
if (gridcount[1]) stepy = dhh / ( 1 + gridcount[1]); // set spacing accordingly
1195
startx = stepx * gridoffset[0] / 100; // variable offsets v.11.11
1196
if (startx < 0) startx += stepx;
1198
starty = stepy * gridoffset[1] / 100;
1199
if (starty < 0) starty += stepy;
1201
gdk_gc_set_foreground(gdkgc,&white); // white lines
1203
if (gridon[0] && stepx)
1204
for (px = Dorgx+startx; px < Dorgx+dww; px += stepx)
1205
gdk_draw_line(drWin->window,gdkgc,px,Dorgy,px,Dorgy+dhh);
1207
if (gridon[1] && stepy)
1208
for (py = Dorgy+starty; py < Dorgy+dhh; py += stepy)
1209
gdk_draw_line(drWin->window,gdkgc,Dorgx,py,Dorgx+dww,py);
1211
gdk_gc_set_foreground(gdkgc,&black); // adjacent black lines
1212
fg_color = black; // v.10.12
1214
if (gridon[0] && stepx)
1215
for (px = Dorgx+startx+1; px < Dorgx+dww; px += stepx)
1216
gdk_draw_line(drWin->window,gdkgc,px,Dorgy,px,Dorgy+dhh);
1218
if (gridon[1] && stepy)
1219
for (py = Dorgy+starty+1; py < Dorgy+dhh; py += stepy)
1220
gdk_draw_line(drWin->window,gdkgc,Dorgx,py,Dorgx+dww,py);
1226
/**************************************************************************/
1228
// refresh overlay lines on top of image
1229
// arg = 1: paint lines only (because window repainted)
1230
// 2: erase lines and forget them
1231
// 3: erase old lines, paint new lines, save new in old
1233
void paint_toplines(int arg)
1237
if (arg == 2 || arg == 3) // erase old lines
1238
for (ii = 0; ii < Nptoplines; ii++)
1239
erase_line(ptoplinex1[ii],ptopliney1[ii],ptoplinex2[ii],ptopliney2[ii]);
1241
if (arg == 1 || arg == 3) // draw new lines
1242
for (ii = 0; ii < Ntoplines; ii++)
1243
draw_line(toplinex1[ii],topliney1[ii],toplinex2[ii],topliney2[ii]);
1246
Nptoplines = Ntoplines = 0; // forget lines
1250
for (ii = 0; ii < Ntoplines; ii++) // save for future erase
1252
ptoplinex1[ii] = toplinex1[ii];
1253
ptopliney1[ii] = topliney1[ii];
1254
ptoplinex2[ii] = toplinex2[ii];
1255
ptopliney2[ii] = topliney2[ii];
1258
Nptoplines = Ntoplines;
1264
/**************************************************************************/
1266
// refresh overlay arc (circle/ellipse) on top of image
1267
// arg = 1: paint arc only (because window repainted)
1268
// 2: erase arc and forget it
1269
// 3: erase old arc, paint new arc, save new in old
1271
void paint_toparc(int arg)
1273
int arcx, arcy, arcw, arch;
1275
if (ptoparc && (arg == 2 || arg == 3)) { // erase old arc
1276
arcx = (ptoparcx-Iorgx) * Mscale + Dorgx + 0.5; // image to window space
1277
arcy = (ptoparcy-Iorgy) * Mscale + Dorgy + 0.5;
1278
arcw = ptoparcw * Mscale;
1279
arch = ptoparch * Mscale;
1281
gdk_gc_set_function(gdkgc,GDK_INVERT); // invert pixels
1282
gdk_draw_arc(drWin->window,gdkgc,0,arcx,arcy,arcw,arch,0,64*360); // draw arc
1283
gdk_gc_set_function(gdkgc,GDK_COPY);
1286
if (Ftoparc && (arg == 1 || arg == 3)) { // draw new arc
1287
arcx = (toparcx-Iorgx) * Mscale + Dorgx + 0.5; // image to window space
1288
arcy = (toparcy-Iorgy) * Mscale + Dorgy + 0.5;
1289
arcw = toparcw * Mscale;
1290
arch = toparch * Mscale;
1292
gdk_gc_set_function(gdkgc,GDK_INVERT); // invert pixels
1293
gdk_draw_arc(drWin->window,gdkgc,0,arcx,arcy,arcw,arch,0,64*360); // draw arc
1294
gdk_gc_set_function(gdkgc,GDK_COPY);
1298
Ftoparc = ptoparc = 0; // forget arcs
1302
ptoparc = Ftoparc; // save for future erase
1312
/**************************************************************************/
1314
// draw dotted line. coordinates are in image space.
1316
void draw_line(int ix1, int iy1, int ix2, int iy2)
1318
void draw_line_pixel(double pxm, double pym);
1320
double x1, y1, x2, y2;
1321
double pxm, pym, slope;
1323
x1 = Mscale * (ix1-Iorgx); // image to window space
1324
y1 = Mscale * (iy1-Iorgy);
1325
x2 = Mscale * (ix2-Iorgx);
1326
y2 = Mscale * (iy2-Iorgy);
1328
if (abs(y2 - y1) > abs(x2 - x1)) {
1329
slope = 1.0 * (x2 - x1) / (y2 - y1);
1331
for (pym = y1; pym <= y2; pym++) {
1332
pxm = round(x1 + slope * (pym - y1));
1333
draw_line_pixel(pxm,pym);
1337
for (pym = y1; pym >= y2; pym--) {
1338
pxm = round(x1 + slope * (pym - y1));
1339
draw_line_pixel(pxm,pym);
1344
slope = 1.0 * (y2 - y1) / (x2 - x1);
1346
for (pxm = x1; pxm <= x2; pxm++) {
1347
pym = round(y1 + slope * (pxm - x1));
1348
draw_line_pixel(pxm,pym);
1352
for (pxm = x1; pxm >= x2; pxm--) {
1353
pym = round(y1 + slope * (pxm - x1));
1354
draw_line_pixel(pxm,pym);
1359
gdk_gc_set_foreground(gdkgc,&black);
1365
void draw_line_pixel(double px, double py)
1368
static int flip = 0;
1373
if (pxn < 0 || pxn > dww-1) return;
1374
if (pyn < 0 || pyn > dhh-1) return;
1376
if (++flip > 4) flip = -5; // black/white line v.10.10
1377
if (flip < 0) gdk_gc_set_foreground(gdkgc,&black);
1378
else gdk_gc_set_foreground(gdkgc,&white);
1379
gdk_draw_point(drWin->window, gdkgc, pxn + Dorgx, pyn + Dorgy);
1385
// erase line. refresh line path from Dpxm8 pixels.
1387
void erase_line(int ix1, int iy1, int ix2, int iy2)
1389
void erase_line_pixel(double pxm, double pym);
1391
double x1, y1, x2, y2;
1392
double pxm, pym, slope;
1394
if (! Dpxm8) zappcrash("Dpxm8 = 0"); // v.10.3
1396
x1 = Mscale * (ix1-Iorgx);
1397
y1 = Mscale * (iy1-Iorgy);
1398
x2 = Mscale * (ix2-Iorgx);
1399
y2 = Mscale * (iy2-Iorgy);
1401
if (abs(y2 - y1) > abs(x2 - x1)) {
1402
slope = 1.0 * (x2 - x1) / (y2 - y1);
1404
for (pym = y1; pym <= y2; pym++) {
1405
pxm = x1 + slope * (pym - y1);
1406
erase_line_pixel(pxm,pym);
1410
for (pym = y1; pym >= y2; pym--) {
1411
pxm = x1 + slope * (pym - y1);
1412
erase_line_pixel(pxm,pym);
1417
slope = 1.0 * (y2 - y1) / (x2 - x1);
1419
for (pxm = x1; pxm <= x2; pxm++) {
1420
pym = y1 + slope * (pxm - x1);
1421
erase_line_pixel(pxm,pym);
1425
for (pxm = x1; pxm >= x2; pxm--) {
1426
pym = y1 + slope * (pxm - x1);
1427
erase_line_pixel(pxm,pym);
1435
void erase_line_pixel(double px, double py)
1442
if (pxn < 0 || pxn > dww-1) return;
1443
if (pyn < 0 || pyn > dhh-1) return;
1445
uint8 *pixel = (uint8 *) Dpxm8->bmp + (pyn * dww + pxn) * 3;
1446
gdk_draw_rgb_image(drWin->window, gdkgc, pxn + Dorgx, pyn + Dorgy,
1447
1, 1, NODITHER, pixel, dww * 3);
1452
// draw one pixel using given color, R/G/B = red/green/black
1454
void draw_pixel(int px, int py, GdkColor *color) // v.10.12
1457
static int pqx, pqy;
1459
qx = Mscale * (px-Iorgx) + 0.5; // image to window space
1460
qy = Mscale * (py-Iorgy) + 0.5;
1462
if (qx == pqx && qy == pqy) return; // avoid redundant points v.9.7
1467
qx = qx + Dorgx; // image origin in drawing window
1470
if (qx < 0 || qx > Dww-1) return; // bugfix v.11.03.1
1471
if (qy < 0 || qy > Dhh-1) return;
1473
if (color != &fg_color) {
1475
gdk_gc_set_foreground(gdkgc,&fg_color);
1478
gdk_draw_point(drWin->window,gdkgc,qx,qy); // draw pixel
1484
// draw a fat pixel using given color, R/G/B = red/green/black
1486
void draw_fat_pixel(int px, int py, GdkColor *color) // speedup v.11.04
1489
static int pqx, pqy;
1491
qx = Mscale * (px-Iorgx) + 0.5; // image to window space
1492
qy = Mscale * (py-Iorgy) + 0.5;
1494
if (qx == pqx && qy == pqy) return; // avoid redundant points v.9.7
1499
qx = qx + Dorgx; // image origin in drawing window
1502
if (qx < 0 || qy < 0) return;
1504
if (color != &fg_color) {
1506
gdk_gc_set_foreground(gdkgc,&fg_color);
1509
if (qx < Dww-1 && qy < Dhh-1 && Mscale > 1.0)
1510
gdk_draw_rectangle(drWin->window,gdkgc,0,qx,qy,2,2); // draw fat pixel 2x2
1512
else if (qx < Dww && qy < Dhh)
1513
gdk_draw_point(drWin->window,gdkgc,qx,qy); // on edge, draw pixel
1519
/**************************************************************************/
1521
// maintain a set of text strings written whenever the window is painted
1523
// add a new text string to the list
1524
// multiple text strings can be added with the same ID
1525
// px and py are image coordinates
1527
void add_toptext(int ID, int px, int py, cchar *text, cchar *font) // new v.11.07
1529
if (Ntoptext == maxtoptext) {
1530
printf("maxtoptext exceeded \n");
1534
int ii = Ntoptext++;
1535
toptext[ii].ID = ID;
1536
toptext[ii].px = px;
1537
toptext[ii].py = py;
1538
toptext[ii].text = text;
1539
toptext[ii].font = font;
1545
// delete text strings having the given ID from the list
1547
void erase_toptext(int ID) // new v.11.07
1551
for (ii = jj = 0; ii < Ntoptext; ii++)
1553
if (toptext[ii].ID == ID) continue;
1554
else toptext[jj++] = toptext[ii];
1563
// paint current text strings on window whenever it is repainted
1564
// called from mwpaint()
1566
void paint_toptext() // new v.11.07
1570
for (ii = 0; ii < Ntoptext; ii++) {
1571
px = toptext[ii].px;
1572
py = toptext[ii].py;
1573
px = Mscale * (px - Iorgx) + Dorgx; // image to window space
1574
py = Mscale * (py - Iorgy) + Dorgy;
1575
paint_text(px,py,toptext[ii].text,toptext[ii].font);
1582
// paint text on window, black on white background
1583
// px and py are window coordinates
1585
void paint_text(int px, int py, cchar *text, cchar *font) // new v.11.07
1587
static PangoFontDescription *pangofont = null;
1588
static PangoLayout *pangolayout = null;
1589
static char priorfont[40] = "";
1591
if (strNeq(font,priorfont)) { // change font
1592
strncpy0(priorfont,font,40);
1593
if (pangofont) pango_font_description_free(pangofont);
1594
if (pangolayout) g_object_unref(pangolayout);
1595
pangofont = pango_font_description_from_string(font);
1596
pangolayout = gtk_widget_create_pango_layout(drWin,0);
1597
pango_layout_set_font_description(pangolayout,pangofont);
1600
pango_layout_set_text(pangolayout,text,-1);
1601
gdk_draw_layout_with_colors(drWin->window,gdkgc,px,py,pangolayout,&black,&white);
1607
/**************************************************************************
1609
spline curve setup and edit functions
1611
Support multiple frames with curves in parallel // v.11.01
1612
(edit curve(s) and image mask curve)
1615
Add frame widget to dialog or zdialog.
1616
Set up drawing in frame:
1617
sd = curve_init(frame, callback_func)
1618
Initialize no. of curves in frame (1-10):
1620
Initialize vert/horz flag for curve spc:
1622
Initialize anchor points for curve spc:
1623
sd->nap[spc], sd->apx[spc][xx], sd->apy[spc][yy]
1624
Generate data for curve spc:
1625
splcurve_generate(sd,spc)
1626
Curves will now be shown and edited inside the frame when window is realized.
1627
The callback_func(spc) will be called when curve spc is edited.
1628
Change curve in program:
1629
set anchor points, call splcurve_generate(sd,spc)
1630
Get y-value (0-1) for curve spc and given x-value (0-1):
1631
yval = splcurve_yval(sd,spc,xval)
1632
If faster access to curve is needed (no interpolation):
1634
if (kk > 999) kk = 999;
1635
yval = sd->yval[spc][kk];
1639
// initialize for spline curve editing
1640
// initial anchor points are pre-loaded into spldat before window is realized
1642
spldat * splcurve_init(GtkWidget *frame, void func(int spc))
1644
int cc = sizeof(spldat); // allocate spc curve data area
1645
spldat * sd = (spldat *) zmalloc(cc,"curvedat");
1648
sd->drawarea = gtk_drawing_area_new(); // drawing area for curves
1649
gtk_container_add(GTK_CONTAINER(frame),sd->drawarea);
1650
sd->spcfunc = func; // user callback function
1652
gtk_widget_add_events(sd->drawarea,GDK_BUTTON_PRESS_MASK); // connect mouse events to drawing area
1653
gtk_widget_add_events(sd->drawarea,GDK_BUTTON_RELEASE_MASK);
1654
gtk_widget_add_events(sd->drawarea,GDK_BUTTON1_MOTION_MASK);
1655
G_SIGNAL(sd->drawarea,"motion-notify-event",splcurve_adjust,sd)
1656
G_SIGNAL(sd->drawarea,"button-press-event",splcurve_adjust,sd)
1657
G_SIGNAL(sd->drawarea,"expose-event",splcurve_draw,sd)
1663
// modify anchor points in curve using mouse
1665
int splcurve_adjust(void *, GdkEventButton *event, spldat *sd)
1668
int mx, my, button, evtype;
1669
static int spc, ap, mbusy = 0, Fdrag = 0; // drag continuation logic v.9.7
1671
double mxval, myval, cxval, cyval;
1672
double dist2, mindist2 = 0;
1674
mx = event->x; // mouse position in drawing area
1676
evtype = event->type;
1677
button = event->button;
1679
if (evtype == GDK_MOTION_NOTIFY) {
1680
if (mbusy) return 0; // discard excess motion events v.11.01
1686
if (evtype == GDK_BUTTON_RELEASE) {
1691
ww = sd->drawarea->allocation.width; // drawing area size
1692
hh = sd->drawarea->allocation.height;
1694
if (mx < 0) mx = 0; // limit edge excursions
1695
if (mx > ww) mx = ww;
1697
if (my > hh) my = hh;
1699
if (evtype == GDK_BUTTON_PRESS) Fdrag = 0; // left or right click
1701
if (Fdrag) // continuation of drag
1703
if (sd->vert[spc]) {
1704
mxval = 1.0 * my / hh; // mouse position in curve space
1705
myval = 1.0 * mx / ww;
1708
mxval = 1.0 * mx / ww;
1709
myval = 1.0 * (hh - my) / hh;
1712
if (ap < sd->nap[spc]-1 && sd->apx[spc][ap+1] - mxval < 0.05) // disallow < 0.05 from next
1714
if (ap > 0 && mxval - sd->apx[spc][ap-1] < 0.05) return 0; // or prior anchor point
1717
else // mouse click or new drag begin
1719
minspc = minap = -1; // find closest curve/anchor point
1722
for (spc = 0; spc < sd->Nspc; spc++) // loop curves
1724
if (sd->vert[spc]) {
1725
mxval = 1.0 * my / hh; // mouse position in curve space
1726
myval = 1.0 * mx / ww;
1729
mxval = 1.0 * mx / ww;
1730
myval = 1.0 * (hh - my) / hh;
1733
for (ap = 0; ap < sd->nap[spc]; ap++) // loop anchor points
1735
cxval = sd->apx[spc][ap];
1736
cyval = sd->apy[spc][ap];
1737
dist2 = (mxval-cxval)*(mxval-cxval)
1738
+ (myval-cyval)*(myval-cyval);
1739
if (dist2 < mindist2) {
1740
mindist2 = dist2; // remember closest anchor point
1747
if (minspc < 0) return 0; // impossible
1752
if (evtype == GDK_BUTTON_PRESS && button == 3) // right click, remove anchor point
1754
if (sqrt(mindist2) > 0.05) return 0; // not close enough
1755
if (sd->nap[spc] < 3) return 0; // < 2 anchor points would remain
1756
sd->nap[spc]--; // decr. before loop v.11.11
1757
for (kk = ap; kk < sd->nap[spc]; kk++) {
1758
if (! kk) printf("meaningless reference %d",kk); // stop gcc optimization bug v.11.11 /////
1759
sd->apx[spc][kk] = sd->apx[spc][kk+1];
1760
sd->apy[spc][kk] = sd->apy[spc][kk+1];
1762
splcurve_generate(sd,spc); // regenerate data for modified curve
1763
splcurve_draw(0,0,sd); // regen and redraw all curves
1764
sd->spcfunc(spc); // call user function
1768
if (! Fdrag) // new drag or left click
1770
if (sd->vert[spc]) {
1771
mxval = 1.0 * my / hh; // mouse position in curve space
1772
myval = 1.0 * mx / ww;
1775
mxval = 1.0 * mx / ww;
1776
myval = 1.0 * (hh - my) / hh;
1779
if (sqrt(mindist2) < 0.05) // existing point close enough,
1780
{ // move this anchor point to mouse
1781
if (ap < sd->nap[spc]-1 && sd->apx[spc][ap+1] - mxval < 0.05)
1782
return 0; // disallow < 0.05 from next
1783
if (ap > 0 && mxval - sd->apx[spc][ap-1] < 0.05) return 0; // or prior anchor point
1786
else // none close, add new anchor point
1788
minspc = -1; // find closest curve to mouse
1791
for (spc = 0; spc < sd->Nspc; spc++) // loop curves
1793
if (sd->vert[spc]) {
1794
mxval = 1.0 * my / hh; // mouse position in curve space
1795
myval = 1.0 * mx / ww;
1798
mxval = 1.0 * mx / ww;
1799
myval = 1.0 * (hh - my) / hh;
1802
cyval = splcurve_yval(sd,spc,mxval);
1803
dist2 = fabs(myval - cyval);
1804
if (dist2 < mindist2) {
1805
mindist2 = dist2; // remember closest curve
1810
if (minspc < 0) return 0; // impossible
1811
if (mindist2 > 0.05) return 0; // not close enough to any curve
1814
if (sd->nap[spc] > 49) {
1815
zmessageACK(mWin,ZTX("Exceed 50 anchor points"));
1819
if (sd->vert[spc]) {
1820
mxval = 1.0 * my / hh; // mouse position in curve space
1821
myval = 1.0 * mx / ww;
1824
mxval = 1.0 * mx / ww;
1825
myval = 1.0 * (hh - my) / hh;
1828
for (ap = 0; ap < sd->nap[spc]; ap++) // find anchor point with next higher x
1829
if (mxval <= sd->apx[spc][ap]) break; // (ap may come out 0 or nap)
1831
if (ap < sd->nap[spc] && sd->apx[spc][ap] - mxval < 0.05) // disallow < 0.05 from next
1832
return 0; // or prior anchor point
1833
if (ap > 0 && mxval - sd->apx[spc][ap-1] < 0.05) return 0;
1835
for (kk = sd->nap[spc]; kk > ap; kk--) { // make hole for new point
1836
sd->apx[spc][kk] = sd->apx[spc][kk-1];
1837
sd->apy[spc][kk] = sd->apy[spc][kk-1];
1840
sd->nap[spc]++; // up point count
1844
sd->apx[spc][ap] = mxval; // new or moved anchor point
1845
sd->apy[spc][ap] = myval; // at mouse position
1847
splcurve_generate(sd,spc); // regenerate data for modified curve
1848
splcurve_draw(0,0,sd); // regen and redraw all curves
1849
sd->spcfunc(spc); // call user function
1851
if (evtype == GDK_MOTION_NOTIFY) Fdrag = 1; // remember drag is underway
1856
// for expose event or when a curve is changed
1857
// draw all curves based on current anchor points
1859
int splcurve_draw(void *, void *, spldat *sd)
1861
int ww, hh, px, py, qx, qy, spc, ap;
1864
ww = sd->drawarea->allocation.width; // drawing area size
1865
hh = sd->drawarea->allocation.height;
1866
if (ww < 50 || hh < 50) return 0;
1868
gdk_window_clear(sd->drawarea->window); // clear window
1870
gdk_gc_set_foreground(gdkgc,&dgray);
1872
for (int ii = 0; ii < sd->Nscale; ii++) // draw y-scale lines if any v.11.07
1874
px = ww * sd->xscale[0][ii] + 0.5; // any line, not only horizontal v.11.10
1875
py = hh - hh * sd->yscale[0][ii] + 0.5;
1876
qx = ww * sd->xscale[1][ii] + 0.5;
1877
qy = hh - hh * sd->yscale[1][ii] + 0.5;
1878
gdk_draw_line(sd->drawarea->window,gdkgc,px,py,qx,qy);
1881
gdk_gc_set_foreground(gdkgc,&black);
1884
for (spc = 0; spc < sd->Nspc; spc++)
1886
if (sd->vert[spc]) // vert. curve
1888
for (py = 0; py < hh; py++) // generate all points for curve
1890
xval = 1.0 * py / hh; // remove anchor point limits v.10.9
1891
yval = splcurve_yval(sd,spc,xval);
1892
px = ww * yval + 0.49; // "almost" round - erratic floating point
1893
gdk_draw_point(sd->drawarea->window,gdkgc,px,py); // causes "bumps" in a flat curve
1896
for (ap = 0; ap < sd->nap[spc]; ap++) // draw boxes at anchor points
1898
xval = sd->apx[spc][ap];
1899
yval = sd->apy[spc][ap];
1902
for (qx = -2; qx < 3; qx++)
1903
for (qy = -2; qy < 3; qy++) {
1904
if (px+qx < 0 || px+qx >= ww) continue;
1905
if (py+qy < 0 || py+qy >= hh) continue;
1906
gdk_draw_point(sd->drawarea->window,gdkgc,px+qx,py+qy);
1912
for (px = 0; px < ww; px++) // generate all points for curve
1914
xval = 1.0 * px / ww; // remove anchor point limits v.10.9
1915
yval = splcurve_yval(sd,spc,xval);
1916
py = hh - hh * yval + 0.49; // almost round - erratic FP in Intel CPUs
1917
gdk_draw_point(sd->drawarea->window,gdkgc,px,py); // causes "bumps" in a flat curve
1920
for (ap = 0; ap < sd->nap[spc]; ap++) // draw boxes at anchor points
1922
xval = sd->apx[spc][ap];
1923
yval = sd->apy[spc][ap];
1925
py = hh - hh * yval;
1926
for (qx = -2; qx < 3; qx++)
1927
for (qy = -2; qy < 3; qy++) {
1928
if (px+qx < 0 || px+qx >= ww) continue;
1929
if (py+qy < 0 || py+qy >= hh) continue;
1930
gdk_draw_point(sd->drawarea->window,gdkgc,px+qx,py+qy);
1940
// generate all curve data points when anchor points are modified
1942
int splcurve_generate(spldat *sd, int spc)
1947
spline1(sd->nap[spc],sd->apx[spc],sd->apy[spc]); // compute curve fitting anchor points
1949
kklo = 1000 * sd->apx[spc][0] - 30; // xval range = anchor point range
1950
if (kklo < 0) kklo = 0; // + 0.03 extra below/above v.9.5
1951
kkhi = 1000 * sd->apx[spc][sd->nap[spc]-1] + 30;
1952
if (kkhi > 1000) kkhi = 1000;
1954
for (kk = 0; kk < 1000; kk++) // generate all points for curve
1956
xval = 0.001 * kk; // remove anchor point limits v.10.9
1957
yvalx = spline2(xval);
1958
if (yvalx < 0) yvalx = 0; // yval < 0 not allowed, > 1 OK v.9.5
1959
sd->yval[spc][kk] = yvalx;
1966
// Retrieve curve data using interpolation of saved table of values
1968
double splcurve_yval(spldat *sd, int spc, double xval)
1971
double x1, x2, y1, y2, y3;
1973
if (xval <= 0) return sd->yval[spc][0];
1974
if (xval >= 0.999) return sd->yval[spc][999];
1979
y1 = sd->yval[spc][ii];
1980
y2 = sd->yval[spc][ii+1];
1981
y3 = y1 + (y2 - y1) * (x2 - x1);
1986
// load curve data from a file
1987
// returns 0 if fail (invalid file data), sd not modified
1988
// returns 1 if succcess, sd is initialized from file data
1990
int splcurve_load(spldat *sd) // v.11.02
1995
int Nspc, vert[10], nap[10];
1996
double apx[10][50], apy[10][50];
1998
zfuncs::F1_help_topic = "curve_edit";
2000
pfile = zgetfile1(ZTX("load curve from a file"),"open",saved_curves_dirk);
2001
if (! pfile) return 0;
2003
fid = fopen(pfile,"r");
2005
if (! fid) goto fail;
2007
nn = fscanf(fid,"%d ",&Nspc); // no. of curves
2008
if (nn != 1) goto fail;
2009
if (Nspc < 1 || Nspc > 10) goto fail;
2010
if (Nspc != sd->Nspc) goto fail2;
2012
for (ii = 0; ii < Nspc; ii++) // loop each curve
2014
nn = fscanf(fid,"%d %d ",&vert[ii],&nap[ii]); // vertical flag, no. anchor points
2015
if (nn != 2) goto fail;
2016
if (vert[ii] < 0 || vert[ii] > 1) goto fail;
2017
if (nap[ii] < 2 || nap[ii] > 50) goto fail;
2019
for (jj = 0; jj < nap[ii]; jj++) // anchor point values
2021
nn = fscanf(fid,"%lf/%lf ",&apx[ii][jj],&apy[ii][jj]);
2022
if (nn != 2) goto fail;
2023
if (apx[ii][jj] < 0 || apx[ii][jj] > 1) goto fail;
2024
if (apy[ii][jj] < 0 || apy[ii][jj] > 1) goto fail;
2030
sd->Nspc = Nspc; // copy curve data to caller's arg
2032
for (ii = 0; ii < Nspc; ii++)
2034
sd->vert[ii] = vert[ii];
2035
sd->nap[ii] = nap[ii];
2037
for (jj = 0; jj < nap[ii]; jj++)
2039
sd->apx[ii][jj] = apx[ii][jj];
2040
sd->apy[ii][jj] = apy[ii][jj];
2044
for (ii = 0; ii < Nspc; ii++) // generate curve data from anchor points
2045
splcurve_generate(sd,ii);
2047
if (sd->drawarea) splcurve_draw(0,0,sd); // regen and redraw all curves
2052
if (fid) fclose(fid);
2053
zmessageACK(mWin,ZTX("curve file is invalid"));
2057
if (fid) fclose(fid);
2058
zmessageACK(mWin,ZTX("curve file has different no. of curves"));
2063
// save curve data to a file
2065
int splcurve_save(spldat *sd) // v.11.02
2071
zfuncs::F1_help_topic = "curve_edit";
2073
pp = zgetfile1(ZTX("save curve to a file"),"save",saved_curves_dirk);
2076
pfile = strdupz(pp,8);
2079
pp = strrchr(pfile,'/'); // force .curve extension
2080
if (pp) pp = strrchr(pp,'.');
2081
if (pp) strcpy(pp,".curve");
2082
else strcat(pfile,".curve");
2084
fid = fopen(pfile,"w");
2086
if (! fid) return 0;
2088
fprintf(fid,"%d \n",sd->Nspc); // no. of curves
2090
for (ii = 0; ii < sd->Nspc; ii++) // loop each curve
2092
fprintf(fid,"%d %d \n",sd->vert[ii],sd->nap[ii]); // vertical flag, no. anchor points
2093
for (jj = 0; jj < sd->nap[ii]; jj++) // anchor point values
2094
fprintf(fid,"%.4f/%.4f ",sd->apx[ii][jj],sd->apy[ii][jj]);
2103
/**************************************************************************
2104
zdialog mouse capture and release
2105
***************************************************************************/
2107
void takeMouse(zdialog *zd, CBfunc func, GdkCursor *cursor) // capture mouse for dialog v.11.03
2110
if (zd && zdialog_widget(zd,"mymouse"))
2111
zdialog_stuff(zd,"mymouse",1); // set zdialog radio button on
2115
gdk_window_set_cursor(drWin->window,cursor); // v.11.03
2120
void freeMouse() // free mouse for main window v.10.12
2122
if (mouse_zd && zdialog_widget(mouse_zd,"mymouse"))
2123
zdialog_stuff(mouse_zd,"mymouse",0); // set radio button off
2127
paint_toparc(2); // remove mouse circle v.11.04
2128
gdk_window_set_cursor(drWin->window,0); // set normal cursor v.11.03
2133
/**************************************************************************
2135
***************************************************************************/
2137
// display image gallery (thumbnails) in a separate window
2139
void m_gallery(GtkWidget *, cchar *)
2141
zfuncs::F1_help_topic = "navigation";
2144
image_gallery(0,"paint1",curr_file_posn,m_gallery2,mWin); // overlay main window v.10.9
2146
char *pp = getcwd(command,ccc); // v.11.09
2148
image_gallery(pp,"init",0,m_gallery2,mWin); // use current directory v.11.04
2149
image_gallery(0,"paint1");
2151
curr_file_posn = 0; // v.11.05
2158
// clicked thumbnail will call this function
2160
void m_gallery2(int Nth, int button)
2164
zfuncs::F1_help_topic = "navigation"; // v.11.08
2166
if (Nth == -1) return; // gallery window closed
2168
file = image_gallery(0,"find",Nth);
2171
if (edit_coll_name && button == 3) // right-click, pass to edit collection
2173
edit_coll_popmenu(null,file);
2178
f_open(file,1,Nth); // clicked file = current file
2181
gtk_window_present(MWIN); // bring main window to front v.10.12
2186
/**************************************************************************/
2188
// start a new parallel instance of fotoxx
2189
// move my window to right half of desktop
2190
// start new instance to open in left half
2192
void m_clone1(GtkWidget *, cchar *)
2197
zfuncs::F1_help_topic = "clone"; // v.10.8
2199
screen = gdk_screen_get_default(); // get desktop screen size
2200
ww = gdk_screen_get_width(screen);
2201
hh = gdk_screen_get_height(screen);
2204
gdk_window_move_resize(mWin->window,ww+20,10,ww,hh); // move my window to right half
2206
snprintf(command,ccc,"fotoxx -clone1 -lang %s",zfuncs::zlanguage); // start new instance
2207
if (curr_file) strncatv(command,ccc," \"",curr_file,"\"",null);
2208
strcat(command," &");
2209
err = system(command);
2210
if (err) printf("error: %s \n",wstrerror(err));
2215
// start a new parallel instance of fotoxx
2216
// new window is slightly down and right from old window
2218
void m_clone2(GtkWidget *, cchar *) // new v.11.07
2220
int xx, yy, ww, hh, err;
2222
zfuncs::F1_help_topic = "clone";
2224
gtk_window_get_position(MWIN,&xx,&yy); // get window position and size
2225
gtk_window_get_size(MWIN,&ww,&hh);
2227
snprintf(command,ccc,"fotoxx -clone2 %d %d %d %d -lang %s",xx,yy,ww,hh,zfuncs::zlanguage);
2228
if (curr_file) strncatv(command,ccc," \"",curr_file,"\"",null);
2229
strcat(command," &"); // start new instance and pass
2230
err = system(command); // my window posn and size
2231
if (err) printf("error: %s \n",wstrerror(err));
2236
/**************************************************************************/
2238
// open file menu function
2240
void m_open(GtkWidget *, cchar *)
2242
zfuncs::F1_help_topic = "open_image_file";
2248
/**************************************************************************/
2250
// open drag-drop file
2252
void m_open_drag(int x, int y, char *file)
2254
zfuncs::F1_help_topic = "open_image_file";
2260
/**************************************************************************/
2262
// open a new file in a new parallel instance of fotoxx
2263
// new window is slightly down and right from old window
2265
void m_open_newin(GtkWidget *, cchar *) // new v.11.07
2267
int xx, yy, ww, hh, err;
2270
zfuncs::F1_help_topic = "open_image_file";
2272
file = zgetfile1(ZTX("Open Image File"),"open",curr_file); // dialog to get filespec
2273
if (! file) return; // canceled
2274
if (image_file_type(file) != 2) return; // not a supported image file v.11.05
2276
gtk_window_get_position(MWIN,&xx,&yy); // get window position and size
2277
gtk_window_get_size(MWIN,&ww,&hh);
2279
snprintf(command,ccc,"fotoxx -c2 %d %d %d %d -l %s",xx,yy,ww,hh,zfuncs::zlanguage);
2280
strncatv(command,ccc," \"",file,"\"",null);
2281
strcat(command," &"); // start new instance and pass
2282
err = system(command); // my window posn and size
2283
if (err) printf("error: %s \n",wstrerror(err));
2288
/**************************************************************************/
2290
// open the previous file opened (not the same as toolbar [prev] button)
2291
// repeated use will cycle back and forth between two most recent files
2293
void m_previous(GtkWidget *, cchar *)
2297
zfuncs::F1_help_topic = "open_previous_file";
2299
if (! menulock(1)) return; // v.11.11
2301
if (mod_keep()) return; // v.11.06
2303
if (curr_file) jj = 1; // 2nd most recent file v.11.08
2304
else jj = 0; // no current file, use most recent
2306
for (ii = jj; ii < Nrecentfiles; ii++)
2308
err = f_open(recentfiles[ii],1); // get first one that is still there
2316
/**************************************************************************/
2318
// open an image file from the list of recent image files
2320
void m_recent(GtkWidget *, cchar *) // overhauled v.11.01
2322
void recentfile2(int Nth, int button);
2327
zfuncs::F1_help_topic = "open_recent_file";
2329
if (! menulock(1)) return;
2331
if (mod_keep()) return; // v.11.11
2333
fid = fopen(recentfiles_file,"w"); // revalidate list of recent files
2334
if (! fid) return; // and update disk file
2336
for (ii = jj = 0; ii < Nrecentfiles; ii++)
2338
if (! recentfiles[ii]) continue; // improved v.11.11
2339
typ = image_file_type(recentfiles[ii]);
2341
zfree(recentfiles[ii]);
2342
recentfiles[ii] = 0;
2346
recentfiles[jj] = recentfiles[ii];
2347
recentfiles[ii] = 0; // bugfix v.11.11.1
2349
fprintf(fid,"%s \n",recentfiles[jj]);
2355
if (! jj) return; // no files found
2357
image_gallery(recentfiles_file,"initF",0,recentfile2,mWin); // generate gallery of recent files
2358
image_gallery(0,"paint1",0); // show new image gallery window
2364
// clicked thumbnail will call this function
2366
void recentfile2(int Nth, int button)
2370
if (Nth == -1) { // gallery window cancelled
2371
if (curr_file) image_gallery(curr_file,"init"); // reset gallery from current file v.11.05
2375
file = image_gallery(0,"find",Nth);
2377
image_gallery(file,"init"); // initz. gallery from chosen file v.11.05
2378
image_gallery(0,"close"); // close the gallery
2379
f_open(file,1); // open the file
2385
/**************************************************************************/
2387
// add a file to the list of recent files
2389
void add_recent_file(cchar *file)
2393
for (ii = 0; ii < Nrecentfiles-1 && recentfiles[ii]; ii++) // find file in recent list
2394
if (strEqu(file,recentfiles[ii])) break; // (or find last entry in list)
2395
if (recentfiles[ii]) zfree(recentfiles[ii]); // free this slot in list
2396
for (; ii > 0; ii--) recentfiles[ii] = recentfiles[ii-1]; // move list UP to fill hole
2397
recentfiles[0] = strdupz(file); // current file >> first in list
2402
/**************************************************************************/
2404
// Open a file and initialize PXM bitmap.
2405
// If flock and menu is locked, do nothing and return.
2406
// Otherwise open the file and display in main window.
2407
// If Nth matches the file position in current file set, curr_file_posn
2408
// will be set to Nth, otherwise it is searched and set correctly
2409
// (a file can be present multiple times in a collection set).
2410
// If fkeepundo is ON, the edit undo image stack is not purged: current
2411
// edits are kept after opening the new file (used by m_saveas()).
2412
// Returns: 0 = OK, +N = error.
2414
int f_open(cchar *filespec, int flock, int Nth, int fkeepundo)
2417
int err, cc, yn, fposn, nfiles, nimages;
2418
char *pp, *file, *rawfile, titlebar[250];
2419
char fname[100], fdirk[100];
2420
cchar *discard = ZTX("Discard special gallery list? \n %s");
2422
int Gtype = image_navi::gallerytype;
2423
char *Gname = image_navi::galleryname;
2427
if (Gname) Grf = strEqu(Gname,recentfiles_file); // flag, gallery is recent files list
2429
if (flock && Fmenulock) {
2430
zmessageACK(mWin,ZTX("prior function still active")); // v.11.06
2434
if (mod_keep()) return 2; // unsaved edits
2437
file = strdupz(filespec,0,"f_open"); // use passed filespec
2439
if (Gtype == 2 && ! Grf) { // if named collection or search results,
2440
yn = zmessageYN(mWin,discard,image_navi::galleryname); // warn user, gallery will be discarded
2441
if (! yn) return 6; // do not discard v.11.09
2442
Gnew = 1; // flag, new gallery needed
2444
file = zgetfile1(ZTX("Open Image File"),"open",curr_file); // dialog to get filespec
2445
if (! file) return 3; // canceled
2448
if (image_file_type(file) != 2) // not a supported image file type
2450
pp = strrchr(file,'.');
2451
if (! pp || strlen(pp) != 4 || ! strcasestr(RAWfiles,pp)) { // check if a RAW file extension
2452
zfree(file); // v.11.09
2456
rawfile = file; // v.11.09
2457
file = strdupz(rawfile,5,"f_open"); // filename.raw >> filename.tiff
2458
pp = file + (pp - rawfile);
2460
snprintf(command,ccc,"ufraw-batch --wb=camera --out-type=tiff " // convert RAW file to tiff-16 v.11.09
2461
"--out-depth=16 --overwrite "
2462
"--output=\"%s\" \"%s\" ",file,rawfile);
2463
err = system(command);
2465
zmessageACK(mWin,wstrerror(err)); // failed, clean up
2470
zfree(rawfile); // tiff file will be opened
2473
temp8 = f_load(file,8); // load image as PXM-8 pixmap
2475
zfree(file); // bad image
2478
// menulock() removed v.11.11
2480
free_resources(fkeepundo); // free resources for old image file
2482
if (curr_file) zfree(curr_file); // current image filespec
2483
curr_file = strdupz(file,8,"curr_file");
2486
if (curr_dirk) zfree(curr_dirk); // set current directory
2487
curr_dirk = strdupz(curr_file,0,"curr_dirk"); // for new current file
2488
pp = strrchr(curr_dirk,'/');
2490
err = chdir(curr_dirk);
2492
Fpxm8 = temp8; // pixmap for current image
2496
strcpy(curr_file_type,f_load_type); // set curr_file_xxx from f_load_xxx
2497
curr_file_bpc = f_load_bpc;
2498
curr_file_size = f_load_size;
2500
fposn = image_gallery_position(curr_file,Nth); // file position in gallery list
2501
if (fposn < 0 || Gnew) { // not there or break current gallery v.11.09
2502
image_gallery(curr_file,"init"); // generate new gallery list
2503
fposn = image_gallery_position(curr_file,0); // position and count in gallery list
2504
image_gallery(0,"paint2",fposn); // refresh gallery window if active v.11.07
2507
nfiles = image_navi::nfiles; // total gallery files (incl. directories)
2508
nimages = image_navi::nimages; // total image files v.11.05
2510
curr_file_posn = fposn; // keep track of file position
2511
curr_file_count = image_navi::nimages; // and image gallery count
2513
add_recent_file(curr_file); // first in recent files list /////////
2515
Fzoom = 0; // zoom level = fit window
2516
zoomx = zoomy = 0; // no zoom center
2518
pp = (char *) strrchr(curr_file,'/');
2519
strncpy0(fname,pp+1,99); // file name //////
2520
cc = pp - curr_file;
2521
if (cc < 99) strncpy0(fdirk,curr_file,cc+2); // get dirk/path/ if short enough
2523
strncpy(fdirk,curr_file,96); // or use /dirk/path...
2524
strcpy(fdirk+95,"...");
2527
fposn = fposn + 1 - nfiles + nimages; // position among images, 1-based
2528
snprintf(titlebar,250,"%s %d/%d %s %s", // window title bar
2529
fversion,fposn,curr_file_count,fname,fdirk);
2530
gtk_window_set_title(MWIN,titlebar);
2532
mwpaint1(); // immediate paint v.11.11
2533
gtk_window_present(MWIN); // bring main window to front v.11.04
2536
if (zdrename) m_rename(0,0); // update active rename dialog
2537
if (zdexifview) info_view(0); // " EXIF/IPTC view window v.10.2
2538
if (zdexifedit) m_info_edit(0,0); // " EXIF/IPTC edit window v.10.11
2539
if (zdedittags) m_edit_tags(0,0); // " edit tags dialog
2540
if (zdeditcctext) m_edit_cctext(0,0); // " edit comments dialog v.10.10
2542
curr_image_time = get_seconds(); // mark time of file load v.11.07
2548
/**************************************************************************/
2550
// open previous or next file in current gallery list
2552
void m_prev(GtkWidget *, cchar *menu)
2556
if (menu) zfuncs::F1_help_topic = "open_image_file";
2558
if (Fmenulock) return;
2559
if (mod_keep()) return;
2560
if (! curr_file) return;
2562
for (Nth = curr_file_posn-1; Nth >= 0; Nth--) // v.11.05
2564
char *pp = image_gallery(0,"find",Nth);
2566
err = f_open(pp,1,Nth);
2574
void m_next(GtkWidget *, cchar *menu)
2577
int nfiles = image_navi::nfiles;
2579
if (menu) zfuncs::F1_help_topic = "open_image_file";
2581
if (Fmenulock) return;
2582
if (mod_keep()) return;
2583
if (! curr_file) return;
2585
for (Nth = curr_file_posn+1; Nth < nfiles; Nth++) // v.11.05
2587
char *pp = image_gallery(0,"find",Nth);
2589
err = f_open(pp,1,Nth);
2598
/**************************************************************************/
2600
// save (modified) image to same file
2602
void m_save(GtkWidget *, cchar *menu)
2604
int Fwarn = 1, zstat, suppress;
2607
cchar *warn_message = ZTX("Overwrite original file?");
2608
cchar *suppress_message = ZTX("Do not warn again");
2610
if (! curr_file) return;
2611
if (is_syncbusy()) return; // v.11.11
2613
if (menu && strNeq(menu,"nowarn")) // don't change help topic v.11.11
2614
zfuncs::F1_help_topic = "save_file";
2616
if (Fwarnoverwrite) { // warn if overwrite original v.11.10
2617
pp = strrchr(curr_file,'/');
2619
pp = strstr(pp,".v"); // look for version notation .vNN
2620
if (pp && pp[2] >= '0' && pp[2] <= '9'
2621
&& pp[3] >= '0' && pp[3] <= '9') Fwarn = 0; // found, file not original
2623
if (Fwarn && ! (menu && strEqu(menu,"nowarn"))) { // no warn if KB rotate save v.11.11
2624
zd = zdialog_new(ZTX("Warning"),mWin,Bproceed,Bcancel,null);
2625
zdialog_add_widget(zd,"label","lab1","dialog",warn_message,"space=5");
2626
zdialog_add_widget(zd,"check","suppress","dialog",suppress_message,"space=5");
2628
zstat = zdialog_wait(zd);
2629
zdialog_fetch(zd,"suppress",suppress);
2635
if (zstat != 1) return;
2639
strcpy(jpeg_quality,def_jpeg_quality); // default jpeg save quality
2641
if (strEqu(curr_file_type,"other")) // if gif, bmp, etc. use jpg v.11.03
2642
strcpy(curr_file_type,"jpg");
2644
f_save(curr_file,curr_file_type,curr_file_bpc); // save file
2646
strcpy(curr_file_type,f_save_type); // update curr_file_xxx from f_save_xxx
2647
curr_file_size = f_save_size;
2653
/**************************************************************************/
2655
// save (modified) image to new version of same file
2656
// - no confirmation of overwrite.
2658
void m_savevers(GtkWidget *, cchar *) // new v.11.07
2660
char *outfile, *pext, *pvers;
2665
if (! curr_file) return;
2666
if (is_syncbusy()) return; // v.11.11
2668
zfuncs::F1_help_topic = "save_file";
2670
strcpy(jpeg_quality,def_jpeg_quality); // default jpeg save quality
2672
outfile = strdupz(curr_file,12,"curr_file"); // output file name TBD
2674
pext = strrchr(outfile,'/'); // find file .ext
2675
if (pext) pext = strrchr(pext,'.');
2676
if (pext && strlen(pext) > 5) pext = 0;
2677
if (! pext) pext = outfile + strlen(outfile); // unknown, none
2679
pvers = pext - 4; // find curr. version: filename.vNN.ext
2680
if (! strnEqu(pvers,".v",2)) nvers = 0;
2682
err = convSI(pvers+2,nvers,1,98,&delim); // convert NN to number 1-98
2683
if (err > 1) nvers = 0; // conversion error
2684
if (delim != pext) nvers = 0; // check format is .vNN
2686
if (nvers == 0) { // no version in file name
2689
memmove(pext,pvers,6); // make space for .vNN before .ext
2690
strncpy(pvers,".vNN",4);
2693
for (ii = 98; ii > nvers; ii--) // look for higher file versions
2695
pvers[2] = ii/10 + '0'; // build filename.vNN.ext
2696
pvers[3] = ii - 10 * (ii/10) + '0';
2697
err = stat(outfile,&fstat);
2701
ii++; // use next version 1-99
2703
pvers[2] = ii/10 + '0'; // build filename.vNN.ext
2704
pvers[3] = ii - 10 * (ii/10) + '0';
2706
f_save(outfile,curr_file_type,curr_file_bpc); // save file (fails at 99 versions)
2708
load_fileinfo(outfile); // update search index file
2709
update_search_index(outfile);
2711
image_gallery(outfile,"init"); // update gallery
2712
image_gallery(outfile,"paint2",-1); // refresh gallery window if active
2714
f_open(outfile,1,0,1); // new version = current file v.11.11.1
2721
/**************************************************************************/
2723
// save (modified) image to new file
2724
// confirm if overwrite existing file
2726
GtkWidget *saveas_fchooser;
2728
void m_saveas(GtkWidget *, cchar *menu)
2730
void saveas_radiobutt(void *, int button);
2731
void saveas_kbkey(void *, GdkEventKey *event);
2733
GtkWidget *fdialog, *hbox;
2734
GtkWidget *tiff8, *tiff16, *jpeg, *png, *jqlab, *jqval;
2735
GtkWidget *makecurrent;
2736
char *outfile = 0, *outfile2 = 0, *pext;
2737
int ii, err, yn, bpc, status, mkcurr = 0;
2740
cchar *exts = ".jpg.JPG.jpeg.JPEG.tif.TIF.tiff.TIFF.png.PNG";
2742
if (! curr_file) return;
2743
if (is_syncbusy()) return; // v.11.11
2745
if (menu) zfuncs::F1_help_topic = "save_file";
2747
fdialog = gtk_dialog_new_with_buttons(ZTX("Save File"), // build file save dialog
2748
MWIN, GTK_DIALOG_MODAL,
2749
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2750
GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, null);
2751
gtk_window_set_default_size(GTK_WINDOW(fdialog),600,500);
2753
saveas_fchooser = gtk_file_chooser_widget_new(GTK_FILE_CHOOSER_ACTION_SAVE);
2754
gtk_container_add(GTK_CONTAINER(GTK_DIALOG(fdialog)->vbox),saveas_fchooser);
2756
// set_filename() should select the file but does nothing GTK bug ? v.10.9.1 /////
2757
// set_current_name() puts file name in input box, but does not select the file
2758
gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(saveas_fchooser),curr_file);
2759
char *fname = strrchr(curr_file,'/') + 1;
2760
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(saveas_fchooser),fname);
2762
hbox = gtk_hbox_new(0,0);
2763
gtk_container_add(GTK_CONTAINER(GTK_DIALOG(fdialog)->vbox),hbox);
2764
gtk_box_set_child_packing(GTK_BOX(GTK_DIALOG(fdialog)->vbox),hbox,0,0,10,GTK_PACK_END);
2766
tiff8 = gtk_radio_button_new_with_label(null,"tiff-8");
2767
tiff16 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(tiff8),"tiff-16");
2768
png = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(tiff8),"png");
2769
jpeg = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(tiff8),"jpeg");
2770
jqlab = gtk_label_new(ZTX("quality"));
2771
jqval = gtk_entry_new();
2772
makecurrent = gtk_check_button_new_with_label(ZTX("make current"));
2774
gtk_entry_set_width_chars(GTK_ENTRY(jqval),2);
2775
gtk_box_pack_start(GTK_BOX(hbox),tiff8,0,0,5); // (o) tiff8 (o) tiff16 (o) png
2776
gtk_box_pack_start(GTK_BOX(hbox),tiff16,0,0,5);
2777
gtk_box_pack_start(GTK_BOX(hbox),png,0,0,5);
2778
gtk_box_pack_start(GTK_BOX(hbox),jpeg,0,0,5); // (o) jpeg jpeg quality [__]
2779
gtk_box_pack_start(GTK_BOX(hbox),jqlab,0,0,0);
2780
gtk_box_pack_start(GTK_BOX(hbox),jqval,0,0,3);
2781
gtk_box_pack_end(GTK_BOX(hbox),makecurrent,0,0,3); // [x] make current
2783
G_SIGNAL(tiff8,"pressed",saveas_radiobutt,0) // connect file type radio buttons
2784
G_SIGNAL(tiff16,"pressed",saveas_radiobutt,1) // to handler function
2785
G_SIGNAL(png,"pressed",saveas_radiobutt,2)
2786
G_SIGNAL(jpeg,"pressed",saveas_radiobutt,3)
2787
G_SIGNAL(fdialog,"key-release-event",saveas_kbkey,0)
2789
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(jpeg),1); // set default file type = jpeg
2790
gtk_entry_set_text(GTK_ENTRY(jqval),def_jpeg_quality); // default jpeg save quality
2792
if (strEqu(curr_file_type,"png")) // default matches file type
2793
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(png),1); // if png or tiff
2794
if (strEqu(curr_file_type,"tiff"))
2795
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tiff8),1);
2796
if (curr_file_bpc == 16)
2797
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tiff16),1);
2801
gtk_widget_show_all(fdialog); // run dialog
2803
status = gtk_dialog_run(GTK_DIALOG(fdialog));
2804
if (status != GTK_RESPONSE_ACCEPT) { // user cancelled
2805
gtk_widget_destroy(fdialog);
2809
outfile2 = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(saveas_fchooser));
2810
if (! outfile2) goto dialog_run;
2811
outfile = strdupz(outfile2,12,"curr_file"); // add space for possible .vNN and .ext
2814
type = "jpg"; // default output type
2817
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(png)))
2819
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tiff8)))
2821
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tiff16))) {
2826
if (strEqu(type,"jpg")) { // set jpeg save quality
2827
ii = atoi(gtk_entry_get_text(GTK_ENTRY(jqval)));
2828
if (ii < 1 || ii > 100) {
2829
zmessageACK(mWin,ZTX("jpeg quality must be 1-100"));
2832
sprintf(jpeg_quality,"%d",ii);
2835
pext = strrchr(outfile,'/'); // locate file .ext
2836
if (pext) pext = strrchr(pext,'.');
2837
if (pext && ! strstr(exts,pext)) pext = 0; // keep .ext and append new v.10.12.1
2839
pext = outfile + strlen(outfile); // missing, add one
2841
strcpy(pext+1,type);
2844
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(makecurrent))) // make saved file the current file?
2847
gtk_widget_destroy(fdialog); // kill dialog /// gtk bug, can crash
2849
err = stat(outfile,&fstat); // check if file exists
2851
yn = zmessageYN(mWin,ZTX("Overwrite file? \n %s"),outfile); // confirm overwrite
2858
f_save(outfile,type,bpc); // save the file
2860
load_fileinfo(outfile); // update search index file v.11.07
2861
update_search_index(outfile);
2863
if (mkcurr) // use the saved file as new current file
2864
f_open(outfile,1,0,1); // and retain edit history v.11.07
2865
else if (samedirk(outfile,curr_file)) { // if same directory, update gallery
2866
image_gallery(outfile,"init"); // add new file to gallery
2867
image_gallery(0,"paint2",curr_file_posn); // refresh if active, keep position
2875
// set dialog file type from user selection of file type radio button
2877
void saveas_radiobutt(void *, int button) // v.9.4
2879
cchar *filetypes[4] = { ".tif", ".tif", ".png", ".jpg" }; // v.10.9
2881
char *filename, *pp;
2883
filespec = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(saveas_fchooser));
2884
if (! filespec) return;
2885
filename = strrchr(filespec,'/');
2886
if (! filename) return;
2887
filename = strdupz(filename+1,6,"saveas");
2888
pp = strrchr(filename,'.');
2889
if (! pp || strlen(pp) > 5) pp = filename + strlen(filename); // bugfix v.10.9.1
2890
strcpy(pp,filetypes[button]);
2891
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(saveas_fchooser),filename);
2892
gtk_widget_show_all(saveas_fchooser);
2894
g_free(filespec); // bugfix, leak v.10.9.1
2899
// response function for KB key release
2901
void saveas_kbkey(void *, GdkEventKey *event) // v.10.5
2903
if (event->keyval == GDK_F1)
2904
showz_userguide(zfuncs::F1_help_topic);
2909
// set new image zoom level or magnification
2911
void m_zoom(GtkWidget *, cchar *menu)
2913
int ii, iww, ihh, Dww, Dhh;
2915
double scalew, scaleh, fitscale;
2916
double scales[11] = { 0.125, 0.176, 0.25, 0.354, 0.5, 0.71, 1.0, 1.41, 2.0, 2.83, 4.0 };
2918
if (strnEqu(menu,"Zoom",4)) zoom = menu[4]; // get + or -
2921
Dww = drWin->allocation.width; // drawing window size
2922
Dhh = drWin->allocation.height;
2924
if (E3pxm16) { // bugfix
2933
if (iww > Dww || ihh > Dhh) { // get window fit scale
2934
scalew = 1.0 * Dww / iww;
2935
scaleh = 1.0 * Dhh / ihh;
2936
if (scalew < scaleh) fitscale = scalew;
2937
else fitscale = scaleh;
2939
else fitscale = 1.0; // if image < window use 100%
2941
if (zoom == '+') { // zoom bigger
2942
if (! Fzoom) Fzoom = fitscale / 1.2;
2943
Fzoom = Fzoom * sqrt(2.0); // new scale: 41% bigger
2944
for (ii = 0; ii < 11; ii++)
2945
if (Fzoom < 1.01 * scales[ii]) break; // next higher scale in table
2946
if (ii == 11) ii = 10;
2948
if (Fzoom < fitscale) Fzoom = 0; // image < window
2951
if (zoom == '-') Fzoom = 0; // zoom to fit window
2954
if (Fzoom != 0) Fzoom = 0; // toggle 100% and fit window
2958
if (! Fzoom) zoomx = zoomy = 0; // no req. zoom center
2960
mwpaint2(); // refresh window
2961
curr_image_time = get_seconds(); // mark time of image change v.11.07
2966
/**************************************************************************/
2968
// create a new blank image with desired background color
2970
void m_create(GtkWidget *, cchar *) // v.11.01
2972
int create_dialog_event(zdialog *zd, cchar *event);
2976
char *prev_file = 0;
2978
zfuncs::F1_help_topic = "create";
2980
if (is_syncbusy()) return; // v.11.11
2981
if (mod_keep()) return; // unsaved edits
2982
if (! menulock(1)) return; // lock menus
2984
if (curr_file) prev_file = strdupz(curr_file);
2986
// file name [___________________________] .jpg // v.11.05
2987
// width [____] height [____] (pixels)
2990
zd = zdialog_new(ZTX("Create Blank Image"),mWin,Bdone,Bcancel,null);
2991
zdialog_add_widget(zd,"hbox","hbf","dialog",0,"space=5");
2992
zdialog_add_widget(zd,"label","labf","hbf",ZTX("file name"),"space=3");
2993
zdialog_add_widget(zd,"entry","file","hbf","no-name","space=3|expand");
2994
zdialog_add_widget(zd,"label","ftype","hbf",".jpg ");
2995
zdialog_add_widget(zd,"hbox","hbz","dialog",0,"space=5");
2996
zdialog_add_widget(zd,"label","space","hbz",0,"space=3");
2997
zdialog_add_widget(zd,"label","labw","hbz",ZTX("width"));
2998
zdialog_add_widget(zd,"spin","width","hbz","100|9999|1|1600");
2999
zdialog_add_widget(zd,"label","space","hbz",0,"space=5");
3000
zdialog_add_widget(zd,"label","labh","hbz",ZTX("height"));
3001
zdialog_add_widget(zd,"spin","height","hbz","100|9999|1|1000");
3002
zdialog_add_widget(zd,"label","space","hbz",0,"space=3");
3003
zdialog_add_widget(zd,"label","labp","hbz","(pixels) ");
3004
zdialog_add_widget(zd,"hbox","hbc","dialog",0,"space=5");
3005
zdialog_add_widget(zd,"label","space","hbc",0,"space=3");
3006
zdialog_add_widget(zd,"label","labc","hbc",ZTX("color"));
3007
zdialog_add_widget(zd,"colorbutt","color","hbc","200|200|200");
3009
create_dialog_event(zd,"init");
3011
zdialog_help(zd,"create"); // zdialog help topic v.11.08
3012
zdialog_run(zd,create_dialog_event);
3013
zstat = zdialog_wait(zd);
3016
if (zstat != 1) { // cancel
3018
f_open(prev_file,0); // back to previous file
3029
// dialog event and completion function
3031
int create_dialog_event(zdialog *zd, cchar *event) // overhauled v.11.05
3033
char color[20], fname[100], *filespec;
3035
int fncc, ww, hh, err;
3036
static int red, green, blue;
3040
if (zd->zstat != 1) return 0; // [done]
3042
zdialog_fetch(zd,"file",fname,100); // get new file name
3044
if (*fname <= ' ') strcpy(fname,"no-name");
3045
fncc = strlen(fname);
3047
filespec = strdupz(curr_dirk,fncc+8,"create"); // make full filespec
3048
strcat(filespec,"/");
3049
strcat(filespec,fname);
3050
strcat(filespec,".jpg");
3052
err = stat(filespec,&statb); // make sure it does not exist
3054
zmessageACK(mWin,"file already exists");
3056
zd->zstat = 0; // keep dialog alive
3060
if (curr_file) zfree(curr_file); // stop copy of metadata from old file
3061
curr_file = 0; // bugfix v.11.08
3063
zdialog_fetch(zd,"width",ww); // get image dimensions
3064
zdialog_fetch(zd,"height",hh);
3066
zdialog_fetch(zd,"color",color,19); // get image color
3067
pp = strField(color,"|",1);
3068
if (pp) red = atoi(pp);
3069
pp = strField(color,"|",2);
3070
if (pp) green = atoi(pp);
3071
pp = strField(color,"|",3);
3072
if (pp) blue = atoi(pp);
3074
mutex_lock(&Fpixmap_lock); // lock pixmaps
3076
PXM_free(Fpxm8); // create new PXM image
3077
Fpxm8 = PXM_make(ww,hh,8);
3080
pixel = (uint8 *) Fpxm8->bmp;
3082
for (int ii = 0; ii < ww * hh * 3; ii += 3) {
3084
pixel[ii+1] = green;
3088
mutex_unlock(&Fpixmap_lock);
3090
strcpy(jpeg_quality,def_jpeg_quality);
3092
err = f_save(filespec,"jpg",8); // save to disk
3094
zmessageACK(mWin,"cannot save file");
3099
f_open(filespec,0); // make it the current file
3106
/**************************************************************************/
3108
// Delete image file - move curr_file to trash.
3109
// Use new Linux standard trash function. // v.10.8
3110
// If not available, revert to Desktop folder for fotoxx trash.
3112
void m_trash(GtkWidget *, cchar *)
3115
char *pp, trashdir[200];
3117
static int gstat, trashworks = 1; // assume it works until otherwise
3120
cchar *gerrmess = ZTX("Linux standard trash is not supported. \n"
3121
"Desktop trash folder will be created.");
3123
zfuncs::F1_help_topic = "trash"; // v.10.8
3125
if (! curr_file) return; // nothing to trash
3127
if (is_syncbusy()) return; // v.11.11
3128
if (! menulock(1)) return; // check lock but leave unlocked
3131
err = stat(curr_file,&trstat); // get file status
3133
zmessLogACK(mWin,strerror(errno));
3137
if (! (trstat.st_mode & S_IWUSR)) { // check permission
3138
yn = zmessageYN(mWin,ZTX("Move read-only file to trash?"));
3140
trstat.st_mode |= S_IWUSR;
3141
chmod(curr_file,trstat.st_mode);
3144
if (trashworks) // try Linux standard trash
3146
gfile = g_file_new_for_path(curr_file);
3147
gstat = g_file_trash(gfile,0,&gerror);
3149
printf("g_file_trash() error: %s \n",gerror->message);
3150
zmessageACK(mWin,gerrmess);
3151
trashworks = 0; // did not work
3157
snprintf(trashdir,199,"%s/%s",getenv("HOME"),ftrash); // use fotoxx trash filespec
3160
err = stat(trashdir,&trstat);
3161
if (! S_ISDIR(trstat.st_mode)) {
3162
err = mkdir(trashdir,0750); // v.11.03
3164
zmessLogACK(mWin,ZTX("Cannot create trash folder: %s"),wstrerror(err));
3169
snprintf(command,ccc,"cp \"%s\" \"%s\" ",curr_file,trashdir); // copy image file to trash
3170
err = system(command);
3172
zmessLogACK(mWin,ZTX("error: %s"),wstrerror(err));
3176
err = remove(curr_file); // remove original file v.11.03
3178
zmessLogACK(mWin,ZTX("error: %s"),wstrerror(err));
3183
delete_search_index(curr_file); // delete in search index file
3184
image_gallery(0,"delete",curr_file_posn); // delete in gallery list
3185
pp = image_gallery(0,"find",curr_file_posn); // open next file (now current position)
3186
if (pp) f_open(pp,1); // v.11.05
3188
image_gallery(0,"paint2",curr_file_posn); // refresh gallery window if active v.11.07
3194
/**************************************************************************/
3196
// rename menu function
3197
// activate rename dialog, stuff data from current file
3198
// dialog remains active when new file is opened
3200
char rename_old[100] = "";
3201
char rename_new[100] = "";
3203
void m_rename(GtkWidget *, cchar *menu)
3205
int rename_dialog_event(zdialog *zd, cchar *event);
3207
char *pdir, *pfile, *pext;
3209
if (menu) zfuncs::F1_help_topic = "rename";
3211
if (! curr_file) return;
3212
if (is_syncbusy()) return; // v.11.11
3214
if (! zdrename) // restart dialog
3216
zdrename = zdialog_new(ZTX("Rename Image File"),mWin,Bcancel,null);
3217
zdialog_add_widget(zdrename,"hbox","hb1","dialog",0,"space=10");
3218
zdialog_add_widget(zdrename,"vbox","vb1","hb1",0,"homog|space=5");
3219
zdialog_add_widget(zdrename,"vbox","vb2","hb1",0,"homog|expand");
3221
zdialog_add_widget(zdrename,"button","Bold","vb1",ZTX("old name"));
3222
zdialog_add_widget(zdrename,"button","Bnew","vb1",ZTX("rename to"));
3223
zdialog_add_widget(zdrename,"button","Bprev","vb1",ZTX("previous"));
3225
zdialog_add_widget(zdrename,"hbox","hb21","vb2",0); // [ old name ] [ oldname ]
3226
zdialog_add_widget(zdrename,"hbox","hb22","vb2",0); // [ new name ] [ newname ] [+1]
3227
zdialog_add_widget(zdrename,"hbox","hb23","vb2",0); // [ previous ] [ prevname ]
3229
zdialog_add_widget(zdrename,"label","Lold","hb21");
3230
zdialog_add_widget(zdrename,"entry","Enew","hb22",0,"expand|scc=30");
3231
zdialog_add_widget(zdrename,"button","B+1","hb22"," +1 ","space=5");
3232
zdialog_add_widget(zdrename,"label","Lprev","hb23");
3234
zdialog_help(zdrename,"rename"); // zdialog help topic v.11.08
3235
zdialog_run(zdrename,rename_dialog_event); // run dialog
3238
parsefile(curr_file,&pdir,&pfile,&pext);
3239
strncpy0(rename_old,pfile,99);
3240
strncpy0(rename_new,pfile,99);
3241
zdialog_stuff(zdrename,"Lold",rename_old); // current file name
3242
zdialog_stuff(zdrename,"Enew",rename_new); // entered file name
3248
// dialog event and completion callback function
3250
int rename_dialog_event(zdialog *zd, cchar *event)
3252
char *pp, *pdir, *pfile, *pext, *pnew, *pold;
3253
int nseq, digits, ccp, ccn, ccx, err;
3256
if (zd->zstat) { // complete
3257
zdialog_free(zdrename); // kill dialog
3261
if (strEqu(event,"Bold")) // reset to current file name
3262
zdialog_stuff(zd,"Enew",rename_old);
3264
if (strEqu(event,"Bprev")) { // previous name >> new name
3265
zdialog_fetch(zd,"Lprev",rename_new,99);
3266
zdialog_stuff(zd,"Enew",rename_new);
3269
if (strEqu(event,"B+1")) // increment sequence number
3271
zdialog_fetch(zd,"Enew",rename_new,94); // get entered filename
3272
pp = rename_new + strlen(rename_new);
3274
while (pp[-1] >= '0' && pp[-1] <= '9') {
3275
pp--; // look for NNN in filenameNNN
3278
nseq = 1 + atoi(pp); // NNN + 1
3279
if (nseq > 9999) nseq = 0;
3280
if (digits < 2) digits = 2; // keep digit count if enough
3281
if (nseq > 99 && digits < 3) digits = 3; // use leading zeros
3282
if (nseq > 999 && digits < 4) digits = 4;
3283
snprintf(pp,digits+1,"%0*d",digits,nseq);
3284
zdialog_stuff(zd,"Enew",rename_new);
3287
if (strEqu(event,"Bnew")) // [rename to] button
3289
if (is_syncbusy()) return 0; // v.11.11
3290
if (! menulock(1)) return 0; // check lock but leave unlocked
3293
parsefile(curr_file,&pdir,&pfile,&pext); // existing /directories/file.ext
3295
zdialog_fetch(zd,"Enew",rename_new,94); // new file name from user
3297
ccp = strlen(pdir); // length of /directories/
3298
ccn = strlen(rename_new); // length of file
3299
if (pext) ccx = strlen(pext); // length of .ext
3302
pnew = zmalloc(ccp + ccn + ccx + 1,"rename"); // put it all together
3303
strncpy(pnew,curr_file,ccp); // /directories/file.ext
3304
strcpy(pnew+ccp,rename_new);
3305
if (ccx) strcpy(pnew+ccp+ccn,pext);
3307
err = stat(pnew,&statb); // check if new name exists
3309
zmessageACK(mWin,ZTX("The target file already exists"));
3314
snprintf(command,ccc,"cp -p \"%s\" \"%s\"",curr_file,pnew); // copy to new file -p v.10.3
3315
err = system(command);
3317
zmessageACK(mWin,ZTX("Rename failed: \n %s"),wstrerror(err));
3318
printf("command: %s \n",command);
3323
zdialog_stuff(zd,"Lprev",rename_new); // set previous name in dialog
3325
load_fileinfo(pnew); // update search index file
3326
update_search_index(pnew);
3328
pold = strdupz(curr_file,0,"curr_file"); // save file name to be deleted
3329
delete_search_index(pold); // delete in search index v.9.7
3330
err = remove(pold); // delete file v.11.03
3332
err = f_open(pnew,1); // no automatic "next" v.11.08
3333
image_gallery(curr_file,"init"); // update gallery v.11.05
3334
image_gallery(0,"paint2",curr_file_posn); // refresh gallery window if active
3344
/**************************************************************************/
3346
// menu function - batch rename files
3348
char **batchrename_filelist = 0;
3349
int batchrename_filecount = 0;
3351
void m_batchrename(GtkWidget *, cchar *) // new v.9.7
3353
int batchrename_dialog_event(zdialog *zd, cchar *event);
3357
if (zdrename) return; // interactive rename is active
3359
if (is_syncbusy()) return; // v.11.11
3360
if (mod_keep()) return; // unsaved edits
3361
if (! menulock(1)) return; // lock menus v.10.8
3363
zfuncs::F1_help_topic = "batch_rename"; // v.10.8
3365
zd = zdialog_new(ZTX("Batch Rename"),mWin,Bproceed,Bcancel,null);
3366
zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=5");
3367
zdialog_add_widget(zd,"button","files","hb1",Bselectfiles,"space=10");
3368
zdialog_add_widget(zd,"label","labcount","hb1",ZTX("%d files selected"),"space=10");
3369
zdialog_add_widget(zd,"hbox","hb2","dialog","space=5");
3370
zdialog_add_widget(zd,"label","lab2","hb2",ZTX("new base name"),"space=10");
3371
zdialog_add_widget(zd,"entry","basename","hb2");
3372
zdialog_add_widget(zd,"hbox","hb3","dialog",0,"space=5");
3373
zdialog_add_widget(zd,"label","lab31","hb3",ZTX("starting sequence"),"space=10");
3374
zdialog_add_widget(zd,"entry","sequence","hb3","100","scc=5");
3375
zdialog_add_widget(zd,"label","lab32","hb3",ZTX("increment"),"space=10");
3376
zdialog_add_widget(zd,"entry","increment","hb3","01","scc=3");
3378
batchrename_filelist = 0;
3379
batchrename_filecount = 0;
3381
zdialog_help(zd,"batch_rename"); // zdialog help topic v.11.08
3382
zdialog_run(zd,batchrename_dialog_event); // run dialog
3383
zdialog_wait(zd); // wait for completion
3391
// dialog event and completion callback function
3393
int batchrename_dialog_event(zdialog *zd, cchar *event)
3395
char **flist = batchrename_filelist, countmess[50];
3396
cchar *selectmess = ZTX("select files to rename");
3397
int ii, err, cc, ccp, ccn, ccx;
3398
int sequence, increment, adder;
3399
char basename[100], filename[120], *oldfile, *newfile;
3400
char *pdir, *pfile, *pext;
3401
cchar *errmess = ZTX("base name / sequence / increment not reasonable");
3404
if (strEqu(event,"files")) // select images to rename
3406
if (flist) { // free prior list
3407
for (ii = 0; flist[ii]; ii++)
3412
flist = zgetfileN(selectmess,"openN",curr_file); // get file list from user
3413
batchrename_filelist = flist;
3415
if (flist) // count files in list
3416
for (ii = 0; flist[ii]; ii++);
3418
batchrename_filecount = ii;
3420
snprintf(countmess,50,ZTX("%d files selected"),batchrename_filecount);
3421
zdialog_stuff(zd,"labcount",countmess);
3424
if (! zd->zstat) return 0; // dialog active
3426
if (zd->zstat != 1) goto cleanup; // dialog canceled
3427
if (! batchrename_filecount) goto cleanup; // no files selected
3429
zdialog_fetch(zd,"basename",basename,99);
3430
zdialog_fetch(zd,"sequence",sequence);
3431
zdialog_fetch(zd,"increment",increment);
3433
if (strlen(basename) < 1 || sequence < 1 || increment < 1) {
3434
zd->zstat = 0; // keep dialog alive bugfix v.10.7
3435
zmessageACK(mWin,errmess);
3439
write_popup_text("open","Renaming files",500,200,mWin); // status monitor popup window v.10.3
3441
for (ii = 0; flist[ii]; ii++)
3443
oldfile = flist[ii];
3444
parsefile(oldfile,&pdir,&pfile,&pext);
3446
if (pext) ccx = strlen(pext);
3449
adder = sequence + ii * increment;
3450
snprintf(filename,119,"%s%d",basename,adder); // removed "-" between v.10.7
3451
ccn = strlen(filename);
3453
newfile = zmalloc(ccp + ccn + ccx + 1,"rename"); // construct /path/filename.ext
3454
strcpy(newfile,pdir);
3455
strcpy(newfile+ccp,filename);
3456
if (ccx) strcpy(newfile+ccp+ccn,pext);
3458
err = stat(newfile,&statb);
3460
snprintf(command,ccc,"%s %s",ZTX("new file already exists:"),newfile);
3461
write_popup_text("write",command);
3466
cc = snprintf(command,ccc,"cp -p \"%s\" \"%s\"",oldfile,newfile); // copy to new file -p v.10.3
3467
if (cc >= maxfcc*2) {
3468
snprintf(command,ccc,"%s %s",ZTX("filespec too long:"),oldfile);
3469
write_popup_text("write",command);
3474
write_popup_text("write",command); // report progress
3477
err = system(command);
3479
snprintf(command,ccc,"%s %s",ZTX("Rename failed:"),wstrerror(err));
3480
write_popup_text("write",command);
3485
load_fileinfo(newfile); // update search index file
3486
update_search_index(newfile);
3489
err = remove(oldfile); // delete old file v.11.03
3490
delete_search_index(oldfile); // remove from search index
3494
write_popup_text("write","COMPLETED");
3495
write_popup_text("close",0);
3497
image_gallery(curr_file,"init"); // update gallery file list
3498
image_gallery(0,"paint2",curr_file_posn); // refresh gallery window if active
3502
if (batchrename_filecount) {
3503
for (ii = 0; flist[ii]; ii++)
3512
/**************************************************************************/
3514
// print current image file
3516
double print_size[2] = { 29.7, 21.0 }; // default A4 landscape
3517
double print_margins[4] = { 0.5, 0.5, 0.5, 0.5 }; // top, bottom, left, right
3519
void m_print(GtkWidget *, cchar *) // use GTK print v.9.4
3521
PXM * print_addgrid(PXM *printimage); // v.11.01
3525
PXM *printimage, *pxmtemp;
3527
zfuncs::F1_help_topic = "print";
3529
if (! curr_file) return; // no image file
3531
printfile = strdupz(get_zuserdir(),20,"printfile"); // make temp print file:
3532
strcat(printfile,"/printfile.tif"); // ~/.fotoxx/printfile.tif v.11.03
3534
if (Fpxm16) printimage = PXM_convbpc(Fpxm16);
3535
else printimage = PXM_copy(Fpxm8);
3537
pxmtemp = print_addgrid(printimage); // add grid lines if wanted v.11.01
3539
PXM_free(printimage);
3540
printimage = pxmtemp;
3543
err = PXBwrite(printimage,printfile);
3544
PXM_free(printimage);
3547
zfree(printfile); // v.10.3
3551
print_image_paper_setup(); // new v.11.10
3552
print_image_margins_setup();
3553
print_image_file(printfile);
3560
// add grid lines to print image if wanted
3562
PXM * print_addgrid(PXM *printimage) // v.11.01
3566
int px, py, ww, hh, row;
3567
int startx, starty, stepx, stepy;
3569
if (! Fgrid) return 0; // grid lines off
3571
temp8 = f_load(curr_file,8);
3572
if (! temp8) return 0;
3578
stepx = gridspace[0]; // space between grid lines
3579
stepy = gridspace[1];
3581
stepx = stepx / Mscale; // window scale to image scale
3582
stepy = stepy / Mscale;
3584
if (gridcount[0]) stepx = ww / (1 + gridcount[0]); // if line counts specified,
3585
if (gridcount[1]) stepy = hh / ( 1 + gridcount[1]); // set spacing accordingly
3587
startx = stepx * gridoffset[0] / 100; // variable offsets v.11.11
3588
if (startx < 0) startx += stepx;
3590
starty = stepy * gridoffset[1] / 100;
3591
if (starty < 0) starty += stepy;
3594
for (px = startx; px < ww-1; px += stepx)
3595
for (py = 0; py < hh; py++)
3597
pixel = PXMpix8(temp8,px,py); // adjoining white and black lines
3598
pixel[0] = pixel[1] = pixel[2] = 255;
3599
pixel[3] = pixel[4] = pixel[5] = 0;
3604
for (py = starty; py < hh-1; py += stepy)
3605
for (px = 0; px < ww; px++)
3607
pixel = PXMpix8(temp8,px,py);
3608
pixel[0] = pixel[1] = pixel[2] = 255;
3609
pixel[row] = pixel[row+1] = pixel[row+2] = 0;
3617
/**************************************************************************/
3619
// normal quit menu function
3621
void m_quit(GtkWidget *, cchar *)
3623
if (mod_keep()) return; // unsaved edits
3625
Fshutdown++; // bugfix v.10.11
3627
for (int ii = 0; ii < 100; ii++) // wait up to a second if something running
3628
if (Ffuncbusy) { // v.11.01
3633
if (Ffuncbusy) printf("busy function killed"); // v.11.05
3635
gtk_window_get_position(MWIN,&mwgeom[0],&mwgeom[1]); // get last window position v.11.07
3636
gtk_window_get_size(MWIN,&mwgeom[2],&mwgeom[3]); // and size for next session
3637
save_params(); // save state for next session
3638
zdialog_positions("save"); // save dialog positions too v.11.07
3639
free_resources(); // delete temp files
3640
if (KBzmalloclog) zmalloc_report(); // report memory v.10.8
3641
fflush(null); // flush stdout, stderr v.11.05
3642
gtk_main_quit(); // gone forever
3647
/**************************************************************************
3648
plugin menu functions
3649
**************************************************************************/
3651
// process plugin menu selection
3652
// execute correspinding command using current image file
3656
void m_run_plugin(GtkWidget *, cchar *menu) // v.11.03
3659
char *pp = 0, pluginfile[200];
3662
zfuncs::F1_help_topic = "plugins";
3664
for (ii = 0; ii < Nplugins; ii++) // search plugins for menu name
3666
pp = strstr(plugins[ii]," = ");
3669
jj = strEqu(plugins[ii],menu);
3674
if (ii == Nplugins) {
3675
zmessageACK(mWin,"plugin menu not found %s",menu);
3680
if (strlen(pp) < 3) {
3681
zmessageACK(mWin,"no plugin command");
3685
strncpy0(command,pp,ccc); // corresp. command
3688
EFplugin.funcname = menu;
3689
if (! edit_setup(EFplugin)) return; // setup edit
3691
snprintf(pluginfile,199,"%s/plugfile.tif",get_zuserdir()); // /home/user/.fotoxx/plugfile.tif
3693
TIFFwrite(E1pxm16,pluginfile); // E1 >> plugin_file
3695
strcat(command," "); // construct: command /.../plugin_file &
3696
strcat(command,pluginfile);
3697
printf("plugin: %s \n",command);
3699
err = system(command); // execute plugin command
3701
zmessageACK(mWin,"plugin command error");
3702
edit_cancel(EFplugin);
3706
pxmtemp = TIFFread(pluginfile); // read command output file
3708
zmessageACK(mWin,"plugin failed");
3709
edit_cancel(EFplugin);
3713
PXM_free(E3pxm16); // plugin_file >> E3
3714
if (pxmtemp->bpc == 16) E3pxm16 = pxmtemp;
3716
E3pxm16 = PXM_convbpc(pxmtemp);
3720
EFplugin.Fmod = 1; // assume image was modified
3721
edit_done(EFplugin);
3727
// edit plugins menu
3729
void m_edit_plugins(GtkWidget *, cchar *) // v.11.03
3731
int edit_plugins_event(zdialog *zd, cchar *event);
3737
zfuncs::F1_help_topic = "plugins";
3739
zd = zdialog_new("Edit Plugins",mWin,ZTX("Add"),ZTX("Remove"),Bdone,null);
3740
zdialog_add_widget(zd,"hbox","hbm","dialog",0,"space=5");
3741
zdialog_add_widget(zd,"label","labm","hbm",ZTX("menu name"),"space=5");
3742
zdialog_add_widget(zd,"comboE","menu","hbm",0,"space=5");
3743
zdialog_add_widget(zd,"hbox","hbc","dialog",0,"space=5");
3744
zdialog_add_widget(zd,"label","labc","hbc","command","space=5");
3745
zdialog_add_widget(zd,"entry","command","hbc",0,"space=5|expand");
3747
for (ii = 0; ii < Nplugins; ii++) // stuff combo box with available
3748
{ // plugin menu names
3749
pp = strstr(plugins[ii]," = ");
3752
zdialog_cb_app(zd,"menu",plugins[ii]);
3756
zdialog_help(zd,"plugins"); // zdialog help topic v.11.08
3757
zdialog_run(zd,edit_plugins_event);
3762
// dialog event function
3764
int edit_plugins_event(zdialog *zd, cchar *event)
3766
int ii, jj, cc, zstat;
3767
char menu[40], *pp = 0;
3769
zdialog_fetch(zd,"menu",menu,40); // selected menu
3770
zdialog_fetch(zd,"command",command,ccc);
3772
if (strEqu(event,"menu"))
3774
for (ii = 0; ii < Nplugins; ii++)
3776
pp = strstr(plugins[ii]," = "); // find corresp. plugin record
3779
jj = strEqu(plugins[ii],menu);
3784
if (ii == Nplugins) return 0;
3787
if (strlen(pp) < 3) return 0;
3788
strncpy0(command,pp,ccc);
3789
zdialog_stuff(zd,"command",command); // stuff corresp. command in dialog
3795
if (! zstat) return 0;
3797
if (zstat == 1) // add new plugin
3799
if (strlen(menu) < 3 || strlen(command) < 3) return 0;
3800
if (Nplugins == maxplugins) {
3801
zmessageACK(mWin,"too many plugins");
3804
ii = Nplugins; // add plugin record
3805
cc = strlen(menu) + strlen(command) + 5;
3806
plugins[ii] = zmalloc(cc,"plugins"); // format: menu = command
3807
strcpy(plugins[ii],menu);
3808
strcat(plugins[ii]," = ");
3809
strcat(plugins[ii],command);
3812
zmessageACK(mWin,ZTX("Restart Fotoxx to update plugin menu"));
3815
if (zstat == 2) // remove current plugin
3817
for (ii = 0; ii < Nplugins; ii++)
3819
pp = strstr(plugins[ii]," = "); // find corresp. plugin record
3822
jj = strEqu(plugins[ii],menu);
3827
if (ii == Nplugins) return 0;
3829
Nplugins--; // remove plugin record
3830
for (jj = ii; jj < Nplugins; jj++)
3831
plugins[jj] = plugins[jj+1];
3833
zmessageACK(mWin,ZTX("Restart Fotoxx to update plugin menu"));
3836
if (zstat == 3) { // done
3845
/**************************************************************************
3847
edit transaction and thread support functions
3849
edit transaction management
3850
edit_setup() start new edit - copy E3 > E1
3851
edit_cancel() cancel edit - E1 > E3, delete E1
3852
edit_done() commit edit - add to undo stack
3853
edit_undo() undo edit - E1 > E3
3854
edit_redo() redo edit - run thread again
3855
edit_fullsize() convert preview to full-size pixmaps
3857
main level thread management
3858
start_thread(func,arg) start thread running
3859
signal_thread() signal thread that work is pending
3860
wait_thread_idle() wait for pending work complete
3861
wrapup_thread(command) wait for exit or command thread exit
3864
thread_idle_loop() wait for pending work, exit if commanded
3865
thread_exit() exit thread unconditionally
3867
thread_status (thread ownership
3868
0 no thread is running
3869
1 thread is running and idle (no work)
3873
thread_command (main program ownership)
3874
0 idle, no work pending
3875
8 exit when pending work is done
3876
9 exit now, unconditionally
3878
thread_pend work requested counter
3879
thread_done work done counter
3880
thread_hiwater high water mark
3881
edit_action done/cancel/undo/redo in progress
3883
***************************************************************************/
3885
int thread_command = 0, thread_status = 0;
3886
int thread_pend = 0, thread_done = 0, thread_hiwater = 0;
3887
int edit_action = 0;
3890
/**************************************************************************
3892
Setup for a new edit transaction
3893
Create E1 (edit input) and E3 (edit output) pixmaps from
3894
previous edit output (Fpxm16) or image file (new Fpxm16).
3896
Fprev 0 edit full-size image
3897
1 edit preview image unless select area exists
3899
Farea 0 select_area is invalid and will be deleted (e.g. rotate)
3900
1 select_area not used but remains valid (e.g. red-eye)
3901
2 select_area can be used and remains valid (e.g. flatten)
3903
***************************************************************************/
3905
int edit_setup(editfunc &EF) // support parallel edits v.11.07
3910
if (! curr_file) return 0; // no image file
3912
if (is_syncbusy()) return 0; // must wait for file sync v.11.11
3914
if (! Fpxm16) { // no 16-bit image from prior edit
3915
pxmtemp = f_load(curr_file,16); // check that file can be loaded
3917
printf("f_load (16) failed: %s \n",curr_file);
3922
if (CEF) { // prior edit function active v.11.07
3923
if (&EF == CEF) return 0; // 2nd instance of one function
3924
if (EF.Fpara && CEF->Fpara) // check if parallel edits are OK
3925
edit_suspend(); // suspend prior edit
3927
zmessageACK(mWin,ZTX("cannot parallel edit"));
3931
else { // if this is 1st active edit
3932
if (! menulock(1)) return 0; // test menu lock is allowed
3933
menulock(0); // but don't lock yet v.11.07
3936
if (! Fexiftool && ! Fexifwarned) {
3937
zmessageACK(mWin,ZTX("exiftool is not installed \n" // warn if starting to edit
3938
"edited images will lose EXIF data")); // and exiftool is missing
3942
if (Pundo > maxedits-2) { // undo capacity reached
3943
zmessageACK(mWin,ZTX("Too many edits, please save image"));
3947
if (EF.Farea == 0 && sa_stat) { // select area will be lost, warn user
3948
yn = zmessageYN(mWin,ZTX("Select area cannot be kept.\n"
3951
sa_unselect(); // unselect area
3952
zdialog_free(zdsela);
3955
if (EF.Farea == 2 && sa_stat && ! Factivearea) { // select area exists and can be used,
3956
yn = zmessageYN(mWin,ZTX("Select area not active.\n" // but not active, ask user
3961
if (! menulock(1)) return 0; // can now commit, lock menu v.11.07
3963
Fpreview = 0; // use preview image if supported
3964
if (EF.Fprev && ! (EF.Farea == 2 && Factivearea)) { // and select area will not be used
3965
Fpreview = 1; // use preview image (smaller)
3966
sa_show(0); // hide area if present
3967
curr_image_time = get_seconds(); // mark time of file load v.11.07
3969
Fzoom = 0; // bugfix, mwpaint() req. v.11.01
3974
mutex_lock(&Fpixmap_lock); // lock pixmaps
3976
if (! Fpxm16) Fpxm16 = pxmtemp; // create Fpxm16 if not already
3978
PXM_fixblue(Fpxm16); // blue=0 >> blue=2 for vpixel() v.11.07
3980
if (Fpreview) // edit pixmaps are window-size
3981
E1pxm16 = PXM_rescale(Fpxm16,dww,dhh); // E1pxm16 = Fpxm16 scaled to window
3982
else E1pxm16 = PXM_copy(Fpxm16); // edit pixmaps are full-size
3983
E3pxm16 = PXM_copy(E1pxm16); // E1 >> E3
3985
E1ww = E3ww = E1pxm16->ww; // E1 and E3 pixmap dimensions
3986
E1hh = E3hh = E1pxm16->hh;
3988
mutex_unlock(&Fpixmap_lock);
3990
if (Pundo == 0) save_undo("initial"); // initial image >> undo stack
3992
CEF = &EF; // set current edit function
3993
CEF->Fmod = 0; // image not modified yet
3995
thread_command = thread_status = 0; // no thread running
3996
thread_pend = thread_done = thread_hiwater = 0; // no work pending or done
3997
if (EF.threadfunc) start_thread(EF.threadfunc,0); // start edit thread
4000
mwpaint1(); // mwpaint1() not mwpaint2()
4005
/**************************************************************************/
4007
// suspend a parallel edit function so it can be resumed
4008
// edit dialog and curve edit are left active
4009
// logically the equivalent of edit_done()
4013
wait_thread_idle(); // wait for thread to finish
4015
if (Fpreview && CEF->Fmod) { // preview image was edited
4017
edit_fullsize(); // update full image
4020
wrapup_thread(8); // tell thread to finish and exit
4022
mutex_lock(&Fpixmap_lock); // lock pixmaps
4024
if (CEF->Fmod) { // image was modified
4025
Fmodified++; // overall mod counter
4027
Fpxm16 = E3pxm16; // E3 >> Fpxm16 v.11.07
4030
Fpxm8 = PXM_convbpc(Fpxm16); // Fpxm16 >> Fpxm8
4033
Pundo++; // save next undo state
4035
save_undo(CEF->funcname);
4038
PXM_free(E1pxm16); // free edit pixmaps
4040
PXM_free(ERpxm16); // free redo copy
4041
E1ww = E3ww = ERww = 0;
4043
mutex_unlock(&Fpixmap_lock);
4045
CEF = 0; // no current edit func
4052
/**************************************************************************/
4054
// process edit cancel
4056
void edit_cancel(editfunc &EF)
4058
if (edit_action) return;
4059
edit_action++; // stop reentry
4061
if (&EF == CEF) // current edit is canceled v.11.07
4062
wrapup_thread(9); // tell thread to quit, wait
4064
if (EF.mousefunc == mouseCBfunc) // if my mouse, free mouse
4066
if (EF.zd) zdialog_free(EF.zd); // kill dialog
4067
if (EF.curves) zfree(EF.curves); // free curves data
4071
if (&EF != CEF) { // inactive edit canceled v.11.07
4076
mutex_lock(&Fpixmap_lock);
4077
PXM_free(E1pxm16); // free edit pixmaps E1, E3
4079
PXM_free(ERpxm16); // free redo copy v.10.3
4080
E1ww = E3ww = ERww = 0;
4081
mutex_unlock(&Fpixmap_lock);
4083
if (Fpreview) curr_image_time = get_seconds(); // mark time of file load v.11.07
4084
Fzoom = 0; // v.11.07
4085
CEF = 0; // no current edit func
4086
Fpreview = 0; // no preview mode
4087
menulock(0); // unlock menu
4088
mwpaint2(); // refresh window
4094
/**************************************************************************/
4096
// process edit dialog [done]
4097
// E3pxm16 >> Fpxm16 >> Fpxm8
4099
void edit_done(editfunc &EF)
4101
if (edit_action) return;
4102
edit_action++; // stop reentry
4104
if (&EF == CEF) { // current edit is done v.11.07
4105
wait_thread_idle(); // wait for thread to finish
4106
if (Fpreview && CEF->Fmod) { // preview image was edited
4108
edit_fullsize(); // update full image
4110
wrapup_thread(8); // tell thread to finish and exit
4113
if (EF.mousefunc == mouseCBfunc) // if my mouse, free mouse
4115
if (EF.zd) zdialog_free(EF.zd); // kill dialog
4116
if (EF.curves) zfree(EF.curves); // free curves data
4120
if (&EF != CEF) { // inactive edit is done v.11.07
4125
mutex_lock(&Fpixmap_lock);
4127
if (CEF->Fmod) { // image was modified
4128
Fmodified++; // overall mod counter
4130
Fpxm16 = E3pxm16; // E3 >> Fpxm16 v.11.07
4133
Fpxm8 = PXM_convbpc(Fpxm16); // Fpxm16 >> Fpxm8
4138
save_undo(CEF->funcname); // save next undo state
4141
PXM_free(E1pxm16); // free edit pixmaps
4143
PXM_free(ERpxm16); // free redo copy v.10.3
4144
E1ww = E3ww = ERww = 0;
4146
mutex_unlock(&Fpixmap_lock);
4148
CEF = 0; // no current edit func
4149
Fpreview = 0; // no preview mode
4150
menulock(0); // unlock menu
4151
mwpaint2(); // update window
4157
/**************************************************************************/
4159
// Setup for edit dialog event or curve edit.
4160
// Call this function before performing any image edit
4161
// (switch current edit function to caller's edit function).
4163
void edit_takeover(editfunc &EF) // v.11.07
4165
static int busy = 0;
4167
if (busy++) return; // KB key bounce
4168
if (CEF && &EF != CEF) { // edit function changed
4169
edit_setup(EF); // close old and setup new edit
4170
if (EF.zd) zdialog_send_event(EF.zd,"reset"); // reset dialog controls v.11.08
4171
freeMouse(); // free mouse from old dialog v.11.08
4178
/**************************************************************************/
4180
// edit undo, redo, reset functions
4181
// these apply within an active edit function
4185
if (thread_status == 2) return; // thread busy
4186
if (! CEF->Fmod) return; // not modified
4187
if (edit_action) return;
4188
edit_action++; // stop reentry
4190
mutex_lock(&Fpixmap_lock);
4191
PXM_free(ERpxm16); // E3 >> redo copy
4195
E3pxm16 = PXM_copy(E1pxm16); // E1 >> E3
4198
mutex_unlock(&Fpixmap_lock);
4200
CEF->Fmod = 0; // reset image modified status
4201
mwpaint2(); // refresh window
4209
if (thread_status == 2) return; // thread busy
4210
if (! ERpxm16) return; // no prior undo
4211
if (edit_action) return;
4212
edit_action++; // stop reentry
4214
mutex_lock(&Fpixmap_lock);
4215
PXM_free(E3pxm16); // redo copy >> E3
4220
mutex_unlock(&Fpixmap_lock);
4222
CEF->Fmod = 1; // image modified
4223
Fmodified++; // overall mod counter
4232
if (thread_status == 2) return; // thread busy
4233
if (! CEF->Fmod) return; // not modified
4234
if (edit_action) return;
4235
edit_action++; // stop reentry
4237
mutex_lock(&Fpixmap_lock);
4238
PXM_free(ERpxm16); // delete redo copy
4240
E3pxm16 = PXM_copy(E1pxm16); // E1 >> E3
4243
mutex_unlock(&Fpixmap_lock);
4245
CEF->Fmod = 0; // reset image modified status
4246
mwpaint2(); // refresh window
4254
PXM_free(ERpxm16); // no redo copy
4259
/**************************************************************************/
4261
// Convert from preview mode (window-size pixmaps) to full-size pixmaps.
4262
// Can only be used when edit function is in an edit thread with idle_loop
4263
// This function is called directly from some edit functions
4265
void edit_fullsize()
4267
mutex_lock(&Fpixmap_lock);
4268
PXM_free(E1pxm16); // free preview pixmaps
4270
E1pxm16 = PXM_copy(Fpxm16); // make full-size pixmaps
4271
E3pxm16 = PXM_copy(Fpxm16);
4274
mutex_unlock(&Fpixmap_lock);
4277
Fzoom = 0; // v.11.07
4278
curr_image_time = get_seconds(); // mark time of file load v.11.07
4280
if (! CEF->Fmod) return; // no change
4281
signal_thread(); // signal thread, repeat edit
4287
/**************************************************************************
4288
undo / redo toolbar buttons
4289
***************************************************************************/
4291
// [undo] menu function - reinstate previous edit in undo/redo stack
4293
void m_undo(GtkWidget *, cchar *)
4295
if (CEF) { // undo active edit
4296
edit_undo(); // v.11.08
4300
if (Pundo == 0) return; // undo past edit
4301
if (! menulock(1)) return;
4303
if (KB_A_key) Pundo = 0; // if KB 'A' key, undo all v.11.11
4305
Fmodified = Pundo; // v.11.08
4311
// [redo] menu function - reinstate next edit in undo/redo stack
4313
void m_redo(GtkWidget *, cchar *)
4315
if (CEF) { // redo active edit
4316
edit_redo(); // v.11.08
4320
if (Pundo == Pumax) return;
4321
if (! menulock(1)) return;
4323
if (KB_A_key) Pundo = Pumax; // if KB 'A' key, redo all v.11.11
4325
Fmodified = Pundo; // v.11.08
4331
// undo all edits of the current image
4332
// (discard modifications)
4334
void undo_all() // v.11.04
4336
if (! menulock(1)) return;
4337
Pundo = 0; // v.11.08
4339
Fmodified = 0; // v.11.07
4345
// Save Fpxm16 to undo/redo file stack
4346
// stack position = Pundo
4348
void save_undo(cchar *funcname)
4353
pp = strstr(undo_files,"_undo_");
4354
if (! pp) zappcrash("undo/redo stack corrupted 1");
4355
snprintf(pp+6,3,"%02d",Pundo);
4357
fid = open(undo_files,O_WRONLY|O_CREAT|O_TRUNC,0640);
4358
if (! fid) zappcrash("undo/redo stack corrupted 2");
4360
snprintf(buff,24," %05d %05d fotoxx ",Fww,Fhh);
4361
cc = write(fid,buff,20);
4362
if (cc != 20) zappcrash("undo/redo stack corrupted 3");
4365
cc2 = write(fid,Fpxm16->bmp,cc);
4366
if (cc2 != cc) zappcrash("undo/redo stack corrupted 4");
4370
pvlist_replace(editlog,Pundo,funcname); // save log of edits done
4375
// Load Fpxm16 from undo/redo file stack
4376
// stack position = Pundo
4380
char *pp, buff[24], fotoxx[8];
4381
int fid, ww, hh, cc, cc2;
4383
pp = strstr(undo_files,"_undo_");
4384
if (! pp) zappcrash("undo/redo stack corrupted 1");
4385
snprintf(pp+6,3,"%02d",Pundo);
4387
fid = open(undo_files,O_RDONLY);
4388
if (! fid) zappcrash("undo/redo stack corrupted 2");
4391
cc = read(fid,buff,20);
4392
sscanf(buff," %d %d %8s ",&ww, &hh, fotoxx);
4393
if (! strEqu(fotoxx,"fotoxx")) zappcrash("undo/redo stack corrupted 4");
4395
mutex_lock(&Fpixmap_lock);
4398
Fpxm16 = PXM_make(ww,hh,16);
4400
cc2 = read(fid,Fpxm16->bmp,cc);
4401
if (cc2 != cc) zappcrash("undo/redo stack corrupted 5");
4405
Fpxm8 = PXM_convbpc(Fpxm16);
4410
if (sa_fww != Fww || sa_fhh != Fhh) // disable area if image size changes
4411
sa_disable(); // bugfix v.11.08
4413
mutex_unlock(&Fpixmap_lock);
4419
/**************************************************************************/
4421
// ask user if modified image should be kept or discarded
4422
// returns: 0 = mods discarded, 1 = do not discard mods
4426
int keep = 0, choice = 0;
4427
cchar *title = ZTX("Discard edits?");
4428
cchar *message = ZTX("This action will discard current edits.\n" // more clarity v.11.10
4429
"Continue to discard edits.\n"
4430
"Go Back to keep edits.");
4431
cchar *continu = ZTX("Continue");
4432
cchar *goback = ZTX("Go Back");
4434
if (Fsaved < Pundo) keep = 1; // curr. edits not saved
4435
if (keep == 0) return 0; // no mods
4436
choice = zdialog_choose(title,mWin,message,continu,goback,null);
4437
if (choice == 2) return 1; // keep mods
4438
undo_all(); // discard mods
4443
/**************************************************************************/
4445
// menu lock/unlock - some functions must not run concurrently
4446
// returns 1 if success, else 0
4448
int menulock(int lock)
4450
if (! lock && ! Fmenulock) zappcrash("menu lock error");
4451
if (lock && Fmenulock) {
4452
zmessageACK(mWin,ZTX("prior function still active")); // v.11.06
4455
if (lock) Fmenulock++;
4461
/**************************************************************************/
4463
// check if Fsyncbusy is active, return 0 if not,
4464
// otherwise message user and return 1
4468
if (! Fsyncbusy) return 0;
4469
threadmessage = "Synchronize files is running. Edits are blocked. \n"
4470
"You can still navigate and view images normally.";
4475
/**************************************************************************/
4477
// start thread that does the edit work
4479
void start_thread(threadfunc func, void *arg)
4481
thread_status = 1; // thread is running
4482
thread_command = thread_pend = thread_done = thread_hiwater = 0; // nothing pending
4483
start_detached_thread(func,arg);
4488
// signal thread that work is pending
4490
void signal_thread()
4492
edit_zapredo(); // reset redo copy
4493
if (thread_status > 0) thread_pend++;
4498
// wait for edit thread to complete pending work and become idle
4500
void wait_thread_idle()
4502
while (thread_status && thread_pend > thread_done)
4512
// wait for thread exit or command thread exit
4513
// command = 0 wait for normal completion
4514
// 8 finish pending work and exit
4517
void wrapup_thread(int command)
4519
thread_command = command; // tell thread to quit or finish
4521
while (thread_status > 0) // wait for thread to finish
4522
{ // pending work and exit
4531
// called only from edit threads
4532
// idle loop - wait for work request or exit command
4534
void thread_idle_loop()
4536
if (thread_status == 2) Ffuncbusy--; // v.11.05
4537
thread_status = 1; // status = idle
4538
thread_done = thread_hiwater; // work done = high-water mark
4542
if (thread_command == 9) thread_exit(); // quit now command
4543
if (thread_command == 8) // finish work and exit
4544
if (thread_pend <= thread_done) thread_exit();
4545
if (thread_pend > thread_done) break; // wait for work request
4549
thread_hiwater = thread_pend; // set high-water mark
4550
thread_status = 2; // thread is working
4551
Ffuncbusy++; // v.11.05
4552
return; // perform edit
4556
// exit thread unconditionally, called only from edit threads
4560
if (thread_status == 2) Ffuncbusy--; // v.11.05
4561
thread_pend = thread_done = thread_hiwater = 0;
4563
pthread_exit(0); // "return" cannot be used here
4567
/**************************************************************************/
4569
// edit support functions for working threads (per processor core)
4571
void start_wthread(threadfunc func, void *arg) // start and increment busy count
4573
zadd_locked(wthreads_busy,+1);
4574
zadd_locked(Ffuncbusy,+1); // v.11.01
4575
start_detached_thread(func,arg);
4580
void exit_wthread() // decrement busy count and exit
4582
zadd_locked(Ffuncbusy,-1); // v.11.01
4583
zadd_locked(wthreads_busy,-1);
4584
pthread_exit(0); // "return" cannot be used here v.9.4
4588
void wait_wthreads() // wait for all working threads done
4589
{ // caller can be main() or a thread
4590
while (wthreads_busy) {
4592
zmainloop(); // v.11.11.1
4598
/**************************************************************************
4599
other support functions
4600
***************************************************************************/
4602
// help menu function
4604
void m_help(GtkWidget *, cchar *menu)
4606
if (strEqu(menu,ZTX("About")))
4607
zmessageACK(mWin,"%s \n%s \n%s \n%s \n\n%s \n\n%s",
4608
fversion,flicense,fhomepage,fcontact,fcredits,ftranslators);
4610
if (strEqu(menu,ZTX("User Guide")))
4613
if (strEqu(menu,ZTX("User Guide Changes")))
4614
showz_userguide("changes");
4616
if (strEqu(menu,ZTX("Help"))) // toolbar button
4617
showz_userguide(zfuncs::F1_help_topic); // show topic if there, or page 1
4619
if (strEqu(menu,"README"))
4622
if (strEqu(menu,ZTX("Edit Functions Summary"))) // v.11.11
4623
showz_doctext("edit-menus");
4625
if (strEqu(menu,ZTX("Change Log")))
4628
if (strEqu(menu,ZTX("Translations")))
4629
showz_translations();
4631
if (strEqu(menu,ZTX("Home Page")))
4632
showz_html(fhomepage);
4638
/**************************************************************************/
4640
// table for loading and saving adjustable parameters between sessions
4651
param paramTab[Nparms] = {
4652
// name type count location
4653
{ "fotoxx version", "char", 1, &pversion },
4654
{ "last session", "char", 1, &last_session },
4655
{ "first time", "int", 1, &Ffirsttime },
4656
{ "new files found", "int", 1, &newfiles },
4657
{ "window geometry", "int", 4, &mwgeom },
4658
{ "toolbar style", "char", 1, &tbar_style },
4659
{ "warn overwrite", "int", 1, &Fwarnoverwrite },
4660
{ "lens name", "char", 4, &lens4_name },
4661
{ "lens mm", "double", 4, &lens4_mm },
4662
{ "lens bow", "double", 4, &lens4_bow },
4663
{ "current lens", "int", 1, &curr_lens },
4664
{ "trim size", "int", 2, &trimsize },
4665
{ "trim buttons", "char", 6, &trimbuttons },
4666
{ "trim ratios", "char", 6, &trimratios },
4667
{ "edit resize", "int", 2, &editresize },
4668
{ "batch resize", "int", 2, &batchresize },
4669
{ "e-mail resize", "int", 2, &emailsize },
4670
{ "annotate font", "char", 1, &annotate_font },
4671
{ "annotate text", "char", 1, &annotate_text },
4672
{ "annotate color", "char", 3, &annotate_color },
4673
{ "annotate trans", "int", 3, &annotate_trans },
4674
{ "annotate outline", "int", 1, &annotate_outline },
4675
{ "annotate angle", "double", 1, &annotate_angle },
4676
{ "grid spacing", "int", 2, &gridspace },
4677
{ "grid counts", "int", 2, &gridcount },
4678
{ "rotate grid", "int", 8, &rotate_grid },
4679
{ "unbend grid", "int", 8, &unbend_grid },
4680
{ "warp-curved grid", "int", 8, &warpC_grid },
4681
{ "warp-linear grid", "int", 8, &warpL_grid },
4682
{ "warp-affine grid", "int", 8, &warpF_grid },
4683
{ "slideshow trans", "int", SSNF, &ss_funcs },
4684
{ "slideshow music", "char", 1, &ss_musicfile } };
4687
// save parameters to file /home/<user>/.fotoxx/parameters
4689
void save_params() // new v.10.9
4692
char buff[1050], text[1000]; // limit for character data cc
4693
char *pp, *name, *type;
4701
pversion = strdupz(fversion); // this fotoxx version v.11.10
4704
last_session = ctime(&currtime) + 4; // fotoxx session exit time v.11.11
4705
if (last_session[4] == ' ') last_session[4] = '0'; // format mmm dd hh:mm:ss yyyy
4706
pp = strchr(last_session,'\n');
4709
snprintf(buff,199,"%s/parameters",get_zuserdir()); // open output file
4710
fid = fopen(buff,"w");
4713
for (int ii = 0; ii < Nparms; ii++) // write table of state data
4715
name = paramTab[ii].name;
4716
type = paramTab[ii].type;
4717
count = paramTab[ii].count;
4718
location = paramTab[ii].location;
4719
charloc = (char **) location;
4720
intloc = (int *) location;
4721
doubleloc = (double *) location;
4723
fprintf(fid,"%-20s %-8s %02d ",name,type,count); // write parm name, type, count
4725
for (int kk = 0; kk < count; kk++) // write "value" "value" ...
4727
if (strEqu(type,"char")) {
4728
if (! *charloc) printf("bad param data %s %d \n",name,kk); // bugfix v.10.11.1
4729
if (! *charloc) break;
4730
repl_1str(*charloc++,text,"\n","\\n"); // replace newlines with "\n" v.10.11
4731
fprintf(fid," \"%s\"",text);
4733
if (strEqu(type,"int"))
4734
fprintf(fid," \"%d\"",*intloc++);
4736
if (strEqu(type,"double"))
4737
fprintf(fid," \"%.2f\"",*doubleloc++);
4740
fprintf(fid,"\n"); // write EOR
4744
fclose(fid); // close file
4746
fid = fopen(recentfiles_file,"w"); // recent files file
4749
for (int ii = 0; ii < Nrecentfiles; ii++) // save list of recent files
4750
if (recentfiles[ii])
4751
fprintf(fid,"%s \n",recentfiles[ii]);
4755
snprintf(buff,199,"%s/plugins",get_zuserdir()); // open file for plugins v.11.03
4756
fid = fopen(buff,"w");
4759
for (int ii = 0; ii < Nplugins; ii++) // save plugins
4761
fprintf(fid,"%s \n",plugins[ii]);
4769
// load parameters from file /home/<user>/.fotoxx/parameters
4771
void load_params() // new v.10.9
4774
int ii, err, pcount, Idata;
4776
char buff[1000], text[1000], *pp;
4777
char name[20], type[12], count[8], data[1000];
4783
for (ii = 0; ii < Nparms; ii++) // set string parameters to "undefined"
4785
if (strNeq(paramTab[ii].type,"char")) continue;
4786
charloc = (char **) paramTab[ii].location;
4787
pcount = paramTab[ii].count;
4788
for (int jj = 0; jj < pcount; jj++)
4789
*charloc++ = strdupz("undefined",20);
4792
snprintf(buff,499,"%s/parameters",get_zuserdir()); // open parameters file
4793
fid = fopen(buff,"r");
4796
while (true) // read parameters
4798
pp = fgets_trim(buff,999,fid,1);
4800
if (*pp == '#') continue; // comment v.10.10
4801
if (strlen(pp) < 40) continue; // rubbish v.10.10
4805
strncpy0(name,pp,20); // parm name
4808
strncpy0(type,pp+22,8); // parm type
4811
strncpy0(count,pp+32,4); // parm count
4813
err = convSI(count,pcount);
4815
strncpy0(data,pp+38,1000); // parm value(s)
4818
for (ii = 0; ii < Nparms; ii++) // match file record to param table
4820
if (strNeq(name,paramTab[ii].name)) continue; // parm name
4821
if (strNeq(type,paramTab[ii].type)) continue; // parm type
4822
if (paramTab[ii].count != pcount) continue; // parm count
4824
location = paramTab[ii].location;
4825
charloc = (char **) location;
4826
intloc = (int *) location;
4827
doubleloc = (double *) location;
4829
for (int kk = 1; kk <= pcount; kk++)
4831
pp = (char *) strField(data,' ',kk);
4834
if (strEqu(type,"char")) {
4835
repl_1str(pp,text,"\\n","\n"); // replace "\n" with real newlines v.10.11
4836
*charloc++ = strdupz(text,0,"parameters"); // v.11.04
4839
if (strEqu(type,"int")) {
4840
err = convSI(pp,Idata);
4845
if (strEqu(type,"double")) {
4846
err = convSD(pp,Rdata);
4848
*doubleloc++ = Rdata;
4856
if (strNeq(pversion,fversion)) // fotoxx version change v.11.10
4857
printf("new Fotoxx version %s %s \n",pversion,fversion);
4859
printf("last Fotoxx session: %s \n",last_session); // last session exit time v.11.11
4861
fid = fopen(topdirk_file,"r"); // set top directory from v.11.11
4862
if (fid) { // from top directory file
4863
pp = fgets_trim(buff,499,fid,1);
4864
if (pp && *pp == '/')
4865
topdirk = strdupz(buff,0,"top_dirk");
4869
printf("top image directory: %s \n",topdirk);
4871
for (ii = 0; ii < Nrecentfiles; ii++) // recent image file list = empty
4872
recentfiles[ii] = 0;
4874
fid = fopen(recentfiles_file,"r"); // open recent files file
4876
for (ii = 0; ii < Nrecentfiles; ii++) // read list of recent files
4878
pp = fgets_trim(buff,499,fid,1);
4881
recentfiles[ii] = strdupz(buff,0,"recentfile");
4882
else ii--; // v.11.11
4888
for (ii = 0; ii < maxplugins; ii++) // plugins list = empty v.11.03
4891
plugins[0] = strdupz("Gimp = gimp"); // if empty, default Gimp plugin
4894
snprintf(buff,499,"%s/plugins",get_zuserdir()); // open plugins file v.11.03
4895
fid = fopen(buff,"r");
4898
for (ii = 0; ii < maxplugins; ii++) // read list of plugins
4900
pp = fgets_trim(buff,499,fid,1);
4902
plugins[ii] = strdupz(buff,0,"plugins");
4913
/**************************************************************************/
4915
// Compute the mean brightness of all pixel neighborhoods, // new v.9.6
4916
// using a Guassian or a flat distribution for the weightings.
4917
// If a select area is active, only inside pixels are calculated.
4918
// The flat method is 10-100x faster.
4921
double brhood_kernel[200][200]; // up to radius = 99
4922
float *brhood_brightness = 0; // neighborhood brightness per pixel
4923
char brhood_method; // g = gaussian, f = flat distribution
4926
void brhood_calc(int radius, char method)
4928
void * brhood_wthread(void *arg);
4930
int rad, rad2, dx, dy, cc, ii;
4933
brhood_radius = radius;
4934
brhood_method = method;
4936
if (brhood_method == 'g') // compute Gaussian kernel
4937
{ // (not currently used v.11.02)
4938
rad = brhood_radius;
4941
for (dy = -rad; dy <= rad; dy++)
4942
for (dx = -rad; dx <= rad; dx++)
4944
if (dx*dx + dy*dy <= rad2) // cells within radius
4945
kern = exp( - (dx*dx + dy*dy) / rad2);
4946
else kern = 0; // outside radius
4947
brhood_kernel[dy+rad][dx+rad] = kern;
4951
if (brhood_brightness) zfree(brhood_brightness); // allocate memory for pixel map
4952
cc = E1ww * E1hh * sizeof(float);
4953
brhood_brightness = (float *) zmalloc(cc,"brhood");
4954
memset(brhood_brightness,0,cc);
4956
if (Factivearea) SB_goal = sa_Npixel; // set up progress tracking
4957
else SB_goal = E1ww * E1hh;
4958
SB_done = 0; // v.11.06
4960
for (ii = 0; ii < Nwt; ii++) // start worker threads
4961
start_wthread(brhood_wthread,&wtnx[ii]);
4962
wait_wthreads(); // wait for completion
4969
// worker thread function
4971
void * brhood_wthread(void *arg)
4973
int index = *((int *) arg);
4974
int rad = brhood_radius;
4975
int ii, px, py, qx, qy, Fstart;
4976
double kern, bsum, bsamp, bmean;
4979
if (brhood_method == 'g') // use round gaussian distribution
4981
for (py = index; py < E1hh; py += Nwt)
4982
for (px = 0; px < E1ww; px++)
4984
if (Factivearea && sa_mode != 7) { // select area, not whole image v.11.02
4985
ii = py * E1ww + px; // use only inside pixels
4986
if (! sa_pixmap[ii]) continue;
4991
for (qy = py-rad; qy <= py+rad; qy++) // computed weighted sum of brightness
4992
for (qx = px-rad; qx <= px+rad; qx++) // for pixels in neighborhood
4994
if (qy < 0 || qy > E1hh-1) continue;
4995
if (qx < 0 || qx > E1ww-1) continue;
4996
kern = brhood_kernel[qy+rad-py][qx+rad-px];
4997
pixel = PXMpix(E1pxm16,qx,qy);
4998
bsum += pixbright(pixel) * kern; // sum brightness * weight
4999
bsamp += kern; // sum weights
5002
bmean = bsum / bsamp; // mean brightness
5003
ii = py * E1ww + px;
5004
brhood_brightness[ii] = bmean; // pixel value
5006
SB_done++; // track progress v.11.06
5007
if (drandz() < 0.0001) zsleep(0.001); // trigger sorry kernel scheduler
5011
if (brhood_method == 'f') // use square flat distribution
5016
for (py = index; py < E1hh; py += Nwt)
5017
for (px = 0; px < E1ww; px++)
5019
if (Factivearea && sa_mode != 7) { // select area, not whole image v.11.02
5020
ii = py * E1ww + px; // compute only inside pixels
5021
if (! sa_pixmap[ii]) {
5027
if (px == 0) Fstart = 1;
5034
for (qy = py-rad; qy <= py+rad; qy++) // add up all columns
5035
for (qx = px-rad; qx <= px+rad; qx++)
5037
if (qy < 0 || qy > E1hh-1) continue;
5038
if (qx < 0 || qx > E1ww-1) continue;
5039
pixel = PXMpix(E1pxm16,qx,qy);
5040
bsum += pixbright(pixel);
5046
qx = px-rad-1; // subtract first-1 column
5048
for (qy = py-rad; qy <= py+rad; qy++)
5050
if (qy < 0 || qy > E1hh-1) continue;
5051
pixel = PXMpix(E1pxm16,qx,qy);
5052
bsum -= pixbright(pixel);
5056
qx = px+rad; // add last column
5058
for (qy = py-rad; qy <= py+rad; qy++)
5060
if (qy < 0 || qy > E1hh-1) continue;
5061
pixel = PXMpix(E1pxm16,qx,qy);
5062
bsum += pixbright(pixel);
5068
bmean = bsum / bsamp; // mean brightness
5069
ii = py * E1ww + px;
5070
brhood_brightness[ii] = bmean;
5072
SB_done++; // track progress v.11.06
5073
if (drandz() < 0.0001) zsleep(0.001); // trigger sorry kernel scheduler
5078
return 0; // not executed, avoid gcc warning
5082
// get the neighborhood brightness for given pixel
5084
float get_brhood(int px, int py)
5086
int ii = py * E1ww + px;
5087
return brhood_brightness[ii];
5091
/**************************************************************************/
5093
// free all resources associated with the current image file
5095
void free_resources(int fkeepundo)
5100
mutex_lock(&Fpixmap_lock); // lock pixmaps
5103
strcpy(command,"rm -f "); // delete all undo files
5104
strcat(command,undo_files);
5105
pp = strstr(command,"_undo_"); // /home/user/.fotoxx/pppppp_undo_*
5107
err = system(command);
5108
if (err) printf("error: %s \n",wstrerror(err));
5109
Fmodified = Pundo = Pumax = Fsaved = 0; // reset undo/redo stack
5112
Ntoplines = Nptoplines; // no image overlay lines
5113
freeMouse(); // free mouse v.11.04
5115
if (Fshutdown) { // stop here if shutdown mode
5116
mutex_unlock(&Fpixmap_lock);
5121
sa_unselect(); // unselect select area
5122
zdialog_free(zdsela); // kill dialogs if active
5123
zfree(curr_file); // free image file
5125
SB_goal = 0; // v.9.2
5126
*SB_text = 0; // v.10.7
5129
if (brhood_brightness) zfree(brhood_brightness); // free brightness map v.9.6
5130
brhood_brightness = 0;
5137
PXM_free(ERpxm16); // v.10.3
5139
Fww = E1ww = E3ww = Dww = 0; // make unusable (crash)
5141
mutex_unlock(&Fpixmap_lock);
5146
// Get a virtual pixel at location (px,py) (real) in an PXM-16 pixmap.
5147
// Get the overlapping real pixels and build a composite.
5149
int vpixel(PXM *pxm, double px, double py, uint16 *vpix)
5151
int ww, hh, px0, py0;
5152
uint16 *ppix, *pix0, *pix1, *pix2, *pix3;
5153
double f0, f1, f2, f3;
5154
double red, green, blue;
5158
ppix = (uint16 *) pxm->bmp;
5160
px0 = px; // pixel containing (px,py)
5163
if (px0 < 1 || py0 < 1) return 0; // void edge pixels
5164
if (px0 > ww-3 || py0 > hh-3) return 0;
5166
pix0 = ppix + (py0 * ww + px0) * 3; // 4 pixels based at (px0,py0)
5167
pix1 = pix0 + ww * 3;
5169
pix3 = pix0 + ww * 3 + 3;
5171
f0 = (px0+1 - px) * (py0+1 - py); // overlap of (px,py)
5172
f1 = (px0+1 - px) * (py - py0); // in each of the 4 pixels
5173
f2 = (px - px0) * (py0+1 - py);
5174
f3 = (px - px0) * (py - py0);
5176
red = f0 * pix0[0] + f1 * pix1[0] + f2 * pix2[0] + f3 * pix3[0]; // sum the weighted inputs
5177
green = f0 * pix0[1] + f1 * pix1[1] + f2 * pix2[1] + f3 * pix3[1];
5178
blue = f0 * pix0[2] + f1 * pix1[2] + f2 * pix2[2] + f3 * pix3[2];
5184
if (blue < 1) { // v.10.7
5185
if (blue < 0.9) return 0; // near edge or voided area
5186
vpix[2] = 1; // avoid 0.999 to integer 0
5193
// compare two doubles for significant difference
5194
// return: 0 difference not significant
5198
int sigdiff(double d1, double d2, double signf)
5200
double diff = fabs(d1-d2);
5201
if (diff == 0.0) return 0;
5202
diff = diff / (fabs(d1) + fabs(d2));
5203
if (diff < signf) return 0;
5204
if (d1 > d2) return 1;
5209
/**************************************************************************
5210
file read and write utilities
5211
PXM pixmap <--> file on disk
5212
***************************************************************************/
5214
// Load an image file into an PXM pixmap of 8 or 16 bits/color
5215
// Also sets the following file scope variables:
5216
// f_load_type = "jpg" "tif" "png" or "other"
5217
// f_load_bpc = disk file bits per color = 1/8/16
5218
// f_load_size = disk file size
5220
PXM * f_load(cchar *filespec, int bpc) // use pixbuf or tiff library v.9.8
5227
if (bpc != 8 && bpc != 16) // requested bpc must be 8 or 16
5228
zappcrash("image_load bpc: %d",bpc);
5230
err = stat(filespec,&fstat);
5231
if (err) return 0; // file not found
5232
if (! S_ISREG(fstat.st_mode)) return 0; // not a regular file
5233
if (image_file_type(filespec) != 2) return 0; // not a supported image type
5234
f_load_size = fstat.st_size; // disk file bytes
5236
pext = strrchr(filespec,'/');
5237
if (! pext) pext = filespec;
5239
if (pext > filespec+12 && strnEqu(pext-12,"/.thumbnails/",13)) { // refuse thumbnail files v.10.0
5240
zmessageACK(mWin,ZTX("cannot open thumbnail file"));
5244
pext = strrchr(pext,'.');
5245
if (! pext) pext = "";
5247
if (strstr(".jpg .jpeg .JPG .JPEG",pext)) strcpy(f_load_type,"jpg");
5248
else if (strstr(".tif .tiff .TIF .TIFF",pext)) strcpy(f_load_type,"tif");
5249
else if (strstr(".png .PNG",pext)) strcpy(f_load_type,"png");
5250
else strcpy(f_load_type,"other");
5252
if (strEqu(f_load_type,"tif")) // use tiff lib to read tiff file
5253
pxm1 = TIFFread(filespec);
5254
else pxm1 = PXBread(filespec); // use pixbuf lib for others
5255
if (! pxm1) return 0; // (both set f_load_bpc = file bpc)
5257
if (pxm1->bpc != bpc) {
5258
pxm2 = PXM_convbpc(pxm1); // convert to requested bpc
5259
PXM_free(pxm1); // 8 <--> 16
5262
// auto upright image removed v.10.12
5267
/**************************************************************************/
5269
// save current image to specified disk file (same or new).
5270
// set f_save_type, f_save_bpc, f_save_size
5271
// update search index file
5272
// return 0 if OK, else +N
5274
int f_save(char *outfile, cchar *type, int bpc) // use pixbuf or tiff library v.9.8
5277
cchar *exifkey[2] = { exif_orientation_key, iptc_editlog_key };
5279
char **ppv, funcslist[1000];
5280
char *pp, *tempfile, *pext;
5281
int nkeys, err = 1, cc1, cc2;
5284
if ((bpc != 8 && bpc != 16) || ! strstr("jpg tif png",type)) // check args
5285
zappcrash("f_save: %s %d",type,bpc);
5287
Ffuncbusy++; // v.11.01
5289
pext = strrchr(outfile,'/'); // force compatible file extension
5290
if (pext) pext = strrchr(pext,'.'); // if not already
5291
if (! pext) pext = outfile + strlen(outfile);
5292
if (strEqu(type,"jpg") && ! strstr(".jpg .JPG .jpeg .JPEG",pext))
5293
strcpy(pext,".jpg");
5294
if (strEqu(type,"png") && ! strstr(".png .PNG",pext))
5295
strcpy(pext,".png");
5296
if (strEqu(type,"tif") && ! strstr(".tif .TIF .tiff .TIFF",pext))
5297
strcpy(pext,".tif");
5299
tempfile = strdupz(get_zuserdir(),24,"f_save"); // use temp output file
5300
strcat(tempfile,"/temp_"); // ~/.fotoxx/temp_ppppp.ext
5301
strcat(tempfile,PIDstring);
5302
strcat(tempfile,".");
5303
strcat(tempfile,type);
5305
if (strEqu(type,"png")) { // save as PNG file (bpc = 8 always)
5306
if (Fpxm16) err = PXBwrite(Fpxm16,tempfile);
5307
else err = PXBwrite(Fpxm8,tempfile);
5311
else if (strEqu(type,"tif")) { // save as tiff file
5314
PXM_free(Fpxm8); // bugfix v.10.8
5315
Fpxm8 = PXM_convbpc(Fpxm16);
5317
err = TIFFwrite(Fpxm8,tempfile);
5319
else if (bpc == 16) {
5320
if (Fpxm16) err = TIFFwrite(Fpxm16,tempfile); // edit file exists, use it
5322
if (curr_file_bpc == 16) pxm16 = TIFFread(curr_file); // use original 16 bpc file
5323
else pxm16 = PXM_convbpc(Fpxm8); // or convert 8 to 16 bpc
5324
if (! pxm16) err = 1;
5326
err = TIFFwrite(pxm16,tempfile);
5333
else { // save as JPEG file (bpc = 8 always)
5334
if (Fpxm16) err = PXBwrite(Fpxm16,tempfile);
5335
else err = PXBwrite(Fpxm8,tempfile);
5340
remove(tempfile); // failure, clean up v.11.02
5346
exifdata[0] = ""; // EXIF orientation = upright
5347
nkeys = 1; // remove old width, height v.10.8
5349
if (Pundo > 0) // if edits were made,
5350
{ // update relevant EXIF key v.10.2
5352
ppv = info_get(curr_file,&exifkey[1],1); // get existing list of edits
5354
strncpy0(funcslist,ppv[0],998);
5357
cc1 = strlen(funcslist); // add blank
5358
if (cc1 && funcslist[cc1-1] != ' ') {
5359
strcpy(funcslist+cc1," ");
5363
for (int ii = 1; ii <= Pundo; ii++) // append new list of edits
5364
{ // (omit index 0 = initial)
5365
pp = pvlist_get(editlog,ii);
5367
if (cc1 + cc2 > 998) break;
5368
strcpy(funcslist+cc1,pp);
5369
strcpy(funcslist+cc1+cc2," ");
5373
exifdata[1] = funcslist; // IPTC edit history key, fotoxx edits done
5377
err = stat(curr_file,&fstat);
5378
if (! err && S_ISREG(fstat.st_mode)) { // if current file exists, v.11.05
5379
err = info_copy(curr_file,tempfile,exifkey,exifdata,nkeys); // copy all EXIF/IPTC data to
5380
if (err) zmessageACK(mWin,"Unable to copy EXIF/IPTC"); // temp file with above revisions
5383
snprintf(command,ccc,"cp -f \"%s\" \"%s\" ",tempfile,outfile); // copy temp file to output file
5384
err = system(command);
5385
if (err) zmessageACK(mWin,"Unable to save image: %s",wstrerror(err));
5387
remove(tempfile); // delete temp file v.11.02
5390
save_fileinfo(outfile); // save tag changes if any
5391
if (! curr_file || strNeq(outfile,curr_file)) // if save to new file, v.11.08
5392
update_search_index(outfile); // update search index file
5394
Fsaved = Pundo; // update which mods are saved to disk
5396
add_recent_file(outfile); // first in recent files list
5398
strcpy(f_save_type,type); // update f_save_xxx data
5401
err = stat(outfile,&fstat);
5407
f_save_size = fstat.st_size;
5410
mwpaint2(); // v.11.03
5415
/**************************************************************************/
5417
// Read from TIFF file using TIFF library.
5418
// Use native TIFF file bits/pixel.
5420
PXM * TIFFread(cchar *filespec) // overhauled v.10.8.1
5425
uint8 *tiff8, *pxm8;
5426
uint16 *tiff16, *pxm16;
5427
uint16 bpc, nch, fmt;
5428
int ww, hh, rps, stb, nst; // int not uint v.11.03
5429
int tiffstat, row, col, strip, cc;
5431
tiff = TIFFOpen(filespec,"r");
5433
zmessageACK(mWin,ZTX("TIFF open failure"));
5437
TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &ww); // width
5438
TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &hh); // height
5439
TIFFGetField(tiff, TIFFTAG_BITSPERSAMPLE, &bpc); // bits per color, 1/8/16
5440
TIFFGetField(tiff, TIFFTAG_SAMPLESPERPIXEL, &nch); // no. channels (colors), 1/3/4
5441
TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rps); // rows per strip
5442
TIFFGetField(tiff, TIFFTAG_SAMPLEFORMAT, &fmt); // data format
5443
stb = TIFFStripSize(tiff); // strip size
5444
nst = TIFFNumberOfStrips(tiff); // number of strips
5446
// printf("ww %d hh %d nch %d bpc %d rps %d stb %d nst %d fmt %d \n",ww,hh,nch,bpc,rps,stb,nst,fmt);
5448
if (! (bpc <= 8 || bpc == 16)) { // check for supported bits/color
5449
zmessageACK(mWin,ZTX("TIFF bits/color=%d not supported"),bpc);
5454
f_load_bpc = bpc; // for f_load(), file bpc 1/8/16 v.10.12
5456
if (bpc <= 8) // use universal TIFF reader
5457
{ // if bits/color <= 8
5458
tiffbuff = zmalloc(ww*hh*4,"tiffbuff");
5460
tiffstat = TIFFReadRGBAImage(tiff, ww, hh, (uint *) tiffbuff, 0);
5463
if (tiffstat != 1) {
5464
zmessageACK(mWin,ZTX("TIFF read failure"));
5469
pxm = PXM_make(ww,hh,8);
5471
tiff8 = (uint8 *) tiffbuff;
5473
for (row = 0; row < hh; row++) // convert RGBA to RGB
5475
pxm8 = (uint8 *) pxm->bmp;
5476
pxm8 += (hh-1 - row) * ww * 3;
5478
for (col = 0; col < ww; col++)
5491
// 16 bits per color
5493
stb += 1000000; // reduce risk of crash v.10.8.2
5494
tiffbuff = zmalloc(stb,"tiffbuff"); // read encoded strips
5496
pxm = PXM_make(ww,hh,16);
5498
for (strip = 0; strip < nst; strip++)
5500
cc = TIFFReadEncodedStrip(tiff,strip,tiffbuff,stb);
5502
zmessageACK(mWin,ZTX("TIFF read failure"));
5511
tiff16 = (uint16 *) tiffbuff;
5512
pxm16 = (uint16 *) pxm->bmp;
5514
pxm16 += row * ww * 3;
5516
while (cc >= 6) // bugfix v.11.03
5518
pxm16[0] = tiff16[0];
5519
pxm16[1] = tiff16[1];
5520
pxm16[2] = tiff16[2];
5533
// Write to TIFF file using TIFF library.
5534
// File bpc is taken from PXM (8 or 16).
5535
// returns 0 if OK, +N if error.
5537
int TIFFwrite(PXM *pxm, cchar *filespec) // new v.9.8
5540
uint8 *tiff8, *pxm8;
5541
uint16 *tiff16, *pxm16;
5543
int ww, hh, row, col, rowcc; // int not uint v.11.03
5544
int bpc, nch, pm = 2, pc = 1, comp = 5;
5547
tiff = TIFFOpen(filespec,"w");
5549
zmessageACK(mWin,ZTX("TIFF open failure"));
5556
nch = 3; // alpha channel removed
5558
TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, ww);
5559
TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, bpc);
5560
TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, nch);
5561
TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, pm); // RGB
5562
TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, pc);
5563
TIFFSetField(tiff, TIFFTAG_COMPRESSION, comp); // LZW
5565
rowcc = TIFFScanlineSize(tiff);
5566
tiffbuff = (char*) zmalloc(rowcc,"tiffbuff");
5568
for (row = 0; row < hh; row++)
5572
tiff8 = (uint8 *) tiffbuff;
5573
pxm8 = (uint8 *) pxm->bmp + row * ww * 3;
5575
for (col = 0; col < ww; col++)
5587
tiff16 = (uint16 *) tiffbuff;
5588
pxm16 = (uint16 *) pxm->bmp + row * ww * 3;
5590
for (col = 0; col < ww; col++)
5592
tiff16[0] = pxm16[0];
5593
tiff16[1] = pxm16[1];
5594
tiff16[2] = pxm16[2];
5600
tiffstat = TIFFWriteScanline(tiff,tiffbuff,row,0);
5601
if (tiffstat != 1) break;
5607
if (tiffstat == 1) return 0;
5608
zmessageACK(mWin,ZTX("TIFF write failure"));
5613
// intercept TIFF warning messages (many) // new v.9.8
5615
void tiffwarninghandler(cchar *module, cchar *format, va_list ap)
5617
return; // stop flood of crap
5619
vsnprintf(message,199,format,ap);
5620
printf("TIFF warning: %s %s \n",module,message);
5625
// Read from jpeg/png file using pixbuf library. bpc = 8.
5627
PXM * PXBread(cchar *filespec) // new v.9.8
5632
int ww, hh, px, py, nch, rowst;
5633
uint8 *bmp1, *bmp2, *pix1, *pix2;
5635
pxb = gdk_pixbuf_new_from_file(filespec,&gerror);
5637
printf("%s \n",gerror->message); // v.10.8
5638
zmessageACK(mWin,ZTX("file type not supported"));
5642
ww = gdk_pixbuf_get_width(pxb);
5643
hh = gdk_pixbuf_get_height(pxb);
5644
nch = gdk_pixbuf_get_n_channels(pxb);
5645
rowst = gdk_pixbuf_get_rowstride(pxb);
5646
bmp1 = gdk_pixbuf_get_pixels(pxb);
5648
pxm = PXM_make(ww,hh,8);
5649
bmp2 = (uint8 *) pxm->bmp;
5651
for (py = 0; py < hh; py++)
5653
pix1 = bmp1 + rowst * py;
5654
pix2 = bmp2 + py * ww * 3;
5656
for (px = 0; px < ww; px++)
5666
f_load_bpc = 8; // file bits per color for f_load()
5667
g_object_unref(pxb); // (any value on disk becomes 8)
5672
// Write to jpeg/png file using pixbuf library. bpc = 8.
5673
// returns 0 if OK, +N if error.
5675
int PXBwrite(PXM *pxm, cchar *filespec) // new v.9.8
5677
int ww, hh, bpc, px, py, rowst;
5678
uint8 *bmp8, *pix8, *bmp2, *pix2;
5679
uint16 *bmp16, *pix16;
5689
pxb = gdk_pixbuf_new(RGBCOLOR,0,8,ww,hh);
5690
if (! pxb) zappcrash("pixbuf allocation failure");
5692
bmp8 = (uint8 *) pxm->bmp;
5693
bmp16 = (uint16 *) pxm->bmp;
5694
bmp2 = gdk_pixbuf_get_pixels(pxb);
5695
rowst = gdk_pixbuf_get_rowstride(pxb);
5699
for (py = 0; py < hh; py++)
5701
pix8 = bmp8 + py * ww * 3;
5702
pix2 = bmp2 + rowst * py;
5704
for (px = 0; px < ww; px++)
5717
for (py = 0; py < hh; py++)
5719
pix16 = bmp16 + py * ww * 3;
5720
pix2 = bmp2 + rowst * py;
5722
for (px = 0; px < ww; px++)
5724
pix2[0] = pix16[0] >> 8;
5725
pix2[1] = pix16[1] >> 8;
5726
pix2[2] = pix16[2] >> 8;
5733
pext = strrchr(filespec,'/');
5734
if (! pext) pext = filespec;
5735
pext = strrchr(pext,'.');
5736
if (! pext) pext = "";
5738
if (strstr(".png .PNG",pext))
5739
pxbstat = gdk_pixbuf_save(pxb,filespec,"png",&gerror,null);
5740
else pxbstat = gdk_pixbuf_save(pxb,filespec,"jpeg",&gerror,"quality",jpeg_quality,null);
5741
g_object_unref(pxb);
5742
if (pxbstat) return 0;
5744
printf("%s \n",gerror->message); // v.10.8
5745
zmessageACK(mWin,ZTX("pixbuf write failure"));
5750
/**************************************************************************
5751
PXM pixmap conversion and rescale functions
5752
***************************************************************************/
5754
// initialize PXM pixmap - allocate memory
5756
PXM * PXM_make(int ww, int hh, int bpc)
5758
if (ww < 1 || hh < 1 || (bpc != 8 && bpc != 16))
5759
zappcrash("PXM_make() %d %d %d",ww,hh,bpc);
5761
PXM *pxm = (PXM *) zmalloc(sizeof(PXM),"PXM");
5765
if (bpc == 8) pxm->bmp = zmalloc(ww*hh*3,"PXM.bmp");
5766
if (bpc == 16) pxm->bmp = zmalloc(ww*hh*6,"PXM.bmp");
5767
strcpy(pxm->wmi,"rgbrgb");
5774
void PXM_free(PXM *&pxm)
5777
if (! strEqu(pxm->wmi,"rgbrgb"))
5778
zappcrash("PXM_free(), bad PXM");
5779
strcpy(pxm->wmi,"xxxxxx");
5787
// create a copy of an PXM pixmap
5789
PXM * PXM_copy(PXM *pxm1)
5794
pxm2 = PXM_make(pxm1->ww, pxm1->hh, pxm1->bpc);
5795
cc = pxm1->ww * pxm1->hh * (pxm1->bpc / 8 * 3);
5796
memcpy(pxm2->bmp,pxm1->bmp,cc);
5801
// create a copy of an PXM area
5803
PXM * PXM_copy_area(PXM *pxm1, int orgx, int orgy, int ww2, int hh2)
5805
uint8 *bmp1, *pix1, *bmp2, *pix2;
5806
uint16 *bmp3, *pix3, *bmp4, *pix4;
5808
int ww1, bpc, px1, py1, px2, py2;
5815
pxm2 = PXM_make(ww2,hh2,8);
5816
bmp1 = (uint8 *) pxm1->bmp;
5817
bmp2 = (uint8 *) pxm2->bmp;
5819
for (py1 = orgy, py2 = 0; py2 < hh2; py1++, py2++)
5821
for (px1 = orgx, px2 = 0; px2 < ww2; px1++, px2++)
5823
pix1 = bmp1 + (py1 * ww1 + px1) * 3;
5824
pix2 = bmp2 + (py2 * ww2 + px2) * 3;
5837
pxm2 = PXM_make(ww2,hh2,16);
5838
bmp3 = (uint16 *) pxm1->bmp;
5839
bmp4 = (uint16 *) pxm2->bmp;
5841
for (py1 = orgy, py2 = 0; py2 < hh2; py1++, py2++)
5843
for (px1 = orgx, px2 = 0; px2 < ww2; px1++, px2++)
5845
pix3 = bmp3 + (py1 * ww1 + px1) * 3;
5846
pix4 = bmp4 + (py2 * ww2 + px2) * 3;
5861
// convert PXM pixmap from 8/16 to 16/8 bits per color
5863
PXM * PXM_convbpc(PXM *pxm1)
5866
uint16 *bmp16, *pix16;
5868
int ww, hh, bpc, px, py;
5874
if (bpc == 8) // 8 > 16
5876
pxm2 = PXM_make(ww,hh,16);
5877
bmp8 = (uint8 *) pxm1->bmp;
5878
bmp16 = (uint16 *) pxm2->bmp;
5880
for (py = 0; py < hh; py++)
5882
pix8 = bmp8 + py * ww * 3;
5883
pix16 = bmp16 + py * ww * 3;
5885
for (px = 0; px < ww; px++)
5887
pix16[0] = pix8[0] << 8;
5888
pix16[1] = pix8[1] << 8;
5889
pix16[2] = pix8[2] << 8;
5896
if (bpc == 16) // 16 > 8
5898
pxm2 = PXM_make(ww,hh,8);
5899
bmp8 = (uint8 *) pxm2->bmp;
5900
bmp16 = (uint16 *) pxm1->bmp;
5902
for (py = 0; py < hh; py++)
5904
pix8 = bmp8 + py * ww * 3;
5905
pix16 = bmp16 + py * ww * 3;
5907
for (px = 0; px < ww; px++)
5909
pix8[0] = pix16[0] >> 8;
5910
pix8[1] = pix16[1] >> 8;
5911
pix8[2] = pix16[2] >> 8;
5922
// rescale PXM pixmap to size ww2 x hh2
5924
PXM * PXM_rescale(PXM *pxm1, int ww2, int hh2)
5926
void bmp8_rescale(uint8*, uint8*, int, int, int, int);
5927
void bmp16_rescale(uint16*, uint16*, int, int, int, int);
5932
uint16 *bmp3, *bmp4;
5938
pxm2 = PXM_make(ww2,hh2,bpc);
5941
bmp1 = (uint8 *) pxm1->bmp;
5942
bmp2 = (uint8 *) pxm2->bmp;
5943
bmp8_rescale(bmp1,bmp2,ww1,hh1,ww2,hh2);
5947
bmp3 = (uint16 *) pxm1->bmp;
5948
bmp4 = (uint16 *) pxm2->bmp;
5949
bmp16_rescale(bmp3,bmp4,ww1,hh1,ww2,hh2);
5956
// Copy and rescale a modified area within a PXM-16 image into the
5957
// corresponding area of a PXM-8 image previously rescaled from the
5958
// PXM-16 image. Keep the same mapping of input to output pixels, so
5959
// that the modified area fits seamlessly into the PXM-8 image. Used
5960
// when a section of an image is edited and the window image is updated.
5962
// pxm1 PXM-16 image with changed area to rescale and copy
5963
// pxm2 existing rescaled PXM-8 copy of pxm1
5964
// org1x, org1y pxm1 origin of area to copy (top left corner)
5965
// ww1a, hh1a width and height of area to copy // new v.10.11
5967
void PXM_update(PXM *pxm1, PXM *pxm2, int org1x, int org1y, int ww1a, int hh1a)
5969
uint16 *bmp1, *pix1;
5971
int ww1, hh1, ww2, hh2;
5972
int px1, py1, px2, py2;
5973
int pxl, pyl, pxm, pym, ii;
5975
int maxmapx, maxmapy;
5976
int org2x, end2x, org2y, end2y;
5977
float scalex, scaley;
5978
float px1a, py1a, px1b, py1b;
5980
float red, green, blue;
5981
float *pxmap, *pymap;
5983
bmp1 = (uint16 *) pxm1->bmp;
5984
bmp2 = (uint8 *) pxm2->bmp;
5990
scalex = 1.0 * ww1 / ww2; // compute x and y scales
5991
scaley = 1.0 * hh1 / hh2;
5993
if (scalex <= 1) maxmapx = 2; // compute max input pixels
5994
else maxmapx = scalex + 2; // mapping into output pixels
5995
maxmapx += 1; // for both dimensions
5996
if (scaley <= 1) maxmapy = 2; // (pixels may not be square)
5997
else maxmapy = scaley + 2;
6000
pymap = (float *) zmalloc(hh2 * maxmapy * sizeof(float)); // maps overlap of < maxmap input
6001
pxmap = (float *) zmalloc(ww2 * maxmapx * sizeof(float)); // pixels per output pixel
6003
py1L = (int *) zmalloc(hh2 * sizeof(int)); // maps first (lowest) input pixel
6004
px1L = (int *) zmalloc(ww2 * sizeof(int)); // per output pixel
6006
for (py2 = 0; py2 < hh2; py2++) // loop output y-pixels
6008
py1a = py2 * scaley; // corresponding input y-pixels
6009
py1b = py1a + scaley;
6010
if (py1b >= hh1) py1b = hh1 - 0.001; // fix precision limitation
6012
py1L[py2] = pyl; // 1st overlapping input pixel
6014
for (py1 = pyl, pym = 0; py1 < py1b; py1++, pym++) // loop overlapping input pixels
6016
if (py1 < py1a) { // compute amount of overlap
6017
if (py1+1 < py1b) fy = py1+1 - py1a; // 0.0 to 1.0
6020
else if (py1+1 > py1b) fy = py1b - py1;
6023
ii = py2 * maxmapy + pym; // save it
6024
pymap[ii] = 0.9999 * fy / scaley;
6026
ii = py2 * maxmapy + pym; // set an end marker after
6027
pymap[ii] = -1; // last overlapping pixel
6030
for (px2 = 0; px2 < ww2; px2++) // do same for x-pixels
6032
px1a = px2 * scalex;
6033
px1b = px1a + scalex;
6034
if (px1b >= ww1) px1b = ww1 - 0.001;
6038
for (px1 = pxl, pxm = 0; px1 < px1b; px1++, pxm++)
6041
if (px1+1 < px1b) fx = px1+1 - px1a;
6044
else if (px1+1 > px1b) fx = px1b - px1;
6047
ii = px2 * maxmapx + pxm;
6048
pxmap[ii] = 0.9999 * fx / scalex;
6050
ii = px2 * maxmapx + pxm;
6054
org2x = org1x / scalex; // compute output image rectangle
6055
end2x = (org1x + ww1a) / scalex + 1; // containing any part of input area
6056
if (org2x < 0) org2x = 0; // revised v.10.12
6057
if (end2x > ww2) end2x = ww2;
6059
org2y = org1y / scaley;
6060
end2y = (org1y + hh1a) / scaley + 1;
6061
if (org2y < 0) org2y = 0;
6062
if (end2y > hh2) end2y = hh2;
6064
for (py2 = org2y; py2 < end2y; py2++) // loop output y-pixels
6066
pyl = py1L[py2]; // corresp. 1st input y-pixel
6068
for (px2 = org2x; px2 < end2x; px2++) // loop output x-pixels
6070
pxl = px1L[px2]; // corresp. 1st input x-pixel
6072
red = green = blue = 0; // initz. output pixel
6074
for (py1 = pyl, pym = 0; ; py1++, pym++) // loop overlapping input y-pixels
6076
ii = py2 * maxmapy + pym; // get y-overlap
6078
if (fy < 0) break; // no more pixels
6080
for (px1 = pxl, pxm = 0; ; px1++, pxm++) // loop overlapping input x-pixels
6082
ii = px2 * maxmapx + pxm; // get x-overlap
6084
if (fx < 0) break; // no more pixels
6086
ftot = fx * fy; // area overlap = x * y overlap
6087
pix1 = bmp1 + (py1 * ww1 + px1) * 3;
6088
red += pix1[0] * ftot; // add input pixel * overlap
6089
green += pix1[1] * ftot;
6090
blue += pix1[2] * ftot;
6093
pix2 = bmp2 + (py2 * ww2 + px2) * 3; // save output pixel
6094
pix2[0] = int(red) >> 8;
6095
pix2[1] = int(green) >> 8;
6096
pix2[2] = int(blue) >> 8;
6109
// rotate PXM pixmap through given angle in degrees (+ = clockwise)
6111
PXM * PXM_rotate(PXM *pxm1, double angle)
6113
PXM * PXM_rotate8(PXM *, double);
6114
PXM * PXM_rotate16(PXM *, double);
6120
if (bpc == 8) pxm2 = PXM_rotate8(pxm1,angle);
6121
if (bpc == 16) pxm2 = PXM_rotate16(pxm1,angle);
6126
/**************************************************************************
6128
Rescale 8 bpc image (3 x 8 bits per color) to new width and height.
6129
The scale ratios may be different for width and height.
6132
The input and output images are overlayed, stretching or shrinking the
6133
output pixels as needed. The contribution of each input pixel overlapping
6134
an output pixel is proportional to the area of the output pixel covered by
6135
the input pixel. The contributions of all overlaping input pixels are added.
6136
The work is spread among Nwt threads to reduce the elapsed time on modern
6137
computers having multiple SMP processors.
6139
Example: if the output image is 40% of the input image, then:
6140
outpix[0,0] = 0.16 * inpix[0,0] + 0.16 * inpix[1,0] + 0.08 * inpix[2,0]
6141
+ 0.16 * inpix[0,1] + 0.16 * inpix[1,1] + 0.08 * inpix[2,1]
6142
+ 0.08 * inpix[0,2] + 0.08 * inpix[1,2] + 0.04 * inpix[2,2]
6146
namespace bmp8rescale { // data for threads
6159
int busy[max_threads];
6163
void bmp8_rescale(uint8 *pixmap1x, uint8 *pixmap2x, int ww1x, int hh1x, int ww2x, int hh2x)
6165
using namespace bmp8rescale;
6167
void * bmp8_rescale_thread(void *arg);
6169
int px1, py1, px2, py2;
6170
int pxl, pyl, pxm, pym, ii;
6171
double scalex, scaley;
6172
double px1a, py1a, px1b, py1b;
6182
memset(pixmap2, 0, ww2 * hh2 * 3 * sizeof(uint8)); // clear output pixmap
6184
scalex = 1.0 * ww1 / ww2; // compute x and y scales
6185
scaley = 1.0 * hh1 / hh2;
6187
if (scalex <= 1) maxmapx = 2; // compute max input pixels
6188
else maxmapx = scalex + 2; // mapping into output pixels
6189
maxmapx += 1; // for both dimensions
6190
if (scaley <= 1) maxmapy = 2; // (pixels may not be square)
6191
else maxmapy = scaley + 2;
6194
pymap = (double *) zmalloc(hh2 * maxmapy * sizeof(double)); // maps overlap of < maxmap input
6195
pxmap = (double *) zmalloc(ww2 * maxmapx * sizeof(double)); // pixels per output pixel
6197
py1L = (int *) zmalloc(hh2 * sizeof(int)); // maps first (lowest) input pixel
6198
px1L = (int *) zmalloc(ww2 * sizeof(int)); // per output pixel
6200
for (py2 = 0; py2 < hh2; py2++) // loop output y-pixels
6202
py1a = py2 * scaley; // corresponding input y-pixels
6203
py1b = py1a + scaley;
6204
if (py1b >= hh1) py1b = hh1 - 0.001; // fix precision limitation
6206
py1L[py2] = pyl; // 1st overlapping input pixel
6208
for (py1 = pyl, pym = 0; py1 < py1b; py1++, pym++) // loop overlapping input pixels
6210
if (py1 < py1a) { // compute amount of overlap
6211
if (py1+1 < py1b) fy = py1+1 - py1a; // 0.0 to 1.0
6214
else if (py1+1 > py1b) fy = py1b - py1;
6217
ii = py2 * maxmapy + pym; // save it
6218
pymap[ii] = 0.9999 * fy / scaley;
6220
ii = py2 * maxmapy + pym; // set an end marker after
6221
pymap[ii] = -1; // last overlapping pixel
6224
for (px2 = 0; px2 < ww2; px2++) // do same for x-pixels
6226
px1a = px2 * scalex;
6227
px1b = px1a + scalex;
6228
if (px1b >= ww1) px1b = ww1 - 0.001;
6232
for (px1 = pxl, pxm = 0; px1 < px1b; px1++, pxm++)
6235
if (px1+1 < px1b) fx = px1+1 - px1a;
6238
else if (px1+1 > px1b) fx = px1b - px1;
6241
ii = px2 * maxmapx + pxm;
6242
pxmap[ii] = 0.9999 * fx / scalex;
6244
ii = px2 * maxmapx + pxm;
6248
for (ii = 0; ii < Nwt; ii++) { // start working threads
6250
start_detached_thread(bmp8_rescale_thread,&wtnx[ii]);
6253
for (ii = 0; ii < Nwt; ii++) // wait for all done
6254
while (busy[ii]) zsleep(0.004);
6264
void * bmp8_rescale_thread(void *arg) // worker thread function
6266
using namespace bmp8rescale;
6268
int index = *((int *) arg);
6269
int px1, py1, px2, py2;
6270
int pxl, pyl, pxm, pym, ii;
6271
uint8 *pixel1, *pixel2;
6272
double fx, fy, ftot;
6273
double red, green, blue;
6275
for (py2 = index; py2 < hh2; py2 += Nwt) // loop output y-pixels
6277
pyl = py1L[py2]; // corresp. 1st input y-pixel
6279
for (px2 = 0; px2 < ww2; px2++) // loop output x-pixels
6281
pxl = px1L[px2]; // corresp. 1st input x-pixel
6283
red = green = blue = 0; // initz. output pixel
6285
for (py1 = pyl, pym = 0; ; py1++, pym++) // loop overlapping input y-pixels
6287
ii = py2 * maxmapy + pym; // get y-overlap
6289
if (fy < 0) break; // no more pixels
6291
for (px1 = pxl, pxm = 0; ; px1++, pxm++) // loop overlapping input x-pixels
6293
ii = px2 * maxmapx + pxm; // get x-overlap
6295
if (fx < 0) break; // no more pixels
6297
ftot = fx * fy; // area overlap = x * y overlap
6298
pixel1 = pixmap1 + (py1 * ww1 + px1) * 3;
6299
red += pixel1[0] * ftot; // add input pixel * overlap
6300
green += pixel1[1] * ftot;
6301
blue += pixel1[2] * ftot;
6304
pixel2 = pixmap2 + (py2 * ww2 + px2) * 3; // save output pixel
6317
/**************************************************************************
6319
Rescale 16 bpc image (3 x 16 bits per color) to new width and height.
6320
Identical to bmp8_rescale except for the following:
6326
namespace bmp16rescale { // data for threads
6339
int busy[max_threads];
6343
void bmp16_rescale(uint16 *pixmap1x, uint16 *pixmap2x, int ww1x, int hh1x, int ww2x, int hh2x)
6345
using namespace bmp16rescale;
6347
void * bmp16_rescale_thread(void *arg);
6349
int px1, py1, px2, py2;
6350
int pxl, pyl, pxm, pym, ii;
6351
double scalex, scaley;
6352
double px1a, py1a, px1b, py1b;
6362
memset(pixmap2, 0, ww2 * hh2 * 3 * sizeof(uint16)); // clear output pixmap
6364
scalex = 1.0 * ww1 / ww2; // compute x and y scales
6365
scaley = 1.0 * hh1 / hh2;
6367
if (scalex <= 1) maxmapx = 2; // compute max input pixels
6368
else maxmapx = scalex + 2; // mapping into output pixels
6369
maxmapx += 1; // for both dimensions
6370
if (scaley <= 1) maxmapy = 2; // (pixels may not be square)
6371
else maxmapy = scaley + 2;
6374
pymap = (double *) zmalloc(hh2 * maxmapy * sizeof(double)); // maps overlap of < maxmap input
6375
pxmap = (double *) zmalloc(ww2 * maxmapx * sizeof(double)); // pixels per output pixel
6377
py1L = (int *) zmalloc(hh2 * sizeof(int)); // maps first (lowest) input pixel
6378
px1L = (int *) zmalloc(ww2 * sizeof(int)); // per output pixel
6380
for (py2 = 0; py2 < hh2; py2++) // loop output y-pixels
6382
py1a = py2 * scaley; // corresponding input y-pixels
6383
py1b = py1a + scaley;
6384
if (py1b >= hh1) py1b = hh1 - 0.001; // fix precision limitation
6386
py1L[py2] = pyl; // 1st overlapping input pixel
6388
for (py1 = pyl, pym = 0; py1 < py1b; py1++, pym++) // loop overlapping input pixels
6390
if (py1 < py1a) { // compute amount of overlap
6391
if (py1+1 < py1b) fy = py1+1 - py1a; // 0.0 to 1.0
6394
else if (py1+1 > py1b) fy = py1b - py1;
6397
ii = py2 * maxmapy + pym; // save it
6398
pymap[ii] = 0.9999 * fy / scaley;
6400
ii = py2 * maxmapy + pym; // set an end marker after
6401
pymap[ii] = -1; // last overlapping pixel
6404
for (px2 = 0; px2 < ww2; px2++) // do same for x-pixels
6406
px1a = px2 * scalex;
6407
px1b = px1a + scalex;
6408
if (px1b >= ww1) px1b = ww1 - 0.001;
6412
for (px1 = pxl, pxm = 0; px1 < px1b; px1++, pxm++)
6415
if (px1+1 < px1b) fx = px1+1 - px1a;
6418
else if (px1+1 > px1b) fx = px1b - px1;
6421
ii = px2 * maxmapx + pxm;
6422
pxmap[ii] = 0.9999 * fx / scalex;
6424
ii = px2 * maxmapx + pxm;
6428
for (ii = 0; ii < Nwt; ii++) { // start working threads
6430
start_detached_thread(bmp16_rescale_thread,&wtnx[ii]);
6433
for (ii = 0; ii < Nwt; ii++) // wait for all done
6434
while (busy[ii]) zsleep(0.004);
6444
void * bmp16_rescale_thread(void *arg) // worker thread function
6446
using namespace bmp16rescale;
6448
int index = *((int *) arg);
6449
int px1, py1, px2, py2;
6450
int pxl, pyl, pxm, pym, ii;
6451
uint16 *pixel1, *pixel2;
6452
double fx, fy, ftot;
6453
double red, green, blue;
6455
for (py2 = index; py2 < hh2; py2 += Nwt) // loop output y-pixels
6457
pyl = py1L[py2]; // corresp. 1st input y-pixel
6459
for (px2 = 0; px2 < ww2; px2++) // loop output x-pixels
6461
pxl = px1L[px2]; // corresp. 1st input x-pixel
6463
red = green = blue = 0; // initz. output pixel
6465
for (py1 = pyl, pym = 0; ; py1++, pym++) // loop overlapping input y-pixels
6467
ii = py2 * maxmapy + pym; // get y-overlap
6469
if (fy < 0) break; // no more pixels
6471
for (px1 = pxl, pxm = 0; ; px1++, pxm++) // loop overlapping input x-pixels
6473
ii = px2 * maxmapx + pxm; // get x-overlap
6475
if (fx < 0) break; // no more pixels
6477
ftot = fx * fy; // area overlap = x * y overlap
6478
pixel1 = pixmap1 + (py1 * ww1 + px1) * 3;
6479
red += pixel1[0] * ftot; // add input pixel * overlap
6480
green += pixel1[1] * ftot;
6481
blue += pixel1[2] * ftot;
6484
pixel2 = pixmap2 + (py2 * ww2 + px2) * 3; // save output pixel
6497
/**************************************************************************
6499
PXM *pxm2 = PXM_rotate8(PXM *pxm1, double angle)
6501
Rotate PXM-8 pixmap through an arbitrary angle (degrees).
6503
The returned image has the same size as the original, but the
6504
pixmap size is increased to accomodate the rotated image.
6505
(e.g. a 100x100 image rotated 45 deg. needs a 142x142 pixmap).
6506
The parameters ww and hh are the dimensions of the input
6507
pixmap, and are updated to the dimensions of the output pixmap.
6509
The space added around the rotated image is black (RGB 0,0,0).
6510
Angle is in degrees. Positive direction is clockwise.
6511
Speed is about 3 million pixels/sec/thread for a 2.4 GHz CPU.
6512
Loss of resolution is less than 1 pixel.
6514
Work is divided among Nwt threads to gain speed.
6516
v.9.3: affine transform instead of trig functions, for speed
6518
***************************************************************************/
6532
PXM * PXM_rotate8(PXM *pxm1, double anglex)
6534
using namespace rotpxm8;
6536
void *PXM_rotate8_thread(void *);
6543
pixmap1 = (uint8 *) pxm1->bmp;
6546
while (angle < -180) angle += 360; // normalize, -180 to +180
6547
while (angle > 180) angle -= 360;
6548
angle = angle * pi / 180; // radians, -pi to +pi
6550
if (fabs(angle) < 0.001) { // angle = 0 within my precision
6551
pxm2 = PXM_make(ww1,hh1,8); // return a copy of the input
6552
pixmap2 = (uint8 *) pxm2->bmp;
6553
cc = ww1 * hh1 * 3 * sizeof(uint8);
6554
memcpy(pixmap2,pixmap1,cc);
6558
ww2 = ww1*fabs(cos(angle)) + hh1*fabs(sin(angle)); // rectangle containing rotated image
6559
hh2 = ww1*fabs(sin(angle)) + hh1*fabs(cos(angle));
6561
pxm2 = PXM_make(ww2,hh2,8);
6562
pixmap2 = (uint8 *) pxm2->bmp;
6564
for (ii = 0; ii < Nwt; ii++) // start worker threads
6565
start_detached_thread(PXM_rotate8_thread,&wtnx[ii]);
6566
zadd_locked(busy,+Nwt);
6568
while (busy) zsleep(0.004); // wait for completion
6573
void * PXM_rotate8_thread(void *arg)
6575
using namespace rotpxm8;
6577
int index = *((int *) (arg));
6578
int px2, py2, px0, py0;
6579
uint8 *pix0, *pix1, *pix2, *pix3;
6581
double f0, f1, f2, f3, red, green, blue;
6582
double a, b, d, e, ww15, hh15, ww25, hh25;
6594
for (py2 = index; py2 < hh2; py2 += Nwt) // loop through output pixels
6595
for (px2 = 0; px2 < ww2; px2++) // outer loop y
6597
px1 = a * (px2 - ww25) + b * (py2 - hh25) + ww15; // (px1,py1) = corresponding v.9.3
6598
py1 = d * (px2 - ww25) + e * (py2 - hh25) + hh15; // point within input pixels
6600
px0 = px1; // pixel containing (px1,py1)
6603
if (px1 < 0 || px0 >= ww1-1 || py1 < 0 || py0 >= hh1-1) { // if outside input pixel array
6604
pix2 = pixmap2 + (py2 * ww2 + px2) * 3; // output is black
6605
pix2[0] = pix2[1] = pix2[2] = 0;
6609
pix0 = pixmap1 + (py0 * ww1 + px0) * 3; // 4 input pixels based at (px0,py0)
6610
pix1 = pix0 + ww1 * 3;
6614
f0 = (px0+1 - px1) * (py0+1 - py1); // overlap of (px1,py1)
6615
f1 = (px0+1 - px1) * (py1 - py0); // in each of the 4 pixels
6616
f2 = (px1 - px0) * (py0+1 - py1);
6617
f3 = (px1 - px0) * (py1 - py0);
6619
red = f0 * pix0[0] + f1 * pix1[0] + f2 * pix2[0] + f3 * pix3[0]; // sum the weighted inputs
6620
green = f0 * pix0[1] + f1 * pix1[1] + f2 * pix2[1] + f3 * pix3[1];
6621
blue = f0 * pix0[2] + f1 * pix1[2] + f2 * pix2[2] + f3 * pix3[2];
6623
pix2 = pixmap2 + (py2 * ww2 + px2) * 3; // output pixel
6629
zadd_locked(busy,-1);
6634
/**************************************************************************
6636
PXM *pxm2 = PXM_rotate16(PXM *pxm1, double angle)
6637
Rotate PXM-16 pixmap through an arbitrary angle (degrees).
6638
Identical to PXM_rotate8() except for:
6645
namespace rotpxm16 {
6657
PXM * PXM_rotate16(PXM *pxm1, double anglex)
6659
using namespace rotpxm16;
6661
void *PXM_rotate16_thread(void *);
6668
pixmap1 = (uint16 *) pxm1->bmp;
6671
while (angle < -180) angle += 360; // normalize, -180 to +180
6672
while (angle > 180) angle -= 360;
6673
angle = angle * pi / 180; // radians, -pi to +pi
6675
if (fabs(angle) < 0.001) { // angle = 0 within my precision
6676
pxm2 = PXM_make(ww1,hh1,16); // return a copy of the input
6677
pixmap2 = (uint16 *) pxm2->bmp;
6678
cc = ww1 * hh1 * 3 * sizeof(uint16);
6679
memcpy(pixmap2,pixmap1,cc);
6683
ww2 = ww1*fabs(cos(angle)) + hh1*fabs(sin(angle)); // rectangle containing rotated image
6684
hh2 = ww1*fabs(sin(angle)) + hh1*fabs(cos(angle));
6686
pxm2 = PXM_make(ww2,hh2,16);
6687
pixmap2 = (uint16 *) pxm2->bmp;
6689
for (ii = 0; ii < Nwt; ii++) // start worker threads
6690
start_detached_thread(PXM_rotate16_thread,&wtnx[ii]);
6691
zadd_locked(busy,+Nwt);
6693
while (busy) zsleep(0.004); // wait for completion
6698
void * PXM_rotate16_thread(void *arg)
6700
using namespace rotpxm16;
6702
int index = *((int *) (arg));
6703
int px2, py2, px0, py0;
6704
uint16 *pix0, *pix1, *pix2, *pix3;
6706
double f0, f1, f2, f3, red, green, blue;
6707
double a, b, d, e, ww15, hh15, ww25, hh25;
6719
for (py2 = index; py2 < hh2; py2 += Nwt) // loop through output pixels
6720
for (px2 = 0; px2 < ww2; px2++) // outer loop y
6722
px1 = a * (px2 - ww25) + b * (py2 - hh25) + ww15; // (px1,py1) = corresponding v.9.3
6723
py1 = d * (px2 - ww25) + e * (py2 - hh25) + hh15; // point within input pixels
6725
px0 = px1; // pixel containing (px1,py1)
6728
if (px1 < 0 || px0 >= ww1-1 || py1 < 0 || py0 >= hh1-1) { // if outside input pixel array
6729
pix2 = pixmap2 + (py2 * ww2 + px2) * 3; // output is black
6730
pix2[0] = pix2[1] = pix2[2] = 0;
6734
pix0 = pixmap1 + (py0 * ww1 + px0) * 3; // 4 input pixels based at (px0,py0)
6735
pix1 = pix0 + ww1 * 3;
6739
f0 = (px0+1 - px1) * (py0+1 - py1); // overlap of (px1,py1)
6740
f1 = (px0+1 - px1) * (py1 - py0); // in each of the 4 pixels
6741
f2 = (px1 - px0) * (py0+1 - py1);
6742
f3 = (px1 - px0) * (py1 - py0);
6744
red = f0 * pix0[0] + f1 * pix1[0] + f2 * pix2[0] + f3 * pix3[0]; // sum the weighted inputs
6745
green = f0 * pix0[1] + f1 * pix1[1] + f2 * pix2[1] + f3 * pix3[1];
6746
blue = f0 * pix0[2] + f1 * pix1[2] + f2 * pix2[2] + f3 * pix3[2];
6748
pix2 = pixmap2 + (py2 * ww2 + px2) * 3; // output pixel
6754
zadd_locked(busy,-1);
6759
// replace blue = 0 pixels with blue = 2 in a 16-bit pixmap
6760
// (blue = 0 reserved for pixels voided by warp or overlay offsets)
6761
// all callers of vpixel() need to do this
6762
// needs about 0.013 seconds for 10 megapixel image and 3.3 GHz processor
6764
void PXM_fixblue(PXM *pxm) // v.11.07
6767
uint16 *pixel, *pixel0, *pixelN;
6769
size = pxm->ww * pxm->hh * 3;
6770
pixel0 = (uint16 *) pxm->bmp + 2;
6771
pixelN = (uint16 *) pixel0 + size;
6773
for (pixel = pixel0; pixel < pixelN; pixel += 3) // +3 is +6 bytes
6774
if (! *pixel) *pixel = 2;