1
/**************************************************************************
3
fotoxx digital photo edit program
5
Copyright 2007, 2008, 2009 Michael Cornelison
6
source URL: kornelix.squarespace.com
7
contact: kornelix@yahoo.de
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
#include <FreeImage.h>
27
#define fversion "fotoxx v.8.6.2 2009.10.29" // version and release date
28
#define flicense "Free software - GNU General Public License v.3"
29
#define fhomepage "http://kornelix.squarespace.com/fotoxx"
30
#define ftranslators "Translators:" \
31
"\n Stanislas Zeller, Antonio Sánchez, Miguel Bouzada," \
32
"\n The Hamsters, Helge Soencksen, Jie Luo, Pavel Cidlina" \
33
"\n Eugenio Baldi, Santiago Torres Batán"
34
#define fcredits "Programs used: FreeImage, ufraw, exiftool"
35
#define fcontact "Bug reports: kornelix@yahoo.de"
40
#define textwin GTK_TEXT_WINDOW_TEXT // GDK window of GTK text view
41
#define nodither GDK_RGB_DITHER_NONE
42
#define ALWAYS GTK_POLICY_ALWAYS
43
#define NEVER GTK_POLICY_NEVER
44
#define colorspace GDK_COLORSPACE_RGB
45
#define lineattributes GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER
47
// FREEIMAGE definitions
49
#define FIF FREE_IMAGE_FORMAT // file types, jpeg or tiff
50
#define FIB FIBITMAP // FreeImage bitmap
51
#define RR FI_RGBA_RED // FIB-24 pixel color order
52
#define GG FI_RGBA_GREEN
53
#define BB FI_RGBA_BLUE
57
#define mwinww 800 // main window default size
59
#define mega (1024 * 1024)
60
#define track(name) printf("track: %s \n",#name); // debug path tracker
61
#define ftrash "Desktop/fotoxx-trash" // trash folder location
62
#define Pundo_max 50 // max undo/redo files
63
#define def_jpeg_quality "90" // default jpeg quality v.8.3
65
#define max_images 100000 // max. images (m_index_tt()) v.8.4
66
#define maxtag1 30 // max tag cc for one tag
67
#define maxtag2 300 // max tag cc for one image file
68
#define maxtag3 20000 // max tag cc for all image files
69
#define maxtag4 200 // max tag cc for search tags
70
#define maxtag5 200 // max tag cc for recent tags
71
#define maxntags 1000 // max tag count for all images
72
#define maxtagF 300 // max image search /path*/file*
74
#define exif_tags_key "UserComment" // EXIF key for image tags
75
#define exif_date_key "DateTimeOriginal" // EXIF key for image date
76
#define exif_width_key "ExifImageWidth" // EXIF key for image width
77
#define exif_height_key "ExifImageHeight" // EXIF key for image height
78
#define exif_orientation_key "Orientation" // EXIF key for image orientation
80
#define bmpixel(rgb,px,py) ((uint16 *) rgb->bmp+((py)*(rgb->ww)+(px))*3) // return RGB pixel[3] at (px,py)
81
#define brightness(pix) (0.25*pix[0]+0.65*pix[1]+0.10*pix[2]) // pixel brightness, 0-64K
82
#define redness(pix) (25 * pix[0] / (brightness(pix)+1)) // pixel redness, 0-100%
84
#define pixed_undomaxmem (100 * mega) // pixel edit max. memory alloc.
85
#define pixed_undomaxpix (mega) // pixel edit max. pixel blocks
87
namespace image_navi { // zfuncs: image_gallery() etc.
88
extern int xwinW, xwinH; // image gallery window size
89
extern int thumbsize; // thumbnail image size
93
extern char zlanguage[8]; // current language lc_RC v.8.5
96
GtkWidget *mWin, *drWin, *mVbox; // main and drawing window
97
GtkWidget *mMbar, *mTbar, *STbar; // menu bar, tool bar, status bar
98
GtkWidget *brightgraph = 0; // brightness distribution graph
99
GtkWidget *drbrightgraph = 0; // drawing area
101
GdkGC *gdkgc = 0; // GDK graphics context
102
GdkColor black, white, red, green;
103
GdkColormap *colormap = 0;
104
uint maxcolor = 0xffff;
105
GdkCursor *arrowcursor = 0;
106
GdkCursor *dragcursor = 0;
107
GdkCursor *busycursor = 0;
109
char PIDstring[12]; // process PID as string
110
pthread_t tid_fmain = 0; // thread ID for main()
111
int NWthreads = 0; // working threads to use
112
int maxWthreads = 4; // max. worker threads to use
113
int wtindex[4] = { 0, 1, 2, 3 }; // internal thread ID
115
int Fexiftool = 0; // exiftool program available
116
int Fufraw = 0; // ufraw program is available
117
int Fprintoxx = 0; // printoxx program available
118
int Fxdgopen = 0; // xdg-open program available
119
int Fshutdown = 0; // app shutdown underway
120
int Fdebug = 0; // debug flag
121
int Wrepaint = 0; // request window paint
122
int Wpainted = 0; // window was repainted
123
int Fmodified = 0; // image was edited/modified
124
int Fsaved = 0; // image saved since last mod
125
int Fpreview = 0; // use window image for edits
126
int Fblowup = 0; // zoom small images to window size
127
int Fshowarea = 0; // show selected area outline
128
int SBupdate = 0; // request to update status bar
129
int PercentDone = 0; // % completion in status bar
130
int Fautolens = 0; // lens parameter search underway
131
int Fsearchlist = 0; // file list via search tags
132
int Fimageturned = 0; // image was turned when loaded
133
int Fslideshow = 0; // slide show mode is active
134
int SS_interval = 3; // slide show interval
135
int SS_timer = 0; // slide show timer
136
int Frgbmode = 0; // RGB mode active
138
char *image_file = 0; // current image file
139
char *recentfiles[40]; // 40 most recent image files
140
int Nrecentfiles = 40; // recent file list size
141
double file_MB; // disk file size, MB
142
int file_bpp; // disk file bits/pixel
143
char file_type[8]; // "jpeg" or "tiff" or "other"
144
char *asstagsfile = 0; // assigned tags file
145
char *topdirk = 0; // top-level image directory
146
char *topmenu; // latest top-menu selection
147
char jpeg_quality[8] = def_jpeg_quality; // jpeg file compression quality
149
// fotoxx RGB pixmaps // v.6.5
151
typedef struct { // RGB pixmap
153
int ww, hh, bpp; // width, height, bits per pixel
154
void *bmp; // uint8*/uint16* (bpp=24/48)
157
RGB *Frgb24 = 0; // input file 1 pixmap, RGB-24
158
RGB *Frgb48 = 0; // input file 1 pixmap, RGB-48
159
RGB *Grgb48 = 0; // input file 2 pixmap, RGB-48
160
RGB *E1rgb48 = 0; // edit pixmap, base image
161
RGB *E3rgb48 = 0; // edit pixmap, edited image
162
RGB *E9rgb48 = 0; // scratch image for some functions
163
RGB *Drgb24 = 0; // drawing window pixmap
164
RGB *A1rgb48 = 0, *A2rgb48 = 0; // align image pixmaps (HDR, pano)
166
int Fww, Fhh, Gww, Ghh; // input image dimensions
167
int E1ww, E1hh, E3ww, E3hh; // edit image dimensions
168
int Dww = mwinww, Dhh = mwinhh; // drawing window size (defaults)
169
int A1ww, A1hh, A2ww, A2hh; // alignment image dimensions
171
double Fzoom = 0; // image zoom scale (0 = fit window)
172
int zoomx = 0, zoomy = 0; // req. zoom center of window
173
double Mscale = 1; // scale factor, file to window
174
int Iww, Ihh; // current image size at 1x
175
int iww, ihh; // area in drawing window at Mscale
176
int dww, dhh; // drawing window image, iww * Mscale
177
int Iorgx, Iorgy; // drawing window origin in image
178
int Dorgx, Dorgy; // image origin in drawing window
180
mutex pixmaps_lock; // lock for accessing RGB pixmaps
181
int menu_lock = 0; // lock for some menu functions
183
char *undo_files = 0; // undo/redo stack, image files
184
int Pundo = 0; // undo/redo stack position
185
int Pumax = 0; // undo/redo stack depth
187
int Mbutton = 0; // mouse button, 1/3 = left/right
188
int LMclick = 0, RMclick = 0; // mouse left, right click
189
int Mxclick, Myclick; // mouse click position
190
int Mxposn, Myposn; // mouse move position
191
int Mxdown, Mydown, Mxdrag, Mydrag; // mouse drag vector
192
int Mdrag = 0; // mouse drag underway
193
int Mcapture = 0; // mouse captured by edit function
195
int KBcapture = 0; // KB key captured by edit function
196
int KBkey = 0; // active keyboard key
198
int Ntoplines = 0, Nptoplines = 0; // lines overlayed on image in window
199
int toplinex1[4], topliney1[4], toplinex2[4], topliney2[4];
200
int ptoplinex1[4], ptopliney1[4], ptoplinex2[4], ptopliney2[4];
202
int toparc = 0, ptoparc = 0; // arc (circle/ellipse) on top
203
int toparcx,toparcy,toparcw,toparch; // of image in window
204
int ptoparcx,ptoparcy,ptoparcw,ptoparch;
206
zdialog *zdtags = null; // edit tags zdialog
207
zdialog *zdrename = null; // rename file zdialog
208
zdialog *zdsela = null; // select area dialog
209
zdialog *zdedit = null; // image edit dialog
210
zdialog *zdburn = null; // burn CD/DVD dialog
212
// pano and HDR control data
214
int curr_lens = 0; // current lens, 0-3
215
int lens_cc = 19; // lens name cc limit
216
char *lens4_name[4]; // names for 4 lenses
217
double lens4_mm[4], lens4_bow[4]; // characteristics for 4 lenses
218
double lens_mm, lens_bow; // current lens characteristics
219
double pano_prealign_size = 500; // pre-align image height
220
double pano_image_increase = 1.6; // image size increase per stage
221
double pano_blend_decrease = 0.8; // blend width decrease per stage
222
double pano_min_alignwidth = 0.10; // min. align area, 10% width
223
double pano_max_alignwidth = 0.20; // max. align area, 20%
224
double pano_ycurveF = 1.41; // image curve, y-adjust factor
226
int fullSize, alignSize; // full and align image sizes
227
int showRedpix = 0; // flag, highlight alignment pixels
228
int xshrink, yshrink; // image shrinkage (pano, HDF)
229
int pxL, pxH, pyL, pyH; // image overlap area
230
int pxM, pxmL, pxmH; // align area width mid/low/high
231
int pyM, pymL, pymH; // align area height
232
int Nalign = 0; // counts alignment cycles
233
int aligntype = 0; // align type (HDR, HDF, pano)
234
int alignWidth, alignHeight; // pano/HDR/HDF alignment area
235
int overlapixs = 0; // overlapped pixels count
236
int pixsamp = 5000; // pixel sample size
237
char *redpixels = 0; // flags edge pixels for image align
239
double Bratios1[3][256], Bratios2[3][256]; // brightness ratios/color/brightness
240
double R12match[65536], G12match[65536], B12match[65536]; // image1/2 color matching factors
241
double R21match[65536], G21match[65536], B21match[65536]; // image2/1 color matching factors
242
double Radjust, Gadjust, Badjust; // RGB manual adjustmants
243
double xoff, yoff, toff; // align data: x, y, theta
244
double warpxu, warpyu, warpxl, warpyl; // pano image2 warp: upper/lower corners
245
double xoffB, yoffB, toffB; // align data: current best values
246
double warpxuB, warpyuB, warpxlB, warpylB; // pano image2 warp: current best values
247
double matchlev, matchB; // image alignment match level
251
int main(int argc, char * argv[]); // main program
252
int gtkinitfunc(void *data); // GTK initz. function
253
int gtimefunc(void *arg); // periodic function
254
int delete_event(); // window delete event function
255
void destroy_event(); // window destroy event function
256
int mwpaint(); // window repaint - expose event
257
void mwpaint2(); // window repaint - image modified
258
void update_statusbar(); // update main window status bar
259
void mouse_event(GtkWidget *, GdkEventButton *, void *); // mouse event function
260
int KBpress(GtkWidget *, GdkEventKey *, void *); // KB key press event function
261
int KBrelease(GtkWidget *, GdkEventKey *, void *); // KB key release event
262
void paint_toplines(int arg); // paint lines on image
263
void paint_toparc(int arg); // paint arc on image
264
void draw_line(int x1, int y1, int x2, int y2); // draw line, image space
265
void erase_line(int x1, int y1, int x2, int y2); // erase line
266
void topmenufunc(GtkWidget *, const char *menu); // menu function
268
typedef void CBfunc(); // callback function type
269
CBfunc *mouseCBfunc = 0; // current mouse handler function
270
CBfunc *KBkeyCBfunc = 0; // current KB handler function
274
void m_gallery(GtkWidget *, const char *); // show image gallery window
275
void m_gallery2(char *); // converts function types
276
void m_open(GtkWidget *, const char *); // open image file (menu)
277
void m_open_drag(int x, int y, char *file); // open drag-drop file
278
void m_recent(GtkWidget *, const char *); // open recently accessed file
279
void add_recent_file(const char *file); // add file to recent file list
280
void f_open(const char *file); // open file helper function
281
void m_raw(GtkWidget *, const char *); // open RAW file
282
void m_prev(GtkWidget *, const char *); // open previous file
283
void m_next(GtkWidget *, const char *); // open next file
284
void m_save(GtkWidget *, const char *); // save modified image to same file
285
void m_saveas(GtkWidget *, const char *); // save modified image to another file
286
void f_save(cchar *outfile, cchar *format); // save file helper function
287
void m_print(GtkWidget *, const char *); // print image file(s)
288
void m_trash(GtkWidget *, const char *); // move image to trash
289
void m_rename(GtkWidget *, const char *); // rename file menu function
290
void rename_dialog(); // start rename file dialog
291
void m_quit(GtkWidget *, const char *); // exit application
295
void m_zoom(GtkWidget *, const char *); // zoom image +/-
296
void m_montest(GtkWidget *, const char *); // check monitor
297
void m_index_tt(GtkWidget *, const char *); // index tags and thumbnails
298
void m_brightgraph(GtkWidget *, const char *); // start brightness dist. graph
299
void brightgraph_paint(); // update brightness dist. graph
300
void brightgraph_destroy(); // remove graph window
301
void m_clone(GtkWidget *, const char *); // start another fotoxx instance
302
void m_slideshow(GtkWidget *, const char *); // slideshow mode
303
void m_RGB(GtkWidget *, const char *); // show RGB values at mouse click
304
void m_parms(GtkWidget *, const char *); // edit parameters
305
void m_lang(GtkWidget *, const char *); // change language
306
void m_launcher(GtkWidget *, const char *); // make desktop icon/launcher
307
void m_multiraw(GtkWidget *, const char *); // convert multiple raw files to tiff
308
void m_burn(GtkWidget *, const char *); // burn images to CD/DVD
309
void burn_insert_file(const char *); // called from image gallery window
311
// tags and EXIF functions
313
void m_edit_tags(GtkWidget *, const char *); // edit tags menu function
314
void edit_tags_dialog(); // start edit tags dialog
315
void load_filetags(const char *file); // load tags from an image file
316
void update_filetags(const char *file); // write updated tags to image file
317
void load_asstags(); // load all assigned tags
318
void update_asstags(const char *file, int del = 0); // update assigned tags file
319
void m_search_tags(GtkWidget *, const char *); // search images for matching tags
320
void m_exif_list(GtkWidget *, const char *); // list EXIF data to popup window
321
char ** exif_get(cchar *file, cchar **keys, int nkeys); // get EXIF data for given key(s)
322
int exif_set(cchar *file, cchar **keys, cchar **text, int nkeys); // set EXIF data for given key(s)
323
int exif_copy(cchar *f1, cchar *f2, cchar **k, cchar **t, int nk); // copy EXIF data + opt. updates
325
// select area functions
327
void m_select_mouse(GtkWidget *, const char *); // select area to edit - mouse
328
void m_select_color(GtkWidget *, const char *); // select area to edit - color
329
void m_select_show(GtkWidget *, const char *); // show area outline
330
void m_select_hide(GtkWidget *, const char *); // hide area outline
331
void m_select_edgecalc(GtkWidget *, const char *); // calculate distances from edge
332
void m_select_invert(GtkWidget *, const char *); // invert area
333
void m_select_disable(GtkWidget *, const char *); // disable area (menu)
334
void m_select_delete(GtkWidget *, const char *); // delete area (menu)
335
void select_disable(); // disable area (callable)
336
void select_delete(); // delete area (callable)
340
void m_whitebal(GtkWidget *, const char *); // adjust white balance
341
void m_flatten(GtkWidget *, const char *); // flatten brightness distribution
342
void m_tune(GtkWidget *, const char *); // brightness / color adjustments
343
void m_redeye(GtkWidget *, const char *); // red-eye removal
344
void m_blur(GtkWidget *, const char *); // blur image
345
void m_sharpen(GtkWidget *, const char *); // sharpen image
346
void m_denoise(GtkWidget *, const char *); // image noise reduction
347
void m_resize(GtkWidget *, const char *); // resize image
348
void m_trim(GtkWidget *, const char *); // trim image
349
void m_rotate(GtkWidget *, const char *); // rotate image
350
void m_unbend(GtkWidget *, const char *); // fix perspective problems
351
void m_WarpA(GtkWidget *, const char *); // warp image area
352
void m_WarpI(GtkWidget *, const char *); // warp image globally
353
void m_colordep(GtkWidget *, const char *); // set color depth 1-16 bits/color
354
void m_draw(GtkWidget *, const char *); // make simulated drawing
355
void m_emboss(GtkWidget *, const char *); // make simulated embossing
356
void m_tiles(GtkWidget *, const char *); // make simulated tiles (pixelate)
357
void m_painting(GtkWidget *, const char *); // make simulated painting
358
void m_pixedit(GtkWidget *, const char *); // edit individual pixels
359
void m_HDR(GtkWidget *, const char *); // make HDR image
360
void m_HDF(GtkWidget *, const char *); // make HDF image
361
void m_pano(GtkWidget *, const char *); // make panorama image
363
// pano and HDR common functions
365
int sigdiff(double d1, double d2, double signf); // test for significant difference
366
void getAlignArea(); // get image overlap area
367
void getBrightRatios(); // get color brightness ratios
368
void setColorfixFactors(int state); // set color matching factors
369
void flagEdgePixels(); // flag high-contrast pixels
370
double matchImages(); // match images in overlap region
371
double matchPixels(uint16 *pix1, uint16 *pix2); // match two pixels
372
int vpixel(RGB *rgb, double px, double py, uint16 *vpix); // get virtual pixel at (px,py)
374
// edit support functions
376
typedef void * threadfunc(void *); // edit thread function
378
int edit_setup(int preview, int delsa); // start new edit transaction
379
void edit_cancel(); // cancel edit
380
void edit_done(); // commit edit, add undo stack
381
void edit_undo(); // undo edit, revert
382
void edit_redo(); // redo the last undo
383
void edit_fullsize(); // convert to full-size pixmaps
384
void edit_progress(int done, int goal); // update status bar progress
386
void start_thread(threadfunc func, void *arg); // start a working thread
387
void signal_thread(); // signal work is pending
388
void wait_thread_idle(); // wait for work complete
389
void wrapup_thread(int command); // wait for exit or command exit
390
void thread_idle_loop(); // wait for work or exit command
391
void exit_thread(); // exit thread unconditionally
392
int thread_working(); // thread is working on edit function
394
void m_undo(GtkWidget *, const char *); // undo one edit
395
void m_redo(GtkWidget *, const char *); // redo one edit
396
void save_undo(); // undo/redo save function
397
void load_undo(); // undo/redo read function
399
// other support functions
401
void m_help(GtkWidget *, const char *); // various help functions
402
int load_fotoxx_state(); // load state from prior session
403
int save_fotoxx_state(); // save state for next session
404
void free_resources(); // free all allocated resources
405
int mod_keep(); // query keep/discard edited image
406
int menulock(int lock); // lock/unlock menu for some funcs
407
void turn_image(int angle); // turn image (upright)
408
void FI_error(FIF fif, const char *message); // catch FreeImage error messages
410
// RGB pixmap and FI bitmap conversion functions // v.6.5
412
RGB * RGB_make(int ww, int hh, int bpp); // initialize RGB pixmap
413
void RGB_free(RGB *rgb); // free RGB pixmap
414
RGB * RGB_copy(RGB *rgb); // copy RGB pixmap
415
RGB * RGB_copy_area(RGB *rgb, int orgx, int orgy, int ww, int hh); // copy section of RGB pixmap
416
RGB * RGB_convbpp(RGB *rgb); // convert from 24/48 to 48/24 bpp
417
FIB * RGB_FIB(RGB *rgb); // convert RGB to FI bitmap
418
RGB * FIB_RGB(FIB *fib); // convert FI to RGB pixmap
419
PXB * RGB_PXB(RGB *rgb); // convert RGB pixmap to pixbuf
420
RGB * PXB_RGB(PXB *pxb); // convert pixbuf to RGB pixmap
421
RGB * RGB_rescale(RGB *rgb, int ww, int hh); // rescale RGB pixmap (ww/hh)
422
RGB * RGB_rotate(RGB *rgb, double angle); // rotate RGB pixmap
423
RGB * image_load(const char *filespec, int bpp); // image file >> RGB-24/48 pixmap
425
// translatable strings used in multiple dialogs
427
const char *Bsavetoedit;
428
const char *Bexiftoolmissing;
429
const char *Bopenrawfile;
436
const char *Bundolast;
437
const char *Bundoall;
444
const char *Bsuspend;
450
const char *Bedgecalc;
451
const char *Bblendwidth;
452
const char *Bdeletearea;
455
const char *Bpercent;
457
const char *Bproceed;
461
const char *Bbrightness;
463
const char *Blighter;
467
/**************************************************************************
468
main program and GTK/GDK functions
469
***************************************************************************/
471
int main(int argc, char *argv[])
475
printf(fversion "\n"); // print version
476
if (argc > 1 && strEqu(argv[1],"-v")) return 0;
478
gtk_init(&argc,&argv); // initz. GTK
481
initz_appfiles("fotoxx",null); // get app directories
482
load_fotoxx_state(); // restore data from last session
484
for (int ii = 1; ii < argc; ii++) // command line options
486
if (strEqu(argv[ii],"-d")) // -d (debug flag)
488
else if (strEqu(argv[ii],"-l") && argc > ii+1) // -l language code
489
strncpy0(lang,argv[++ii],7);
490
else image_file = strdupz(argv[ii]); // initial file or directory
493
ZTXinit(lang); // setup translations
495
mWin = gtk_window_new(GTK_WINDOW_TOPLEVEL); // create main window
496
gtk_window_set_title(GTK_WINDOW(mWin),fversion);
497
gtk_window_set_position(GTK_WINDOW(mWin),GTK_WIN_POS_CENTER);
498
gtk_window_set_default_size(GTK_WINDOW(mWin),Dww,Dhh);
500
mVbox = gtk_vbox_new(0,0); // add vert. packing box
501
gtk_container_add(GTK_CONTAINER(mWin),mVbox);
503
mMbar = create_menubar(mVbox,16); // menus / sub-menus
505
GtkWidget *mFile = add_menubar_item(mMbar,ZTX("File"),topmenufunc);
506
add_submenu_item(mFile, ZTX("Image Gallery"), "m-gallery.png", m_gallery);
507
add_submenu_item(mFile, ZTX("Open RAW File"), "m-open.png", m_raw);
508
add_submenu_item(mFile, ZTX("Open Image File"), "m-open.png", m_open);
509
add_submenu_item(mFile, ZTX("Open Recent File"), "m-open.png", m_recent);
510
add_submenu_item(mFile, ZTX("Save to Same File"), "m-save.png", m_save);
511
add_submenu_item(mFile, ZTX("Save to New File"), "m-save.png", m_saveas);
512
add_submenu_item(mFile, ZTX("Print Image File"), "m-print.png", m_print);
513
add_submenu_item(mFile, ZTX("Trash Image File"), "m-trash.png", m_trash);
514
add_submenu_item(mFile, ZTX("Rename Image File"), "m-rename.png", m_rename);
515
add_submenu_item(mFile, ZTX("Quit fotoxx"), "m-quit.png", m_quit);
517
GtkWidget *mTools = add_menubar_item(mMbar,ZTX("Tools"),topmenufunc);
518
add_submenu_item(mTools, ZTX("Check Monitor"), "m-montest.png", m_montest);
519
add_submenu_item(mTools, ZTX("Index Tags and Thumbs"), "m-index.png", m_index_tt);
520
add_submenu_item(mTools, ZTX("Brightness Graph"), "m-distr.png", m_brightgraph);
521
add_submenu_item(mTools, ZTX("Clone fotoxx"), "m-clone.png", m_clone);
522
add_submenu_item(mTools, ZTX("Slide Show"), "m-slideshow.png", m_slideshow);
523
add_submenu_item(mTools, ZTX("Show RGB"), "m-RGB.png", m_RGB);
524
add_submenu_item(mTools, ZTX("Lens Parameters"), "m-parms.png", m_parms);
525
add_submenu_item(mTools, ZTX("Change Language"), "m-lang.png", m_lang);
526
add_submenu_item(mTools, ZTX("Create Launcher"), "m-launcher.png", m_launcher);
527
add_submenu_item(mTools, ZTX("Convert multiple RAWs"), "m-multiraw.png", m_multiraw);
528
add_submenu_item(mTools, ZTX("Burn Images to CD/DVD"), "m-burn.png", m_burn);
530
GtkWidget *mTags = add_menubar_item(mMbar,ZTX("Tags"),topmenufunc);
531
add_submenu_item(mTags, ZTX("Edit Tags"), "m-tags.png", m_edit_tags);
532
add_submenu_item(mTags, ZTX("Search Tags"), "m-tags.png", m_search_tags);
533
add_submenu_item(mTags, ZTX("Basic EXIF data"), "m-exif.png", m_exif_list);
534
add_submenu_item(mTags, ZTX("All EXIF data"), "m-exif.png", m_exif_list);
536
GtkWidget *mArea = add_menubar_item(mMbar,ZTX("Area"),topmenufunc);
537
add_submenu_item(mArea, ZTX("Select Area -mouse"), "m-select.png", m_select_mouse);
538
add_submenu_item(mArea, ZTX("Select Area -color"), "m-select.png", m_select_color);
539
add_submenu_item(mArea, ZTX("Show Area"), "m-select.png", m_select_show);
540
add_submenu_item(mArea, ZTX("Hide Area"), "m-select.png", m_select_hide);
541
add_submenu_item(mArea, ZTX("Area Edge Calc"), "m-select.png", m_select_edgecalc);
542
add_submenu_item(mArea, ZTX("Invert Area"), "m-select.png", m_select_invert);
543
add_submenu_item(mArea, ZTX("Disable Area"), "m-select.png", m_select_disable);
544
add_submenu_item(mArea, ZTX("Delete Area"), "m-select.png", m_select_delete);
546
GtkWidget *mLight = add_menubar_item(mMbar,ZTX("Retouch"),topmenufunc);
547
add_submenu_item(mLight, ZTX("White Balance"), "m-whitebal.png", m_whitebal);
548
add_submenu_item(mLight, ZTX("Flatten Brightness"), "m-flatten.png", m_flatten);
549
add_submenu_item(mLight, ZTX("Brightness/Color"), "m-tune.png", m_tune);
550
add_submenu_item(mLight, ZTX("Red Eyes"), "m-redeye.png", m_redeye);
552
GtkWidget *mSharp = add_menubar_item(mMbar,ZTX("Sharp"),topmenufunc);
553
add_submenu_item(mSharp, ZTX("Blur Image"), "m-blur.png", m_blur);
554
add_submenu_item(mSharp, ZTX("Sharpen Image"), "m-sharpen.png", m_sharpen);
555
add_submenu_item(mSharp, ZTX("Reduce Noise"), "m-denoise.png", m_denoise);
557
GtkWidget *mSize = add_menubar_item(mMbar,ZTX("Size"),topmenufunc);
558
add_submenu_item(mSize, ZTX("Trim Image"), "m-trim.png", m_trim);
559
add_submenu_item(mSize, ZTX("Resize Image"), "m-resize.png", m_resize);
560
add_submenu_item(mSize, ZTX("Rotate Image"), "m-rotate.png", m_rotate);
562
GtkWidget *mBend = add_menubar_item(mMbar,ZTX("Bend"),topmenufunc);
563
add_submenu_item(mBend, ZTX("Unbend Image"), "m-unbend.png", m_unbend);
564
add_submenu_item(mBend, ZTX("Warp Area"), "m-warp.png", m_WarpA);
565
add_submenu_item(mBend, ZTX("Warp Image"), "m-warp.png", m_WarpI);
567
GtkWidget *mArt = add_menubar_item(mMbar,ZTX("Art"),topmenufunc);
568
add_submenu_item(mArt, ZTX("Color Depth"), "m-colordep.png", m_colordep);
569
add_submenu_item(mArt, ZTX("Simulate Drawing"), "m-draw.png", m_draw);
570
add_submenu_item(mArt, ZTX("Simulate Embossing"), "m-emboss.png", m_emboss);
571
add_submenu_item(mArt, ZTX("Simulate Tiles"), "m-tiles.png", m_tiles);
572
add_submenu_item(mArt, ZTX("Simulate Painting"), "m-painting.png", m_painting);
573
add_submenu_item(mArt, ZTX("Edit Pixels"), "m-pixedit.png", m_pixedit);
575
GtkWidget *mComb = add_menubar_item(mMbar,ZTX("Combine"),topmenufunc);
576
add_submenu_item(mComb, ZTX("Make HDR Image"), "m-hdr.png", m_HDR);
577
add_submenu_item(mComb, ZTX("Make HDF Image"), "m-hdf.png", m_HDF);
578
add_submenu_item(mComb, ZTX("Make Panorama"), "m-pano.png", m_pano);
580
GtkWidget *mHelp = add_menubar_item(mMbar,ZTX("Help"),topmenufunc);
581
add_submenu_item(mHelp, ZTX("About"), "m-about.png", m_help);
582
add_submenu_item(mHelp, ZTX("User Guide"), "m-userguide.png", m_help);
583
add_submenu_item(mHelp, "README", "m-readme.png", m_help);
584
add_submenu_item(mHelp, ZTX("Change Log"), "m-changelog.png", m_help);
585
add_submenu_item(mHelp, ZTX("Translate"), "m-translate.png", m_help);
586
add_submenu_item(mHelp, "FreeImage", "m-FI.png", m_help);
587
add_submenu_item(mHelp, ZTX("Home Page"), "m-fotoxx.png", m_help);
589
mTbar = create_toolbar(mVbox,24); // toolbar buttons
590
add_toolbar_button(mTbar, ZTX("Gallery"), ZTX("Image Gallery"), "gallery.png", m_gallery);
591
add_toolbar_button(mTbar, ZTX("Open"), ZTX("Open Image File"), "open.png", m_open);
592
add_toolbar_button(mTbar, ZTX("Prev"), ZTX("Open Previous File"), "prev.png", m_prev);
593
add_toolbar_button(mTbar, ZTX("Next"), ZTX("Open Next File"), "next.png", m_next);
594
add_toolbar_button(mTbar, ZTX("Save"), ZTX("Save to Same File"), "save.png", m_save);
595
add_toolbar_button(mTbar, ZTX("Save As"), ZTX("Save to New File"), "save.png", m_saveas);
596
add_toolbar_button(mTbar, ZTX("Undo"), ZTX("Undo One Edit"), "undo.png", m_undo);
597
add_toolbar_button(mTbar, ZTX("Redo"), ZTX("Redo One Edit"), "redo.png", m_redo);
598
add_toolbar_button(mTbar, "Zoom+", ZTX("Zoom-in (bigger)"), "zoom+.png", m_zoom);
599
add_toolbar_button(mTbar, "Zoom-", ZTX("Zoom-out (smaller)"), "zoom-.png", m_zoom);
600
add_toolbar_button(mTbar, ZTX("Trash"), ZTX("Move Image to Trash"), "trash.png", m_trash);
601
add_toolbar_button(mTbar, ZTX("Quit"), ZTX("Quit fotoxx"), "quit.png", m_quit);
603
drWin = gtk_drawing_area_new(); // add drawing window
604
gtk_box_pack_start(GTK_BOX(mVbox),drWin,1,1,0);
606
STbar = create_stbar(mVbox); // add status bar
608
G_SIGNAL(mWin,"delete_event",delete_event,0) // connect signals to windows
609
G_SIGNAL(mWin,"destroy",destroy_event,0)
610
G_SIGNAL(drWin,"expose-event",mwpaint,0)
612
gtk_widget_add_events(drWin,GDK_BUTTON_PRESS_MASK); // connect mouse events
613
gtk_widget_add_events(drWin,GDK_BUTTON_RELEASE_MASK);
614
gtk_widget_add_events(drWin,GDK_BUTTON_MOTION_MASK); // all buttons v.6.8
615
gtk_widget_add_events(drWin,GDK_POINTER_MOTION_MASK); // pointer motion v.8.3
616
G_SIGNAL(drWin,"button-press-event",mouse_event,0)
617
G_SIGNAL(drWin,"button-release-event",mouse_event,0)
618
G_SIGNAL(drWin,"motion-notify-event",mouse_event,0)
620
G_SIGNAL(mWin,"key-press-event",KBpress,0) // connect KB events
621
G_SIGNAL(mWin,"key-release-event",KBrelease,0)
623
drag_drop_connect(drWin,m_open_drag); // connect drag-drop event v.7.3
625
gtk_widget_show_all(mWin); // show all widgets
627
gdkgc = gdk_gc_new(drWin->window); // initz. graphics context
629
black.red = black.green = black.blue = 0; // set up colors
630
white.red = white.green = white.blue = maxcolor;
631
red.red = maxcolor; red.green = red.blue = 0;
632
green.green = maxcolor; green.red = green.blue = 0;
634
colormap = gtk_widget_get_colormap(drWin);
635
gdk_rgb_find_color(colormap,&black);
636
gdk_rgb_find_color(colormap,&white);
637
gdk_rgb_find_color(colormap,&red);
638
gdk_rgb_find_color(colormap,&green);
640
gdk_gc_set_foreground(gdkgc,&black);
641
gdk_gc_set_background(gdkgc,&white);
642
gdk_gc_set_line_attributes(gdkgc,1,lineattributes);
644
arrowcursor = gdk_cursor_new(GDK_TOP_LEFT_ARROW); // cursor for selection
645
dragcursor = gdk_cursor_new(GDK_CROSSHAIR); // cursor for dragging
646
busycursor = gdk_cursor_new(GDK_WATCH); // cursor for function busy
648
gtk_init_add((GtkFunction) gtkinitfunc,0); // set initz. call from gtk_main()
650
gtk_main(); // process window events
655
/**************************************************************************/
657
// initial function called from gtk_main() at startup
659
int gtkinitfunc(void * data)
662
char procfile[20], *pp;
663
char printoxx[200], command[200];
664
const char *ppc, *ppc2;
667
Bsavetoedit = ZTX("Unknown file type, save as tiff or jpeg to edit");
668
Bexiftoolmissing = ZTX("Package exiftool is missing");
669
Bopenrawfile = ZTX("Open RAW File");
671
Bcancel = ZTX("Cancel");
673
Bclear = ZTX("Clear");
674
Bapply = ZTX("Apply");
676
Bundolast = ZTX("Undo Last");
677
Bundoall = ZTX("Undo All");
679
Bsearch = ZTX("Search");
680
Binsert = ZTX("Insert");
681
Baddall = ZTX("Add All");
682
Bstart = ZTX("Start");
683
Bfinish = ZTX("Finish");
684
Bsuspend = ZTX("Suspend");
685
Bresume = ZTX("Resume");
688
Bdelete = ZTX("Delete");
689
Binvert = ZTX("Invert");
690
Bedgecalc = ZTX("Edge Calc");
691
Bblendwidth = ZTX("Blend Width");
692
Bdeletearea = ZTX("Delete selected area?");
693
Bwidth = ZTX("Width");
694
Bheight = ZTX("Height");
695
Bpercent = ZTX("Percent");
696
Bpreset = ZTX("Presets");
697
Bproceed = ZTX("Proceed");
699
Bgreen = ZTX("Green");
701
Bbrightness = ZTX("Brightness");
702
Bdarker = ZTX("Darker Areas");
703
Blighter = ZTX("Lighter Areas");
704
Breduce = ZTX("Reduce");
706
tid_fmain = pthread_self(); // get main() thread ID
707
snprintf(PIDstring,11,"%06d",getpid()); // get fotoxx process PID
709
NWthreads = sysconf(_SC_NPROCESSORS_ONLN); // get SMP CPU count v.7.1
710
if (! NWthreads) NWthreads = 1;
711
if (NWthreads > maxWthreads) NWthreads = maxWthreads; // compile time limit
712
printf("using %d threads \n",NWthreads);
714
FreeImage_Initialise(FALSE); // FREEIMAGE setup
715
FreeImage_SetOutputMessage(FI_error);
716
printf("FreeImage %s \n",FreeImage_GetVersion());
718
err = system("echo -n \"exiftool \"; exiftool -ver"); // check for exiftool v.6.9.1
719
if (! err) Fexiftool = 1;
720
err = system("xdg-open --version"); // check for xdg-open
721
if (! err) Fxdgopen = 1;
722
err = system("ufraw --version"); // check for xdg-open
723
if (! err) Fufraw = 1;
725
err = system("printoxx -v"); // check for printoxx
727
pp = getenv("_"); // look in same place as me
729
strncpy0(printoxx,pp,188);
730
pp = (char *) strrchr(printoxx,'/');
732
strcpy(pp,"/printoxx -v");
733
err = system(printoxx);
737
if (! err) Fprintoxx = 1;
739
if (! Fexiftool) zmessageACK(ZTX("exiftool is not installed \n" // warn user
740
"edited images will lose EXIF data"));
742
asstagsfile = zmalloc(200); // setup assigned tags file
743
strncatv(asstagsfile,199,get_zuserdir(),"/assigned_tags",null); // home/user/.fotoxx/assigned_tags
745
undo_files = zmalloc(200);
747
strncatv(undo_files,199,get_zuserdir(),"/*_undo_*",null); // home/user/.fotoxx/pppppp_undo_nn
750
while ((ppc = SearchWild(undo_files,flag))) // look for orphaned undo files
752
ppc2 = strstr(ppc,".fotoxx/");
753
if (! ppc2) continue;
754
npid = atoi(ppc2+8); // pid of file owner
755
snprintf(procfile,19,"/proc/%d",npid);
756
err = stat(procfile,&statb);
757
if (! err) continue; // pid is active, keep file
758
printf("orphaned undo file deleted: %s \n",ppc);
759
snprintf(command,199,"rm -f %s",ppc); // delete orphaned files v.8.0
760
err = system(command);
763
*undo_files = 0; // setup undo stack files
764
strncatv(undo_files,199,get_zuserdir(),"/",PIDstring,"_undo_nn",null); // home/user/.fotoxx/pppppp_undo_nn
766
mutex_init(&pixmaps_lock,0); // setup lock for edit pixmaps
768
g_timeout_add(20,gtimefunc,0); // start periodic function (20 ms)
771
char * pp = canonicalize_file_name(image_file); // add cwd if needed
774
if (pp) image_file = strdupz(pp); // change pp to zmalloc
779
err = stat(image_file,&statb);
780
if (! err && S_ISREG(statb.st_mode)) f_open(image_file); // open initial file
787
/**************************************************************************/
789
// Periodic function - runs every few milliseconds.
790
// Avoid any thread usage of gtk/gdk functions.
792
int gtimefunc(void *arg)
795
static int fbusy = 0;
797
if (Wrepaint) { // update drawing window
802
if (SBupdate) // status bar update
809
secs = get_seconds(); // show next slide
810
if (secs > SS_timer) {
811
SS_timer = secs + SS_interval;
816
if (! fbusy && thread_working()) { // v.8.4
818
gdk_window_set_cursor(drWin->window,busycursor); // set function busy cursor
821
if (fbusy && ! thread_working()) {
823
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
830
/**************************************************************************/
832
// main window delete_event and destroy signals
834
int delete_event() // main window closed
836
if (mod_keep()) return 1; // allow user bailout
837
Fshutdown++; // shutdown in progress
838
save_fotoxx_state(); // save state for next session
839
free_resources(); // delete undo files
843
void destroy_event() // main window destroyed
846
printf("main window destroyed \n");
847
exit(1); // instead of gtk_main_quit();
852
/**************************************************************************/
854
// cause (modified) output image to get repainted immediately
855
// this function may be called from threads
864
/**************************************************************************/
866
// paint window when created, exposed, resized, or modified (edited) // overhauled v.7.4
871
int incrx, incry; // mouse drag
872
static int pincrx = 0, pincry = 0; // prior mouse drag
873
double wscale, hscale;
874
static double pscale = 1; // prior scale
875
RGB *rgbtemp1, *rgbtemp2;
877
if (Fshutdown) return 1; // shutdown underway
879
gdk_window_clear(drWin->window);
880
return 1; // no image
883
Dww = drWin->allocation.width; // (new) drawing window size
884
Dhh = drWin->allocation.height;
885
if (Dww < 20 || Dhh < 20) return 1; // v.7.5
887
if (mutex_trylock(&pixmaps_lock) != 0) { // lock pixmaps
888
Wrepaint++; // cannot, return later
892
if (E3rgb48) { // get image size
894
Ihh = E3hh; // edit in progress
897
Iww = Fww; // no edit
901
if (Fzoom == 0) { // scale to fit window
902
wscale = 1.0 * Dww / Iww;
903
hscale = 1.0 * Dhh / Ihh;
904
if (wscale < hscale) Mscale = wscale; // use greatest ww/hh ratio
905
else Mscale = hscale;
906
if (Iww < Dww && Ihh < Dhh && ! Fblowup) Mscale = 1.0; // small image 1x unless Fblowup
909
else Mscale = Fzoom; // scale to Fzoom level
911
if (Mscale > pscale) { // zoom increased
912
Iorgx += iww * 0.5 * (1.0 - pscale / Mscale); // keep current image center v.7.5
913
Iorgy += ihh * 0.5 * (1.0 - pscale / Mscale);
917
iww = Dww / Mscale; // image space fitting in window
918
if (iww > Iww) iww = Iww;
920
if (ihh > Ihh) ihh = Ihh;
922
if (zoomx || zoomy) { // req. zoom center v.7.5
923
Iorgx = zoomx - 0.5 * iww; // corresp. image origin
924
Iorgy = zoomy - 0.5 * ihh;
928
if ((Mxdrag || Mydrag) && ! Mcapture) { // scroll via mouse drag
929
incrx = (Mxdrag - Mxdown) * 1.3 * Iww / iww; // scale v.7.5
930
incry = (Mydrag - Mydown) * 1.3 * Ihh / ihh;
931
if (pincrx > 0 && incrx < 0) incrx = 0; // stop bounce at extremes
932
if (pincrx < 0 && incrx > 0) incrx = 0;
934
if (pincry > 0 && incry < 0) incry = 0;
935
if (pincry < 0 && incry > 0) incry = 0;
937
Iorgx += incrx; // new image origin after scroll
939
Mxdown = Mxdrag + incrx; // new drag origin
940
Mydown = Mydrag + incry;
944
if (iww == Iww) { // scaled image <= window width
945
Iorgx = 0; // center image in window
946
Dorgx = 0.5 * (Dww - Iww * Mscale);
948
else Dorgx = 0; // image > window, use entire window
950
if (ihh == Ihh) { // same for image height
952
Dorgy = 0.5 * (Dhh - Ihh * Mscale);
956
if (Iorgx + iww > Iww) Iorgx = Iww - iww; // set limits
957
if (Iorgy + ihh > Ihh) Iorgy = Ihh - ihh;
958
if (Iorgx < 0) Iorgx = 0;
959
if (Iorgy < 0) Iorgy = 0;
961
if (E3rgb48) { // edit in progress
962
rgbtemp1 = RGB_copy_area(E3rgb48,Iorgx,Iorgy,iww,ihh); // copy RGB-48
963
rgbtemp2 = RGB_convbpp(rgbtemp1); // convert to RGB-24
966
else rgbtemp2 = RGB_copy_area(Frgb24,Iorgx,Iorgy,iww,ihh); // no edit, copy RGB-24
968
dww = iww * Mscale; // scale to window
971
Drgb24 = RGB_rescale(rgbtemp2,dww,dhh);
974
wrect.x = wrect.y = 0; // stop flicker
977
gdk_window_begin_paint_rect(drWin->window,&wrect);
979
gdk_window_clear(drWin->window); // clear window
981
gdk_draw_rgb_image(drWin->window, gdkgc, Dorgx, Dorgy, dww, dhh, // draw scaled image to window
982
nodither, (uint8 *) Drgb24->bmp, dww*3);
984
if (Ntoplines) paint_toplines(1); // draw line overlays v.8.3
985
if (toparc) paint_toparc(1); // draw arc overlay
986
if (Fshowarea) m_select_show(0,0); // draw select area outline
988
gdk_window_end_paint(drWin->window); // release all window updates
990
mutex_unlock(&pixmaps_lock); // unlock pixmaps
991
Wpainted++; // notify edit function of repaint
992
if (brightgraph) brightgraph_paint(); // update brightness graph
993
SBupdate++; // update status bar
998
/**************************************************************************/
1000
// update status bar with image data and status
1002
void update_statusbar()
1004
static char text1[200], text2[100];
1005
int ww, hh, bpp, scale;
1007
if (! image_file) return;
1009
*text1 = *text2 = 0;
1011
if (E3rgb48) { // v.8.5
1027
snprintf(text1,199,"%dx%dx%d",ww,hh,bpp); // 2345x1234x48 (preview) 0.56MB 45%
1028
if (Fpreview) strcat(text1," (preview)"); // ... (turned)
1029
sprintf(text2," %.2fMB",file_MB);
1030
if (Fmodified || Pundo > Fsaved) sprintf(text2," ?? MB"); // file size TBD
1031
strcat(text1,text2);
1032
scale = int(Mscale * 100 + 0.5);
1033
sprintf(text2," %d%c",scale,'%');
1034
strcat(text1,text2);
1035
if (Fimageturned) strcat(text1," (turned)");
1037
if (Pundo) // edit undo stack depth
1039
snprintf(text2,99," edits: %d",Pundo);
1040
strcat(text1,text2);
1043
if (Nalign && aligntype == 1) // HDR alignment data
1045
snprintf(text2,99," align: %d offsets: %+.1f %+.1f %+.4f match: %.5f",
1046
Nalign,xoffB,yoffB,toffB,matchB);
1047
strcat(text1,text2);
1050
if (Nalign && aligntype == 2) // HDF alignment data
1052
snprintf(text2,99," align: %d offsets: %+.1f %+.1f %+.4f match: %.5f",
1053
Nalign,xoffB,yoffB,toffB,matchB);
1054
strcat(text1,text2);
1057
if (Nalign && aligntype == 3) // pano alignment data
1059
snprintf(text2,99," align: %d offsets: %+.1f %+.1f %+.4f %+.1f %+.1f %+.1f %+.1f match: %.5f",
1060
Nalign,xoffB,yoffB,toffB,warpxuB,warpyuB,warpxlB,warpylB,matchB);
1061
strcat(text1,text2);
1064
if (Fautolens) // lens parameters (search status)
1066
snprintf(text2,99," lens: %.1f %.2f",lens_mm,lens_bow);
1067
strcat(text1,text2);
1070
if (PercentDone > 0 && PercentDone < 100) // % completion v.6.3
1072
snprintf(text2,99," done: %d%c",PercentDone,'%');
1073
strcat(text1,text2);
1076
stbar_message(STbar,text1);
1081
/**************************************************************************/
1083
// mouse event function - capture buttons and drag movements
1085
void mouse_event(GtkWidget *, GdkEventButton *event, void *)
1087
void mouse_convert(int &xpos, int &ypos);
1089
static int bdtime = 0, butime = 0, mbusy = 0;
1090
int button, time, type;
1093
button = event->button; // button, 1/3 = left/right
1095
Mxposn = int(event->x); // mouse position in window
1096
Myposn = int(event->y);
1098
mouse_convert(Mxposn,Myposn); // convert to image space v.8.4
1100
if (type == GDK_MOTION_NOTIFY) {
1101
if (mbusy) return; // discard excess motion events
1107
if (type == GDK_BUTTON_PRESS) { // button down
1108
bdtime = time; // time of button down
1109
Mxdown = Mxposn; // position at button down time
1112
Mdrag++; // possible drag start
1115
Mxdrag = Mydrag = 0;
1118
if (type == GDK_BUTTON_RELEASE) { // button up
1119
Mxclick = Myclick = 0; // reset click status
1120
butime = time; // time of button up
1121
if (butime - bdtime < 400) // less than 0.4 secs down
1122
if (Mxposn == Mxdown && Myposn == Mydown) { // and not moving v.8.6.1
1123
if (Mbutton == 1) LMclick++; // left mouse click
1124
if (Mbutton == 3) RMclick++; // right mouse click
1125
Mxclick = Mxdown; // click = button down position
1128
Mxdown = Mydown = Mxdrag = Mydrag = Mdrag = Mbutton = 0; // forget buttons and drag
1131
if (type == GDK_MOTION_NOTIFY && Mdrag) { // drag underway
1136
if (mouseCBfunc) { // pass to handler function
1141
if (LMclick && ! Mcapture) { // left click = zoom request
1143
zoomx = Mxclick; // zoom center = mouse v.7.5
1145
m_zoom(null, (char *) "+");
1148
if (RMclick && ! Mcapture) { // right click = reset zoom
1150
zoomx = zoomy = 0; // v.7.5
1151
m_zoom(null, (char *) "-");
1154
if ((Mxdrag || Mydrag) && ! Mcapture) mwpaint(); // drag = scroll
1159
// convert mouse position from window space to image space
1161
void mouse_convert(int &xpos, int &ypos)
1163
xpos = int((xpos - Dorgx) / Mscale + Iorgx + 0.5);
1164
ypos = int((ypos - Dorgy) / Mscale + Iorgy + 0.5);
1166
if (xpos < 0) xpos = 0; // if outside image put at edge
1167
if (ypos < 0) ypos = 0; // v.8.4
1170
if (xpos >= E3ww) xpos = E3ww-1;
1171
if (ypos >= E3hh) ypos = E3hh-1;
1174
if (xpos >= Fww) xpos = Fww-1;
1175
if (ypos >= Fhh) ypos = Fhh-1;
1182
/**************************************************************************/
1184
// keyboard event function - some toolbar buttons have KB equivalents
1185
// GDK key symbols: /usr/include/gtk-2.0/gdk/gdkkeysyms.h
1187
int KBcontrolkey = 0;
1189
int KBpress(GtkWidget *win, GdkEventKey *event, void *) // prevent propagation of key-press
1190
{ // events to toolbar buttons
1191
KBkey = event->keyval;
1192
if (KBkey == 65507) KBcontrolkey = 1; // Ctrl key is pressed v.8.3
1196
int KBrelease(GtkWidget *win, GdkEventKey *event, void *)
1198
KBkey = event->keyval;
1200
if (KBkeyCBfunc) { // pass to handler function
1201
(* KBkeyCBfunc)(); // v.6.5
1206
if (KBcapture) return 1; // let function handle it
1208
if (KBkey == 65507) KBcontrolkey = 0; // Ctrk key released v.8.3
1211
if (KBkey == GDK_s) m_save(0,0); // Ctrl-* shortcuts v.8.3
1212
if (KBkey == GDK_S) m_saveas(0,0);
1213
if (KBkey == GDK_q) m_quit(0,0);
1214
if (KBkey == GDK_Q) m_quit(0,0);
1217
if (KBkey == GDK_G) m_gallery(0,0); // key G >> image gallery v.8.4.2
1218
if (KBkey == GDK_g) m_gallery(0,0);
1220
if (KBkey == GDK_Left) m_prev(0,0); // arrow keys >> prev/next image
1221
if (KBkey == GDK_Right) m_next(0,0);
1223
if (KBkey == GDK_plus) m_zoom(null, (char *) "+"); // +/- keys >> zoom in/out
1224
if (KBkey == GDK_equal) m_zoom(null, (char *) "+"); // = key: same as +
1225
if (KBkey == GDK_minus) m_zoom(null, (char *) "-");
1226
if (KBkey == GDK_KP_Add) m_zoom(null, (char *) "+"); // keypad +
1227
if (KBkey == GDK_KP_Subtract) m_zoom(null, (char *) "-"); // keypad -
1229
if (KBkey == GDK_Z) m_zoom(null, (char *) "Z"); // Z key: zoom to 100%
1230
if (KBkey == GDK_z) m_zoom(null, (char *) "Z");
1232
if (KBkey == GDK_Escape) { // escape v.7.0
1233
if (Fslideshow) m_slideshow(0,0); // exit slideshow mode
1234
if (Frgbmode && mouseCBfunc) (* mouseCBfunc)(); // exit RGB mode
1235
Fslideshow = Frgbmode = 0; // v.8.6
1238
if (KBkey == GDK_Delete) m_trash(0,0); // delete >> trash
1240
if (KBkey == GDK_R) turn_image(+90); // keys L, R >> rotate
1241
if (KBkey == GDK_r) turn_image(+90);
1242
if (KBkey == GDK_L) turn_image(-90);
1243
if (KBkey == GDK_l) turn_image(-90);
1250
/**************************************************************************/
1252
// refresh overlay lines on top of image
1253
// arg = 1: paint lines only (because window repainted)
1254
// 2: erase lines and forget them
1255
// 3: erase old lines, paint new lines, save new in old
1257
void paint_toplines(int arg) // v.8.3
1261
if (arg == 2 || arg == 3) // erase old lines
1262
for (ii = 0; ii < Nptoplines; ii++)
1263
erase_line(ptoplinex1[ii],ptopliney1[ii],ptoplinex2[ii],ptopliney2[ii]);
1265
if (arg == 1 || arg == 3) // draw new lines
1266
for (ii = 0; ii < Ntoplines; ii++)
1267
draw_line(toplinex1[ii],topliney1[ii],toplinex2[ii],topliney2[ii]);
1270
Nptoplines = Ntoplines = 0; // forget lines
1274
for (ii = 0; ii < Ntoplines; ii++) // save for future erase
1276
ptoplinex1[ii] = toplinex1[ii];
1277
ptopliney1[ii] = topliney1[ii];
1278
ptoplinex2[ii] = toplinex2[ii];
1279
ptopliney2[ii] = topliney2[ii];
1282
Nptoplines = Ntoplines;
1288
/**************************************************************************/
1290
// refresh overlay arc (circle/ellipse) on top of image
1291
// arg = 1: paint arc only (because window repainted)
1292
// 2: erase arc and forget it
1293
// 3: erase old arc, paint new arc, save new in old
1295
void paint_toparc(int arg) // v.8.3
1297
int arcx, arcy, arcw, arch;
1299
if (ptoparc && (arg == 2 || arg == 3)) { // erase old arc
1300
arcx = int((ptoparcx-Iorgx) * Mscale + Dorgx + 0.5); // image to window space
1301
arcy = int((ptoparcy-Iorgy) * Mscale + Dorgy + 0.5);
1302
arcw = int(ptoparcw * Mscale);
1303
arch = int(ptoparch * Mscale);
1305
gdk_gc_set_function(gdkgc,GDK_INVERT); // invert pixels
1306
gdk_draw_arc(drWin->window,gdkgc,0,arcx,arcy,arcw,arch,0,64*360); // draw arc
1307
gdk_gc_set_function(gdkgc,GDK_COPY);
1310
if (toparc && (arg == 1 || arg == 3)) { // draw new arc
1311
arcx = int((toparcx-Iorgx) * Mscale + Dorgx + 0.5); // image to window space
1312
arcy = int((toparcy-Iorgy) * Mscale + Dorgy + 0.5);
1313
arcw = int(toparcw * Mscale);
1314
arch = int(toparch * Mscale);
1316
gdk_gc_set_function(gdkgc,GDK_INVERT); // invert pixels
1317
gdk_draw_arc(drWin->window,gdkgc,0,arcx,arcy,arcw,arch,0,64*360); // draw arc
1318
gdk_gc_set_function(gdkgc,GDK_COPY);
1322
toparc = ptoparc = 0; // forget arcs
1326
ptoparc = toparc; // save for future erase
1336
/**************************************************************************/
1338
// draw line. coordinates are in image space. // overhauled v.8.4.2
1340
void draw_line(int ix1, int iy1, int ix2, int iy2)
1342
void draw_pixel(double pxm, double pym);
1344
double x1, y1, x2, y2;
1345
double pxm, pym, slope;
1347
x1 = Mscale * (ix1-Iorgx); // image to window space
1348
y1 = Mscale * (iy1-Iorgy);
1349
x2 = Mscale * (ix2-Iorgx);
1350
y2 = Mscale * (iy2-Iorgy);
1352
if (abs(y2 - y1) > abs(x2 - x1)) {
1353
slope = 1.0 * (x2 - x1) / (y2 - y1);
1355
for (pym = y1; pym <= y2; pym++) {
1356
pxm = x1 + slope * (pym - y1);
1357
draw_pixel(pxm,pym);
1361
for (pym = y1; pym >= y2; pym--) {
1362
pxm = x1 + slope * (pym - y1);
1363
draw_pixel(pxm,pym);
1368
slope = 1.0 * (y2 - y1) / (x2 - x1);
1370
for (pxm = x1; pxm <= x2; pxm++) {
1371
pym = y1 + slope * (pxm - x1);
1372
draw_pixel(pxm,pym);
1376
for (pxm = x1; pxm >= x2; pxm--) {
1377
pym = y1 + slope * (pxm - x1);
1378
draw_pixel(pxm,pym);
1383
gdk_gc_set_foreground(gdkgc,&black);
1388
void draw_pixel(double px, double py)
1391
static int flip = 0;
1396
if (pxn < 0 || pxn > dww-1) return;
1397
if (pyn < 0 || pyn > dhh-1) return;
1399
if (++flip > 2) flip = -3;
1400
if (flip < 0) gdk_gc_set_foreground(gdkgc,&red);
1401
else gdk_gc_set_foreground(gdkgc,&white);
1402
gdk_draw_point(drWin->window, gdkgc, pxn + Dorgx, pyn + Dorgy);
1408
// erase line. refresh line path from Drgb24 pixels.
1410
void erase_line(int ix1, int iy1, int ix2, int iy2)
1412
void erase_pixel(double pxm, double pym);
1414
double x1, y1, x2, y2;
1415
double pxm, pym, slope;
1417
x1 = Mscale * (ix1-Iorgx);
1418
y1 = Mscale * (iy1-Iorgy);
1419
x2 = Mscale * (ix2-Iorgx);
1420
y2 = Mscale * (iy2-Iorgy);
1422
if (abs(y2 - y1) > abs(x2 - x1)) {
1423
slope = 1.0 * (x2 - x1) / (y2 - y1);
1425
for (pym = y1; pym <= y2; pym++) {
1426
pxm = x1 + slope * (pym - y1);
1427
erase_pixel(pxm,pym);
1431
for (pym = y1; pym >= y2; pym--) {
1432
pxm = x1 + slope * (pym - y1);
1433
erase_pixel(pxm,pym);
1438
slope = 1.0 * (y2 - y1) / (x2 - x1);
1440
for (pxm = x1; pxm <= x2; pxm++) {
1441
pym = y1 + slope * (pxm - x1);
1442
erase_pixel(pxm,pym);
1446
for (pxm = x1; pxm >= x2; pxm--) {
1447
pym = y1 + slope * (pxm - x1);
1448
erase_pixel(pxm,pym);
1456
void erase_pixel(double px, double py)
1463
if (pxn < 0 || pxn > dww-1) return;
1464
if (pyn < 0 || pyn > dhh-1) return;
1466
uint8 *pixel = (uint8 *) Drgb24->bmp + (pyn * dww + pxn) * 3;
1467
gdk_draw_rgb_image(drWin->window, gdkgc, pxn + Dorgx, pyn + Dorgy,
1468
1, 1, nodither, pixel, dww * 3);
1473
/**************************************************************************/
1475
// process top-level menu entry
1477
void topmenufunc(GtkWidget *, const char *menu)
1479
topmenu = (char *) menu; // remember top-level menu in
1480
return; // case this is needed somewhere
1484
/**************************************************************************
1486
***************************************************************************/
1488
// display image gallery (thumbnails) in a separate window
1490
void m_gallery(GtkWidget *, const char *)
1492
if (image_file) image_gallery(image_file,"paint1",0,m_gallery2); // show image gallery window
1494
char *pp = get_current_dir_name(); // initz. gallery file list and show
1496
image_gallery(pp,"init",0,m_gallery2);
1497
image_gallery(0,"paint1");
1507
// clicked thumbnails will call this function
1509
void m_gallery2(char *file)
1511
if (zdburn) { // add file for CD burn v.7.2
1512
burn_insert_file(file);
1516
f_open((const char *) file);
1521
/**************************************************************************/
1523
// open menu function
1525
void m_open(GtkWidget *, const char *)
1533
/**************************************************************************/
1535
// open drag-drop file
1537
void m_open_drag(int x, int y, char *file) // v.7.3
1539
Fsearchlist = 0; // v.8.1
1546
/**************************************************************************/
1548
// open an image file from the list of recent image files
1550
char recent_selection[300];
1552
void m_recent(GtkWidget *, const char *) // v.8.1
1554
int recent_dialog_event(zdialog *zd, const char *event);
1559
if (! recentfiles[0]) return;
1561
zd = zdialog_new(ZTX("Open Recent File"),mWin,BOK,Bcancel,0);
1562
zdialog_add_widget(zd,"combo","combo","dialog",0);
1564
for (ii = 0; ii < Nrecentfiles && recentfiles[ii]; ii++) // stuff files into combo box list
1565
zdialog_cb_app(zd,"combo",recentfiles[ii]);
1566
zdialog_stuff(zd,"combo",recentfiles[0]);
1568
*recent_selection = 0;
1569
zstat = zdialog_run(zd,recent_dialog_event,0); // run dialog, blocking
1572
if (zstat == 1 && *recent_selection == '/')
1573
f_open(recent_selection); // open selected file
1577
int recent_dialog_event(zdialog *zd, const char *event) // dialog event function v.8.2
1579
if (strNeq(event,"combo")) return 0;
1580
zdialog_fetch(zd,"combo",recent_selection,299); // get user selection
1581
zdialog_send_response(zd,1); // complete dialog with OK status
1586
// add a file to the list of recent files // v.8.4
1588
void add_recent_file(const char *file)
1592
for (ii = 0; ii < Nrecentfiles-1 && recentfiles[ii]; ii++) // find file in recent list v.8.1
1593
if (strEqu(file,recentfiles[ii])) break; // (or find last entry in list)
1594
if (recentfiles[ii]) zfree(recentfiles[ii]); // free this slot in list
1595
for (; ii > 0; ii--) recentfiles[ii] = recentfiles[ii-1]; // move list down to fill hole
1596
recentfiles[0] = strdupz(file); // current file >> first in list
1601
/**************************************************************************/
1603
// open a file and initialize FI bitmap
1605
void f_open(const char *filespec)
1607
int cc, fposn, fcount;
1608
char *pp, wtitle[200], fname[100], fdirk[100];
1611
const char *orientationkey[1] = { exif_orientation_key };
1613
zmondirk("close",null,null); // reset monitored directory
1615
if (! menulock(1)) return; // lock menu
1616
if (mod_keep()) goto openfail;
1618
if (filespec) filespec = strdupz(filespec);
1619
else filespec = zgetfile(ZTX("Open Image File"),image_file,"open");
1620
if (! filespec) goto openfail;
1622
temp24 = image_load(filespec,24); // load image as RGB-24 pixmap
1623
if (! temp24) goto openfail;
1625
free_resources(); // free resources for old image file
1627
mutex_lock(&pixmaps_lock); // lock pixmaps
1629
image_file = (char *) filespec; // setup new image file
1634
pp = (char *) strrchr(image_file,'/'); // get image file name
1635
strncpy0(fname,pp+1,99);
1636
cc = pp - image_file;
1637
if (cc < 99) strncpy0(fdirk,image_file,cc+2); // get dirk/path/ if short enough
1639
strncpy(fdirk,image_file,96); // or use /dirk/path...
1640
strcpy(fdirk+95,"...");
1642
image_position(image_file,fposn,fcount); // position and count in gallery list
1644
Fzoom = 0; // zoom level = fit window
1645
zoomx = zoomy = 0; // no zoom center v.7.5
1647
snprintf(wtitle,199,"%s %d/%d %s",fname,fposn,fcount,fdirk); // set window title
1648
gtk_window_set_title(GTK_WINDOW(mWin),wtitle);
1649
gtk_window_present(GTK_WINDOW(mWin)); // bring main window to front
1650
if (zdtags) edit_tags_dialog(); // update active tags dialog
1651
if (zdrename) rename_dialog(); // update active rename dialog
1653
mutex_unlock(&pixmaps_lock); // unlock pixmaps
1656
orientation = exif_get(image_file,orientationkey,1); // upright turned image
1658
if (strstr(*orientation,"90")) turn_image(90);
1659
if (strstr(*orientation,"270")) turn_image(270);
1662
mwpaint2(); // refresh main window
1664
pp = image_gallery(image_file,"find",0); // file in current image gallery ?
1666
image_gallery(image_file,"init"); // no, reset gallery file list
1667
image_gallery(0,"paint2"); // refresh gallery window if active
1672
if (! Fsearchlist) { // start monitoring this directory
1673
pp = (char *) strrchr(image_file,'/'); // if not from search tags
1675
zmondirk("open",image_file,null);
1679
add_recent_file(image_file); // first in recent files list
1688
/**************************************************************************/
1690
// open a raw image file and convert to tiff-48
1692
void m_raw(GtkWidget *, const char *)
1694
char *pp, *rawfile, *outfile;
1699
if (! menulock(1)) return; // lock menu
1700
if (mod_keep()) goto openfail;
1703
zmessageACK(ZTX("Package ufraw required for this function"));
1707
rawfile = zgetfile(Bopenrawfile,image_file,"open");
1708
if (! rawfile) goto openfail;
1710
err = stat(rawfile,&fstat);
1712
zmessageACK(strerror(errno));
1717
if (S_ISDIR(fstat.st_mode)) {
1718
pp = zgetfile(Bopenrawfile,rawfile,"open");
1720
if (! pp) goto openfail;
1724
outfile = strdupz(rawfile,5);
1725
pp = (char *) strrchr(rawfile,'.');
1726
pp = outfile + (pp - rawfile);
1729
yn = zmessageYN(ZTX("Convert raw file to 48-bit tiff format? \n"
1730
" (this may take a while) "));
1736
gdk_window_set_cursor(drWin->window,busycursor); // set function busy cursor v.8.4
1739
snprintf(command,999,"ufraw-batch --out-type=tiff --out-depth=16" // new ufraw v.6.5
1740
" --overwrite --output=\"%s\" \"%s\" ",outfile,rawfile);
1741
err = system(command);
1744
snprintf(command,999,"ufraw-batch --out-type=tiff16" // old ufraw
1745
" --overwrite --output=\"%s\" \"%s\" ",outfile,rawfile);
1746
err = system(command);
1749
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
1752
zmessageACK(wstrerror(err));
1757
menulock(0); // bugfix v.6.3
1758
image_gallery(outfile,"init"); // update image gallery file list
1759
image_gallery(0,"paint2"); // refresh gallery window if active
1761
f_open(outfile); // open converted file
1771
/**************************************************************************/
1773
// open previous or next file in same gallery as last file opened
1775
void m_prev(GtkWidget *, const char *)
1777
if (! image_file) return;
1780
while (zmondirk("event",null,null) > 0) mods++; // detect directory mods v.6.4
1781
if (mods) image_gallery(image_file,"init"); // refresh gallery file list
1783
char *pp = image_gallery(image_file,"prev");
1785
if (image_file_type(pp) == 2) f_open(pp);
1790
void m_next(GtkWidget *, const char *)
1792
if (! image_file) return;
1795
while (zmondirk("event",null,null) > 0) mods++; // v.6.4
1796
if (mods) image_gallery(image_file,"init");
1798
char *pp = image_gallery(image_file,"next");
1800
if (image_file_type(pp) == 2) f_open(pp);
1806
/**************************************************************************/
1808
// save (modified) image to same file - no confirmation of overwrite.
1810
void m_save(GtkWidget *, const char *) // v.8.3
1812
char *outfile, *pext;
1815
if (! image_file) return;
1817
format = "jpeg"; // use jpeg unless tiff file
1818
if (strEqu(file_type,"tiff")) format = "tiff-24"; // or 48-bits/pixel file
1819
if (file_bpp == 48) format = "tiff-48";
1821
strcpy(jpeg_quality,def_jpeg_quality); // set default jpeg quality
1823
outfile = strdupz(image_file,8); // use input file name
1825
pext = (char *) strrchr(outfile,'/'); // force compatible file .ext
1826
if (pext) pext = (char *) strrchr(pext,'.'); // if not already
1827
if (! pext) pext = outfile + strlen(outfile);
1828
if (strEqu(format,"jpeg") && ! strcmpv(pext,".jpg",".JPG",".jpeg",".JPEG",0))
1829
strcpy(pext,".jpeg");
1830
if (strnEqu(format,"tiff",4) && ! strcmpv(pext,".tif",".TIF",".tiff",".TIFF",0))
1831
strcpy(pext,".tiff");
1833
f_save(outfile,format);
1839
/**************************************************************************/
1841
// save (modified) image to new file, confirm if overwrite existing file.
1843
void m_saveas(GtkWidget *, const char *)
1845
GtkWidget *fdialog, *fchooser, *hbox;
1846
GtkWidget *label1, *tiff2, *tiff4, *jpeg, *jqlab, *jqval;
1847
char *outfile = 0, *outfile2 = 0, *pext;
1849
int ii, err, yn, status;
1852
if (! image_file) return;
1854
fdialog = gtk_dialog_new_with_buttons(ZTX("Save File"), // build file save dialog
1855
GTK_WINDOW(mWin), GTK_DIALOG_MODAL,
1856
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1857
GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, null);
1859
gtk_window_set_default_size(GTK_WINDOW(fdialog),500,400);
1861
fchooser = gtk_file_chooser_widget_new(GTK_FILE_CHOOSER_ACTION_SAVE);
1862
gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(fchooser),image_file);
1863
gtk_container_add(GTK_CONTAINER(GTK_DIALOG(fdialog)->vbox),fchooser);
1865
hbox = gtk_hbox_new(0,0);
1866
gtk_container_add(GTK_CONTAINER(GTK_DIALOG(fdialog)->vbox),hbox);
1867
gtk_box_set_child_packing(GTK_BOX(GTK_DIALOG(fdialog)->vbox),hbox,0,0,10,GTK_PACK_END);
1869
label1 = gtk_label_new("file type"); // add file type options
1870
tiff2 = gtk_radio_button_new_with_label(null,"tiff-24");
1871
tiff4 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(tiff2),"tiff-48");
1872
jpeg = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(tiff2),"jpeg");
1873
jqlab = gtk_label_new("jpeg quality");
1874
jqval = gtk_entry_new();
1875
gtk_entry_set_width_chars(GTK_ENTRY(jqval),3);
1876
gtk_box_pack_start(GTK_BOX(hbox),label1,0,0,5);
1877
gtk_box_pack_start(GTK_BOX(hbox),tiff2,0,0,5); // simplified v.6.7.1
1878
gtk_box_pack_start(GTK_BOX(hbox),tiff4,0,0,5);
1879
gtk_box_pack_start(GTK_BOX(hbox),jpeg,0,0,10); // jpeg quality v.6.7.2
1880
gtk_box_pack_start(GTK_BOX(hbox),jqlab,0,0,5);
1881
gtk_box_pack_start(GTK_BOX(hbox),jqval,0,0,5);
1883
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(jpeg),1); // set default output file type
1884
gtk_entry_set_text(GTK_ENTRY(jqval),def_jpeg_quality); // default jpeg quality
1886
if (strEqu(file_type,"tiff"))
1887
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tiff2),1);
1889
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tiff4),1);
1893
gtk_widget_show_all(fdialog); // run dialog
1894
status = gtk_dialog_run(GTK_DIALOG(fdialog));
1895
if (status != GTK_RESPONSE_ACCEPT) { // user cancelled
1896
gtk_widget_destroy(fdialog);
1900
outfile2 = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fchooser)); // get user inputs
1901
if (! outfile2) goto dialog_run;
1902
outfile = strdupz(outfile2,8);
1906
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tiff2)))
1908
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tiff4)))
1911
if (strEqu(format,"jpeg")) { // get jpeg quality v.6.7.2
1912
ii = atoi(gtk_entry_get_text(GTK_ENTRY(jqval)));
1913
if (ii < 1 || ii > 100) {
1914
zmessageACK("jpeg quality must be 1-100");
1917
sprintf(jpeg_quality,"%d",ii);
1920
gtk_widget_destroy(fdialog); // kill dialog
1922
pext = (char *) strrchr(outfile,'/'); // force compatible file .ext
1923
if (pext) pext = (char *) strrchr(pext,'.'); // if not already
1924
if (! pext) pext = outfile + strlen(outfile);
1925
if (strEqu(format,"jpeg") && ! strcmpv(pext,".jpg",".JPG",".jpeg",".JPEG",0))
1926
strcpy(pext,".jpeg");
1927
if (strnEqu(format,"tiff",4) && ! strcmpv(pext,".tif",".TIF",".tiff",".TIFF",0))
1928
strcpy(pext,".tiff");
1930
err = stat(outfile,&fstat); // check if file exists
1932
yn = zmessageYN(ZTX("Overwrite file? \n %s"),outfile); // confirm overwrite
1939
f_save(outfile,format);
1945
/**************************************************************************/
1947
// file save helper function - do the actual file save
1949
void f_save(const char *outfile, const char *format) // code simplification v.8.6
1954
const char *exifkey[3] = { exif_width_key, exif_height_key, exif_orientation_key };
1955
const char *exifdata[3];
1956
char wwchar[8], hhchar[8];
1957
char *tempfile, command[2000];
1961
update_filetags(image_file); // commit poss. tag changes v.8.3
1963
tempfile = strdupz(get_zuserdir(),24); // use temp output file
1964
strcat(tempfile,"/temp_"); // ~/.fotoxx/temp_ppppp.ext
1965
strcat(tempfile,PIDstring);
1966
strcat(tempfile,".");
1967
strncat(tempfile,format,4);
1973
gdk_window_set_cursor(drWin->window,busycursor); // set function busy cursor v.8.4
1976
if (strEqu(format,"jpeg")) { // save as JPEG file
1978
rgb24 = RGB_convbpp(Frgb48);
1979
pxb = RGB_PXB(rgb24);
1981
} // use GDK pixbuf for jpeg
1982
else pxb = RGB_PXB(Frgb24);
1983
status = gdk_pixbuf_save(pxb,tempfile,"jpeg",gerror,"quality",jpeg_quality,null);
1986
if (strEqu(format,"tiff-24")) { // save as TIFF-24 file
1988
rgb24 = RGB_convbpp(Frgb48);
1989
fib = RGB_FIB(rgb24);
1992
else fib = RGB_FIB(Frgb24);
1993
status = FreeImage_Save(FIF_TIFF,fib,tempfile,0);
1996
if (strEqu(format,"tiff-48")) { // save as TIFF-48 file
1997
if (Frgb48) fib = RGB_FIB(Frgb48);
1999
if (file_bpp == 48) rgb48 = image_load(image_file,48); // use original 48 bpp file
2000
else rgb48 = RGB_convbpp(Frgb24);
2002
fib = RGB_FIB(rgb48);
2006
status = FreeImage_Save(FIF_TIFF,fib,tempfile,0);
2009
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
2011
if (fib) FreeImage_Unload(fib);
2012
if (pxb) g_object_unref(pxb);
2015
zmessageACK(ZTX("Unable to save image"));
2016
snprintf(command,1999,"rm \"%s\"",tempfile); // clean up
2017
err = system(command);
2022
snprintf(wwchar,6,"%d",Fww);
2023
snprintf(hhchar,6,"%d",Fhh); // copy EXIF data from source file
2024
exifdata[0] = wwchar; // with possible new dimensions
2025
exifdata[1] = hhchar;
2026
exifdata[2] = ""; // assume saved upright v.6.2
2027
err = exif_copy(image_file,tempfile,exifkey,exifdata,3);
2028
if (err) zmessageACK(ZTX("Unable to copy EXIF data"));
2030
snprintf(command,1999,"cp -f \"%s\" \"%s\" ",tempfile,outfile); // copy to final destination
2031
err = system(command);
2032
if (err) zmessageACK(ZTX("Unable to save image: %s"),wstrerror(err));
2034
snprintf(command,1999,"rm \"%s\"",tempfile); // clean up
2035
err = system(command);
2038
load_filetags(outfile); // update assigned tags file
2039
update_asstags(outfile);
2041
Fmodified = Fimageturned = 0; // reset file modified status
2042
Fsaved = Pundo; // note which mods are saved v.8.3
2044
if (! Fsearchlist && samedirk(image_file,outfile)) { // update image gallery file list
2045
image_gallery(image_file,"init"); // if output in same directory
2046
image_gallery(0,"paint2"); // refresh gallery window if active
2047
gtk_window_present(GTK_WINDOW(mWin)); // bring main to foreground
2050
stat(outfile,&fstat); // update file size v.8.3
2051
file_MB = 1.0 * fstat.st_size / mega;
2052
SBupdate++; // update status bar
2054
add_recent_file(outfile); // first in recent files list v.8.4
2059
/**************************************************************************/
2061
// print image files
2063
void m_print(GtkWidget *, const char *)
2068
if (! image_file) return;
2071
zmessageACK(ZTX("printoxx program not found (see user guide)"));
2075
snprintf(command,999,"printoxx \"%s\" &",image_file); // send curr. file to printoxx
2076
err = system(command);
2077
if (err) zmessLogACK(wstrerror(err));
2082
/**************************************************************************/
2084
// Delete image file - move image_file to trash.
2085
// Trash has no standard location, so use a trash folder on the desktop.
2086
// User must delete or move to the official trash bin.
2088
void m_trash(GtkWidget *, const char *)
2091
char command[1000], trashdir[100];
2094
if (! image_file) return; // nothing to trash
2096
err = stat(image_file,&trstat); // get file status
2098
zmessLogACK(strerror(errno));
2102
if (! (trstat.st_mode & S_IWUSR)) { // check permission
2103
yn = zmessageYN(ZTX("Move read-only file to trash?"));
2105
trstat.st_mode |= S_IWUSR;
2106
chmod(image_file,trstat.st_mode);
2109
snprintf(trashdir,99,"%s/%s",getenv("HOME"),ftrash); // get full fotoxx trash file
2112
err = stat(trashdir,&trstat);
2113
if (! S_ISDIR(trstat.st_mode)) {
2114
snprintf(command,999,"mkdir -m 0750 \"%s\"",trashdir);
2115
err = system(command);
2117
zmessLogACK(ZTX("Cannot create trash folder: %s"),wstrerror(err));
2122
snprintf(command,999,"cp \"%s\" \"%s\" ",image_file,trashdir); // copy image file to trash
2123
err = system(command);
2125
zmessLogACK(ZTX("error: %s"),wstrerror(err));
2129
snprintf(command,999,"rm \"%s\"",image_file); // delete image file
2130
err = system(command);
2132
zmessLogACK(ZTX("error: %s"),wstrerror(err));
2136
update_asstags(image_file,1); // delete in assigned tags file
2137
if (! Fsearchlist) image_gallery(image_file,"init"); // reset image gallery file list
2138
image_gallery(0,"paint2"); // refresh gallery window if active
2139
m_next(0,0); // step to next file if there
2145
/**************************************************************************/
2147
// rename menu function
2149
char rename_old[100] = "";
2150
char rename_new[100] = "";
2152
void m_rename(GtkWidget *, const char *)
2154
rename_dialog(); // activate rename dialog
2159
// activate rename dialog, stuff data from current file
2161
void rename_dialog()
2163
int rename_dialog_event(zdialog *zd, const char *event);
2164
int rename_dialog_compl(zdialog *zd, int zstat);
2166
char *pdir, *pfile, *pext;
2168
if (! image_file) return;
2170
if (! zdrename) // restart dialog
2172
zdrename = zdialog_new(ZTX("Rename Image File"),mWin,Bcancel,0);
2173
zdialog_add_widget(zdrename,"hbox","hb1","dialog",0,"space=10");
2174
zdialog_add_widget(zdrename,"vbox","vb1","hb1",0,"homog|space=5");
2175
zdialog_add_widget(zdrename,"vbox","vb2","hb1",0,"homog|expand");
2177
zdialog_add_widget(zdrename,"button","Bold","vb1",ZTX("old name"));
2178
zdialog_add_widget(zdrename,"button","Bnew","vb1",ZTX("rename to"));
2179
zdialog_add_widget(zdrename,"button","Bprev","vb1",ZTX("previous"));
2181
zdialog_add_widget(zdrename,"hbox","hb21","vb2",0); // [ old name ] [ oldname ]
2182
zdialog_add_widget(zdrename,"hbox","hb22","vb2",0); // [ new name ] [ newname ] [+1]
2183
zdialog_add_widget(zdrename,"hbox","hb23","vb2",0); // [ previous ] [ prevname ]
2185
zdialog_add_widget(zdrename,"label","Lold","hb21");
2186
zdialog_add_widget(zdrename,"entry","Enew","hb22",0,"expand|scc=30");
2187
zdialog_add_widget(zdrename,"button","B+1","hb22"," +1 ","space=5");
2188
zdialog_add_widget(zdrename,"label","Lprev","hb23");
2190
zdialog_run(zdrename,rename_dialog_event,rename_dialog_compl); // start dialog
2193
parsefile(image_file,&pdir,&pfile,&pext);
2194
strncpy0(rename_old,pfile,99);
2195
strncpy0(rename_new,pfile,99);
2196
zdialog_stuff(zdrename,"Lold",rename_old); // current file name
2197
zdialog_stuff(zdrename,"Enew",rename_new); // entered file name
2203
// rename dialog event and completion functions
2205
int rename_dialog_event(zdialog *zd, const char *event)
2207
char *pp, *pdir, *pfile, *pext, *pnew, command[2000];
2208
int nseq, digits, ccp, ccn, ccx, err;
2211
if (strEqu(event,"Bold")) // reset to current file name
2212
zdialog_stuff(zd,"Enew",rename_old);
2214
if (strEqu(event,"Bprev")) { // previous name >> new name
2215
zdialog_fetch(zd,"Lprev",rename_new,99);
2216
zdialog_stuff(zd,"Enew",rename_new);
2219
if (strEqu(event,"B+1")) // increment sequence number
2221
zdialog_fetch(zd,"Enew",rename_new,94); // get entered filename
2222
pp = rename_new + strlen(rename_new);
2224
while (pp[-1] >= '0' && pp[-1] <= '9') {
2225
pp--; // look for NNN in filenameNNN
2228
nseq = 1 + atoi(pp); // NNN + 1
2229
if (nseq > 9999) nseq = 0;
2230
if (digits < 2) digits = 2; // keep digit count if enough
2231
if (nseq > 99 && digits < 3) digits = 3; // use leading zeros
2232
if (nseq > 999 && digits < 4) digits = 4;
2233
snprintf(pp,digits+1,"%0*d",digits,nseq);
2234
zdialog_stuff(zd,"Enew",rename_new);
2237
if (strEqu(event,"Bnew"))
2239
parsefile(image_file,&pdir,&pfile,&pext); // existing /directories/file.ext
2241
zdialog_fetch(zd,"Enew",rename_new,94); // new file name from user
2243
ccp = strlen(pdir); // length of /directories/
2244
ccn = strlen(rename_new); // length of file
2245
if (pext) ccx = strlen(pext); // length of .ext
2248
pnew = zmalloc(ccp + ccn + ccx + 1); // put it all together
2249
strncpy(pnew,image_file,ccp); // /directories/file.ext
2250
strcpy(pnew+ccp,rename_new);
2251
if (ccx) strcpy(pnew+ccp+ccn,pext);
2253
err = stat(pnew,&statb); // check for new name exists
2255
zmessageACK(ZTX("The target file already exists"));
2260
snprintf(command,1999,"cp \"%s\" \"%s\"",image_file,pnew); // copy to new file
2261
err = system(command);
2263
zmessageACK(ZTX("Rename failed \n %s"),wstrerror(err));
2268
zdialog_stuff(zd,"Lprev",rename_new); // set previous name in dialog
2270
load_filetags(pnew); // update assigned tags file
2271
update_asstags(pnew);
2274
pnew = strdupz(image_file); // save file name to be deleted
2275
m_next(0,0); // move to image_file + 1
2276
snprintf(command,999,"rm \"%s\"",pnew); // delete old file
2277
err = system(command);
2280
if (! Fsearchlist) image_gallery(image_file,"init"); // update image gallery file list
2281
image_gallery(0,"paint2"); // refresh gallery window if active
2282
gtk_window_present(GTK_WINDOW(mWin)); // bring main to foreground
2289
int rename_dialog_compl(zdialog *zd, int zstat)
2291
zdialog_free(zdrename); // kill dialog
2297
/**************************************************************************/
2299
// forced quit - can cause running function to crash
2301
void m_quit(GtkWidget *, const char *)
2303
if (image_file) update_filetags(image_file); // commit tag changes, if any
2304
if (mod_keep()) return; // keep or discard pending changes
2307
save_fotoxx_state(); // save state for next session
2308
free_resources(); // delete temp files
2309
gtk_main_quit(); // gone forever
2314
/**************************************************************************
2316
***************************************************************************/
2318
// set new image zoom level or magnification
2320
void m_zoom(GtkWidget *, const char *menu)
2322
int ii, iww, ihh, Dww, Dhh;
2324
double scalew, scaleh, fitscale;
2325
double scales[9] = { 0.125, 0.176, 0.25, 0.354, 0.5, 0.71, 1.0, 1.41, 2.0 };
2327
if (strnEqu(menu,"Zoom",4)) zoom = menu[4]; // get + or -
2330
Dww = drWin->allocation.width; // drawing window size
2331
Dhh = drWin->allocation.height;
2333
if (E3rgb48) { // bugfix v.8.1
2342
if (iww > Dww || ihh > Dhh) { // get window fit scale
2343
scalew = 1.0 * Dww / iww;
2344
scaleh = 1.0 * Dhh / ihh;
2345
if (scalew < scaleh) fitscale = scalew;
2346
else fitscale = scaleh;
2348
else fitscale = 1.0; // if image < window use 100%
2350
if (zoom == '+') { // zoom bigger
2351
if (! Fzoom) Fzoom = fitscale / 1.2;
2352
Fzoom = Fzoom * sqrt(2.0); // new scale: 41% bigger
2353
for (ii = 0; ii < 9; ii++)
2354
if (Fzoom < 1.01 * scales[ii]) break; // next higher scale in table
2355
if (ii == 9) ii = 8;
2357
if (Fzoom < fitscale) Fzoom = 0; // image < window
2360
if (zoom == '-') Fzoom = 0; // zoom to fit window
2363
if (Fzoom != 0) Fzoom = 0; // toggle 100% and fit window
2367
if (! Fzoom) zoomx = zoomy = 0; // no req. zoom center v.7.5
2369
mwpaint2(); // refresh window
2374
/**************************************************************************/
2376
// monitor test function
2378
void m_montest(GtkWidget *, const char *)
2381
int red, green, blue;
2382
int row, col, row1, row2;
2383
int ww = 800, hh = 500;
2385
if (mod_keep()) return;
2386
if (! menulock(1)) return;
2388
mutex_lock(&pixmaps_lock);
2391
Frgb24 = RGB_make(ww,hh,24);
2397
for (red = 0; red <= 1; red++)
2398
for (green = 0; green <= 1; green++)
2399
for (blue = 0; blue <= 1; blue++)
2401
row1 = 4 * red + 2 * green + blue; // row 0 to 7
2402
row1 = row1 * hh / 8; // stripe, 1/8 of image
2403
row2 = row1 + hh / 8;
2405
for (row = row1; row < row2; row++)
2406
for (col = 0; col < ww; col++)
2408
pixel = (uint8 *) Frgb24->bmp + (row * ww + col) * 3;
2409
pixel[0] = red * 256 * col / ww;
2410
pixel[1] = green * 256 * col / ww;
2411
pixel[2] = blue * 256 * col / ww;
2415
Fzoom = 0; // scale to window
2416
gtk_window_set_title(GTK_WINDOW(mWin),"monitor check");
2417
mutex_unlock(&pixmaps_lock);
2418
mwpaint2(); // repaint window
2424
/**************************************************************************/
2426
// Rebuild assigned tags index and thumbnail files.
2427
// Process all image files within given top-level directory. // overhauled v.8.4
2428
// Works incrementally and is very fast after the first run.
2431
char *file; // image file filespec
2432
char *tags; // image file tags
2433
char imagedate[12], filedate[16]; // both in one rec.
2437
tt_tagrec tt_old[max_images];
2438
tt_tagrec tt_new[max_images];
2441
void m_index_tt(GtkWidget *, const char *)
2444
int err, contx, fcount;
2445
char buff[1000], stbartext[200];
2446
char *subdirk, *pp, **ppv;
2447
char *filespec1, *filespec2;
2448
char *imagedate, *imagetags;
2449
cchar *exifkeys[2] = { exif_date_key, exif_tags_key };
2451
int Nold = 0, Nnew = 0;
2452
int comp, orec, nrec;
2457
if (! Fexiftool) { // exiftool is required
2458
zmessageACK(Bexiftoolmissing);
2462
pp = zgetfile(ZTX("Select top image directory"),topdirk,"folder");
2464
if (topdirk) zfree(topdirk);
2467
if (! menulock(1)) return;
2469
gdk_window_set_cursor(drWin->window,busycursor); // set function busy cursor v.8.4
2472
// read current assigned tags file and build "oldlist" of tags
2474
fid = fopen(asstagsfile,"r"); // open assigned tags file
2479
pp = fgets_trim(buff,999,fid); // read tag and file dates in one rec.
2482
tt_old[Nold].file = 0;
2483
tt_old[Nold].tags = 0;
2484
tt_old[Nold].imagedate[0] = 0;
2485
tt_old[Nold].filedate[0] = 0;
2487
ppc = strField(buff,' ',2); // date: yyyy:mm:dd yyyymmddhhmmss
2488
if (ppc) strncpy0(tt_old[Nold].imagedate,ppc,12);
2490
ppc = strField(buff,' ',3);
2491
if (ppc) strncpy0(tt_old[Nold].filedate,ppc,16);
2493
pp = fgets_trim(buff,999,fid); // tags: xxxxx xxxxx xxxxxxx xxxx
2495
tt_old[Nold].tags = strdupz(pp+6);
2497
pp = fgets_trim(buff,999,fid); // file: /directory/.../filename.jpg
2499
tt_old[Nold].file = strdupz(pp+6);
2501
fgets_trim(buff,999,fid); // read blank separator rec.
2503
if (++Nold == max_images)
2504
zappcrash("more than %d image files: %d",max_images);
2510
printf("%d current tag records found \n",Nold);
2512
// find all image files and create "newlist" with no tags
2514
snprintf(buff,999,"find \"%s\" -type d",topdirk);
2517
while ((subdirk = command_output(contx,buff))) // find directories under top directory
2519
pp = (char *) strrchr(subdirk,'/');
2520
if (pp && strEqu(pp,"/.thumbnails")) { // ignore .thumbnails
2525
image_gallery(subdirk,"init"); // get all image files in directory
2526
filespec1 = image_gallery(subdirk,"first");
2530
if (image_file_type(filespec1) == 2) { // construct new tag record
2531
err = stat(filespec1,&statb);
2533
tt_new[Nnew].file = strdupz(filespec1); // filespec
2534
tt_new[Nnew].tags = 0; // tags = empty
2535
tt_new[Nnew].imagedate[0] = 0; // tag date = empty
2536
gmtime_r(&statb.st_mtime,&bdt);
2537
sprintf(tt_new[Nnew].filedate,"%04d%02d%02d%02d%02d%02d", // file date = yyyymmddhhmmss
2538
bdt.tm_year + 1900, bdt.tm_mon + 1, bdt.tm_mday,
2539
bdt.tm_hour, bdt.tm_min, bdt.tm_sec);
2540
if (++Nnew == max_images)
2541
zappcrash("more than %d image files: %d",max_images);
2544
filespec2 = image_gallery(filespec1,"next"); // next image file
2546
filespec1 = filespec2;
2552
printf("found %d image files \n",Nnew);
2554
// sort old and new lists by filespec in preparation for merging them
2556
int index_tt_comp(cchar *rec1, cchar *rec2);
2558
HeapSort((char *) tt_old,sizeof(tt_tagrec),Nold,index_tt_comp);
2559
HeapSort((char *) tt_new,sizeof(tt_tagrec),Nnew,index_tt_comp);
2561
// merge and compare lists
2562
// if filespecs match and have the same date, then "tt_old" tags are OK
2564
for (orec = nrec = 0; nrec < Nnew; )
2566
tt_new[nrec].update = 1;
2568
if (orec == Nold) comp = +1;
2569
else comp = strcmp(tt_old[orec].file, tt_new[nrec].file);
2571
if (comp > 0) nrec++;
2572
else if (comp < 0) orec++;
2575
if (strEqu(tt_new[nrec].filedate, tt_old[orec].filedate)) {
2576
tt_new[nrec].tags = tt_old[orec].tags; // copy tags and tag date
2577
tt_old[orec].tags = 0; // from old to new
2578
strcpy(tt_new[nrec].imagedate, tt_old[orec].imagedate);
2579
tt_new[nrec].update = 0;
2586
// release old list memory
2588
for (orec = 0; orec < Nold; orec++)
2590
zfree(tt_old[orec].file);
2591
if (tt_old[orec].tags) zfree(tt_old[orec].tags);
2594
// process entries needing update in new list, get updated tags from image file EXIF data
2596
for (fcount = nrec = 0; nrec < Nnew; nrec++)
2598
if (tt_new[nrec].update == 0) continue;
2600
ppv = exif_get(tt_new[nrec].file,exifkeys,2);
2604
if (imagedate && strlen(imagedate)) {
2605
if (strlen(imagedate) > 9) imagedate[10] = 0; // truncate to yyyy:mm:dd
2606
strcpy(tt_new[nrec].imagedate,imagedate);
2608
else strcpy(tt_new[nrec].imagedate,"null");
2610
if (imagetags && strlen(imagetags))
2611
tt_new[nrec].tags = strdupz(imagetags);
2612
else tt_new[nrec].tags = strdupz("null");
2614
if (imagedate) zfree(imagedate);
2615
if (imagetags) zfree(imagetags);
2617
snprintf(stbartext,199,"%5d %s",++fcount,tt_new[nrec].file); // update status bar
2618
stbar_message(STbar,stbartext);
2621
// write new assigned tags file
2623
fid = fopen(asstagsfile,"w"); // open assigned tags file
2624
if (! fid) zappcrash("cannot write tags file");
2626
for (nrec = 0; nrec < Nnew; nrec++)
2628
fprintf(fid,"date: %s %s""\n", tt_new[nrec].imagedate, tt_new[nrec].filedate);
2629
fprintf(fid,"tags: %s""\n",tt_new[nrec].tags);
2630
fprintf(fid,"file: %s""\n",tt_new[nrec].file);
2636
// look for missing thumbnails and create them
2638
for (fcount = nrec = 0; nrec < Nnew; nrec++)
2640
pp = image_thumbfile(tt_new[nrec].file); // find/update/create thumbnail
2642
snprintf(stbartext,199,"%5d %s",++fcount,tt_new[nrec].file); // update status bar
2643
stbar_message(STbar,stbartext);
2646
// release new list memory
2648
for (nrec = 0; nrec < Nnew; nrec++)
2650
zfree(tt_new[nrec].file);
2651
if (tt_new[nrec].tags) zfree(tt_new[nrec].tags);
2654
image_gallery(image_file,"init"); // reset image gallery file list
2655
image_gallery(0,"paint2"); // refresh gallery window if active
2658
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
2664
// sort compare function - compare tag record filespecs and return
2665
// <0 | 0 | >0 for file1 < | == | > file2
2667
int index_tt_comp(cchar *rec1, cchar *rec2)
2669
char * file1 = ((tt_tagrec *) rec1)->file;
2670
char * file2 = ((tt_tagrec *) rec2)->file;
2671
return strcmp(file1,file2);
2675
/**************************************************************************/
2677
// create or update brightness distribution graph
2679
void m_brightgraph(GtkWidget *, const char *) // menu function
2681
if (! Drgb24) return;
2684
brightgraph_paint();
2688
brightgraph = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2689
gtk_window_set_title(GTK_WINDOW(brightgraph),ZTX("Brightness Distribution"));
2690
gtk_window_set_transient_for(GTK_WINDOW(brightgraph),GTK_WINDOW(mWin));
2691
gtk_window_set_default_size(GTK_WINDOW(brightgraph),300,200);
2692
gtk_window_set_position(GTK_WINDOW(brightgraph),GTK_WIN_POS_MOUSE);
2694
drbrightgraph = gtk_drawing_area_new();
2695
gtk_container_add(GTK_CONTAINER(brightgraph),drbrightgraph);
2697
G_SIGNAL(brightgraph,"destroy",brightgraph_destroy,0)
2698
G_SIGNAL(drbrightgraph,"expose-event",brightgraph_paint,0)
2700
gtk_widget_show_all(brightgraph);
2706
void brightgraph_paint() // paint graph window
2708
int brdist[20], nbins = 20;
2711
int ww, hh, orgx, orgy;
2712
int dist_maxbin = 0;
2716
if (! brightgraph) return;
2717
if (! Drgb24) return;
2719
for (ii = 0; ii < nbins; ii++) // clear brightness distribution
2722
mutex_lock(&pixmaps_lock);
2724
for (py = 0; py < dhh; py++) // compute brightness distribution
2725
for (px = 0; px < dww; px++) // for image in visible window
2726
{ // Dww/hh -> dww/hh bugfix v.7.4.2
2727
pixel = (uint8 *) Drgb24->bmp + (py * dww + px) * 3;
2728
bright = brightness(pixel); // 0 to 255
2729
brdist[int(bright / 256 * nbins)]++; // 0 to nbins
2732
mutex_unlock(&pixmaps_lock);
2734
gdk_window_clear(drbrightgraph->window);
2736
winww = drbrightgraph->allocation.width;
2737
winhh = drbrightgraph->allocation.height;
2739
for (ii = 0; ii < nbins; ii++)
2740
if (brdist[ii] > dist_maxbin) dist_maxbin = brdist[ii];
2742
for (ii = 0; ii < nbins; ii++)
2745
hh = int(0.9 * winhh * brdist[ii] / dist_maxbin);
2748
gdk_draw_rectangle(drbrightgraph->window,gdkgc,1,orgx,orgy,ww,hh);
2755
void brightgraph_destroy() // delete window
2757
if (brightgraph) gtk_widget_destroy(brightgraph);
2763
/**************************************************************************/
2765
// start a new instance of fotoxx in parallel
2767
void m_clone(GtkWidget *, const char *)
2772
snprintf(command,299,"fotoxx -l %s",zfuncs::zlanguage); // keep language v.8.5
2773
if (image_file) strncatv(command,299," \"",image_file,"\"",null);
2774
strcat(command," &");
2775
ignore = system(command);
2780
/**************************************************************************/
2782
// enter or leave slideshow mode
2784
void m_slideshow(GtkWidget *, const char *)
2792
gtk_window_get_size(GTK_WINDOW(mWin),&ww,&hh);
2793
gtk_widget_hide_all(GTK_WIDGET(mMbar)); // enter slide show mode
2794
gtk_widget_hide_all(GTK_WIDGET(mTbar)); // (full screen, no extras)
2795
gtk_widget_hide_all(GTK_WIDGET(STbar));
2796
gtk_window_fullscreen(GTK_WINDOW(mWin));
2798
zd = zdialog_new(ZTX("Time Interval"),mWin,Bapply,Bcancel,0); // dialog to get interval v.8.4
2799
zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=10");
2800
zdialog_add_widget(zd,"label","lab1","hb1",ZTX("seconds"));
2801
zdialog_add_widget(zd,"entry","secs","hb1",0,"scc=5");
2802
zdialog_stuff(zd,"secs",SS_interval);
2803
zstat = zdialog_run(zd,0,0);
2804
zdialog_fetch(zd,"secs",secs);
2806
SS_interval = secs; // interval between slides
2807
if (zstat != 1) secs = 9999; // cancel, use huge interval
2808
SS_timer = get_seconds() + secs + 1; // set timer for next slide
2814
gtk_window_unfullscreen(GTK_WINDOW(mWin)); // leave slide show mode
2815
gtk_window_resize(GTK_WINDOW(mWin),ww,hh);
2816
gtk_widget_show_all(GTK_WIDGET(mMbar));
2817
gtk_widget_show_all(GTK_WIDGET(mTbar));
2818
gtk_widget_show_all(GTK_WIDGET(STbar));
2822
Fzoom = 0; // fit image to window
2823
Fblowup = Fslideshow; // blow-up small images if SS mode
2829
/**************************************************************************/
2831
// show RGB values for pixel at mouse click
2833
CBfunc *RGB_func_save;
2837
void m_RGB(GtkWidget *, const char *) // menu function
2839
void RGB_mousefunc();
2841
if (! Frgb24) return; // no image
2842
if (Frgbmode) return;
2843
RGB_func_save = mouseCBfunc;
2844
RGB_mcap_save = Mcapture;
2845
mouseCBfunc = RGB_mousefunc; // connect mouse function
2846
Mcapture = 1; // capture mouse clicks
2851
void RGB_mousefunc() // mouse function
2854
double red, green, blue;
2855
double fbright, fred;
2860
if (! Frgb24) return;
2862
if (LMclick) // left mouse click
2865
px = Mxclick; // click position
2868
if (E3rgb48) { // use current image being edited
2869
if (px < 0 || px > E3ww-1 || // outside image area v.6.4
2870
py < 0 || py > E3hh-1) return;
2871
ppix48 = bmpixel(E3rgb48,px,py); // bugfix: * Mscale removed v.6.7
2872
red = ppix48[0] / 256.0;
2873
green = ppix48[1] / 256.0;
2874
blue = ppix48[2] / 256.0;
2875
fbright = brightness(ppix48) / 256.0;
2876
fred = redness(ppix48);
2879
else if (Frgb48) { // use edited image
2880
if (px < 0 || px > Fww-1 ||
2881
py < 0 || py > Fhh-1) return;
2882
ppix48 = bmpixel(Frgb48,px,py);
2883
red = ppix48[0] / 256.0;
2884
green = ppix48[1] / 256.0;
2885
blue = ppix48[2] / 256.0;
2886
fbright = brightness(ppix48) / 256.0;
2887
fred = redness(ppix48);
2890
else { // use 24 bpp image
2891
if (px < 0 || px > Fww-1 ||
2892
py < 0 || py > Fhh-1) return;
2893
ppix24 = (uint8 *) Frgb24->bmp + (py * Fww + px) * 3;
2897
fbright = brightness(ppix24);
2898
fred = redness(ppix24);
2901
snprintf(work,89,"Pixel: %d %d RGB: %6.3f %6.3f %6.3f", // show pixel and RGB colors v.7.0
2902
px, py, red, green, blue);
2903
stbar_message(STbar,work);
2906
if (RMclick || KBkey == GDK_Escape) // escape or right-click v.7.0
2908
RMclick = Frgbmode = KBkey = 0; // v.8.6
2909
Mcapture = RGB_mcap_save;
2910
mouseCBfunc = RGB_func_save; // disconnect mouse
2917
/**************************************************************************/
2919
// choose or set lens parameters for panoramas
2921
void m_parms(GtkWidget *, const char *)
2924
int ii, zstat, radb;
2927
zd = zdialog_new(ZTX("Lens Parameters"),mWin,Bapply,Bcancel,0);
2928
zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=5");
2930
zdialog_add_widget(zd,"vbox","vb1","hb1",0,"space=5|homog"); // Lens mm bow
2931
zdialog_add_widget(zd,"vbox","vb2","hb1",0,"space=5|homog"); // (o) name1 30 0.33
2932
zdialog_add_widget(zd,"vbox","vb3","hb1",0,"space=5|homog"); // (x) name2 40 0.22
2933
zdialog_add_widget(zd,"vbox","vb4","hb1",0,"space=5|homog"); // (o) name3 45 0.28
2934
zdialog_add_widget(zd,"label","space","vb1"); // (o) name4 50 0.44
2935
zdialog_add_widget(zd,"radio","radb0","vb1",0); //
2936
zdialog_add_widget(zd,"radio","radb1","vb1",0); // [apply] [cancel]
2937
zdialog_add_widget(zd,"radio","radb2","vb1",0);
2938
zdialog_add_widget(zd,"radio","radb3","vb1",0);
2939
zdialog_add_widget(zd,"label","lname","vb2",ZTX("lens name")); // fix translation v.8.4.1
2940
zdialog_add_widget(zd,"entry","name0","vb2","scc=10");
2941
zdialog_add_widget(zd,"entry","name1","vb2","scc=10");
2942
zdialog_add_widget(zd,"entry","name2","vb2","scc=10");
2943
zdialog_add_widget(zd,"entry","name3","vb2","scc=10");
2944
zdialog_add_widget(zd,"label","lmm","vb3",ZTX("lens mm"));
2945
zdialog_add_widget(zd,"entry","mm0","vb3","0","scc=5");
2946
zdialog_add_widget(zd,"entry","mm1","vb3","0","scc=5");
2947
zdialog_add_widget(zd,"entry","mm2","vb3","0","scc=5");
2948
zdialog_add_widget(zd,"entry","mm3","vb3","0","scc=5");
2949
zdialog_add_widget(zd,"label","lbow","vb4",ZTX("lens bow"));
2950
zdialog_add_widget(zd,"entry","bow0","vb4","0.0","scc=6");
2951
zdialog_add_widget(zd,"entry","bow1","vb4","0.0","scc=6");
2952
zdialog_add_widget(zd,"entry","bow2","vb4","0.0","scc=6");
2953
zdialog_add_widget(zd,"entry","bow3","vb4","0.0","scc=6");
2955
for (ii = 0; ii < 4; ii++) // stuff lens data into dialog
2957
snprintf(text,20,"name%d",ii);
2958
zdialog_stuff(zd,text,lens4_name[ii]);
2959
snprintf(text,20,"mm%d",ii);
2960
zdialog_stuff(zd,text,lens4_mm[ii]);
2961
snprintf(text,20,"bow%d",ii);
2962
zdialog_stuff(zd,text,lens4_bow[ii]);
2965
snprintf(text,20,"radb%d",curr_lens); // current lens = selected
2966
zdialog_stuff(zd,text,1);
2968
zstat = zdialog_run(zd,0,0); // run dialog, get inputs
2971
zdialog_free(zd); // canceled
2975
for (ii = 0; ii < 4; ii++) // fetch lens data (revisions)
2977
snprintf(text,20,"name%d",ii);
2978
zdialog_fetch(zd,text,lens4_name[ii],lens_cc);
2979
repl_1str(lens4_name[ii],lens4_name[ii]," ","_"); // replace blank with _
2980
snprintf(text,20,"mm%d",ii);
2981
zdialog_fetch(zd,text,lens4_mm[ii]);
2982
snprintf(text,20,"bow%d",ii);
2983
zdialog_fetch(zd,text,lens4_bow[ii]);
2984
snprintf(text,20,"radb%d",ii); // detect which is selected
2985
zdialog_fetch(zd,text,radb);
2986
if (radb) curr_lens = ii;
2994
/**************************************************************************/
2998
void m_lang(GtkWidget *, const char *) // v.6.2
3001
int ii, cc, err, zstat;
3003
char lang[20], *pp, command[100], locmess[200];
3005
const char *langs[10] = { "cz Czech", "de German", "el Greek",
3006
"en English", "es Spanish", "fr French",
3007
"gl Galacian", "it Italian", "zh_CN Chinese", 0 };
3009
strcpy(locmess,ZTX("Available Translations"));
3010
cc = strlen(locmess);
3012
for (ii = 0; langs[ii]; ii++)
3014
strcpy(locmess+cc,"\n ");
3015
strcpy(locmess+cc+2,langs[ii]);
3016
cc += strlen(langs[ii]) + 2;
3019
zd = zdialog_new(ZTX("Set Language"),mWin,Bapply,Bcancel,0);
3020
zdialog_add_widget(zd,"label","lab0","dialog",locmess,"space=5");
3021
zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=10");
3022
zdialog_add_widget(zd,"label","lab1","hb1","language code");
3023
zdialog_add_widget(zd,"combo","combo","hb1",0);
3025
for (ii = 0; langs[ii]; ii++) // stuff languages into list
3026
zdialog_cb_app(zd,"combo",langs[ii]);
3028
zstat = zdialog_run(zd,0,0); // run dialog
3031
zdialog_free(zd); // canceled
3035
zdialog_fetch(zd,"combo",lang,20); // get user input
3038
pp = strchr(lang,' '); // isolate lc_RC part
3041
sprintf(command,"fotoxx -l %s &",lang); // restart fotoxx
3042
err = system(command);
3049
/**************************************************************************/
3051
// create desktop icon / launcher
3053
void m_launcher(GtkWidget *, const char *) // v.7.0
3055
zmake_launcher("Graphics","Image Editor");
3060
/**************************************************************************/
3062
// convert multiple RAW files to tiff
3064
void m_multiraw(GtkWidget *, const char *) // v.7.1
3067
char entraw[200], *pp, *rawspec = 0;
3068
char *outfile, command[1000];
3069
const char *rawfile;
3070
int zstat, ftf, err;
3073
if (mod_keep()) return;
3074
if (! menulock(1)) return;
3077
zmessageACK(ZTX("Package ufraw required for this function"));
3081
zd = zdialog_new(ZTX("Convert multiple RAW files"),mWin,BOK,Bcancel,null);
3082
zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=10");
3083
zdialog_add_widget(zd,"label","labraw","hb1",ZTX("RAW file template"),"space=5");
3084
zdialog_add_widget(zd,"entry","entraw","hb1","*.RAW","space=5");
3086
zstat = zdialog_run(zd); // run dialog (blocking)
3087
zdialog_fetch(zd,"entraw",entraw,199); // get RAW file spec
3089
if (zstat != 1) goto rawdone;
3091
if (*entraw != '/') // needs a directory
3094
rawspec = strdupz(image_file,200); // use same as image file
3095
pp = (char *) strrchr(rawspec,'/');
3098
strncpy0(pp,entraw,199);
3101
pp = getcwd(null,0); // use curr. directory
3102
rawspec = strdupz(pp,200);
3104
strcat(rawspec,"/");
3105
pp = (char *) strrchr(rawspec,'/');
3106
strncpy0(pp+1,entraw,199);
3110
else rawspec = strdupz(entraw); // absolute path was given
3112
write_popup_text("open","converting RAW files",500,200);
3114
gdk_window_set_cursor(drWin->window,busycursor); // set function busy cursor v.8.4
3119
rawfile = SearchWild(rawspec,ftf); // find all *.RAW files
3120
if (! rawfile) break;
3122
err = stat(rawfile,&fstat);
3125
outfile = strdupz(rawfile,5);
3126
pp = (char *) strrchr(rawfile,'.');
3127
pp = outfile + (pp - rawfile);
3130
write_popup_text("write",rawfile,0,0); // convert next file
3132
// try new ufraw command format first, then old if it fails
3134
snprintf(command,999,"ufraw-batch --out-type=tiff --out-depth=16"
3135
" --overwrite --output=\"%s\" \"%s\" ",outfile, rawfile);
3136
err = system(command);
3139
snprintf(command,999,"ufraw-batch --out-type=tiff16"
3140
" --overwrite --output=\"%s\" \"%s\" ",outfile, rawfile);
3141
err = system(command);
3145
write_popup_text("write",wstrerror(err),0,0);
3151
f_open(outfile); // open converted file
3156
image_gallery(outfile,"init"); // update image gallery file list
3157
image_gallery(0,"paint2"); // refresh gallery window if active
3162
write_popup_text("close",0,0,0);
3164
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
3167
if (rawspec) zfree(rawspec);
3173
/**************************************************************************/
3175
// burn images to CD/DVD // v.7.2
3177
int burn_showthumb();
3179
GtkWidget *burn_drawarea = 0;
3180
GtkWidget *burn_files = 0;
3181
const char *burn_font = "Monospace 9";
3182
int burn_fontheight = 14;
3183
int burn_cursorpos = 0;
3185
void m_burn(GtkWidget *, const char *)
3187
int burn_dialog_event(zdialog *zd, const char *event);
3188
int burn_dialog_compl(zdialog *zd, int zstat);
3189
int burn_mouseclick(GtkWidget *, GdkEventButton *event, void *);
3194
if (! menulock(1)) return; // lock menus
3196
m_gallery(0,0); // activate image gallery window
3198
zdburn = zdialog_new(ZTX("Burn Images to CD/DVD"),0,ZTX("Burn"),Bcancel,null);
3199
zdialog_add_widget(zdburn,"hbox","hb1","dialog",0,"expand|space=5");
3200
zdialog_add_widget(zdburn,"frame","fr11","hb1",0,"expand");
3201
zdialog_add_widget(zdburn,"scrwin","scrwin","fr11",0,"expand");
3202
zdialog_add_widget(zdburn,"edit","files","scrwin");
3203
zdialog_add_widget(zdburn,"vbox","vb12","hb1");
3204
zdialog_add_widget(zdburn,"frame","fr12","vb12");
3205
zdialog_add_widget(zdburn,"hbox","hb2","dialog",0,"space=5");
3206
zdialog_add_widget(zdburn,"button","delete","hb2",Bdelete,"space=8");
3207
zdialog_add_widget(zdburn,"button","insert","hb2",Binsert,"space=8");
3208
zdialog_add_widget(zdburn,"button","addall","hb2",Baddall,"space=30");
3210
GtkWidget *frame = zdialog_widget(zdburn,"fr12"); // drawing area for thumbnail image
3211
burn_drawarea = gtk_drawing_area_new();
3212
gtk_widget_set_size_request(burn_drawarea,128,128);
3213
gtk_container_add(GTK_CONTAINER(frame),burn_drawarea);
3215
burn_files = zdialog_widget(zdburn,"files"); // activate mouse-clicks for
3216
gtk_widget_add_events(burn_files,GDK_BUTTON_PRESS_MASK); // file list widget
3217
G_SIGNAL(burn_files,"button-press-event",burn_mouseclick,0)
3219
PangoFontDescription *pfontdesc = pango_font_description_from_string(burn_font);
3220
gtk_widget_modify_font(burn_files,pfontdesc);
3222
zdialog_resize(zdburn,400,0);
3223
zdialog_run(zdburn,burn_dialog_event,burn_dialog_compl);
3225
cursor = gdk_cursor_new(GDK_TOP_LEFT_ARROW); // arrow cursor for file list widget
3226
gdkwin = gtk_text_view_get_window(GTK_TEXT_VIEW(burn_files),textwin);
3227
gdk_window_set_cursor(gdkwin,cursor);
3234
// burn dialog event function
3236
int burn_dialog_event(zdialog *zd, const char *event)
3238
GtkTextBuffer *textBuff;
3239
GtkTextIter iter1, iter2;
3240
static char *imagefile = 0;
3244
if (strEqu(event,"delete")) // delete file at cursor position
3246
if (imagefile) free(imagefile);
3248
textBuff = gtk_text_view_get_buffer(GTK_TEXT_VIEW(burn_files));
3249
line = burn_cursorpos;
3250
gtk_text_buffer_get_iter_at_line(textBuff,&iter1,line); // iter at line start
3252
gtk_text_iter_forward_to_line_end(&iter2); // iter at line end
3254
imagefile = gtk_text_buffer_get_text(textBuff,&iter1,&iter2,0); // save selected file
3255
if (*imagefile != '/') {
3261
gtk_text_buffer_delete(textBuff,&iter1,&iter2); // delete file text
3262
gtk_text_buffer_get_iter_at_line(textBuff,&iter2,line+1);
3263
gtk_text_buffer_delete(textBuff,&iter1,&iter2); // delete empty line (\n)
3265
burn_showthumb(); // thumbnail = next file
3268
if (strEqu(event,"insert")) // insert last deleted file
3270
if (! imagefile) return 0; // at current cursor position
3271
burn_insert_file(imagefile);
3274
if (strEqu(event,"addall")) // insert all files in image gallery
3276
if (imagefile) free(imagefile);
3279
textBuff = gtk_text_view_get_buffer(GTK_TEXT_VIEW(burn_files));
3284
imagefile = image_gallery(imagefile,xfile,1,0); // next file
3285
if (! imagefile) break;
3287
gtk_text_buffer_get_iter_at_line(textBuff,&iter1,burn_cursorpos);
3288
gtk_text_buffer_insert(textBuff,&iter1,"\n",1); // insert new blank line
3289
gtk_text_buffer_get_iter_at_line(textBuff,&iter1,burn_cursorpos);
3290
gtk_text_buffer_insert(textBuff,&iter1,imagefile,-1); // insert image file
3291
burn_cursorpos++; // advance cursor position
3299
// burn dialog completion function - send all selected files to brasero
3301
int burn_dialog_compl(zdialog *zd, int zstat)
3303
int line, nlines, cc1, cc2, err;
3304
char *imagefile = 0;
3306
GtkTextBuffer *textBuff;
3307
GtkTextIter iter1, iter2;
3309
if (zstat != 1) { // cancelled
3310
zdialog_free(zdburn); // kill dialog
3316
textBuff = gtk_text_view_get_buffer(GTK_TEXT_VIEW(burn_files));
3317
nlines = gtk_text_buffer_get_line_count(textBuff);
3318
cc1 = gtk_text_buffer_get_char_count(textBuff);
3319
cc1 = cc1 + 5 * nlines + 20;
3320
command = zmalloc(cc1);
3321
strcpy(command,"brasero");
3322
cc2 = strlen(command);
3324
for (line = 0; line < nlines; line++)
3326
gtk_text_buffer_get_iter_at_line(textBuff,&iter1,line); // iter at line start
3328
gtk_text_iter_forward_to_line_end(&iter2);
3329
imagefile = gtk_text_buffer_get_text(textBuff,&iter1,&iter2,0); // get imagefile at line
3330
if (imagefile && *imagefile == '/') {
3331
strcpy(command+cc2," \"");
3333
strcpy(command+cc2,imagefile);
3334
cc2 += strlen(imagefile);
3335
strcpy(command+cc2,"\"");
3341
zdialog_free(zdburn); // kill dialog
3344
strcat(command," &"); // do command in background v.8.4
3345
err = system(command); // start brasero
3352
// called from image gallery window when a thumbnail is clicked
3353
// add image file to list at current cursor position, set thumbnail = file
3355
void burn_insert_file(const char *imagefile)
3358
GtkTextBuffer *textBuff;
3360
if (*imagefile == '/') {
3361
textBuff = gtk_text_view_get_buffer(GTK_TEXT_VIEW(burn_files));
3362
gtk_text_buffer_get_iter_at_line(textBuff,&iter,burn_cursorpos);
3363
gtk_text_buffer_insert(textBuff,&iter,"\n",1); // insert new blank line
3364
gtk_text_buffer_get_iter_at_line(textBuff,&iter,burn_cursorpos);
3365
gtk_text_buffer_insert(textBuff,&iter,imagefile,-1); // insert image file
3366
burn_showthumb(); // update thumbnail
3367
burn_cursorpos++; // advance cursor position
3374
// process mouse click in files window:
3375
// set new cursor position and set thumbnail = clicked file
3377
int burn_mouseclick(GtkWidget *, GdkEventButton *event, void *)
3380
GtkWidget *scrollwin;
3381
GtkAdjustment *scrolladj;
3384
if (event->type != GDK_BUTTON_PRESS) return 0;
3385
mpx = int(event->x); // mouse position
3386
mpy = int(event->y);
3387
scrollwin = zdialog_widget(zdburn,"scrwin"); // window scroll position
3388
scrolladj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scrollwin));
3389
scrollpos = gtk_adjustment_get_value(scrolladj);
3390
burn_cursorpos = (mpy + scrollpos) / burn_fontheight; // line selected
3391
burn_showthumb(); // show thumbnail image
3396
// show thumbnail for file at current cursor position
3398
int burn_showthumb()
3402
GtkTextBuffer *textBuff;
3403
GtkTextIter iter1, iter2;
3404
GdkPixbuf *thumbnail = 0;
3406
gdk_window_clear(burn_drawarea->window);
3408
line = burn_cursorpos;
3409
textBuff = gtk_text_view_get_buffer(GTK_TEXT_VIEW(burn_files));
3410
gtk_text_buffer_get_iter_at_line(textBuff,&iter1,line); // iter at line start
3412
gtk_text_iter_forward_to_line_end(&iter2); // iter at line end
3414
imagefile = gtk_text_buffer_get_text(textBuff,&iter1,&iter2,0); // get selected file
3415
if (*imagefile != '/') {
3420
thumbnail = image_thumbnail(imagefile,128); // get thumbnail
3424
gdk_draw_pixbuf(burn_drawarea->window,0,thumbnail,0,0,0,0,-1,-1,nodither,0,0);
3425
g_object_unref(thumbnail);
3431
/**************************************************************************
3432
Image Tag and EXIF functions
3433
***************************************************************************/
3435
void edit_tags_fixup(cchar * widgetname); // fixup tag selection widgets
3436
void edit_tags_mouse(GtkTextView *, GdkEventButton *, cchar *); // select tag via mouse click
3437
int add_unique_tag(cchar *tag, char *taglist, int maxcc); // add tag if unique and enough space
3438
void add_new_filetag(); // add tags_atag to tags_filetags
3439
void delete_filetag(); // remove tags_atag from tags_filetags
3440
void add_new_recentag(); // add tags_atag to tags_recentags
3442
char tags_imdate[12] = ""; // image date, yyyymmdd
3443
char tags_limdate[12] = ""; // last image date read or set
3444
int tags_stars = 0; // image rating in "stars"
3445
char tags_atag[maxtag1] = ""; // one tag
3446
char tags_filetags[maxtag2] = ""; // tags for one file
3447
char tags_asstags[maxtag3] = ""; // all assigned tags
3448
char tags_searchfile[maxtagF] = ""; // image search /path*/file*
3449
char tags_searchtags[maxtag4] = ""; // image search tags
3450
char tags_recentags[maxtag5] = ""; // recently added tags
3451
int tags_changed = 0; // tags have been changed
3454
/**************************************************************************/
3456
// edit tags menu function
3458
void m_edit_tags(GtkWidget *, const char *)
3460
if (! Fexiftool) { // exiftool is required
3461
zmessageACK(Bexiftoolmissing);
3470
// activate edit tags dialog, stuff data from current file
3472
void edit_tags_dialog()
3474
int edit_tags_dialog_event(zdialog *zd, const char *event);
3475
int edit_tags_dialog_compl(zdialog *zd, int zstat);
3477
char *ppv, pstarsN[12];
3479
if (! image_file) return;
3481
if (! zdtags) // (re) start tag edit dialog
3483
load_asstags(); // get all assigned tags
3485
zdtags = zdialog_new(ZTX("Edit Tags"),mWin,Bdone,Bcancel,0); // tag edit dialog
3487
zdialog_add_widget(zdtags,"hbox","hb1","dialog",0,"space=5");
3488
zdialog_add_widget(zdtags,"label","labfile","hb1",ZTX("file:"),"space=10");
3489
zdialog_add_widget(zdtags,"label","file","hb1");
3491
zdialog_add_widget(zdtags,"hbox","hb2","dialog",0,"space=5");
3492
zdialog_add_widget(zdtags,"label","lab21","hb2",ZTX("image date (yyyymmdd)"),"space=10");
3493
zdialog_add_widget(zdtags,"entry","imdate","hb2",0,"scc=12");
3494
zdialog_add_widget(zdtags,"button","limdate","hb2",ZTX("use last"),"space=10");
3496
zdialog_add_widget(zdtags,"hbox","hb3","dialog",0,"space=5");
3497
zdialog_add_widget(zdtags,"label","labstars","hb3",ZTX("image stars"),"space=10");
3498
zdialog_add_widget(zdtags,"vbox","vb3","hb3");
3499
zdialog_add_widget(zdtags,"hbox","hb31","vb3",0,"homog");
3500
zdialog_add_widget(zdtags,"hbox","hb32","vb3",0,"homog");
3501
zdialog_add_widget(zdtags,"label","lab30","hb31","0");
3502
zdialog_add_widget(zdtags,"label","lab31","hb31","1");
3503
zdialog_add_widget(zdtags,"label","lab32","hb31","2");
3504
zdialog_add_widget(zdtags,"label","lab33","hb31","3");
3505
zdialog_add_widget(zdtags,"label","lab34","hb31","4");
3506
zdialog_add_widget(zdtags,"label","lab35","hb31","5");
3507
zdialog_add_widget(zdtags,"radio","pstars0","hb32",0);
3508
zdialog_add_widget(zdtags,"radio","pstars1","hb32",0);
3509
zdialog_add_widget(zdtags,"radio","pstars2","hb32",0);
3510
zdialog_add_widget(zdtags,"radio","pstars3","hb32",0);
3511
zdialog_add_widget(zdtags,"radio","pstars4","hb32",0);
3512
zdialog_add_widget(zdtags,"radio","pstars5","hb32",0);
3514
zdialog_add_widget(zdtags,"hbox","hb4","dialog","space=5");
3515
zdialog_add_widget(zdtags,"label","lab4","hb4",ZTX("current tags"),"space=10");
3516
zdialog_add_widget(zdtags,"frame","frame4","hb4",0,"expand");
3517
zdialog_add_widget(zdtags,"edit","filetags","frame4",0,"expand");
3519
zdialog_add_widget(zdtags,"hbox","hb5","dialog","space=5");
3520
zdialog_add_widget(zdtags,"label","recent","hb5",ZTX("recently added"),"space=10");
3521
zdialog_add_widget(zdtags,"frame","frame5","hb5",0,"expand");
3522
zdialog_add_widget(zdtags,"edit","recentags","frame5",0,"expand");
3524
zdialog_add_widget(zdtags,"hbox","hb6","dialog",0,"space=5");
3525
zdialog_add_widget(zdtags,"button","addtag","hb6",ZTX("create tag"),"space=10");
3526
zdialog_add_widget(zdtags,"entry","atag","hb6",0);
3528
zdialog_add_widget(zdtags,"hbox","hb7","dialog",0,"space=5");
3529
zdialog_add_widget(zdtags,"hbox","hb8","dialog");
3530
zdialog_add_widget(zdtags,"label","labasstags","hb8",ZTX("assigned tags"),"space=10");
3531
zdialog_add_widget(zdtags,"frame","frame8","dialog",0,"expand");
3532
zdialog_add_widget(zdtags,"edit","asstags","frame8",0,"expand");
3534
zdialog_resize(zdtags,400,300);
3535
zdialog_run(zdtags,edit_tags_dialog_event,edit_tags_dialog_compl); // start dialog
3537
edit_tags_fixup("filetags"); // setup for mouse tag selection
3538
edit_tags_fixup("asstags");
3539
edit_tags_fixup("recentags");
3542
load_filetags(image_file); // get file tags from EXIF data
3544
ppv = (char *) strrchr(image_file,'/');
3545
zdialog_stuff(zdtags,"file",ppv+1); // stuff dialog file name
3547
zdialog_stuff(zdtags,"imdate",tags_imdate); // stuff dialog data
3548
sprintf(pstarsN,"pstars%d",tags_stars);
3549
zdialog_stuff(zdtags,pstarsN,1);
3550
zdialog_stuff(zdtags,"filetags",tags_filetags);
3551
zdialog_stuff(zdtags,"asstags",tags_asstags);
3552
zdialog_stuff(zdtags,"recentags",tags_recentags);
3559
// setup tag display widget for tag selection using mouse clicks
3561
void edit_tags_fixup(const char * widgetname)
3566
widget = zdialog_widget(zdtags,widgetname); // make widget wrap text
3567
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(widget),GTK_WRAP_WORD);
3568
gtk_text_view_set_editable(GTK_TEXT_VIEW(widget),0); // disable widget editing
3570
gdkwin = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),textwin); // cursor for tag selection
3571
gdk_window_set_cursor(gdkwin,arrowcursor);
3573
gtk_widget_add_events(widget,GDK_BUTTON_PRESS_MASK); // connect mouse-click event
3574
G_SIGNAL(widget,"button-press-event",edit_tags_mouse,widgetname)
3578
// edit tags mouse-click event function
3579
// get clicked tag and add to or remove from tags_filetags
3581
void edit_tags_mouse(GtkTextView *widget, GdkEventButton *event, const char *widgetname)
3584
int mpx, mpy, tbx, tby, offset, cc;
3585
char *ptext, *pp1, *pp2;
3587
if (event->type != GDK_BUTTON_PRESS) return;
3588
mpx = int(event->x); // mouse click position
3589
mpy = int(event->y);
3591
gtk_text_view_window_to_buffer_coords(widget,GTK_TEXT_WINDOW_TEXT,mpx,mpy,&tbx,&tby);
3592
gtk_text_view_get_iter_at_location(widget,&iter,tbx,tby);
3593
offset = gtk_text_iter_get_offset(&iter); // graphic position in widget text
3596
if (strEqu(widgetname,"filetags")) ptext = tags_filetags; // get corresponding text
3597
if (strEqu(widgetname,"asstags")) ptext = tags_asstags;
3598
if (strEqu(widgetname,"recentags")) ptext = tags_recentags;
3599
if (strEqu(widgetname,"asstags2")) ptext = tags_asstags;
3600
if (! ptext) return;
3602
pp1 = ptext + utf8_position(ptext,offset); // graphic position to byte position
3603
if (! *pp1 || *pp1 == ' ') return; // reject ambiguity
3604
while (pp1 > ptext && *pp1 != ' ') pp1--; // find preceeding delimiter
3605
if (*pp1 == ' ') pp1++;
3607
pp2 = strchr(pp1,' '); // find delimiter following
3608
if (pp2) cc = pp2 - pp1;
3609
else cc = strlen(pp1);
3610
if (cc >= maxtag1) return; // reject tag too big
3611
strncpy0(tags_atag,pp1,cc+1); // tags_atag = selected tag
3613
if (strEqu(widgetname,"filetags")) {
3614
delete_filetag(); // remove tag from file tags
3615
zdialog_stuff(zdtags,"filetags",tags_filetags); // update dialog widgets
3618
if (strEqu(widgetname,"asstags")) {
3619
add_new_filetag(); // add assigned tag to file tags
3621
zdialog_stuff(zdtags,"filetags",tags_filetags);
3624
if (strEqu(widgetname,"recentags")) {
3625
add_new_filetag(); // add recent tag to file tags
3626
zdialog_stuff(zdtags,"filetags",tags_filetags);
3629
if (strEqu(widgetname,"asstags2")) { // search dialog:
3630
zdialog_fetch(zdtags,"searchtags",tags_searchtags,maxtag4); // add assigned tag to search tags
3631
strncatv(tags_searchtags,maxtag4," ",tags_atag,0);
3632
zdialog_stuff(zdtags,"searchtags",tags_searchtags);
3639
// edit tags dialog event and completion functions
3641
int edit_tags_dialog_event(zdialog *zd, const char *event)
3645
if (strEqu(event,"imdate")) { // image date revised
3646
err = zdialog_fetch(zd,"imdate",tags_imdate,11);
3651
if (strEqu(event,"limdate")) { // repeat last date used v.8.1
3652
if (*tags_limdate) {
3653
zdialog_stuff(zd,"imdate",tags_limdate);
3654
strcpy(tags_imdate,tags_limdate);
3659
if (strnEqu(event,"pstars",6)) { // stars revised
3660
tags_stars = event[6] - '0';
3664
if (strEqu(event,"addtag")) {
3665
err = zdialog_fetch(zd,"atag",tags_atag,maxtag1); // add new tag to file
3666
if (err) return 1; // reject too big tag
3669
zdialog_stuff(zd,"filetags",tags_filetags); // update dialog widgets
3670
zdialog_stuff(zd,"asstags",tags_asstags);
3671
zdialog_stuff(zd,"atag","");
3678
int edit_tags_dialog_compl(zdialog *zd, int zstat)
3680
if (zstat == 1) update_filetags(image_file); // done, update file EXIF tags
3681
zdialog_free(zdtags); // kill dialog
3687
// add input tag to output tag list if not already there and enough room
3688
// returns: 0 = added OK 1 = not unique (case ignored)
3689
// 2 = overflow 3 = bad utf8 characters
3691
int add_unique_tag(const char *tag, char *taglist, int maxcc)
3693
char *pp1, *pp2, *ppv, temptag1[maxtag1], temptag2[maxtag1];
3696
strncpy0(temptag1,tag,maxtag1); // remove leading and trailing blanks
3697
atcc = strTrim2(temptag2,temptag1);
3698
if (! atcc) return 0;
3700
for (ppv = temptag2; *ppv; ppv++) // replace imbedded blanks with '_'
3701
if (*ppv == ' ') *ppv = '_';
3703
if (utf8_check(temptag2)) { // check for valid utf8 encoding
3704
printf("bad utf8 characters: %s \n",temptag2);
3709
cc1 = strlen(temptag2);
3711
while (true) // check if already in tag list
3713
while (*pp1 == ' ') pp1++;
3716
while (*pp2 && *pp2 != ' ') pp2++;
3718
if (cc2 == cc1 && strncaseEqu(temptag2,pp1,cc1)) return 1;
3722
cc2 = strlen(taglist); // append to tag list if space enough
3723
if (cc1 + cc2 + 1 >= maxcc) return 2;
3724
strcpy(taglist + cc2,temptag2);
3725
strcpy(taglist + cc2 + cc1," ");
3730
/**************************************************************************/
3732
// image file EXIF data >> tags_imdate, tags_stars, tags_filetags in memory
3734
void load_filetags(const char *file)
3736
int ii, jj, cc, err;
3738
const char *exifkeys[2] = { exif_date_key, exif_tags_key };
3739
char **ppv, *imagedate, *imagetags;
3741
*tags_filetags = *tags_imdate = 0;
3744
ppv = exif_get(image_file,exifkeys,2);
3749
if (strlen(imagedate) > 9) {
3750
strncpy(tags_imdate,imagedate,4); // reformat yyyy:mm:dd
3751
strncpy(tags_imdate+4,imagedate+5,2); // to yyyymmdd
3752
strncpy(tags_imdate+6,imagedate+8,2);
3754
strcpy(tags_limdate,tags_imdate);
3761
for (ii = 1; ; ii++)
3763
pp = strField(imagetags,' ',ii); // assume blank delimited tags
3766
if (cc >= maxtag1) continue; // reject tags too big
3767
for (jj = 0; jj < cc; jj++)
3768
if (pp[jj] > 0 && pp[jj] < ' ') break; // reject tags with control characters
3769
if (jj < cc) continue;
3771
if (strnEqu(pp,"stars=",6)) { // "stars=N" tag
3772
err = convSI(pp+6,tags_stars,0,5);
3773
if (err > 1) tags_stars = 0;
3777
strcpy(tags_atag,pp); // add to file tags if unique
3788
/**************************************************************************/
3790
// tags_imdate, tags_stars, tags_filetags in memory >> image file EXIF data
3792
void update_filetags(const char *file)
3794
const char *exifkeys[2] = { exif_date_key, exif_tags_key };
3795
const char *exifdata[2];
3798
if (! tags_changed) return;
3800
*imagedate = 0; // v.7.5
3803
if (strlen(tags_imdate) == 4) strcat(tags_imdate,"0101"); // allow short dates v.8.1
3804
if (strlen(tags_imdate) == 6) strcat(tags_imdate,"01");
3805
strncpy(imagedate,tags_imdate,4); // yyyymmdd >> yyyy:mm:dd
3806
strncpy(imagedate+5,tags_imdate+4,2);
3807
strncpy(imagedate+8,tags_imdate+6,2);
3808
imagedate[4] = imagedate[7] = ':';
3810
strcpy(tags_limdate,tags_imdate);
3813
if (tags_stars > 0) { // add "stars=N" tag
3814
sprintf(tags_atag,"stars=%d",tags_stars);
3818
exifdata[0] = imagedate; // update file EXIF data
3819
exifdata[1] = tags_filetags;
3820
exif_set(file,exifkeys,exifdata,2);
3822
update_asstags(file); // update assigned tags file
3828
// add new tag to file tags, if not already and enough space.
3830
void add_new_filetag()
3834
err = add_unique_tag(tags_atag,tags_filetags,maxtag2);
3836
zmessageACK(ZTX("File tags exceed %d characters"),maxtag2);
3845
// add new tag to recent tags, if not already.
3846
// remove oldest to make space if needed.
3848
void add_new_recentag()
3851
char *ppv, temp_recentags[maxtag5];
3853
if (strnEqu(tags_atag,"stars=",6)) return; // omit this tag
3855
err = add_unique_tag(tags_atag,tags_recentags,maxtag5); // add tag to recent tags
3859
strncpy0(temp_recentags,tags_recentags,maxtag5); // remove oldest to make room
3860
ppv = temp_recentags;
3861
while (*ppv && *ppv == ' ') ppv++;
3862
while (*ppv && *ppv != ' ') ppv++;
3863
while (*ppv && *ppv == ' ') ppv++;
3864
strcpy(tags_recentags,ppv);
3865
err = add_unique_tag(tags_atag,tags_recentags,maxtag5);
3868
zdialog_stuff(zdtags,"recentags",tags_recentags); // update dialog
3873
// delete a tag from file tags, if present
3875
void delete_filetag()
3878
char temp_filetags[maxtag2];
3881
strncpy0(temp_filetags,tags_filetags,maxtag2);
3885
for (ii = 1; ; ii++)
3887
pp = strField(temp_filetags,' ',ii);
3889
if (strcaseEqu(pp,tags_atag)) continue;
3891
strcpy(tags_filetags + ftcc, pp);
3893
tags_filetags[ftcc] = ' ';
3895
tags_filetags[ftcc] = 0;
3903
/**************************************************************************/
3905
// load assigned tags file >> tags_asstags in memory
3906
// create list of all assigned tags with no duplicates
3911
int ntags = 0, ntcc, atcc, ii, err;
3912
char *ppv, buff[1000];
3914
char *tags[maxntags];
3919
fid = fopen(asstagsfile,"r");
3920
if (! fid) return; // no tags
3922
while (true) // read assigned tags file
3924
ppv = fgets_trim(buff,999,fid);
3926
if (strnNeq(buff,"tags: ",6)) continue;
3928
for (ii = 1; ; ii++) // add file tags to assigned tags
3929
{ // unless already present
3930
pp1 = strField(buff+6,' ',ii);
3932
if (strnEqu(pp1,"stars=",6)) continue; // omit this tag
3933
err = add_unique_tag(pp1,tags_asstags,maxtag3);
3934
if (err == 2) goto overflow;
3939
if (err) goto tagsfileerr;
3941
for (ii = 1; ; ii++) // build sort list
3943
pp1 = strField(tags_asstags,' ',ii);
3945
tags[ntags] = strdupz(pp1);
3947
if (ntags == maxntags) goto toomanytags;
3950
HeapSort(tags,ntags); // sort alphabetically
3955
for (ii = 0; ii < ntags; ii++) // build sorted assigned tags list
3957
atcc = strlen(tags[ii]);
3958
if (ntcc + atcc + 1 > maxtag3) goto overflow;
3959
strcpy(tags_asstags + ntcc,tags[ii]);
3961
tags_asstags[ntcc] = ' ';
3966
tags_asstags[ntcc] = 0;
3971
zmessageACK(ZTX("Total tags exceed %d characters"),maxtag3);
3975
zmessageACK(ZTX("Too many tags: %d"),maxntags);
3979
zmessLogACK(ZTX("Assigned tags file error: %s"),strerror(errno));
3984
/**************************************************************************/
3986
// update tags_asstags in memory from tags_filetags
3987
// update assigned tags file (add or replace changed file and its tags)
3989
void update_asstags(const char *file, int del)
3991
char *ppv, temp_asstagsfile[1000], imagedate[12], filedate[16];
3992
char datebuff[1000], tagsbuff[1000], filebuff[1000];
3999
ntcc = strlen(tags_asstags);
4001
if (! del) // unless deleted
4003
for (ii = 1; ; ii++) // add file tags to assigned tags
4004
{ // unless already present
4005
pp1 = strField(tags_filetags,' ',ii);
4007
if (strnEqu(pp1,"stars=",6)) continue; // omit this tag
4009
err = add_unique_tag(pp1,tags_asstags,maxtag3);
4011
zmessageACK(ZTX("Total tags exceed %d characters"),maxtag3);
4017
strcpy(temp_asstagsfile,asstagsfile); // temp tag file
4018
strcat(temp_asstagsfile,"_temp");
4020
fidr = fopen(asstagsfile,"r"); // read tag file
4022
fidw = fopen(temp_asstagsfile,"w"); // write temp tag file
4023
if (! fidw) goto tagserror;
4026
while (true) // copy assigned tags file to temp
4027
{ // file, omitting this image file
4028
ppv = fgets_trim(datebuff,999,fidr);
4030
if (strnNeq(datebuff,"date: ",6)) continue;
4032
ppv = fgets_trim(tagsbuff,999,fidr);
4034
if (strnNeq(tagsbuff,"tags: ",6)) continue;
4036
ppv = fgets_trim(filebuff,999,fidr);
4038
if (strnNeq(filebuff,"file: ",6)) continue;
4040
if (strEqu(filebuff+6,file)) continue; // if my file, skip copy
4042
fprintf(fidw,"%s\n",datebuff); // copy to temp file
4043
fprintf(fidw,"%s\n",tagsbuff);
4044
fprintf(fidw,"%s\n\n",filebuff);
4048
if (! del) // unless deleted, append
4049
{ // revised file data to temp file
4051
strncpy(imagedate,tags_imdate,4);
4052
strncpy(imagedate+5,tags_imdate+4,2); // tag date = yyyy:mm:dd
4053
strncpy(imagedate+8,tags_imdate+6,2);
4054
imagedate[4] = imagedate[7] = ':';
4057
else strcpy(imagedate,"null");
4059
err = stat(file,&statb);
4060
gmtime_r(&statb.st_mtime,&bdt);
4061
sprintf(filedate,"%04d%02d%02d%02d%02d%02d", // file date = yyyymmddhhmmss v.8.4
4062
bdt.tm_year + 1900, bdt.tm_mon + 1, bdt.tm_mday,
4063
bdt.tm_hour, bdt.tm_min, bdt.tm_sec);
4065
err = fprintf(fidw,"date: %s %s\n",imagedate,filedate); // output tag date and file date
4067
if (*tags_filetags) err = fprintf(fidw,"tags: %s\n",tags_filetags); // output image tags
4068
else err = fprintf(fidw,"tags: null\n"); // "null" if none v.7.5
4070
err = fprintf(fidw,"file: %s\n\n",file); // output filespec
4071
if (err <= 0) goto tagserror;
4076
if (err) goto tagserror;
4080
if (err) goto tagserror;
4082
err = rename(temp_asstagsfile,asstagsfile); // replace tag file with temp file
4083
if (err) goto tagserror;
4088
zmessLogACK(ZTX("Assigned tags file error: %s"),strerror(errno));
4093
/**************************************************************************/
4095
// search image tags for matching images
4097
char searchDateFrom[12] = ""; // image search date range
4098
char searchDateTo[12] = "";
4099
int searchStarsFrom = 0; // image search stars range
4100
int searchStarsTo = 0;
4102
void m_search_tags(GtkWidget *, const char *)
4104
int search_tags_dialog_event(zdialog*, const char *event);
4105
int search_tags_dialog_compl(zdialog*, int zstat);
4108
zdialog_free(zdtags);
4112
zdtags = zdialog_new(ZTX("Search Tags"),mWin,Bsearch,Bcancel,0);
4114
zdialog_add_widget(zdtags,"hbox","hb1","dialog",0,"space=2");
4115
zdialog_add_widget(zdtags,"vbox","vb1","hb1",0,"homog|space=5");
4116
zdialog_add_widget(zdtags,"vbox","vb2","hb1",0,"homog|space=2");
4118
zdialog_add_widget(zdtags,"label","labDR","vb1",ZTX("date range"));
4119
zdialog_add_widget(zdtags,"label","labSR","vb1",ZTX("stars range"));
4120
zdialog_add_widget(zdtags,"label","labF","vb1",ZTX("/path*/file*"));
4121
zdialog_add_widget(zdtags,"label","labT","vb1",ZTX("search tags"));
4123
zdialog_add_widget(zdtags,"hbox","hbDR","vb2",0,"space=5"); // date range yyyymmdd yyyymmdd
4124
zdialog_add_widget(zdtags,"entry","datefrom","hbDR",0,"scc=10"); // stars range 4 5
4125
zdialog_add_widget(zdtags,"entry","dateto","hbDR",0,"scc=10"); // /path*/file* /dname/dname*/fname*
4126
zdialog_add_widget(zdtags,"label","labDF","hbDR","(yyyymmdd)"); // search tags rosi alaska
4128
zdialog_add_widget(zdtags,"hbox","hbSR","vb2",0); // (o) match all tags (o) match any
4129
zdialog_add_widget(zdtags,"entry","starsfrom","hbSR",0,"scc=2");
4130
zdialog_add_widget(zdtags,"entry","starsto","hbSR",0,"scc=2"); // assigned tags
4132
zdialog_add_widget(zdtags,"hbox","hbF","vb2",0,"space=5"); // list of all tags in a box
4133
zdialog_add_widget(zdtags,"entry","searchfile","hbF",0,"expand");
4134
zdialog_add_widget(zdtags,"button","fileclear","hbF",Bclear);
4136
zdialog_add_widget(zdtags,"hbox","hbT","vb2",0,"space=5");
4137
zdialog_add_widget(zdtags,"entry","searchtags","hbT",0,"expand");
4138
zdialog_add_widget(zdtags,"button","tagsclear","hbT",Bclear);
4140
zdialog_add_widget(zdtags,"hbox","hbM","dialog");
4141
zdialog_add_widget(zdtags,"radio","rmall","hbM",ZTX("match all tags"),"space=5");
4142
zdialog_add_widget(zdtags,"label","lspace","hbM","","space=10");
4143
zdialog_add_widget(zdtags,"radio","rmany","hbM",ZTX("match any tag"));
4145
zdialog_add_widget(zdtags,"hbox","hbsp","dialog",0,"space=1");
4147
zdialog_add_widget(zdtags,"hbox","hbAT","dialog");
4148
zdialog_add_widget(zdtags,"label","labasstags","hbAT",ZTX("assigned tags"));
4149
zdialog_add_widget(zdtags,"frame","frameAT","dialog",0,"expand");
4150
zdialog_add_widget(zdtags,"edit","asstags2","frameAT",0,"expand");
4152
zdialog_resize(zdtags,400,0); // start dialog
4153
zdialog_run(zdtags,search_tags_dialog_event,search_tags_dialog_compl);
4155
edit_tags_fixup("asstags2"); // setup tag selection via mouse
4157
zdialog_stuff(zdtags,"datefrom",searchDateFrom); // stuff previous date range
4158
zdialog_stuff(zdtags,"dateto",searchDateTo);
4159
if (strNeq(tags_searchtags,"null")) // stuff previous search tags
4160
zdialog_stuff(zdtags,"searchtags",tags_searchtags);
4161
zdialog_stuff(zdtags,"rmall",1); // default is match all tags
4162
zdialog_stuff(zdtags,"rmany",0);
4163
load_asstags(); // stuff assigned tags
4164
zdialog_stuff(zdtags,"asstags2",tags_asstags);
4170
int search_tags_dialog_event(zdialog *zd, const char *event) // dialog event function
4172
if (strEqu(event,"fileclear"))
4173
zdialog_stuff(zd,"searchfile","");
4175
if (strEqu(event,"tagsclear"))
4176
zdialog_stuff(zd,"searchtags","");
4182
int search_tags_dialog_compl(zdialog *zd, int zstat) // dialog completion function
4184
const char *pps, *ppf;
4185
char resultsfile[200];
4186
char *ppv, *file, *tags, rbuff[1000];
4187
char date1[12], date2[12], lcfile[1000];
4188
int err, nfiles, iis, iif, stars, nmatch, nfail;
4189
int date1cc, date2cc, Fmall, Fdates, Ffiles, Ftags, Fstars;
4191
struct stat statbuf;
4194
zdialog_fetch(zd,"datefrom",searchDateFrom,10); // get search date range
4195
zdialog_fetch(zd,"dateto",searchDateTo,10);
4196
zdialog_fetch(zd,"starsfrom",searchStarsFrom); // get search stars range
4197
zdialog_fetch(zd,"starsto",searchStarsTo);
4198
zdialog_fetch(zd,"searchfile",tags_searchfile,maxtagF); // get search /path*/file* v.6.6
4199
zdialog_fetch(zd,"searchtags",tags_searchtags,maxtag4); // get search tags
4200
zdialog_fetch(zd,"rmall",Fmall); // get match all/any option
4203
zdialog_free(zdtags); // kill dialog
4205
if (zstat != 1) return 0; // cancelled
4207
strcpy(date1,"0000"); // defaults for missing dates v.6.6
4208
strcpy(date2,"9999"); // (year 0000 to year 9999)
4209
date1cc = date2cc = 4;
4212
if (*searchDateFrom) { // date from is given
4214
strncpy(date1,searchDateFrom,4); // convert format
4215
strncpy(date1+5,searchDateFrom+4,2); // yyyymmdd >> yyyy:mm:dd
4216
strncpy(date1+8,searchDateFrom+6,2);
4217
date1[4] = date1[7] = ':';
4218
date1cc = strlen(date1);
4220
if (*searchDateTo) { // date to is given
4222
strncpy(date2,searchDateTo,4);
4223
strncpy(date2+5,searchDateTo+4,2);
4224
strncpy(date2+8,searchDateTo+6,2);
4225
date2[4] = date2[7] = ':';
4226
date2cc = strlen(date2);
4230
if (searchStarsFrom || searchStarsTo) Fstars = 1; // stars given
4233
if (! blank_null(tags_searchfile)) Ffiles = 1; // search file* given v.6.6
4236
if (! blank_null(tags_searchtags)) Ftags = 1; // search tags given
4238
if (! Ffiles && ! Ftags && ! Fdates && ! Fstars) { // no search criteria was given,
4239
strcpy(tags_searchtags,"null"); // find images with no tags v.6.9.3
4243
if (Ffiles) strToLower(tags_searchfile); // v.6.6
4244
if (Ftags) strToLower(tags_searchtags);
4246
snprintf(resultsfile,199,"%s/search_results",get_zuserdir());
4247
fidw = fopen(resultsfile,"w"); // search results output file
4248
if (! fidw) goto writerror;
4250
fidr = fopen(asstagsfile,"r"); // read assigned tags file
4251
if (! fidr) goto noasstags;
4253
nfiles = 0; // count matching files found
4257
ppv = fgets_trim(rbuff,999,fidr); // next assigned tags record
4259
if (! strnEqu(ppv,"date: ",6)) continue; // date: yyyy:mm:dd
4262
if (strncmp(ppv+6,date1,date1cc) < 0) continue; // check search date range
4263
if (strncmp(ppv+6,date2,date2cc) > 0) continue;
4266
ppv = fgets_trim(rbuff,999,fidr); // next record
4268
if (! strnEqu(ppv,"tags: ",6)) continue; // tags: xxxx xxxxx ...
4273
strToLower(tags); // v.6.6
4277
for (iis = 1; ; iis++)
4279
pps = strField(tags_searchtags,' ',iis); // step thru search tags
4282
for (iif = 1; ; iif++) // step thru file tags
4284
ppf = strField(tags,' ',iif);
4285
if (! ppf) { nfail++; break; } // count matches and fails
4286
if (MatchWild(pps,ppf) == 0) { nmatch++; break; } // wildcard match v.6.6
4290
if (nmatch == 0) continue; // no match to any tag
4291
if (Fmall && nfail) continue; // no match to all tags
4298
for (iif = 1; ; iif++) // step thru file tags
4300
ppf = strField(ppv+6,' ',iif);
4301
if (! ppf) { nfail++; break; }
4302
if (! strnEqu(ppf,"stars=",6)) continue;
4303
stars = atoi(ppf+6);
4304
if (stars < searchStarsFrom) nfail++;
4305
if (searchStarsTo && stars > searchStarsTo) nfail++;
4309
if (nfail) continue;
4312
ppv = fgets_trim(rbuff,999,fidr,1); // next record
4314
if (! strnEqu(ppv,"file: ",6)) continue; // file: /dirks.../file.jpg
4316
strToLower(lcfile,file); // v.6.6
4318
if (Ffiles) // test for path*/file* match
4322
for (iis = 1; ; iis++)
4324
pps = strField(tags_searchfile,' ',iis); // step thru search files
4326
if (MatchWild(pps,lcfile) == 0) { nmatch++; break; }
4329
if (nmatch == 0) continue;
4332
err=stat(file,&statbuf); // check file exists
4334
if (! S_ISREG(statbuf.st_mode)) continue;
4336
fprintf(fidw,"%s\n",file); // write matching file
4343
if (err) goto writerror;
4346
zmessageACK(ZTX("No matching images found"));
4350
image_gallery(resultsfile,"initF",0,m_gallery2); // generate gallery of matching files
4351
file = image_gallery(0,"first",0);
4352
f_open(file); // show first matching file
4353
image_gallery(0,"paint1"); // show new image gallery window
4354
Fsearchlist = 1; // restricted updates v.6.4
4359
zmessageACK(ZTX("No assigned tags index file"));
4363
zmessLogACK(ZTX("Search results file error %s"),strerror(errno));
4368
/**************************************************************************/
4370
// list available EXIF data to popup window
4372
void m_exif_list(GtkWidget *, const char *menu)
4375
const char *basic = "-common -focallengthin35mmformat";
4377
if (! image_file) return;
4379
if (! Fexiftool) { // exiftool is required
4380
zmessageACK(Bexiftoolmissing);
4384
if (strEqu(menu,ZTX("Basic EXIF data"))) { // bugfix v.7.7
4385
snprintf(command,999,"exiftool %s \"%s\" ",basic,image_file);
4386
popup_command(command,500,300);
4389
if (strEqu(menu,ZTX("All EXIF data"))) {
4390
snprintf(command,999,"exiftool -e \"%s\" ",image_file);
4391
popup_command(command,500,500);
4398
/**************************************************************************/
4400
// get EXIF metadata for given image file and EXIF key(s)
4401
// returns array of pointers to corresponding key values
4402
// if a key is missing, corresponding pointer is null
4403
// returned strings belong to caller, are subject for zfree()
4404
// no more than 9 keynames may be requested per call
4406
// exiftool -exif:keyname1 -exif:keyname2 ... "file"
4408
// keyname1: keyvalue1
4409
// keyname2: keyvalue2
4412
char ** exif_get(cchar *file, cchar **keys, int nkeys)
4414
char command[1000], *pp;
4415
static char *rettext[10];
4416
int contx = 0, err, ii;
4419
if (nkeys < 1 || nkeys > 9) appcrash("exif_get nkeys: %d",nkeys);
4421
strcpy(command,"exiftool -m -q -S -fast");
4423
for (ii = 0; ii < nkeys; ii++)
4426
strncatv(command,999," -exif:",keys[ii],null);
4429
strncatv(command,999," \"",file,"\"",null);
4433
pp = command_output(contx,command);
4436
for (ii = 0; ii < nkeys; ii++)
4438
cc = strlen(keys[ii]);
4439
if (strnEqu(pp,keys[ii],cc))
4440
if (strlen(pp) > cc+2) rettext[ii] = strdupz(pp+cc+2); // check not empty bugfix v.7.3
4446
err = command_status(contx);
4447
if (err) printf("exif_get failed \n"); // v.6.9
4453
/**************************************************************************/
4455
// create or change EXIF metadata for given image file and key(s)
4456
// up to 9 keys may be processed
4458
// exiftool -overwrite_original -exif:keyname="keyvalue" ... "file"
4460
int exif_set(cchar *file, cchar **keys, cchar **text, int nkeys)
4465
if (nkeys < 1 || nkeys > 9) appcrash("exif_set nkeys: %d",nkeys);
4467
strcpy(command,"exiftool -m -q -overwrite_original");
4469
for (ii = 0; ii < nkeys; ii++)
4470
strncatv(command,999," -exif:",keys[ii],"=\"",text[ii],"\"",null);
4471
strncatv(command,999," \"",file,"\"",null);
4473
err = system(command);
4474
if (err) printf(" exif_set: %s \n",wstrerror(err));
4479
/**************************************************************************/
4481
// copy EXIF data from original image file to new (edited) image file
4482
// if nkeys > 0, up to 9 keys may be replaced with new values
4484
// exiftool -tagsfromfile "file1" -exif:keyname="keyvalue" ... "file2"
4486
int exif_copy(cchar *file1, cchar *file2, cchar **keys, cchar **text, int nkeys)
4491
strcpy(command,"exiftool -m -q -overwrite_original -tagsfromfile");
4492
strncatv(command,1999," \"",file1,"\"",null);
4494
for (ii = 0; ii < nkeys; ii++)
4495
strncatv(command,1999," -exif:",keys[ii],"=\"",text[ii],"\"",null);
4497
strncatv(command,1999," \"",file2,"\"",null);
4499
err = system(command);
4500
if (err) printf(" exif_copy: %s \n",wstrerror(err));
4505
/**************************************************************************
4506
Select an area within the current image.
4507
Subsequent edit functions are carried out within the area.
4508
Otherwise, edit functions apply to the entire image.
4509
***************************************************************************/
4511
// data defining a selected area (either mouse or color select)
4513
int sa_type = 0; // 0=none, 1=mouse, 2=color
4514
int sa_stat = 0; // 0=none, 1=active, 2=suspend, 3=compl
4515
int sa_blend = 0; // blend width
4516
int sa_calced = 0; // edge distance calculation done
4517
char *sa_pixisin = 0; // flag pixels inside select area
4518
int sa_Npixel = 0; // count of select_area pixels
4519
struct sa_pixel1 { int16 px, py, dist; }; // maps pixels in select area
4520
sa_pixel1 *sa_pixel = 0; // and distance from edge
4523
/**************************************************************************/
4525
// outline an image area using the mouse
4526
// may run parallel with edit functions
4528
int sam_dialog_event(zdialog*, const char *event); // dialog event and completion funcs
4529
int sam_dialog_compl(zdialog*, int zstat);
4531
void sam_select_show();
4532
void sam_select_finish();
4533
int sam_select_within(int px, int py);
4534
void sam_select_edgecalc();
4535
int sam_select_edgecalc2(int px, int py, int nth);
4536
void sam_select_invert();
4538
void sam_mousefunc();
4539
void sam_nearest_node(int mx, int my, int &node, int &dist);
4540
void sam_append_node(int mx, int my, int &node);
4541
void sam_insert_node(int mx, int my, int &node);
4542
void sam_move_node(int mx, int my, int node);
4543
void sam_remove_node(int node);
4544
int sam_duplicate_node(int mx, int my);
4546
int sam_npg, sam_maxpg = 1000, sam_pgx[1000], sam_pgy[1000]; // polygon nodes
4549
void m_select_mouse(GtkWidget *, const char *) // menu function
4551
const char *title = ZTX("Image Area for Following Edits");
4552
const char *helptext = ZTX("Use mouse drag and click to enclose an area. \n"
4553
"Use right click to remove the nearest point");
4555
if (! image_file) return; // no image
4556
if (zdsela) return; // already active
4558
if (Fpreview) edit_fullsize(); // use full-size pixmaps
4560
zdsela = zdialog_new(title,mWin,BOK,Bcancel,null);
4561
zdialog_add_widget(zdsela,"label","labhelp","dialog",helptext,"space=10");
4562
zdialog_add_widget(zdsela,"hbox","hb1","dialog",0,"space=10");
4563
zdialog_add_widget(zdsela,"button","start","hb1",Bstart);
4564
zdialog_add_widget(zdsela,"button","susp-resm","hb1",Bsuspend);
4565
zdialog_add_widget(zdsela,"button","show-hide","hb1",Bhide);
4566
zdialog_add_widget(zdsela,"button","finish","hb1",Bfinish);
4567
zdialog_add_widget(zdsela,"button","delete","hb1",Bdelete);
4568
zdialog_add_widget(zdsela,"hsep","hsep1","dialog");
4569
zdialog_add_widget(zdsela,"hbox","hb3","dialog",0,"space=10");
4570
zdialog_add_widget(zdsela,"label","labblend","hb3",Bblendwidth);
4571
zdialog_add_widget(zdsela,"hscale","blendwidth","hb3","0|300|1|0","expand");
4573
zdialog_run(zdsela,sam_dialog_event,sam_dialog_compl); // run dialog - parallel
4575
if (Fimageturned) turn_image(-Fimageturned); // use native orientation
4576
if (sa_type != 1) select_disable(); // disable other type area
4577
sa_type = 1; // mouse type select area
4579
sa_calced = 0; // v.8.6.1
4580
m_select_show(0,0); // show area
4585
int sam_dialog_compl(zdialog *zd, int zstat)
4587
if (zstat != 1) m_select_delete(0,0); // kill, cancel
4588
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
4589
mouseCBfunc = 0; // disconnect mouse function
4591
zdialog_free(zdsela); // kill dialog
4597
int sam_dialog_event(zdialog *zd, const char *event)
4599
if (strEqu(event,"start")) // start or resume
4601
Fshowarea++; // show area
4602
zdialog_stuff(zdsela,"show-hide",Bhide); // set buttons
4603
zdialog_stuff(zdsela,"susp-resm",Bsuspend);
4604
gdk_window_set_cursor(drWin->window,dragcursor); // set drag cursor
4605
mouseCBfunc = sam_mousefunc; // connect mouse function
4606
Mcapture++; // mouse captured for me
4607
select_disable(); // re-edit still possible
4608
sa_stat = 1; // edit is active
4611
if (strEqu(event,"show-hide")) { // toggle show/hide
4614
zdialog_stuff(zdsela,"show-hide",Bhide);
4618
zdialog_stuff(zdsela,"show-hide",Bshow);
4622
if (strEqu(event,"susp-resm")) { // toggle suspend/resume
4624
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
4625
mouseCBfunc = 0; // disconnect mouse function
4627
zdialog_stuff(zdsela,"susp-resm",Bresume);
4630
else if (sa_stat == 2) {
4631
gdk_window_set_cursor(drWin->window,dragcursor); // set drag cursor
4632
mouseCBfunc = sam_mousefunc; // connect mouse function
4634
zdialog_stuff(zdsela,"susp-resm",Bsuspend);
4639
if (strEqu(event,"finish")) // finish area
4641
mouseCBfunc = 0; // disconnect mouse function
4643
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
4645
zdialog_stuff(zdsela,"show-hide",Bhide);
4646
sam_select_finish();
4649
if (strEqu(event,"delete")) { // delete area
4650
mouseCBfunc = 0; // disconnect mouse function
4652
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
4656
if (strEqu(event,"blendwidth")) { // blend width changed
4657
if (sa_Npixel && zdedit) {
4658
if (! sa_calced) zmessageACK(ZTX("edge calculation needed")); // v.8.6.1
4660
zdialog_fetch(zd,"blendwidth",sa_blend); // update sa_blend
4661
sa_blend = (sa_blend * sa_blend + 15) / 300; // 0-300 scaled v.8.5
4662
zdialog_send_event(zdedit,event); // notify active edit dialog
4667
mwpaint2(); // update window
4672
// show select area polygon outline
4674
void sam_select_show()
4678
for (ii = 0; ii < sam_npg-1; ii++)
4681
draw_line(sam_pgx[ii],sam_pgy[ii],sam_pgx[jj],sam_pgy[jj]);
4688
// finish select area - map pixels enclosed by polygon into sa_area[]
4690
void sam_select_finish()
4692
int ii, inside, px, py, cc;
4693
int minpgx, minpgy, maxpgx, maxpgy;
4694
int Nrect, Ndone, npix;
4696
if (sa_Npixel) return; // already finished
4697
if (sam_npg < 3) return; // not enough points
4699
gdk_window_set_cursor(drWin->window,busycursor); // set function busy cursor v.8.4
4703
if (sam_pgx[ii] != sam_pgx[0] || sam_pgy[ii] != sam_pgy[0]) { // if not already
4704
ii++; // make closing connection
4705
sam_pgx[ii] = sam_pgx[0]; // last point = 1st point
4706
sam_pgy[ii] = sam_pgy[0];
4710
minpgx = maxpgx = sam_pgx[0];
4711
minpgy = maxpgy = sam_pgy[0];
4713
for (ii = 0; ii < sam_npg; ii++) // get rectangle enclosing polygon
4715
if (sam_pgx[ii] < minpgx) minpgx = sam_pgx[ii];
4716
if (sam_pgx[ii] > maxpgx) maxpgx = sam_pgx[ii];
4717
if (sam_pgy[ii] < minpgy) minpgy = sam_pgy[ii];
4718
if (sam_pgy[ii] > maxpgy) maxpgy = sam_pgy[ii];
4721
if (sa_pixel) zfree(sa_pixel);
4722
Nrect = (maxpgx - minpgx) * (maxpgy - minpgy); // allocate enough memory for
4723
cc = Nrect * sizeof(sa_pixel1); // all pixels in rectangle
4724
sa_pixel = (sa_pixel1 *) zmalloc(cc);
4726
if (sa_pixisin) zfree(sa_pixisin);
4727
cc = Fww * Fhh; // set up to map all pixels
4728
sa_pixisin = zmalloc(cc); // inside select area
4729
memset(sa_pixisin,0,cc);
4733
for (py = minpgy; py < maxpgy; py++) // loop all pixels in rectangle
4734
for (px = minpgx; px < maxpgx; px++)
4736
inside = sam_select_within(px,py); // check if pixel inside polygon
4737
if (! inside) continue;
4738
ii = py * Fww + px; // mark pixel inside area v.6.3
4740
sa_pixel[npix].px = px;
4741
sa_pixel[npix].py = py; // save pixel
4742
sa_pixel[npix].dist = 0;
4746
sa_Npixel = npix; // no. pixels in area
4747
sa_stat = 3; // area select finished
4748
sa_calced = 0; // no edge calculation v.8.6.1
4750
mutex_unlock(&pixmaps_lock);
4751
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
4756
// determine if pixel at (px,py) is inside the area selection polygon
4758
int sam_select_within(int px, int py)
4760
int sam_select_within2(int px, int py, int nth);
4764
for (ii = 0; ii < sam_npg-1; ii++) // count intersections of line up
4765
if (sam_select_within2(px,py,ii) == 2) // from (px,py) with a polygon face
4766
inside = 1 - inside;
4772
int sam_select_within2(int px, int py, int nth) // determine if point (px,py) is
4773
{ // above or below nth polygon face
4777
x1 = sam_pgx[nth]; // line segment of nth face
4779
x2 = sam_pgx[nth+1];
4780
y2 = sam_pgy[nth+1];
4782
if (x1 == x2) return 0; // line is vertical
4785
if (px <= x1) return 0; // point is left or right of line
4786
if (px > x2) return 0;
4789
if (px <= x2) return 0;
4790
if (px > x1) return 0;
4793
if (py <= y1 && py <= y2) return 1; // point is above or below both ends
4794
if (py >= y1 && py >= y2) return 2;
4796
yp = (px*(y2-y1) + (x2*y1-x1*y2))/(1.0*(x2-x1)); // intersect of vertical line x = px
4797
if (py < yp) return 1; // above
4798
if (py > yp) return 2; // below
4799
return 0; // dead on
4803
// calculate distance of all pixels in area to nearest edge
4805
void sam_select_edgecalc()
4807
int ii, kk, px, py, dd, mindd;
4809
sam_select_finish(); // finish if needed
4810
if (! sa_Npixel) return;
4812
for (ii = 0; ii < sa_Npixel; ii++)
4814
edit_progress(ii,sa_Npixel); // track progress
4816
px = sa_pixel[ii].px;
4817
py = sa_pixel[ii].py;
4818
mindd = sam_select_edgecalc2(px,py,0);
4820
for (kk = 1; kk < sam_npg-1; kk++)
4822
dd = sam_select_edgecalc2(px,py,kk); // distance^2 to Nth polygon face
4823
if (dd < mindd) mindd = dd; // remember smallest distance^2
4826
sa_pixel[ii].dist = int(sqrt(mindd)); // use smallest distance
4830
sa_calced = 1; // v.8.6.1
4835
int sam_select_edgecalc2(int px, int py, int nth) // get distance^2 from point (px,py)
4836
{ // to nth polygon face
4839
int h1, h2, ls, temp;
4841
int huge = 1000000; // distance = 1000
4843
x1 = sam_pgx[nth]; // line segment of nth face
4845
x2 = sam_pgx[nth+1];
4846
y2 = sam_pgy[nth+1];
4848
if (x1 < 5 && x2 < 5) return huge;
4849
if (y1 < 5 && y2 < 5) return huge; // if line segment hugs image edge,
4850
if (Fww - x1 < 5 && Fww - x2 < 5) return huge; // then it is logically distant
4851
if (Fhh - y1 < 5 && Fhh - y2 < 5) return huge;
4853
a = y1-y2; // get min. distance ^2 from point
4854
b = x2-x1; // to entire line of line segment
4855
c = x1*y2 - y1*x2; // (intersect may be outside segment)
4856
d = (a*px + b*py + c);
4857
dist2 = int(1.0 * d*d / (a*a + b*b)); // (can overflow 32 bits)
4859
h1 = (x1-px)*(x1-px) + (y1-py)*(y1-py); // point to (x1,y1) ^2
4860
h2 = (x2-px)*(x2-px) + (y2-py)*(y2-py); // point to (x2,y2) ^2
4861
ls = (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2); // segment length ^2
4862
if (h1 > h2) { temp = h2; h2 = h1; h1 = temp; } // re-order, h1 <= h2
4864
if (h1 < 0 || dist2 < 0) {
4865
printf("px py nth: %d %d %d \n",px,py,nth); // algebra failure ***
4866
printf("x1 y1 x2 y2: %d %d %d %d \n",x1,y1,x2,y2);
4867
printf("h1 dist2: %d %d \n",h1,dist2);
4868
printf("sam_edgecalc2() failure \n");
4872
if (h2 - dist2 < ls) return dist2; // intersect in segment, use dist2
4873
else return h1; // distance ^2 to nearest end point
4877
// invert selected area
4879
void sam_select_invert() // v.8.5
4881
int ii, kk, npix, cc, px, py;
4883
sam_select_finish(); // finish if needed
4884
if (! sa_Npixel) return;
4886
zfree(sa_pixel); // free old select area
4887
npix = Fww * Fhh - sa_Npixel;
4888
cc = npix * sizeof(sa_pixel1); // allocate new select area v.8.5.1
4889
sa_pixel = (sa_pixel1 *) zmalloc(cc);
4891
for (ii = kk = 0; ii < Fww * Fhh; ii++) // reverse member pixels
4893
if (sa_pixisin[ii]) sa_pixisin[ii] = 0;
4898
sa_pixel[kk].px = px;
4899
sa_pixel[kk].py = py;
4900
sa_pixel[kk].dist = 0;
4905
if (npix != kk) zappcrash("invert area bug: %d %d",kk,npix);
4908
sa_calced = 0; // no edge calculation v.8.6.1
4913
// select area mouse function - add polygon points, draw lines
4914
// overhauled v.8.3 - add or move points anywhere around the polygon
4916
void sam_mousefunc()
4918
static int node, dist, newdrag;
4919
static int mdx0, mdy0;
4920
int thresh = 3.0 / Mscale; // capture node mouse threshold
4922
if (LMclick) // left mouse click
4924
newdrag = LMclick = node = 0;
4925
if (sam_duplicate_node(Mxclick,Myclick)) return;
4926
sam_append_node(Mxclick,Myclick,node); // add new node at end
4929
else if (RMclick) // right mouse click
4931
newdrag = RMclick = node = 0;
4932
if (sam_npg == 0) return;
4933
sam_nearest_node(Mxclick,Myclick,node,dist); // find nearest node
4934
sam_remove_node(node); // remove node
4937
else if (Mxdrag || Mydrag) // mouse drag underway
4939
if (Mxdown != mdx0 || Mydown != mdy0) { // new drag start position?
4940
newdrag = 1; // yes, new drag start
4941
mdx0 = Mxdown; // save start position
4945
if (Mxdrag == mdx0 && Mydrag == mdy0) return; // no progress
4946
if (sam_duplicate_node(Mxdrag,Mydrag)) return; // defer recognition
4948
if (newdrag) { // new drag start
4950
if (sam_npg < 2) sam_append_node(mdx0,mdy0,node); // < 2 nodes, add next node
4952
sam_nearest_node(mdx0,mdy0,node,dist); // nearest node and distance
4953
if (dist < thresh) sam_move_node(Mxdrag,Mydrag,node); // if close, drag node
4954
else sam_insert_node(Mxdrag,Mydrag,node); // else add new node
4957
else { // continuation of drag
4958
if (sam_npg < 2) sam_append_node(Mxdrag,Mydrag,node); // only 1 node, add 2nd node
4959
else sam_move_node(Mxdrag,Mydrag,node); // else move the drag node
4967
// find nearest node to mouse position, return node and distance
4969
void sam_nearest_node(int mx, int my, int &node, int &dist)
4971
int ii, dx, dy, tdist;
4972
int mindist = 99999999, minii = -1;
4974
if (sam_npg == 0) zappcrash("sam_nearest_node() no nodes");
4976
for (ii = 0; ii < sam_npg; ii++)
4978
dx = sam_pgx[ii] - mx;
4979
dy = sam_pgy[ii] - my;
4980
tdist = dx*dx + dy*dy;
4981
if (tdist < mindist) {
4988
dist = int(sqrt(mindist) + 0.5);
4993
// append new last node
4995
void sam_append_node(int mx, int my, int &node)
4999
if (sam_npg > sam_maxpg-2) {
5000
zmessageACK(ZTX("Too many points"));
5005
if (sam_npg == 0) { // new node is 1st node
5013
sam_npg++; // add new last node
5018
draw_line(sam_pgx[jj],sam_pgy[jj],sam_pgx[ii],sam_pgy[ii]);
5024
// insert new node at mouse position, return node number
5025
// find closest polygon face
5026
// insert node there if distance < threshold
5027
// if distance > threshold, append new last node
5029
void sam_insert_node(int mx, int my, int &node)
5031
int ii, jj, kk, dist, mindist = 999999999;
5032
int thresh = 5.0 / Mscale; // insert new node mouse threshold
5034
if (sam_npg < 2) zappcrash("sam_insert_node() < 2 nodes");
5036
if (sam_npg > sam_maxpg-2) {
5037
zmessageACK(ZTX("Too many points"));
5041
for (ii = 0; ii < sam_npg-1; ii++)
5043
dist = sam_select_edgecalc2(mx,my,ii); // distance^2 to polygon face
5044
if (dist < mindist) {
5045
mindist = dist; // remember closest face
5046
node = ii; // 1st node of face
5050
node += 1; // new node number
5052
mindist = int(sqrt(mindist) + 0.5);
5053
if (mindist > thresh) { // too far
5054
sam_append_node(mx,my,node); // append new last node
5058
for (ii = sam_npg; ii > node; ii--) // make hole for inserted node
5060
sam_pgx[ii] = sam_pgx[ii-1];
5061
sam_pgy[ii] = sam_pgy[ii-1];
5064
sam_pgx[node] = mx; // insert node
5068
ii = node - 1; // prior node
5070
kk = node + 1; // next node
5072
erase_line(sam_pgx[ii],sam_pgy[ii],sam_pgx[kk],sam_pgy[kk]); // erase line prior to next
5073
draw_line(sam_pgx[ii],sam_pgy[ii],sam_pgx[jj],sam_pgy[jj]); // draw line prior to node
5074
draw_line(sam_pgx[jj],sam_pgy[jj],sam_pgx[kk],sam_pgy[kk]); // draw line node to next
5080
// move given node to mouse position
5082
void sam_move_node(int mx, int my, int node)
5090
if (sam_npg < 2) { // only one node exists
5096
if (node == 0) { // move node 0
5097
erase_line(sam_pgx[jj],sam_pgy[jj],sam_pgx[kk],sam_pgy[kk]);
5100
draw_line(sam_pgx[jj],sam_pgy[jj],sam_pgx[kk],sam_pgy[kk]);
5104
if (node == sam_npg-1) { // move last node
5105
erase_line(sam_pgx[ii],sam_pgy[ii],sam_pgx[jj],sam_pgy[jj]);
5108
draw_line(sam_pgx[ii],sam_pgy[ii],sam_pgx[jj],sam_pgy[jj]);
5112
erase_line(sam_pgx[ii],sam_pgy[ii],sam_pgx[jj],sam_pgy[jj]); // move intermediate node
5113
erase_line(sam_pgx[jj],sam_pgy[jj],sam_pgx[kk],sam_pgy[kk]);
5116
draw_line(sam_pgx[ii],sam_pgy[ii],sam_pgx[jj],sam_pgy[jj]);
5117
draw_line(sam_pgx[jj],sam_pgy[jj],sam_pgx[kk],sam_pgy[kk]);
5122
// remove given node
5124
void sam_remove_node(int node)
5128
if (node > sam_npg-1) zappcrash("sam_remove_node() invalid node");
5134
if (ii >= 0) // erase line from predecessor
5135
erase_line(sam_pgx[ii],sam_pgy[ii],sam_pgx[jj],sam_pgy[jj]);
5137
if (kk < sam_npg) // erase line to successor
5138
erase_line(sam_pgx[jj],sam_pgy[jj],sam_pgx[kk],sam_pgy[kk]);
5140
for (int ii = node; ii < sam_npg-1; ii++) // remove node
5142
sam_pgx[ii] = sam_pgx[ii+1];
5143
sam_pgy[ii] = sam_pgy[ii+1];
5146
if (sam_npg == 1) sam_npg = 0; // if one left, remove it
5150
if (ii >= 0 && jj < sam_npg) // draw new line from
5151
draw_line(sam_pgx[ii],sam_pgy[ii],sam_pgx[jj],sam_pgy[jj]); // predecessor to successor
5157
// detect if proposed node is a duplicate of an existing node
5159
int sam_duplicate_node(int mx, int my)
5161
for (int ii = 0; ii < sam_npg; ii++)
5162
if (mx == sam_pgx[ii] && my == sam_pgy[ii]) return 1;
5168
/**************************************************************************/
5170
// select an image area by clicking and mapping colors // new v.8.5
5171
// may run parallel with edit functions
5173
int sac_dialog_event(zdialog*, const char *event); // dialog event and completion funcs
5174
int sac_dialog_compl(zdialog*, int zstat);
5176
void sac_select_show();
5177
void sac_select_finish();
5178
void sac_select_edgecalc();
5179
void sac_select_invert();
5181
void sac_mousefunc();
5182
void sac_select_pixels();
5183
void sac_unselect_pixels();
5185
int sac_mousex, sac_mousey; // mouse position in image
5186
uint16 *sac_targpix; // target image pixel to match
5187
int sac_targR, sac_targG, sac_targB; // target pixel RGB
5188
double sac_targmatch; // color range to match (0.001 to 1.0)
5190
int16 *sac_pixseq = 0; // map pixel to selection sequence
5191
int sac_currseq = 0; // current sequence number
5192
int sac_Ncurrseq = 0; // current sequence pixel count
5194
int sac_stackii[1000000]; // pixel search stack
5195
char sac_stackdir[1000000]; // 1M pixel limit
5196
int sac_maxstack = 1000000;
5199
int sac_seldist_busy = 0; // thread controls for
5200
int sac_seldist_kill = 0; // sac_select_edgecalc()
5201
int sac_seldist_pixdone = 0;
5204
void m_select_color(GtkWidget *, const char *) // menu function
5206
const char *title = ZTX("Image Area for Following Edits");
5207
const char *helptext = ZTX("Left click/drag: add to selected area. \n"
5208
"Right click: remove prior selection(s). \n"
5209
"Color range: add more or less at once.");
5211
if (! image_file) return; // no image
5212
if (zdsela) return; // already active
5214
if (Fpreview) edit_fullsize(); // use full-size pixmaps
5216
if (! Frgb48) { // create Frgb48 if not already
5217
mutex_lock(&pixmaps_lock);
5218
Frgb48 = image_load(image_file,48);
5219
mutex_unlock(&pixmaps_lock);
5220
if (! Frgb48) return;
5224
zdsela = zdialog_new(title,mWin,BOK,Bcancel,null);
5225
zdialog_add_widget(zdsela,"label","labhelp","dialog",helptext,"space=5");
5226
zdialog_add_widget(zdsela,"hbox","hb1","dialog",0,"space=10");
5227
zdialog_add_widget(zdsela,"button","start","hb1",Bstart);
5228
zdialog_add_widget(zdsela,"button","susp-resm","hb1",Bsuspend);
5229
zdialog_add_widget(zdsela,"button","show-hide","hb1",Bhide);
5230
zdialog_add_widget(zdsela,"button","finish","hb1",Bfinish);
5231
zdialog_add_widget(zdsela,"button","delete","hb1",Bdelete);
5232
zdialog_add_widget(zdsela,"hbox","hb3","dialog",0,"space=5");
5233
zdialog_add_widget(zdsela,"label","labcolor","hb3",ZTX("color range"));
5234
zdialog_add_widget(zdsela,"hscale","range","hb3","0|99.9|0.1|40","expand");
5235
zdialog_add_widget(zdsela,"hbox","hb4","dialog",0,"space=5");
5236
zdialog_add_widget(zdsela,"label","labblend","hb4",Bblendwidth);
5237
zdialog_add_widget(zdsela,"hscale","blendwidth","hb4","0|300|1|0","expand");
5239
zdialog_run(zdsela,sac_dialog_event,sac_dialog_compl); // run dialog - parallel
5241
if (Fimageturned) turn_image(-Fimageturned); // use native orientation
5242
if (sa_type != 2) select_disable(); // disable other type area
5243
sa_type = 2; // area type, select by color
5245
sa_calced = 0; // v.8.6.1
5246
m_select_show(0,0); // show area
5251
int sac_dialog_compl(zdialog *zd, int zstat)
5253
if (zstat != 1) { // kill, cancel
5254
if (sac_seldist_busy) sac_seldist_kill++;
5255
else m_select_delete(0,0);
5257
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
5258
mouseCBfunc = 0; // disconnect mouse function
5260
zdialog_free(zdsela); // kill dialog
5266
int sac_dialog_event(zdialog *zd, const char *event)
5268
if (strEqu(event,"start")) // start or resume
5270
Fshowarea++; // show area
5271
zdialog_stuff(zdsela,"show-hide",Bhide); // set buttons
5272
zdialog_stuff(zdsela,"susp-resm",Bsuspend);
5273
gdk_window_set_cursor(drWin->window,dragcursor); // set drag cursor
5274
mouseCBfunc = sac_mousefunc; // connect mouse function
5275
Mcapture++; // mouse captured for me
5276
select_disable(); // partial delete - re-edit possible
5279
sac_pixseq = (int16 *) zmalloc(cc*2); // maps pixels to selection sequence
5280
memset(sac_pixseq,0,cc*2);
5283
sa_stat = 1; // edit is active
5286
if (strEqu(event,"show-hide")) { // toggle show/hide
5289
zdialog_stuff(zdsela,"show-hide",Bhide);
5293
zdialog_stuff(zdsela,"show-hide",Bshow);
5297
if (strEqu(event,"susp-resm")) { // toggle suspend/resume
5299
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
5300
mouseCBfunc = 0; // disconnect mouse function
5302
zdialog_stuff(zdsela,"susp-resm",Bresume);
5305
else if (sa_stat == 2) {
5306
gdk_window_set_cursor(drWin->window,dragcursor); // set drag cursor
5307
mouseCBfunc = sac_mousefunc; // connect mouse function
5309
zdialog_stuff(zdsela,"susp-resm",Bsuspend);
5314
if (strEqu(event,"finish")) { // finish area
5315
mouseCBfunc = 0; // disconnect mouse function
5317
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
5319
zdialog_stuff(zdsela,"show-hide",Bhide);
5320
sac_select_finish();
5323
if (strEqu(event,"delete")) { // delete area
5324
mouseCBfunc = 0; // disconnect mouse function
5326
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
5330
if (strEqu(event,"blendwidth")) { // blend width changed
5331
if (sa_Npixel && zdedit) {
5332
if (! sa_calced) zmessageACK(ZTX("edge calculation needed")); // v.8.6.1
5334
zdialog_fetch(zd,"blendwidth",sa_blend); // update sa_blend
5335
sa_blend = (sa_blend * sa_blend + 15) / 300; // 0-300 scaled v.8.5
5336
zdialog_send_event(zdedit,event); // notify active edit dialog
5341
mwpaint2(); // update window
5346
// find pixels at edge of area and paint red
5348
void sac_select_show()
5350
int px, py, ii, kk, qx, qy;
5352
if (! sac_pixseq) return;
5354
for (py = 1; py < Fhh-2; py++) // find pixels in area
5355
for (px = 1; px < Fww-2; px++)
5358
if (! sac_pixseq[ii]) continue; // outside of area
5360
if (! sac_pixseq[ii-1] || ! sac_pixseq[ii+1]) goto edgepixel; // check 8 neighbor pixels
5362
if (! sac_pixseq[kk] || ! sac_pixseq[kk-1] || ! sac_pixseq[kk+1]) goto edgepixel;
5364
if (! sac_pixseq[kk] || ! sac_pixseq[kk-1] || ! sac_pixseq[kk+1]) goto edgepixel;
5368
qx = Mscale * (px-Iorgx); // image to window space
5369
qy = Mscale * (py-Iorgy);
5371
if (kk < 3) gdk_gc_set_foreground(gdkgc,&red);
5372
else gdk_gc_set_foreground(gdkgc,&green);
5373
gdk_draw_point(drWin->window, gdkgc, qx + Dorgx, qy + Dorgy); // draw a red/green pixel
5376
gdk_gc_set_foreground(gdkgc,&black);
5382
// construct sa_pixel[] { px, py, dist } from selected pixels in sac_pixseq[]
5384
void sac_select_finish()
5386
int ii, kk, cc, npix, px, py;
5388
if (sa_Npixel) return; // already finished
5389
if (! sac_pixseq) return; // no pixels selected
5391
for (ii = npix = 0; ii < Fww * Fhh; ii++) // count selected pixels
5392
if (sac_pixseq[ii]) npix++;
5393
if (npix < 10) return;
5395
gdk_window_set_cursor(drWin->window,busycursor); // set function busy cursor
5398
for (px = 0; px < Fww; px += 1) // image top & bottom edges
5399
for (py = 0; py < Fhh; py += Fhh-1)
5402
if (sac_pixseq[ii]) sac_pixseq[ii] = 2; // if selected, force non-edge
5405
for (px = 0; px < Fww; px += Fww-1) // image left and right edges
5406
for (py = 0; py < Fhh; py += 1)
5409
if (sac_pixseq[ii]) sac_pixseq[ii] = 2; // if selected, force non-edge
5412
for (py = 1; py < Fhh-1; py++) // check all other pixels
5413
for (px = 1; px < Fww-1; px++)
5416
if (! sac_pixseq[ii]) continue; // outside of selected area
5418
if (! sac_pixseq[ii-1] || ! sac_pixseq[ii+1]) goto edgepixel; // check 8 neighbor pixels
5420
if (! sac_pixseq[kk] || ! sac_pixseq[kk-1] || ! sac_pixseq[kk+1]) goto edgepixel;
5422
if (! sac_pixseq[kk] || ! sac_pixseq[kk-1] || ! sac_pixseq[kk+1]) goto edgepixel;
5423
sac_pixseq[ii] = 2; // non-edge pixel
5426
sac_pixseq[ii] = 1; // edge pixel
5429
if (sa_pixel) zfree(sa_pixel);
5430
cc = npix * sizeof(sa_pixel1); // allocate memory for sa_pixel[]
5431
sa_pixel = (sa_pixel1 *) zmalloc(cc);
5433
if (sa_pixisin) zfree(sa_pixisin);
5434
cc = Fww * Fhh; // allocate memory for sa_pixisin[]
5435
sa_pixisin = zmalloc(cc);
5436
memset(sa_pixisin,0,cc);
5438
for (ii = kk = 0; ii < Fww * Fhh; ii++) // construct sa_pixel[]
5439
{ // { px, py, dist } all pixels in area
5440
if (! sac_pixseq[ii]) continue;
5441
sa_pixisin[ii] = 1; // construct sa_pixisin[]
5442
py = ii / Fww; // =0/1 for pixels out/in area
5444
sa_pixel[kk].px = px;
5445
sa_pixel[kk].py = py;
5446
sa_pixel[kk].dist = sac_pixseq[ii] - 1; // edge = 0, other = 1
5452
sa_calced = 0; // v.8.6.1
5454
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
5459
// calculate distance for all pixels inside the area to the
5460
// nearest edge pixel of the area
5462
void sac_select_edgecalc()
5464
void * sac_seldist_wthread(void *);
5466
sac_select_finish(); // finish if needed
5467
if (! sa_Npixel) return;
5469
sac_seldist_pixdone = 0;
5471
for (int ii = 0; ii < NWthreads; ii++) // start worker threads to calculate
5472
start_detached_thread(sac_seldist_wthread,&wtindex[ii]); // sa_pixel[].dist values
5473
zadd_locked(sac_seldist_busy,+NWthreads);
5475
while (sac_seldist_busy)
5477
edit_progress(sac_seldist_pixdone,sa_Npixel); // monitor progress
5479
zmainloop(); // allow kill
5483
sa_calced = 1; // v.8.6.1
5484
if (sac_seldist_kill) select_disable();
5485
sac_seldist_kill = 0;
5490
void * sac_seldist_wthread(void *arg) // worker thread function
5492
int index = *((int *) (arg));
5493
int ii, kk, npix = sa_Npixel;
5494
int px1, py1, px2, py2;
5495
int distx, disty, dist, mindist;
5497
for (ii = index; ii < npix; ii += NWthreads)
5499
if (! sa_pixel[ii].dist) continue; // find non-edge pixels
5500
px1 = sa_pixel[ii].px;
5501
py1 = sa_pixel[ii].py;
5504
for (kk = 0; kk < npix; kk++)
5506
if (sa_pixel[kk].dist) continue; // find edge pixels
5507
px2 = sa_pixel[kk].px;
5508
py2 = sa_pixel[kk].py; // calculate distance to edge pixel
5509
distx = abs(px2 - px1);
5510
disty = abs(py2 - py1);
5511
if (distx > disty) dist = distx + disty / 3; // fast, error < 6 percent
5512
else dist = disty + distx / 3;
5513
if (dist < mindist) mindist = dist; // remember minimum
5516
sa_pixel[ii].dist = mindist; // distance to nearest edge pixel
5517
sac_seldist_pixdone++;
5518
if (sac_seldist_kill) break;
5521
zadd_locked(sac_seldist_busy,-1);
5526
// invert a selected area
5528
void sac_select_invert() // v.8.5
5530
int ii, kk, npix, cc, px, py;
5532
sac_select_finish(); // finish if needed
5533
if (! sa_Npixel) return;
5535
for (ii = 0; ii < Fww * Fhh; ii++)
5536
if (sac_pixseq[ii] < 0 || sac_pixseq[ii] > 2)
5537
zappcrash("invert area bug 1 %d %d",ii,sac_pixseq[ii]);
5539
for (ii = npix = 0; ii < Fww * Fhh; ii++) // count pixels for inverted area
5540
if (sac_pixseq[ii] < 2) npix++;
5542
zfree(sa_pixel); // free old select area
5543
cc = npix * sizeof(sa_pixel1); // allocate new select area v.8.5.1
5544
sa_pixel = (sa_pixel1 *) zmalloc(cc);
5546
for (ii = kk = 0; ii < Fww * Fhh; ii++)
5548
if (sac_pixseq[ii] == 0) sac_pixseq[ii] = 2; // non-member >> member
5549
else if (sac_pixseq[ii] == 2) sac_pixseq[ii] = 0; // member >> non-member
5550
if (sac_pixseq[ii]) { // edge pixels (=1) remain edge pixels
5553
sa_pixel[kk].px = px;
5554
sa_pixel[kk].py = py;
5555
sa_pixel[kk].dist = sac_pixseq[ii] - 1; // edge = 0, other = 1
5560
if (npix != kk) zappcrash("invert area bug 2 %d %d",kk,npix);
5562
sa_calced = 0; // v.8.6.1
5568
// select area by color - mouse function
5570
void sac_mousefunc()
5572
static int mxdown, mydown;
5574
sac_mousex = sac_mousey = 0;
5576
if (LMclick) { // get mouse position at click
5577
sac_mousex = Mxclick;
5578
sac_mousey = Myclick;
5580
sac_currseq++; // new sequence number for undo
5583
if (Mxdrag || Mydrag) { // get mouse drag position
5584
sac_mousex = Mxdrag;
5585
sac_mousey = Mydrag;
5586
Mxdrag = Mydrag = 0;
5588
if (Mxdown != mxdown || Mydown != mydown) { // detect if new drag started
5591
sac_currseq++; // yes - new sequence number
5595
if (sac_mousex || sac_mousey) {
5596
sac_select_pixels(); // accumulate pixels
5603
if (sac_currseq) { // remove selected pixels having
5604
sac_unselect_pixels(); // current sequence number
5614
// find all contiguous pixels within the specified color range
5616
void sac_select_pixels()
5618
void sac_select_stack(int px, int py, char direc);
5623
if (! sac_pixseq) return;
5625
px = sac_mousex; // mouse position in image
5629
for (kk = 0; kk <= int(1/Mscale + 2); kk++) // if target pixel already selected,
5630
for (px = sac_mousex-kk; px <= sac_mousex+kk; px++) // find nearest unselected pixel
5631
for (py = sac_mousey-kk; py <= sac_mousey+kk; py++) // (relax need for mouse precision)
5632
{ // (works better for scaled-down image)
5633
if (px < 0 || px >= Fww) continue;
5634
if (py < 0 || py >= Fhh) continue;
5636
if (sac_pixseq[ii] == 0) goto gotpix;
5639
if (sac_pixseq[ii] > 0) return; // nothing will be selected
5641
sac_pixseq[ii] = sac_currseq; // map pixel to current sequence
5642
sac_Ncurrseq = 1; // current sequence pixel count
5644
sac_targpix = bmpixel(Frgb48,px,py); // get color at mouse position
5645
sac_targR = sac_targpix[0]; // = target color
5646
sac_targG = sac_targpix[1];
5647
sac_targB = sac_targpix[2];
5649
zdialog_fetch(zdsela,"range",sac_targmatch); // color range, 0.0 to 99.9
5650
sac_targmatch = 1.0 - 0.01 * sac_targmatch; // target match level, 0.001 to 1.0
5652
sac_stackii[0] = ii; // put 1st pixel into stack
5653
sac_stackdir[0] = 'r'; // direction = right
5654
sac_Nstack = 1; // stack count
5658
kk = sac_Nstack - 1; // get last pixel in stack
5659
ii = sac_stackii[kk];
5660
direc = sac_stackdir[kk];
5662
py = ii / Fww; // reconstruct px, py
5665
if (direc == 'x') { // no neighbors left to check
5670
if (direc == 'r') { // push next right pixel into stack
5671
sac_select_stack(px,py,'r'); // if color within range
5672
sac_stackdir[kk] = 'l'; // this pixel next direction to look
5676
if (direc == 'l') { // or next left pixel
5677
sac_select_stack(px,py,'l');
5678
sac_stackdir[kk] = 'a';
5682
if (direc == 'a') { // or next ahead pixel
5683
sac_select_stack(px,py,'a');
5684
sac_stackdir[kk] = 'x';
5693
// un-select all pixels mapped to current sequence number
5695
void sac_unselect_pixels()
5697
if (! sac_currseq) return;
5699
for (int ii = 0; ii < Fww * Fhh; ii++)
5701
if (sac_pixseq[ii] != sac_currseq) continue;
5710
// push pixel into stack memory if its color is within range
5711
// and not already mapped to a prior sequence number
5713
void sac_select_stack(int px, int py, char direc)
5715
int ii, kk, ppx, ppy, npx, npy;
5717
double match, ff = 1.0 / 65536.0;
5718
double dred, dgreen, dblue;
5720
if (sac_Nstack > 1) {
5721
kk = sac_Nstack - 2; // get prior pixel in stack
5722
ii = sac_stackii[kk];
5724
ppx = ii - ppy * Fww;
5727
ppx = px - 1; // if only one, assume prior = left
5731
if (direc == 'r') { // get pixel in direction right
5732
npx = px + ppy - py;
5733
npy = py + px - ppx;
5735
else if (direc == 'l') { // or left
5736
npx = px + py - ppy;
5737
npy = py + ppx - px;
5739
else if (direc == 'a') { // or ahead
5740
npx = px + px - ppx;
5741
npy = py + py - ppy;
5743
else npx = npy = -1; // stop warning
5745
if (npx < 0 || npx >= Fww) return; // pixel off the edge
5746
if (npy < 0 || npy >= Fhh) return;
5748
ii = npy * Fww + npx;
5749
if (sac_pixseq[ii]) return; // pixel already mapped
5751
matchpix = bmpixel(Frgb48,npx,npy); // match pixel RGB colors
5752
dred = ff * abs(sac_targR - matchpix[0]); // with target pixel colors
5753
dgreen = ff * abs(sac_targG - matchpix[1]);
5754
dblue = ff * abs(sac_targB - matchpix[2]);
5755
match = (1.0 - dred) * (1.0 - dgreen) * (1.0 - dblue);
5756
if (match < sac_targmatch) return; // inadequate match
5758
if (sac_Nstack == sac_maxstack) return; // stack is full
5760
sac_pixseq[ii] = sac_currseq; // map pixel to curr. sequence
5763
kk = sac_Nstack++; // put pixel into stack
5764
sac_stackii[kk] = ii;
5765
sac_stackdir[kk] = 'r'; // direction = right
5771
/**************************************************************************
5772
functions common to both mouse-selected and color-selected areas
5773
***************************************************************************/
5775
// show outline of selected area - also called when window is repainted
5777
void m_select_show(GtkWidget *, const char *) // v.8.5
5780
if (sa_type == 1) sam_select_show();
5781
if (sa_type == 2) sac_select_show();
5788
void m_select_hide(GtkWidget *, const char *) // v.8.5
5795
// compute distance from all pixels in area to nearest edge
5797
void m_select_edgecalc(GtkWidget *, const char *) // v.8.5
5800
double limit1 = 100 * mega, limit2 = 0.1 * mega;
5802
if (sa_type == 1) sam_select_finish(); // finish if needed
5803
if (sa_type == 2) sac_select_finish();
5804
if (! sa_Npixel) return; // no finished area
5806
if (sa_type == 1 && (1.0 * sam_npg * sa_Npixel) > limit1) warn = 1;
5807
if (sa_type == 2 && sa_Npixel > limit2) warn = 1;
5810
yn = zmessageYN(ZTX("Distance calculation needs a long time.\n"
5811
" Do you want to continue?"));
5815
gdk_window_set_cursor(drWin->window,busycursor); // set busy cursor
5818
if (sa_type == 1) sam_select_edgecalc(); // mouse select, polygon
5819
if (sa_type == 2) sac_select_edgecalc(); // color select
5821
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
5827
// invert a selected area
5829
void m_select_invert(GtkWidget *, const char *) // v.8.5
5831
if (sa_type == 1) sam_select_finish(); // finish if needed
5832
if (sa_type == 2) sac_select_finish();
5833
if (! sa_Npixel) return; // no finished area
5835
gdk_window_set_cursor(drWin->window,busycursor); // set busy cursor
5838
if (sa_type == 1) sam_select_invert();
5839
if (sa_type == 2) sac_select_invert();
5841
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
5847
// menu function - clear selected image area, free memory
5849
void m_select_delete(GtkWidget *, const char *) // v.8.5
5853
if (! sam_npg && ! sac_pixseq) return; // nothing to delete
5855
yn = zmessageYN(Bdeletearea);
5862
// menu function - disable selected image area, re-edit possible
5864
void m_select_disable(GtkWidget *, const char *) // v.8.5
5871
// completely delete both kinds of select area
5873
void select_delete() // v.8.5
5876
sam_npg = sac_currseq = sa_stat = 0;
5877
if (sac_pixseq) zfree(sac_pixseq);
5883
// disable area so that re-edit is possible
5885
void select_disable() // v.8.5
5887
if (sa_Npixel) sam_npg--; // remove closing polygon segment
5888
if (sam_npg < 3) sam_npg = 0; // if < 3 nodes, remove all nodes
5889
sa_stat = 1; // status = editable
5890
sa_Npixel = sa_blend = 0;
5891
sac_Ncurrseq = sac_Nstack = 0;
5892
if (sa_pixel) zfree(sa_pixel);
5893
if (sa_pixisin) zfree(sa_pixisin);
5896
sa_calced = 0; // v.8.6.1
5902
/**************************************************************************
5903
begin image edit functions
5904
***************************************************************************/
5907
// adjust white balance
5909
double whitebal_red, whitebal_green, whitebal_blue;
5910
int whitebal_busy = 0;
5913
void m_whitebal(GtkWidget *, const char *) // v.8.6
5915
void whitebal_mousefunc();
5916
int whitebal_dialog_compl(zdialog* zd, int zstat);
5917
void *whitebal_thread(void *);
5919
const char *wbtitle = ZTX("Adjust White Balance");
5920
const char *wbhelp = ZTX("Click white or gray image location");
5922
if (! edit_setup(1,2)) return; // setup edit: preview
5924
zdedit = zdialog_new(wbtitle,mWin,Bdone,Bcancel,null); // white balance dialog
5925
zdialog_add_widget(zdedit,"hbox","hb1","dialog",0,"space=10");
5926
zdialog_add_widget(zdedit,"label","labwbh","hb1",wbhelp,"space=5");
5927
zdialog_add_widget(zdedit,"hbox","hb2","dialog",0,"space=5");
5928
zdialog_add_widget(zdedit,"label","labpix","hb2","pixel:");
5929
zdialog_add_widget(zdedit,"label","pixel","hb2","0000 0000");
5930
zdialog_add_widget(zdedit,"label","labrgb","hb2"," RGB:");
5931
zdialog_add_widget(zdedit,"label","rgb","hb2","000 000 000");
5933
zdialog_run(zdedit,0,whitebal_dialog_compl); // run dialog - parallel
5935
whitebal_red = whitebal_green = whitebal_blue = 1.0;
5936
start_thread(whitebal_thread,0); // start working thread
5938
mouseCBfunc = whitebal_mousefunc; // connect mouse function
5939
Mcapture = 1; // capture mouse clicks
5945
int whitebal_dialog_compl(zdialog *zd, int zstat) // dialog completion function
5947
mouseCBfunc = 0; // disconnect mouse
5949
if (zstat == 1) edit_done(); // done
5950
else edit_cancel(); // cancel or destroy
5955
void whitebal_mousefunc() // mouse function
5958
double red, green, blue, rgbmean;
5962
if (! LMclick) return;
5965
px = Mxclick; // mouse click position
5968
if (px < 2) px = 2; // pull back from edge
5969
if (px > E3ww-3) px = E3ww-3;
5971
if (py > E3hh-3) py = E3hh-3;
5973
red = green = blue = 0;
5975
for (dy = -2; dy <= 2; dy++) // 5x5 block around mouse position
5976
for (dx = -2; dx <= 2; dx++)
5978
ppix48 = bmpixel(E1rgb48,px+dx,py+dy); // input image
5984
red = red / 25.0; // mean RGB levels
5985
green = green / 25.0;
5987
rgbmean = (red + green + blue) / 3.0;
5989
whitebal_red = rgbmean / red;
5990
whitebal_green = rgbmean / green;
5991
whitebal_blue = rgbmean / blue;
5993
signal_thread(); // trigger image update
5995
snprintf(work,40,"%d %d",px,py);
5996
zdialog_stuff(zdedit,"pixel",work);
5998
snprintf(work,40,"%7.3f %7.3f %7.3f",red/256,green/256,blue/256);
5999
zdialog_stuff(zdedit,"rgb",work);
6005
// Update image based on neutral pixel that was clicked
6007
void * whitebal_thread(void *)
6009
void * whitebal_wthread(void *arg);
6013
thread_idle_loop(); // wait for work or exit request
6015
for (int ii = 0; ii < NWthreads; ii++) // start worker threads
6016
start_detached_thread(whitebal_wthread,&wtindex[ii]);
6017
zadd_locked(whitebal_busy,+NWthreads);
6019
while (whitebal_busy) zsleep(0.01); // wait for completion
6022
mwpaint2(); // update window
6025
return 0; // not executed, stop g++ warning
6029
void * whitebal_wthread(void *arg) // worker thread function
6031
void whitebalpix(int px, int py, int dist);
6033
int px, py, ii, dist;
6034
int index = *((int *) arg);
6036
if (sa_Npixel) // process selected area
6038
for (ii = index; ii < sa_Npixel; ii += NWthreads) // process all enclosed pixels
6040
px = sa_pixel[ii].px;
6041
py = sa_pixel[ii].py;
6042
dist = sa_pixel[ii].dist;
6043
whitebalpix(px,py,dist);
6047
else // process whole image
6049
dist = sa_blend = 0;
6050
for (py = index; py < E1hh; py += NWthreads)
6051
for (px = 0; px < E1ww; px++)
6052
whitebalpix(px,py,dist);
6055
zadd_locked(whitebal_busy,-1);
6060
void whitebalpix(int px, int py, int dist) // process one pixel
6062
uint16 *pix1, *pix3;
6063
double red1, green1, blue1;
6064
double red3, green3, blue3;
6065
double brmax, dold, dnew;
6067
pix1 = bmpixel(E1rgb48,px,py); // input pixel
6068
pix3 = bmpixel(E3rgb48,px,py); // output pixel
6074
red3 = whitebal_red * red1; // change color ratios
6075
green3 = whitebal_green * green1;
6076
blue3 = whitebal_blue * blue1;
6078
if (dist < sa_blend) { // blend select area if req.
6079
dnew = 1.0 * dist / sa_blend;
6081
red3 = dnew * red3 + dold * red1;
6082
green3 = dnew * green3 + dold * green1;
6083
blue3 = dnew * blue3 + dold * blue1;
6086
brmax = red3; // brmax = brightest color
6087
if (green3 > brmax) brmax = green3;
6088
if (blue3 > brmax) brmax = blue3;
6090
if (brmax > 65535) { // if overflow, reduce
6091
brmax = 65535 / brmax;
6092
red3 = red3 * brmax;
6093
green3 = green3 * brmax;
6094
blue3 = blue3 * brmax;
6097
pix3[0] = int(red3);
6098
pix3[1] = int(green3);
6099
pix3[2] = int(blue3);
6105
/**************************************************************************/
6107
// flatten brightness distribution
6109
int flatten_busy = 0;
6110
double flatten_value = 0; // flatten value, 0 - 100%
6111
double flatten_brdist[65536];
6113
void m_flatten(GtkWidget *, const char *)
6115
int flatten_dialog_event(zdialog* zd, const char *event);
6116
int flatten_dialog_compl(zdialog* zd, int zstat);
6117
void * flatten_thread(void *);
6119
const char *title = ZTX("Flatten Brightness Distribution");
6121
if (! edit_setup(1,2)) return; // setup edit: preview
6123
zdedit = zdialog_new(title,mWin,Bundo,Bredo,Bdone,Bcancel,null); // flatten distribution dialog
6124
zdialog_add_widget(zdedit,"hbox","hb1","dialog",0,"space=15");
6125
zdialog_add_widget(zdedit,"label","labfd","hb1",ZTX("Flatten"),"space=5");
6126
zdialog_add_widget(zdedit,"hscale","flatten","hb1","0|100|1|0","expand");
6128
zdialog_resize(zdedit,300,0);
6129
zdialog_run(zdedit,flatten_dialog_event,flatten_dialog_compl); // run dialog - parallel
6132
start_thread(flatten_thread,0); // start working thread
6137
// flatten dialog event and completion functions
6139
int flatten_dialog_event(zdialog *zd, const char *event) // flatten dialog event function
6141
zdialog_fetch(zd,"flatten",flatten_value); // get slider value
6142
signal_thread(); // trigger update thread
6147
int flatten_dialog_compl(zdialog *zd, int zstat) // flatten dialog completion function
6149
if (zstat == 1) edit_undo(); // undo
6150
else if (zstat == 2) edit_redo(); // redo
6151
else if (zstat == 3) edit_done(); // done
6152
else edit_cancel(); // cancel or destroy
6157
// thread function - use multiple working threads
6159
void * flatten_thread(void *)
6161
void * flatten_wthread(void *arg);
6169
thread_idle_loop(); // wait for work or exit request
6171
for (ii = 0; ii < 65536; ii++) // clear brightness distribution data
6172
flatten_brdist[ii] = 0;
6174
if (sa_Npixel) // process selected area
6176
for (ii = 0; ii < sa_Npixel; ii++) // process enclosed pixels
6178
px = sa_pixel[ii].px; // compute brightness distribution
6179
py = sa_pixel[ii].py;
6180
pix1 = bmpixel(E1rgb48,px,py);
6181
bright1 = brightness(pix1);
6182
flatten_brdist[int(bright1)]++;
6185
for (ii = 1; ii < 65536; ii++) // cumulative brightness distribution
6186
flatten_brdist[ii] += flatten_brdist[ii-1]; // 0 ... sa_Npixel
6188
for (ii = 0; ii < 65536; ii++)
6189
flatten_brdist[ii] = flatten_brdist[ii] // multiplier per brightness level
6190
/ sa_Npixel * 65536.0 / (ii + 1);
6193
else // process whole image
6195
for (py = 0; py < E1hh; py++) // compute brightness distribution
6196
for (px = 0; px < E1ww; px++)
6198
pix1 = bmpixel(E1rgb48,px,py);
6199
bright1 = brightness(pix1);
6200
flatten_brdist[int(bright1)]++;
6203
for (ii = 1; ii < 65536; ii++) // cumulative brightness distribution
6204
flatten_brdist[ii] += flatten_brdist[ii-1]; // 0 ... (ww1 * hh1)
6206
for (ii = 0; ii < 65536; ii++)
6207
flatten_brdist[ii] = flatten_brdist[ii] // multiplier per brightness level
6208
/ (E1ww * E1hh) * 65536.0 / (ii + 1);
6211
for (ii = 0; ii < NWthreads; ii++) // start worker threads
6212
start_detached_thread(flatten_wthread,&wtindex[ii]);
6213
zadd_locked(flatten_busy,+NWthreads);
6215
while (flatten_busy) zsleep(0.004); // wait for completion
6218
mwpaint2(); // update window
6221
return 0; // not executed, stop g++ warning
6225
void * flatten_wthread(void *arg) // worker thread function
6227
void flatten_1pix(int px, int py, int dist);
6229
int index = *((int *) (arg));
6230
int px, py, ii, dist;
6232
if (sa_Npixel) // process selected area
6234
for (ii = index; ii < sa_Npixel; ii += NWthreads) // process all enclosed pixels
6236
px = sa_pixel[ii].px; // flatten brightness distribution
6237
py = sa_pixel[ii].py;
6238
dist = sa_pixel[ii].dist;
6239
flatten_1pix(px,py,dist);
6245
dist = sa_blend = 0;
6246
for (py = index; py < E1hh; py += NWthreads) // flatten brightness distribution
6247
for (px = 0; px < E1ww; px++)
6248
flatten_1pix(px,py,dist);
6251
zadd_locked(flatten_busy,-1);
6256
void flatten_1pix(int px, int py, int dist)
6258
uint16 *pix1, *pix3;
6259
double fold, fnew, dold, dnew, cmax;
6260
double red1, green1, blue1, red3, green3, blue3;
6261
double bright1, bright2;
6263
pix1 = bmpixel(E1rgb48,px,py); // input pixel
6264
pix3 = bmpixel(E3rgb48,px,py); // output pixel
6266
fnew = 0.01 * flatten_value; // 0.0 - 1.0 how much to flatten
6267
fold = 1.0 - fnew; // 1.0 - 0.0 how much to retain
6273
bright1 = brightness(pix1); // input brightness
6274
bright2 = flatten_brdist[int(bright1)]; // output brightness adjustment
6276
red3 = bright2 * red1; // flattened brightness
6277
green3 = bright2 * green1;
6278
blue3 = bright2 * blue1;
6280
red3 = fnew * red3 + fold * red1; // blend new and old brightness
6281
green3 = fnew * green3 + fold * green1;
6282
blue3 = fnew * blue3 + fold * blue1;
6284
if (dist < sa_blend) { // blend over distance sa_blend
6285
dnew = 1.0 * dist / sa_blend;
6287
red3 = dnew * red3 + dold * red1;
6288
green3 = dnew * green3 + dold * green1;
6289
blue3 = dnew * blue3 + dold * blue1;
6292
cmax = red3; // stop overflow, keep color balance
6293
if (green3 > cmax) cmax = green3;
6294
if (blue3 > cmax) cmax = blue3;
6296
cmax = 65535 / cmax;
6298
green3 = green3 * cmax;
6299
blue3 = blue3 * cmax;
6302
pix3[0] = int(red3 + 0.5);
6303
pix3[1] = int(green3 + 0.5);
6304
pix3[2] = int(blue3 + 0.5);
6309
/**************************************************************************/
6311
// brightness / color / contrast adjustment
6313
int tune_curve_adjust(void *,GdkEventButton *); // mouse events in drawing area
6314
int tune_curve_draw(); // draw curve in drawing area
6316
GtkWidget *tune_drawarea; // drawing area for curve
6318
int tune_ii; // ii = current spline curve
6319
int tune_nap[7]; // no. anchor points for 7 curves
6320
double tune_apx[7][50], tune_apy[7][50]; // anchor points for 7 curves
6321
double tune_dat[7][100]; // data points for 7 curves
6324
void m_tune(GtkWidget *, const char *) // overhauled v.6.8
6326
int tune_dialog_event(zdialog *zd, cchar *event);
6327
int tune_dialog_compl(zdialog *zd, int zstat);
6328
void *tune_thread(void *);
6330
const char *title = ZTX("Adjust Brightness and Color");
6332
if (! edit_setup(1,2)) return; // setup edit: preview
6334
for (int ii = 0; ii < 7; ii++)
6335
{ // initz. all curves to flat
6337
tune_apx[ii][0] = 0; // 3 anchor points:
6338
tune_apy[ii][0] = 50; // (0,50) (50,50) (100,50)
6339
tune_apx[ii][1] = 50;
6340
tune_apy[ii][1] = 50;
6341
tune_apx[ii][2] = 100;
6342
tune_apy[ii][2] = 50;
6344
for (int jj = 0; jj < 100; jj++) // initz. curve data points
6345
tune_dat[ii][jj] = 50;
6348
tune_ii = 0; // default curve = brightness
6351
--------------------------------------------
6353
| curve drawing area |
6355
--------------------------------------------
6356
darker areas lighter areas
6358
[+++] [---] [+ -] [- +] [+-+] [-+-]
6362
(o) color intensity [reset 1] [reset all]
6363
(o) color saturation
6364
(o) color balance (o) red (o) green (o) blue
6368
zdedit = zdialog_new(title,mWin,Bundo,Bredo,Bdone,Bcancel,null); // create dialog
6370
zdialog_add_widget(zdedit,"frame","fr1","dialog",0,"expand");
6371
zdialog_add_widget(zdedit,"hbox","hba","dialog");
6372
zdialog_add_widget(zdedit,"label","labda","hba",Bdarker,"space=5");
6373
zdialog_add_widget(zdedit,"label","space","hba",0,"expand");
6374
zdialog_add_widget(zdedit,"label","labba","hba",Blighter,"space=5");
6375
zdialog_add_widget(zdedit,"hbox","hbb","dialog",0,"space=5");
6376
zdialog_add_widget(zdedit,"button","b +++","hbb","+++");
6377
zdialog_add_widget(zdedit,"button","b ---","hbb"," - - - ");
6378
zdialog_add_widget(zdedit,"button","b +-", "hbb"," + - ");
6379
zdialog_add_widget(zdedit,"button","b -+", "hbb"," - + ");
6380
zdialog_add_widget(zdedit,"button","b +-+","hbb","+ - +");
6381
zdialog_add_widget(zdedit,"button","b -+-","hbb"," - + - ");
6382
zdialog_add_widget(zdedit,"hbox","hb2","dialog");
6383
zdialog_add_widget(zdedit,"vbox","vb21","hb2");
6384
zdialog_add_widget(zdedit,"vbox","vb22","hb2");
6385
zdialog_add_widget(zdedit,"radio","radbri","vb21",Bbrightness);
6386
zdialog_add_widget(zdedit,"radio","radfog","vb21",ZTX("defog"));
6387
zdialog_add_widget(zdedit,"radio","radcol","vb21",ZTX("color intensity"));
6388
zdialog_add_widget(zdedit,"radio","radsat","vb21",ZTX("color saturation"));
6389
zdialog_add_widget(zdedit,"radio","radbal","vb21",ZTX("color balance"));
6390
zdialog_add_widget(zdedit,"hbox","hbrs","vb22",0,"space=5");
6391
zdialog_add_widget(zdedit,"label","space","hbrs",0,"space=20");
6392
zdialog_add_widget(zdedit,"button","reset1","hbrs",ZTX(" reset 1 "));
6393
zdialog_add_widget(zdedit,"button","resetA","hbrs",ZTX("reset all"));
6394
zdialog_add_widget(zdedit,"label","space","vb22",0,"expand");
6395
zdialog_add_widget(zdedit,"hbox","hbrgb","vb22");
6396
zdialog_add_widget(zdedit,"radio","radR","hbrgb",Bred,"space=10");
6397
zdialog_add_widget(zdedit,"radio","radG","hbrgb",Bgreen,"space=5");
6398
zdialog_add_widget(zdedit,"radio","radB","hbrgb",Bblue,"space=5");
6400
GtkWidget *frame = zdialog_widget(zdedit,"fr1"); // add drawing area to frame
6401
tune_drawarea = gtk_drawing_area_new();
6402
gtk_container_add(GTK_CONTAINER(frame),tune_drawarea);
6404
gtk_widget_add_events(tune_drawarea,GDK_BUTTON_PRESS_MASK); // connect drawing area events
6405
gtk_widget_add_events(tune_drawarea,GDK_BUTTON_RELEASE_MASK);
6406
gtk_widget_add_events(tune_drawarea,GDK_BUTTON1_MOTION_MASK);
6407
G_SIGNAL(tune_drawarea,"motion-notify-event",tune_curve_adjust,0)
6408
G_SIGNAL(tune_drawarea,"button-press-event",tune_curve_adjust,0)
6409
G_SIGNAL(tune_drawarea,"expose-event",tune_curve_draw,0)
6411
zdialog_stuff(zdedit,"radbri",1); // stuff defaults
6412
zdialog_stuff(zdedit,"radR",1);
6414
zdialog_resize(zdedit,0,370);
6415
zdialog_run(zdedit,tune_dialog_event,tune_dialog_compl); // run dialog - parallel
6416
start_thread(tune_thread,0); // start working thread
6421
// tune dialog event and completion functions
6423
int tune_dialog_event(zdialog *zd, const char *event)
6425
int ii, jj, curve = -1;
6428
if (strnEqu(event,"rad",3)) // new choice of curve
6430
ii = strcmpv(event,"radbri","radfog","radcol","radsat","radR","radG","radB",0);
6431
if (ii > 0) curve = ii - 1;
6433
if (strstr("radR radG radB",event)) // if RGB, set color balance
6434
zdialog_stuff(zd,"radbal",1);
6436
if (strEqu(event,"radbal")) { // if color balance,
6437
zdialog_fetch(zd,"radR",ii); // get current RGB selection
6439
zdialog_fetch(zd,"radG",ii);
6441
zdialog_fetch(zd,"radB",ii);
6445
if (curve >= 0 && curve != tune_ii) {
6446
tune_ii = curve; // set new curve
6447
tune_curve_draw(); // redraw curve
6451
if (strnEqu(event,"b ",2)) // button to move entire curve
6455
for (jj = 0; jj < tune_nap[ii]; jj++)
6457
px = tune_apx[ii][jj];
6458
py = tune_apy[ii][jj];
6460
if (strEqu(event,"b +++")) py += 10;
6461
if (strEqu(event,"b ---")) py -= 10;
6462
if (strEqu(event,"b +-")) py += 10.0 - 0.2 * px;
6463
if (strEqu(event,"b -+")) py -= 10.0 - 0.2 * px;
6464
if (strEqu(event,"b +-+")) py -= 5 - 0.2 * abs(px-50);
6465
if (strEqu(event,"b -+-")) py += 5 - 0.2 * abs(px-50);
6467
if (py > 100) py = 100;
6469
tune_apy[ii][jj] = py;
6472
tune_curve_draw(); // redraw curve
6473
signal_thread(); // trigger image update
6476
if (strEqu(event,"reset1"))
6478
ii = tune_ii; // current curve
6480
tune_apx[ii][0] = 0; // 3 anchor points:
6481
tune_apy[ii][0] = 50; // (0,50) (50,50) (100,50)
6482
tune_apx[ii][1] = 50;
6483
tune_apy[ii][1] = 50;
6484
tune_apx[ii][2] = 100;
6485
tune_apy[ii][2] = 50;
6487
for (int jj = 0; jj < 100; jj++) // initz. curve data points
6488
tune_dat[ii][jj] = 50;
6490
tune_curve_draw(); // update dialog curve
6491
signal_thread(); // update image
6494
if (strEqu(event,"resetA"))
6496
for (int ii = 0; ii < 7; ii++) // do all curves
6499
tune_apx[ii][0] = 0;
6500
tune_apy[ii][0] = 50;
6501
tune_apx[ii][1] = 50;
6502
tune_apy[ii][1] = 50;
6503
tune_apx[ii][2] = 100;
6504
tune_apy[ii][2] = 50;
6506
for (int jj = 0; jj < 100; jj++)
6507
tune_dat[ii][jj] = 50;
6514
if (strEqu(event,"blendwidth")) signal_thread(); // select area blend width change
6520
int tune_dialog_compl(zdialog *zd, int zstat) // tune dialog completion function
6522
if (zstat == 1) edit_undo(); // undo
6523
else if (zstat == 2) edit_redo(); // redo
6524
else if (zstat == 3) edit_done(); // done
6525
else edit_cancel(); // cancel or destroy
6530
// Add, delete, or move anchor points in curve using mouse.
6532
int tune_curve_adjust(void *,GdkEventButton *event)
6535
int kk, ii, jj, newjj, minjj = -1;
6536
int mx, my, button, evtype;
6537
double dist2, mindist2 = 1000000;
6540
ii = tune_ii; // get current curve
6542
if (tune_nap[ii] > 49) {
6543
zmessageACK(ZTX("Exceed 50 anchor points"));
6547
mx = int(event->x); // mouse position in drawing area
6549
evtype = event->type;
6550
button = event->button;
6551
ww = tune_drawarea->allocation.width; // drawing area size
6552
hh = tune_drawarea->allocation.height;
6554
for (jj = 0; jj < tune_nap[ii]; jj++) // find closest anchor point
6556
xval = tune_apx[ii][jj];
6557
yval = spline2(xval);
6558
px = int(0.01 * ww * xval); // 0 - ww
6559
py = int(hh - 0.01 * hh * yval + 0.5); // 0 - hh
6560
dist2 = (px-mx)*(px-mx) + (py-my)*(py-my);
6561
if (dist2 < mindist2) {
6567
if (minjj < 0) return 0; // huh?
6569
if (evtype == GDK_BUTTON_PRESS && button == 3) { // right click, remove anchor point
6570
if (mindist2 > 25) return 0;
6571
if (tune_nap[ii] < 3) return 0;
6572
for (kk = minjj; kk < tune_nap[ii] -1; kk++) {
6573
tune_apx[ii][kk] = tune_apx[ii][kk+1];
6574
tune_apy[ii][kk] = tune_apy[ii][kk+1];
6578
tune_curve_draw(); // regen and redraw curve
6579
signal_thread(); // trigger image update
6583
// drag or left click, move nearby anchor point to mouse position,
6584
// or add a new anchor point if nothing near enough
6586
xval = 100.0 * mx / ww; // 0 - 100
6587
yval = 100.0 * (hh - my) / hh; // 0 - 100
6589
if (xval < 0 || xval > 100) return 0; // v.6.8
6590
if (yval < 0 || yval > 100) return 0;
6592
if (mindist2 < 100) { // existing point < 10 pixels away
6594
if (jj < tune_nap[ii] - 1 && tune_apx[ii][jj+1] - xval < 5) // disallow < 5 x-pixels
6595
return 0; // to next or prior point
6596
if (jj > 0 && xval - tune_apx[ii][jj-1] < 5) return 0;
6597
newjj = minjj; // point to be moved
6599
else // > 10 pixels, add a point
6601
for (jj = 0; jj < tune_nap[ii]; jj++)
6602
if (xval <= tune_apx[ii][jj]) break; // find point with next higher x
6604
if (jj < tune_nap[ii] && tune_apx[ii][jj] - xval < 5) return 0; // disallow < 5 pixels
6605
if (jj > 0 && xval - tune_apx[ii][jj-1] < 5) return 0; // to next or prior point
6607
for (kk = tune_nap[ii]; kk > jj; kk--) { // make hole for new point
6608
tune_apx[ii][kk] = tune_apx[ii][kk-1];
6609
tune_apy[ii][kk] = tune_apy[ii][kk-1];
6612
tune_nap[ii]++; // up point count
6613
newjj = jj; // point to be added
6616
tune_apx[ii][newjj] = xval; // coordinates of new or moved point
6617
tune_apy[ii][newjj] = yval;
6619
tune_curve_draw(); // regen and redraw the curve
6620
signal_thread(); // trigger image update
6625
// Draw brightness curve based on defined spline anchor points.
6627
int tune_curve_draw()
6630
int ii, jj, jjx, jjy;
6633
ii = tune_ii; // current curve
6635
ww = tune_drawarea->allocation.width; // drawing area size
6636
hh = tune_drawarea->allocation.height;
6637
if (ww < 50 || hh < 20) return 0;
6639
spline1(tune_nap[ii],tune_apx[ii],tune_apy[ii]); // make curve fitting anchor points
6641
gdk_window_clear(tune_drawarea->window); // clear window
6643
for (px = 0; px < ww; px++) // generate all points for curve
6645
xval = 100.0 * px / ww;
6646
yval = spline2(xval);
6647
py = int(hh - 0.01 * hh * yval + 0.5);
6648
gdk_draw_point(tune_drawarea->window,gdkgc,px,py);
6651
for (jj = 0; jj < tune_nap[ii]; jj++) // draw boxes at anchor points
6653
xval = tune_apx[ii][jj];
6654
yval = spline2(xval);
6655
px = int(0.01 * ww * xval);
6656
py = int(hh - 0.01 * hh * yval + 0.5);
6657
for (jjx = -2; jjx < 3; jjx++)
6658
for (jjy = -2; jjy < 3; jjy++) {
6659
if (px+jjx < 0 || px+jjx >= ww) continue;
6660
if (py+jjy < 0 || py+jjy >= hh) continue;
6661
gdk_draw_point(tune_drawarea->window,gdkgc,px+jjx,py+jjy);
6665
for (jj = 0; jj < 100; jj++) // save 100 curve data points
6666
tune_dat[ii][jj] = spline2(jj);
6672
// Update image based on latest settings of all dialog controls.
6674
void * tune_thread(void *)
6676
void * tune_wthread(void *arg);
6680
thread_idle_loop(); // wait for work or exit request
6682
for (int ii = 0; ii < NWthreads; ii++) // start worker threads
6683
start_detached_thread(tune_wthread,&wtindex[ii]);
6684
zadd_locked(tune_busy,+NWthreads);
6686
while (tune_busy) zsleep(0.004); // wait for completion
6689
mwpaint2(); // update window
6692
return 0; // not executed, stop g++ warning
6696
void * tune_wthread(void *arg) // worker thread function
6698
void tune1pix(int px, int py, int dist);
6700
int px, py, ii, dist;
6701
int index = *((int *) arg);
6703
if (sa_Npixel) // process selected area
6705
for (ii = index; ii < sa_Npixel; ii += NWthreads) // process all enclosed pixels
6707
px = sa_pixel[ii].px;
6708
py = sa_pixel[ii].py;
6709
dist = sa_pixel[ii].dist;
6710
tune1pix(px,py,dist);
6714
else // process whole image
6716
dist = sa_blend = 0;
6717
for (py = index; py < E1hh; py += NWthreads)
6718
for (px = 0; px < E1ww; px++)
6719
tune1pix(px,py,dist);
6722
zadd_locked(tune_busy,-1);
6727
void tune1pix(int px, int py, int dist) // process one pixel
6729
uint16 *pix1, *pix3;
6730
double red1, green1, blue1, red3, green3, blue3;
6731
double brmin, brmax, brout;
6734
pix1 = bmpixel(E1rgb48,px,py); // input pixel
6735
pix3 = bmpixel(E3rgb48,px,py); // output pixel
6737
red1 = red3 = pix1[0];
6738
green1 = green3 = pix1[1];
6739
blue1 = blue3 = pix1[2];
6741
brmax = red1; // brmax = brightest color
6742
if (green1 > brmax) brmax = green1;
6743
if (blue1 > brmax) brmax = blue1;
6745
curveindex = int(brmax/656); // index into curve data, 0-99
6747
/* ------------------------------------------------------------------------
6749
brightness curve values:
6751
50 = normal, unchanged
6752
100 = 200% brightness, clipped
6755
brout = tune_dat[0][curveindex]; // brightness factor, 0 - 99
6757
if (brout < 49 || brout > 51)
6759
brout = brout / 50.0; // 0 - 2.0
6760
if (brout * brmax > 65535.0) brout = 65535.0 / brmax; // reduce if necessary
6762
red3 = red3 * brout; // apply to all colors
6763
green3 = green3 * brout;
6764
blue3 = blue3 * brout;
6767
/* ------------------------------------------------------------------------
6769
defog (whiteness) curve values: // v.8.2
6771
50 = normal, unchanged
6772
100 = double whiteness, clipped
6775
brout = tune_dat[1][curveindex]; // whiteness factor, 0 - 99
6777
if (brout < 49 || brout > 51)
6779
brmin = red3; // brmin = darkest color
6780
if (green3 < brmin) brmin = green3;
6781
if (blue3 < brmin) brmin = blue3;
6783
brout = brout / 50.0 - 1.0; // range -1 .. +1
6784
brmin = brmin * brout; // range -brmin .. +brmin
6787
if (brmax + brmin > 65535.0) brmin = 65535.0 - brmax; // prevent overflow
6789
red3 = red3 + brmin; // reduce or add whiteness
6790
green3 = green3 + brmin;
6791
blue3 = blue3 + brmin;
6794
/* ------------------------------------------------------------------------
6796
color intensity curve values:
6797
0 = no color (grey scale)
6798
50 = normal, unchanged
6801
50 >> 0: move all RGB values to their mean: (R+G+B)/3
6802
50 >> 100: increase all RGB values by same factor
6804
In the 2nd case, the movement is greater for darker pixels
6807
double red50, green50, blue50, red100, green100, blue100;
6808
double rgb0, max50, min50, color, bright, ramper;
6810
brout = tune_dat[2][curveindex]; // brightness factor, 0 - 99
6812
if (brout < 49 || brout > 51)
6814
red50 = red3; // 50% color values (normal)
6818
rgb0 = (red50 + green50 + blue50) / 3; // 0% color values (grey scale)
6820
max50 = min50 = red50;
6821
if (green50 > max50) max50 = green50; // get max/min normal color values
6822
else if (green50 < min50) min50 = green50;
6823
if (blue50 > max50) max50 = blue50;
6824
else if (blue50 < min50) min50 = blue50;
6826
color = (max50 - min50) * 1.0 / (max50 + 1); // gray .. color 0 .. 1
6827
color = sqrt(color); // accelerated curve 0 .. 1
6828
bright = max50 / 65535.0; // dark .. bright 0 .. 1
6829
bright = sqrt(bright); // accelerated curve 0 .. 1
6830
ramper = 1 - color + bright * color; // 1 - color * (1 - bright)
6831
ramper = 1.0 / ramper; // large if color high and bright low
6833
red100 = int(red50 * ramper); // 100% color values (max)
6834
green100 = int(green50 * ramper);
6835
blue100 = int(blue50 * ramper);
6839
red3 = rgb0 + (brout) * 0.02 * (red50 - rgb0); // compute new color value
6840
green3 = rgb0 + (brout) * 0.02 * (green50 - rgb0);
6841
blue3 = rgb0 + (brout) * 0.02 * (blue50 - rgb0);
6846
red3 = red50 + (brout - 50) * 0.02 * (red100 - red50);
6847
green3 = green50 + (brout - 50) * 0.02 * (green100 - green50);
6848
blue3 = blue50 + (brout - 50) * 0.02 * (blue100 - blue50);
6852
/* ------------------------------------------------------------------------
6854
color saturation curve values:
6855
0 = no color saturation (gray scale)
6856
50 = normal (initial unmodified RGB)
6857
100 = max. color saturation
6859
50 >> 0: move all RGB values to their mean: (R+G+B)/3
6860
50 >> 100: increase RGB spread until one color is 0 or 65535
6862
In both cases, the average of RGB is not changed.
6865
double rinc, ginc, binc, scale;
6866
double spread, spread1, spread2;
6867
int againlimit = 10;
6869
brout = tune_dat[3][curveindex]; // saturation factor, 0 - 99
6871
if (brout < 49 || brout > 51)
6873
spread = brout - 50; // -50 to 0 to 50
6874
spread1 = 0.02 * spread + 1; // 0 to 1 to 2
6875
spread2 = spread1 - 1.0; // -1 to 0 to 1
6877
red50 = red3; // 50% color values (normal)
6881
rgb0 = (red50 + green50 + blue50 + 1) / 3;
6883
rinc = red50 - rgb0;
6884
ginc = green50 - rgb0;
6885
binc = blue50 - rgb0;
6890
rinc = scale * rinc;
6891
ginc = scale * ginc;
6892
binc = scale * binc;
6894
red100 = red50 + rinc;
6895
green100 = green50 + ginc;
6896
blue100 = blue50 + binc;
6898
if (--againlimit > 0) // prevent loops v.8.5.2
6900
if (red100 > 65535) { scale = (65535.0 - red50) / rinc; goto again; }
6901
if (red100 < 0) { scale = -1.0 * red50 / rinc; goto again; }
6902
if (green100 > 65535) { scale = (65535.0 - green50) / ginc; goto again; }
6903
if (green100 < 0) { scale = -1.0 * green50 / ginc; goto again; }
6904
if (blue100 > 65535) { scale = (65535.0 - blue50) / binc; goto again; }
6905
if (blue100 < 0) { scale = -1.0 * blue50 / binc; goto again; }
6908
if (spread < 0) { // make mid-scale == original RGB
6909
red3 = int(rgb0 + spread1 * (red50 - rgb0));
6910
green3 = int(rgb0 + spread1 * (green50 - rgb0));
6911
blue3 = int(rgb0 + spread1 * (blue50 - rgb0));
6914
red3 = int(red50 + spread2 * (red100 - red50));
6915
green3 = int(green50 + spread2 * (green100 - green50));
6916
blue3 = int(blue50 + spread2 * (blue100 - blue50));
6920
/* ------------------------------------------------------------------------
6922
color balance curve values:
6923
0 = 0.5 * original color
6925
100 = 1.5 * original color, clipped
6928
brout = tune_dat[4][curveindex];
6929
if (brout < 49 || brout > 51) red3 = red3 * 0.01 * (brout + 50);
6930
brout = tune_dat[5][curveindex];
6931
if (brout < 49 || brout > 51) green3 = green3 * 0.01 * (brout + 50);
6932
brout = tune_dat[6][curveindex];
6933
if (brout < 49 || brout > 51) blue3 = blue3 * 0.01 * (brout + 50);
6936
/* ------------------------------------------------------------------------
6938
if working within a select area, blend changes over distance from edge
6944
if (dist < sa_blend) {
6945
dnew = 1.0 * dist / sa_blend;
6947
red3 = dnew * red3 + dold * red1;
6948
green3 = dnew * green3 + dold * green1;
6949
blue3 = dnew * blue3 + dold * blue1;
6952
if (red3 > 65535) red3 = 65535; // clip overflows
6953
if (green3 > 65535) green3 = 65535;
6954
if (blue3 > 65535) blue3 = 65535;
6956
pix3[0] = int(red3);
6957
pix3[1] = int(green3);
6958
pix3[2] = int(blue3);
6964
/**************************************************************************/
6966
// red eye removal function
6968
struct sredmem { // red-eye struct in memory
6969
char type, space[3];
6970
int cx, cy, ww, hh, rad, clicks;
6971
double thresh, tstep;
6973
sredmem redmem[100]; // store up to 100 red-eyes
6975
int Nredmem = 0, maxredmem = 100;
6978
void m_redeye(GtkWidget *, const char *)
6980
void redeye_mousefunc();
6981
int redeye_dialog_compl(zdialog *zd, int zstat);
6983
const char *redeye_message
6985
" Left-click on red-eye to darken.\n"
6987
" Drag down and right to enclose red-eye.\n"
6988
" Left-click on red-eye to darken.\n"
6990
" Right-click on red-eye.");
6992
if (! edit_setup(0,1)) return; // setup edit: no preview
6994
zdedit = zdialog_new(ZTX("Red Eye Reduction"),mWin,Bdone,Bcancel,null);
6995
zdialog_add_widget(zdedit,"label","lab1","dialog",redeye_message);
6996
zdialog_run(zdedit,0,redeye_dialog_compl); // run dialog, parallel mode
6999
mouseCBfunc = redeye_mousefunc; // connect mouse function
7000
Mcapture = 1; // capture mouse clicks
7005
// dialog completion callback function
7007
int redeye_dialog_compl(zdialog *zd, int zstat)
7009
mouseCBfunc = 0; // disconnect mouse
7011
if (Nredmem > 0) Fmodified = 1;
7012
if (zstat == 1) edit_done();
7014
toparc = ptoparc = 0;
7019
// mouse functions to define, darken, and undo red-eyes
7021
int redeye_createF(int px, int py); // create 1-click red-eye (type F)
7022
int redeye_createR(int px, int py, int ww, int hh); // create robust red-eye (type R)
7023
void redeye_darken(int ii); // darken red-eye
7024
void redeye_distr(int ii); // build pixel redness distribution
7025
int redeye_find(int px, int py); // find red-eye at mouse position
7026
void redeye_remove(int ii); // remove red-eye at mouse position
7027
int redeye_radlim(int cx, int cy); // compute red-eye radius limit
7030
void redeye_mousefunc()
7032
int ii, px, py, ww, hh;
7034
if (Nredmem == maxredmem) {
7035
zmessageACK("%d red-eye limit reached",maxredmem); // too many red-eyes
7039
if (LMclick) // left mouse click
7043
px = Mxclick; // click position
7045
if (px < 0 || px > E3ww-1 || py < 0 || py > E3hh-1) return; // outside image area
7047
ii = redeye_find(px,py); // find existing red-eye
7048
if (ii < 0) ii = redeye_createF(px,py); // or create new type F
7049
redeye_darken(ii); // darken red-eye
7052
if (RMclick) // right mouse click
7055
px = Mxclick; // click position
7057
ii = redeye_find(px,py); // find red-eye
7058
if (ii >= 0) redeye_remove(ii); // if found, remove
7061
if (Mxdrag || Mydrag) // mouse drag underway
7063
px = Mxdown; // initial position
7065
ww = Mxdrag - Mxdown; // increment
7066
hh = Mydrag - Mydown;
7067
if (ww < 2 && hh < 2) return;
7070
if (px < 1) px = 1; // keep within image area
7072
if (px + ww > E3ww-1) ww = E3ww-1 - px;
7073
if (py + hh > E3hh-1) hh = E3hh-1 - py;
7074
ii = redeye_find(px,py); // find existing red-eye
7075
if (ii >= 0) redeye_remove(ii); // remove it
7076
ii = redeye_createR(px,py,ww,hh); // create new red-eye type R
7084
// create type F redeye (1-click automatic)
7086
int redeye_createF(int cx, int cy)
7088
int cx0, cy0, cx1, cy1, px, py, rad, radlim;
7090
int Tnpix, Rnpix, R2npix;
7091
double rd, rcx, rcy, redpart;
7092
double Tsum, Rsum, R2sum, Tavg, Ravg, R2avg;
7093
double sumx, sumy, sumr;
7099
for (loops = 0; loops < 8; loops++)
7104
radlim = redeye_radlim(cx,cy); // radius limit (image edge)
7105
Tsum = Tavg = Ravg = Tnpix = 0;
7107
for (rad = 0; rad < radlim-2; rad++) // find red-eye radius from (cx,cy)
7112
for (py = cy-rad-2; py <= cy+rad+2; py++)
7113
for (px = cx-rad-2; px <= cx+rad+2; px++)
7115
rd = sqrt((px-cx)*(px-cx) + (py-cy)*(py-cy));
7116
ppix = bmpixel(E3rgb48,px,py);
7117
redpart = redness(ppix);
7119
if (rd <= rad + 0.5 && rd > rad - 0.5) { // accum. redness at rad
7123
else if (rd <= rad + 2.5 && rd > rad + 1.5) { // accum. redness at rad+2
7131
Tavg = Tsum / Tnpix; // avg. redness over 0-rad
7132
Ravg = Rsum / Rnpix; // avg. redness at rad
7133
R2avg = R2sum / R2npix; // avg. redness at rad+2
7134
if (R2avg > Ravg || Ravg > Tavg) continue;
7135
if ((Ravg - R2avg) < 0.2 * (Tavg - Ravg)) break; // 0.1 --> 0.2 v.8.6
7138
sumx = sumy = sumr = 0;
7139
rad = int(1.2 * rad + 1);
7140
if (rad > radlim) rad = radlim;
7142
for (py = cy-rad; py <= cy+rad; py++) // compute center of gravity for
7143
for (px = cx-rad; px <= cx+rad; px++) // pixels within rad of (cx,cy)
7145
rd = sqrt((px-cx)*(px-cx) + (py-cy)*(py-cy));
7146
if (rd > rad + 0.5) continue;
7147
ppix = bmpixel(E3rgb48,px,py);
7148
redpart = redness(ppix); // weight by redness v.8.6
7149
sumx += redpart * (px - cx);
7150
sumy += redpart * (py - cy);
7154
rcx = cx + 1.0 * sumx / sumr; // new center of red-eye
7155
rcy = cy + 1.0 * sumy / sumr;
7156
if (fabs(cx0 - rcx) > 0.6 * rad) break; // give up if big movement
7157
if (fabs(cy0 - rcy) > 0.6 * rad) break;
7158
cx = int(rcx + 0.5);
7159
cy = int(rcy + 0.5);
7160
if (cx == cx1 && cy == cy1) break; // done if no change
7163
radlim = redeye_radlim(cx,cy);
7164
if (rad > radlim) rad = radlim;
7166
ii = Nredmem++; // add red-eye to memory
7167
redmem[ii].type = 'F';
7170
redmem[ii].rad = rad;
7171
redmem[ii].clicks = 0;
7172
redmem[ii].thresh = 0;
7177
// create type R red-eye (drag an ellipse over red-eye area)
7179
int redeye_createR(int cx, int cy, int ww, int hh)
7183
toparc = 1; // paint ellipse over image
7184
toparcx = cx - ww; // v.8.3
7189
if (ww > hh) rad = ww;
7191
radlim = redeye_radlim(cx,cy);
7192
if (rad > radlim) rad = radlim;
7194
int ii = Nredmem++; // add red-eye to memory
7195
redmem[ii].type = 'R';
7198
redmem[ii].ww = 2 * ww;
7199
redmem[ii].hh = 2 * hh;
7200
redmem[ii].rad = rad;
7201
redmem[ii].clicks = 0;
7202
redmem[ii].thresh = 0;
7207
// darken a red-eye and increase click count
7209
void redeye_darken(int ii)
7211
int cx, cy, ww, hh, px, py, rad, clicks;
7212
double rd, thresh, tstep;
7216
type = redmem[ii].type;
7221
rad = redmem[ii].rad;
7222
thresh = redmem[ii].thresh;
7223
tstep = redmem[ii].tstep;
7224
clicks = redmem[ii].clicks++;
7226
if (thresh == 0) // 1st click
7228
redeye_distr(ii); // get pixel redness distribution
7229
thresh = redmem[ii].thresh; // initial redness threshhold
7230
tstep = redmem[ii].tstep; // redness step size
7234
tstep = (thresh - tstep) / thresh; // convert to reduction factor
7235
thresh = thresh * pow(tstep,clicks); // reduce threshhold by total clicks
7237
for (py = cy-rad; py <= cy+rad; py++) // darken pixels over threshhold
7238
for (px = cx-rad; px <= cx+rad; px++)
7241
if (px < cx - ww/2) continue;
7242
if (px > cx + ww/2) continue;
7243
if (py < cy - hh/2) continue;
7244
if (py > cy + hh/2) continue;
7246
rd = sqrt((px-cx)*(px-cx) + (py-cy)*(py-cy));
7247
if (rd > rad + 0.5) continue;
7248
ppix = bmpixel(E3rgb48,px,py); // set redness = threshhold
7249
if (redness(ppix) > thresh)
7250
ppix[0] = int(thresh * (0.65 * ppix[1] + 0.10 * ppix[2] + 1) / (25 - 0.25 * thresh));
7257
// Build a distribution of redness for a red-eye. Use this information
7258
// to set initial threshhold and step size for stepwise darkening.
7260
void redeye_distr(int ii)
7262
int cx, cy, ww, hh, rad, px, py;
7263
int bin, npix, dbins[20], bsum, blim;
7264
double rd, maxred, minred, redpart, dbase, dstep;
7268
type = redmem[ii].type;
7273
rad = redmem[ii].rad;
7278
for (py = cy-rad; py <= cy+rad; py++)
7279
for (px = cx-rad; px <= cx+rad; px++)
7282
if (px < cx - ww/2) continue;
7283
if (px > cx + ww/2) continue;
7284
if (py < cy - hh/2) continue;
7285
if (py > cy + hh/2) continue;
7287
rd = sqrt((px-cx)*(px-cx) + (py-cy)*(py-cy));
7288
if (rd > rad + 0.5) continue;
7289
ppix = bmpixel(E3rgb48,px,py);
7290
redpart = redness(ppix);
7291
if (redpart > maxred) maxred = redpart;
7292
if (redpart < minred) minred = redpart;
7296
dstep = (maxred - minred) / 19.99;
7298
for (bin = 0; bin < 20; bin++) dbins[bin] = 0;
7301
for (py = cy-rad; py <= cy+rad; py++)
7302
for (px = cx-rad; px <= cx+rad; px++)
7305
if (px < cx - ww/2) continue;
7306
if (px > cx + ww/2) continue;
7307
if (py < cy - hh/2) continue;
7308
if (py > cy + hh/2) continue;
7310
rd = sqrt((px-cx)*(px-cx) + (py-cy)*(py-cy));
7311
if (rd > rad + 0.5) continue;
7312
ppix = bmpixel(E3rgb48,px,py);
7313
redpart = redness(ppix);
7314
bin = int((redpart - dbase) / dstep);
7320
blim = int(0.5 * npix);
7322
for (bin = 0; bin < 20; bin++) // find redness level for 50% of
7323
{ // pixels within red-eye radius
7325
if (bsum > blim) break;
7328
redmem[ii].thresh = dbase + dstep * bin; // initial redness threshhold
7329
redmem[ii].tstep = dstep; // redness step (5% of range) v.6.9
7335
// find a red-eye (nearly) overlapping the mouse click position
7337
int redeye_find(int cx, int cy)
7339
for (int ii = 0; ii < Nredmem; ii++)
7341
if (cx > redmem[ii].cx - 2 * redmem[ii].rad &&
7342
cx < redmem[ii].cx + 2 * redmem[ii].rad &&
7343
cy > redmem[ii].cy - 2 * redmem[ii].rad &&
7344
cy < redmem[ii].cy + 2 * redmem[ii].rad)
7347
return -1; // not found
7351
// remove a red-eye from memory
7353
void redeye_remove(int ii)
7355
int cx, cy, rad, px, py;
7356
uint16 *pix1, *pix3;
7360
rad = redmem[ii].rad;
7362
for (px = cx-rad; px <= cx+rad; px++)
7363
for (py = cy-rad; py <= cy+rad; py++)
7365
pix1 = bmpixel(E1rgb48,px,py);
7366
pix3 = bmpixel(E3rgb48,px,py);
7372
for (ii++; ii < Nredmem; ii++)
7373
redmem[ii-1] = redmem[ii];
7381
// compute red-eye radius limit: smaller of 100 and nearest image edge
7383
int redeye_radlim(int cx, int cy)
7386
if (cx < 100) radlim = cx;
7387
if (E3ww-1 - cx < 100) radlim = E3ww-1 - cx;
7388
if (cy < 100) radlim = cy;
7389
if (E3hh-1 - cy < 100) radlim = E3hh-1 - cy;
7394
/**************************************************************************/
7396
// image blur function
7400
double blur_weight[100][100]; // up to blur radius = 99 v.6.3
7401
int blur_Npixels, blur_pixdone;
7404
void m_blur(GtkWidget *, const char *)
7406
int blur_dialog_event(zdialog *zd, const char *event);
7407
int blur_dialog_compl(zdialog *zd, int zstat);
7408
void * blur_thread(void *);
7410
if (! edit_setup(0,2)) return; // setup edit: no preview
7412
zdedit = zdialog_new(ZTX("Set Blur Radius"),mWin,Bdone,Bcancel,null);
7413
zdialog_add_widget(zdedit,"hbox","hb2","dialog",0,"space=10");
7414
zdialog_add_widget(zdedit,"label","labrad","hb2",ZTX("blur radius"),"space=5");
7415
zdialog_add_widget(zdedit,"spin","radius","hb2","0|99|1|1","space=5");
7416
zdialog_add_widget(zdedit,"button","apply","hb2",Bapply,"space=5");
7418
zdialog_run(zdedit,blur_dialog_event,blur_dialog_compl); // start dialog
7421
start_thread(blur_thread,0); // start working thread
7426
// blur dialog event and completion callback functions
7428
int blur_dialog_compl(zdialog * zd, int zstat)
7430
if (zstat == 1) edit_done(); // done
7431
else edit_cancel(); // cancel or destroy
7436
int blur_dialog_event(zdialog * zd, const char *event)
7438
if (strNeq(event,"apply")) return 0;
7440
zdialog_fetch(zd,"radius",blur_radius); // get blur radius
7442
if (blur_radius == 0) {
7443
if (Fmodified) edit_undo(); // restore original image
7448
signal_thread(); // trigger working thread
7449
wait_thread_idle(); // wait for completion
7455
// image blur thread function
7457
void * blur_thread(void *)
7459
void * blur_wthread(void *arg);
7461
int dx, dy, rad, rad2;
7462
double m, d, w, sum;
7466
thread_idle_loop(); // wait for work or exit request
7471
for (dx = 0; dx <= rad; dx++) // clear weights array
7472
for (dy = 0; dy <= rad; dy++)
7473
blur_weight[dx][dy] = 0;
7475
for (dx = -rad; dx <= rad; dx++) // blur_weight[dx][dy] = no. of pixels
7476
for (dy = -rad; dy <= rad; dy++) // at distance (dx,dy) from center
7477
++blur_weight[abs(dx)][abs(dy)];
7479
m = sqrt(rad2 + rad2); // corner pixel distance from center
7482
for (dx = 0; dx <= rad; dx++) // compute weight of pixel
7483
for (dy = 0; dy <= rad; dy++) // at distance dx, dy
7485
d = sqrt(dx*dx + dy*dy);
7486
w = (m + 1 - d) / m;
7488
sum += blur_weight[dx][dy] * w;
7489
blur_weight[dx][dy] = w;
7492
for (dx = 0; dx <= rad; dx++) // make weights add up to 1.0
7493
for (dy = 0; dy <= rad; dy++)
7494
blur_weight[dx][dy] = blur_weight[dx][dy] / sum;
7496
if (sa_Npixel) blur_Npixels = sa_Npixel;
7497
else blur_Npixels = E3ww * E3hh;
7500
for (int ii = 0; ii < NWthreads; ii++) // start worker threads
7501
start_detached_thread(blur_wthread,&wtindex[ii]);
7502
zadd_locked(blur_busy,+NWthreads);
7504
while (blur_busy) // wait for completion
7507
edit_progress(blur_pixdone,blur_Npixels); // show progress counter
7512
mwpaint2(); // update window
7515
return 0; // not executed, stop g++ warning
7519
void * blur_wthread(void *arg) // worker thread function
7521
void blur_pixel(int px, int py, int dist);
7523
int index = *((int *) arg);
7524
int ii, px, py, dist = 0;
7526
if (! sa_Npixel) // process entire image
7528
for (py = index; py < E3hh-1; py += NWthreads) // loop all image pixels
7529
for (px = 0; px < E3ww-1; px++)
7530
blur_pixel(px,py,dist);
7533
if (sa_Npixel) // process selected area
7535
for (ii = index; ii < sa_Npixel; ii += NWthreads) // process all enclosed pixels
7537
px = sa_pixel[ii].px;
7538
py = sa_pixel[ii].py;
7539
dist = sa_pixel[ii].dist;
7540
blur_pixel(px,py,dist);
7544
zadd_locked(blur_busy,-1);
7549
void blur_pixel(int px, int py, int dist)
7551
int jj, dx, dy, adx, ady, rad;
7552
double red, green, blue;
7553
double weight1, weight2, f1, f2;
7554
uint16 *pix1, *pix3, *pixN;
7556
pix1 = bmpixel(E1rgb48,px,py); // source pixel
7557
pix3 = bmpixel(E3rgb48,px,py); // target pixel
7560
red = green = blue = 0;
7565
for (dy = -rad; dy <= rad; dy++) // loop neighbor pixels within radius
7566
for (dx = -rad; dx <= rad; dx++)
7568
if (px+dx < 0 || px+dx > E3ww-1) continue; // omit pixels off edge v.6.3
7569
if (py+dy < 0 || py+dy > E3hh-1) continue;
7572
pixN = pix1 + (dy * E3ww + dx) * 3;
7573
weight1 = blur_weight[adx][ady]; // weight at distance (dx,dy)
7575
red += pixN[0] * weight1; // accumulate contributions
7576
green += pixN[1] * weight1;
7577
blue += pixN[2] * weight1;
7580
red = red / weight2; // weighted average v.6.3
7581
green = green / weight2;
7582
blue = blue / weight2;
7585
pix3[1] = int(green);
7586
pix3[2] = int(blue);
7591
for (dy = -rad; dy <= rad; dy++) // loop neighbor pixels within radius
7592
for (dx = -rad; dx <= rad; dx++)
7594
if (px+dx < 0 || px+dx > E3ww-1) continue; // omit pixels off edge
7595
if (py+dy < 0 || py+dy > E3hh-1) continue;
7596
jj = (py+dy) * E3ww + (px+dx);
7597
if (! sa_pixisin[jj]) continue; // omit pixels outside area v.6.3
7600
pixN = pix1 + (dy * E3ww + dx) * 3;
7601
weight1 = blur_weight[adx][ady]; // weight at distance (dx,dy)
7603
red += pixN[0] * weight1; // accumulate contributions
7604
green += pixN[1] * weight1;
7605
blue += pixN[2] * weight1;
7608
red = red / weight2; // weighted average
7609
green = green / weight2;
7610
blue = blue / weight2;
7612
if (dist < sa_blend) { // blend changes over sa_blend
7613
f1 = 1.0 * dist / sa_blend;
7615
red = f1 * red + f2 * pix1[0];
7616
green = f1 * green + f2 * pix1[1];
7617
blue = f1 * blue + f2 * pix1[2];
7621
pix3[1] = int(green);
7622
pix3[2] = int(blue);
7630
/**************************************************************************/
7632
// image sharpening function
7634
int sharp_ED_cycles;
7635
int sharp_ED_reduce;
7636
int sharp_ED_thresh;
7637
int sharp_UM_radius;
7638
int sharp_UM_amount;
7639
int sharp_UM_thresh;
7640
int sharp_LP_radius;
7641
int sharp_LP_amount;
7642
double sharp_kernel[19][19];
7643
char sharp_function[4];
7645
int sharp_Npixels, sharp_Ndone;
7648
void m_sharpen(GtkWidget *, const char *)
7650
int sharp_dialog_event(zdialog *zd, const char *event);
7651
int sharp_dialog_compl(zdialog *zd, int zstat);
7652
void * sharp_thread(void *);
7654
if (! edit_setup(0,2)) return; // setup edit: no preview
7656
zdedit = zdialog_new(ZTX("Sharpen Image"),mWin,Bundo,Bdone,Bcancel,null);
7658
zdialog_add_widget(zdedit,"hbox","hb2","dialog",0,"space=5");
7659
zdialog_add_widget(zdedit,"vbox","vb21","hb2",0,"space=5");
7660
zdialog_add_widget(zdedit,"vbox","vb22","hb2",0,"homog|space=5");
7661
zdialog_add_widget(zdedit,"vbox","vb23","hb2",0,"homog|space=5");
7662
zdialog_add_widget(zdedit,"button","ED","vb21",ZTX("edge detection"),"space=5");
7663
zdialog_add_widget(zdedit,"label","lab21","vb22",ZTX("cycles"));
7664
zdialog_add_widget(zdedit,"label","lab22","vb22",ZTX("reduce"));
7665
zdialog_add_widget(zdedit,"label","lab23","vb22",ZTX("threshold"));
7666
zdialog_add_widget(zdedit,"spin","cyclesED","vb23","1|30|1|10");
7667
zdialog_add_widget(zdedit,"spin","reduceED","vb23","50|95|1|80");
7668
zdialog_add_widget(zdedit,"spin","threshED","vb23","1|99|1|1");
7670
zdialog_add_widget(zdedit,"hsep","sep4","dialog");
7671
zdialog_add_widget(zdedit,"hbox","hb4","dialog",0,"space=5");
7672
zdialog_add_widget(zdedit,"vbox","vb41","hb4",0,"space=5");
7673
zdialog_add_widget(zdedit,"vbox","vb42","hb4",0,"homog|space=5");
7674
zdialog_add_widget(zdedit,"vbox","vb43","hb4",0,"homog|space=5");
7675
zdialog_add_widget(zdedit,"button","UM","vb41",ZTX("unsharp mask"),"space=5");
7676
zdialog_add_widget(zdedit,"label","lab41","vb42",ZTX("radius"));
7677
zdialog_add_widget(zdedit,"label","lab42","vb42",ZTX("amount"));
7678
zdialog_add_widget(zdedit,"label","lab43","vb42",ZTX("threshold"));
7679
zdialog_add_widget(zdedit,"spin","radiusUM","vb43","1|9|1|2");
7680
zdialog_add_widget(zdedit,"spin","amountUM","vb43","1|200|1|100");
7681
zdialog_add_widget(zdedit,"spin","threshUM","vb43","1|99|1|1");
7683
zdialog_add_widget(zdedit,"hsep","sep5","dialog");
7684
zdialog_add_widget(zdedit,"hbox","hb5","dialog",0,"space=5");
7685
zdialog_add_widget(zdedit,"vbox","vb51","hb5",0,"space=5");
7686
zdialog_add_widget(zdedit,"vbox","vb52","hb5",0,"homog|space=5");
7687
zdialog_add_widget(zdedit,"vbox","vb53","hb5",0,"homog|space=5");
7688
zdialog_add_widget(zdedit,"button","LP","vb51","Laplacian","space=5");
7689
zdialog_add_widget(zdedit,"label","lab51","vb52",ZTX("radius"));
7690
zdialog_add_widget(zdedit,"label","lab52","vb52",ZTX("amount"));
7691
zdialog_add_widget(zdedit,"spin","radiusLP","vb53","1|9|1|1");
7692
zdialog_add_widget(zdedit,"spin","amountLP","vb53","1|100|1|50");
7694
zdialog_run(zdedit,sharp_dialog_event,sharp_dialog_compl); // run dialog, parallel
7695
*sharp_function = 0;
7696
start_thread(sharp_thread,0); // start working thread
7701
// sharpen dialog event and completion callback functions
7703
int sharp_dialog_compl(zdialog *zd, int zstat)
7705
if (zstat == 1) edit_undo();
7706
else if (zstat == 2) edit_done();
7712
int sharp_dialog_event(zdialog *zd, const char *event)
7714
edit_undo(); // restore original image
7716
if (strcmpv(event,"ED","UM","LP",null))
7718
zdialog_fetch(zd,"cyclesED",sharp_ED_cycles); // get all input values
7719
zdialog_fetch(zd,"reduceED",sharp_ED_reduce);
7720
zdialog_fetch(zd,"threshED",sharp_ED_thresh);
7721
zdialog_fetch(zd,"radiusUM",sharp_UM_radius);
7722
zdialog_fetch(zd,"amountUM",sharp_UM_amount);
7723
zdialog_fetch(zd,"threshUM",sharp_UM_thresh);
7724
zdialog_fetch(zd,"radiusLP",sharp_LP_radius);
7725
zdialog_fetch(zd,"amountLP",sharp_LP_amount);
7727
strcpy(sharp_function,event); // pass to working thread
7736
// sharpen image thread function
7738
void * sharp_thread(void *)
7746
thread_idle_loop(); // wait for work or exit request
7748
if (strEqu(sharp_function,"ED")) sharp_ED();
7749
if (strEqu(sharp_function,"UM")) sharp_UM(); // do requested function
7750
if (strEqu(sharp_function,"LP")) sharp_LP();
7756
return 0; // not executed, stop g++ warning
7760
// image sharpen function by edge detection and compression
7764
void sharp_pixel_ED(int px, int py, int dist, int thresh);
7766
int sharp_thresh1 = 100; // initial threshold
7767
double sharp_thresh2 = 0.01 * sharp_ED_reduce; // decline rate
7768
int px, py, dist, thresh;
7771
thresh = sharp_thresh1;
7773
for (cycles = 0; cycles < sharp_ED_cycles; cycles++)
7775
if (cycles > 0) thresh = int(thresh * sharp_thresh2);
7777
if (! sa_Npixel) // process entire image
7779
dist = sa_blend = 0;
7780
for (py = 2; py < E3hh-2; py++)
7781
for (px = 2; px < E3ww-2; px++) // loop all pixels
7782
sharp_pixel_ED(px,py,dist,thresh);
7785
if (sa_Npixel) // selected area to sharpen
7787
for (ii = 0; ii < sa_Npixel; ii++) // process all enclosed pixels
7789
px = sa_pixel[ii].px;
7790
py = sa_pixel[ii].py;
7791
dist = sa_pixel[ii].dist;
7792
if (px < 2 || px >= E3ww-2) continue; // omit pixels on edge
7793
if (py < 2 || py >= E3hh-2) continue;
7794
sharp_pixel_ED(px,py,dist,thresh);
7798
edit_progress(cycles,sharp_ED_cycles); // v.6.3
7806
void sharp_pixel_ED(int px, int py, int dist, int thresh)
7808
uint16 *pix1, *pix1u, *pix1d;
7809
uint16 *pix3, *pix3u, *pix3d, *pix3uu, *pix3dd;
7810
int dd, rgb, pthresh;
7811
int dx[4] = { -1, 0, 1, 1 }; // 4 directions: NW N NE E
7812
int dy[4] = { -1, -1, -1, 0 };
7813
int pv2, pv2u, pv2d, pv2uu, pv2dd, pvdiff;
7816
pthresh = sharp_ED_thresh; // pthresh = larger
7817
if (thresh > pthresh) pthresh = thresh;
7819
pix1 = bmpixel(E1rgb48,px,py); // input pixel
7820
pix3 = bmpixel(E3rgb48,px,py); // output pixel
7822
for (dd = 0; dd < 4; dd++) // 4 directions
7824
pix3u = pix3 + (dy[dd] * E3ww + dx[dd]) * 3; // upstream pixel
7825
pix3d = pix3 - (dy[dd] * E3ww - dx[dd]) * 3; // downstream pixel
7827
for (rgb = 0; rgb < 3; rgb++) // loop 3 RGB colors
7830
pv2u = pix3u[rgb]; // brightness difference
7831
pv2d = pix3d[rgb]; // across target pixel
7833
pvdiff = pv2d - pv2u;
7834
if (pvdiff < 0) pvdiff = -pvdiff;
7835
if (pvdiff < 256 * pthresh) continue; // brightness slope < threshold
7837
if (pv2u < pv2 && pv2 < pv2d) // slope up, monotone
7839
pix3uu = pix3u + (dy[dd] * E3ww + dx[dd]) * 3; // upstream of upstream pixel
7840
pix3dd = pix3d - (dy[dd] * E3ww - dx[dd]) * 3; // downstream of downstream
7841
pv2uu = pix3uu[rgb];
7842
pv2dd = pix3dd[rgb];
7844
if (pv2uu >= pv2u) { // shift focus of changes to
7845
pix3u = pix3; // avoid up/down/up jaggies
7849
if (pv2dd <= pv2d) {
7854
if (pv2u > 256) pv2u -= 256;
7855
if (pv2d < 65279) pv2d += 256;
7858
else if (pv2u > pv2 && pv2 > pv2d) // slope down, monotone
7860
pix3uu = pix3u + (dy[dd] * E3ww + dx[dd]) * 3;
7861
pix3dd = pix3d - (dy[dd] * E3ww - dx[dd]) * 3;
7862
pv2uu = pix3uu[rgb];
7863
pv2dd = pix3dd[rgb];
7865
if (pv2uu <= pv2u) {
7870
if (pv2dd >= pv2d) {
7875
if (pv2d > 256) pv2d -= 256;
7876
if (pv2u < 65279) pv2u += 256;
7879
else continue; // slope too small
7881
if (sa_Npixel && dist < sa_blend) { // if area selection, blend pixel
7882
f1 = 1.0 * dist / sa_blend; // changes over sa_blend
7884
pix1u = pix1 + (dy[dd] * E1ww + dx[dd]) * 3; // upstream input pixel bugfix
7885
pix1d = pix1 - (dy[dd] * E1ww - dx[dd]) * 3; // downstream input pixel v.7.2.1
7886
pv2u = int(f1 * pv2u + f2 * pix1u[rgb]);
7887
pv2d = int(f1 * pv2d + f2 * pix1d[rgb]);
7890
pix3u[rgb] = pv2u; // modified brightness values
7891
pix3d[rgb] = pv2d; // >> image3 pixel
7899
// image sharpen function using unsharp mask
7903
void * sharp_UM_wthread(void *arg);
7905
if (sa_Npixel) sharp_Npixels = sa_Npixel;
7906
else sharp_Npixels = E3ww * E3hh;
7909
for (int ii = 0; ii < NWthreads; ii++) // start worker threads
7910
start_detached_thread(sharp_UM_wthread,&wtindex[ii]);
7911
zadd_locked(sharp_busy,+NWthreads);
7913
while (sharp_busy) // wait for completion
7916
edit_progress(sharp_Ndone,sharp_Npixels); // show progress counter
7924
void * sharp_UM_wthread(void *arg) // worker thread function v.7.7
7926
void sharp_pixel_UM(int px, int py, int dist);
7928
int index = *((int *) arg);
7929
int ii, px, py, dist, rad;
7931
rad = sharp_UM_radius;
7933
if (! sa_Npixel) // process entire image
7935
dist = sa_blend = 0;
7936
for (py = index+rad; py < E3hh-rad; py += NWthreads)
7937
for (px = rad; px < E3ww-rad; px++) // loop all image3 pixels
7939
sharp_pixel_UM(px,py,dist);
7944
if (sa_Npixel) // selected area to sharpen
7946
for (ii = index; ii < sa_Npixel; ii += NWthreads) // process all enclosed pixels
7948
px = sa_pixel[ii].px;
7949
py = sa_pixel[ii].py;
7950
dist = sa_pixel[ii].dist;
7951
if (px < rad || px >= E3ww-rad) continue; // omit pixels on edge
7952
if (py < rad || py >= E3hh-rad) continue;
7953
sharp_pixel_UM(px,py,dist);
7958
zadd_locked(sharp_busy,-1);
7963
void sharp_pixel_UM(int px, int py, int dist) // process one pixel
7965
int rad, dx, dy, rgb;
7966
int incr, cval, mean;
7967
uint16 *pix1, *pix3, *pixN;
7968
double rad2, f1, f2;
7970
pix1 = bmpixel(E1rgb48,px,py); // input pixel
7971
pix3 = bmpixel(E3rgb48,px,py); // output pixel
7973
rad = sharp_UM_radius;
7975
rad2 = 1.0 / (rad2 * rad2); // 1 / area of unsharp mask
7977
for (rgb = 0; rgb < 3; rgb++) // loop 3 RGB colors
7981
for (dy = -rad; dy <= rad; dy++) // loop neighbor pixels within radius
7982
for (dx = -rad; dx <= rad; dx++) // outer loop y
7984
pixN = pix1 + (dy * E3ww + dx) * 3; // compute mean brightness
7988
mean = int(mean * rad2 + 0.5);
7990
cval = pix3[rgb]; // pixel brightness
7991
incr = cval - mean; // - mean of neighbors
7992
if (abs(incr) < sharp_UM_thresh) continue; // if < threshold, skip
7993
incr = incr * sharp_UM_amount / 100; // reduce to sharp_amount
7994
cval = cval + incr; // add back to pixel
7995
if (cval < 0) cval = 0;
7996
if (cval > 65535) cval = 65535;
7998
if (sa_Npixel && dist < sa_blend) { // if area selection, blend pixel
7999
f1 = 1.0 * dist / sa_blend; // changes over sa_blend
8001
cval = int(f1 * cval + f2 * pix1[rgb]);
8011
// image sharpen function using Lapacian method
8015
void * sharp_LP_wthread(void *arg);
8017
int rad, ii, jj, dx, dy;
8018
double rad2, kern, kernsum;
8020
rad = sharp_LP_radius;
8024
for (dy = -rad; dy <= rad; dy++) // kernel, Gaussian distribution
8025
for (dx = -rad; dx <= rad; dx++)
8029
kern = (0.5 / rad2) * exp( -(dx*dx + dy*dy) / (2 * rad2));
8031
sharp_kernel[ii][jj] = -kern; // surrounding cells < 0
8036
sharp_kernel[rad][rad] = kernsum + 1.0; // middle cell, total = +1
8038
if (sa_Npixel) sharp_Npixels = sa_Npixel;
8039
else sharp_Npixels = E3ww * E3hh;
8042
for (int ii = 0; ii < NWthreads; ii++) // start worker threads
8043
start_detached_thread(sharp_LP_wthread,&wtindex[ii]);
8044
zadd_locked(sharp_busy,+NWthreads);
8046
while (sharp_busy) // wait for completion
8049
edit_progress(sharp_Ndone,sharp_Npixels); // show progress counter
8057
void * sharp_LP_wthread(void *arg) // worker thread function v.7.7
8059
void sharp_pixel_LP(int px, int py, int dist);
8061
int index = *((int *) arg);
8062
int ii, rad, px, py, dist;
8064
rad = sharp_LP_radius;
8066
if (! sa_Npixel) // process entire image
8068
dist = sa_blend = 0;
8069
for (py = index+rad; py < E3hh-rad; py += NWthreads)
8070
for (px = rad; px < E3ww-rad; px++) // loop all image3 pixels
8072
sharp_pixel_LP(px,py,dist);
8077
if (sa_Npixel) // selected area to sharpen
8079
for (ii = index; ii < sa_Npixel; ii += NWthreads) // process all enclosed pixels
8081
px = sa_pixel[ii].px;
8082
py = sa_pixel[ii].py;
8083
dist = sa_pixel[ii].dist;
8084
if (px < rad || px >= E3ww-rad) continue; // omit pixels on edge
8085
if (py < rad || py >= E3hh-rad) continue;
8086
sharp_pixel_LP(px,py,dist);
8091
zadd_locked(sharp_busy,-1);
8096
void sharp_pixel_LP(int px, int py, int dist) // process one pixel
8098
uint16 *pix1, *pix3, *pixN;
8100
int rad, dx, dy, rgb;
8101
int sumpix[3], maxrgb;
8102
double kern, f1, f2;
8103
double scale, scale1, scale2;
8105
pix1 = bmpixel(E1rgb48,px,py); // input pixel
8106
pix3 = bmpixel(E3rgb48,px,py); // output pixel
8108
rad = sharp_LP_radius;
8109
sumpix[0] = sumpix[1] = sumpix[2] = 0;
8111
for (dy = -rad; dy <= rad; dy++) // loop surrounding block of pixels
8112
for (dx = -rad; dx <= rad; dx++) // outer loop y
8114
pixN = pix1 + (dy * E3ww + dx) * 3;
8115
kern = sharp_kernel[dx+rad][dy+rad];
8116
for (rgb = 0; rgb < 3; rgb++)
8117
sumpix[rgb] += int(kern * pixN[rgb] - 0.5); // round
8121
if (sumpix[1] > maxrgb) maxrgb = sumpix[1];
8122
if (sumpix[2] > maxrgb) maxrgb = sumpix[2];
8123
if (sumpix[0] < 0) sumpix[0] = 0;
8124
if (sumpix[1] < 0) sumpix[1] = 0;
8125
if (sumpix[2] < 0) sumpix[2] = 0;
8127
if (maxrgb > 65535) scale = 65535.0 / maxrgb;
8129
scale1 = (100 - sharp_LP_amount) / 100.0;
8130
scale2 = (1.0 - scale1) * scale;
8132
pix3[0] = int(scale1 * pix1[0] + scale2 * sumpix[0]);
8133
pix3[1] = int(scale1 * pix1[1] + scale2 * sumpix[1]);
8134
pix3[2] = int(scale1 * pix1[2] + scale2 * sumpix[2]);
8136
if (sa_Npixel && dist < sa_blend) { // if area selection, blend pixel
8137
f1 = 1.0 * dist / sa_blend; // changes over sa_blend
8139
pix3[0] = int(f1 * pix3[0] + f2 * pix1[0]);
8140
pix3[1] = int(f1 * pix3[1] + f2 * pix1[1]);
8141
pix3[2] = int(f1 * pix3[2] + f2 * pix1[2]);
8148
/**************************************************************************/
8150
// image noise reduction
8152
int denoise_method = 5;
8153
int denoise_radius = 4;
8155
int denoise_Npixels, denoise_Ndone;
8157
void m_denoise(GtkWidget *, const char *)
8159
int denoise_dialog_event(zdialog *zd, const char *event); // dialog event function
8160
int denoise_dialog_compl(zdialog *zd, int zstat); // dialog completion function
8161
void * denoise_thread(void *);
8163
const char *denoise_message = ZTX(" Press the reduce button to \n"
8164
" reduce noise in small steps. \n"
8165
" Use undo to start over.");
8167
if (! edit_setup(0,2)) return; // setup edit: no preview
8169
zdedit = zdialog_new(ZTX("Noise Reduction"),mWin,Bdone,Bcancel,null);
8170
zdialog_add_widget(zdedit,"label","lab1","dialog",denoise_message,"space=5");
8171
zdialog_add_widget(zdedit,"hbox","hb1","dialog",0,"space=5");
8172
zdialog_add_widget(zdedit,"label","labalg","hb1",ZTX("algorithm"),"space=5");
8173
zdialog_add_widget(zdedit,"combo","method","hb1",0,"space=5|expand");
8174
zdialog_add_widget(zdedit,"hbox","hb2","dialog",0,"space=5");
8175
zdialog_add_widget(zdedit,"label","labrad","hb2",ZTX("radius"),"space=5");
8176
zdialog_add_widget(zdedit,"spin","radius","hb2","1|9|1|4","space=5");
8177
zdialog_add_widget(zdedit,"hbox","hb3","dialog",0,"space=5");
8178
zdialog_add_widget(zdedit,"button","reduce","hb3",Breduce,"space=5");
8179
zdialog_add_widget(zdedit,"button","Undo","hb3",Bundo,"space=5");
8181
zdialog_cb_app(zdedit,"method",ZTX("flatten outliers by color (1)"));
8182
zdialog_cb_app(zdedit,"method",ZTX("flatten outliers by color (2)"));
8183
zdialog_cb_app(zdedit,"method",ZTX("set median brightness by color"));
8184
zdialog_cb_app(zdedit,"method",ZTX("top hat filter by color"));
8186
zdialog_stuff(zdedit,"method",ZTX("top hat filter by color")); // default
8188
zdialog_run(zdedit,denoise_dialog_event,denoise_dialog_compl); // run dialog
8189
start_thread(denoise_thread,0); // start working thread
8194
// denoise dialog event and completion callback functions
8196
int denoise_dialog_compl(zdialog *zd, int zstat)
8198
if (zstat == 1) edit_done(); // done
8199
else edit_cancel(); // cancel or destroy
8204
int denoise_dialog_event(zdialog * zd, const char *event)
8208
if (strEqu(event,"radius"))
8209
zdialog_fetch(zd,"radius",denoise_radius);
8211
if (strEqu(event,"method"))
8213
zdialog_fetch(zd,"method",method,39);
8215
if (strEqu(method,"flatten outliers by color (1)")) {
8220
if (strEqu(method,"flatten outliers by color (2)")) {
8225
if (strEqu(method,"set median brightness by color")) {
8230
if (strEqu(method,"top hat filter by color")) {
8235
zdialog_stuff(zd,"radius",denoise_radius);
8238
if (strEqu(event,"reduce")) {
8239
signal_thread(); // trigger update thread
8240
wait_thread_idle(); // wait for thread done
8243
if (strEqu(event,"Undo")) edit_undo();
8248
// image noise reduction thread
8250
void * denoise_thread(void *)
8252
void * denoise_wthread(void *arg);
8256
thread_idle_loop(); // wait for work or exit request
8258
E9rgb48 = RGB_copy(E3rgb48); // image3 is reference source
8259
// image9 will be modified
8260
if (sa_Npixel) denoise_Npixels = sa_Npixel;
8261
else denoise_Npixels = E3ww * E3hh;
8264
for (int ii = 0; ii < NWthreads; ii++) // start worker threads
8265
start_detached_thread(denoise_wthread,&wtindex[ii]);
8266
zadd_locked(denoise_busy,+NWthreads);
8268
while (denoise_busy) // wait for completion
8271
edit_progress(denoise_Ndone,denoise_Npixels); // show progress counter
8276
mutex_lock(&pixmaps_lock);
8277
RGB_free(E3rgb48); // image9 >> image3
8280
mutex_unlock(&pixmaps_lock);
8283
mwpaint2(); // update window
8286
return 0; // not executed, stop g++ warning
8290
void * denoise_wthread(void *arg) // worker thread function v.7.7
8292
void denoise_func1(uint16 *pix3, uint16 *pix9);
8293
void denoise_func2(uint16 *pix3, uint16 *pix9);
8294
void denoise_func4(uint16 *pix3, uint16 *pix9);
8295
void denoise_func5(uint16 *pix3, uint16 *pix9);
8297
int index = *((int *) arg);
8298
int ii, px, py, dist, rad;
8300
uint16 *pix1, *pix3, *pix9;
8302
rad = denoise_radius;
8306
for (ii = index; ii < sa_Npixel; ii += NWthreads) // process pixels in select area
8308
px = sa_pixel[ii].px;
8309
py = sa_pixel[ii].py;
8310
dist = sa_pixel[ii].dist;
8311
if (px < rad || px >= E3ww-rad) continue;
8312
if (py < rad || py >= E3hh-rad) continue;
8313
pix1 = bmpixel(E1rgb48,px,py); // source pixel
8314
pix3 = bmpixel(E3rgb48,px,py); // source pixel
8315
pix9 = bmpixel(E9rgb48,px,py); // target pixel
8316
if (denoise_method == 1) denoise_func1(pix3,pix9);
8317
if (denoise_method == 2) denoise_func2(pix3,pix9);
8318
if (denoise_method == 4) denoise_func4(pix3,pix9);
8319
if (denoise_method == 5) denoise_func5(pix3,pix9);
8321
if (dist < sa_blend) { // blend changes over sa_blend
8322
f1 = 1.0 * dist / sa_blend;
8324
pix9[0] = int(f1 * pix9[0] + f2 * pix1[0]);
8325
pix9[1] = int(f1 * pix9[1] + f2 * pix1[1]);
8326
pix9[2] = int(f1 * pix9[2] + f2 * pix1[2]);
8335
for (py = index+rad; py < E3hh-rad; py += NWthreads) // loop all image3 pixels
8336
for (px = rad; px < E3ww-rad; px++)
8338
pix3 = bmpixel(E3rgb48,px,py); // source pixel
8339
pix9 = bmpixel(E9rgb48,px,py); // target pixel
8340
if (denoise_method == 1) denoise_func1(pix3,pix9);
8341
if (denoise_method == 2) denoise_func2(pix3,pix9);
8342
if (denoise_method == 4) denoise_func4(pix3,pix9);
8343
if (denoise_method == 5) denoise_func5(pix3,pix9);
8348
zadd_locked(denoise_busy,-1);
8353
// flatten outliers within radius, by color
8354
// an outlier is the max or min value within a radius
8356
void denoise_func1(uint16 *pix3, uint16 *pix9)
8359
int min0, min1, min2, max0, max1, max2;
8362
min0 = min1 = min2 = 65535;
8363
max0 = max1 = max2 = 0;
8364
rad = denoise_radius;
8366
for (dy = -rad; dy <= rad; dy++) // loop surrounding pixels
8367
for (dx = -rad; dx <= rad; dx++)
8369
if (dy == 0 && dx == 0) continue; // skip self
8371
pixN = pix3 + (dy * E3ww + dx) * 3;
8372
if (pixN[0] < min0) min0 = pixN[0]; // find min and max per color
8373
if (pixN[0] > max0) max0 = pixN[0];
8374
if (pixN[1] < min1) min1 = pixN[1];
8375
if (pixN[1] > max1) max1 = pixN[1];
8376
if (pixN[2] < min2) min2 = pixN[2];
8377
if (pixN[2] > max2) max2 = pixN[2];
8380
if (pix3[0] <= min0 && min0 < 65279) pix9[0] = min0 + 256; // if outlier, flatten a little
8381
if (pix3[0] >= max0 && max0 > 256) pix9[0] = max0 - 256;
8382
if (pix3[1] <= min1 && min1 < 65279) pix9[1] = min1 + 256;
8383
if (pix3[1] >= max1 && max1 > 256) pix9[1] = max1 - 256;
8384
if (pix3[2] <= min2 && min2 < 65279) pix9[2] = min2 + 256;
8385
if (pix3[2] >= max2 && max2 > 256) pix9[2] = max2 - 256;
8392
// An outlier pixel has an RGB value outside one sigma of
8393
// the mean for all pixels within a given radius of the pixel.
8395
void denoise_func2(uint16 *pix3, uint16 *pix9) // v.8.5
8397
int rgb, dy, dx, rad, nn;
8398
double nn1, val, sum, sum2, mean, variance, sigma;
8401
rad = denoise_radius;
8406
for (rgb = 0; rgb < 3; rgb++) // loop RGB color
8410
for (dy = -rad; dy <= rad; dy++) // loop surrounding pixels
8411
for (dx = -rad; dx <= rad; dx++)
8413
if (dy == 0 && dx == 0) continue; // skip self
8414
pixN = pix3 + (dy * E3ww + dx) * 3;
8421
variance = nn1 * (sum2 - 2.0 * mean * sum) + mean * mean;
8422
sigma = sqrt(variance);
8425
if (val > mean + sigma) { // move value to mean +/- sigma
8426
val = mean + sigma; // v.8.6
8429
else if (val < mean - sigma) {
8439
// use median brightness for pixels within radius
8441
void denoise_func4(uint16 *pix3, uint16 *pix9)
8444
int ns, rgb, bsortN[400];
8447
rad = denoise_radius;
8449
for (rgb = 0; rgb < 3; rgb++) // loop all RGB colors
8453
for (dy = -rad; dy <= rad; dy++) // loop surrounding pixels
8454
for (dx = -rad; dx <= rad; dx++) // get brightness values
8456
pixN = pix3 + (dy * E3ww + dx) * 3;
8457
bsortN[ns] = pixN[rgb];
8461
HeapSort(bsortN,ns);
8462
pix9[rgb] = bsortN[ns/2]; // median brightness of ns pixels
8469
// modified top hat filter: execute with increasing radius from 1 to limit
8470
// detect outlier by comparing with pixels in outer radius
8472
void denoise_func5(uint16 *pix3, uint16 *pix9)
8475
int min0, min1, min2, max0, max1, max2;
8478
for (rad = 1; rad <= denoise_radius; rad++)
8479
for (int loops = 0; loops < 2; loops++)
8481
min0 = min1 = min2 = 65535;
8482
max0 = max1 = max2 = 0;
8484
for (dy = -rad; dy <= rad; dy++) // loop all pixels within rad
8485
for (dx = -rad; dx <= rad; dx++)
8487
if (dx > -rad && dx < rad) continue; // skip inner pixels
8488
if (dy > -rad && dy < rad) continue;
8490
pixN = pix3 + (dy * E3ww + dx) * 3;
8491
if (pixN[0] < min0) min0 = pixN[0]; // find min and max per color
8492
if (pixN[0] > max0) max0 = pixN[0]; // among outermost pixels
8493
if (pixN[1] < min1) min1 = pixN[1];
8494
if (pixN[1] > max1) max1 = pixN[1];
8495
if (pixN[2] < min2) min2 = pixN[2];
8496
if (pixN[2] > max2) max2 = pixN[2];
8499
if (pix3[0] < min0 && pix9[0] < 65279) pix9[0] += 256; // if central pixel is outlier,
8500
if (pix3[0] > max0 && pix9[0] > 256) pix9[0] -= 256; // moderate its values
8501
if (pix3[1] < min1 && pix9[1] < 65279) pix9[1] += 256;
8502
if (pix3[1] > max1 && pix9[1] > 256) pix9[1] -= 256;
8503
if (pix3[2] < min2 && pix9[2] < 65279) pix9[2] += 256;
8504
if (pix3[2] > max2 && pix9[2] > 256) pix9[2] -= 256;
8511
/**************************************************************************/
8513
// trim image - use mouse to select image region to retain
8515
int trimx1, trimy1, trimx2, trimy2; // trim rectangle
8517
double trimR; // trim aspect ratio
8518
int trim_status = 0;
8521
void m_trim(GtkWidget *, const char *)
8523
void trim_mousefunc();
8524
int trim_dialog_compl(zdialog *zd, int zstat);
8525
void * trim_thread(void *);
8527
const char *trim_message = ZTX("Drag middle to move \n"
8528
"Drag corners to resize");
8531
if (! edit_setup(1,0)) return; // setup edit: use preview v.8.4.1
8533
zdedit = zdialog_new(ZTX("Trim Image"),mWin,ZTX("Trim"),Bcancel,null); // dialog to get user inuts
8534
zdialog_add_widget(zdedit,"label","lab1","dialog",trim_message,"space=10");
8535
zdialog_add_widget(zdedit,"hbox","hb1","dialog");
8536
zdialog_add_widget(zdedit,"label","space","hb1",0,"space=10");
8537
zdialog_add_widget(zdedit,"label","labwhr","hb1","2345 x 1234 (R=1.90)");
8538
zdialog_add_widget(zdedit,"hbox","hb2","dialog");
8539
zdialog_add_widget(zdedit,"label","space","hb2",0,"space=10");
8540
zdialog_add_widget(zdedit,"check","lockwh","hb2",ZTX("Lock Ratio")); // v.8.4
8542
zdialog_run(zdedit,0,trim_dialog_compl); // run dialog, parallel
8544
mouseCBfunc = trim_mousefunc; // connect mouse function
8547
trimx1 = int(0.2 * E3ww); // start with 20% trim margins
8548
trimy1 = int(0.2 * E3hh);
8549
trimx2 = int(0.8 * E3ww);
8550
trimy2 = int(0.8 * E3hh);
8552
trimww = (trimx2 - trimx1);
8553
trimhh = (trimy2 - trimy1);
8554
trimR = 1.0 * trimww / trimhh;
8556
snprintf(text,39,"%d x %d (R=%.2f)", trimww * Fww / E3ww, // stuff dialog poop
8557
trimhh * Fhh / E3hh, trimR); // (final size) v.8.4.1
8558
zdialog_stuff(zdedit,"labwhr",text);
8561
start_thread(trim_thread,0); // start working thread
8567
// trim dialog completion callback function
8569
int trim_dialog_compl(zdialog *zd, int zstat)
8571
mouseCBfunc = 0; // disconnect mouse
8579
trimx1 = trimx1 * Fww / E3ww; // scale from preview size
8580
trimy1 = trimy1 * Fhh / E3hh; // to final size v.8.4.1
8581
trimx2 = trimx2 * Fww / E3ww;
8582
trimy2 = trimy2 * Fhh / E3hh;
8583
trimww = trimx2 - trimx1;
8584
trimhh = trimy2 - trimy1;
8586
trim_status = 1; // trim full image
8593
// trim mouse function // overhauled v.8.4.1
8595
void trim_mousefunc()
8597
int mpx, mpy, rlock;
8598
int corner, moveall = 0;
8599
int dx, dy, dd, d1, d2, d3, d4;
8603
if (LMclick || Mxdrag || Mydrag) // mouse click or drag
8615
if (Mxdrag || Mydrag) {
8617
dd = 0.1 * (trimx2 - trimx1); // test if mouse is in the broad
8618
if (mpx < trimx1 + dd) moveall = 0; // middle of the rectangle
8619
if (mpx > trimx2 - dd) moveall = 0;
8620
dd = 0.1 * (trimy2 - trimy1);
8621
if (mpy < trimy1 + dd) moveall = 0;
8622
if (mpy > trimy2 - dd) moveall = 0;
8625
if (moveall) { // yes, move the whole rectangle
8626
trimx1 += Mxdrag - Mxdown;
8627
trimx2 += Mxdrag - Mxdown;
8628
trimy1 += Mydrag - Mydown;
8629
trimy2 += Mydrag - Mydown;
8630
Mxdown = Mxdrag; // reset drag origin
8635
else { // no, find closest corner
8638
d1 = sqrt(dx*dx + dy*dy);
8642
d2 = sqrt(dx*dx + dy*dy);
8646
d3 = sqrt(dx*dx + dy*dy);
8650
d4 = sqrt(dx*dx + dy*dy);
8654
if (d2 < dd) { corner = 2; dd = d2; }
8655
if (d3 < dd) { corner = 3; dd = d3; }
8656
if (d4 < dd) { corner = 4; dd = d4; }
8658
if (corner == 1) { trimx1 = mpx; trimy1 = mpy; } // move this corner to mouse
8659
if (corner == 2) { trimx2 = mpx; trimy1 = mpy; }
8660
if (corner == 3) { trimx2 = mpx; trimy2 = mpy; }
8661
if (corner == 4) { trimx1 = mpx; trimy2 = mpy; }
8664
if (trimx1 > trimx2-10) trimx1 = trimx2-10; // sanity limits
8665
if (trimy1 > trimy2-10) trimy1 = trimy2-10;
8666
if (trimx1 < 0) trimx1 = 0;
8667
if (trimy1 < 0) trimy1 = 0;
8668
if (trimx2 > E3ww) trimx2 = E3ww;
8669
if (trimy2 > E3hh) trimy2 = E3hh;
8671
zdialog_fetch(zdedit,"lockwh",rlock); // w/h ratio locked
8672
if (rlock && corner) {
8674
trimy2 = trimy1 + 1.0 * (trimx2 - trimx1) / trimR;
8676
trimy1 = trimy2 - 1.0 * (trimx2 - trimx1) / trimR;
8679
if (trimx1 > trimx2-10) trimx1 = trimx2-10; // sanity limits
8680
if (trimy1 > trimy2-10) trimy1 = trimy2-10;
8681
if (trimx1 < 0) trimx1 = 0;
8682
if (trimy1 < 0) trimy1 = 0;
8683
if (trimx2 > E3ww) trimx2 = E3ww;
8684
if (trimy2 > E3hh) trimy2 = E3hh;
8686
trimww = trimx2 - trimx1; // new rectangle dimensions
8687
trimhh = trimy2 - trimy1;
8689
drr = 1.0 * trimww / trimhh; // new w/h ratio
8690
if (! rlock) trimR = drr;
8692
snprintf(text,39,"%d x %d (R=%.2f)", trimww * Fww / E3ww, // stuff dialog poop
8693
trimhh * Fhh / E3hh, drr); // (final size)
8694
zdialog_stuff(zdedit,"labwhr",text);
8696
signal_thread(); // trigger update thread
8703
// trim thread function
8705
void * trim_thread(void *)
8707
int px1, py1, px2, py2;
8708
uint16 *pix1, *pix3;
8712
thread_idle_loop(); // wait for work or exit request
8714
if (trim_status == 0) // darken margins v.8.4
8716
for (py1 = 0; py1 < E3hh; py1++) // copy pixels E1 >> E3
8717
for (px1 = 0; px1 < E3ww; px1++)
8719
pix1 = bmpixel(E1rgb48,px1,py1);
8720
pix3 = bmpixel(E3rgb48,px1,py1);
8722
if (px1 < trimx1 || px1 > trimx2 || py1 < trimy1 || py1 > trimy2)
8724
pix3[0] = pix1[0] / 2;
8725
pix3[1] = pix1[1] / 2;
8726
pix3[2] = pix1[2] / 2;
8737
mwpaint2(); // update window
8740
if (trim_status == 1) // do the trim
8742
mutex_lock(&pixmaps_lock);
8744
E3rgb48 = RGB_make(trimww,trimhh,48); // new pixmap with requested size
8748
for (py1 = trimy1; py1 < trimy2; py1++) // copy pixels
8749
for (px1 = trimx1; px1 < trimx2; px1++)
8753
pix1 = bmpixel(E1rgb48,px1,py1);
8754
pix3 = bmpixel(E3rgb48,px2,py2);
8761
mutex_unlock(&pixmaps_lock);
8762
mwpaint2(); // update window
8766
return 0; // not executed, stop g++ warning
8770
/**************************************************************************/
8772
// Resize (rescale) image
8774
// Output pixels are composites of input pixels, e.g. 2/3 size means
8775
// that 3x3 input pixels are mapped into 2x2 output pixels, and an
8776
// image size of 1000 x 600 becomes 667 x 400.
8779
int resize_ww0, resize_hh0, resize_ww1, resize_hh1;
8782
void m_resize(GtkWidget *, const char *)
8784
int resize_dialog_event(zdialog *zd, cchar * event);
8785
int resize_dialog_compl(zdialog *zd, int zstat);
8786
void * resize_thread(void *);
8788
const char *lockmess = ZTX("Lock aspect ratio");
8790
if (! edit_setup(0,0)) return; // setup edit: no preview
8792
zdedit = zdialog_new(ZTX("Resize Image"),mWin,Bapply,Bcancel,null);
8793
zdialog_add_widget(zdedit,"hbox","hb1","dialog",0,"space=10");
8794
zdialog_add_widget(zdedit,"vbox","vb11","hb1",0,"homog|space=5");
8795
zdialog_add_widget(zdedit,"vbox","vb12","hb1",0,"homog|space=5");
8796
zdialog_add_widget(zdedit,"vbox","vb13","hb1",0,"homog|space=5");
8797
zdialog_add_widget(zdedit,"label","placeholder","vb11",0); // pixels percent
8798
zdialog_add_widget(zdedit,"label","labw","vb11",Bwidth); // width [______] [______]
8799
zdialog_add_widget(zdedit,"label","labh","vb11",Bheight); // height [______] [______]
8800
zdialog_add_widget(zdedit,"label","labpix","vb12","pixels"); //
8801
zdialog_add_widget(zdedit,"spin","wpix","vb12","20|9999|1|0"); // presets [2/3] [1/2] [1/3] [1/4]
8802
zdialog_add_widget(zdedit,"spin","hpix","vb12","20|9999|1|0"); //
8803
zdialog_add_widget(zdedit,"label","labpct","vb13",Bpercent); // [_] lock width/height ratio
8804
zdialog_add_widget(zdedit,"spin","wpct","vb13","1|500|0.1|100"); //
8805
zdialog_add_widget(zdedit,"spin","hpct","vb13","1|500|0.1|100"); // [ done ] [ cancel ]
8806
zdialog_add_widget(zdedit,"hbox","hb2","dialog",0,"space=5");
8807
zdialog_add_widget(zdedit,"label","preset","hb2",Bpreset,"space=5");
8808
zdialog_add_widget(zdedit,"button","b 3/4","hb2"," 3/4 ");
8809
zdialog_add_widget(zdedit,"button","b 2/3","hb2"," 2/3 ");
8810
zdialog_add_widget(zdedit,"button","b 1/2","hb2"," 1/2 ");
8811
zdialog_add_widget(zdedit,"button","b 1/3","hb2"," 1/3 ");
8812
zdialog_add_widget(zdedit,"button","b 1/4","hb2"," 1/4 ");
8813
zdialog_add_widget(zdedit,"check","lock","dialog",lockmess);
8815
resize_ww0 = Frgb48->ww; // original width, height
8816
resize_hh0 = Frgb48->hh;
8817
zdialog_stuff(zdedit,"wpix",resize_ww0);
8818
zdialog_stuff(zdedit,"hpix",resize_hh0);
8819
zdialog_stuff(zdedit,"lock",1);
8821
zdialog_run(zdedit,resize_dialog_event,resize_dialog_compl); // run dialog - parallel
8822
start_thread(resize_thread,0); // start working thread
8827
// resize dialog event function
8829
int resize_dialog_event(zdialog *zd, const char * event)
8832
double wpct1, hpct1;
8834
zdialog_fetch(zd,"wpix",resize_ww1); // get all widget values
8835
zdialog_fetch(zd,"hpix",resize_hh1);
8836
zdialog_fetch(zd,"wpct",wpct1);
8837
zdialog_fetch(zd,"hpct",hpct1);
8838
zdialog_fetch(zd,"lock",lock);
8840
if (strEqu(event,"b 3/4")) {
8841
resize_ww1 = (3 * resize_ww0 + 3) / 4;
8842
resize_hh1 = (3 * resize_hh0 + 3) / 4;
8845
if (strEqu(event,"b 2/3")) {
8846
resize_ww1 = (2 * resize_ww0 + 2) / 3;
8847
resize_hh1 = (2 * resize_hh0 + 2) / 3;
8850
if (strEqu(event,"b 1/2")) {
8851
resize_ww1 = (resize_ww0 + 1) / 2;
8852
resize_hh1 = (resize_hh0 + 1) / 2;
8855
if (strEqu(event,"b 1/3")) {
8856
resize_ww1 = (resize_ww0 + 2) / 3;
8857
resize_hh1 = (resize_hh0 + 2) / 3;
8860
if (strEqu(event,"b 1/4")) {
8861
resize_ww1 = (resize_ww0 + 3) / 4;
8862
resize_hh1 = (resize_hh0 + 3) / 4;
8865
if (strEqu(event,"wpct")) // width % - set pixel width
8866
resize_ww1 = int(wpct1 / 100.0 * resize_ww0 + 0.5);
8868
if (strEqu(event,"hpct")) // height % - set pixel height
8869
resize_hh1 = int(hpct1 / 100.0 * resize_hh0 + 0.5);
8871
if (lock && event[0] == 'w') // preserve width/height ratio
8872
resize_hh1 = int(resize_ww1 * (1.0 * resize_hh0 / resize_ww0) + 0.5);
8873
if (lock && event[0] == 'h')
8874
resize_ww1 = int(resize_hh1 * (1.0 * resize_ww0 / resize_hh0) + 0.5);
8876
hpct1 = 100.0 * resize_hh1 / resize_hh0; // set percents to match pixels
8877
wpct1 = 100.0 * resize_ww1 / resize_ww0;
8879
zdialog_stuff(zd,"wpix",resize_ww1); // index all widget values
8880
zdialog_stuff(zd,"hpix",resize_hh1);
8881
zdialog_stuff(zd,"wpct",wpct1);
8882
zdialog_stuff(zd,"hpct",hpct1);
8888
// resize dialog completion function
8890
int resize_dialog_compl(zdialog *zd, int zstat)
8901
// resize image based on dialog controls.
8903
void * resize_thread(void *)
8907
thread_idle_loop(); // wait for work or exit request
8909
if (resize_ww1 != resize_ww0 || resize_hh1 != resize_hh0)
8910
{ // rescale to target size
8911
mutex_lock(&pixmaps_lock);
8913
E3rgb48 = RGB_rescale(Frgb48,resize_ww1,resize_hh1);
8917
mutex_unlock(&pixmaps_lock);
8920
mwpaint2(); // update window
8923
return 0; // not executed, stop g++ warning
8927
/**************************************************************************/
8929
// rotate image through any arbitrary angle
8931
double rotate_angle = 0; // E3 rotatation vs. F
8932
double rotate_delta = 0;
8933
int rotate_trim = 0;
8936
void m_rotate(GtkWidget *, const char *menu) // menu function
8938
int rotate_dialog_event(zdialog *zd, const char * event);
8939
int rotate_dialog_compl(zdialog *zd, int zstat);
8940
void * rotate_thread(void *);
8941
void rotate_mousefunc();
8943
const char *rotmess = ZTX("Use buttons or drag right edge with mouse");
8945
if (! edit_setup(1,0)) return; // setup edit: use preview v.6.2
8947
zdedit = zdialog_new(ZTX("Rotate Image"),mWin,Bundo,Bdone,Bcancel,null);
8948
zdialog_add_widget(zdedit,"label","labrot","dialog",ZTX(rotmess),"space=5");
8949
zdialog_add_widget(zdedit,"label","labdeg","dialog",ZTX("degrees"),"space=5");
8950
zdialog_add_widget(zdedit,"hbox","hb1","dialog",0,"homog|space=5");
8951
zdialog_add_widget(zdedit,"vbox","vb1","hb1",0,"space=5");
8952
zdialog_add_widget(zdedit,"vbox","vb2","hb1",0,"space=5");
8953
zdialog_add_widget(zdedit,"vbox","vb3","hb1",0,"space=5");
8954
zdialog_add_widget(zdedit,"vbox","vb4","hb1",0,"space=5");
8955
zdialog_add_widget(zdedit,"button"," +0.1 ","vb1"," + 0.1 "); // button name is increment to use
8956
zdialog_add_widget(zdedit,"button"," -0.1 ","vb1"," - 0.1 ");
8957
zdialog_add_widget(zdedit,"button"," +1.0 ","vb2"," + 1 ");
8958
zdialog_add_widget(zdedit,"button"," -1.0 ","vb2"," - 1 ");
8959
zdialog_add_widget(zdedit,"button"," +10.0 ","vb3"," + 10 ");
8960
zdialog_add_widget(zdedit,"button"," -10.0 ","vb3"," - 10 ");
8961
zdialog_add_widget(zdedit,"button"," +90.0 ","vb4"," + 90 ");
8962
zdialog_add_widget(zdedit,"button"," -90.0 ","vb4"," - 90 ");
8963
zdialog_add_widget(zdedit,"hbox","hb2","dialog",0,"space=10");
8964
zdialog_add_widget(zdedit,"button","trim","hb2",ZTX("Trim"),"space=10");
8966
zdialog_run(zdedit,rotate_dialog_event,rotate_dialog_compl); // run dialog - parallel
8968
mouseCBfunc = rotate_mousefunc; // connect mouse function
8971
gdk_window_set_cursor(drWin->window,dragcursor); // set drag cursor
8972
rotate_angle = rotate_delta = rotate_trim = 0;
8973
start_thread(rotate_thread,0); // start working thread
8978
// rotate dialog event and completion callback functions
8980
int rotate_dialog_event(zdialog *zd, const char * event)
8986
if (strEqu(event,"trim")) {
8987
rotate_trim = 1 - rotate_trim; // toggle trim button v.8.3
8988
if (rotate_trim) zdialog_stuff(zd,"trim",ZTX("Undo Trim"));
8989
else zdialog_stuff(zd,"trim",ZTX("Trim"));
8992
if (strpbrk(event,"+-")) {
8993
err = convSD(event,incr); // button name is increment to use
8995
rotate_delta += incr;
8998
zdialog_stuff(zd,"labdeg","computing");
9002
gdk_window_set_cursor(drWin->window,dragcursor); // set drag cursor v.8.4
9004
sprintf(text,ZTX("degrees: %.1f"),rotate_angle); // update dialog angle display
9005
zdialog_stuff(zd,"labdeg",text);
9010
int rotate_dialog_compl(zdialog *zd, int zstat)
9012
if (zstat == 1) { // undo, dialog stays active
9013
edit_undo(); // v.6.1.2
9014
rotate_angle = rotate_delta = rotate_trim = 0;
9019
rotate_delta = rotate_angle; // rotate main image v.6.2
9026
rotate_angle = rotate_delta = rotate_trim = 0;
9028
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
9029
mouseCBfunc = 0; // disconnect mouse
9035
// rotate mouse function - drag right edge of image up/down for rotation
9037
void rotate_mousefunc()
9039
static int mpx0 = 0, mpy0 = 0;
9040
static int mpy1, mpy2, dist;
9042
if (! Mxdrag && ! Mydrag) return; // no drag underway
9043
if (Mxdrag < 0.8 * E3ww) return; // not right edge of image
9045
if (Mxdown != mpx0 || Mydown != mpy0) {
9046
mpx0 = Mxdown; // new drag started
9047
mpy0 = mpy1 = Mydown;
9051
dist = mpy2 - mpy1; // drag distance
9052
mpy1 = mpy2; // reset origin for next time
9055
rotate_delta = 30.0 * dist / E3ww; // convert to angle
9056
rotate_dialog_event(zdedit,"mouse");
9061
// rotate thread function
9063
void * rotate_thread(void *)
9065
int px3, py3, px9, py9;
9066
int wwcut, hhcut, ww, hh;
9067
double trim_angle, radians;
9068
uint16 *pix3, *pix9;
9072
thread_idle_loop(); // wait for work or exit request
9074
mutex_lock(&pixmaps_lock);
9076
rotate_angle += rotate_delta; // accum. net rotation
9077
rotate_delta = 0; // from dialog widget
9079
if (rotate_angle >= 360) rotate_angle -=360;
9080
if (rotate_angle <= -360) rotate_angle +=360;
9081
if (fabs(rotate_angle) < 0.01) rotate_angle = 0;
9083
if (! rotate_angle) {
9084
RGB_free(E3rgb48); // E1 >> E3
9085
E3rgb48 = RGB_copy(E1rgb48);
9093
E3rgb48 = RGB_rotate(E1rgb48,rotate_angle); // E3 is rotated E1
9100
{ // auto trim no reset v.8.3
9101
trim_angle = fabs(rotate_angle);
9102
while (trim_angle > 45) trim_angle -= 90;
9103
radians = fabs(trim_angle / 57.296);
9104
wwcut = int(E3rgb48->hh * sin(radians) + 1); // amount to trim
9105
hhcut = int(E3rgb48->ww * sin(radians) + 1);
9106
ww = E3rgb48->ww - 2 * wwcut;
9107
hh = E3rgb48->hh - 2 * hhcut;
9108
if (ww > 0 && hh > 0) {
9109
E9rgb48 = RGB_make(ww,hh,48);
9111
for (py3 = hhcut; py3 < E3hh-hhcut; py3++) // E9 = trimmed E3
9112
for (px3 = wwcut; px3 < E3ww-wwcut; px3++)
9116
pix3 = bmpixel(E3rgb48,px3,py3);
9117
pix9 = bmpixel(E9rgb48,px9,py9);
9123
RGB_free(E3rgb48); // E3 = E9
9131
mutex_unlock(&pixmaps_lock);
9132
mwpaint2(); // update window
9135
return 0; // not executed, stop g++ warning
9139
/**************************************************************************/
9142
// straighten curvature added by pano or improve perspective
9144
int unbend_horz, unbend_vert; // unbend values from dialog
9145
int unbend_vert2, unbend_horz2;
9146
double unbend_x1, unbend_x2, unbend_y1, unbend_y2; // unbend axes scaled 0 to 1
9148
int unbend_Npixels, unbend_Ndone;
9149
int unbend_hx1, unbend_hy1, unbend_hx2, unbend_hy2;
9150
int unbend_vx1, unbend_vy1, unbend_vx2, unbend_vy2;
9155
void m_unbend(GtkWidget *, const char *) // overhaul for preview v.6.2
9157
int unbend_dialog_event(zdialog* zd, const char *event);
9158
int unbend_dialog_compl(zdialog* zd, int zstat);
9159
void * unbend_thread(void *);
9160
void unbend_mousefunc();
9162
if (! edit_setup(1,0)) return; // setup edit: preview
9164
zdedit = zdialog_new(ZTX("Unbend Image"),mWin,Bdone,Bcancel,null);
9165
zdialog_add_widget(zdedit,"hbox","hb1","dialog",0,"space=10");
9166
zdialog_add_widget(zdedit,"vbox","vb1","hb1",0,"homog|space=10");
9167
zdialog_add_widget(zdedit,"vbox","vb2","hb1",0,"homog|space=10");
9168
zdialog_add_widget(zdedit,"spin","spvert","vb1","-30|30|1|0");
9169
zdialog_add_widget(zdedit,"spin","sphorz","vb1","-20|20|1|0");
9170
zdialog_add_widget(zdedit,"label","labvert","vb2",ZTX("vertical unbend"));
9171
zdialog_add_widget(zdedit,"label","labhorz","vb2",ZTX("horizontal unbend"));
9173
zdialog_resize(zdedit,260,0);
9174
zdialog_run(zdedit,unbend_dialog_event,unbend_dialog_compl); // run dialog, parallel
9176
unbend_x1 = unbend_x2 = unbend_y1 = unbend_y2 = 0.5; // initial axes thru image middle
9177
unbend_horz = unbend_vert = 0; // v.6.3
9180
mouseCBfunc = unbend_mousefunc; // connect mouse function
9182
start_thread(unbend_thread,0); // start working thread
9189
// dialog event and completion functions
9191
int unbend_dialog_event(zdialog *zd, const char *event)
9193
zdialog_fetch(zd,"spvert",unbend_vert); // get new unbend values
9194
zdialog_fetch(zd,"sphorz",unbend_horz);
9195
signal_thread(); // trigger thread
9200
int unbend_dialog_compl(zdialog *zd, int zstat)
9202
paint_toplines(2); // erase axes-lines v.8.4.3
9203
mouseCBfunc = 0; // disconnect mouse
9205
LMclick = RMclick = 0;
9208
edit_cancel(); // canceled
9212
if (unbend_vert || unbend_horz) Fmodified = 1; // image3 modified
9214
edit_done(); // commit changes to image3
9219
// unbend mouse function // adjustable axes
9221
void unbend_mousefunc()
9224
double dist1, dist2;
9225
double mpx = 0, mpy = 0;
9227
if (LMclick) { // left mouse click v.7.5
9233
if (Mxdrag || Mydrag) { // mouse dragged
9238
if (! mpx && ! mpy) return;
9240
mpx = 1.0 * mpx / E3ww; // scale mouse position 0 to 1
9241
mpy = 1.0 * mpy / E3hh;
9243
if (mpx < 0.2 || mpx > 0.8 ) { // check reasonable position
9244
if (mpy < 0.1 || mpy > 0.9) return;
9246
else if (mpy < 0.2 || mpy > 0.8) {
9247
if (mpx < 0.1 || mpx > 0.9) return;
9251
close = "?"; // find closest axis end-point
9254
dist2 = mpx * mpx + (mpy-unbend_y1) * (mpy-unbend_y1);
9255
if (dist2 < dist1) {
9260
dist2 = (1-mpx) * (1-mpx) + (mpy-unbend_y2) * (mpy-unbend_y2);
9261
if (dist2 < dist1) {
9266
dist2 = (mpx-unbend_x1) * (mpx-unbend_x1) + mpy * mpy;
9267
if (dist2 < dist1) {
9272
dist2 = (mpx-unbend_x2) * (mpx-unbend_x2) + (1-mpy) * (1-mpy);
9273
if (dist2 < dist1) {
9278
if (strEqu(close,"left")) unbend_y1 = mpy; // set new axis end-point
9279
if (strEqu(close,"right")) unbend_y2 = mpy;
9280
if (strEqu(close,"top")) unbend_x1 = mpx;
9281
if (strEqu(close,"bottom")) unbend_x2 = mpx;
9283
signal_thread(); // trigger thread
9289
// unbend thread function
9291
void * unbend_thread(void *arg)
9293
void * unbend_wthread(void *);
9297
thread_idle_loop(); // wait for work or exit request
9299
unbend_vert2 = int(unbend_vert * 0.01 * E3hh); // convert % to pixels
9300
unbend_horz2 = int(unbend_horz * 0.005 * E3ww);
9302
unbend_hx1 = 0; // scale axes to E3ww/hh
9303
unbend_hy1 = unbend_y1 * E3hh;
9305
unbend_hy2 = unbend_y2 * E3hh;
9307
unbend_vx1 = unbend_x1 * E3ww;
9309
unbend_vx2 = unbend_x2 * E3ww;
9312
if (Fpreview) { // omit for final unbend v.8.4.3
9314
toplinex1[0] = unbend_hx1; // lines on window
9315
topliney1[0] = unbend_hy1;
9316
toplinex2[0] = unbend_hx2;
9317
topliney2[0] = unbend_hy2;
9318
toplinex1[1] = unbend_vx1;
9319
topliney1[1] = unbend_vy1;
9320
toplinex2[1] = unbend_vx2;
9321
topliney2[1] = unbend_vy2;
9324
unbend_Npixels = E3ww * E3hh;
9327
for (int ii = 0; ii < NWthreads; ii++) // start worker threads
9328
start_detached_thread(unbend_wthread,&wtindex[ii]);
9329
zadd_locked(unbend_busy,+NWthreads);
9331
while (unbend_busy) // wait for completion
9334
edit_progress(unbend_Ndone,unbend_Npixels); // show progress counter
9338
mwpaint2(); // update window
9341
return 0; // not executed, stop g++ warning
9345
void * unbend_wthread(void *arg) // worker thread function v.7.7
9347
int index = *((int *) arg);
9348
int vstat, px3, py3, cx3, cy3;
9349
double px1, py1, dispx, dispx2, dispy;
9350
uint16 vpix[3], *pix3;
9352
for (py3 = index; py3 < E3hh; py3 += NWthreads) // step through F3 pixels
9353
for (px3 = 0; px3 < E3ww; px3++)
9355
pix3 = bmpixel(E3rgb48,px3,py3); // output pixel
9357
cx3 = unbend_vx1 + (unbend_vx2 - unbend_vx1) * py3 / E3hh; // center of unbend
9358
cy3 = unbend_hy1 + (unbend_hy2 - unbend_hy1) * px3 / E3ww;
9359
dispx = 2.0 * (px3 - cx3) / E3ww; // -1.0 .. 0.0 .. +1.0 (roughly)
9360
dispy = 2.0 * (py3 - cy3) / E3hh; // -1.0 .. 0.0 .. +1.0
9361
dispx2 = dispx * dispx - 0.5; // +0.5 .. -0.5 .. +0.5 curved
9363
px1 = px3 + dispx * dispy * unbend_horz2; // input virtual pixel, x
9364
py1 = py3 - dispy * dispx2 * unbend_vert2; // input virtual pixel, y
9365
vstat = vpixel(E1rgb48,px1,py1,vpix); // input virtual pixel
9368
pix3[0] = vpix[0]; // input pixel >> output pixel
9372
else pix3[0] = pix3[1] = pix3[2] = 0;
9377
zadd_locked(unbend_busy,-1);
9382
/**************************************************************************/
9384
// warp/distort area - select image area and pull with mouse
9386
float *WarpAx, *WarpAy; // memory of all displaced pixels
9387
float WarpAmem[4][100]; // undo memory, last 100 warps
9388
int NWarpA; // WarpA mem count
9390
void WarpA_warpfunc(float wdx, float wdy, float wdw, float wdh, int acc);
9395
void m_WarpA(GtkWidget *, const char *)
9397
int WarpA_dialog_event(zdialog *zd, const char *event);
9398
int WarpA_dialog_compl(zdialog *zd, int zstat);
9400
const char *WarpA_message =
9401
ZTX(" Select an area to warp using menu select function. \n" // v.6.3
9402
" Press [start warp] and pull area with mouse. \n"
9403
" Make multiple mouse pulls until satisfied. \n"
9404
" When finished, select another area or press [done].");
9408
if (! edit_setup(0,2)) return; // setup edit: no preview
9410
zdedit = zdialog_new(ZTX("Warp Image in Selected Area"),mWin,Bdone,Bcancel,null);
9411
zdialog_add_widget(zdedit,"label","lab1","dialog",WarpA_message,"space=5");
9412
zdialog_add_widget(zdedit,"hbox","hb1","dialog",0,"space=10");
9413
zdialog_add_widget(zdedit,"button","swarp","hb1",ZTX("start warp"),"space=5");
9414
zdialog_add_widget(zdedit,"button","undlast","hb1",Bundolast,"space=5");
9415
zdialog_add_widget(zdedit,"button","undall","hb1",Bundoall,"space=5");
9417
zdialog_run(zdedit,WarpA_dialog_event,WarpA_dialog_compl); // run dialog
9419
WarpAx = (float *) zmalloc(E3ww * E3hh * sizeof(float)); // get memory for pixel displacements
9420
WarpAy = (float *) zmalloc(E3ww * E3hh * sizeof(float));
9422
NWarpA = 0; // no warp data
9424
for (py = 0; py < E3hh; py++) // no pixel displacements
9425
for (px = 0; px < E3ww; px++)
9427
ii = py * E3ww + px;
9428
WarpAx[ii] = WarpAy[ii] = 0.0;
9435
// warp dialog event and completion callback functions
9437
int WarpA_dialog_compl(zdialog * zd, int zstat)
9439
if (NWarpA) Fmodified = 1;
9440
if (zstat == 1) edit_done();
9443
mouseCBfunc = 0; // disconnect mouse
9445
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
9446
zfree(WarpAx); // release undo memory
9452
int WarpA_dialog_event(zdialog * zd, const char *event)
9454
void WarpA_mousefunc(void);
9457
float wdx, wdy, wdw, wdh;
9458
uint16 *pix1, *pix3;
9460
if (strEqu(event,"swarp")) { // start warp
9462
zmessageACK(ZTX("Must select area first"));
9466
gdk_window_set_cursor(drWin->window,dragcursor); // set drag cursor
9467
mouseCBfunc = WarpA_mousefunc; // connect mouse function
9471
if (strEqu(event,"undlast")) {
9472
if (NWarpA) { // undo most recent warp
9474
wdx = WarpAmem[0][ii];
9475
wdy = WarpAmem[1][ii];
9476
wdw = WarpAmem[2][ii];
9477
wdh = WarpAmem[3][ii];
9478
WarpA_warpfunc(wdx,wdy,-wdw,-wdh,0); // unwarp image
9479
WarpA_warpfunc(wdx,wdy,-wdw,-wdh,1); // unwarp memory
9483
if (strEqu(event,"undall")) { // undo all warps
9484
for (ii = 0; ii < sa_Npixel; ii++) // process all enclosed pixels
9486
px = sa_pixel[ii].px;
9487
py = sa_pixel[ii].py;
9488
pix1 = bmpixel(E1rgb48,px,py); // image1 pixel >> image3
9489
pix3 = bmpixel(E3rgb48,px,py);
9495
for (py = 0; py < E3hh; py++) // reset pixel displacements
9496
for (px = 0; px < E3ww; px++)
9498
ii = py * E3ww + px;
9499
WarpAx[ii] = WarpAy[ii] = 0.0;
9502
NWarpA = 0; // erase undo memory
9510
// warp mouse function
9512
void WarpA_mousefunc(void)
9514
static float wdx, wdy, wdw, wdh;
9515
static int ii, warped = 0;
9517
if (Mxdrag || Mydrag) // mouse drag underway
9519
wdx = Mxdown; // drag origin, image coordinates
9521
wdw = Mxdrag - Mxdown; // drag increment
9522
wdh = Mydrag - Mydown;
9523
WarpA_warpfunc(wdx,wdy,wdw,wdh,0); // warp image
9531
WarpA_warpfunc(wdx,wdy,wdw,wdh,1); // drag done, add to warp memory
9533
if (NWarpA == 100) // if full, throw away oldest
9536
for (ii = 0; ii < NWarpA; ii++)
9538
WarpAmem[0][ii] = WarpAmem[0][ii+1];
9539
WarpAmem[1][ii] = WarpAmem[1][ii+1];
9540
WarpAmem[2][ii] = WarpAmem[2][ii+1];
9541
WarpAmem[3][ii] = WarpAmem[3][ii+1];
9546
WarpAmem[0][ii] = wdx; // save warp for undo
9547
WarpAmem[1][ii] = wdy;
9548
WarpAmem[2][ii] = wdw;
9549
WarpAmem[3][ii] = wdh;
9557
// warp image and accumulate warp memory
9559
void WarpA_warpfunc(float wdx, float wdy, float wdw, float wdh, int acc)
9561
int ii, jj, px, py, vstat;
9562
double ddx, ddy, dpe, dpm, mag, dispx, dispy;
9563
uint16 vpix[3], *pix3;
9565
for (ii = 0; ii < sa_Npixel; ii++) // process all enclosed pixels
9567
px = sa_pixel[ii].px;
9568
py = sa_pixel[ii].py;
9569
dpe = sa_pixel[ii].dist; // distance from area edge
9571
ddx = (px - wdx); // distance from drag origin
9573
dpm = sqrt(ddx*ddx + ddy*ddy);
9575
if (dpm < 1) mag = 1;
9576
else mag = dpe / (dpe + dpm); // magnification, 0...1
9579
dispx = -wdw * mag; // warp = drag * magnification
9582
jj = py * E3ww + px;
9584
if (acc) { // mouse drag done,
9585
WarpAx[jj] += dispx; // accumulate warp memory
9586
WarpAy[jj] += dispy;
9590
dispx += WarpAx[jj]; // add this warp to prior
9591
dispy += WarpAy[jj];
9593
vstat = vpixel(E1rgb48,px+dispx,py+dispy,vpix); // input virtual pixel
9595
pix3 = bmpixel(E3rgb48,px,py); // output pixel
9602
mwpaint2(); // update window
9607
/**************************************************************************/
9609
// warp/distort whole image
9610
// fix perspective problems (e.g. curved walls, leaning buildings)
9612
float *WarpIx, *WarpIy; // memory of all dragged pixels
9613
float WarpImem[4][100]; // undo memory, last 100 drags
9614
int NWarpI; // WarpImem count
9616
int WarpIww, WarpIhh;
9618
void WarpI_warpfunc(float wdx, float wdy, float wdw, float wdh, int acc);
9623
void m_WarpI(GtkWidget *, const char *) // v.6.1
9625
int WarpI_dialog_event(zdialog *zd, const char *event);
9626
int WarpI_dialog_compl(zdialog *zd, int zstat);
9627
void WarpI_mousefunc(void);
9629
const char *WarpI_message =
9630
ZTX(" Pull on an image edge using the mouse. \n"
9631
" Make multiple mouse pulls until satisfied. \n"
9632
" When finished, press [done].");
9636
if (! edit_setup(1,0)) return; // setup edit: use preview
9638
zdedit = zdialog_new(ZTX("Fix Image Perspective"),mWin,Bdone,Bcancel,null);
9639
zdialog_add_widget(zdedit,"label","lab1","dialog",WarpI_message,"space=5");
9640
zdialog_add_widget(zdedit,"hbox","hb1","dialog",0,"space=10");
9641
zdialog_add_widget(zdedit,"button","undlast","hb1",Bundolast,"space=5");
9642
zdialog_add_widget(zdedit,"button","undall","hb1",Bundoall,"space=5");
9644
zdialog_run(zdedit,WarpI_dialog_event,WarpI_dialog_compl); // run dialog
9646
NWarpI = WarpIdrag = 0; // no drag data
9648
WarpIx = (float *) zmalloc(E3ww * E3hh * sizeof(float)); // get memory for pixel displacements
9649
WarpIy = (float *) zmalloc(E3ww * E3hh * sizeof(float));
9651
for (py = 0; py < E3hh; py++) // no pixel displacements
9652
for (px = 0; px < E3ww; px++)
9654
ii = py * E3ww + px;
9655
WarpIx[ii] = WarpIy[ii] = 0.0;
9658
WarpIww = E3ww; // preview dimensions
9661
gdk_window_set_cursor(drWin->window,dragcursor); // set drag cursor
9662
mouseCBfunc = WarpI_mousefunc; // connect mouse function
9669
// WarpI dialog event and completion callback functions
9671
int WarpI_dialog_compl(zdialog * zd, int zstat)
9673
int fpx, fpy, epx, epy, ii, vstat;
9674
double scale, dispx, dispy;
9675
uint16 vpix[3], *pix3;
9677
if (zstat != 1) edit_cancel();
9678
else if (NWarpI == 0) edit_cancel();
9682
edit_fullsize(); // get full-size E1/E3
9684
scale = 1.0 * (E3ww + E3hh) / (WarpIww + WarpIhh);
9686
for (fpy = 0; fpy < E3hh; fpy++) // scale net pixel displacements
9687
for (fpx = 0; fpx < E3ww; fpx++) // to full image size
9689
epx = WarpIww * fpx / E3ww;
9690
epy = WarpIhh * fpy / E3hh;
9691
ii = epy * WarpIww + epx;
9692
dispx = WarpIx[ii] * scale;
9693
dispy = WarpIy[ii] * scale;
9695
vstat = vpixel(E1rgb48,fpx+dispx,fpy+dispy,vpix); // input virtual pixel
9696
pix3 = bmpixel(E3rgb48,fpx,fpy); // output pixel
9702
else pix3[0] = pix3[1] = pix3[2] = 0;
9708
mouseCBfunc = 0; // disconnect mouse
9710
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
9711
zfree(WarpIx); // release memory
9717
int WarpI_dialog_event(zdialog * zd, const char *event)
9720
float wdx, wdy, wdw, wdh;
9722
if (strEqu(event,"undlast"))
9724
if (NWarpI == 1) event = "undall";
9725
else if (NWarpI) { // undo most recent drag
9727
wdx = WarpImem[0][ii];
9728
wdy = WarpImem[1][ii];
9729
wdw = WarpImem[2][ii];
9730
wdh = WarpImem[3][ii];
9731
WarpI_warpfunc(wdx,wdy,-wdw,-wdh,0); // undrag image
9732
WarpI_warpfunc(wdx,wdy,-wdw,-wdh,1); // undrag memory
9736
if (strEqu(event,"undall")) // undo all drags
9738
NWarpI = 0; // erase undo memory
9740
for (py = 0; py < E3hh; py++) // reset pixel displacements
9741
for (px = 0; px < E3ww; px++)
9743
ii = py * E3ww + px;
9744
WarpIx[ii] = WarpIy[ii] = 0.0;
9747
edit_undo(); // restore image1
9748
Fmodified = 0; // v.6.3
9755
// WarpI mouse function
9757
void WarpI_mousefunc(void)
9759
static float wdx, wdy, wdw, wdh;
9762
if (Mxdrag || Mydrag) // mouse drag underway
9764
wdx = Mxdown * Mscale; // drag origin, window coordinates
9765
wdy = Mydown * Mscale;
9766
wdw = (Mxdrag - Mxdown) * Mscale; // drag increment
9767
wdh = (Mydrag - Mydown) * Mscale;
9768
WarpI_warpfunc(wdx,wdy,wdw,wdh,0); // drag image
9776
WarpI_warpfunc(wdx,wdy,wdw,wdh,1); // drag done, add to memory
9778
if (NWarpI == 100) // if full, throw away oldest
9781
for (ii = 0; ii < NWarpI; ii++)
9783
WarpImem[0][ii] = WarpImem[0][ii+1];
9784
WarpImem[1][ii] = WarpImem[1][ii+1];
9785
WarpImem[2][ii] = WarpImem[2][ii+1];
9786
WarpImem[3][ii] = WarpImem[3][ii+1];
9791
WarpImem[0][ii] = wdx; // save drag for undo
9792
WarpImem[1][ii] = wdy;
9793
WarpImem[2][ii] = wdw;
9794
WarpImem[3][ii] = wdh;
9802
// warp image and accumulate warp memory
9803
// mouse at (mx,my) is moved (mw,mh) pixels
9805
void WarpI_warpfunc(float mx, float my, float mw, float mh, int acc)
9807
int ii, px, py, vstat;
9808
double mag, dispx, dispy;
9810
uint16 vpix[3], *pix3;
9812
d1 = E3ww * E3ww + E3hh * E3hh;
9814
for (py = 0; py < E3hh; py++) // process all pixels
9815
for (px = 0; px < E3ww; px++)
9817
d2 = (px-mx)*(px-mx) + (py-my)*(py-my); // better algorithm v.8.0
9818
mag = (1.0 - d2 / d1);
9819
mag = mag * mag; // faster than pow(mag,16);
9823
dispx = -mw * mag; // displacement = drag * mag
9826
ii = py * E3ww + px;
9828
if (acc) { // drag done, accumulate drag sum
9829
WarpIx[ii] += dispx;
9830
WarpIy[ii] += dispy;
9834
dispx += WarpIx[ii]; // add this drag to prior sum
9835
dispy += WarpIy[ii];
9837
vstat = vpixel(E1rgb48,px+dispx,py+dispy,vpix); // input virtual pixel
9838
pix3 = bmpixel(E3rgb48,px,py); // output pixel
9844
else pix3[0] = pix3[1] = pix3[2] = 0;
9847
Fmodified = 1; // v.6.3
9848
mwpaint2(); // update window
9853
/**************************************************************************/
9855
// image color-depth reduction
9857
int colordep_depth = 16; // bits per RGB color
9860
void m_colordep(GtkWidget *, const char *)
9862
int colordep_dialog_event(zdialog *zd, const char *event);
9863
int colordep_dialog_compl(zdialog *zd, int zstat);
9864
void * colordep_thread(void *);
9866
const char *colmess = ZTX("Set color depth to 1-16 bits");
9868
if (! edit_setup(1,2)) return; // setup edit: preview
9870
zdedit = zdialog_new(ZTX("Set Color Depth"),mWin,Bdone,Bcancel,null);
9871
zdialog_add_widget(zdedit,"label","lab1","dialog",colmess,"space=5");
9872
zdialog_add_widget(zdedit,"hbox","hb1","dialog",0,"space=10");
9873
zdialog_add_widget(zdedit,"spin","colors","hb1","1|16|1|16","space=5");
9874
zdialog_add_widget(zdedit,"button","Undo","hb1",Bundo,"space=5");
9875
zdialog_add_widget(zdedit,"button","Redo","hb1",Bredo,"space=5");
9877
zdialog_run(zdedit,colordep_dialog_event,colordep_dialog_compl); // run dialog - parallel
9879
colordep_depth = 16;
9880
start_thread(colordep_thread,0); // start working thread
9885
// colors dialog event and completion callback functions
9887
int colordep_dialog_event(zdialog *zd, const char *event)
9889
if (strcmpv(event,"colors","blendwidth",0)) {
9890
zdialog_fetch(zd,"colors",colordep_depth);
9894
if (strEqu(event,"Undo")) edit_undo();
9895
if (strEqu(event,"Redo")) edit_redo();
9901
int colordep_dialog_compl(zdialog * zd, int zstat)
9903
if (zstat == 1) edit_done();
9909
// image color depth thread function
9911
void * colordep_thread(void *)
9913
int ii, px, py, rgb, dist;
9914
uint16 m1, m2, val1, val3;
9915
uint16 *pix1, *pix3;
9916
double fmag, f1, f2;
9920
thread_idle_loop(); // wait for work or exit request
9922
m1 = 0xFFFF << (16 - colordep_depth); // 5 > 1111100000000000
9923
m2 = 0x8000 >> colordep_depth; // 5 > 0000010000000000
9925
fmag = 65535.0 / m1; // full brightness range v.7.0
9927
if (! sa_Npixel) // process entire image
9929
for (py = 0; py < E3hh; py++)
9930
for (px = 0; px < E3ww; px++)
9932
pix1 = bmpixel(E1rgb48,px,py); // input pixel
9933
pix3 = bmpixel(E3rgb48,px,py); // output pixel
9935
for (rgb = 0; rgb < 3; rgb++)
9938
if (val1 < m1) val3 = (val1 + m2) & m1; // round v.7.0
9940
val3 = uint(val3 * fmag);
9946
if (sa_Npixel) // process select area
9948
for (ii = 0; ii < sa_Npixel; ii++)
9950
px = sa_pixel[ii].px;
9951
py = sa_pixel[ii].py;
9952
dist = sa_pixel[ii].dist;
9953
pix1 = bmpixel(E1rgb48,px,py); // input pixel
9954
pix3 = bmpixel(E3rgb48,px,py); // output pixel
9956
for (rgb = 0; rgb < 3; rgb++)
9959
if (val1 < m1) val3 = (val1 + m2) & m1;
9961
val3 = uint(val3 * fmag);
9963
if (dist < sa_blend) { // if area selection, blend pixel
9964
f2 = 1.0 * dist / sa_blend; // changes over distance sa_blend
9966
val3 = int(f1 * val1 + f2 * val3);
9974
mwpaint2(); // update window
9977
return 0; // not executed, stop g++ warning
9981
/**************************************************************************/
9983
// convert image to simulate a drawing
9990
double draw_trfunc[256];
9991
double draw_pixcon3;
9992
uint8 *draw_pixcon_map = 0;
9994
void m_draw(GtkWidget *, const char *) // v.6.7
9996
int draw_dialog_event(zdialog* zd, const char *event);
9997
int draw_dialog_compl(zdialog* zd, int zstat);
9998
void * draw_thread(void *);
10000
const char *title = ZTX("Simulate Drawing");
10001
uint16 *pix1, *pix2;
10002
int ii, px, py, qx, qy;
10003
int red, green, blue, con, maxcon;
10005
if (! edit_setup(0,2)) return; // setup edit: no preview
10007
draw_pixcon_map = (uint8 *) zmalloc(E1ww*E1hh); // set up pixel contrast map
10008
memset(draw_pixcon_map,0,E1ww*E1hh);
10010
for (py = 1; py < E1hh-1; py++) // scan image pixels
10011
for (px = 1; px < E1ww-1; px++)
10013
pix1 = bmpixel(E1rgb48,px,py); // pixel at (px,py)
10014
red = pix1[0]; // pixel RGB levels
10019
for (qy = py-1; qy < py+2; qy++) // loop 3x3 block of neighbor
10020
for (qx = px-1; qx < px+2; qx++) // pixels around pix1
10022
pix2 = bmpixel(E1rgb48,qx,qy); // find max. contrast with
10023
con = abs(red-pix2[0]) + abs(green-pix2[1]) + abs(blue-pix2[2]); // neighbor pixel
10024
if (con > maxcon) maxcon = con;
10027
ii = py * E1ww + px;
10028
draw_pixcon_map[ii] = (maxcon/3) >> 8; // contrast for (px,py) 0-255
10031
zdedit = zdialog_new(title,mWin,Bundo,Bredo,Bdone,Bcancel,null); // setup drawing dialog
10032
zdialog_add_widget(zdedit,"hbox","hb1","dialog",0,"space=10");
10033
zdialog_add_widget(zdedit,"vbox","vb1","hb1",0,"homog");
10034
zdialog_add_widget(zdedit,"vbox","vb2","hb1",0,"homog|expand");
10035
zdialog_add_widget(zdedit,"label","lab1","vb1",ZTX("contrast"));
10036
zdialog_add_widget(zdedit,"label","lab2","vb1",ZTX("threshold"));
10037
zdialog_add_widget(zdedit,"label","lab3","vb1",ZTX("outlines"));
10038
zdialog_add_widget(zdedit,"hscale","contrast","vb2","0|100|1|0","expand");
10039
zdialog_add_widget(zdedit,"hscale","threshold","vb2","0|100|1|0","expand");
10040
zdialog_add_widget(zdedit,"hscale","pixcon","vb2","0|255|1|0","expand");
10041
zdialog_add_widget(zdedit,"hbox","hb4","dialog");
10042
zdialog_add_widget(zdedit,"radio","pencil","hb4",ZTX("pencil"),"space=10");
10043
zdialog_add_widget(zdedit,"radio","chalk","hb4",ZTX("chalk"),"space=10");
10045
zdialog_run(zdedit,draw_dialog_event,draw_dialog_compl); // run dialog - parallel
10047
start_thread(draw_thread,0); // start working thread
10052
// draw dialog event and completion functions
10054
int draw_dialog_event(zdialog *zd, const char *event) // draw dialog event function
10056
zdialog_fetch(zd,"contrast",draw_contrast); // get slider values
10057
zdialog_fetch(zd,"threshold",draw_threshold);
10058
zdialog_fetch(zd,"pixcon",draw_pixcon);
10059
zdialog_fetch(zd,"chalk",draw_reverse);
10060
signal_thread(); // trigger update thread
10065
int draw_dialog_compl(zdialog *zd, int zstat) // draw dialog completion function
10067
if (zstat == 1) { edit_undo(); return 0; } // undo
10068
else if (zstat == 2) { edit_redo(); return 0; } // redo
10069
else if (zstat == 3) edit_done(); // done
10070
else edit_cancel(); // cancel or destroy
10071
zfree(draw_pixcon_map);
10076
// thread function - use multiple working threads
10078
void * draw_thread(void *)
10080
void * draw_wthread(void *arg);
10083
double threshold, contrast, trf;
10087
thread_idle_loop(); // wait for work or exit request
10089
threshold = 0.01 * draw_threshold; // range 0 to 1
10090
contrast = 0.01 * draw_contrast; // range 0 to 1
10092
for (ii = 0; ii < 256; ii++) // brightness transfer function
10094
trf = 1.0 - 0.003906 * (256 - ii) * contrast; // ramp-up from 0-1 to 1
10095
if (ii < 256 * threshold) trf = 0; // 0 if below threshold
10096
draw_trfunc[ii] = trf;
10099
for (ii = 0; ii < NWthreads; ii++) // start worker threads
10100
start_detached_thread(draw_wthread,&wtindex[ii]);
10101
zadd_locked(draw_busy,+NWthreads);
10103
while (draw_busy) zsleep(0.004); // wait for completion
10106
mwpaint2(); // update window
10109
return 0; // not executed, stop g++ warning
10113
void * draw_wthread(void *arg) // worker thread function
10115
void draw_1pix(int px, int py, int dist);
10117
int index = *((int *) (arg));
10118
int px, py, ii, dist;
10121
pixcon = draw_pixcon / 255.0; // 0-1 linear ramp
10122
draw_pixcon3 = 255 * pixcon * pixcon * pixcon; // 0-255 cubic ramp
10124
if (sa_Npixel) // process selected area
10126
for (ii = index; ii < sa_Npixel; ii += NWthreads) // process all enclosed pixels
10128
px = sa_pixel[ii].px;
10129
py = sa_pixel[ii].py;
10130
dist = sa_pixel[ii].dist;
10131
draw_1pix(px,py,dist);
10137
dist = sa_blend = 0;
10138
for (py = index; py < E1hh; py += NWthreads) // process all pixels
10139
for (px = 0; px < E1ww; px++)
10140
draw_1pix(px,py,dist);
10143
zadd_locked(draw_busy,-1);
10148
void draw_1pix(int px, int py, int dist) // process one pixel
10150
uint16 *pix1, *pix3;
10151
int bright1, bright2;
10152
int red1, green1, blue1;
10153
int red3, green3, blue3;
10155
double pixcon = draw_pixcon3;
10157
if (! px || ! py) return;
10158
if (px > E1ww-2 || py > E1hh-2) return;
10160
pix1 = bmpixel(E1rgb48,px,py); // input pixel
10161
pix3 = bmpixel(E3rgb48,px,py); // output pixel
10167
bright1 = ((red1 + green1 + blue1) / 3) >> 8; // old brightness 0-255
10168
bright2 = bright1 * draw_trfunc[bright1]; // new brightness 0-255
10170
int ii = py * E1ww + px;
10171
if (draw_pixcon_map[ii] < pixcon) bright2 = 255;
10173
if (pixcon > 1 && bright2 > draw_threshold) bright2 = 255; // empirical !!!
10175
if (draw_reverse) bright2 = 255 - bright2; // negate if "chalk"
10177
red3 = green3 = blue3 = bright2 << 8; // gray scale, new brightness
10179
if (dist < sa_blend) { // blend over distance sa_blend
10180
dnew = 1.0 * dist / sa_blend;
10182
red3 = dnew * red3 + dold * red1;
10183
green3 = dnew * green3 + dold * green1;
10184
blue3 = dnew * blue3 + dold * blue1;
10195
/**************************************************************************/
10197
// convert image to simulate an embossing
10199
int emboss_busy = 0;
10200
int emboss_radius, emboss_color;
10201
double emboss_depth;
10202
double emboss_kernel[20][20]; // support radius <= 9
10204
void m_emboss(GtkWidget *, const char *) // v.6.7
10206
int emboss_dialog_event(zdialog* zd, const char *event);
10207
int emboss_dialog_compl(zdialog* zd, int zstat);
10208
void * emboss_thread(void *);
10210
const char *title = ZTX("Simulate Embossing");
10212
if (! edit_setup(0,2)) return; // setup edit: no preview
10214
zdedit = zdialog_new(title,mWin,Bundo,Bredo,Bdone,Bcancel,null); // setup embossing dialog
10215
zdialog_add_widget(zdedit,"hbox","hb1","dialog",0,"space=10");
10216
zdialog_add_widget(zdedit,"label","lab1","hb1",ZTX("radius"),"space=5");
10217
zdialog_add_widget(zdedit,"spin","radius","hb1","0|9|1|0");
10218
zdialog_add_widget(zdedit,"label","lab2","hb1",ZTX("depth"),"space=5");
10219
zdialog_add_widget(zdedit,"spin","depth","hb1","0|99|1|0");
10220
zdialog_add_widget(zdedit,"check","color","hb1",ZTX("color"),"space=8");
10222
zdialog_run(zdedit,emboss_dialog_event,emboss_dialog_compl); // run dialog - parallel
10224
start_thread(emboss_thread,0); // start working thread
10229
// emboss dialog event and completion functions
10231
int emboss_dialog_event(zdialog *zd, const char *event) // emboss dialog event function
10233
zdialog_fetch(zd,"radius",emboss_radius); // get user inputs
10234
zdialog_fetch(zd,"depth",emboss_depth);
10235
zdialog_fetch(zd,"color",emboss_color);
10236
signal_thread(); // trigger update thread
10241
int emboss_dialog_compl(zdialog *zd, int zstat) // emboss dialog completion function
10243
if (zstat == 1) edit_undo(); // undo
10244
else if (zstat == 2) edit_redo(); // redo
10245
else if (zstat == 3) edit_done(); // done
10246
else edit_cancel(); // cancel or destroy
10251
// thread function - use multiple working threads
10253
void * emboss_thread(void *)
10255
void * emboss_wthread(void *arg);
10257
int ii, dx, dy, rad;
10258
double depth, kern, coeff;
10262
thread_idle_loop(); // wait for work or exit request
10264
rad = emboss_radius;
10265
depth = emboss_depth;
10267
coeff = 0.1 * depth / (rad * rad + 1);
10269
for (dy = -rad; dy <= rad; dy++) // build kernel with radius and depth
10270
for (dx = -rad; dx <= rad; dx++)
10272
kern = coeff * (dx + dy);
10273
emboss_kernel[dx+rad][dy+rad] = kern;
10276
emboss_kernel[rad][rad] = 1; // kernel center cell = 1
10278
for (ii = 0; ii < NWthreads; ii++) // start worker threads
10279
start_detached_thread(emboss_wthread,&wtindex[ii]);
10280
zadd_locked(emboss_busy,+NWthreads);
10282
while (emboss_busy) zsleep(0.004); // wait for completion
10285
mwpaint2(); // update window
10288
return 0; // not executed, stop g++ warning
10292
void * emboss_wthread(void *arg) // worker thread function
10294
void emboss_1pix(int px, int py, int dist);
10296
int index = *((int *) (arg));
10297
int px, py, ii, dist;
10299
if (sa_Npixel) // process selected area
10301
for (ii = index; ii < sa_Npixel; ii += NWthreads) // process all enclosed pixels
10303
px = sa_pixel[ii].px;
10304
py = sa_pixel[ii].py;
10305
dist = sa_pixel[ii].dist;
10306
emboss_1pix(px,py,dist);
10312
dist = sa_blend = 0;
10313
for (py = index; py < E1hh; py += NWthreads) // process all pixels
10314
for (px = 0; px < E1ww; px++)
10315
emboss_1pix(px,py,dist);
10318
zadd_locked(emboss_busy,-1);
10323
void emboss_1pix(int px, int py, int dist) // process one pixel
10325
uint16 *pix1, *pix3, *pixN;
10326
int bright1, bright3;
10327
int rgb, dx, dy, rad;
10328
double sumpix, kern, dold, dnew;
10330
rad = emboss_radius;
10332
if (px < rad || py < rad) return;
10333
if (px > E3ww-rad-1 || py > E3hh-rad-1) return;
10335
pix1 = bmpixel(E1rgb48,px,py); // input pixel
10336
pix3 = bmpixel(E3rgb48,px,py); // output pixel
10338
if (emboss_color) // keep color v.6.9
10340
for (rgb = 0; rgb < 3; rgb++)
10344
for (dy = -rad; dy <= rad; dy++) // loop surrounding block of pixels
10345
for (dx = -rad; dx <= rad; dx++)
10347
pixN = pix1 + (dy * E1ww + dx) * 3;
10348
kern = emboss_kernel[dx+rad][dy+rad];
10349
sumpix += kern * pixN[rgb];
10351
bright1 = pix1[rgb];
10353
if (bright3 < 0) bright3 = 0;
10354
if (bright3 > 65535) bright3 = 65535;
10356
if (dist < sa_blend) { // blend over distance sa_blend
10357
dnew = 1.0 * dist / sa_blend;
10359
bright3 = dnew * bright3 + dold * bright1;
10362
pix3[rgb] = bright3;
10367
else // use gray scale
10371
for (dy = -rad; dy <= rad; dy++) // loop surrounding block of pixels
10372
for (dx = -rad; dx <= rad; dx++)
10374
pixN = pix1 + (dy * E1ww + dx) * 3;
10375
kern = emboss_kernel[dx+rad][dy+rad];
10376
sumpix += kern * (pixN[0] + pixN[1] + pixN[2]);
10379
bright1 = 0.3333 * (pix1[0] + pix1[1] + pix1[2]);
10380
bright3 = 0.3333 * sumpix;
10381
if (bright3 < 0) bright3 = 0;
10382
if (bright3 > 65535) bright3 = 65535;
10384
if (dist < sa_blend) { // blend over distance sa_blend
10385
dnew = 1.0 * dist / sa_blend;
10387
bright3 = dnew * bright3 + dold * bright1;
10390
pix3[0] = pix3[1] = pix3[2] = bright3;
10397
/**************************************************************************/
10399
// convert image to simulate square tiles
10401
int tile_size, tile_gap;
10402
int tile_Npixels, tile_pixdone;
10403
uint16 *tile_pixmap = 0;
10406
void m_tiles(GtkWidget *, const char *) // new v.6.8
10408
int tile_dialog_event(zdialog *zd, const char *event);
10409
int tile_dialog_compl(zdialog *zd, int zstat);
10410
void * tile_thread(void *);
10412
if (! edit_setup(0,2)) return; // setup edit: no preview
10414
zdedit = zdialog_new(ZTX("Set Tile and Gap Size"),mWin,Bdone,Bcancel,null);
10415
zdialog_add_widget(zdedit,"hbox","hb1","dialog",0,"space=5");
10416
zdialog_add_widget(zdedit,"label","labt","hb1",ZTX("tile size"),"space=5");
10417
zdialog_add_widget(zdedit,"spin","size","hb1","1|99|1|5","space=5");
10418
zdialog_add_widget(zdedit,"button","apply","hb1",Bapply,"space=10");
10419
zdialog_add_widget(zdedit,"hbox","hb2","dialog",0,"space=5");
10420
zdialog_add_widget(zdedit,"label","labg","hb2",ZTX("tile gap"),"space=5");
10421
zdialog_add_widget(zdedit,"spin","gap","hb2","0|9|1|1","space=5");
10423
zdialog_run(zdedit,tile_dialog_event,tile_dialog_compl); // start dialog
10428
tile_pixmap = (uint16 *) zmalloc(E1ww*E1hh*6); // set up pixel color map
10429
memset(tile_pixmap,0,E1ww*E1hh*6);
10431
start_thread(tile_thread,0); // start working thread
10436
// tiles dialog event and completion callback functions
10438
int tile_dialog_compl(zdialog * zd, int zstat)
10440
if (zstat == 1) edit_done(); // done
10441
else edit_cancel(); // cancel or destroy
10442
zfree(tile_pixmap);
10447
int tile_dialog_event(zdialog * zd, const char *event)
10449
if (strNeq(event,"apply")) return 0;
10451
zdialog_fetch(zd,"size",tile_size); // get tile size
10452
zdialog_fetch(zd,"gap",tile_gap); // get tile gap
10454
if (tile_size < 2) {
10455
if (Fmodified) edit_undo(); // restore original image
10460
signal_thread(); // trigger working thread
10461
wait_thread_idle(); // wait for completion
10467
// image tiles thread function
10469
void * tile_thread(void *)
10472
int sumpix, red, green, blue;
10473
int ii, jj, px, py, qx, qy, dist;
10474
uint16 *pix1, *pix3;
10478
thread_idle_loop(); // wait for work or exit request
10480
sg = tile_size + tile_gap;
10483
for (py = 0; py < E1hh; py += sg) // initz. pixel color map for
10484
for (px = 0; px < E1ww; px += sg) // given pixel size
10486
sumpix = red = green = blue = 0;
10488
for (qy = py + gg; qy < py + sg; qy++) // get mean color for pixel block
10489
for (qx = px + gg; qx < px + sg; qx++)
10491
if (qy > E1hh-1 || qx > E1ww-1) continue;
10493
pix1 = bmpixel(E1rgb48,qx,qy);
10501
red = (red / sumpix);
10502
green = (green / sumpix);
10503
blue = (blue / sumpix);
10506
for (qy = py; qy < py + sg; qy++) // set color for pixels in block
10507
for (qx = px; qx < px + sg; qx++)
10509
if (qy > E1hh-1 || qx > E1ww-1) continue;
10511
jj = (qy * E1ww + qx) * 3;
10513
if (qx-px < gg || qy-py < gg) {
10514
tile_pixmap[jj] = tile_pixmap[jj+1] = tile_pixmap[jj+2] = 0;
10518
tile_pixmap[jj] = red;
10519
tile_pixmap[jj+1] = green;
10520
tile_pixmap[jj+2] = blue;
10524
if (! sa_Npixel) // process entire image
10526
for (py = 0; py < E3hh-1; py++) // loop all image pixels
10527
for (px = 0; px < E3ww-1; px++)
10529
pix3 = bmpixel(E3rgb48,px,py); // target pixel
10530
jj = (py * E3ww + px) * 3; // color map for (px,py)
10531
pix3[0] = tile_pixmap[jj];
10532
pix3[1] = tile_pixmap[jj+1];
10533
pix3[2] = tile_pixmap[jj+2];
10537
if (sa_Npixel) // process selected area
10539
for (ii = 0; ii < sa_Npixel; ii++) // process all enclosed pixels
10541
px = sa_pixel[ii].px;
10542
py = sa_pixel[ii].py;
10543
dist = sa_pixel[ii].dist;
10544
pix3 = bmpixel(E3rgb48,px,py);
10545
jj = (py * E3ww + px) * 3;
10546
pix3[0] = tile_pixmap[jj];
10547
pix3[1] = tile_pixmap[jj+1];
10548
pix3[2] = tile_pixmap[jj+2];
10553
mwpaint2(); // update window
10556
return 0; // not executed, stop g++ warning
10560
/**************************************************************************/
10562
// convert image to simulate a painting // v.7.0
10563
// processing a 10 megapixel image needs 140 MB of main memory // v.7.3 select area added
10565
namespace paint_namespace
10569
double color_match;
10578
spixstack *pixstack; // pixel group search memory
10579
int *pixgroup; // maps (px,py) to pixel group no.
10580
int *groupcount; // count of pixels in each group
10587
using namespace paint_namespace;
10590
void m_painting(GtkWidget *, const char *)
10592
int painting_dialog_event(zdialog *zd, const char *event);
10593
int painting_dialog_compl(zdialog *zd, int zstat);
10594
void * painting_thread(void *);
10596
if (! edit_setup(0,2)) return; // setup edit: no preview
10598
zdedit = zdialog_new(ZTX("Simulate Painting"),mWin,Bdone,Bcancel,null);
10600
zdialog_add_widget(zdedit,"hbox","hbcd","dialog",0,"space=5");
10601
zdialog_add_widget(zdedit,"label","lab1","hbcd",ZTX("color depth"),"space=5");
10602
zdialog_add_widget(zdedit,"spin","colordepth","hbcd","1|5|1|3","space=5");
10604
zdialog_add_widget(zdedit,"hbox","hbts","dialog",0,"space=5");
10605
zdialog_add_widget(zdedit,"label","labts","hbts",ZTX("target group area"),"space=5");
10606
zdialog_add_widget(zdedit,"spin","grouparea","hbts","0|999|1|100","space=5");
10608
zdialog_add_widget(zdedit,"hbox","hbcm","dialog",0,"space=5");
10609
zdialog_add_widget(zdedit,"label","labcm","hbcm",ZTX("req. color match"),"space=5");
10610
zdialog_add_widget(zdedit,"spin","colormatch","hbcm","0|99|1|50","space=5");
10612
zdialog_add_widget(zdedit,"hbox","hbbd","dialog",0,"space=5");
10613
zdialog_add_widget(zdedit,"label","labbd","hbbd",ZTX("borders"),"space=5");
10614
zdialog_add_widget(zdedit,"check","borders","hbbd",0,"space=5");
10616
zdialog_add_widget(zdedit,"hbox","hbbu","dialog",0,"space=5");
10617
zdialog_add_widget(zdedit,"button","apply","hbbu",Bapply,"space=5");
10618
zdialog_add_widget(zdedit,"button","undo","hbbu",Bundo,"space=5");
10620
zdialog_run(zdedit,painting_dialog_event,painting_dialog_compl); // run dialog - parallel
10622
start_thread(painting_thread,0); // start working thread
10627
// painting dialog event and completion callback functions
10629
int painting_dialog_event(zdialog *zd, const char *event)
10631
if (strEqu(event,"apply")) { // apply user settings
10632
zdialog_fetch(zd,"colordepth",color_depth); // color depth
10633
zdialog_fetch(zd,"grouparea",group_area); // target group area (pixels)
10634
zdialog_fetch(zd,"colormatch",color_match); // req. color match to combine groups
10635
zdialog_fetch(zd,"borders",borders); // borders wanted
10636
color_match = 0.01 * color_match; // scale 0 to 1
10640
if (strEqu(event,"undo")) edit_undo();
10645
int painting_dialog_compl(zdialog * zd, int zstat) // done or cancel button
10647
if (zstat == 1) edit_done();
10648
else edit_cancel();
10653
// painting thread function
10655
void * painting_thread(void *)
10657
void paint_colordepth();
10658
void paint_pixgroups();
10659
void paint_mergegroups();
10660
void paint_paintborders();
10661
void paint_blend();
10665
thread_idle_loop(); // wait for work or exit request
10667
paint_colordepth(); // set new color depth
10668
paint_pixgroups(); // group pixel patches of a color
10669
paint_mergegroups(); // merge smaller into larger groups
10670
paint_paintborders(); // add borders around groups
10671
paint_blend(); // blend edges of selected area
10674
mwpaint2(); // update window
10677
return 0; // not executed, stop g++ warning
10681
// set the specified color depth, 1-5 bits/color
10683
void paint_colordepth()
10685
int ii, px, py, rgb;
10687
uint16 m1, m2, val1, val3;
10688
uint16 *pix1, *pix3;
10690
m1 = 0xFFFF << (16 - color_depth); // 5 > 1111100000000000
10691
m2 = 0x8000 >> color_depth; // 5 > 0000010000000000
10693
fmag = 65535.0 / m1; // full brightness range
10695
if (! sa_Npixel) // process entire image
10697
for (py = 0; py < E3hh; py++) // loop all pixels
10698
for (px = 0; px < E3ww; px++)
10700
pix1 = bmpixel(E1rgb48,px,py); // input pixel
10701
pix3 = bmpixel(E3rgb48,px,py); // output pixel
10703
for (rgb = 0; rgb < 3; rgb++)
10706
if (val1 < m1) val3 = (val1 + m2) & m1; // round v.7.0
10708
val3 = uint(val3 * fmag);
10714
if (sa_Npixel) // process select area
10716
for (ii = 0; ii < sa_Npixel; ii++) // loop all pixels in area
10718
px = sa_pixel[ii].px;
10719
py = sa_pixel[ii].py;
10720
pix1 = bmpixel(E1rgb48,px,py); // input pixel
10721
pix3 = bmpixel(E3rgb48,px,py); // output pixel
10723
for (rgb = 0; rgb < 3; rgb++)
10726
if (val1 < m1) val3 = (val1 + m2) & m1;
10728
val3 = uint(val3 * fmag);
10738
// find all groups of contiguous pixels with the same color
10740
void paint_pixgroups()
10742
void paint_pushpix(int px, int py);
10745
int ii, kk, px, py;
10750
cc2 = cc1 * sizeof(int);
10751
pixgroup = (int *) zmalloc(cc2); // maps pixel to assigned group
10752
memset(pixgroup,0,cc2);
10754
if (sa_Npixel) cc1 = sa_Npixel;
10756
cc2 = cc1 * sizeof(spixstack);
10757
pixstack = (spixstack *) zmalloc(cc2); // memory stack for pixel search
10758
memset(pixstack,0,cc2);
10760
cc2 = cc1 * sizeof(int);
10761
groupcount = (int *) zmalloc(cc2); // counts pixels per group
10762
memset(groupcount,0,cc2);
10768
for (py = 0; py < E3hh; py++) // loop all pixels
10769
for (px = 0; px < E3ww; px++)
10771
kk = py * E3ww + px;
10772
if (pixgroup[kk]) continue; // already assigned to group
10774
pixgroup[kk] = ++group; // assign next group
10775
++groupcount[group];
10777
pix3 = bmpixel(E3rgb48,px,py);
10778
gcolor[0] = pix3[0];
10779
gcolor[1] = pix3[1];
10780
gcolor[2] = pix3[2];
10782
pixstack[0].px = px; // put pixel into stack with
10783
pixstack[0].py = py; // direction = right
10784
pixstack[0].direc = 'r';
10789
kk = Nstack - 1; // get last pixel in stack
10790
px = pixstack[kk].px;
10791
py = pixstack[kk].py;
10792
direc = pixstack[kk].direc;
10794
if (direc == 'x') {
10799
if (direc == 'r') { // push next right pixel into stack
10800
paint_pushpix(px,py); // if no group assigned and if
10801
pixstack[kk].direc = 'l'; // same color as group color
10805
if (direc == 'l') { // or next left pixel
10806
paint_pushpix(px,py);
10807
pixstack[kk].direc = 'a';
10811
if (direc == 'a') { // or next ahead pixel
10812
paint_pushpix(px,py);
10813
pixstack[kk].direc = 'x';
10822
for (ii = 0; ii < sa_Npixel; ii++) // loop all pixels in area
10824
px = sa_pixel[ii].px;
10825
py = sa_pixel[ii].py;
10827
kk = py * E3ww + px;
10828
if (pixgroup[kk]) continue; // already assigned to group
10830
pixgroup[kk] = ++group; // assign next group
10831
++groupcount[group];
10833
pix3 = bmpixel(E3rgb48,px,py);
10834
gcolor[0] = pix3[0];
10835
gcolor[1] = pix3[1];
10836
gcolor[2] = pix3[2];
10838
pixstack[0].px = px; // put pixel into stack with
10839
pixstack[0].py = py; // direction = right
10840
pixstack[0].direc = 'r';
10845
kk = Nstack - 1; // get last pixel in stack
10846
px = pixstack[kk].px;
10847
py = pixstack[kk].py;
10848
direc = pixstack[kk].direc;
10850
if (direc == 'x') {
10855
if (direc == 'r') { // push next right pixel into stack
10856
paint_pushpix(px,py); // if no group assigned and if
10857
pixstack[kk].direc = 'l'; // same color as group color
10861
if (direc == 'l') { // or next left pixel
10862
paint_pushpix(px,py);
10863
pixstack[kk].direc = 'a';
10867
if (direc == 'a') { // or next ahead pixel
10868
paint_pushpix(px,py);
10869
pixstack[kk].direc = 'x';
10880
// push a pixel into the stack memory if it is not already assigned
10881
// to another group and it has the same color as the current group
10883
void paint_pushpix(int px, int py)
10885
int kk, ppx, ppy, npx, npy;
10889
kk = Nstack - 2; // get prior pixel in stack
10890
ppx = pixstack[kk].px;
10891
ppy = pixstack[kk].py;
10894
ppx = px - 1; // if only one, assume prior = left
10898
if (direc == 'r') { // get pixel in direction right
10899
npx = px + ppy - py;
10900
npy = py + px - ppx;
10902
else if (direc == 'l') { // or left
10903
npx = px + py - ppy;
10904
npy = py + ppx - px;
10906
else if (direc == 'a') { // or ahead
10907
npx = px + px - ppx;
10908
npy = py + py - ppy;
10910
else npx = npy = -1; // stop warning
10912
if (npx < 0 || npx >= E3ww) return; // pixel off the edge
10913
if (npy < 0 || npy >= E3hh) return;
10915
kk = npy * E3ww + npx;
10918
if (! sa_pixisin[kk]) return; // pixel outside area
10920
if (pixgroup[kk]) return; // pixel already assigned
10922
pix3 = bmpixel(E3rgb48,npx,npy);
10923
if (pix3[0] != gcolor[0] || pix3[1] != gcolor[1] // not same color as group
10924
|| pix3[2] != gcolor[2]) return;
10926
pixgroup[kk] = group; // assign pixel to group
10927
++groupcount[group];
10929
kk = Nstack++; // put pixel into stack
10930
pixstack[kk].px = npx;
10931
pixstack[kk].py = npy;
10932
pixstack[kk].direc = 'r'; // direction = right
10938
// merge small pixel groups into adjacent larger groups with best color match
10940
void paint_mergegroups()
10942
int ii, jj, kk, px, py, npx, npy;
10943
int nccc, mcount, group2;
10944
double ff = 1.0 / 65536.0;
10945
double fred, fgreen, fblue, match;
10946
int nnpx[4] = { 0, -1, +1, 0 };
10947
int nnpy[4] = { -1, 0, 0, +1 };
10948
uint16 *pix3, *pixN;
10956
snewgroup *newgroup;
10958
nccc = (group + 1) * sizeof(snewgroup);
10959
newgroup = (snewgroup *) zmalloc(nccc);
10965
memset(newgroup,0,nccc);
10967
for (py = 0; py < E3hh; py++) // loop all pixels
10968
for (px = 0; px < E3ww; px++)
10970
kk = E3ww * py + px; // get assigned group
10971
group = pixgroup[kk];
10972
if (groupcount[group] >= group_area) continue; // group count large enough
10974
pix3 = bmpixel(E3rgb48,px,py);
10976
for (jj = 0; jj < 4; jj++) // get 4 neighbor pixels
10978
npx = px + nnpx[jj];
10979
npy = py + nnpy[jj];
10981
if (npx < 0 || npx >= E3ww) continue; // off the edge
10982
if (npy < 0 || npy >= E3hh) continue;
10984
kk = E3ww * npy + npx;
10985
if (pixgroup[kk] == group) continue; // in same group
10987
pixN = bmpixel(E3rgb48,npx,npy); // match color of group neighbor
10988
fred = ff * abs(pix3[0] - pixN[0]); // to color of group
10989
fgreen = ff * abs(pix3[1] - pixN[1]);
10990
fblue = ff * abs(pix3[2] - pixN[2]);
10991
match = (1.0 - fred) * (1.0 - fgreen) * (1.0 - fblue); // color match, 0 to 1.0
10992
if (match < color_match) continue;
10994
if (match > newgroup[group].match) {
10995
newgroup[group].match = match; // remember best match
10996
newgroup[group].group = pixgroup[kk]; // and corresp. group no.
10997
newgroup[group].pixM[0] = pixN[0]; // and corresp. new color
10998
newgroup[group].pixM[1] = pixN[1];
10999
newgroup[group].pixM[2] = pixN[2];
11006
for (py = 0; py < E3hh; py++) // loop all pixels
11007
for (px = 0; px < E3ww; px++)
11009
kk = E3ww * py + px;
11010
group = pixgroup[kk]; // test for new group assignment
11011
group2 = newgroup[group].group;
11012
if (! group2) continue;
11014
if (groupcount[group] > groupcount[group2]) continue; // accept only bigger new group
11016
pixgroup[kk] = group2; // make new group assignment
11017
--groupcount[group];
11018
++groupcount[group2];
11020
pix3 = bmpixel(E3rgb48,px,py); // make new color assignment
11021
pix3[0] = newgroup[group].pixM[0];
11022
pix3[1] = newgroup[group].pixM[1];
11023
pix3[2] = newgroup[group].pixM[2];
11028
if (mcount == 0) break;
11036
memset(newgroup,0,nccc);
11038
for (ii = 0; ii < sa_Npixel; ii++) // loop all pixels in area
11040
px = sa_pixel[ii].px;
11041
py = sa_pixel[ii].py;
11043
kk = E3ww * py + px; // get assigned group
11044
group = pixgroup[kk];
11045
if (groupcount[group] >= group_area) continue; // group count large enough
11047
pix3 = bmpixel(E3rgb48,px,py);
11049
for (jj = 0; jj < 4; jj++) // get 4 neighbor pixels
11051
npx = px + nnpx[jj];
11052
npy = py + nnpy[jj];
11054
if (npx < 0 || npx >= E3ww) continue; // off the edge
11055
if (npy < 0 || npy >= E3hh) continue;
11057
kk = E3ww * npy + npx;
11058
if (! sa_pixisin[kk]) continue; // pixel outside area
11059
if (pixgroup[kk] == group) continue; // already in same group
11061
pixN = bmpixel(E3rgb48,npx,npy); // match color of group neighbor
11062
fred = ff * abs(pix3[0] - pixN[0]); // to color of group
11063
fgreen = ff * abs(pix3[1] - pixN[1]);
11064
fblue = ff * abs(pix3[2] - pixN[2]);
11065
match = (1.0 - fred) * (1.0 - fgreen) * (1.0 - fblue); // color match, 0 to 1.0
11066
if (match < color_match) continue;
11068
if (match > newgroup[group].match) {
11069
newgroup[group].match = match; // remember best match
11070
newgroup[group].group = pixgroup[kk]; // and corresp. group no.
11071
newgroup[group].pixM[0] = pixN[0]; // and corresp. new color
11072
newgroup[group].pixM[1] = pixN[1];
11073
newgroup[group].pixM[2] = pixN[2];
11080
for (ii = 0; ii < sa_Npixel; ii++) // loop all pixels in area
11082
px = sa_pixel[ii].px;
11083
py = sa_pixel[ii].py;
11085
kk = E3ww * py + px;
11086
group = pixgroup[kk]; // test for new group assignment
11087
group2 = newgroup[group].group;
11088
if (! group2) continue;
11090
if (groupcount[group] > groupcount[group2]) continue; // accept only bigger new group
11092
pixgroup[kk] = group2; // make new group assignment
11093
--groupcount[group];
11094
++groupcount[group2];
11096
pix3 = bmpixel(E3rgb48,px,py); // make new color assignment
11097
pix3[0] = newgroup[group].pixM[0];
11098
pix3[1] = newgroup[group].pixM[1];
11099
pix3[2] = newgroup[group].pixM[2];
11104
if (mcount == 0) break;
11117
// paint borders between the groups of contiguous pixels
11119
void paint_paintborders()
11121
int ii, kk, px, py, cc;
11122
uint16 *pix3, *pixL, *pixA;
11124
if (! borders) return;
11127
char * pixblack = zmalloc(cc);
11128
memset(pixblack,0,cc);
11132
for (py = 1; py < E3hh; py++) // loop all pixels
11133
for (px = 1; px < E3ww; px++) // omit top and left
11135
pix3 = bmpixel(E3rgb48,px,py); // output pixel
11136
pixL = bmpixel(E3rgb48,px-1,py); // pixel to left
11137
pixA = bmpixel(E3rgb48,px,py-1); // pixel above
11139
if (pix3[0] != pixL[0] || pix3[1] != pixL[1] || pix3[2] != pixL[2])
11141
kk = E3ww * py + px-1; // have horiz. transition
11142
if (pixblack[kk]) continue;
11148
if (pix3[0] != pixA[0] || pix3[1] != pixA[1] || pix3[2] != pixA[2])
11150
kk = E3ww * (py-1) + px; // have vertical transition
11151
if (pixblack[kk]) continue;
11157
for (py = 1; py < E3hh; py++)
11158
for (px = 1; px < E3ww; px++)
11160
kk = E3ww * py + px;
11161
if (! pixblack[kk]) continue;
11162
pix3 = bmpixel(E3rgb48,px,py);
11163
pix3[0] = pix3[1] = pix3[2] = 0;
11169
for (ii = 0; ii < sa_Npixel; ii++)
11171
px = sa_pixel[ii].px;
11172
py = sa_pixel[ii].py;
11173
if (px < 1 || py < 1) continue;
11175
pix3 = bmpixel(E3rgb48,px,py);
11176
pixL = bmpixel(E3rgb48,px-1,py);
11177
pixA = bmpixel(E3rgb48,px,py-1);
11179
if (pix3[0] != pixL[0] || pix3[1] != pixL[1] || pix3[2] != pixL[2])
11181
kk = E3ww * py + px-1;
11182
if (pixblack[kk]) continue;
11188
if (pix3[0] != pixA[0] || pix3[1] != pixA[1] || pix3[2] != pixA[2])
11190
kk = E3ww * (py-1) + px;
11191
if (pixblack[kk]) continue;
11197
for (ii = 0; ii < sa_Npixel; ii++)
11199
px = sa_pixel[ii].px;
11200
py = sa_pixel[ii].py;
11201
if (px < 1 || py < 1) continue;
11203
kk = E3ww * py + px;
11204
if (! pixblack[kk]) continue;
11205
pix3 = bmpixel(E3rgb48,px,py);
11206
pix3[0] = pix3[1] = pix3[2] = 0;
11215
// blend edges of selected area
11219
int ii, px, py, rgb, dist;
11220
uint16 *pix1, *pix3;
11223
if (sa_Npixel && sa_blend > 0)
11225
for (ii = 0; ii < sa_Npixel; ii++) // loop all pixels in area
11227
dist = sa_pixel[ii].dist;
11228
if (dist >= sa_blend) continue;
11230
px = sa_pixel[ii].px;
11231
py = sa_pixel[ii].py;
11232
pix1 = bmpixel(E1rgb48,px,py); // input pixel
11233
pix3 = bmpixel(E3rgb48,px,py); // output pixel
11235
f2 = 1.0 * dist / sa_blend; // changes over distance sa_blend
11238
for (rgb = 0; rgb < 3; rgb++)
11239
pix3[rgb] = int(f1 * pix1[rgb] + f2 * pix3[rgb]);
11247
/**************************************************************************/
11249
// pixel edit function - edit individual pixels
11251
void pixed_mousefunc();
11252
void pixed_dopixels(int px, int py);
11253
void pixed_saveundo(int px, int py);
11254
void pixed_undo1();
11255
void pixed_freeundo();
11261
double pixed_kernel[200][200]; // radius <= 99
11263
int pixed_undototpix = 0; // total undo pixel blocks
11264
int pixed_undototmem = 0; // total undo memory allocated
11265
int pixed_undoseq = 0; // undo sequence no.
11266
char pixed_undomemmessage[100]; // translated undo memory message
11268
typedef struct { // pixel block before edit
11269
int seq; // undo sequence no.
11270
uint16 npix; // no. pixels in this block
11271
uint16 px, py; // center pixel (radius org.)
11272
uint16 radius; // radius of pixel block
11273
uint16 pixel[][3]; // array of pixel[npix][3]
11276
pixed_savepix **pixed_undopixmem = 0; // array of *pixed_savepix
11279
void m_pixedit(GtkWidget *, const char *)
11281
int pixed_dialog_event(zdialog* zd, const char *event);
11282
int pixed_dialog_compl(zdialog* zd, int zstat);
11284
char undomemmessage[100];
11286
if (! edit_setup(0,1)) return; // setup edit: no preview
11288
strncpy0(pixed_undomemmessage,ZTX("Undo Memory %d%c"),99); // translate undo memory message
11290
zdedit = zdialog_new(ZTX("Edit Pixels"),mWin,Bdone,Bcancel,null); // setup pixel edit dialog
11291
zdialog_add_widget(zdedit,"hbox","hbc","dialog",0,"space=5");
11292
zdialog_add_widget(zdedit,"label","labc","hbc",ZTX("color"),"space=8");
11293
zdialog_add_widget(zdedit,"colorbutt","color","hbc","100|100|100");
11294
zdialog_add_widget(zdedit,"label","space","hbc",0,"space=10");
11295
zdialog_add_widget(zdedit,"radio","radio1","hbc",ZTX("pick"),"space=3");
11296
zdialog_add_widget(zdedit,"radio","radio2","hbc",ZTX("paint"),"space=3");
11297
zdialog_add_widget(zdedit,"radio","radio3","hbc",ZTX("erase"),"space=3");
11298
zdialog_add_widget(zdedit,"hbox","hbbr","dialog",0,"space=5");
11299
zdialog_add_widget(zdedit,"vbox","vbbr1","hbbr",0,"homog|space=3");
11300
zdialog_add_widget(zdedit,"vbox","vbbr2","hbbr",0,"homog|space=3");
11301
zdialog_add_widget(zdedit,"label","space","hbbr",0,"space=10");
11302
zdialog_add_widget(zdedit,"vbox","vbbr3","hbbr",0,"homog|space=3");
11303
zdialog_add_widget(zdedit,"hbox","hbrad","vbbr1",0,"space=3");
11304
zdialog_add_widget(zdedit,"label","space","hbrad",0,"expand");
11305
zdialog_add_widget(zdedit,"label","labbr","hbrad",ZTX("paintbrush radius"));
11306
zdialog_add_widget(zdedit,"label","labtc","vbbr1",ZTX("transparency center"));
11307
zdialog_add_widget(zdedit,"label","labte","vbbr1",ZTX("transparency edge"));
11308
zdialog_add_widget(zdedit,"spin","radius","vbbr2","1|99|1|2");
11309
zdialog_add_widget(zdedit,"spin","trcent","vbbr2","0|99|1|60");
11310
zdialog_add_widget(zdedit,"spin","tredge","vbbr2","0|99|1|99");
11311
zdialog_add_widget(zdedit,"button","susp-resm","vbbr3",Bsuspend);
11312
zdialog_add_widget(zdedit,"button","undlast","vbbr3",Bundolast);
11313
zdialog_add_widget(zdedit,"button","undall","vbbr3",Bundoall);
11314
zdialog_add_widget(zdedit,"label","labmem","dialog");
11316
zdialog_run(zdedit,pixed_dialog_event,pixed_dialog_compl); // run dialog - parallel
11318
zdialog_send_event(zdedit,"radius"); // get kernel initialized
11320
snprintf(undomemmessage,99,pixed_undomemmessage,0,'%'); // stuff undo memory status
11321
zdialog_stuff(zdedit,"labmem",undomemmessage);
11323
pixed_RGB[0] = pixed_RGB[1] = pixed_RGB[2] = 100; // initialize color
11325
pixed_mode = 1; // mode = pick color
11326
pixed_suspend = 0; // not suspended
11328
pixed_undopixmem = 0; // no undo data
11329
pixed_undototpix = 0;
11330
pixed_undototmem = 0;
11333
mouseCBfunc = pixed_mousefunc; // connect mouse function
11334
Mcapture = 1; // capture mouse clicks
11339
// pixedit dialog event and completion functions
11341
int pixed_dialog_compl(zdialog *zd, int zstat) // pixedit dialog completion function
11343
if (zstat == 1) edit_done(); // done
11344
else edit_cancel(); // cancel or destroy
11346
paint_toparc(2); // remove brush outline v.8.3
11347
Mcapture = 0; // disconnect mouse
11349
pixed_freeundo(); // free undo memory
11354
int pixed_dialog_event(zdialog *zd, const char *event) // pixedit dialog event function
11358
int radius, dx, dy, brad;
11359
double rad, kern, trcent, tredge;
11361
zdialog_fetch(zd,"radio1",brad); // pick v.6.8
11362
if (brad) pixed_mode = 1;
11363
zdialog_fetch(zd,"radio2",brad); // paint
11364
if (brad) pixed_mode = 2;
11365
zdialog_fetch(zd,"radio3",brad); // erase
11366
if (brad) pixed_mode = 3;
11368
if (strEqu(event,"color"))
11370
zdialog_fetch(zd,"color",color,19); // get color from color wheel
11371
pp = strField(color,"|",1);
11372
if (pp) pixed_RGB[0] = atoi(pp);
11373
pp = strField(color,"|",2);
11374
if (pp) pixed_RGB[1] = atoi(pp);
11375
pp = strField(color,"|",3);
11376
if (pp) pixed_RGB[2] = atoi(pp);
11379
if (strstr("radius trcent tredge",event)) // get new brush attributes
11381
zdialog_fetch(zd,"radius",radius); // radius
11382
zdialog_fetch(zd,"trcent",trcent); // center transparency v.7.8
11383
zdialog_fetch(zd,"tredge",tredge); // edge transparency
11385
pixed_radius = radius;
11386
trcent = 0.01 * trcent; // scale 0 ... 1
11387
tredge = 0.01 * tredge;
11388
tredge = (1 - trcent) * (1 - tredge);
11389
tredge = 1 - tredge;
11390
trcent = sqrt(trcent); // speed up the curve
11391
tredge = sqrt(tredge);
11393
for (dy = -radius; dy <= radius; dy++) // build kernel
11394
for (dx = -radius; dx <= radius; dx++)
11396
rad = sqrt(dx*dx + dy*dy);
11397
kern = (radius - rad) / radius; // 1 ... 0
11398
kern = kern * (trcent - tredge) + tredge; // trcent ... tredge
11399
if (rad > radius) kern = 1;
11400
if (kern < 0) kern = 0;
11401
if (kern > 1) kern = 1;
11402
pixed_kernel[dx+radius][dy+radius] = kern;
11406
if (strEqu(event,"undlast")) // undo last edit (click or drag)
11407
pixed_undo1(); // v.7.8
11409
if (strEqu(event,"undall")) { // undo all edits v.7.8
11414
if (strEqu(event,"susp-resm")) // toggle suspend / resume v.7.8
11416
if (pixed_suspend) {
11418
mouseCBfunc = pixed_mousefunc; // connect mouse function
11420
zdialog_stuff(zd,"susp-resm",Bsuspend);
11424
mouseCBfunc = 0; // disconnect mouse function
11426
zdialog_stuff(zd,"susp-resm",Bresume);
11434
// pixel edit mouse function
11436
void pixed_mousefunc()
11438
static int pmxdown = 0, pmydown = 0;
11443
toparcx = Mxposn - pixed_radius; // define brush outline circle
11444
toparcy = Myposn - pixed_radius; // v.8.3
11445
toparcw = toparch = 2 * pixed_radius;
11446
if (pixed_mode == 1) toparc = 0;
11448
if (toparc) paint_toparc(3);
11450
if (LMclick) // left mouse click
11456
if (pixed_mode == 1) // pick new color from image
11458
ppix3 = bmpixel(E3rgb48,px,py);
11459
pixed_RGB[0] = ppix3[0] / 256;
11460
pixed_RGB[1] = ppix3[1] / 256;
11461
pixed_RGB[2] = ppix3[2] / 256;
11462
snprintf(color,19,"%d|%d|%d",pixed_RGB[0],pixed_RGB[1],pixed_RGB[2]);
11463
if (zdedit) zdialog_stuff(zdedit,"color",color);
11465
else { // paint or erase
11466
pixed_undoseq++; // new undo seq. no.
11467
pixed_saveundo(px,py); // save for poss. undo
11468
pixed_dopixels(px,py); // do 1 block of pixels
11472
if (Mxdrag || Mydrag) // drag in progress
11476
Mxdrag = Mydrag = 0;
11478
if (Mxdown != pmxdown || Mydown != pmydown) { // new drag
11479
pixed_undoseq++; // new undo seq. no.
11483
pixed_saveundo(px,py); // save for poss. undo
11484
pixed_dopixels(px,py); // do 1 block of pixels
11491
// paint or erase 1 block of pixels within radius of px, py
11493
void pixed_dopixels(int px, int py)
11495
uint16 *ppix1, *ppix3;
11496
int radius, dx, dy;
11497
int red, green, blue;
11500
radius = pixed_radius;
11502
red = 256 * pixed_RGB[0];
11503
green = 256 * pixed_RGB[1];
11504
blue = 256 * pixed_RGB[2];
11506
for (dy = -radius; dy <= radius; dy++) // loop surrounding block of pixels
11507
for (dx = -radius; dx <= radius; dx++)
11509
if (px + dx < 0 || px + dx > E3ww-1) continue; // v.7.5
11510
if (py + dy < 0 || py + dy > E3hh-1) continue;
11512
kern = pixed_kernel[dx+radius][dy+radius];
11513
ppix1 = bmpixel(E1rgb48,(px+dx),(py+dy)); // original image pixel
11514
ppix3 = bmpixel(E3rgb48,(px+dx),(py+dy)); // edited image pixel
11516
if (pixed_mode == 2) // color pixels transparently
11518
ppix3[0] = (1.0 - kern) * red + kern * ppix3[0];
11519
ppix3[1] = (1.0 - kern) * green + kern * ppix3[1];
11520
ppix3[2] = (1.0 - kern) * blue + kern * ppix3[2];
11524
if (pixed_mode == 3) // restore org. pixels transparently
11526
ppix3[0] = (1.0 - kern) * ppix1[0] + kern * ppix3[0];
11527
ppix3[1] = (1.0 - kern) * ppix1[1] + kern * ppix3[1];
11528
ppix3[2] = (1.0 - kern) * ppix1[2] + kern * ppix3[2];
11537
// save 1 block of pixels for possible undo
11539
void pixed_saveundo(int px, int py)
11541
int npix, radius, dx, dy;
11543
pixed_savepix *ppixsave1;
11544
char undomemmessage[100];
11546
static int ppercent = 0;
11548
if (! pixed_undopixmem) // first call
11550
pixed_undopixmem = (pixed_savepix **) zmalloc(pixed_undomaxpix * sizeof(void *));
11551
pixed_undototpix = 0;
11552
pixed_undototmem = 0;
11555
if (pixed_undototmem > pixed_undomaxmem)
11557
zmessageACK(ZTX("Undo memory limit has been reached (100 MB). \n"
11558
"Save work with [done], then resume editing."));
11559
Mdrag = 0; // stop mouse drag v.8.3
11563
radius = pixed_radius;
11566
for (dy = -radius; dy <= radius; dy++) // count pixels in block
11567
for (dx = -radius; dx <= radius; dx++)
11569
if (px + dx < 0 || px + dx > E3ww-1) continue;
11570
if (py + dy < 0 || py + dy > E3hh-1) continue;
11574
ppixsave1 = (pixed_savepix *) zmalloc(npix * 6 + 12); // allocate memory for block
11575
pixed_undopixmem[pixed_undototpix] = ppixsave1;
11576
pixed_undototpix += 1;
11577
pixed_undototmem += npix * 6 + 12;
11579
ppixsave1->seq = pixed_undoseq; // save pixel block poop
11580
ppixsave1->npix = npix;
11581
ppixsave1->px = px;
11582
ppixsave1->py = py;
11583
ppixsave1->radius = radius;
11587
for (dy = -radius; dy <= radius; dy++) // save pixels in block
11588
for (dx = -radius; dx <= radius; dx++)
11590
if (px + dx < 0 || px + dx > E3ww-1) continue;
11591
if (py + dy < 0 || py + dy > E3hh-1) continue;
11592
ppix3 = bmpixel(E3rgb48,(px+dx),(py+dy)); // edited image pixel
11593
ppixsave1->pixel[npix][0] = ppix3[0];
11594
ppixsave1->pixel[npix][1] = ppix3[1];
11595
ppixsave1->pixel[npix][2] = ppix3[2];
11599
mempercent = int(100.0 * pixed_undototmem / pixed_undomaxmem); // update undo memory status
11600
if (mempercent != ppercent) {
11601
ppercent = mempercent;
11602
snprintf(undomemmessage,99,pixed_undomemmessage,mempercent,'%');
11603
zdialog_stuff(zdedit,"labmem",undomemmessage);
11610
// undo last undo sequence number
11614
int pindex, npix, radius, px, py, dx, dy;
11616
pixed_savepix *ppixsave1;
11617
char undomemmessage[100];
11620
pindex = pixed_undototpix;
11625
ppixsave1 = pixed_undopixmem[pindex];
11626
if (ppixsave1->seq != pixed_undoseq) break;
11627
px = ppixsave1->px;
11628
py = ppixsave1->py;
11629
radius = ppixsave1->radius;
11633
for (dy = -radius; dy <= radius; dy++)
11634
for (dx = -radius; dx <= radius; dx++)
11636
if (px + dx < 0 || px + dx > E3ww-1) continue;
11637
if (py + dy < 0 || py + dy > E3hh-1) continue;
11638
ppix3 = bmpixel(E3rgb48,(px+dx),(py+dy));
11639
ppix3[0] = ppixsave1->pixel[npix][0];
11640
ppix3[1] = ppixsave1->pixel[npix][1];
11641
ppix3[2] = ppixsave1->pixel[npix][2];
11645
npix = ppixsave1->npix;
11647
pixed_undopixmem[pindex] = 0;
11648
pixed_undototmem -= (npix * 6 + 12);
11649
--pixed_undototpix;
11652
if (pixed_undoseq > 0) --pixed_undoseq;
11654
mempercent = int(100.0 * pixed_undototmem / pixed_undomaxmem); // update undo memory status
11655
snprintf(undomemmessage,99,pixed_undomemmessage,mempercent,'%');
11656
zdialog_stuff(zdedit,"labmem",undomemmessage);
11663
// free all undo memory
11665
void pixed_freeundo()
11668
pixed_savepix *ppixsave1;
11669
char undomemmessage[100];
11671
pindex = pixed_undototpix;
11676
ppixsave1 = pixed_undopixmem[pindex];
11680
if (pixed_undopixmem) zfree(pixed_undopixmem);
11681
pixed_undopixmem = 0;
11684
pixed_undototpix = 0;
11685
pixed_undototmem = 0;
11688
snprintf(undomemmessage,99,pixed_undomemmessage,0,'%'); // undo memory = 0%
11689
zdialog_stuff(zdedit,"labmem",undomemmessage);
11696
/**************************************************************************/
11698
// Make an HDR (high dynamic range) image from two images of the same
11699
// subject with different exposure levels. The HDR image has expanded
11700
// visibility of detail in both the brightest and darkest areas.
11703
void * HDR_align_thread(void *);
11704
void * HDR_dialog_thread(void *);
11705
void HDR_combine(int ww);
11707
int HDR_brcurve_adjust(GtkWidget *, GdkEventButton *);
11708
int HDR_brcurve_draw(GtkWidget *);
11710
int HDR_align_stat = 0;
11712
int HDR_br_np = 0; // up to 50 anchor points
11713
double HDR_br_px[50], HDR_br_py[50]; // for image weight graph
11714
double HDR_weights[256]; // curve, weight per brightness
11715
double HDR_bratio = 1;
11717
GtkWidget *HDR_brcurve;
11722
void m_HDR(GtkWidget *, const char *)
11726
if (! edit_setup(0,0)) return; // setup edit: no preview
11728
file2 = zgetfile(ZTX("Select image to combine"),image_file,"open"); // get 2nd HDR image
11734
Grgb48 = image_load(file2,48); // load and validate image
11743
if (Fww != Gww || Fhh != Ghh) {
11744
zmessageACK(ZTX("2nd image not same size as 1st image"));
11751
start_thread(HDR_align_thread,0); // start thread to align images
11752
wrapup_thread(0); // wait for thread exit
11754
if (HDR_align_stat != 1) { // check thread exit status
11756
Nalign = 0; // reset align counter
11757
RGB_free(A1rgb48); // free alignment images
11759
RGB_free(Grgb48); // free 2nd input image pixmap
11760
if (redpixels) zfree(redpixels); // free edge-pixel flags
11765
int HDR_dialog_event(zdialog *zd, const char *name); // dialog for user adjustment
11766
int HDR_dialog_compl(zdialog *zd, int zstat);
11768
const char *weightmess = ZTX("Image Weights per Brightness Level");
11770
zdedit = zdialog_new(ZTX("HDR Image Weights"),mWin,Bdone,Bcancel,null);
11771
zdialog_add_widget(zdedit,"label","labt","dialog",weightmess,"space=5");
11772
zdialog_add_widget(zdedit,"hbox","hb1","dialog",0,"expand");
11773
zdialog_add_widget(zdedit,"vbox","vb1","hb1",0,"space=10");
11774
zdialog_add_widget(zdedit,"vbox","vb2","hb1",0,"expand|space=10");
11776
zdialog_add_widget(zdedit,"label","lab11","vb1",ZTX("Input Images"));
11777
zdialog_add_widget(zdedit,"hbox","hb12","vb1",0,"expand");
11778
zdialog_add_widget(zdedit,"vbox","vb11","hb12",0,"expand|space=10");
11779
zdialog_add_widget(zdedit,"vbox","vb12","hb12",0,"expand");
11780
zdialog_add_widget(zdedit,"label","lab111","vb11","image 1");
11781
zdialog_add_widget(zdedit,"label","lab112","vb11",0,"expand");
11782
zdialog_add_widget(zdedit,"label","lab113","vb11","image 2");
11783
zdialog_add_widget(zdedit,"label","lab121","vb12","100 %");
11784
zdialog_add_widget(zdedit,"label","lab122","vb12",0,"expand");
11785
zdialog_add_widget(zdedit,"label","lab123","vb12","50/50","expand");
11786
zdialog_add_widget(zdedit,"label","lab124","vb12",0,"expand");
11787
zdialog_add_widget(zdedit,"label","lab125","vb12","100 %");
11788
zdialog_add_widget(zdedit,"label","lab13","vb1"," ");
11790
zdialog_add_widget(zdedit,"label","lab21","vb2",ZTX("Output Image"));
11791
zdialog_add_widget(zdedit,"frame","fr22","vb2",0,"expand");
11792
zdialog_add_widget(zdedit,"hbox","hb23","vb2",0);
11793
zdialog_add_widget(zdedit,"label","lab231","hb23",Bdarker);
11794
zdialog_add_widget(zdedit,"label","lab232","hb23",0,"expand");
11795
zdialog_add_widget(zdedit,"label","lab233","hb23",Blighter);
11797
zdialog_add_widget(zdedit,"hbox","hbb","dialog",0,"space=10"); // convenience buttons v.8.6
11798
zdialog_add_widget(zdedit,"label","space","hbb",0,"expand");
11799
zdialog_add_widget(zdedit,"button","b100/0","hbb"," 100/0 ");
11800
zdialog_add_widget(zdedit,"button","b50/50","hbb"," 50/50 ");
11801
zdialog_add_widget(zdedit,"button","b0/100","hbb"," 0/100 ");
11802
zdialog_add_widget(zdedit,"label","space","hbb"," ");
11804
GtkWidget *brframe = zdialog_widget(zdedit,"fr22"); // add drawing area to frame
11805
GtkWidget *brcurve = gtk_drawing_area_new();
11806
gtk_container_add(GTK_CONTAINER(brframe),brcurve);
11807
HDR_brcurve = brcurve;
11809
gtk_widget_add_events(brcurve,GDK_BUTTON_PRESS_MASK); // connect drawing area events
11810
gtk_widget_add_events(brcurve,GDK_BUTTON_RELEASE_MASK);
11811
gtk_widget_add_events(brcurve,GDK_BUTTON1_MOTION_MASK);
11812
G_SIGNAL(brcurve,"motion-notify-event",HDR_brcurve_adjust,0)
11813
G_SIGNAL(brcurve,"button-press-event",HDR_brcurve_adjust,0)
11814
G_SIGNAL(brcurve,"expose-event",HDR_brcurve_draw,0)
11816
HDR_bratio = 0; // get mean brightness ratio
11817
for (int ii = 0; ii < 256; ii++)
11818
HDR_bratio += Bratios2[0][ii] + Bratios2[1][ii] + Bratios2[2][ii];
11819
HDR_bratio = HDR_bratio / 256 / 3;
11821
HDR_br_np = 3; // initz. image weights curve,
11822
HDR_br_px[0] = 5; // 3 spline anchor points
11823
HDR_br_px[1] = 127;
11824
HDR_br_px[2] = 250;
11826
if (HDR_bratio < 1) { // image1 < image2, ramp up
11827
HDR_br_py[0] = 0.02;
11828
HDR_br_py[1] = 0.50;
11829
HDR_br_py[2] = 0.98;
11831
else { // image1 > image2, ramp down
11832
HDR_br_py[0] = 0.98;
11833
HDR_br_py[1] = 0.50;
11834
HDR_br_py[2] = 0.02;
11837
spline1(HDR_br_np,HDR_br_px,HDR_br_py); // make curve fitting anchor points
11839
zdialog_resize(zdedit,450,350);
11840
zdialog_run(zdedit,HDR_dialog_event,HDR_dialog_compl); // run dialog, parallel
11841
start_thread(HDR_dialog_thread,0); // start compute thread
11847
// dialog event and completion functions for HDR image level adjustment
11849
int HDR_dialog_event(zdialog *zd, const char *event)
11851
if (! event || *event != 'b') return 0;
11853
HDR_br_np = 3; // initz. image weights curve,
11854
HDR_br_px[0] = 5; // 3 spline anchor points
11855
HDR_br_px[1] = 127;
11856
HDR_br_px[2] = 250;
11858
if (strEqu(event,"b100/0")) // 100% image 1 v.8.6
11859
HDR_br_py[0] = HDR_br_py[1] = HDR_br_py[2] = 0.98;
11861
if (strEqu(event,"b0/100")) // 100% image 2
11862
HDR_br_py[0] = HDR_br_py[1] = HDR_br_py[2] = 0.02;
11864
if (strEqu(event,"b50/50")) { // 50/50
11865
if (HDR_bratio < 1) {
11866
HDR_br_py[0] = 0.02; // ramp up
11867
HDR_br_py[1] = 0.50;
11868
HDR_br_py[2] = 0.98;
11871
HDR_br_py[0] = 0.98; // ramp down
11872
HDR_br_py[1] = 0.50;
11873
HDR_br_py[2] = 0.02;
11877
HDR_brcurve_draw(HDR_brcurve); // regen and redraw the curve
11883
int HDR_dialog_compl(zdialog *zd, int zstat)
11885
if (zstat != 1) edit_cancel(); // user cancel
11887
Nalign = 0; // reset align counter
11888
RGB_free(A1rgb48); // free alignment images
11890
RGB_free(Grgb48); // free 2nd input image pixmap
11895
// thread to update image brightness levels
11896
// runs asynchronously to dialog updates
11898
void * HDR_dialog_thread(void *)
11902
thread_idle_loop(); // wait for work or exit request
11903
HDR_combine(1); // combine images >> E3rgb48
11906
return 0; // not executed, stop g++ warning
11910
// Add, delete, or move anchor points to weights curve using mouse.
11912
int HDR_brcurve_adjust(GtkWidget *brcurve, GdkEventButton *event)
11914
int ww, hh, px, py;
11915
int kk, ii, newii, minii = -1;
11916
int mx, my, button, evtype;
11917
double dist2, mindist2 = 1000000;
11920
if (HDR_br_np > 49) {
11921
zmessageACK(ZTX("Exceed 50 anchor points"));
11925
mx = int(event->x); // mouse position in drawing area
11926
my = int(event->y);
11927
evtype = event->type;
11928
button = event->button;
11929
ww = brcurve->allocation.width; // drawing area size
11930
hh = brcurve->allocation.height;
11932
for (ii = 0; ii < HDR_br_np; ii++) // find closest anchor point
11934
xval = HDR_br_px[ii];
11935
yval = spline2(xval);
11936
px = int(0.00392 * ww * xval); // 0 - ww
11937
py = int(hh - hh * yval + 0.5); // 0 - hh
11938
dist2 = (px-mx)*(px-mx) + (py-my)*(py-my);
11939
if (dist2 < mindist2) {
11945
if (minii < 0) return 0; // huh?
11947
if (evtype == GDK_BUTTON_PRESS && button == 3) { // right click, remove anchor point
11948
if (mindist2 > 25) return 0;
11949
if (HDR_br_np < 3) return 0;
11950
for (kk = minii; kk < HDR_br_np -1; kk++) {
11951
HDR_br_px[kk] = HDR_br_px[kk+1];
11952
HDR_br_py[kk] = HDR_br_py[kk+1];
11956
HDR_brcurve_draw(brcurve); // regen and redraw the curve
11960
// drag or left click, move nearby anchor point to mouse position,
11961
// or add a new anchor point if nothing near enough
11963
xval = 255.0 * mx / ww; // 0 - 255
11964
yval = 1.0 * (hh - my) / hh; // 0 - 1.0
11966
if (xval < 0 || xval > 255) return 0; // v.6.8
11967
if (yval < 0 || yval > 1.0) return 0;
11969
if (mindist2 < 100) { // existing point < 10 pixels away
11971
if (ii < HDR_br_np - 1 && HDR_br_px[ii+1] - xval < 5) return 0; // disallow < 5 x-pixels
11972
if (ii > 0 && xval - HDR_br_px[ii-1] < 5) return 0; // to next or prior point
11973
newii = minii; // this point to be moved
11975
else // > 10 pixels, add a point
11977
for (ii = 0; ii < HDR_br_np; ii++)
11978
if (xval <= HDR_br_px[ii]) break; // find point with next higher x
11980
if (ii < HDR_br_np && HDR_br_px[ii] - xval < 5) return 0; // disallow < 5 x-pixels
11981
if (ii > 0 && xval - HDR_br_px[ii-1] < 5) return 0; // to next or prior point
11983
for (kk = HDR_br_np; kk > ii; kk--) { // make hole for new point
11984
HDR_br_px[kk] = HDR_br_px[kk-1];
11985
HDR_br_py[kk] = HDR_br_py[kk-1];
11988
HDR_br_np++; // up point count
11989
newii = ii; // this point to be added
11992
HDR_br_px[newii] = xval; // coordinates of new or moved point
11993
HDR_br_py[newii] = yval;
11995
HDR_brcurve_draw(brcurve); // regen and redraw the curve
12000
// Draw brightness curve based on defined spline anchor points.
12002
int HDR_brcurve_draw(GtkWidget *brcurve)
12004
int ww, hh, px, py;
12008
ww = brcurve->allocation.width; // drawing area size
12009
hh = brcurve->allocation.height;
12010
if (ww < 50 || hh < 20) return 0;
12012
spline1(HDR_br_np,HDR_br_px,HDR_br_py); // make curve fitting anchor points
12014
gdk_window_clear(brcurve->window); // clear window
12016
for (px = 0; px < ww; px++) // generate all points for curve
12018
xval = 255.0 * px / ww;
12019
yval = spline2(xval);
12020
py = int(hh - hh * yval + 0.5);
12021
gdk_draw_point(brcurve->window,gdkgc,px,py);
12024
for (ii = 0; ii < HDR_br_np; ii++) // draw boxes at anchor points
12026
xval = HDR_br_px[ii];
12027
yval = spline2(xval);
12028
px = int(0.00392 * ww * xval);
12029
py = int(hh - hh * yval + 0.5);
12030
for (iix = -2; iix < 3; iix++)
12031
for (iiy = -2; iiy < 3; iiy++) {
12032
if (px+iix < 0 || px+iix >= ww) continue;
12033
if (py+iiy < 0 || py+iiy >= hh) continue;
12034
gdk_draw_point(brcurve->window,gdkgc,px+iix,py+iiy);
12038
for (int ii = 0; ii < 256; ii++) // compute new weight curve for
12039
HDR_weights[ii] = spline2(ii); // all 256 brightness levels
12041
signal_thread(); // signal thread to update
12046
// HDR image align thread, combine Frgb48 + Grgb48 >> E3rgb48
12048
void * HDR_align_thread(void *)
12050
double xfL, xfH, yfL, yfH, tfL, tfH;
12051
double xystep, xylim, tstep, tlim;
12052
int firstpass, lastpass;
12054
HDR_align_stat = 0; // no status yet
12055
Radjust = Gadjust = Badjust = 1.0; // no manual color adjustments
12056
Nalign = 1; // alignment in progress
12057
aligntype = 1; // HDR
12058
pixsamp = 5000; // pixel sample size
12059
showRedpix = 1; // highlight alignment pixels
12060
Fzoom = 0; // fit to window if big
12061
Fblowup = 1; // scale up to window if small
12065
fullSize = Fww; // full image size
12066
if (Fhh > Fww) fullSize = Fhh; // (largest dimension)
12068
alignSize = 140; // initial alignment image size
12069
if (alignSize > fullSize) alignSize = fullSize;
12070
A1rgb48 = A2rgb48 = 0;
12072
xoff = yoff = toff = 0; // initial offsets = 0
12073
xshrink = yshrink = 0; // no image shrinkage (pano)
12074
warpxu = warpyu = warpxl = warpyl = 0; // no warp factors (pano)
12075
warpxuB = warpyuB = warpxlB = warpylB = 0;
12077
while (true) // next alignment stage / image size
12079
A1ww = Fww * alignSize / fullSize; // align width, height in same ratio
12080
A1hh = Fhh * alignSize / fullSize;
12086
RGB_free(A1rgb48); // align images = scaled input images
12088
A1rgb48 = RGB_rescale(Frgb48,A1ww,A1hh);
12089
A2rgb48 = RGB_rescale(Grgb48,A2ww,A2hh);
12091
alignWidth = A1ww; // use full image for alignment
12092
alignHeight = A1hh; // v.8.0
12094
getAlignArea(); // get image overlap area
12095
getBrightRatios(); // get color brightness ratios
12096
setColorfixFactors(1); // set color matching factors
12097
flagEdgePixels(); // flag high-contrast pixels
12099
mutex_lock(&pixmaps_lock);
12100
RGB_free(E3rgb48); // resize output image
12101
E3rgb48 = RGB_make(A1ww,A1hh,48);
12104
mutex_unlock(&pixmaps_lock);
12107
xylim = 2; // search range from prior stage:
12108
xystep = 1; // -2 -1 0 +1 +2 pixels
12110
if (firstpass) xylim = 0.05 * alignSize; // 1st stage search range, huge
12113
xylim = 1; // final stage search range:
12114
xystep = 0.5; // -1.0 -0.5 0.0 +0.5 +1.0
12117
tlim = xylim / alignSize / 2; // theta max offset, radians
12118
tstep = xystep / alignSize / 2; // theta step size
12120
xfL = xoff - xylim;
12121
xfH = xoff + xylim + xystep/2;
12122
yfL = yoff - xylim;
12123
yfH = yoff + xylim + xystep/2;
12125
tfH = toff + tlim + tstep/2;
12131
matchB = matchImages(); // set base match level
12132
HDR_combine(0); // v.8.4
12134
for (xoff = xfL; xoff < xfH; xoff += xystep) // test all offset dimensions
12135
for (yoff = yfL; yoff < yfH; yoff += xystep) // in all combinations
12136
for (toff = tfL; toff < tfH; toff += tstep)
12138
matchlev = matchImages();
12139
if (sigdiff(matchlev,matchB,0.00001) > 0) {
12146
Nalign++; // count alignment tests
12147
SBupdate++; // update status bar
12150
xoff = xoffB; // recover best offsets
12154
HDR_combine(0); // combine images >> E3rgb48
12157
if (lastpass) break; // done
12159
if (alignSize == fullSize) { // full size image was aligned
12160
lastpass++; // one more pass
12164
double R = alignSize;
12165
alignSize = 2 * alignSize; // next larger image size
12166
if (alignSize > 0.8 * fullSize) alignSize = fullSize; // if near goal, jump to it now
12167
R = alignSize / R; // ratio of new / old image size
12168
xoff = R * xoff; // adjust offsets for image size
12172
zfree(redpixels); // free edge-pixel flags
12174
showRedpix = 0; // stop red pixel highlights
12175
Fblowup = 0; // reset forced image scaling
12176
Fmodified = 1; // image is modified
12177
HDR_align_stat = 1; // signal success
12179
return 0; // never executed, stop g++ warning
12183
// Combine images A1rgb48 and A2rgb48 using weights in HDR_weights[256].
12184
// Output is to E3rgb48 (not reallocated). Update window.
12186
void HDR_combine(int weight)
12188
int px3, py3, ii, vstat1, vstat2;
12189
double px1, py1, px2, py2;
12190
double sintf = sin(toff), costf = cos(toff);
12191
double br1, br2, brm;
12192
uint16 vpix1[3], vpix2[3], *pix3;
12194
for (py3 = 0; py3 < A1hh; py3++) // step through A1rgb48 pixels
12195
for (px3 = 0; px3 < A1ww; px3++)
12197
px1 = costf * px3 - sintf * (py3 - yoff); // A1rgb48 pixel, after offsets
12198
py1 = costf * py3 + sintf * (px3 - xoff);
12199
vstat1 = vpixel(A1rgb48,px1,py1,vpix1);
12201
px2 = costf * (px3 - xoff) + sintf * (py3 - yoff); // corresponding A2rgb48 pixel
12202
py2 = costf * (py3 - yoff) - sintf * (px3 - xoff);
12203
vstat2 = vpixel(A2rgb48,px2,py2,vpix2);
12205
pix3 = bmpixel(E3rgb48,px3,py3); // output pixel
12207
if (! vstat1 || ! vstat2) { // if non-overlapping pixel,
12208
pix3[0] = pix3[1] = pix3[2] = 0; // set output pixel black
12213
br1 = vpix1[0] + vpix1[1] + vpix1[2]; // image1 pixel brightness
12214
br2 = vpix2[0] + vpix2[1] + vpix2[2]; // image2
12215
brm = (br1 + br2) * 0.000651; // mean, 0 to 255.98
12216
br1 = HDR_weights[int(brm)]; // image1 weight
12217
br2 = 1.0 - br1; // image2 weight
12219
pix3[0] = int(vpix1[0] * br1 + vpix2[0] * br2); // build output pixel
12220
pix3[1] = int(vpix1[1] * br1 + vpix2[1] * br2);
12221
pix3[2] = int(vpix1[2] * br1 + vpix2[2] * br2);
12225
pix3[0] = (vpix1[0] + vpix2[0]) / 2; // output pixel is simple average
12226
pix3[1] = (vpix1[1] + vpix2[1]) / 2;
12227
pix3[2] = (vpix1[2] + vpix2[2]) / 2;
12230
if (showRedpix && vstat2) { // highlight alignment pixels
12231
ii = py3 * A1ww + px3;
12232
if (redpixels[ii]) {
12234
pix3[1] = pix3[2] = 0;
12239
mwpaint2(); // update window
12244
/**************************************************************************
12245
Make an HDF (high depth of field) image from two images of the same v.8.0
12246
subject with different focus settings, near and far. One image has
12247
the nearer parts of the subject in sharp focus, the other image has
12248
the farther parts in focus. The output image is constructed from the
12249
sharpest pixels in each of the two input images. Minor differences
12250
in image center, rotation and size are automatically compensated.
12251
**************************************************************************/
12253
void * HDF_align_thread(void *);
12254
void HDF_combine(int Fcolor);
12255
void HDF_distort();
12256
void HDF_mousefunc();
12257
int HDF_dialog_event(zdialog *zd, const char *event);
12258
int HDF_dialog_compl(zdialog *zd, int zstat);
12260
RGB *A2rgb48cache = 0;
12261
int HDF_align_stat = 0;
12262
int HDF_distort_busy = 0;
12263
double HDF_zoffx[4], HDF_zoffxB[4];
12264
double HDF_zoffy[4], HDF_zoffyB[4];
12272
void m_HDF(GtkWidget *, const char *)
12276
if (! edit_setup(0,0)) return; // setup edit: no preview
12278
file2 = zgetfile(ZTX("Select image to combine"),image_file,"open"); // get 2nd HDF image
12284
Grgb48 = image_load(file2,48); // load and validate image
12293
start_thread(HDF_align_thread,0); // start thread to align images
12294
wrapup_thread(0); // wait for thread exit
12296
if (HDF_align_stat != 1) {
12297
edit_cancel(); // failure
12301
HDF_combine(1); // combine with color comp.
12304
zdedit = zdialog_new(ZTX("Retouch Image"),mWin,Bdone,Bcancel,null); // dialog for retouch
12305
zdialog_add_widget(zdedit,"hbox","hb1","dialog",0,"space=5");
12306
zdialog_add_widget(zdedit,"radio","radio1","hb1","image 1");
12307
zdialog_add_widget(zdedit,"radio","radio2","hb1","image 2","space=10");
12308
zdialog_add_widget(zdedit,"hbox","hb2","dialog",0,"space=3");
12309
zdialog_add_widget(zdedit,"label","labr","hb2","brush","space=5");
12310
zdialog_add_widget(zdedit,"spin","radius","hb2","1|199|1|20");
12311
zdialog_add_widget(zdedit,"button","susp-resm","hb2",Bsuspend,"space=10");
12313
zdialog_stuff(zdedit,"radio1",1);
12318
mouseCBfunc = HDF_mousefunc; // connect mouse function
12319
Mcapture = 1; // capture mouse clicks
12321
zdialog_run(zdedit,HDF_dialog_event,HDF_dialog_compl); // run dialog, parallel
12326
// dialog event function
12328
int HDF_dialog_event(zdialog *zd, const char *event)
12332
zdialog_fetch(zd,"radio1",ii);
12333
if (ii) HDF_image = 1;
12334
else HDF_image = 2;
12336
if (strEqu(event,"radius"))
12337
zdialog_fetch(zd,"radius",HDF_brush);
12339
if (strEqu(event,"susp-resm")) // toggle suspend / resume
12343
paint_toparc(3); // start brush outline v.8.3
12344
mouseCBfunc = HDF_mousefunc; // connect mouse function
12346
zdialog_stuff(zd,"susp-resm",Bsuspend);
12350
paint_toparc(2); // stop brush outline v.8.3
12351
mouseCBfunc = 0; // disconnect mouse function
12353
zdialog_stuff(zd,"susp-resm",Bresume);
12361
// dialog completion function
12363
int HDF_dialog_compl(zdialog *zd, int zstat)
12365
if (zstat != 1) edit_cancel(); // user cancel
12367
RGB_free(A1rgb48); // free memory
12369
paint_toparc(2); // stop brush outline v.8.3
12370
Mcapture = 0; // disconnect mouse
12376
// dialog mouse function
12378
void HDF_mousefunc()
12380
uint16 vpixI[3], *ppix3;
12381
int radius, radius2, vstat;
12382
int mx, my, dx, dy, px3, py3;
12383
int red, green, blue, max;
12384
double pxI, pyI, f1;
12385
double sintf = sin(toff), costf = cos(toff);
12387
radius = HDF_brush;
12388
radius2 = radius * radius;
12390
toparcx = Mxposn - radius; // paint brush outline circle
12391
toparcy = Myposn - radius; // v.8.3
12392
toparcw = toparch = 2 * radius;
12396
if (LMclick) { // mouse click
12401
else if (Mxdrag || Mydrag) { // drag in progress
12408
LMclick = RMclick = 0;
12410
if (mx < 0 || mx > E3ww-1 || my < 0 || my > E3hh-1) // outside image area
12413
for (dy = -radius; dy <= radius; dy++) // loop surrounding block of pixels
12414
for (dx = -radius; dx <= radius; dx++)
12419
if (px3 < 0 || px3 > E3ww-1) continue; // outside image
12420
if (py3 < 0 || py3 > E3hh-1) continue;
12421
if (dx*dx + dy*dy > radius2) continue; // outside radius
12423
if (HDF_image == 1) {
12424
pxI = costf * px3 - sintf * (py3 - yoff); // image1 virtual pixel
12425
pyI = costf * py3 + sintf * (px3 - xoff);
12426
vstat = vpixel(A1rgb48,pxI,pyI,vpixI);
12427
if (! vstat) continue;
12428
red = int(R12match[vpixI[0]]); // compensate color
12429
green = int(G12match[vpixI[1]]);
12430
blue = int(B12match[vpixI[2]]);
12434
pxI = costf * (px3 - xoff) + sintf * (py3 - yoff); // image2 virtual pixel
12435
pyI = costf * (py3 - yoff) - sintf * (px3 - xoff);
12436
vstat = vpixel(A2rgb48,pxI,pyI,vpixI);
12437
if (! vstat) continue;
12438
red = int(R21match[vpixI[0]]);
12439
green = int(G21match[vpixI[1]]);
12440
blue = int(B21match[vpixI[2]]);
12443
if (red > 65535 || green > 65535 || blue > 65535) { // fix overflow
12445
if (green > max) max = green;
12446
if (blue > max) max = blue;
12447
f1 = 65535.0 / max;
12448
red = int(red * f1);
12449
green = int(green * f1);
12450
blue = int(blue * f1);
12453
ppix3 = bmpixel(E3rgb48,px3,py3); // image3 real pixel
12464
// image align thread, combine Frgb48 + Grgb48 >> E3rgb48
12466
void * HDF_align_thread(void *)
12468
double xfL, xfH, yfL, yfH, xystep, xylim;
12469
double tfL, tfH, tstep, tlim;
12470
double zlim, zoffx0, zoffy0;
12472
double eighth = 0.7854; // 1/8 of circle in radians
12473
int ii, jj, lastpass;
12475
HDF_align_stat = 0; // no status yet
12476
Radjust = Gadjust = Badjust = 1.0; // no manual color adjustments
12477
Nalign = 1; // alignment in progress
12478
aligntype = 2; // HDF
12479
pixsamp = 10000; // pixel sample size
12480
showRedpix = 1; // highlight alignment pixels
12481
Fzoom = 0; // fit to window if big
12482
Fblowup = 1; // scale up to window if small
12483
xoff = yoff = toff = 0; // initial offsets = 0
12484
for (ii = 0; ii < 4; ii++) // initial distortions = 0
12485
HDF_zoffx[ii] = HDF_zoffy[ii] = 0;
12487
A1rgb48 = A2rgb48 = A2rgb48cache = 0;
12489
fullSize = Fww; // full image size
12490
if (Fhh > Fww) fullSize = Fhh; // (largest dimension)
12492
alignSize = 200; // initial alignment image size
12493
if (alignSize > fullSize) alignSize = fullSize;
12496
xyrange = 0.05 * alignSize; // first pass, huge search range
12497
xshrink = yshrink = xyrange; // image shrink from distortion v.8.1
12498
warpxu = warpyu = warpxl = warpyl = 0; // no warp factors (pano)
12499
warpxuB = warpyuB = warpxlB = warpylB = 0;
12501
while (true) // next alignment stage / image size
12503
A1ww = Fww * alignSize / fullSize; // align width, height in same ratio
12504
A1hh = Fhh * alignSize / fullSize;
12509
RGB_free(A2rgb48cache);
12511
if (alignSize < fullSize) {
12512
A1rgb48 = RGB_rescale(Frgb48,A1ww,A1hh); // alignment images are
12513
A2rgb48cache = RGB_rescale(Grgb48,A2ww,A2hh); // down-scaled input images
12516
A1rgb48 = RGB_copy(Frgb48); // full size, copy input images
12517
A2rgb48cache = RGB_copy(Grgb48);
12520
RGB_free(A2rgb48); // distort image2 using current
12521
HDF_distort(); // zoffx/y settings
12523
mutex_lock(&pixmaps_lock);
12524
RGB_free(E3rgb48); // prepare new output RGB pixmap
12525
E3rgb48 = RGB_make(A1ww,A1hh,48);
12528
mutex_unlock(&pixmaps_lock);
12531
alignHeight = A1hh;
12532
getAlignArea(); // get image align area
12533
getBrightRatios(); // get image brightness ratios
12534
setColorfixFactors(1); // compute color matching factors
12535
flagEdgePixels(); // flag high-contrast pixels
12537
HDF_combine(0); // combine and update window
12539
xylim = xyrange; // xy search range, pixels
12540
xystep = 0.5 * xyrange; // -1.0 -0.5 0.0 +0.5 +1.0
12541
tlim = xylim / alignSize; // theta search range, radians
12542
tstep = xystep / alignSize; // step size
12543
zlim = xylim; // distortion search range
12545
// find best alignment based on xoff, yoff, toff
12549
xfL = xoff - xylim; // set x, y, theta search ranges
12550
xfH = xoff + xylim + xystep/2;
12551
yfL = yoff - xylim;
12552
yfH = yoff + xylim + xystep/2;
12554
tfH = toff + tlim + tstep/2;
12556
for (xoff = xfL; xoff < xfH; xoff += xystep) // test all offset dimensions
12557
for (yoff = yfL; yoff < yfH; yoff += xystep) // in all combinations
12558
for (toff = tfL; toff < tfH; toff += tstep)
12560
matchlev = matchImages();
12561
if (sigdiff(matchlev,matchB,0.00001) > 0) { // remember best match
12566
HDF_combine(0); // combine and update window
12569
Nalign++; // count alignment tests
12570
SBupdate++; // update status bar
12573
xoff = xoffB; // recover best offsets
12577
// find best distortion settings at the four corners
12579
for (int mpass = 1; mpass <= 2; mpass++)
12581
for (ii = 0; ii < 4; ii++) // corner NW NE SE SW
12583
HDF_zoffxB[ii] = HDF_zoffx[ii]; // save baseline match level
12584
HDF_zoffyB[ii] = HDF_zoffy[ii];
12586
zoffx0 = HDF_zoffx[ii]; // current setting
12587
zoffy0 = HDF_zoffy[ii];
12589
for (jj = 0; jj < 8; jj++) // 8 positions around current
12590
{ // distortion setting
12591
HDF_zoffx[ii] = zoffx0 + zlim * cos(eighth * jj);
12592
HDF_zoffy[ii] = zoffy0 + zlim * sin(eighth * jj);
12594
RGB_free(A2rgb48); // distort image2
12597
matchlev = matchImages();
12598
if (sigdiff(matchlev,matchB,0.00001) > 0) { // remember best match
12600
HDF_zoffxB[ii] = HDF_zoffx[ii];
12601
HDF_zoffyB[ii] = HDF_zoffy[ii];
12602
HDF_combine(0); // combine and update window
12605
Nalign++; // count alignment tests
12606
SBupdate++; // update status bar
12609
HDF_zoffx[ii] = HDF_zoffxB[ii]; // recover best offset
12610
HDF_zoffy[ii] = HDF_zoffyB[ii];
12614
// set up for next pass
12616
if (lastpass) break; // done
12618
if (alignSize == fullSize) { // full size image was aligned
12619
lastpass++; // one more pass
12624
double R = alignSize;
12625
alignSize = 1.3 * alignSize; // next larger image size
12626
if (alignSize > 0.8 * fullSize) alignSize = fullSize; // if near goal, jump to it now
12627
R = alignSize / R; // ratio of new / old image size
12628
xoff = R * xoff; // adjust offsets for image size
12630
for (ii = 0; ii < 4; ii++) {
12631
HDF_zoffx[ii] = R * HDF_zoffx[ii];
12632
HDF_zoffy[ii] = R * HDF_zoffy[ii];
12635
xyrange = 0.7 * xyrange; // reduce search range
12636
if (xyrange < 1) xyrange = 1;
12637
xshrink = yshrink = xyrange; // image shrink from distortion v.8.1
12640
RGB_free(A2rgb48cache); // free memory
12641
RGB_free(Grgb48); // A1/A2rgb48 still needed
12643
Fmodified = 1; // image is modified
12644
showRedpix = 0; // stop red pixel highlights
12645
Fblowup = 0; // reset forced image scaling
12646
Nalign = 0; // reset align counter
12647
zfree(redpixels); // free edge-pixel flags
12649
HDF_align_stat = 1; // signal success
12651
return 0; // not executed, stop g++ warning
12655
// Combine images A1rgb48 and A2rgb48 using brightness adjustments.
12656
// Output is to E3rgb48 (not reallocated). Update main window.
12658
void HDF_combine(int Fcolor)
12660
int px3, py3, ii, vstat1, vstat2;
12661
int red1, green1, blue1, red2, green2, blue2, max;
12662
double px1, py1, px2, py2, f1;
12663
double sintf = sin(toff), costf = cos(toff);
12664
uint16 vpix1[3], vpix2[3], *pix3;
12666
Radjust = Gadjust = Badjust = 1.0;
12668
for (py3 = 1; py3 < E3hh-1; py3++) // step through output pixels
12669
for (px3 = 1; px3 < E3ww-1; px3++)
12671
px1 = costf * px3 - sintf * (py3 - yoff); // A1rgb48 pixel, after offsets
12672
py1 = costf * py3 + sintf * (px3 - xoff);
12673
vstat1 = vpixel(A1rgb48,px1,py1,vpix1);
12675
px2 = costf * (px3 - xoff) + sintf * (py3 - yoff); // corresponding A2rgb48 pixel
12676
py2 = costf * (py3 - yoff) - sintf * (px3 - xoff);
12677
vstat2 = vpixel(A2rgb48,px2,py2,vpix2);
12679
pix3 = bmpixel(E3rgb48,px3,py3); // output pixel
12681
if (! vstat1 || ! vstat2) { // no overlap
12682
pix3[0] = pix3[1] = pix3[2] = 0; // output pixel is black
12686
if (showRedpix) { // show alignment pixels in red
12687
ii = py3 * A1ww + px3;
12688
if (redpixels[ii]) {
12690
pix3[1] = pix3[2] = 0;
12695
red1 = vpix1[0]; // image1 and image2 pixels
12705
red1 = int(R12match[red1]); // compensate color
12706
green1 = int(G12match[green1]);
12707
blue1 = int(B12match[blue1]);
12709
red2 = int(R21match[red2]);
12710
green2 = int(G21match[green2]);
12711
blue2 = int(B21match[blue2]);
12713
if (red1 > 65535 || green1 > 65535 || blue1 > 65535) { // fix overflow
12715
if (green1 > max) max = green1;
12716
if (blue1 > max) max = blue1;
12717
f1 = 65535.0 / max;
12718
red1 = int(red1 * f1);
12719
green1 = int(green1 * f1);
12720
blue1 = int(blue1 * f1);
12723
if (red2 > 65535 || green2 > 65535 || blue2 > 65535) {
12725
if (green2 > max) max = green2;
12726
if (blue2 > max) max = blue2;
12727
f1 = 65535.0 / max;
12728
red2 = int(red2 * f1);
12729
green2 = int(green2 * f1);
12730
blue2 = int(blue2 * f1);
12734
pix3[0] = (red1 + red2) / 2; // output = combined inputs
12735
pix3[1] = (green1 + green2) / 2;
12736
pix3[2] = (blue1 + blue2) / 2;
12739
mwpaint2(); // update window
12744
// Distort A2rgb48cache, returning A2rgb48
12745
// 4 corners move HDF_zoffx[ii], HDF_zoffy[ii] pixels
12746
// and center does not move,
12750
void * HDF_distort_wthread(void *arg);
12752
RGB *rgbin, *rgbout;
12755
rgbin = A2rgb48cache;
12756
ww = rgbin->ww; // create output RGB pixmap
12758
rgbout = RGB_make(ww,hh,48);
12761
for (ii = 0; ii < NWthreads; ii++) // start worker threads
12762
start_detached_thread(HDF_distort_wthread,&wtindex[ii]);
12763
zadd_locked(HDF_distort_busy,+NWthreads);
12765
while (HDF_distort_busy) // wait for completion
12771
void * HDF_distort_wthread(void *arg) // worker thread function
12773
int index = *((int *) arg);
12774
int pxm, pym, ww, hh, vstat;
12775
double diag, px, py, dispx, dispy, dispx0, dispy0;
12776
double disp0, disp1, disp2, disp3;
12777
double disp0x, disp1x, disp2x, disp3x;
12778
double disp0y, disp1y, disp2y, disp3y;
12779
uint16 vpix[3], *pixm;
12780
RGB *rgbin, *rgbout;
12782
rgbin = A2rgb48cache;
12785
ww = rgbin->ww; // create output RGB pixmap
12787
diag = sqrt(ww*ww + hh*hh);
12789
pxm = ww/2; // center pixel
12792
disp0 = (1 - pxm/diag) * (1 - pym/diag);
12793
disp0 = disp0 * disp0;
12794
disp0x = disp0 * HDF_zoffx[0];
12795
disp0y = disp0 * HDF_zoffy[0];
12797
disp1 = (1 - (ww-pxm)/diag) * (1 - pym/diag);
12798
disp1 = disp1 * disp1;
12799
disp1x = disp1 * HDF_zoffx[0];
12800
disp1y = disp1 * HDF_zoffy[0];
12802
disp2 = (1 - (ww-pxm)/diag) * (1 - (hh-pym)/diag);
12803
disp2 = disp2 * disp2;
12804
disp2x = disp2 * HDF_zoffx[0];
12805
disp2y = disp2 * HDF_zoffy[0];
12807
disp3 = (1 - pxm/diag) * (1 - (hh-pym)/diag);
12808
disp3 = disp3 * disp3;
12809
disp3x = disp3 * HDF_zoffx[0];
12810
disp3y = disp3 * HDF_zoffy[0];
12812
dispx = +disp0x - disp1x - disp2x + disp3x; // center pixel displacement
12813
dispy = +disp0y + disp1y - disp2y - disp3y;
12815
dispx0 = -dispx; // anti-displacement
12818
for (pym = index; pym < hh; pym += NWthreads) // loop all pixels
12819
for (pxm = 0; pxm < ww; pxm++)
12821
disp0 = (1 - pxm/diag) * (1 - pym/diag);
12822
disp0 = disp0 * disp0;
12823
disp0x = disp0 * HDF_zoffx[0];
12824
disp0y = disp0 * HDF_zoffy[0];
12826
disp1 = (1 - (ww-pxm)/diag) * (1 - pym/diag);
12827
disp1 = disp1 * disp1;
12828
disp1x = disp1 * HDF_zoffx[0];
12829
disp1y = disp1 * HDF_zoffy[0];
12831
disp2 = (1 - (ww-pxm)/diag) * (1 - (hh-pym)/diag);
12832
disp2 = disp2 * disp2;
12833
disp2x = disp2 * HDF_zoffx[0];
12834
disp2y = disp2 * HDF_zoffy[0];
12836
disp3 = (1 - pxm/diag) * (1 - (hh-pym)/diag);
12837
disp3 = disp3 * disp3;
12838
disp3x = disp3 * HDF_zoffx[0];
12839
disp3y = disp3 * HDF_zoffy[0];
12841
dispx = +disp0x - disp1x - disp2x + disp3x; // (pxm,pym) displacement
12842
dispy = +disp0y + disp1y - disp2y - disp3y;
12844
dispx += dispx0; // relative to center pixel
12847
px = pxm + dispx; // source pixel location
12850
vstat = vpixel(rgbin,px,py,vpix); // input virtual pixel
12851
pixm = bmpixel(rgbout,pxm,pym); // output real pixel
12858
else pixm[0] = pixm[1] = pixm[2] = 0;
12861
zadd_locked(HDF_distort_busy,-1);
12866
/**************************************************************************/
12868
// panorama function - combine left and right images into a wide image
12870
void pano_prealign(); // manual pre-align
12871
void pano_autolens(); // auto optimize lens parameters
12872
void * pano_align_thread(void *); // auto align images
12873
void pano_final_adjust(); // manual final adjustment
12874
void pano_get_align_images(int newf, int strf); // scale and curve images for align
12875
void pano_combine(int fcolor); // combine images
12877
int pano_stat; // dialog and thread status
12878
int pano_automatch; // auto color matching on/off
12879
RGB *pano_A1cache, *pano_A2cache; // cached alignment images
12880
double pano_curve, pano_bow; // converted lens parameters
12883
void m_pano(GtkWidget *, const char *)
12887
Grgb48 = A1rgb48 = A2rgb48 = pano_A1cache = pano_A2cache = 0;
12889
if (! edit_setup(0,0)) return; // setup edit: no preview
12891
file2 = zgetfile(ZTX("Select image to combine"),image_file,"open"); // 2nd or next pano file
12892
if (! file2) goto pano_cancel;
12894
Grgb48 = image_load(file2,48); // load and validate image
12895
if (! Grgb48) goto pano_cancel;
12899
xoff = yoff = xoffB = yoffB = toff = toffB = 0; // initial offsets
12900
warpxu = warpyu = warpxl = warpyl = 0; // initial warp factors
12901
warpxuB = warpyuB = warpxlB = warpylB = 0;
12903
pano_prealign(); // do manual pre-align
12904
if (pano_stat != 1) goto pano_cancel;
12906
if (overlapixs < alignSize * 20) { // need > 20 pixel overlap
12907
zmessageACK(ZTX("Too little overlap, cannot align"));
12911
start_thread(pano_align_thread,0); // start thread to align images
12912
wrapup_thread(0); // wait for thread exit
12913
if (pano_stat != 1) goto pano_cancel;
12915
pano_final_adjust(); // do manual final adjustments
12916
if (pano_stat != 1) goto pano_cancel;
12926
if (file2) zfree(file2);
12930
RGB_free(pano_A1cache);
12931
RGB_free(pano_A2cache);
12936
// perform manual pre-align of image2 to image1
12937
// return offsets: xoff, yoff, toff
12938
// lens_mm and lens_bow may also be altered
12940
void pano_prealign()
12942
int pano_prealign_event(zdialog *zd, const char *event); // dialog event function
12943
int pano_prealign_compl(zdialog *zd, int zstat); // dialog completion function
12944
void * pano_prealign_thread(void *); // working thread
12946
const char *align_mess = ZTX("Drag right image into rough alignment with left \n"
12947
" to rotate, drag right edge up or down");
12948
const char *proceed_mess = ZTX("Merge the images together");
12949
const char *search_mess = ZTX("Auto-search lens mm and bow");
12951
zdedit = zdialog_new(ZTX("Pre-align Images"),mWin,Bcancel,null);
12952
zdialog_add_widget(zdedit,"label","lab1","dialog",align_mess,"space=5");
12953
zdialog_add_widget(zdedit,"hbox","hb1","dialog",0,"space=5");
12954
zdialog_add_widget(zdedit,"spin","spmm","hb1","22|200|0.1|35","space=5");
12955
zdialog_add_widget(zdedit,"label","labmm","hb1",ZTX("lens mm")); // fix translation v.8.4.1
12956
zdialog_add_widget(zdedit,"hbox","hb2","dialog",0,"space=5");
12957
zdialog_add_widget(zdedit,"spin","spbow","hb2","-9|9|0.01|0","space=5");
12958
zdialog_add_widget(zdedit,"label","labbow","hb2",ZTX("lens bow"));
12959
zdialog_add_widget(zdedit,"hbox","hb3","dialog",0,"space=5");
12960
zdialog_add_widget(zdedit,"button","proceed","hb3",Bproceed,"space=5");
12961
zdialog_add_widget(zdedit,"label","labproceed","hb3",proceed_mess);
12962
zdialog_add_widget(zdedit,"hbox","hb4","dialog",0,"space=5");
12963
zdialog_add_widget(zdedit,"button","search","hb4",Bsearch,"space=5");
12964
zdialog_add_widget(zdedit,"label","labsearch","hb4",search_mess);
12966
lens_mm = lens4_mm[curr_lens]; // initz. curr. lens parameters
12967
lens_bow = lens4_bow[curr_lens];
12968
zdialog_stuff(zdedit,"spmm",lens_mm);
12969
zdialog_stuff(zdedit,"spbow",lens_bow);
12971
zdialog_run(zdedit,pano_prealign_event,pano_prealign_compl); // run dialog, parallel
12974
start_thread(pano_prealign_thread,0); // start working thread
12975
wrapup_thread(0); // wait for completion
12980
int pano_prealign_event(zdialog *zd, const char *event) // dialog event function
12982
if (strstr("spmm spbow",event)) { // revised lens data v.7.8
12983
zdialog_fetch(zd,"spmm",lens_mm);
12984
zdialog_fetch(zd,"spbow",lens_bow);
12987
if (strEqu(event,"search")) Fautolens = 1; // trigger auto-lens function
12989
if (strEqu(event,"proceed")) { // proceed with pano
12990
pano_stat = 1; // signal align success
12991
wrapup_thread(0); // wait for thread exit
12992
zdialog_free(zdedit); // kill dialog
12999
int pano_prealign_compl(zdialog *zd, int zstat) // dialog completion function
13001
pano_stat = 0; // signal cancel
13002
wrapup_thread(0); // wait for thread exit
13003
zdialog_free(zdedit); // kill dialog
13009
void * pano_prealign_thread(void *) // prealign working thread
13011
int mx0, my0, mx, my; // mouse drag origin, position
13012
double lens_mm0, lens_bow0;
13013
double mlever = 0.5;
13017
Radjust = Gadjust = Badjust = 1.0; // no manual color adjustments
13018
Nalign = 1; // alignment in progress
13019
aligntype = 3; // pano
13020
pixsamp = 5000; // pixel sample size
13021
showRedpix = 0; // no pixel highlight (yet)
13022
Fblowup = 1; // scale-up small image to window
13024
fullSize = Ghh; // full size = image2 height
13025
alignSize = int(pano_prealign_size); // prealign image size
13026
pano_get_align_images(1,0); // get prealign images and curve them
13028
xoff = xoffB = 0.8 * A1ww; // initial x offset (20% overlap)
13030
alignWidth = int(A1ww - xoff); // initial alignment on full overlap
13031
alignHeight = A1hh;
13032
getAlignArea(); // get image overlap area
13033
pano_combine(0); // combine images
13035
lens_mm0 = lens_mm; // to detect changes
13036
lens_bow0 = lens_bow;
13038
mx0 = my0 = 0; // no drag in progress
13039
Mcapture = KBcapture = 1; // capture mouse drag and KB keys
13041
while (pano_stat == -1) // loop and align until done
13043
zsleep(0.05); // logic simplified v.7.5
13046
pano_autolens(); // get lens parameters
13047
zdialog_stuff(zdedit,"spmm",lens_mm); // update dialog
13048
zdialog_stuff(zdedit,"spbow",lens_bow);
13052
if (lens_mm != lens_mm0 || lens_bow != lens_bow0) { // change in lens parameters
13053
lens_mm0 = lens_mm;
13054
lens_bow0 = lens_bow;
13055
pano_get_align_images(0,0);
13059
if (KBkey) { // KB input
13060
if (KBkey == GDK_Left) xoff -= 0.2; // tweak alignment offsets
13061
if (KBkey == GDK_Right) xoff += 0.2;
13062
if (KBkey == GDK_Up) yoff -= 0.2;
13063
if (KBkey == GDK_Down) yoff += 0.2;
13064
if (KBkey == GDK_r) toff += 0.0002;
13065
if (KBkey == GDK_l) toff -= 0.0002;
13068
alignWidth = int(A1ww - xoff); // entire overlap
13069
alignHeight = A1hh; // v.8.0
13071
pano_combine(0); // show combined images
13076
if (! Mxdrag && ! Mydrag) mx0 = my0 = 0; // no drag in progress
13078
if (Mxdrag || Mydrag) // mouse drag underway
13080
mx = Mxdrag; // mouse position in image
13083
if (mx < xoff || mx > xoff + A2ww || my > E3hh) { // if mouse not in image2 area,
13084
mx0 = my0 = Mxdrag = Mydrag = 0; // no drag in progress
13088
if (! mx0 && ! my0) { // new drag, set drag origin
13093
if (mx != mx0 || my != my0) // drag is progressing
13095
if (mx > xoff + 0.8 * A2ww) { // near right edge, theta drag
13096
dtoff = mlever * (my - my0) / A2ww; // delta theta, radians
13098
xoff += dtoff * (A1hh + yoff); // change center of rotation
13099
yoff -= dtoff * (A1ww - xoff); // to middle of overlap area
13102
xoff += mlever * (mx - mx0); // image2 offsets / mouse leverage
13103
yoff += mlever * (my - my0);
13106
mx0 = mx; // next drag origin = current mouse
13109
if (xoff > A1ww) xoff = A1ww; // limit nonsense
13110
if (xoff < 0.3 * A1ww) xoff = 0.3 * A1ww;
13111
if (yoff < -0.5 * A2hh) yoff = -0.5 * A2hh;
13112
if (yoff > 0.5 * A1hh) yoff = 0.5 * A1hh;
13113
if (toff < -0.20) toff = -0.20;
13114
if (toff > 0.20) toff = 0.20;
13116
alignWidth = int(A1ww - xoff); // entire overlap
13118
pano_combine(0); // show combined images
13124
if (majupdate) { // do major update
13126
alignWidth = int(A1ww - xoff); // entire overlap
13127
getAlignArea(); // get image overlap area
13128
getBrightRatios(); // get color brightness ratios
13129
setColorfixFactors(1); // set color matching factors
13130
flagEdgePixels(); // flag edge pixels in overlap
13131
matchB = matchImages(); // match images
13132
xoffB = xoff; // new base alignment data
13135
pano_combine(0); // show combined images
13136
SBupdate++; // update status bar
13140
Fzoom = 0; // reset in case user zoomed in
13141
KBcapture = Mcapture = 0;
13143
return 0; // never executed, stop g++ warning
13147
// optimize lens parameters
13148
// assumes a good starting point since search ranges are limited
13149
// inputs and outputs: lens_mm, lens_bow, xoff, yoff, toff
13151
void pano_autolens()
13153
double mm_range, bow_range, xoff_range, yoff_range, toff_range;
13154
double squeeze, xoff_rfinal, rnum;
13155
double lens_mmB, lens_bowB;
13158
mm_range = 0.2 * lens_mm; // set initial search ranges
13159
bow_range = 0.5 * lens_bow;
13160
if (bow_range < 1) bow_range = 1;
13165
xoff_rfinal = 0.2; // final xoff range - when to quit
13170
pano_get_align_images(0,0);
13171
alignWidth = int(A1ww - xoff);
13172
if (alignWidth > A2ww/3) alignWidth = A2ww/3;
13173
alignHeight = A1hh - abs(yoff);
13174
alignWidth = 0.9 * alignWidth; // do not change align pixels
13175
alignHeight = 0.9 * alignHeight; // when align parms change v.8.1
13178
setColorfixFactors(1);
13182
lens_mmB = lens_mm; // initial best fit = current data
13183
lens_bowB = lens_bow;
13187
matchB = matchImages();
13191
srand48(time(0) + counter++);
13192
lens_mm = lens_mmB + mm_range * (drand48() - 0.5); // new random lens factors
13193
lens_bow = lens_bowB + bow_range * (drand48() - 0.5); // within search range
13194
pano_get_align_images(0,0); // curve images
13195
getAlignArea(); // synch align data v.8.5
13197
setColorfixFactors(1);
13199
squeeze = 0.95; // search range reduction
13201
for (int ii = 0; ii < 500; ii++) // loop random alignments v.8.5
13204
if (rnum < 0.33) // random change some alignment offset
13205
xoff = xoffB + xoff_range * (drand48() - 0.5); // within search range
13206
else if (rnum < 0.67)
13207
yoff = yoffB + yoff_range * (drand48() - 0.5);
13209
toff = toffB + toff_range * (drand48() - 0.5);
13211
matchlev = matchImages(); // test quality of image alignment
13212
if (sigdiff(matchlev,matchB,0.0001) > 0) {
13213
lens_mmB = lens_mm; // better
13214
lens_bowB = lens_bow;
13215
xoffB = xoff; // (no refresh align pixels v.8.1)
13218
matchB = matchlev; // save new best fit
13220
squeeze = 1; // keep same search range as long
13221
break; // as improvements are found
13226
if (pano_stat != -1) goto done;
13229
if (xoff_range < xoff_rfinal) goto done;
13231
mm_range = squeeze * mm_range; // reduce search range if no
13232
if (mm_range < 0.02 * lens_mmB) mm_range = 0.02 * lens_mmB; // improvements were found
13233
bow_range = squeeze * bow_range;
13234
if (bow_range < 0.1 * lens_bowB) bow_range = 0.1 * lens_bowB;
13235
if (bow_range < 0.2) bow_range = 0.2;
13236
xoff_range = squeeze * xoff_range;
13237
yoff_range = squeeze * yoff_range;
13238
toff_range = squeeze * toff_range;
13242
lens_mm = lens_mmB; // set best alignment found
13243
lens_bow = lens_bowB;
13255
// Thread function for combining A1 + A2 >> E3
13257
void * pano_align_thread(void *)
13259
int firstpass, lastpass;
13260
double xystep, xylim, tstep, tlim;
13261
double xfL, xfH, yfL, yfH, tfL, tfH;
13262
double wxL, wxH, wyL, wyH;
13265
Nalign = 1; // alignment in progress
13266
aligntype = 3; // pano
13268
Fzoom = 0; // fit to window if big
13269
Fblowup = 1; // scale up to window if small
13270
showRedpix = 1; // highlight alignment pixels
13276
alignR = alignSize; // from pre-align or prior pass
13277
if (firstpass) alignSize = 140; // set next align size
13278
else if (lastpass) alignSize = fullSize;
13279
else alignSize = int(pano_image_increase * alignSize); // next larger image size
13280
if (alignSize > 0.8 * fullSize) alignSize = fullSize; // if near goal, jump to it now
13281
alignR = alignSize / alignR; // ratio of new / old image size
13283
xoff = alignR * xoff; // adjust offsets for new image size
13284
yoff = alignR * yoff;
13287
warpxu = alignR * warpxu; // adjust warp values
13288
warpyu = alignR * warpyu;
13289
warpxl = alignR * warpxl;
13290
warpyl = alignR * warpyl;
13292
if (! lastpass) pano_get_align_images(1,0); // get new alignment images
13295
alignWidth = int(A1ww - xoff); // set new alignment area
13296
if (alignWidth > A1ww/3) alignWidth = A1ww/3;
13299
alignWidth = int(alignR * alignWidth * pano_blend_decrease);
13300
if (alignWidth < pano_min_alignwidth * alignSize) // keep within range
13301
alignWidth = int(pano_min_alignwidth * alignSize);
13302
if (alignWidth > pano_max_alignwidth * alignSize)
13303
alignWidth = int(pano_max_alignwidth * alignSize);
13306
alignHeight = A1hh;
13308
getAlignArea(); // get image overlap area
13309
getBrightRatios(); // get color brightness ratios
13310
setColorfixFactors(1); // set color matching factors
13311
flagEdgePixels(); // flag high-contrast pixels in blend
13313
xylim = 2; // +/- search range, centered on
13314
xystep = 0.571; // results from prior stage
13317
xylim = alignSize * 0.03; // 3% error tolerance in pre-alignment
13322
xylim = 1; // final stage pixel search steps
13323
xystep = 0.5; // -1.0 -0.5 0.0 +0.5 +1.0
13326
tlim = xylim / alignSize / 2; // theta max offset, radians
13327
tstep = xystep / alignSize / 2; // theta step size
13329
xfL = xoff - xylim; // set x/y/t search ranges, step sizes
13330
xfH = xoff + xylim + xystep/2;
13331
yfL = yoff - xylim;
13332
yfH = yoff + xylim + xystep/2;
13334
tfH = toff + tlim + tstep/2;
13336
xoffB = xoff; // initial offsets = best so far
13340
warpxuB = warpxu; // initial warp values
13345
matchB = matchImages(); // set base match level
13346
pano_combine(0); // v.8.4
13348
for (xoff = xfL; xoff < xfH; xoff += xystep) // test x, y, theta offsets
13349
for (yoff = yfL; yoff < yfH; yoff += xystep) // in all possible combinations
13350
for (toff = tfL; toff < tfH; toff += tstep)
13352
matchlev = matchImages();
13353
if (sigdiff(matchlev,matchB,0.00001) > 0) { // remember best alignment and offsets
13361
SBupdate++; // update status bar
13364
xoff = xoffB; // recover best offsets
13370
wxL = warpxuB - xylim; // warp image2 corners v.8.5
13371
wxH = warpxuB + xylim + xystep/2; // double range v.8.6.1
13372
wyL = warpyuB - xylim;
13373
wyH = warpyuB + xylim + xystep/2;
13375
for (warpxu = wxL; warpxu < wxH; warpxu += xystep) // search upper warp
13376
for (warpyu = wyL; warpyu < wyH; warpyu += xystep)
13378
pano_get_align_images(0,1); // curve and warp
13379
matchlev = matchImages();
13380
if (sigdiff(matchlev,matchB,0.00001) > 0) { // remember best warp
13390
warpxu = warpxuB; // restore best warp
13392
pano_get_align_images(0,0);
13394
wxL = warpxlB - xylim;
13395
wxH = warpxlB + xylim + xystep/2;
13396
wyL = warpylB - xylim;
13397
wyH = warpylB + xylim + xystep/2;
13399
for (warpxl = wxL; warpxl < wxH; warpxl += xystep) // search lower warp
13400
for (warpyl = wyL; warpyl < wyH; warpyl += xystep)
13402
pano_get_align_images(0,2);
13403
matchlev = matchImages();
13404
if (sigdiff(matchlev,matchB,0.00001) > 0) {
13416
pano_get_align_images(0,0);
13419
pano_combine(0); // combine images and update window
13422
if (lastpass) break;
13423
if (alignSize == fullSize) lastpass = 1; // one more pass, reduced step size
13426
pano_stat = 1; // signal success
13427
showRedpix = 0; // pixel highlights off
13428
Fzoom = Fblowup = 0; // reset image scaling
13430
return 0; // never executed, stop g++ warning
13434
// do manual adjustment of brightness, color, blend width
13436
void pano_final_adjust()
13438
int pano_adjust_event(zdialog *zd, const char *event); // dialog event function
13439
int pano_adjust_compl(zdialog *zd, int zstat); // dialog completion function
13440
void * pano_adjust_thread(void *);
13444
const char *adjmessage = ZTX("\n Match Brightness and Color");
13446
pano_automatch = 1; // init. auto color match on
13450
getBrightRatios(); // get color brightness ratios
13451
setColorfixFactors(pano_automatch); // set color matching factors
13452
pano_combine(1); // show final results
13454
zdedit = zdialog_new(ZTX("Match Images"),mWin,Bdone,Bcancel,null); // color adjustment dialog
13456
zdialog_add_widget(zdedit,"label","lab0","dialog",adjmessage,"space=10");
13457
zdialog_add_widget(zdedit,"hbox","hb1","dialog",0,"space=10"); // match brightness and color
13458
zdialog_add_widget(zdedit,"vbox","vb1","hb1",0,"homog");
13459
zdialog_add_widget(zdedit,"vbox","vb2","hb1",0,"homog"); // red [ 100 ]
13460
zdialog_add_widget(zdedit,"label","lab1","vb1",Bred,"space=7"); // green [ 100 ]
13461
zdialog_add_widget(zdedit,"label","lab2","vb1",Bgreen,"space=7"); // blue [ 100 ]
13462
zdialog_add_widget(zdedit,"label","lab3","vb1",Bblue,"space=7"); // brightness [ 100 ]
13463
zdialog_add_widget(zdedit,"label","lab4","vb1",Bbrightness,"space=7"); // blend width [ 0 ]
13464
zdialog_add_widget(zdedit,"label","lab5","vb1",Bblendwidth,"space=7"); //
13465
zdialog_add_widget(zdedit,"spin","spred","vb2","50|200|0.1|100"); // [ apply ] [ auto ] off
13466
zdialog_add_widget(zdedit,"spin","spgreen","vb2","50|200|0.1|100");
13467
zdialog_add_widget(zdedit,"spin","spblue","vb2","50|200|0.1|100");
13468
zdialog_add_widget(zdedit,"spin","spbright","vb2","50|200|0.1|100");
13469
zdialog_add_widget(zdedit,"spin","spblend","vb2","1|200|1|1");
13470
zdialog_add_widget(zdedit,"hbox","hb2","dialog",0,"space=5");
13471
zdialog_add_widget(zdedit,"button","apply","hb2",Bapply,"space=5");
13472
zdialog_add_widget(zdedit,"button","auto","hb2",ZTX("Auto"));
13473
zdialog_add_widget(zdedit,"label","labauto","hb2","on");
13475
zstat = zdialog_run(zdedit,pano_adjust_event,pano_adjust_compl); // run dialog, parallel
13477
start_thread(pano_adjust_thread,0); // start thread
13478
wrapup_thread(0); // wait for completion
13483
// thread function - stall return from final_adjust until dialog done
13485
void * pano_adjust_thread(void *)
13487
while (true) thread_idle_loop(); // wait for work or exit request
13488
return 0; // not executed, stop g++ warning
13492
// dialog completion function
13494
int pano_adjust_compl(zdialog *zd, int zstat)
13496
zdialog_free(zdedit); // kill dialog
13498
if (zstat == 1) pano_stat = 1;
13499
else pano_stat = 0;
13500
wrapup_thread(8); // kill thread
13505
// dialog event function
13506
// A1 + A2 >> E3 under control of spin buttons
13508
int pano_adjust_event(zdialog *zd, const char *event)
13510
double red, green, blue, bright, bright2;
13512
if (strEqu(event,"auto"))
13514
pano_automatch = 1 - pano_automatch; // toggle color automatch state
13515
setColorfixFactors(pano_automatch); // corresp. color matching factors
13516
pano_combine(1); // combine images and update window
13517
if (pano_automatch) zdialog_stuff(zd,"labauto","on");
13518
else zdialog_stuff(zd,"labauto","off");
13522
if (strNeq(event,"apply")) return 0; // wait for apply button
13524
zdialog_fetch(zd,"spred",red); // get color adjustments
13525
zdialog_fetch(zd,"spgreen",green);
13526
zdialog_fetch(zd,"spblue",blue);
13527
zdialog_fetch(zd,"spbright",bright); // brightness adjustment
13528
zdialog_fetch(zd,"spblend",alignWidth); // align and blend width
13530
bright2 = (red + green + blue) / 3; // RGB brightness
13531
bright = bright / bright2; // bright setpoint / RGB brightness
13532
red = red * bright; // adjust RGB brightness
13533
green = green * bright;
13534
blue = blue * bright;
13536
bright = (red + green + blue) / 3;
13537
zdialog_stuff(zd,"spred",red); // force back into consistency
13538
zdialog_stuff(zd,"spgreen",green);
13539
zdialog_stuff(zd,"spblue",blue);
13540
zdialog_stuff(zd,"spbright",bright);
13542
Radjust = red / 100; // normalize 0.5 ... 2.0
13543
Gadjust = green / 100;
13544
Badjust = blue / 100;
13547
getBrightRatios(); // get color brightness ratios
13548
setColorfixFactors(pano_automatch); // set color matching factors
13549
pano_combine(1); // combine and update window
13555
// create scaled and curved alignment images in A1rgb48, A2rgb48
13556
// newf: create new alignment images from scratch
13557
// warp: 0: curve both images, warp both halves of image2
13558
// 1: curve image2 only, warp upper half only
13559
// 2: curve image2 only, warp lower half only
13560
// global variables warpxu/yu and warpxl/yl determine the amount of warp
13562
void pano_get_align_images(int newf, int warp)
13564
void pano_curve_image(RGB *rgbin, RGB *rgbout, int curve, int warp);
13566
double lens_curve, R;
13568
double mm[12] = { 20, 22, 25, 26, 28, 32, 40, 48, 60, 80, 100, 200 };
13569
double curve[12] = { 35, 19, 10.1, 8.7, 6.8, 5.26, 3.42, 2.32, 1.7, 1.35, 1.2, 1.1 };
13573
A1ww = Fww * alignSize / fullSize; // size of alignment images
13574
A1hh = Fhh * alignSize / fullSize;
13575
A2ww = Gww * alignSize / fullSize;
13576
A2hh = Ghh * alignSize / fullSize;
13578
RGB_free(pano_A1cache); // align images = scaled input images
13579
RGB_free(pano_A2cache);
13580
pano_A1cache = RGB_rescale(Frgb48,A1ww,A1hh); // new scaled align images
13581
pano_A2cache = RGB_rescale(Grgb48,A2ww,A2hh);
13583
RGB_free(A1rgb48); // A1/A2 will be curved
13585
A1rgb48 = A2rgb48 = 0;
13587
spline1(12,mm,curve); // always initialize bugfix v.6.2
13590
lens_curve = spline2(lens_mm);
13591
pano_curve = lens_curve * 0.01 * A2ww; // curve % to pixels
13592
pano_bow = 0.01 * A2ww * lens_bow; // lens_bow % to pixels
13593
xshrink = 0.5 * pano_curve; // image shrinkage from curving
13594
yshrink = xshrink * pano_ycurveF * A2hh / A2ww;
13596
R = 1.0 * A2hh / A2ww;
13597
if (R > 1) pano_curve = pano_curve / R / R; // adjust for vertical format
13599
if (warp == 0) { // curve image1
13600
if (A2ww <= 0.8 * A1ww) {
13601
if (A1rgb48) RGB_free(A1rgb48); // already curved via prior pano
13602
A1rgb48 = RGB_copy(pano_A1cache); // make a copy
13605
if (! A1rgb48) A1rgb48 = RGB_make(A1ww,A1hh,48); // curve image1, both halves, no warp
13606
pano_curve_image(pano_A1cache,A1rgb48,2,0);
13610
if (! A2rgb48) A2rgb48 = RGB_make(A2ww,A2hh,48);
13612
if (warp == 0) pano_curve_image(pano_A2cache,A2rgb48,2,3); // curve and warp all image2
13613
if (warp == 1) pano_curve_image(pano_A2cache,A2rgb48,1,1); // "" left-upper quadrant
13614
if (warp == 2) pano_curve_image(pano_A2cache,A2rgb48,1,2); // "" left-lower quadrant
13620
// curve and warp alignment image
13621
// curve: 1: left half only 2: both halves
13622
// warp: 0: none 1: upper half 2: lower half 3: both
13624
// global variables:
13625
// warpxu/yu: upper left corner warp displacement
13626
// warpxl/yl: lower left corner warp displacement
13628
void pano_curve_image(RGB *rgbin, RGB *rgbout, int curve, int warp) // overhauled v.8.5
13630
int pxc, pyc, ww, hh, vstat;
13631
int pycL, pycH, pxcL, pxcH;
13632
double px, py, xdisp, ydisp, xpull, ypull;
13633
uint16 vpix[3], *pixc;
13641
if (curve == 1) { // curve left half only
13642
pxcL = pxmL - xoff; // from blend stripe to middle
13643
pxcH = pxmH - xoff; // v.7.7
13648
if (warp == 1) pycH = hh / 2; // upper half only
13649
if (warp == 2) pycL = hh / 2; // lower half only
13651
for (pyc = pycL; pyc < pycH; pyc++)
13652
for (pxc = pxcL; pxc < pxcH; pxc++)
13654
xdisp = (pxc - ww/2.0) / (ww/2.0); // -1 ... 0 ... +1
13655
ydisp = (pyc - hh/2.0) / (ww/2.0);
13656
xpull = xdisp * xdisp * xdisp;
13657
ypull = pano_ycurveF * ydisp * xdisp * xdisp;
13659
px = pxc + pano_curve * xpull; // apply lens curve factor
13660
py = pyc + pano_curve * ypull;
13661
px -= pano_bow * xdisp * ydisp * ydisp; // apply lens bow factor
13663
if (warp && xdisp < 0) { // bugfix v.8.6.1
13664
if (pyc < hh / 2) { // warp upper-left quadrant
13665
px += warpxu * xdisp;
13666
py += warpyu * ydisp * xdisp; // bugfix v.8.6.1
13669
px += warpxl * xdisp; // warp lower-left
13670
py += warpyl * ydisp * xdisp;
13674
vstat = vpixel(rgbin,px,py,vpix); // input virtual pixel
13675
pixc = bmpixel(rgbout,pxc,pyc); // output real pixel
13681
else pixc[0] = pixc[1] = pixc[2] = 0;
13688
// combine and images: A1rgb48 + A2rgb48 >> E3rgb48
13689
// update window showing current progress
13691
void pano_combine(int fcolor)
13693
int px3, py3, ii, max, vstat1, vstat2;
13694
int red1, green1, blue1;
13695
int red2, green2, blue2;
13696
int red3, green3, blue3;
13697
uint16 vpix1[3], vpix2[3], *pix3;
13698
double ww, px1, py1, px2, py2, f1, f2;
13699
double costf = cos(toff), sintf = sin(toff);
13701
mutex_lock(&pixmaps_lock);
13703
ww = xoff + A2ww; // combined width
13704
if (toff < 0) ww -= A2hh * toff; // adjust for theta
13706
E3hh = A1rgb48->hh;
13708
RGB_free(E3rgb48); // allocate output pixmap
13709
E3rgb48 = RGB_make(E3ww,E3hh,48);
13711
overlapixs = 0; // counts overlapping pixels
13712
red1 = green1 = blue1 = 0; // suppress compiler warnings
13713
red2 = green2 = blue2 = 0;
13715
for (py3 = 0; py3 < E3hh; py3++) // step through E3 rows
13716
for (px3 = 0; px3 < E3ww; px3++) // step through E3 pixels in row
13718
vstat1 = vstat2 = 0;
13719
red3 = green3 = blue3 = 0;
13722
px1 = costf * px3 - sintf * (py3 - yoff); // A1 pixel, after offsets
13723
py1 = costf * py3 + sintf * (px3 - xoff);
13724
vstat1 = vpixel(A1rgb48,px1,py1,vpix1);
13728
px2 = costf * (px3 - xoff) + sintf * (py3 - yoff); // A2 pixel, after offsets
13729
py2 = costf * (py3 - yoff) - sintf * (px3 - xoff);
13730
vstat2 = vpixel(A2rgb48,px2,py2,vpix2);
13737
if (!red1 && !green1 && !blue1) vstat1 = 0; // ignore black pixels
13744
if (!red2 && !green2 && !blue2) vstat2 = 0;
13747
if (fcolor) { // brightness compensation
13748
if (vstat1) { // (auto + manual adjustments)
13749
red1 = int(R12match[red1]);
13750
green1 = int(G12match[green1]);
13751
blue1 = int(B12match[blue1]);
13752
if (red1 > 65535 || green1 > 65535 || blue1 > 65535) {
13754
if (green1 > max) max = green1;
13755
if (blue1 > max) max = blue1;
13756
f1 = 65535.0 / max;
13757
red1 = int(red1 * f1);
13758
green1 = int(green1 * f1);
13759
blue1 = int(blue1 * f1);
13763
if (vstat2) { // adjust both images
13764
red2 = int(R21match[red2]); // in opposite directions
13765
green2 = int(G21match[green2]);
13766
blue2 = int(B21match[blue2]);
13767
if (red2 > 65535 || green2 > 65535 || blue2 > 65535) {
13769
if (green2 > max) max = green2;
13770
if (blue2 > max) max = blue2;
13771
f1 = 65535.0 / max;
13772
red2 = int(red2 * f1);
13773
green2 = int(green2 * f1);
13774
blue2 = int(blue2 * f1);
13781
red3 = red1; // use image1 pixel
13786
overlapixs++; // count overlapped pixels
13788
if (alignWidth == 0) f1 = 1.0;
13789
else f1 = 1.0 * (pxmH - px3) / alignWidth; // use progressive blend
13791
red3 = int(f1 * red1 + f2 * red2);
13792
green3 = int(f1 * green1 + f2 * green2);
13793
blue3 = int(f1 * blue1 + f2 * blue2);
13795
else { // use 50/50 mix
13796
red3 = (red1 + red2) / 2;
13797
green3 = (green1 + green2) / 2;
13798
blue3 = (blue1 + blue2) / 2;
13804
red3 = red2; // use image2 pixel
13809
pix3 = bmpixel(E3rgb48,px3,py3); // output pixel
13814
if (showRedpix && vstat1 && vstat2) { // highlight alignment pixels
13815
ii = py3 * A1ww + px3;
13816
if (redpixels[ii]) {
13818
pix3[1] = pix3[2] = 0;
13823
mutex_unlock(&pixmaps_lock);
13824
mwpaint2(); // update window
13829
/**************************************************************************
13830
HDR and pano shared functions (image matching, alignment, overlay)
13831
***************************************************************************/
13833
// compare two doubles for significant difference
13834
// return: 0 difference not significant
13838
int sigdiff(double d1, double d2, double signf)
13840
double diff = fabs(d1-d2);
13841
if (diff == 0.0) return 0;
13842
diff = diff / (fabs(d1) + fabs(d2));
13843
if (diff < signf) return 0;
13844
if (d1 > d2) return 1;
13849
/**************************************************************************/
13851
// Get the rectangle containing the overlap region of two images
13852
// outputs: pxL pxH pyL pyH total image overlap rectangle
13853
// pxmL pxmH pymL pymH reduced to overlap align area
13855
void getAlignArea()
13860
if (xoff > 0) pxL = int(xoff);
13863
if (pxH > xoff + A2ww) pxH = int(xoff + A2ww);
13866
if (yoff > 0) pyL = int(yoff);
13869
if (pyH > yoff + A2hh) pyH = int(yoff + A2hh);
13872
pyL2 = int(yoff + toff * (pxH - pxL));
13873
if (pyL2 > pyL) pyL = pyL2;
13877
pxL2 = int(xoff - toff * (pyH - pyL));
13878
if (pxL2 > pxL) pxL = pxL2;
13882
pxL = pxL + xshrink; // reduce overlap area by amount
13883
pxH = pxH - xshrink; // of image shrink (pano, HDF)
13884
pyL = pyL + yshrink;
13885
pyH = pyH - yshrink;
13888
pxM = (pxL + pxH) / 2; // midpoint of overlap
13889
pyM = (pyL + pyH) / 2;
13891
if (alignWidth < (pxH - pxL)) {
13892
pxmL = pxM - alignWidth/2; // overlap area width for
13893
pxmH = pxM + alignWidth/2; // image matching & blending
13894
if (pxmL < pxL) pxmL = pxL;
13895
if (pxmH > pxH) pxmH = pxH;
13897
else { // use whole range
13902
if (alignHeight < (pyH - pyL)) {
13903
pymL = pyM - alignHeight/2;
13904
pymH = pyM + alignHeight/2;
13905
if (pymL < pyL) pymL = pyL;
13906
if (pymH > pyH) pymH = pyH;
13917
/**************************************************************************/
13919
// Compute brightness ratio by color for overlapping image areas.
13920
// (image2 is overlayed on image1, offset by xoff, yoff, toff)
13923
// Bratios1[rgb][ii] = image2/image1 brightness ratio for color rgb
13924
// and image1 brightness ii
13925
// Bratios2[rgb][ii] = image1/image2 brightness ratio for color rgb
13926
// and image2 brightness ii
13928
void getBrightRatios()
13930
uint16 vpix1[3], vpix2[3];
13931
int vstat1, vstat2;
13932
int px, py, pxinc, pyinc, ii, jj, rgb;
13933
int npix, npix1, npix2, npix3;
13934
int brdist1[3][256], brdist2[3][256];
13935
double px1, py1, px2, py2;
13936
double brlev1[3][256], brlev2[3][256];
13937
double costf = cos(toff), sintf = sin(toff);
13938
double a1, a2, b1, b2, bratio = 1;
13939
double s8 = 1.0 / 256.0;
13941
for (rgb = 0; rgb < 3; rgb++) // clear distributions
13942
for (ii = 0; ii < 256; ii++)
13943
brdist1[rgb][ii] = brdist2[rgb][ii] = 0;
13945
pxinc = pyinc = 1; // bugfix v.7.7.1
13946
npix = (pxH - pxL) * (pyH - pyL);
13947
if (npix > 500000) pxinc = 2; // reduce excessive sample v.7.7
13948
if (npix > 1000000) pyinc = 2;
13952
for (py = pyL; py < pyH; py += pyinc) // scan image1/image2 pixels parallel
13953
for (px = pxL; px < pxH; px += pxinc) // use entire overlap area
13955
px1 = costf * px - sintf * (py - yoff); // image1 pixel, after offsets
13956
py1 = costf * py + sintf * (px - xoff);
13957
vstat1 = vpixel(A1rgb48,px1,py1,vpix1);
13958
if (! vstat1) continue; // does not exist
13959
if (!vpix1[0] && !vpix1[1] && !vpix1[2]) continue; // ignore black pixels
13961
px2 = costf * (px - xoff) + sintf * (py - yoff); // corresponding image2 pixel
13962
py2 = costf * (py - yoff) - sintf * (px - xoff);
13963
vstat2 = vpixel(A2rgb48,px2,py2,vpix2);
13964
if (! vstat2) continue; // does not exist
13965
if (!vpix2[0] && !vpix2[1] && !vpix2[2]) continue; // ignore black pixels
13967
++npix; // count overlapping pixels
13969
for (rgb = 0; rgb < 3; rgb++) // accumulate distributions
13970
{ // by color in 256 bins
13971
++brdist1[rgb][int(s8*vpix1[rgb])];
13972
++brdist2[rgb][int(s8*vpix2[rgb])];
13976
npix1 = npix / 256; // 1/256th of total pixels
13978
for (rgb = 0; rgb < 3; rgb++) // get brlev1[rgb][N] = mean bright
13979
for (ii = jj = 0; jj < 256; jj++) // for Nth group of image1 pixels
13981
brlev1[rgb][jj] = 0;
13982
npix2 = npix1; // 1/256th of total pixels
13984
while (npix2 > 0 && ii < 256) // next 1/256th group from distr,
13986
npix3 = brdist1[rgb][ii];
13987
if (npix3 == 0) { ++ii; continue; }
13988
if (npix3 > npix2) npix3 = npix2;
13989
brlev1[rgb][jj] += ii * npix3; // brightness * (pixels with)
13990
brdist1[rgb][ii] -= npix3;
13994
brlev1[rgb][jj] = brlev1[rgb][jj] / npix1; // mean brightness for group, 0-255
13997
for (rgb = 0; rgb < 3; rgb++) // do same for image2
13998
for (ii = jj = 0; jj < 256; jj++)
14000
brlev2[rgb][jj] = 0;
14003
while (npix2 > 0 && ii < 256)
14005
npix3 = brdist2[rgb][ii];
14006
if (npix3 == 0) { ++ii; continue; }
14007
if (npix3 > npix2) npix3 = npix2;
14008
brlev2[rgb][jj] += ii * npix3;
14009
brdist2[rgb][ii] -= npix3;
14013
brlev2[rgb][jj] = brlev2[rgb][jj] / npix1;
14016
for (rgb = 0; rgb < 3; rgb++) // color
14017
for (ii = jj = 0; ii < 256; ii++) // brlev1 brightness, 0 to 255
14019
if (ii == 0) bratio = 1;
14020
while (ii > brlev2[rgb][jj] && jj < 256) ++jj; // find matching brlev2 brightness
14021
a2 = brlev2[rgb][jj]; // next higher value
14022
b2 = brlev1[rgb][jj];
14023
if (a2 > 0 && b2 > 0) {
14025
a1 = brlev2[rgb][jj-1]; // next lower value
14026
b1 = brlev1[rgb][jj-1];
14029
if (ii == 0) bratio = b2 / a2;
14030
else bratio = (b1 + (ii-a1)/(a2-a1) * (b2-b1)) / ii; // interpolate
14033
if (bratio < 0.2) bratio = 0.2; // contain outliers
14034
if (bratio > 5) bratio = 5;
14035
Bratios2[rgb][ii] = bratio;
14038
for (rgb = 0; rgb < 3; rgb++) // color
14039
for (ii = jj = 0; ii < 256; ii++) // brlev2 brightness, 0 to 255
14041
if (ii == 0) bratio = 1;
14042
while (ii > brlev1[rgb][jj] && jj < 256) ++jj; // find matching brlev1 brightness
14043
a2 = brlev1[rgb][jj]; // next higher value
14044
b2 = brlev2[rgb][jj];
14045
if (a2 > 0 && b2 > 0) {
14047
a1 = brlev1[rgb][jj-1]; // next lower value
14048
b1 = brlev2[rgb][jj-1];
14051
if (ii == 0) bratio = b2 / a2;
14052
else bratio = (b1 + (ii-a1)/(a2-a1) * (b2-b1)) / ii; // interpolate
14055
if (bratio < 0.2) bratio = 0.2; // contain outliers
14056
if (bratio > 5) bratio = 5;
14057
Bratios1[rgb][ii] = bratio;
14064
/**************************************************************************/
14066
// Set color matching factors
14067
// on: Bratios are used for color matching the two images
14068
// off: Bratios are not used - use 1.0 instead
14069
// In both cases, manual settings Radjust/Gadjust/Badjust are used
14071
void setColorfixFactors(int state)
14077
for (ii = 0; ii < 65536; ii++)
14081
R12match[ii] = sqrt(Bratios1[0][jj]) / Radjust * ii; // use sqrt(ratio) so that adjustment
14082
G12match[ii] = sqrt(Bratios1[1][jj]) / Gadjust * ii; // can be applied to both images
14083
B12match[ii] = sqrt(Bratios1[2][jj]) / Badjust * ii; // in opposite directions
14085
R21match[ii] = sqrt(Bratios2[0][jj]) * Radjust * ii;
14086
G21match[ii] = sqrt(Bratios2[1][jj]) * Gadjust * ii;
14087
B21match[ii] = sqrt(Bratios2[2][jj]) * Badjust * ii;
14093
for (ii = 0; ii < 65536; ii++)
14095
R12match[ii] = 1.0 / Radjust * ii;
14096
G12match[ii] = 1.0 / Gadjust * ii;
14097
B12match[ii] = 1.0 / Badjust * ii;
14099
R21match[ii] = Radjust * ii;
14100
G21match[ii] = Gadjust * ii;
14101
B21match[ii] = Badjust * ii;
14109
/**************************************************************************/
14111
// find pixels of greatest contrast within overlap area
14112
// flag high-contrast pixels to use in each image compare region
14114
void flagEdgePixels()
14116
void flagEdgePixels2(int pxL, int pxH, int pyL, int pyH, int samp);
14118
int samp = pixsamp / 9;
14119
int pxm1, pxm2, pym1, pym2;
14121
if (redpixels) zfree(redpixels); // clear flags for alignment pixels
14122
redpixels = zmalloc(A1ww*A1hh);
14123
memset(redpixels,0,A1ww*A1hh);
14125
pxm1 = pxmL + 0.333 * (pxmH - pxmL);
14126
pxm2 = pxmL + 0.667 * (pxmH - pxmL);
14127
pym1 = pymL + 0.333 * (pymH - pymL);
14128
pym2 = pymL + 0.667 * (pymH - pymL);
14130
flagEdgePixels2(pxmL+8, pxm1, pymL+8, pym1, samp); // 9 zones v.8.0
14131
flagEdgePixels2(pxmL+8, pxm1, pym1, pym2, samp);
14132
flagEdgePixels2(pxmL+8, pxm1, pym2, pymH-10, samp);
14133
flagEdgePixels2(pxm1, pxm2, pymL+8, pym1, samp);
14134
flagEdgePixels2(pxm1, pxm2, pym1, pym2, samp);
14135
flagEdgePixels2(pxm1, pxm2, pym2, pymH-10, samp);
14136
flagEdgePixels2(pxm2, pxmH-10, pymL+8, pym1, samp);
14137
flagEdgePixels2(pxm2, pxmH-10, pym1, pym2, samp);
14138
flagEdgePixels2(pxm2, pxmH-10, pym2, pymH-10, samp);
14144
// Find the highest contrast pixels meeting sample size
14145
// within the specified sub-region of image overlap.
14147
void flagEdgePixels2(int pxL, int pxH, int pyL, int pyH, int samp)
14149
int px, py, ii, jj, npix, vstat1, vstat2, vstat3;
14150
int red1, green1, blue1, red2, green2, blue2, tcon;
14151
int Hdist[256], Vdist[256], Hmin, Vmin;
14152
double costf = cos(toff), sintf = sin(toff);
14153
double px1, py1, px2, py2, s8 = 1.0 / 769.0;
14154
uchar *Hcon, *Vcon;
14155
uint16 vpix1[3], vpix2[3], vpix3[3];
14157
npix = (pxH - pxL) * (pyH - pyL); // overlapping pixels
14158
if (npix < 100) return; // insignificant
14159
if (samp > npix / 4) samp = npix / 4; // use max. 1/4 of pixels
14161
Hcon = (uchar *) zmalloc(npix); // horizontal pixel contrast 0-255
14162
Vcon = (uchar *) zmalloc(npix); // vertical pixel contrast 0-255
14164
for (py = pyL; py < pyH; py++) // scan image pixels in sub-region
14165
for (px = pxL; px < pxH; px++)
14167
ii = (py-pyL) * (pxH-pxL) + (px-pxL);
14168
Hcon[ii] = Vcon[ii] = 0; // horiz. = vert. contrast = 0
14170
px1 = costf * px - sintf * (py - yoff); // image1 pixel
14171
py1 = costf * py + sintf * (px - xoff);
14172
vstat1 = vpixel(A1rgb48,px1,py1,vpix1);
14173
if (! vstat1) continue; // does not exist
14174
if (!vpix1[0] && !vpix1[1] && !vpix1[2]) continue; // ignore black pixels
14176
px2 = costf * (px - xoff) + sintf * (py - yoff); // corresponding image2 pixel
14177
py2 = costf * (py - yoff) - sintf * (px - xoff); // v.7.5
14178
vstat2 = vpixel(A2rgb48,px2,py2,vpix2);
14179
if (! vstat2) continue;
14180
if (!vpix2[0] && !vpix2[1] && !vpix2[2]) continue;
14182
vstat3 = vpixel(A1rgb48,px1+4,py1,vpix3); // 4 pixels to right
14183
if (! vstat3) continue; // reject if off edge
14184
if (! vpix3[0] && ! vpix3[1] && ! vpix3[2]) continue;
14186
vstat3 = vpixel(A1rgb48,px1,py1+4,vpix3); // 4 pixels below
14187
if (! vstat3) continue;
14188
if (! vpix3[0] && ! vpix3[1] && ! vpix3[2]) continue;
14190
vstat3 = vpixel(A1rgb48,px1+4,py1+4,vpix3); // verify overlap of +4 pixels
14191
if (! vstat3) continue; // in all directions v.7.5
14192
if (!vpix3[0] && !vpix3[1] && !vpix3[2]) continue;
14194
vstat3 = vpixel(A2rgb48,px2+4,py2+4,vpix3);
14195
if (! vstat3) continue;
14196
if (!vpix3[0] && !vpix3[1] && !vpix3[2]) continue;
14198
vstat3 = vpixel(A1rgb48,px1-4,py1-4,vpix3);
14199
if (! vstat3) continue;
14200
if (!vpix3[0] && !vpix3[1] && !vpix3[2]) continue;
14202
vstat3 = vpixel(A2rgb48,px2-4,py2-4,vpix3);
14203
if (! vstat3) continue;
14204
if (!vpix3[0] && !vpix3[1] && !vpix3[2]) continue;
14210
vstat3 = vpixel(A1rgb48,px1+2,py1,vpix3); // 2 pixels to right
14214
tcon = abs(red1-red2) + abs(green1-green2) + abs(blue1-blue2); // horizontal contrast
14215
Hcon[ii] = int(tcon * s8); // 0 - 255
14217
vstat3 = vpixel(A1rgb48,px1,py1+2,vpix3); // 2 pixels below
14221
tcon = abs(red1-red2) + abs(green1-green2) + abs(blue1-blue2); // vertical contrast
14222
Vcon[ii] = int(tcon * s8);
14225
for (ii = 0; ii < 256; ii++) Hdist[ii] = Vdist[ii] = 0; // clear contrast distributions
14227
for (py = pyL; py < pyH; py++) // scan image pixels v.7.5
14228
for (px = pxL; px < pxH; px++)
14229
{ // build contrast distributions
14230
ii = (py-pyL) * (pxH-pxL) + (px-pxL);
14235
for (npix = 0, ii = 255; ii > 0; ii--) // find minimum contrast needed to get
14236
{ // enough pixels for sample size
14237
npix += Hdist[ii]; // (horizontal contrast pixels)
14238
if (npix > samp) break;
14242
for (npix = 0, ii = 255; ii > 0; ii--) // (verticle contrast pixels)
14245
if (npix > samp) break;
14249
for (py = pyL; py < pyH; py++) // scan image pixels v.7.5
14250
for (px = pxL; px < pxH; px++)
14252
ii = (py-pyL) * (pxH-pxL) + (px-pxL);
14253
jj = py * A1ww + px;
14255
if (Hcon[ii] > Hmin) {
14256
redpixels[jj] = 1; // flag horizontal group of 3
14257
redpixels[jj+1] = 1;
14258
redpixels[jj+2] = 1;
14261
if (Vcon[ii] > Vmin) {
14262
redpixels[jj] = 1; // flag verticle group of 3
14263
redpixels[jj+A1ww] = 1;
14264
redpixels[jj+2*A1ww] = 1;
14274
/**************************************************************************/
14276
// Compare two images in overlapping areas.
14277
// (image2 is overlayed on image1, offset by xoff, yoff, toff).
14278
// Use pixels with contrast > minimum needed to reach sample size.
14279
// return: 1 = perfect match, 0 = total mismatch (black/white)
14281
double matchImages() // weighting removed v.8.5
14283
uint16 vpix1[3], vpix2[3];
14284
int px, py, ii, vstat1, vstat2;
14285
double px1, py1, px2, py2;
14286
double costf = cos(toff), sintf = sin(toff);
14287
double match, cmatch, maxcmatch;
14289
if (pxM > A1ww || pyM > A1hh) return 0; // overlap runs off image, no match
14291
cmatch = maxcmatch = 0;
14293
for (py = pymL; py < pymH; py++) // step through image1 pixels, rows
14294
for (px = pxmL; px < pxmH; px++) // step through image1 pixels, cols
14296
ii = py * A1ww + px; // skip low-contrast pixels
14297
if (! redpixels[ii]) continue;
14299
px1 = costf * px - sintf * (py - yoff); // image1 pixel
14300
py1 = costf * py + sintf * (px - xoff);
14301
vstat1 = vpixel(A1rgb48,px1,py1,vpix1);
14302
if (! vstat1) continue; // does not exist
14303
if (!vpix1[0] && !vpix1[1] && !vpix1[2]) continue; // ignore black pixels
14305
px2 = costf * (px - xoff) + sintf * (py - yoff); // corresponding image2 pixel
14306
py2 = costf * (py - yoff) - sintf * (px - xoff);
14307
vstat2 = vpixel(A2rgb48,px2,py2,vpix2);
14308
if (! vstat2) continue;
14309
if (!vpix2[0] && !vpix2[1] && !vpix2[2]) continue;
14311
match = matchPixels(vpix1,vpix2); // compare brightness adjusted
14312
cmatch += match; // accumulate total match
14316
return cmatch / maxcmatch;
14320
/**************************************************************************/
14322
// Compare 2 pixels using precalculated brightness ratios
14323
// 1.0 = perfect match 0 = total mismatch (black/white)
14325
double matchPixels(uint16 *pix1, uint16 *pix2)
14327
double red1, green1, blue1, red2, green2, blue2;
14328
double reddiff, greendiff, bluediff, match;
14329
double ff = 1.0 / 65536.0;
14331
red1 = R12match[pix1[0]];
14332
green1 = G12match[pix1[1]];
14333
blue1 = B12match[pix1[2]];
14335
red2 = R21match[pix2[0]];
14336
green2 = G21match[pix2[1]];
14337
blue2 = B21match[pix2[2]];
14339
reddiff = ff * fabs(red1-red2); // 0 = perfect match
14340
greendiff = ff * fabs(green1-green2); // 1 = total mismatch
14341
bluediff = ff * fabs(blue1-blue2);
14343
match = (1.0 - reddiff) * (1.0 - greendiff) * (1.0 - bluediff); // 1 = perfect match
14348
/**************************************************************************/
14350
// Get a virtual pixel at location (px,py) (real) in an RGB-48 pixmap.
14351
// Get the overlapping real pixels and build a composite.
14353
int vpixel(RGB *rgb, double px, double py, uint16 *vpix) // overhauled v.7.7
14355
int ww, hh, px0, py0;
14356
uint16 *ppix, *pix0, *pix1, *pix2, *pix3;
14357
double f0, f1, f2, f3;
14358
double red, green, blue;
14362
ppix = (uint16 *) rgb->bmp;
14364
px0 = int(px); // pixel containing (px,py)
14367
if (px0 < 1 || py0 < 1) return 0;
14368
if (px0 > ww-3 || py0 > hh-3) return 0;
14370
pix0 = ppix + (py0 * ww + px0) * 3; // 4 pixels based at (px0,py0)
14371
pix1 = pix0 + ww * 3;
14373
pix3 = pix0 + ww * 3 + 3;
14375
f0 = (px0+1 - px) * (py0+1 - py); // overlap of (px,py)
14376
f1 = (px0+1 - px) * (py - py0); // in each of the 4 pixels
14377
f2 = (px - px0) * (py0+1 - py);
14378
f3 = (px - px0) * (py - py0);
14380
red = f0 * pix0[0] + f1 * pix1[0] + f2 * pix2[0] + f3 * pix3[0]; // sum the weighted inputs
14381
green = f0 * pix0[1] + f1 * pix1[1] + f2 * pix2[1] + f3 * pix3[1];
14382
blue = f0 * pix0[2] + f1 * pix1[2] + f2 * pix2[2] + f3 * pix3[2];
14384
vpix[0] = int(red);
14385
vpix[1] = int(green);
14386
vpix[2] = int(blue);
14392
/**************************************************************************
14394
edit transaction and thread support functions overhauled v.6.2
14396
edit transaction management
14397
edit_setup() start new edit - copy E3 > E1
14398
edit_cancel() cancel edit - E1 > E3, delete E1
14399
edit_done() commit edit - add to undo stack
14400
edit_undo() undo edit - E1 > E3
14401
edit_redo() redo edit - run thread again
14402
edit_fullsize() convert preview to full-size pixmaps
14404
main level thread management
14405
start_thread(func,arg) start thread running
14406
signal_thread() signal thread that work is pending
14407
wait_thread_idle() wait for pending work complete
14408
wrapup_thread(command) wait for exit or command thread exit
14409
thread_working() return idle/working status
14412
thread_idle_loop() wait for pending work, exit if commanded
14413
exit_thread() exit thread unconditionally
14415
thread_status (thread ownership
14416
0 no thread is running
14417
1 thread is running and idle (no work)
14418
2 thread is working
14419
0 thread has exited
14421
thread_command (main program ownership)
14422
0 idle, no work pending
14423
8 exit when pending work is done
14424
9 exit now, unconditionally
14426
thread_pend work requested counter
14427
thread_done work done counter
14428
thread_hwm high water mark
14429
edit_action done/cancel/undo/redo in progress
14431
***************************************************************************/
14433
int thread_command = 0, thread_status = 0;
14434
int thread_pend = 0, thread_done = 0, thread_hwm = 0;
14435
int edit_action = 0;
14438
/**************************************************************************
14440
Setup for a new edit transaction
14441
Create E1 (edit input) and E3 (edit output) pixmaps from
14442
previous edit output (Frgb48) or image file (new Frgb48).
14444
uprev 0 edit full-size image
14445
1 edit preview image unless area exists and uarea = 2
14447
uarea 0 area is invalid and will be deleted (e.g. rotate)
14448
1 area not used but remains valid (e.g. red-eye)
14449
2 area is used and remains valid (e.g. flatten)
14451
***************************************************************************/
14453
int edit_setup(int uprev, int uarea)
14455
if (! image_file) return 0; // no image file
14456
if (! menulock(1)) return 0; // lock menu
14458
if (Pundo > Pundo_max) {
14459
zmessageACK(ZTX("Too many undo buffers, please save image"));
14464
if (uarea == 0 && sa_stat) { // v.8.5
14465
if (! zmessageYN(ZTX("Select area cannot be kept.\n"
14472
if (uarea == 2 && sa_stat && ! sa_Npixel) { // v.8.5
14473
if (! zmessageYN(ZTX("Select area is not finished.\n"
14474
"Continue without using it?"))) {
14480
if (Fimageturned) {
14481
turn_image(-Fimageturned); // un-turn if needed
14482
uarea = 0; // (select area did already)
14485
if (uarea == 0 && sa_stat) { // delete select area v.8.5
14487
zdialog_free(zdsela);
14491
Fpreview = 0; // use preview image if supported
14492
if (uprev && ! (uarea == 2 && sa_Npixel)) Fpreview = 1; // and select area will not be used
14494
if (Fpreview && Fzoom) {
14496
mwpaint(); // do immediately v.8.6
14499
if (Fpreview && sa_stat) m_select_hide(0,0); // v.8.5
14501
mutex_lock(&pixmaps_lock); // lock pixmaps
14503
if (! Frgb48) Frgb48 = image_load(image_file,48); // create Frgb48 if not already
14505
mutex_unlock(&pixmaps_lock);
14510
RGB_free(E1rgb48); // free prior edit pixmaps
14513
if (Fpreview) // edit pixmaps are window-size
14514
E1rgb48 = RGB_rescale(Frgb48,dww,dhh); // E1rgb48 = Frgb48 scaled to window
14515
else E1rgb48 = RGB_copy(Frgb48); // edit pixmaps are full-size
14516
E3rgb48 = RGB_copy(E1rgb48); // E1 >> E3
14518
E1ww = E3ww = E1rgb48->ww;
14519
E1hh = E3hh = E1rgb48->hh;
14521
save_undo(); // save Frgb48 in undo stack
14522
Fmodified = 0; // image not modified yet
14523
thread_command = thread_status = 0; // no thread running
14524
thread_pend = thread_done = thread_hwm = 0; // no work pending or done
14526
mutex_unlock(&pixmaps_lock);
14532
/**************************************************************************/
14534
// process edit cancel
14538
if (edit_action) return; // v.6.8
14541
wrapup_thread(9); // tell thread to quit now
14543
if (zdedit) { // v.6.6
14544
zdialog_free(zdedit); // kill dialog
14548
mutex_lock(&pixmaps_lock);
14549
RGB_free(E1rgb48); // free edit pixmaps E1, E3
14555
E1ww = E3ww = Dww = 0;
14557
Fmodified = Fpreview = 0; // reset flags
14558
Ntoplines = Nptoplines; // no overlay lines
14559
paint_toparc(2); // no brush outline
14560
mutex_unlock(&pixmaps_lock);
14561
menulock(0); // unlock menu
14562
mwpaint2(); // refresh window
14568
/**************************************************************************/
14570
// process edit dialog [done]
14571
// E3rgb48 >> Frgb48 >> Frgb24
14575
if (edit_action) return; // v.6.8
14578
if (Fpreview && Fmodified) {
14579
Fzoom = 0; // v.8.3
14580
edit_fullsize(); // update full image
14583
wrapup_thread(8); // wait for thread done
14585
if (zdedit) { // v.6.6
14586
zdialog_free(zdedit); // kill dialog
14590
mutex_lock(&pixmaps_lock);
14593
RGB_free(Frgb48); // memory leak v.6.8
14594
Frgb48 = RGB_copy(E3rgb48); // E3 >> Frgb48
14596
Frgb24 = RGB_convbpp(Frgb48); // Frgb48 >> Frgb24
14604
save_undo(); // save next undo state
14607
RGB_free(E1rgb48); // free edit pixmaps
14609
E1rgb48 = E3rgb48 = 0;
14612
Fmodified = Fpreview = 0; // reset flags
14613
Ntoplines = Nptoplines; // no overlay lines
14614
paint_toparc(2); // no brush outline
14615
mutex_unlock(&pixmaps_lock);
14616
menulock(0); // unlock menu
14617
mwpaint2(); // update window
14623
/**************************************************************************/
14625
// process edit dialog [undo] and [redo] (not the toolbar functions)
14629
uint16 *pix1, *pix3;
14632
if (! Fmodified) return; // v.6.3
14634
if (edit_action) return; // v.6.8
14637
if (sa_Npixel) // select area exists
14639
for (int ii = 0; ii < sa_Npixel; ii++) // restore enclosed pixels
14641
px = sa_pixel[ii].px;
14642
py = sa_pixel[ii].py;
14643
pix1 = bmpixel(E1rgb48,px,py);
14644
pix3 = bmpixel(E3rgb48,px,py);
14652
{ // restore entire image
14653
mutex_lock(&pixmaps_lock);
14654
RGB_free(E3rgb48); // (size may have changed)
14655
E3rgb48 = RGB_copy(E1rgb48);
14661
Fmodified = 0; // reset image modified status
14662
mutex_unlock(&pixmaps_lock);
14665
mwpaint2(); // refresh window
14673
if (edit_action) return; // v.6.8
14675
signal_thread(); // start thread working
14682
/**************************************************************************/
14684
// Convert from preview mode (window-size pixmaps) to full-size pixmaps.
14686
void edit_fullsize() // v.6.2
14688
if (! Fpreview) return;
14691
mutex_lock(&pixmaps_lock);
14692
RGB_free(E1rgb48); // free preview pixmaps
14694
E1rgb48 = RGB_copy(Frgb48); // make full-size pixmaps
14695
E3rgb48 = RGB_copy(Frgb48);
14698
mutex_unlock(&pixmaps_lock);
14700
signal_thread(); // reinstate edits
14701
wait_thread_idle();
14706
/**************************************************************************/
14708
// update progress for long-running functions on status bar
14709
// update in steps of 1%
14710
// call with args = 0 when done
14712
void edit_progress(int done, int goal)
14714
static int ppercentdone = 0;
14717
PercentDone = ppercentdone = 0;
14718
if (pthread_equal(tid_fmain,pthread_self())) update_statusbar();
14723
PercentDone = 100.0 * done / goal;
14724
if (PercentDone - ppercentdone < 1) return;
14726
ppercentdone = PercentDone;
14727
if (pthread_equal(tid_fmain,pthread_self())) update_statusbar();
14733
/**************************************************************************/
14735
// start thread that does the edit work
14737
void start_thread(threadfunc func, void *arg)
14739
thread_status = 1; // thread is running
14740
thread_command = thread_pend = thread_done = thread_hwm = 0; // nothing pending
14741
start_detached_thread(func,arg);
14746
// signal thread that work is pending
14748
void signal_thread()
14750
if (thread_status > 0) thread_pend++; // v.6.2
14755
// wait for edit thread to complete pending work and become idle
14757
void wait_thread_idle()
14759
while (thread_status && thread_pend > thread_done)
14769
// wait for thread exit or command thread exit
14770
// command = 0 wait for normal completion
14771
// 8 finish pending work and exit
14772
// 9 quit, exit now
14774
void wrapup_thread(int command)
14776
thread_command = command; // tell thread to quit or finish
14778
while (thread_status > 0) // wait for thread to finish
14779
{ // pending work and exit
14788
// return thread idle/working status
14790
int thread_working() // v.8.4
14792
if (thread_status == 2) return 1;
14797
// called only from edit threads
14798
// idle loop - wait for work request or exit command
14800
void thread_idle_loop()
14802
thread_status = 1; // status = idle
14803
thread_done = thread_hwm; // work done = high-water mark
14807
if (thread_command == 9) exit_thread(); // quit now command
14808
if (thread_command == 8) // finish work and exit
14809
if (thread_pend <= thread_done) exit_thread();
14810
if (thread_pend > thread_done) break; // wait for work request
14814
thread_hwm = thread_pend; // set high-water mark
14815
thread_status = 2; // thread is working
14816
return; // loop to thread
14820
// called only from edit threads
14821
// exit thread unconditionally
14825
thread_pend = thread_done = thread_hwm = 0;
14831
/**************************************************************************
14832
undo / redo toolbar buttons
14833
***************************************************************************/
14835
// [undo] menu function - reinstate previous edit in undo/redo stack
14837
void m_undo(GtkWidget *, const char *)
14839
if (Pundo == 0) return;
14840
if (! menulock(1)) return;
14848
// [redo] menu function - reinstate next edit in undo/redo stack
14850
void m_redo(GtkWidget *, const char *)
14852
if (Pundo == Pumax) return;
14853
if (! menulock(1)) return;
14861
// Save Frgb48 to undo/redo file stack
14862
// stack position = Pundo
14866
char *pp, buff[24];
14869
pp = strstr(undo_files,"_undo_");
14870
if (! pp) zappcrash("undo/redo stack corrupted 1");
14871
snprintf(pp+6,3,"%02d",Pundo);
14873
fid = open(undo_files,O_WRONLY|O_CREAT|O_TRUNC,0640);
14874
if (! fid) zappcrash("undo/redo stack corrupted 2");
14876
snprintf(buff,24," %05d %05d fotoxx ",Fww,Fhh);
14877
cc = write(fid,buff,20);
14878
if (cc != 20) zappcrash("undo/redo stack corrupted 3");
14880
cc = Fww * Fhh * 6;
14881
cc2 = write(fid,Frgb48->bmp,cc);
14882
if (cc2 != cc) zappcrash("undo/redo stack corrupted 4");
14889
// Load Frgb48 from undo/redo file stack
14890
// stack position = Pundo
14894
char *pp, buff[24], fotoxx[8];
14895
int fid, ww, hh, cc, cc2;
14897
pp = strstr(undo_files,"_undo_");
14898
if (! pp) zappcrash("undo/redo stack corrupted 1");
14899
snprintf(pp+6,3,"%02d",Pundo);
14901
fid = open(undo_files,O_RDONLY);
14902
if (! fid) zappcrash("undo/redo stack corrupted 2");
14905
cc = read(fid,buff,20);
14906
sscanf(buff," %d %d %8s ",&ww, &hh, fotoxx);
14907
if (! strEqu(fotoxx,"fotoxx")) zappcrash("undo/redo stack corrupted 4");
14909
mutex_lock(&pixmaps_lock); // v.6.3
14912
Frgb48 = RGB_make(ww,hh,48);
14914
cc2 = read(fid,Frgb48->bmp,cc);
14915
if (cc2 != cc) zappcrash("undo/redo stack corrupted 5");
14919
Frgb24 = RGB_convbpp(Frgb48);
14922
RGB_free(Drgb24); // v.6.8
14926
mutex_unlock(&pixmaps_lock);
14932
/**************************************************************************
14933
other support functions
14934
***************************************************************************/
14936
// help menu function
14938
void m_help(GtkWidget *, const char *menu)
14940
if (strEqu(menu,ZTX("About")))
14941
zmessageACK(" %s \n %s \n %s \n %s \n\n %s \n\n %s",
14942
fversion,flicense,fhomepage,fcredits,ftranslators,fcontact);
14944
if (strEqu(menu,"FreeImage"))
14945
zmessageACK(FreeImage_GetCopyrightMessage());
14947
if (strEqu(menu,ZTX("User Guide")))
14950
if (strEqu(menu,"README"))
14953
if (strEqu(menu,ZTX("Change Log")))
14956
if (strEqu(menu,ZTX("Translate")))
14957
showz_translations();
14959
if (strEqu(menu,ZTX("Home Page")))
14960
showz_html(fhomepage);
14966
/**************************************************************************/
14968
// restore state data from prior session
14970
int load_fotoxx_state()
14972
int ww, hh, ii, np;
14974
char buff[1000], text[100], *pp;
14977
lens4_name[0] = strdupz("lens_1",lens_cc);
14978
lens4_name[1] = strdupz("lens_2",lens_cc);
14979
lens4_name[2] = strdupz("lens_3",lens_cc);
14980
lens4_name[3] = strdupz("lens_4",lens_cc);
14991
snprintf(buff,999,"%s/saved_state",get_zuserdir()); // open saved state file
14992
fid = fopen(buff,"r");
14993
if (! fid) return 0;
14995
pp = fgets_trim(buff,999,fid,1); // read last image file
14997
if (pp && *pp == '/') {
14998
if (image_file) zfree(image_file);
14999
image_file = strdupz(pp);
15002
pp = fgets_trim(buff,999,fid,1); // top directory
15003
if (pp && *pp == '/') topdirk = strdupz(pp);
15005
pp = fgets(buff,999,fid); // main window size
15008
sscanf(buff," %d %d ",&ww,&hh);
15009
if (ww > 200 && ww < 3000) Dww = ww;
15010
if (hh > 200 && hh < 2000) Dhh = hh;
15013
pp = fgets(buff,999,fid); // image gallery window size
15016
sscanf(buff," %d %d ",&ww,&hh);
15017
if (ww > 200 && ww < 3000) image_navi::xwinW = ww;
15018
if (hh > 200 && hh < 2000) image_navi::xwinH = hh;
15021
pp = fgets_trim(buff,999,fid,1); // thumbnail image size
15023
sscanf(buff," %d ",&ww);
15024
if (ww > 32 && ww < 256) image_navi::thumbsize = ww;
15027
for (ii = 0; ii < 4; ii++) // 4 sets of lens parameters
15029
pp = fgets_trim(buff,999,fid,1);
15031
np = sscanf(buff," %s %f %f ",text,&parms[0],&parms[1]);
15032
if (np != 3) break;
15033
strncpy0(lens4_name[ii],text,lens_cc);
15034
lens4_mm[ii] = parms[0];
15035
lens4_bow[ii] = parms[1];
15038
pp = fgets_trim(buff,999,fid,1); // current lens
15040
sscanf(buff," %f ",&parms[0]);
15041
if (parms[0] >= 0 && parms[0] < 4) curr_lens = int(parms[0]);
15044
pp = fgets_trim(buff,999,fid,1); // fotoxx version changed
15045
if (! pp || strNeq(pp,fversion)) { /* do nothing */ }
15049
for (ii = 0; ii < Nrecentfiles; ii++) // recent image file list = empty
15050
recentfiles[ii] = 0;
15052
snprintf(buff,999,"%s/recent_files",get_zuserdir()); // open recent files file v.8.2
15053
fid = fopen(buff,"r");
15054
if (! fid) return 0;
15056
for (ii = 0; ii < Nrecentfiles; ii++) // read list of recent files
15058
pp = fgets_trim(buff,999,fid,1);
15060
if (*pp == '/') recentfiles[ii] = strdupz(buff);
15069
/**************************************************************************/
15071
// save state data for next session
15073
int save_fotoxx_state()
15079
snprintf(buff,999,"%s/saved_state",get_zuserdir()); // open output file
15080
fid = fopen(buff,"w");
15081
if (! fid) return 0;
15083
if (image_file && *image_file == '/') // current image file
15084
fputs(image_file,fid);
15087
if (topdirk && *topdirk == '/') // top image directory
15088
fputs(topdirk,fid);
15091
gtk_window_get_size(GTK_WINDOW(mWin),&ww,&hh);
15092
snprintf(buff,20," %d %d \n",ww,hh); // window size
15095
snprintf(buff,20," %d %d \n",image_navi::xwinW,image_navi::xwinH); // image gallery window size
15098
snprintf(buff,20," %d \n",image_navi::thumbsize); // thumbnail size
15101
for (ii = 0; ii < 4; ii++) // 4 sets of lens parameters
15103
snprintf(buff,100," %s %.1f %.2f \n",lens4_name[ii],lens4_mm[ii],lens4_bow[ii]);
15107
snprintf(buff,100," %d \n",curr_lens); // current lens
15110
fputs(fversion,fid); // fotoxx version
15116
snprintf(buff,999,"%s/recent_files",get_zuserdir()); // open output file
15117
fid = fopen(buff,"w");
15118
if (! fid) return 0;
15120
for (ii = 0; ii < Nrecentfiles; ii++) // save list of recent files v.8.2
15121
if (recentfiles[ii])
15122
fprintf(fid,"%s \n",recentfiles[ii]);
15130
/**************************************************************************/
15132
// free all resources associated with the current image file
15134
void free_resources()
15136
char *pp, command[200];
15139
mutex_lock(&pixmaps_lock); // lock pixmaps
15141
strcpy(command,"rm -f "); // delete all undo files
15142
strcat(command,undo_files);
15143
pp = strstr(command,"_undo_"); // clone edit, bugfix v.6.5
15144
strcpy(pp + 6,"*");
15145
ignore = system(command);
15147
Fmodified = Pundo = Pumax = Fsaved = 0; // reset undo/redo stack
15148
Ntoplines = Nptoplines; // no image overlay lines
15149
paint_toparc(2); // no brush outline
15151
if (Fshutdown) { // stop here if shutdown mode
15152
mutex_unlock(&pixmaps_lock);
15157
select_delete(); // delete select area
15158
zdialog_free(zdsela); // kill dialog if active v.8.5
15160
update_filetags(image_file); // commit tag changes, if any
15161
zfree(image_file); // free image file
15176
Fww = E1ww = E3ww = Dww = 0; // make unusable (crash)
15178
mutex_unlock(&pixmaps_lock);
15183
/**************************************************************************/
15185
// ask user if modified image should be kept or discarded
15189
if (Fmodified == 0 && Pundo == 0) return 0; // no mods
15190
if (Fsaved == Pundo) return 0; // last mods were saved v.8.3
15191
if (zmessageYN(ZTX("Discard modifications?"))) return 0; // OK to discard
15196
/**************************************************************************/
15198
// menu lock/unlock - some functions must not run concurrently
15200
int menulock(int lock)
15202
static int mlock = 0;
15204
if (lock && mlock) {
15205
zmessageACK("please wait for prior function to complete");
15209
if (! lock && ! mlock) zappcrash("menu lock error");
15217
/**************************************************************************/
15219
// Upright a turned image - not like an edit.
15220
// Rotate Frgb24 without setting the Fmodified flag.
15222
void turn_image(int angle)
15224
while (angle >= 360) angle -= 360; // amount to turn now
15225
while (angle <= -360) angle += 360;
15226
Fimageturned += angle; // total turn v.8.3
15227
while (Fimageturned >= 360) Fimageturned -= 360;
15228
while (Fimageturned <= -360) Fimageturned += 360;
15229
if (angle == 0) return;
15231
mutex_lock(&pixmaps_lock); // lock pixmaps v.8.5
15232
RGB * temp_bmp = RGB_rotate(Frgb24,angle);
15238
mutex_unlock(&pixmaps_lock);
15240
mwpaint(); // synch Dww etc. v.6.8
15245
/**************************************************************************/
15247
// FREEIMAGE error handler
15249
void FI_error(FIF fif, const char *message)
15251
printf("FreeImage error: %s file: %s \n",message,image_file);
15256
/**************************************************************************
15257
pixmap conversion and rescale functions revamped v.6.5
15258
***************************************************************************/
15260
// initialize an RGB pixmap - allocate memory
15262
RGB * RGB_make(int ww, int hh, int bpp)
15264
if (ww < 1 || hh < 1 || (bpp != 24 && bpp != 48))
15265
zappcrash("RGB_make() %d %d %d",ww,hh,bpp);
15267
RGB *rgb = (RGB *) zmalloc(sizeof(RGB));
15271
if (bpp == 24) rgb->bmp = zmalloc(ww * hh * 3);
15272
if (bpp == 48) rgb->bmp = zmalloc(ww * hh * 6);
15273
strcpy(rgb->wmi,"rgbrgb");
15280
void RGB_free(RGB *rgb)
15282
if (! rgb) return; // v.6.8
15283
if (! strEqu(rgb->wmi,"rgbrgb"))
15284
zappcrash("RGB_free(), bad RGB");
15285
strcpy(rgb->wmi,"xxxxxx");
15292
// create a copy of an RGB pixmap
15294
RGB * RGB_copy(RGB *rgb1)
15299
rgb2 = RGB_make(rgb1->ww, rgb1->hh, rgb1->bpp);
15300
cc = rgb1->ww * rgb1->hh * (rgb1->bpp / 8); // fix integer overflow for
15301
memcpy(rgb2->bmp,rgb1->bmp,cc); // huge images v.7.8
15306
// create a copy of an RGB area
15308
RGB * RGB_copy_area(RGB *rgb1, int orgx, int orgy, int ww2, int hh2)
15310
uint8 *bmp1, *pix1, *bmp2, *pix2;
15311
uint16 *bmp3, *pix3, *bmp4, *pix4;
15313
int ww1, hh1, bpp, px1, py1, px2, py2;
15321
rgb2 = RGB_make(ww2,hh2,24);
15322
bmp1 = (uint8 *) rgb1->bmp;
15323
bmp2 = (uint8 *) rgb2->bmp;
15325
for (py1 = orgy, py2 = 0; py2 < hh2; py1++, py2++)
15327
for (px1 = orgx, px2 = 0; px2 < ww2; px1++, px2++)
15329
pix1 = bmp1 + (py1 * ww1 + px1) * 3;
15330
pix2 = bmp2 + (py2 * ww2 + px2) * 3;
15343
rgb2 = RGB_make(ww2,hh2,48);
15344
bmp3 = (uint16 *) rgb1->bmp;
15345
bmp4 = (uint16 *) rgb2->bmp;
15347
for (py1 = orgy, py2 = 0; py2 < hh2; py1++, py2++)
15349
for (px1 = orgx, px2 = 0; px2 < ww2; px1++, px2++)
15351
pix3 = bmp3 + (py1 * ww1 + px1) * 3;
15352
pix4 = bmp4 + (py2 * ww2 + px2) * 3;
15367
// convert RGB pixmap from 24/48 to 48/24 bits per pixel
15369
RGB * RGB_convbpp(RGB *rgb1)
15371
uint8 *bmp8, *pix8;
15372
uint16 *bmp16, *pix16;
15374
int ww, hh, bpp, px, py;
15382
rgb2 = RGB_make(ww,hh,48);
15383
bmp8 = (uint8 *) rgb1->bmp;
15384
bmp16 = (uint16 *) rgb2->bmp;
15386
for (py = 0; py < hh; py++)
15388
pix8 = bmp8 + py * ww * 3;
15389
pix16 = bmp16 + py * ww * 3;
15391
for (px = 0; px < ww; px++)
15393
pix16[0] = pix8[0] << 8;
15394
pix16[1] = pix8[1] << 8;
15395
pix16[2] = pix8[2] << 8;
15404
rgb2 = RGB_make(ww,hh,24);
15405
bmp8 = (uint8 *) rgb2->bmp;
15406
bmp16 = (uint16 *) rgb1->bmp;
15408
for (py = 0; py < hh; py++)
15410
pix8 = bmp8 + py * ww * 3;
15411
pix16 = bmp16 + py * ww * 3;
15413
for (px = 0; px < ww; px++)
15415
pix8[0] = pix16[0] >> 8;
15416
pix8[1] = pix16[1] >> 8;
15417
pix8[2] = pix16[2] >> 8;
15428
// convert RGB to FI bitmap, inverting row order
15430
FIB * RGB_FIB(RGB *rgb)
15434
uint8 *bmp1, *pix1, *pix2;
15435
uint16 *bmp2, *pix3, *pix4;
15436
int ww, hh, bpp, px, py, pitch;
15444
fib = FreeImage_Allocate(ww,hh,24);
15445
if (! fib) zappcrash("FIB allocation failure");
15447
bmp1 = (uint8 *) rgb->bmp;
15448
bmpf = FreeImage_GetBits(fib);
15449
pitch = FreeImage_GetPitch(fib);
15451
for (py = 0; py < hh; py++)
15453
pix1 = bmp1 + (hh - py - 1) * ww * 3;
15454
pix2 = bmpf + py * pitch;
15456
for (px = 0; px < ww; px++)
15458
pix2[RR] = pix1[0];
15459
pix2[GG] = pix1[1];
15460
pix2[BB] = pix1[2];
15469
fib = FreeImage_AllocateT(FIT_RGB16,ww,hh,48);
15470
if (! fib) zappcrash("FIB allocation failure");
15472
bmp2 = (uint16 *) rgb->bmp;
15473
bmpf = FreeImage_GetBits(fib);
15474
pitch = FreeImage_GetPitch(fib);
15476
for (py = 0; py < hh; py++)
15478
pix3 = bmp2 + (hh - py - 1) * ww * 3;
15479
pix4 = (uint16 *) (bmpf + py * pitch);
15481
for (px = 0; px < ww; px++)
15496
// convert FI to RGB pixmap, inverting row order
15498
RGB * FIB_RGB(FIB *fib)
15502
uint8 *bmp1, *pix1, *pix2;
15503
uint16 *bmp2, *pix3, *pix4;
15504
int ww, hh, bpp, px, py, pitch;
15506
ww = FreeImage_GetWidth(fib);
15507
hh = FreeImage_GetHeight(fib);
15508
bpp = FreeImage_GetBPP(fib);
15509
pitch = FreeImage_GetPitch(fib);
15510
bmpf = FreeImage_GetBits(fib);
15512
rgb = RGB_make(ww,hh,bpp);
15516
bmp1 = (uint8 *) rgb->bmp;
15518
for (py = 0; py < hh; py++)
15520
pix1 = bmp1 + (hh - py - 1) * ww * 3;
15521
pix2 = bmpf + py * pitch;
15523
for (px = 0; px < ww; px++)
15525
pix1[0] = pix2[RR];
15526
pix1[1] = pix2[GG];
15527
pix1[2] = pix2[BB];
15536
bmp2 = (uint16 *) rgb->bmp;
15538
for (py = 0; py < hh; py++)
15540
pix3 = bmp2 + (hh - py - 1) * ww * 3;
15541
pix4 = (uint16 *) (bmpf + py * pitch);
15543
for (px = 0; px < ww; px++)
15558
// convert RGB-24 pixmap to GDK pixbuf (24)
15560
PXB * RGB_PXB(RGB *rgb) // new v.6.7.2
15562
int ww, hh, bpp, px, py, rowst;
15563
uint8 *bmp1, *bmp2, *pix1, *pix2;
15569
if (bpp != 24) zappcrash("RGB_PXB bpp %d",bpp);
15571
pxb = gdk_pixbuf_new(colorspace,0,8,ww,hh);
15572
if (! pxb) zappcrash("pixbuf allocation failure");
15574
bmp1 = (uint8 *) rgb->bmp;
15575
bmp2 = gdk_pixbuf_get_pixels(pxb);
15576
rowst = gdk_pixbuf_get_rowstride(pxb);
15578
for (py = 0; py < hh; py++)
15579
for (px = 0; px < ww; px++)
15581
pix1 = bmp1 + (py * ww + px) * 3;
15582
pix2 = bmp2 + rowst * py + 3 * px;
15592
// convert GDK pixbuf (24/32) to RGB-24 pixmap
15594
RGB * PXB_RGB(PXB *pxb)
15597
int ww, hh, px, py, nch, rowst;
15598
uint8 *bmp1, *bmp2, *pix1, *pix2;
15600
ww = gdk_pixbuf_get_width(pxb);
15601
hh = gdk_pixbuf_get_height(pxb);
15602
nch = gdk_pixbuf_get_n_channels(pxb);
15603
rowst = gdk_pixbuf_get_rowstride(pxb);
15604
bmp1 = gdk_pixbuf_get_pixels(pxb);
15606
rgb = RGB_make(ww,hh,24);
15607
bmp2 = (uint8 *) rgb->bmp;
15609
for (py = 0; py < hh; py++)
15610
for (px = 0; px < ww; px++)
15612
pix1 = bmp1 + rowst * py + nch * px;
15613
pix2 = bmp2 + (py * ww + px) * 3;
15623
// rescale RGB pixmap to size ww2 x hh2
15625
RGB * RGB_rescale(RGB *rgb1, int ww2, int hh2)
15627
void RGB_rescale_24(uint8*, uint8*, int, int, int, int);
15628
void RGB_rescale_48(uint16*, uint16*, int, int, int, int);
15632
uint8 *bmp1, *bmp2;
15633
uint16 *bmp3, *bmp4;
15639
rgb2 = RGB_make(ww2,hh2,bpp);
15642
bmp1 = (uint8 *) rgb1->bmp;
15643
bmp2 = (uint8 *) rgb2->bmp;
15644
RGB_rescale_24(bmp1,bmp2,ww1,hh1,ww2,hh2);
15648
bmp3 = (uint16 *) rgb1->bmp;
15649
bmp4 = (uint16 *) rgb2->bmp;
15650
RGB_rescale_48(bmp3,bmp4,ww1,hh1,ww2,hh2);
15657
// rotate RGB pixmap through given angle in degrees (+ = clockwise)
15659
RGB * RGB_rotate(RGB *rgb1, double angle)
15661
RGB * RGB_rotate_24(RGB *, double);
15662
RGB * RGB_rotate_48(RGB *, double);
15668
if (bpp == 24) rgb2 = RGB_rotate_24(rgb1,angle);
15669
if (bpp == 48) rgb2 = RGB_rotate_48(rgb1,angle);
15674
// Load an image file into an RGB pixmap of 24 or 48 bits/pixel.
15675
// Also sets the following global variables:
15676
// file_MB = disk file megabytes
15677
// file_bpp = disk file bits per pixel (24 or 48)
15678
// file_type = "jpeg" or "tiff" or "other"
15680
RGB * image_load(const char *filespec, int bpp) // revamped v.6.6.1
15683
FIB *fib = 0, *fib2 = 0;
15684
int err, fibpp = 0; // gcc 4.4 v.7.1.1
15685
RGB *rgb24 = 0, *rgb48 = 0;
15688
if (bpp != 24 && bpp != 48) // requested bpp must be 24 or 48
15689
zappcrash("image_load bpp: %d",bpp);
15691
err = stat(filespec,&fstat);
15692
if (err) return 0; // file not found
15693
if (! S_ISREG(fstat.st_mode)) return 0; // not a regular file
15695
fif = FreeImage_GetFileType(filespec,0); // unsupported file type
15696
if (fif == FIF_UNKNOWN) goto ILreturn;
15698
fib = FreeImage_Load(fif,filespec,0);
15699
if (! fib) goto ILreturn; // cannot load file
15701
fibpp = FreeImage_GetBPP(fib); // disk file bits/pixel
15704
fib2 = FreeImage_ConvertTo24Bits(fib); // 1 or 8
15705
if (! fib2) goto ILreturn;
15706
rgb24 = FIB_RGB(fib2);
15707
if (bpp == 48) rgb48 = RGB_convbpp(rgb24);
15710
if (fibpp == 24) { // 24
15711
rgb24 = FIB_RGB(fib);
15712
if (bpp == 48) rgb48 = RGB_convbpp(rgb24);
15715
if (fibpp == 32) { // 24 + alpha
15716
fib2 = FreeImage_ConvertTo24Bits(fib);
15717
if (! fib2) goto ILreturn;
15718
rgb24 = FIB_RGB(fib2);
15719
if (bpp == 48) rgb48 = RGB_convbpp(rgb24);
15722
if (fibpp == 48) { // 48
15723
rgb48 = FIB_RGB(fib);
15724
if (bpp == 24) rgb24 = RGB_convbpp(rgb48);
15728
if (fib) FreeImage_Unload(fib); // free FI bitmaps
15729
if (fib2) FreeImage_Unload(fib2);
15730
if (! rgb24 && ! rgb48) {
15731
zmessageACK(Bsavetoedit); // unsupported file type
15735
file_bpp = fibpp; // disk file bits/pixel
15736
file_MB = 1.0 * fstat.st_size / mega; // disk file megabytes
15737
if (fif == FIF_JPEG) strcpy(file_type,"jpeg"); // disk file type
15738
else if (fif == FIF_TIFF) strcpy(file_type,"tiff");
15739
else strcpy(file_type,"other");
15742
RGB_free(rgb48); // return RGB-24 pixmap
15746
RGB_free(rgb24); // return RGB-48 pixmap
15751
/**************************************************************************
15753
Rescale 24 bpp image (3 x 8 bits per color) to new width and height.
15754
The scale ratios may be different for width and height.
15757
The input and output images are overlayed, stretching or shrinking the
15758
output pixels as needed. The contribution of each input pixel overlapping
15759
an output pixel is proportional to the area of the output pixel covered by
15760
the input pixel. The contributions of all overlaping input pixels are added.
15761
The work is spread among NWthreads to reduce the elapsed time on modern
15762
computers having multiple SMP processors.
15764
Example: if the output image is 40% of the input image, then:
15765
outpix[0,0] = 0.16 * inpix[0,0] + 0.16 * inpix[1,0] + 0.08 * inpix[2,0]
15766
+ 0.16 * inpix[0,1] + 0.16 * inpix[1,1] + 0.08 * inpix[2,1]
15767
+ 0.08 * inpix[0,2] + 0.08 * inpix[1,2] + 0.04 * inpix[2,2]
15772
uint8 *pixmap1; // file scope data for threads
15788
void RGB_rescale_24(uint8 *pixmap1, uint8 *pixmap2, int ww1, int hh1, int ww2, int hh2)
15790
int px1, py1, px2, py2;
15791
int pxl, pyl, pxm, pym, ii;
15792
int maxmapx, maxmapy;
15793
float scalex, scaley;
15794
float px1a, py1a, px1b, py1b;
15797
scalex = 1.0 * ww1 / ww2; // compute x and y scales
15798
scaley = 1.0 * hh1 / hh2;
15800
if (scalex <= 1) maxmapx = 2; // compute max input pixels
15801
else maxmapx = int(scalex + 2); // mapping into output pixels
15802
maxmapx += 1; // for both dimensions
15803
if (scaley <= 1) maxmapy = 2; // (pixels may not be square)
15804
else maxmapy = int(scaley + 2);
15807
memset(pixmap2, 0, ww2 * hh2 * 3); // clear output pixmap
15809
int *py1L = (int *) zmalloc(hh2 * sizeof(int)); // maps first (lowest) input pixel
15810
int *px1L = (int *) zmalloc(ww2 * sizeof(int)); // per output pixel
15812
float *pymap = (float *) zmalloc(hh2 * maxmapy * sizeof(float)); // maps overlap of < maxmap input
15813
float *pxmap = (float *) zmalloc(ww2 * maxmapx * sizeof(float)); // pixels per output pixel
15815
for (py2 = 0; py2 < hh2; py2++) // loop output y-pixels
15817
py1a = py2 * scaley; // corresponding input y-pixels
15818
py1b = py1a + scaley;
15819
if (py1b >= hh1) py1b = hh1 - 0.001; // fix precision limitation
15821
py1L[py2] = pyl; // 1st overlapping input pixel
15823
for (py1 = pyl, pym = 0; py1 < py1b; py1++, pym++) // loop overlapping input pixels
15825
if (py1 < py1a) { // compute amount of overlap
15826
if (py1+1 < py1b) fy = py1+1 - py1a; // 0.0 to 1.0
15829
else if (py1+1 > py1b) fy = py1b - py1;
15832
ii = py2 * maxmapy + pym; // save it
15833
pymap[ii] = 0.9999 * fy / scaley;
15835
ii = py2 * maxmapy + pym; // set an end marker after
15836
pymap[ii] = -1; // last overlapping pixel
15839
for (px2 = 0; px2 < ww2; px2++) // do same for x-pixels
15841
px1a = px2 * scalex;
15842
px1b = px1a + scalex;
15843
if (px1b >= ww1) px1b = ww1 - 0.001;
15847
for (px1 = pxl, pxm = 0; px1 < px1b; px1++, pxm++)
15850
if (px1+1 < px1b) fx = px1+1 - px1a;
15853
else if (px1+1 > px1b) fx = px1b - px1;
15856
ii = px2 * maxmapx + pxm;
15857
pxmap[ii] = 0.9999 * fx / scalex;
15859
ii = px2 * maxmapx + pxm;
15863
rs24poop threaddata[maxWthreads]; // data for threads
15864
void * RGB_rescale_24_thread(void *arg);
15866
for (ii = 0; ii < NWthreads; ii++) // start worker threads
15868
threaddata[ii].pixmap1 = pixmap1;
15869
threaddata[ii].pixmap2 = pixmap2;
15870
threaddata[ii].ww1 = ww1;
15871
threaddata[ii].hh1 = hh1;
15872
threaddata[ii].ww2 = ww2;
15873
threaddata[ii].hh2 = hh2;
15874
threaddata[ii].py1L = py1L;
15875
threaddata[ii].px1L = px1L;
15876
threaddata[ii].pymap = pymap;
15877
threaddata[ii].pxmap = pxmap;
15878
threaddata[ii].maxmapx = maxmapx;
15879
threaddata[ii].maxmapy = maxmapy;
15880
threaddata[ii].index = ii;
15881
threaddata[ii].done = 0;
15882
start_detached_thread(RGB_rescale_24_thread,&threaddata[ii]);
15885
for (ii = 0; ii < NWthreads; ii++) // wait for all done
15886
while (threaddata[ii].done == 0) zsleep(0.004);
15895
void * RGB_rescale_24_thread(void *arg) // worker thread function
15897
rs24poop * threaddata = (rs24poop *) arg;
15899
uint8 *pixmap1 = threaddata->pixmap1;
15900
uint8 *pixmap2 = threaddata->pixmap2;
15901
int ww1 = threaddata->ww1;
15902
int ww2 = threaddata->ww2;
15903
int hh2 = threaddata->hh2;
15904
int *py1L = threaddata->py1L;
15905
int *px1L = threaddata->px1L;
15906
float *pymap = threaddata->pymap;
15907
float *pxmap = threaddata->pxmap;
15908
int maxmapx = threaddata->maxmapx;
15909
int maxmapy = threaddata->maxmapy;
15910
int index = threaddata->index;
15912
int px1, py1, px2, py2;
15913
int pxl, pyl, pxm, pym, ii;
15914
uint8 *pixel1, *pixel2;
15915
float fx, fy, ftot;
15916
float red, green, blue;
15918
for (py2 = index; py2 < hh2; py2 += NWthreads) // loop output y-pixels
15920
pyl = py1L[py2]; // corresp. 1st input y-pixel
15922
for (px2 = 0; px2 < ww2; px2++) // loop output x-pixels
15924
pxl = px1L[px2]; // corresp. 1st input x-pixel
15926
red = green = blue = 0; // initz. output pixel
15928
for (py1 = pyl, pym = 0; ; py1++, pym++) // loop overlapping input y-pixels
15930
ii = py2 * maxmapy + pym; // get y-overlap
15932
if (fy < 0) break; // no more pixels
15934
for (px1 = pxl, pxm = 0; ; px1++, pxm++) // loop overlapping input x-pixels
15936
ii = px2 * maxmapx + pxm; // get x-overlap
15938
if (fx < 0) break; // no more pixels
15940
ftot = fx * fy; // area overlap = x * y overlap
15941
pixel1 = pixmap1 + (py1 * ww1 + px1) * 3;
15942
red += pixel1[0] * ftot; // add input pixel * overlap
15943
green += pixel1[1] * ftot;
15944
blue += pixel1[2] * ftot;
15947
pixel2 = pixmap2 + (py2 * ww2 + px2) * 3; // save output pixel
15948
pixel2[0] = int(red);
15949
pixel2[1] = int(green);
15950
pixel2[2] = int(blue);
15955
threaddata->done = 1;
15960
/**************************************************************************
15962
Rescale 48 bpp image (3 x 16 bits per color) to new width and height.
15963
Identical to RGB_rescale_24 except for the following:
15972
uint16 *pixmap1; // file scope data for threads
15988
void RGB_rescale_48(uint16 *pixmap1, uint16 *pixmap2, int ww1, int hh1, int ww2, int hh2)
15990
int px1, py1, px2, py2;
15991
int pxl, pyl, pxm, pym, ii;
15992
int maxmapx, maxmapy;
15993
float scalex, scaley;
15994
float px1a, py1a, px1b, py1b;
15997
scalex = 1.0 * ww1 / ww2; // compute x and y scales
15998
scaley = 1.0 * hh1 / hh2;
16000
if (scalex <= 1) maxmapx = 2; // compute max input pixels
16001
else maxmapx = int(scalex + 2); // mapping into output pixels
16002
maxmapx += 1; // for both dimensions
16003
if (scaley <= 1) maxmapy = 2; // (pixels may not be square)
16004
else maxmapy = int(scaley + 2);
16007
memset(pixmap2, 0, ww2 * hh2 * 6); // clear output pixmap
16009
int *py1L = (int *) zmalloc(hh2 * sizeof(int)); // maps first (lowest) input pixel
16010
int *px1L = (int *) zmalloc(ww2 * sizeof(int)); // per output pixel
16012
float *pymap = (float *) zmalloc(hh2 * maxmapy * sizeof(float)); // maps overlap of < maxmap input
16013
float *pxmap = (float *) zmalloc(ww2 * maxmapx * sizeof(float)); // pixels per output pixel
16015
for (py2 = 0; py2 < hh2; py2++) // loop output y-pixels
16017
py1a = py2 * scaley; // corresponding input y-pixels
16018
py1b = py1a + scaley;
16019
if (py1b >= hh1) py1b = hh1 - 0.001; // fix precision limitation
16021
py1L[py2] = pyl; // 1st overlapping input pixel
16023
for (py1 = pyl, pym = 0; py1 < py1b; py1++, pym++) // loop overlapping input pixels
16025
if (py1 < py1a) { // compute amount of overlap
16026
if (py1+1 < py1b) fy = py1+1 - py1a; // 0.0 to 1.0
16029
else if (py1+1 > py1b) fy = py1b - py1;
16032
ii = py2 * maxmapy + pym; // save it
16033
pymap[ii] = 0.9999 * fy / scaley;
16035
ii = py2 * maxmapy + pym; // set an end marker after
16036
pymap[ii] = -1; // last overlapping pixel
16039
for (px2 = 0; px2 < ww2; px2++) // do same for x-pixels
16041
px1a = px2 * scalex;
16042
px1b = px1a + scalex;
16043
if (px1b >= ww1) px1b = ww1 - 0.001;
16047
for (px1 = pxl, pxm = 0; px1 < px1b; px1++, pxm++)
16050
if (px1+1 < px1b) fx = px1+1 - px1a;
16053
else if (px1+1 > px1b) fx = px1b - px1;
16056
ii = px2 * maxmapx + pxm;
16057
pxmap[ii] = 0.9999 * fx / scalex;
16059
ii = px2 * maxmapx + pxm;
16063
rs48poop threaddata[maxWthreads]; // data for threads
16064
void * RGB_rescale_48_thread(void *arg);
16066
for (ii = 0; ii < NWthreads; ii++) // start worker threads
16068
threaddata[ii].pixmap1 = pixmap1;
16069
threaddata[ii].pixmap2 = pixmap2;
16070
threaddata[ii].ww1 = ww1;
16071
threaddata[ii].hh1 = hh1;
16072
threaddata[ii].ww2 = ww2;
16073
threaddata[ii].hh2 = hh2;
16074
threaddata[ii].py1L = py1L;
16075
threaddata[ii].px1L = px1L;
16076
threaddata[ii].pymap = pymap;
16077
threaddata[ii].pxmap = pxmap;
16078
threaddata[ii].maxmapx = maxmapx;
16079
threaddata[ii].maxmapy = maxmapy;
16080
threaddata[ii].index = ii;
16081
threaddata[ii].done = 0;
16082
start_detached_thread(RGB_rescale_48_thread,&threaddata[ii]);
16085
for (ii = 0; ii < NWthreads; ii++) // wait for all done
16086
while (threaddata[ii].done == 0) zsleep(0.004);
16095
void * RGB_rescale_48_thread(void *arg) // worker thread function
16097
rs48poop * threaddata = (rs48poop *) arg;
16099
uint16 *pixmap1 = threaddata->pixmap1;
16100
uint16 *pixmap2 = threaddata->pixmap2;
16101
int ww1 = threaddata->ww1;
16102
int ww2 = threaddata->ww2;
16103
int hh2 = threaddata->hh2;
16104
int *py1L = threaddata->py1L;
16105
int *px1L = threaddata->px1L;
16106
float *pymap = threaddata->pymap;
16107
float *pxmap = threaddata->pxmap;
16108
int maxmapx = threaddata->maxmapx;
16109
int maxmapy = threaddata->maxmapy;
16110
int index = threaddata->index;
16112
int px1, py1, px2, py2;
16113
int pxl, pyl, pxm, pym, ii;
16114
uint16 *pixel1, *pixel2;
16115
float fx, fy, ftot;
16116
float red, green, blue;
16118
for (py2 = index; py2 < hh2; py2 += NWthreads) // loop output y-pixels
16120
pyl = py1L[py2]; // corresp. 1st input y-pixel
16122
for (px2 = 0; px2 < ww2; px2++) // loop output x-pixels
16124
pxl = px1L[px2]; // corresp. 1st input x-pixel
16126
red = green = blue = 0; // initz. output pixel
16128
for (py1 = pyl, pym = 0; ; py1++, pym++) // loop overlapping input y-pixels
16130
ii = py2 * maxmapy + pym; // get y-overlap
16132
if (fy < 0) break; // no more pixels
16134
for (px1 = pxl, pxm = 0; ; px1++, pxm++) // loop overlapping input x-pixels
16136
ii = px2 * maxmapx + pxm; // get x-overlap
16138
if (fx < 0) break; // no more pixels
16140
ftot = fx * fy; // area overlap = x * y overlap
16141
pixel1 = pixmap1 + (py1 * ww1 + px1) * 3;
16142
red += pixel1[0] * ftot; // add input pixel * overlap
16143
green += pixel1[1] * ftot;
16144
blue += pixel1[2] * ftot;
16147
pixel2 = pixmap2 + (py2 * ww2 + px2) * 3; // save output pixel
16148
pixel2[0] = int(red);
16149
pixel2[1] = int(green);
16150
pixel2[2] = int(blue);
16155
threaddata->done = 1;
16160
/**************************************************************************
16162
RGB *rgb2 = RGB_rotate_24(RGB *rgb1, double angle)
16164
Rotate RGB-24 pixmap through an arbitrary angle (degrees).
16166
The returned image has the same size as the original, but the
16167
pixmap size is increased to accomodate the rotated image.
16168
(e.g. a 100x100 image rotated 45 deg. needs a 142x142 pixmap).
16169
The parameters ww and hh are the dimensions of the input
16170
pixmap, and are updated to the dimensions of the output pixmap.
16172
The space added around the rotated image is black (RGB 0,0,0).
16173
Angle is in degrees. Positive direction is clockwise.
16174
Speed is about 3 million pixels/sec/thread for a 2.4 GHz CPU.
16175
Loss of resolution is less than 1 pixel.
16177
Work is divided among NWthreads to gain speed.
16180
create output pixmap big enough for rotated input pixmap
16181
loop all output pixels:
16182
get next output pixel (px2,py2)
16183
compute (R,theta) from center of pixbuf
16184
rotate theta by -angle
16185
(R,theta) is now within the closest input pixel
16186
convert to input pixel (px1,py1)
16187
if outside of pixmap
16188
output pixel = black
16190
for 4 input pixels based at (px0,py0) = (int(px1),int(py1))
16191
compute overlap (0 to 1) with (px1,py1)
16192
sum RGB values * overlap
16193
output aggregate RGB to pixel (px2,py2)
16197
int rotrgb24_busy = 0;
16198
uint8 *rotrgb24_pixmap1;
16199
uint8 *rotrgb24_pixmap2;
16204
double rotrgb24_angle;
16207
RGB * RGB_rotate_24(RGB *rgb1, double angle)
16209
void *RGB_rotate_24_thread(void *);
16211
int ww1, hh1, ww2, hh2, cc, ii;
16212
double pi = 3.141592654;;
16213
uint8 *pixmap1, *pixmap2;
16218
pixmap1 = (uint8 *) rgb1->bmp;
16220
while (angle < -180) angle += 360; // normalize, -180 to +180
16221
while (angle > 180) angle -= 360;
16222
angle = angle * pi / 180; // radians, -pi to +pi
16224
if (fabs(angle) < 0.001) { // angle = 0 within my precision
16225
rgb2 = RGB_make(ww1,hh1,24); // return a copy of the input
16226
pixmap2 = (uint8 *) rgb2->bmp;
16227
cc = ww1 * hh1 * 3;
16228
memcpy(pixmap2,pixmap1,cc);
16232
ww2 = int(ww1*fabs(cos(angle)) + hh1*fabs(sin(angle))); // rectangle containing rotated image
16233
hh2 = int(ww1*fabs(sin(angle)) + hh1*fabs(cos(angle)));
16235
rgb2 = RGB_make(ww2,hh2,24);
16236
pixmap2 = (uint8 *) rgb2->bmp;
16238
rotrgb24_pixmap1 = pixmap1;
16239
rotrgb24_pixmap2 = pixmap2;
16240
rotrgb24_ww1 = ww1;
16241
rotrgb24_hh1 = hh1;
16242
rotrgb24_ww2 = ww2;
16243
rotrgb24_hh2 = hh2;
16244
rotrgb24_angle = angle;
16246
for (ii = 0; ii < NWthreads; ii++) // start worker threads
16247
start_detached_thread(RGB_rotate_24_thread,&wtindex[ii]);
16248
zadd_locked(rotrgb24_busy,+NWthreads);
16250
while (rotrgb24_busy) zsleep(0.004); // wait for completion
16255
void * RGB_rotate_24_thread(void *arg)
16257
int index = *((int *) (arg));
16258
int ww1, hh1, ww2, hh2;
16259
int px2, py2, px0, py0;
16260
uint8 *ppix1, *ppix2, *pix0, *pix1, *pix2, *pix3;
16261
double rx1, ry1, rx2, ry2, R, theta, px1, py1;
16262
double f0, f1, f2, f3, red, green, blue;
16263
double angle, pi = 3.141592654;
16265
ppix1 = rotrgb24_pixmap1; // input pixel array
16266
ppix2 = rotrgb24_pixmap2; // output pixel array
16267
ww1 = rotrgb24_ww1;
16268
hh1 = rotrgb24_hh1;
16269
ww2 = rotrgb24_ww2;
16270
hh2 = rotrgb24_hh2;
16271
angle = rotrgb24_angle;
16273
for (py2 = index; py2 < hh2; py2 += NWthreads) // loop through output pixels
16274
for (px2 = 0; px2 < ww2; px2++) // outer loop y
16276
rx2 = px2 - 0.5 * ww2; // (rx2,ry2) = center of pixel
16277
ry2 = py2 - 0.5 * hh2;
16278
R = sqrt(rx2*rx2 + ry2*ry2); // convert to (R,theta)
16279
if (R < 0.1) theta = 0;
16280
else theta = qarcsine(ry2 / R); // quick arc sine
16282
if (theta < 0) theta = - pi - theta; // adjust for quandrant
16283
else theta = pi - theta;
16286
theta = theta - angle; // rotate theta backwards
16287
if (theta > pi) theta -= 2 * pi; // range -pi to +pi
16288
if (theta < -pi) theta += 2 * pi;
16290
rx1 = R * qcosine(theta); // quick cosine, sine
16291
ry1 = R * qsine(theta);
16292
px1 = rx1 + 0.5 * ww1; // (px1,py1) = corresponding
16293
py1 = ry1 + 0.5 * hh1; // point within input pixels
16295
px0 = int(px1); // pixel containing (px1,py1)
16298
if (px1 < 0 || px0 >= ww1-1 || py1 < 0 || py0 >= hh1-1) { // if outside input pixel array
16299
pix2 = ppix2 + (py2 * ww2 + px2) * 3; // output is black
16300
pix2[0] = pix2[1] = pix2[2] = 0;
16304
pix0 = ppix1 + (py0 * ww1 + px0) * 3; // 4 input pixels based at (px0,py0)
16305
pix1 = pix0 + ww1 * 3;
16309
f0 = (px0+1 - px1) * (py0+1 - py1); // overlap of (px1,py1)
16310
f1 = (px0+1 - px1) * (py1 - py0); // in each of the 4 pixels
16311
f2 = (px1 - px0) * (py0+1 - py1);
16312
f3 = (px1 - px0) * (py1 - py0);
16314
red = f0 * pix0[0] + f1 * pix1[0] + f2 * pix2[0] + f3 * pix3[0]; // sum the weighted inputs
16315
green = f0 * pix0[1] + f1 * pix1[1] + f2 * pix2[1] + f3 * pix3[1];
16316
blue = f0 * pix0[2] + f1 * pix1[2] + f2 * pix2[2] + f3 * pix3[2];
16318
pix2 = ppix2 + (py2 * ww2 + px2) * 3; // output pixel
16319
pix2[0] = int(red);
16320
pix2[1] = int(green);
16321
pix2[2] = int(blue);
16324
zadd_locked(rotrgb24_busy,-1);
16329
/**************************************************************************
16331
RGB *rgb2 = RGB_rotate_48(RGB *rgb1, double angle)
16332
Rotate RGB-48 pixmap through an arbitrary angle (degrees).
16333
Identical to RGB_rotate_24() except for:
16335
rotrgb24 >> rotrgb48
16341
int rotrgb48_busy = 0;
16342
uint16 *rotrgb48_pixmap1;
16343
uint16 *rotrgb48_pixmap2;
16348
double rotrgb48_angle;
16351
RGB * RGB_rotate_48(RGB *rgb1, double angle)
16353
void *RGB_rotate_48_thread(void *);
16355
int ww1, hh1, ww2, hh2, cc, ii;
16356
double pi = 3.141592654;
16357
uint16 *pixmap1, *pixmap2;
16362
pixmap1 = (uint16 *) rgb1->bmp;
16364
while (angle < -180) angle += 360; // normalize, -180 to +180
16365
while (angle > 180) angle -= 360;
16366
angle = angle * pi / 180; // radians, -pi to +pi
16368
if (fabs(angle) < 0.001) { // angle = 0 within my precision
16369
rgb2 = RGB_make(ww1,hh1,48); // return a copy of the input
16370
pixmap2 = (uint16 *) rgb2->bmp;
16371
cc = ww1 * hh1 * 6;
16372
memcpy(pixmap2,pixmap1,cc);
16376
ww2 = int(ww1*fabs(cos(angle)) + hh1*fabs(sin(angle))); // rectangle containing rotated image
16377
hh2 = int(ww1*fabs(sin(angle)) + hh1*fabs(cos(angle)));
16379
rgb2 = RGB_make(ww2,hh2,48);
16380
pixmap2 = (uint16 *) rgb2->bmp;
16382
rotrgb48_pixmap1 = pixmap1;
16383
rotrgb48_pixmap2 = pixmap2;
16384
rotrgb48_ww1 = ww1;
16385
rotrgb48_hh1 = hh1;
16386
rotrgb48_ww2 = ww2;
16387
rotrgb48_hh2 = hh2;
16388
rotrgb48_angle = angle;
16390
for (ii = 0; ii < NWthreads; ii++) // start worker threads
16391
start_detached_thread(RGB_rotate_48_thread,&wtindex[ii]);
16392
zadd_locked(rotrgb48_busy,+NWthreads);
16394
while (rotrgb48_busy) zsleep(0.004); // wait for completion
16399
void * RGB_rotate_48_thread(void *arg)
16401
int index = *((int *) (arg));
16402
int ww1, hh1, ww2, hh2;
16403
int px2, py2, px0, py0;
16404
uint16 *ppix1, *ppix2, *pix0, *pix1, *pix2, *pix3;
16405
double rx1, ry1, rx2, ry2, R, theta, px1, py1;
16406
double f0, f1, f2, f3, red, green, blue;
16407
double angle, pi = 3.141592654;
16409
ppix1 = rotrgb48_pixmap1; // input pixel array
16410
ppix2 = rotrgb48_pixmap2; // output pixel array
16411
ww1 = rotrgb48_ww1;
16412
hh1 = rotrgb48_hh1;
16413
ww2 = rotrgb48_ww2;
16414
hh2 = rotrgb48_hh2;
16415
angle = rotrgb48_angle;
16417
for (py2 = index; py2 < hh2; py2 += NWthreads) // loop through output pixels
16418
for (px2 = 0; px2 < ww2; px2++) // outer loop y
16420
rx2 = px2 - 0.5 * ww2; // (rx2,ry2) = center of pixel
16421
ry2 = py2 - 0.5 * hh2;
16422
R = sqrt(rx2*rx2 + ry2*ry2); // convert to (R,theta)
16423
if (R < 0.1) theta = 0;
16424
else theta = qarcsine(ry2 / R); // quick arc sine
16426
if (theta < 0) theta = - pi - theta; // adjust for quandrant
16427
else theta = pi - theta;
16430
theta = theta - angle; // rotate theta backwards
16431
if (theta > pi) theta -= 2 * pi; // range -pi to +pi
16432
if (theta < -pi) theta += 2 * pi;
16434
rx1 = R * qcosine(theta); // quick cosine, sine
16435
ry1 = R * qsine(theta);
16436
px1 = rx1 + 0.5 * ww1; // (px1,py1) = corresponding
16437
py1 = ry1 + 0.5 * hh1; // point within input pixels
16439
px0 = int(px1); // pixel containing (px1,py1)
16442
if (px1 < 0 || px0 >= ww1-1 || py1 < 0 || py0 >= hh1-1) { // if outside input pixel array
16443
pix2 = ppix2 + (py2 * ww2 + px2) * 3; // output is black
16444
pix2[0] = pix2[1] = pix2[2] = 0;
16448
pix0 = ppix1 + (py0 * ww1 + px0) * 3; // 4 input pixels based at (px0,py0)
16449
pix1 = pix0 + ww1 * 3;
16453
f0 = (px0+1 - px1) * (py0+1 - py1); // overlap of (px1,py1)
16454
f1 = (px0+1 - px1) * (py1 - py0); // in each of the 4 pixels
16455
f2 = (px1 - px0) * (py0+1 - py1);
16456
f3 = (px1 - px0) * (py1 - py0);
16458
red = f0 * pix0[0] + f1 * pix1[0] + f2 * pix2[0] + f3 * pix3[0]; // sum the weighted inputs
16459
green = f0 * pix0[1] + f1 * pix1[1] + f2 * pix2[1] + f3 * pix3[1];
16460
blue = f0 * pix0[2] + f1 * pix1[2] + f2 * pix2[2] + f3 * pix3[2];
16462
pix2 = ppix2 + (py2 * ww2 + px2) * 3; // output pixel
16463
pix2[0] = int(red);
16464
pix2[1] = int(green);
16465
pix2[2] = int(blue);
16468
zadd_locked(rotrgb48_busy,-1);