~ubuntu-branches/ubuntu/lucid/fotoxx/lucid

« back to all changes in this revision

Viewing changes to fotoxx-8.6.2.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Santiago Torres Batan
  • Date: 2009-10-29 19:34:43 UTC
  • mfrom: (1.1.5 upstream)
  • Revision ID: james.westby@ubuntu.com-20091029193443-ybj3g5y6vpjgjbeq
Tags: 8.6.2-1
New upstream release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**************************************************************************
 
2
 
 
3
   fotoxx      digital photo edit program
 
4
 
 
5
   Copyright 2007, 2008, 2009  Michael Cornelison
 
6
   source URL:  kornelix.squarespace.com
 
7
   contact: kornelix@yahoo.de
 
8
   
 
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.
 
13
 
 
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.
 
18
 
 
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/>.
 
21
 
 
22
***************************************************************************/
 
23
 
 
24
#include <FreeImage.h>
 
25
#include "zfuncs.h"
 
26
 
 
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"
 
36
 
 
37
//  GTK definitions
 
38
 
 
39
#define PXB GdkPixbuf
 
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
 
46
 
 
47
//  FREEIMAGE definitions
 
48
 
 
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
 
54
 
 
55
//  fotoxx definitions
 
56
 
 
57
#define mwinww 800                                                         //  main window default size
 
58
#define mwinhh 600
 
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
 
64
 
 
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*
 
73
 
 
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
 
79
 
 
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%
 
83
 
 
84
#define pixed_undomaxmem (100 * mega)                                      //  pixel edit max. memory alloc.
 
85
#define pixed_undomaxpix (mega)                                            //  pixel edit max. pixel blocks
 
86
 
 
87
namespace      image_navi {                                                //  zfuncs: image_gallery() etc.
 
88
   extern int     xwinW, xwinH;                                            //  image gallery window size
 
89
   extern int     thumbsize;                                               //  thumbnail image size
 
90
}
 
91
 
 
92
namespace      zfuncs {
 
93
   extern char    zlanguage[8];                                            //  current language lc_RC  v.8.5
 
94
}
 
95
 
 
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
 
100
GError         **gerror = 0;
 
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;
 
108
 
 
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
 
114
 
 
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
 
137
 
 
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
 
148
 
 
149
//  fotoxx RGB pixmaps                                                     //  v.6.5
 
150
 
 
151
typedef struct  {                                                          //  RGB pixmap
 
152
   char     wmi[8];
 
153
   int      ww, hh, bpp;                                                   //  width, height, bits per pixel
 
154
   void     *bmp;                                                          //  uint8*/uint16* (bpp=24/48)
 
155
}  RGB;
 
156
 
 
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)
 
165
 
 
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
 
170
 
 
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
 
179
 
 
180
mutex       pixmaps_lock;                                                  //  lock for accessing RGB pixmaps
 
181
int         menu_lock = 0;                                                 //  lock for some menu functions
 
182
 
 
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
 
186
 
 
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
 
194
 
 
195
int         KBcapture = 0;                                                 //  KB key captured by edit function
 
196
int         KBkey = 0;                                                     //  active keyboard key
 
197
 
 
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];
 
201
 
 
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;
 
205
 
 
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
 
211
 
 
212
//  pano and HDR control data
 
213
 
 
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
 
225
 
 
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
 
238
 
 
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
 
248
 
 
249
//  GTK functions
 
250
 
 
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
 
267
 
 
268
typedef void CBfunc();                                                     //  callback function type
 
269
CBfunc   *mouseCBfunc = 0;                                                 //  current mouse handler function
 
270
CBfunc   *KBkeyCBfunc = 0;                                                 //  current KB handler function
 
271
 
 
272
//  file functions
 
273
 
 
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
 
292
 
 
293
//  tools functions
 
294
 
 
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
 
310
 
 
311
//  tags and EXIF functions
 
312
 
 
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
 
324
 
 
325
//  select area functions
 
326
 
 
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)
 
337
 
 
338
//  edit functions
 
339
 
 
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
 
362
 
 
363
//  pano and HDR common functions
 
364
 
 
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)
 
373
 
 
374
//  edit support functions
 
375
 
 
376
typedef void * threadfunc(void *);                                         //  edit thread function
 
377
 
 
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
 
385
 
 
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
 
393
 
 
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
 
398
 
 
399
//  other support functions
 
400
 
 
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
 
409
 
 
410
//  RGB pixmap and FI bitmap conversion functions                          //  v.6.5
 
411
 
 
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
 
424
 
 
425
//  translatable strings used in multiple dialogs
 
426
 
 
427
const char  *Bsavetoedit;
 
428
const char  *Bexiftoolmissing;
 
429
const char  *Bopenrawfile;
 
430
const char  *BOK;
 
431
const char  *Bcancel;
 
432
const char  *Bdone;
 
433
const char  *Bclear;
 
434
const char  *Bapply;
 
435
const char  *Bundo;
 
436
const char  *Bundolast;
 
437
const char  *Bundoall;
 
438
const char  *Bredo;
 
439
const char  *Bsearch;
 
440
const char  *Binsert;
 
441
const char  *Baddall;
 
442
const char  *Bstart;
 
443
const char  *Bfinish;
 
444
const char  *Bsuspend;
 
445
const char  *Bresume;
 
446
const char  *Bshow;
 
447
const char  *Bhide;
 
448
const char  *Bdelete;
 
449
const char  *Binvert;
 
450
const char  *Bedgecalc;
 
451
const char  *Bblendwidth;
 
452
const char  *Bdeletearea;
 
453
const char  *Bwidth;
 
454
const char  *Bheight;
 
455
const char  *Bpercent;
 
456
const char  *Bpreset;
 
457
const char  *Bproceed;
 
458
const char  *Bred;
 
459
const char  *Bgreen;
 
460
const char  *Bblue;
 
461
const char  *Bbrightness;
 
462
const char  *Bdarker;
 
463
const char  *Blighter;
 
464
const char  *Breduce;
 
465
 
 
466
 
 
467
/**************************************************************************
 
468
      main program and GTK/GDK functions
 
469
***************************************************************************/
 
470
 
 
471
int main(int argc, char *argv[])
 
472
{
 
473
   char           lang[8] = "";
 
474
   
 
475
   printf(fversion "\n");                                                  //  print version
 
476
   if (argc > 1 && strEqu(argv[1],"-v")) return 0;
 
477
 
 
478
   gtk_init(&argc,&argv);                                                  //  initz. GTK
 
479
   zlockInit();
 
480
 
 
481
   initz_appfiles("fotoxx",null);                                          //  get app directories
 
482
   load_fotoxx_state();                                                    //  restore data from last session
 
483
 
 
484
   for (int ii = 1; ii < argc; ii++)                                       //  command line options
 
485
   {
 
486
      if (strEqu(argv[ii],"-d"))                                           //  -d (debug flag)
 
487
            Fdebug = 1;
 
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
 
491
   }
 
492
 
 
493
   ZTXinit(lang);                                                          //  setup translations
 
494
 
 
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);
 
499
 
 
500
   mVbox = gtk_vbox_new(0,0);                                              //  add vert. packing box
 
501
   gtk_container_add(GTK_CONTAINER(mWin),mVbox);
 
502
 
 
503
   mMbar = create_menubar(mVbox,16);                                       //  menus / sub-menus
 
504
 
 
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);
 
516
 
 
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);
 
529
 
 
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);
 
535
 
 
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);
 
545
 
 
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);
 
551
 
 
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);
 
556
 
 
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);
 
561
 
 
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);
 
566
 
 
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);
 
574
 
 
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);
 
579
 
 
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);
 
588
 
 
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);
 
602
 
 
603
   drWin = gtk_drawing_area_new();                                         //  add drawing window
 
604
   gtk_box_pack_start(GTK_BOX(mVbox),drWin,1,1,0);
 
605
 
 
606
   STbar = create_stbar(mVbox);                                            //  add status bar
 
607
 
 
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)
 
611
 
 
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)
 
619
 
 
620
   G_SIGNAL(mWin,"key-press-event",KBpress,0)                                   //  connect KB events
 
621
   G_SIGNAL(mWin,"key-release-event",KBrelease,0)
 
622
   
 
623
   drag_drop_connect(drWin,m_open_drag);                                   //  connect drag-drop event   v.7.3
 
624
 
 
625
   gtk_widget_show_all(mWin);                                              //  show all widgets
 
626
 
 
627
   gdkgc = gdk_gc_new(drWin->window);                                      //  initz. graphics context
 
628
 
 
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;
 
633
 
 
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);
 
639
 
 
640
   gdk_gc_set_foreground(gdkgc,&black);
 
641
   gdk_gc_set_background(gdkgc,&white);
 
642
   gdk_gc_set_line_attributes(gdkgc,1,lineattributes);
 
643
 
 
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
 
647
 
 
648
   gtk_init_add((GtkFunction) gtkinitfunc,0);                              //  set initz. call from gtk_main()
 
649
 
 
650
   gtk_main();                                                             //  process window events
 
651
   return 0;
 
652
}
 
653
 
 
654
 
 
655
/**************************************************************************/
 
656
 
 
657
//  initial function called from gtk_main() at startup
 
658
 
 
659
int gtkinitfunc(void * data)
 
660
{
 
661
   int               err, flag, npid;
 
662
   char              procfile[20], *pp;
 
663
   char              printoxx[200], command[200];
 
664
   const char        *ppc, *ppc2;
 
665
   struct stat       statb;
 
666
 
 
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");
 
670
   BOK = ZTX("OK");
 
671
   Bcancel = ZTX("Cancel");
 
672
   Bdone = ZTX("Done");
 
673
   Bclear = ZTX("Clear");
 
674
   Bapply = ZTX("Apply");
 
675
   Bundo = ZTX("Undo");
 
676
   Bundolast = ZTX("Undo Last");
 
677
   Bundoall = ZTX("Undo All");
 
678
   Bredo = ZTX("Redo");
 
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");
 
686
   Bshow = ZTX("Show");
 
687
   Bhide = ZTX("Hide");
 
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");
 
698
   Bred = ZTX("Red");
 
699
   Bgreen = ZTX("Green");
 
700
   Bblue = ZTX("Blue");
 
701
   Bbrightness = ZTX("Brightness");
 
702
   Bdarker = ZTX("Darker Areas");
 
703
   Blighter = ZTX("Lighter Areas");
 
704
   Breduce = ZTX("Reduce");
 
705
 
 
706
   tid_fmain = pthread_self();                                             //  get main() thread ID
 
707
   snprintf(PIDstring,11,"%06d",getpid());                                 //  get fotoxx process PID
 
708
 
 
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);
 
713
 
 
714
   FreeImage_Initialise(FALSE);                                            //  FREEIMAGE setup
 
715
   FreeImage_SetOutputMessage(FI_error);
 
716
   printf("FreeImage %s \n",FreeImage_GetVersion());
 
717
 
 
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;
 
724
 
 
725
   err = system("printoxx -v");                                            //  check for printoxx
 
726
   if (err) {
 
727
      pp = getenv("_");                                                    //  look in same place as me
 
728
      if (pp) {                                                            //  v.6.2
 
729
         strncpy0(printoxx,pp,188);
 
730
         pp = (char *) strrchr(printoxx,'/');
 
731
         if (pp) {
 
732
            strcpy(pp,"/printoxx -v");
 
733
            err = system(printoxx);
 
734
         }
 
735
      }
 
736
   }
 
737
   if (! err) Fprintoxx = 1;
 
738
   
 
739
   if (! Fexiftool) zmessageACK(ZTX("exiftool is not installed \n"         //  warn user
 
740
                               "edited images will lose EXIF data"));
 
741
 
 
742
   asstagsfile = zmalloc(200);                                             //  setup assigned tags file
 
743
   strncatv(asstagsfile,199,get_zuserdir(),"/assigned_tags",null);         //    home/user/.fotoxx/assigned_tags
 
744
 
 
745
   undo_files = zmalloc(200);
 
746
   *undo_files = 0;
 
747
   strncatv(undo_files,199,get_zuserdir(),"/*_undo_*",null);               //  home/user/.fotoxx/pppppp_undo_nn
 
748
 
 
749
   flag = 1;
 
750
   while ((ppc = SearchWild(undo_files,flag)))                             //  look for orphaned undo files
 
751
   {
 
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);
 
761
   }
 
762
 
 
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
 
765
   
 
766
   mutex_init(&pixmaps_lock,0);                                            //  setup lock for edit pixmaps
 
767
   
 
768
   g_timeout_add(20,gtimefunc,0);                                          //  start periodic function (20 ms)
 
769
   
 
770
   if (image_file) {
 
771
      char * pp = canonicalize_file_name(image_file);                      //  add cwd if needed
 
772
      zfree(image_file);
 
773
      image_file = null;
 
774
      if (pp) image_file = strdupz(pp);                                    //  change pp to zmalloc
 
775
      if (pp) free(pp);
 
776
   }
 
777
      
 
778
   if (image_file) {
 
779
      err = stat(image_file,&statb);
 
780
      if (! err && S_ISREG(statb.st_mode)) f_open(image_file);             //  open initial file
 
781
   }
 
782
 
 
783
   return 0;
 
784
}
 
785
 
 
786
 
 
787
/**************************************************************************/
 
788
 
 
789
//  Periodic function - runs every few milliseconds.
 
790
//  Avoid any thread usage of gtk/gdk functions.
 
791
 
 
792
int gtimefunc(void *arg)
 
793
{
 
794
   double      secs;
 
795
   static int  fbusy = 0;
 
796
 
 
797
   if (Wrepaint) {                                                         //  update drawing window
 
798
      Wrepaint = 0;
 
799
      mwpaint();
 
800
   }
 
801
 
 
802
   if (SBupdate)                                                           //  status bar update 
 
803
   {
 
804
      SBupdate = 0;
 
805
      update_statusbar();
 
806
   }
 
807
   
 
808
   if (Fslideshow) {
 
809
      secs = get_seconds();                                                //  show next slide
 
810
      if (secs > SS_timer) {
 
811
         SS_timer = secs + SS_interval;
 
812
         m_next(0,0);  
 
813
      }
 
814
   }
 
815
   
 
816
   if (! fbusy && thread_working()) {                                      //  v.8.4
 
817
      fbusy = 1;
 
818
      gdk_window_set_cursor(drWin->window,busycursor);                     //  set function busy cursor
 
819
   }
 
820
   
 
821
   if (fbusy && ! thread_working()) {
 
822
      fbusy = 0;
 
823
      gdk_window_set_cursor(drWin->window,0);                              //  restore normal cursor
 
824
   }
 
825
 
 
826
   return 1;
 
827
}
 
828
 
 
829
 
 
830
/**************************************************************************/
 
831
 
 
832
//  main window delete_event and destroy signals
 
833
 
 
834
int delete_event()                                                         //  main window closed
 
835
{
 
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
 
840
   return 0;
 
841
}
 
842
 
 
843
void destroy_event()                                                       //  main window destroyed
 
844
{
 
845
   Fshutdown++;
 
846
   printf("main window destroyed \n");
 
847
   exit(1);                                                                //  instead of gtk_main_quit();
 
848
   return;
 
849
}
 
850
 
 
851
 
 
852
/**************************************************************************/
 
853
 
 
854
//  cause (modified) output image to get repainted immediately
 
855
//  this function may be called from threads
 
856
 
 
857
void mwpaint2()
 
858
{
 
859
   Wrepaint++;
 
860
   return;
 
861
}
 
862
 
 
863
 
 
864
/**************************************************************************/
 
865
 
 
866
//  paint window when created, exposed, resized, or modified (edited)      //  overhauled  v.7.4
 
867
 
 
868
int mwpaint()
 
869
{
 
870
   GdkRectangle   wrect;
 
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;
 
876
   
 
877
   if (Fshutdown) return 1;                                                //  shutdown underway
 
878
   if (! Frgb24) {
 
879
      gdk_window_clear(drWin->window);
 
880
      return 1;                                                            //  no image
 
881
   }
 
882
 
 
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
 
886
 
 
887
   if (mutex_trylock(&pixmaps_lock) != 0) {                                //  lock pixmaps
 
888
      Wrepaint++;                                                          //  cannot, return later
 
889
      return 1;
 
890
   }
 
891
   
 
892
   if (E3rgb48) {                                                          //  get image size
 
893
      Iww = E3ww;
 
894
      Ihh = E3hh;                                                          //  edit in progress
 
895
   }
 
896
   else {
 
897
      Iww = Fww;                                                           //  no edit
 
898
      Ihh = Fhh;
 
899
   }
 
900
   
 
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
 
907
      zoomx = zoomy = 0;
 
908
   }
 
909
   else Mscale = Fzoom;                                                    //  scale to Fzoom level
 
910
 
 
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);
 
914
   }
 
915
   pscale = Mscale;
 
916
   
 
917
   iww = Dww / Mscale;                                                     //  image space fitting in window
 
918
   if (iww > Iww) iww = Iww;
 
919
   ihh = Dhh / Mscale;
 
920
   if (ihh > Ihh) ihh = Ihh;
 
921
 
 
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;
 
925
      zoomx = zoomy = 0;
 
926
   }
 
927
 
 
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;
 
933
      pincrx = incrx;
 
934
      if (pincry > 0 && incry < 0) incry = 0;
 
935
      if (pincry < 0 && incry > 0) incry = 0;
 
936
      pincry = incry;
 
937
      Iorgx += incrx;                                                      //  new image origin after scroll
 
938
      Iorgy += incry;
 
939
      Mxdown = Mxdrag + incrx;                                             //  new drag origin
 
940
      Mydown = Mydrag + incry;
 
941
      Mxdrag = Mydrag = 0;
 
942
   }
 
943
 
 
944
   if (iww == Iww) {                                                       //  scaled image <= window width
 
945
      Iorgx = 0;                                                           //  center image in window
 
946
      Dorgx = 0.5 * (Dww - Iww * Mscale);
 
947
   }
 
948
   else Dorgx = 0;                                                         //  image > window, use entire window
 
949
 
 
950
   if (ihh == Ihh) {                                                       //  same for image height
 
951
      Iorgy = 0;
 
952
      Dorgy = 0.5 * (Dhh - Ihh * Mscale);
 
953
   }
 
954
   else Dorgy = 0;
 
955
   
 
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;
 
960
 
 
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
 
964
      RGB_free(rgbtemp1);
 
965
   }
 
966
   else rgbtemp2 = RGB_copy_area(Frgb24,Iorgx,Iorgy,iww,ihh);              //  no edit, copy RGB-24
 
967
 
 
968
   dww = iww * Mscale;                                                     //  scale to window
 
969
   dhh = ihh * Mscale;
 
970
   RGB_free(Drgb24);
 
971
   Drgb24 = RGB_rescale(rgbtemp2,dww,dhh);
 
972
   RGB_free(rgbtemp2);
 
973
 
 
974
   wrect.x = wrect.y = 0;                                                  //  stop flicker
 
975
   wrect.width = Dww;
 
976
   wrect.height = Dhh;
 
977
   gdk_window_begin_paint_rect(drWin->window,&wrect);
 
978
 
 
979
   gdk_window_clear(drWin->window);                                        //  clear window
 
980
 
 
981
   gdk_draw_rgb_image(drWin->window, gdkgc, Dorgx, Dorgy, dww, dhh,        //  draw scaled image to window
 
982
                      nodither, (uint8 *) Drgb24->bmp, dww*3);
 
983
 
 
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
 
987
   
 
988
   gdk_window_end_paint(drWin->window);                                    //  release all window updates
 
989
 
 
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
 
994
   return 1;
 
995
}
 
996
 
 
997
 
 
998
/**************************************************************************/
 
999
 
 
1000
//  update status bar with image data and status
 
1001
 
 
1002
void update_statusbar()
 
1003
{
 
1004
   static char    text1[200], text2[100];
 
1005
   int            ww, hh, bpp, scale;
 
1006
 
 
1007
   if (! image_file) return;
 
1008
   
 
1009
   *text1 = *text2 = 0;
 
1010
   
 
1011
   if (E3rgb48) {                                                          //  v.8.5
 
1012
      ww = E3ww;
 
1013
      hh = E3hh;
 
1014
      bpp = 48;
 
1015
   }
 
1016
   else if (Frgb48) {
 
1017
      ww = Fww;
 
1018
      hh = Fhh;
 
1019
      bpp = 48;
 
1020
   }
 
1021
   else {
 
1022
      ww = Fww;
 
1023
      hh = Fhh;
 
1024
      bpp = file_bpp;
 
1025
   }
 
1026
   
 
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)");
 
1036
   
 
1037
   if (Pundo)                                                              //  edit undo stack depth 
 
1038
   {
 
1039
      snprintf(text2,99,"  edits: %d",Pundo);
 
1040
      strcat(text1,text2);
 
1041
   }
 
1042
 
 
1043
   if (Nalign && aligntype == 1)                                           //  HDR alignment data
 
1044
   {
 
1045
      snprintf(text2,99,"  align: %d  offsets: %+.1f %+.1f %+.4f  match: %.5f",
 
1046
                     Nalign,xoffB,yoffB,toffB,matchB);
 
1047
      strcat(text1,text2);
 
1048
   }
 
1049
   
 
1050
   if (Nalign && aligntype == 2)                                           //  HDF alignment data
 
1051
   {
 
1052
      snprintf(text2,99,"  align: %d  offsets: %+.1f %+.1f %+.4f  match: %.5f",
 
1053
                     Nalign,xoffB,yoffB,toffB,matchB);
 
1054
      strcat(text1,text2);
 
1055
   }
 
1056
 
 
1057
   if (Nalign && aligntype == 3)                                           //  pano alignment data
 
1058
   {
 
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);
 
1062
   }
 
1063
 
 
1064
   if (Fautolens)                                                          //  lens parameters (search status)
 
1065
   {
 
1066
      snprintf(text2,99,"  lens: %.1f %.2f",lens_mm,lens_bow);
 
1067
      strcat(text1,text2);
 
1068
   }
 
1069
 
 
1070
   if (PercentDone > 0 && PercentDone < 100)                               //  % completion       v.6.3
 
1071
   {
 
1072
      snprintf(text2,99,"  done: %d%c",PercentDone,'%');
 
1073
      strcat(text1,text2);
 
1074
   }
 
1075
 
 
1076
   stbar_message(STbar,text1);
 
1077
   return;
 
1078
}
 
1079
 
 
1080
 
 
1081
/**************************************************************************/
 
1082
 
 
1083
//  mouse event function - capture buttons and drag movements
 
1084
 
 
1085
void mouse_event(GtkWidget *, GdkEventButton *event, void *)
 
1086
{
 
1087
   void mouse_convert(int &xpos, int &ypos);
 
1088
 
 
1089
   static int     bdtime = 0, butime = 0, mbusy = 0;
 
1090
   int            button, time, type;
 
1091
 
 
1092
   type = event->type;
 
1093
   button = event->button;                                                 //  button, 1/3 = left/right
 
1094
   time = event->time;
 
1095
   Mxposn = int(event->x);                                                 //  mouse position in window
 
1096
   Myposn = int(event->y);
 
1097
 
 
1098
   mouse_convert(Mxposn,Myposn);                                           //  convert to image space  v.8.4
 
1099
 
 
1100
   if (type == GDK_MOTION_NOTIFY) {
 
1101
      if (mbusy) return;                                                   //  discard excess motion events
 
1102
      mbusy++;
 
1103
      zmainloop();
 
1104
      mbusy = 0;
 
1105
   }
 
1106
 
 
1107
   if (type == GDK_BUTTON_PRESS) {                                         //  button down
 
1108
      bdtime = time;                                                       //  time of button down
 
1109
      Mxdown = Mxposn;                                                     //  position at button down time
 
1110
      Mydown = Myposn;
 
1111
      if (button) {
 
1112
         Mdrag++;                                                          //  possible drag start
 
1113
         Mbutton = button;
 
1114
      }
 
1115
      Mxdrag = Mydrag = 0;
 
1116
   }
 
1117
 
 
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
 
1126
            Myclick = Mydown;
 
1127
         }
 
1128
      Mxdown = Mydown = Mxdrag = Mydrag = Mdrag = Mbutton = 0;             //  forget buttons and drag
 
1129
   }
 
1130
   
 
1131
   if (type == GDK_MOTION_NOTIFY && Mdrag) {                               //  drag underway
 
1132
      Mxdrag = Mxposn;
 
1133
      Mydrag = Myposn;
 
1134
   }
 
1135
 
 
1136
   if (mouseCBfunc) {                                                      //  pass to handler function
 
1137
      (* mouseCBfunc)();
 
1138
      return;
 
1139
   }
 
1140
 
 
1141
   if (LMclick && ! Mcapture) {                                            //  left click = zoom request
 
1142
      LMclick = 0;
 
1143
      zoomx = Mxclick;                                                     //  zoom center = mouse   v.7.5
 
1144
      zoomy = Myclick;
 
1145
      m_zoom(null, (char *) "+");
 
1146
   }
 
1147
 
 
1148
   if (RMclick && ! Mcapture) {                                            //  right click = reset zoom
 
1149
      RMclick = 0;
 
1150
      zoomx = zoomy = 0;                                                   //  v.7.5
 
1151
      m_zoom(null, (char *) "-");
 
1152
   }
 
1153
 
 
1154
   if ((Mxdrag || Mydrag) && ! Mcapture) mwpaint();                        //  drag = scroll
 
1155
   return;
 
1156
}
 
1157
 
 
1158
 
 
1159
//  convert mouse position from window space to image space
 
1160
 
 
1161
void mouse_convert(int &xpos, int &ypos)
 
1162
{
 
1163
   xpos = int((xpos - Dorgx) / Mscale + Iorgx + 0.5);
 
1164
   ypos = int((ypos - Dorgy) / Mscale + Iorgy + 0.5);
 
1165
 
 
1166
   if (xpos < 0) xpos = 0;                                                 //  if outside image put at edge
 
1167
   if (ypos < 0) ypos = 0;                                                 //                   v.8.4
 
1168
 
 
1169
   if (E3rgb48) { 
 
1170
      if (xpos >= E3ww) xpos = E3ww-1;
 
1171
      if (ypos >= E3hh) ypos = E3hh-1;
 
1172
   }
 
1173
   else {
 
1174
      if (xpos >= Fww) xpos = Fww-1;
 
1175
      if (ypos >= Fhh) ypos = Fhh-1;
 
1176
   }
 
1177
   
 
1178
   return;
 
1179
}
 
1180
 
 
1181
 
 
1182
/**************************************************************************/
 
1183
 
 
1184
//  keyboard event function - some toolbar buttons have KB equivalents
 
1185
//  GDK key symbols: /usr/include/gtk-2.0/gdk/gdkkeysyms.h
 
1186
 
 
1187
int   KBcontrolkey = 0;
 
1188
 
 
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
 
1193
   return 1;
 
1194
}
 
1195
 
 
1196
int KBrelease(GtkWidget *win, GdkEventKey *event, void *)
 
1197
{
 
1198
   KBkey = event->keyval;
 
1199
 
 
1200
   if (KBkeyCBfunc) {                                                      //  pass to handler function
 
1201
      (* KBkeyCBfunc)();                                                   //  v.6.5
 
1202
      KBkey = 0;
 
1203
      return 1;
 
1204
   }
 
1205
 
 
1206
   if (KBcapture) return 1;                                                //  let function handle it
 
1207
 
 
1208
   if (KBkey == 65507) KBcontrolkey = 0;                                   //  Ctrk key released   v.8.3
 
1209
   
 
1210
   if (KBcontrolkey) {
 
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);  
 
1215
   }
 
1216
 
 
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);  
 
1219
 
 
1220
   if (KBkey == GDK_Left) m_prev(0,0);                                     //  arrow keys  >>  prev/next image
 
1221
   if (KBkey == GDK_Right) m_next(0,0);  
 
1222
 
 
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 -
 
1228
   
 
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");
 
1231
 
 
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
 
1236
   }
 
1237
 
 
1238
   if (KBkey == GDK_Delete) m_trash(0,0);                                  //  delete  >>  trash
 
1239
 
 
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);
 
1244
   
 
1245
   KBkey = 0;                                                              //  v.7.0
 
1246
   return 1;
 
1247
}
 
1248
 
 
1249
 
 
1250
/**************************************************************************/
 
1251
 
 
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
 
1256
 
 
1257
void paint_toplines(int arg)                                               //  v.8.3
 
1258
{
 
1259
   int      ii;
 
1260
 
 
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]);
 
1264
   
 
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]);
 
1268
 
 
1269
   if (arg == 2) {
 
1270
      Nptoplines = Ntoplines = 0;                                          //  forget lines
 
1271
      return;
 
1272
   }
 
1273
 
 
1274
   for (ii = 0; ii < Ntoplines; ii++)                                      //  save for future erase
 
1275
   {
 
1276
      ptoplinex1[ii] = toplinex1[ii];
 
1277
      ptopliney1[ii] = topliney1[ii];
 
1278
      ptoplinex2[ii] = toplinex2[ii];
 
1279
      ptopliney2[ii] = topliney2[ii];
 
1280
   }
 
1281
 
 
1282
   Nptoplines = Ntoplines;
 
1283
 
 
1284
   return;
 
1285
}
 
1286
 
 
1287
 
 
1288
/**************************************************************************/
 
1289
 
 
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
 
1294
 
 
1295
void paint_toparc(int arg)                                                 //  v.8.3
 
1296
{
 
1297
   int      arcx, arcy, arcw, arch;
 
1298
 
 
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);
 
1304
 
 
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);
 
1308
   }
 
1309
   
 
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);
 
1315
 
 
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);
 
1319
   }
 
1320
 
 
1321
   if (arg == 2) {
 
1322
      toparc = ptoparc = 0;                                                //  forget arcs
 
1323
      return;
 
1324
   }
 
1325
   
 
1326
   ptoparc = toparc;                                                       //  save for future erase
 
1327
   ptoparcx = toparcx;
 
1328
   ptoparcy = toparcy;
 
1329
   ptoparcw = toparcw;
 
1330
   ptoparch = toparch;
 
1331
 
 
1332
   return;
 
1333
}
 
1334
 
 
1335
 
 
1336
/**************************************************************************/
 
1337
 
 
1338
//  draw line. coordinates are in image space.                             //  overhauled   v.8.4.2
 
1339
 
 
1340
void draw_line(int ix1, int iy1, int ix2, int iy2)
 
1341
{
 
1342
   void draw_pixel(double pxm, double pym);
 
1343
 
 
1344
   double      x1, y1, x2, y2;   
 
1345
   double      pxm, pym, slope;
 
1346
   
 
1347
   x1 = Mscale * (ix1-Iorgx);                                              //  image to window space
 
1348
   y1 = Mscale * (iy1-Iorgy);
 
1349
   x2 = Mscale * (ix2-Iorgx);
 
1350
   y2 = Mscale * (iy2-Iorgy);
 
1351
   
 
1352
   if (abs(y2 - y1) > abs(x2 - x1)) {
 
1353
      slope = 1.0 * (x2 - x1) / (y2 - y1);
 
1354
      if (y2 > y1) {
 
1355
         for (pym = y1; pym <= y2; pym++) {
 
1356
            pxm = x1 + slope * (pym - y1);
 
1357
            draw_pixel(pxm,pym);
 
1358
         }
 
1359
      }
 
1360
      else {
 
1361
         for (pym = y1; pym >= y2; pym--) {
 
1362
            pxm = x1 + slope * (pym - y1);
 
1363
            draw_pixel(pxm,pym);
 
1364
         }
 
1365
      }
 
1366
   }
 
1367
   else {
 
1368
      slope = 1.0 * (y2 - y1) / (x2 - x1);
 
1369
      if (x2 > x1) {
 
1370
         for (pxm = x1; pxm <= x2; pxm++) {
 
1371
            pym = y1 + slope * (pxm - x1);
 
1372
            draw_pixel(pxm,pym);
 
1373
         }
 
1374
      }
 
1375
      else {
 
1376
         for (pxm = x1; pxm >= x2; pxm--) {
 
1377
            pym = y1 + slope * (pxm - x1);
 
1378
            draw_pixel(pxm,pym);
 
1379
         }
 
1380
      }
 
1381
   }
 
1382
 
 
1383
   gdk_gc_set_foreground(gdkgc,&black);
 
1384
 
 
1385
   return;
 
1386
}
 
1387
 
 
1388
void draw_pixel(double px, double py)
 
1389
{
 
1390
   int            pxn, pyn;
 
1391
   static int     flip = 0;
 
1392
   
 
1393
   pxn = int(px);
 
1394
   pyn = int(py);
 
1395
   
 
1396
   if (pxn < 0 || pxn > dww-1) return;
 
1397
   if (pyn < 0 || pyn > dhh-1) return;
 
1398
   
 
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);
 
1403
   
 
1404
   return;
 
1405
}
 
1406
 
 
1407
 
 
1408
//  erase line. refresh line path from Drgb24 pixels.
 
1409
 
 
1410
void erase_line(int ix1, int iy1, int ix2, int iy2)
 
1411
{
 
1412
   void erase_pixel(double pxm, double pym);
 
1413
 
 
1414
   double      x1, y1, x2, y2;   
 
1415
   double      pxm, pym, slope;
 
1416
   
 
1417
   x1 = Mscale * (ix1-Iorgx);
 
1418
   y1 = Mscale * (iy1-Iorgy);
 
1419
   x2 = Mscale * (ix2-Iorgx);
 
1420
   y2 = Mscale * (iy2-Iorgy);
 
1421
   
 
1422
   if (abs(y2 - y1) > abs(x2 - x1)) {
 
1423
      slope = 1.0 * (x2 - x1) / (y2 - y1);
 
1424
      if (y2 > y1) {
 
1425
         for (pym = y1; pym <= y2; pym++) {
 
1426
            pxm = x1 + slope * (pym - y1);
 
1427
            erase_pixel(pxm,pym);
 
1428
         }
 
1429
      }
 
1430
      else {
 
1431
         for (pym = y1; pym >= y2; pym--) {
 
1432
            pxm = x1 + slope * (pym - y1);
 
1433
            erase_pixel(pxm,pym);
 
1434
         }
 
1435
      }
 
1436
   }
 
1437
   else {
 
1438
      slope = 1.0 * (y2 - y1) / (x2 - x1);
 
1439
      if (x2 > x1) {
 
1440
         for (pxm = x1; pxm <= x2; pxm++) {
 
1441
            pym = y1 + slope * (pxm - x1);
 
1442
            erase_pixel(pxm,pym);
 
1443
         }
 
1444
      }
 
1445
      else {
 
1446
         for (pxm = x1; pxm >= x2; pxm--) {
 
1447
            pym = y1 + slope * (pxm - x1);
 
1448
            erase_pixel(pxm,pym);
 
1449
         }
 
1450
      }
 
1451
   }
 
1452
 
 
1453
   return;
 
1454
}
 
1455
 
 
1456
void erase_pixel(double px, double py)
 
1457
{
 
1458
   int            pxn, pyn;
 
1459
   
 
1460
   pxn = int(px);
 
1461
   pyn = int(py);
 
1462
   
 
1463
   if (pxn < 0 || pxn > dww-1) return;
 
1464
   if (pyn < 0 || pyn > dhh-1) return;
 
1465
 
 
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);         
 
1469
   return;
 
1470
}
 
1471
 
 
1472
 
 
1473
/**************************************************************************/
 
1474
 
 
1475
//  process top-level menu entry
 
1476
 
 
1477
void topmenufunc(GtkWidget *, const char *menu)
 
1478
{
 
1479
   topmenu = (char *) menu;                                                //  remember top-level menu in
 
1480
   return;                                                                 //    case this is needed somewhere
 
1481
}
 
1482
 
 
1483
 
 
1484
/**************************************************************************
 
1485
      file functions
 
1486
***************************************************************************/
 
1487
 
 
1488
//  display image gallery (thumbnails) in a separate window
 
1489
 
 
1490
void m_gallery(GtkWidget *, const char *)
 
1491
{
 
1492
   if (image_file) image_gallery(image_file,"paint1",0,m_gallery2);        //  show image gallery window
 
1493
   else {
 
1494
      char *pp = get_current_dir_name();                                   //  initz. gallery file list and show
 
1495
      if (pp) {
 
1496
         image_gallery(pp,"init",0,m_gallery2);
 
1497
         image_gallery(0,"paint1");
 
1498
         free(pp);
 
1499
         Fsearchlist = 0;
 
1500
      }
 
1501
   }
 
1502
 
 
1503
   return;
 
1504
}
 
1505
 
 
1506
 
 
1507
//  clicked thumbnails will call this function
 
1508
 
 
1509
void  m_gallery2(char *file)
 
1510
{
 
1511
   if (zdburn) {                                                           //  add file for CD burn  v.7.2
 
1512
      burn_insert_file(file);
 
1513
      return;
 
1514
   }
 
1515
 
 
1516
   f_open((const char *) file);
 
1517
   return;
 
1518
}
 
1519
 
 
1520
 
 
1521
/**************************************************************************/
 
1522
 
 
1523
//  open menu function
 
1524
 
 
1525
void m_open(GtkWidget *, const char *)
 
1526
{
 
1527
   Fsearchlist = 0;
 
1528
   f_open(null);
 
1529
   return;
 
1530
}
 
1531
 
 
1532
 
 
1533
/**************************************************************************/
 
1534
 
 
1535
//  open drag-drop file
 
1536
 
 
1537
void  m_open_drag(int x, int y, char *file)                                //  v.7.3
 
1538
{
 
1539
   Fsearchlist = 0;                                                        //  v.8.1
 
1540
   f_open(file);
 
1541
   zfree(file);
 
1542
   return;
 
1543
}
 
1544
 
 
1545
 
 
1546
/**************************************************************************/
 
1547
 
 
1548
//  open an image file from the list of recent image files
 
1549
 
 
1550
char     recent_selection[300];
 
1551
 
 
1552
void  m_recent(GtkWidget *, const char *)                                  //  v.8.1
 
1553
{
 
1554
   int  recent_dialog_event(zdialog *zd, const char *event);
 
1555
 
 
1556
   zdialog     *zd;
 
1557
   int         ii, zstat;
 
1558
   
 
1559
   if (! recentfiles[0]) return;
 
1560
 
 
1561
   zd = zdialog_new(ZTX("Open Recent File"),mWin,BOK,Bcancel,0);
 
1562
   zdialog_add_widget(zd,"combo","combo","dialog",0);
 
1563
 
 
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]);
 
1567
 
 
1568
   *recent_selection = 0;
 
1569
   zstat = zdialog_run(zd,recent_dialog_event,0);                          //  run dialog, blocking
 
1570
   zdialog_free(zd);
 
1571
 
 
1572
   if (zstat == 1 && *recent_selection == '/') 
 
1573
      f_open(recent_selection);                                            //  open selected file
 
1574
   return;
 
1575
}
 
1576
 
 
1577
int  recent_dialog_event(zdialog *zd, const char *event)                   //  dialog event function  v.8.2
 
1578
{
 
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
 
1582
   return 1;
 
1583
}
 
1584
 
 
1585
 
 
1586
//  add a file to the list of recent files                                 //  v.8.4
 
1587
 
 
1588
void  add_recent_file(const char *file)
 
1589
{
 
1590
   int      ii;
 
1591
 
 
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
 
1597
   return;
 
1598
}
 
1599
 
 
1600
 
 
1601
/**************************************************************************/
 
1602
 
 
1603
//  open a file and initialize FI bitmap
 
1604
 
 
1605
void f_open(const char *filespec)
 
1606
{
 
1607
   int            cc, fposn, fcount;
 
1608
   char           *pp, wtitle[200], fname[100], fdirk[100];
 
1609
   RGB            *temp24;
 
1610
   char           **orientation;
 
1611
   const char     *orientationkey[1] = { exif_orientation_key };
 
1612
   
 
1613
   zmondirk("close",null,null);                                            //  reset monitored directory
 
1614
 
 
1615
   if (! menulock(1)) return;                                              //  lock menu
 
1616
   if (mod_keep()) goto openfail;
 
1617
   
 
1618
   if (filespec) filespec = strdupz(filespec);
 
1619
   else filespec = zgetfile(ZTX("Open Image File"),image_file,"open");
 
1620
   if (! filespec) goto openfail;
 
1621
   
 
1622
   temp24 = image_load(filespec,24);                                       //  load image as RGB-24 pixmap
 
1623
   if (! temp24) goto openfail;
 
1624
 
 
1625
   free_resources();                                                       //  free resources for old image file
 
1626
 
 
1627
   mutex_lock(&pixmaps_lock);                                              //  lock pixmaps
 
1628
 
 
1629
   image_file = (char *) filespec;                                         //  setup new image file
 
1630
   Frgb24 = temp24;
 
1631
   Fww = Frgb24->ww;
 
1632
   Fhh = Frgb24->hh;
 
1633
   
 
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
 
1638
   else {
 
1639
      strncpy(fdirk,image_file,96);                                        //  or use /dirk/path...
 
1640
      strcpy(fdirk+95,"...");
 
1641
   }
 
1642
   image_position(image_file,fposn,fcount);                                //  position and count in gallery list
 
1643
 
 
1644
   Fzoom = 0;                                                              //  zoom level = fit window
 
1645
   zoomx = zoomy = 0;                                                      //  no zoom center      v.7.5
 
1646
 
 
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
 
1652
 
 
1653
   mutex_unlock(&pixmaps_lock);                                            //  unlock pixmaps
 
1654
 
 
1655
   Fimageturned = 0;
 
1656
   orientation = exif_get(image_file,orientationkey,1);                    //  upright turned image
 
1657
   if (*orientation) {
 
1658
      if (strstr(*orientation,"90")) turn_image(90);
 
1659
      if (strstr(*orientation,"270")) turn_image(270);
 
1660
   }
 
1661
 
 
1662
   mwpaint2();                                                             //  refresh main window
 
1663
 
 
1664
   pp = image_gallery(image_file,"find",0);                                //  file in current image gallery ?
 
1665
   if (! pp) {
 
1666
      image_gallery(image_file,"init");                                    //  no, reset gallery file list
 
1667
      image_gallery(0,"paint2");                                           //  refresh gallery window if active
 
1668
      Fsearchlist = 0;
 
1669
   }
 
1670
   else zfree(pp);
 
1671
 
 
1672
   if (! Fsearchlist) {                                                    //  start monitoring this directory
 
1673
      pp = (char *) strrchr(image_file,'/');                               //    if not from search tags
 
1674
      *pp = 0;                                                             //                v.6.4
 
1675
      zmondirk("open",image_file,null);
 
1676
      *pp = '/';
 
1677
   }
 
1678
   
 
1679
   add_recent_file(image_file);                                            //  first in recent files list
 
1680
 
 
1681
openfail:
 
1682
   menulock(0);
 
1683
   mwpaint2();
 
1684
   return;
 
1685
}
 
1686
 
 
1687
 
 
1688
/**************************************************************************/
 
1689
 
 
1690
//  open a raw image file and convert to tiff-48 
 
1691
 
 
1692
void m_raw(GtkWidget *, const char *)
 
1693
{
 
1694
   char           *pp, *rawfile, *outfile;
 
1695
   char           command[1000];
 
1696
   int            yn, err;
 
1697
   struct stat    fstat;
 
1698
   
 
1699
   if (! menulock(1)) return;                                              //  lock menu
 
1700
   if (mod_keep()) goto openfail;
 
1701
   
 
1702
   if (! Fufraw) {
 
1703
      zmessageACK(ZTX("Package ufraw required for this function"));
 
1704
      goto openfail;
 
1705
   }
 
1706
   
 
1707
   rawfile = zgetfile(Bopenrawfile,image_file,"open");
 
1708
   if (! rawfile) goto openfail;
 
1709
   
 
1710
   err = stat(rawfile,&fstat);
 
1711
   if (err) {
 
1712
      zmessageACK(strerror(errno));
 
1713
      zfree(rawfile);
 
1714
      goto openfail;
 
1715
   }
 
1716
 
 
1717
   if (S_ISDIR(fstat.st_mode)) {
 
1718
      pp = zgetfile(Bopenrawfile,rawfile,"open");
 
1719
      zfree(rawfile);
 
1720
      if (! pp) goto openfail;
 
1721
      rawfile = pp;
 
1722
   }
 
1723
 
 
1724
   outfile = strdupz(rawfile,5);
 
1725
   pp = (char *) strrchr(rawfile,'.');
 
1726
   pp = outfile + (pp - rawfile);
 
1727
   strcpy(pp,".tiff");
 
1728
 
 
1729
   yn = zmessageYN(ZTX("Convert raw file to 48-bit tiff format? \n"
 
1730
                       " (this may take a while) "));
 
1731
   if (! yn) {
 
1732
      zfree(outfile);
 
1733
      goto openfail;
 
1734
   }
 
1735
   
 
1736
   gdk_window_set_cursor(drWin->window,busycursor);                        //  set function busy cursor  v.8.4
 
1737
   zmainloop();
 
1738
 
 
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);
 
1742
 
 
1743
   if (err) {
 
1744
      snprintf(command,999,"ufraw-batch --out-type=tiff16"                 //  old ufraw
 
1745
               " --overwrite --output=\"%s\" \"%s\" ",outfile,rawfile);
 
1746
      err = system(command);
 
1747
   }
 
1748
 
 
1749
   gdk_window_set_cursor(drWin->window,0);                                 //  restore normal cursor
 
1750
 
 
1751
   if (err) {
 
1752
      zmessageACK(wstrerror(err));
 
1753
      zfree(outfile);
 
1754
      goto openfail;
 
1755
   }
 
1756
 
 
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
 
1760
   Fsearchlist = 0;
 
1761
   f_open(outfile);                                                        //  open converted file
 
1762
   return;
 
1763
 
 
1764
openfail:
 
1765
   menulock(0);
 
1766
   mwpaint2();
 
1767
   return;
 
1768
}
 
1769
 
 
1770
 
 
1771
/**************************************************************************/
 
1772
 
 
1773
//  open previous or next file in same gallery as last file opened
 
1774
 
 
1775
void m_prev(GtkWidget *, const char *)
 
1776
{
 
1777
   if (! image_file) return;
 
1778
 
 
1779
   int mods = 0;
 
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
 
1782
 
 
1783
   char *pp = image_gallery(image_file,"prev");
 
1784
   if (! pp) return;
 
1785
   if (image_file_type(pp) == 2) f_open(pp);
 
1786
   zfree(pp);
 
1787
   return;
 
1788
}
 
1789
 
 
1790
void m_next(GtkWidget *, const char *)
 
1791
{
 
1792
   if (! image_file) return;
 
1793
 
 
1794
   int mods = 0;
 
1795
   while (zmondirk("event",null,null) > 0) mods++;                         //  v.6.4
 
1796
   if (mods) image_gallery(image_file,"init");
 
1797
 
 
1798
   char *pp = image_gallery(image_file,"next");
 
1799
   if (! pp) return;
 
1800
   if (image_file_type(pp) == 2) f_open(pp);
 
1801
   zfree(pp);
 
1802
   return;
 
1803
}
 
1804
 
 
1805
 
 
1806
/**************************************************************************/
 
1807
 
 
1808
//  save (modified) image to same file - no confirmation of overwrite.
 
1809
 
 
1810
void m_save(GtkWidget *, const char *)                                     //  v.8.3
 
1811
{   
 
1812
   char           *outfile, *pext;
 
1813
   const char     *format;
 
1814
   
 
1815
   if (! image_file) return;
 
1816
   
 
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";
 
1820
 
 
1821
   strcpy(jpeg_quality,def_jpeg_quality);                                  //  set default jpeg quality
 
1822
 
 
1823
   outfile = strdupz(image_file,8);                                        //  use input file name
 
1824
 
 
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");
 
1832
 
 
1833
   f_save(outfile,format);
 
1834
   zfree(outfile);
 
1835
   return;
 
1836
}
 
1837
 
 
1838
 
 
1839
/**************************************************************************/
 
1840
 
 
1841
//  save (modified) image to new file, confirm if overwrite existing file.
 
1842
 
 
1843
void m_saveas(GtkWidget *, const char *)
 
1844
{   
 
1845
   GtkWidget      *fdialog, *fchooser, *hbox;
 
1846
   GtkWidget      *label1, *tiff2, *tiff4, *jpeg, *jqlab, *jqval;
 
1847
   char           *outfile = 0, *outfile2 = 0, *pext;
 
1848
   const char     *format;
 
1849
   int            ii, err, yn, status;
 
1850
   struct stat    fstat;
 
1851
 
 
1852
   if (! image_file) return;
 
1853
 
 
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);
 
1858
 
 
1859
   gtk_window_set_default_size(GTK_WINDOW(fdialog),500,400);
 
1860
 
 
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);
 
1864
   
 
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);
 
1868
 
 
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);
 
1882
   
 
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
 
1885
 
 
1886
   if (strEqu(file_type,"tiff"))
 
1887
      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tiff2),1);
 
1888
   if (file_bpp == 48)
 
1889
      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tiff4),1);
 
1890
 
 
1891
dialog_run:
 
1892
 
 
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);
 
1897
      return;
 
1898
   }
 
1899
 
 
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);
 
1903
   g_free(outfile2);
 
1904
 
 
1905
   format = "jpeg";
 
1906
   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tiff2)))
 
1907
      format = "tiff-24";
 
1908
   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tiff4)))
 
1909
      format = "tiff-48";
 
1910
 
 
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");
 
1915
         goto dialog_run;
 
1916
      }
 
1917
      sprintf(jpeg_quality,"%d",ii);
 
1918
   }
 
1919
 
 
1920
   gtk_widget_destroy(fdialog);                                            //  kill dialog
 
1921
 
 
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");
 
1929
 
 
1930
   err = stat(outfile,&fstat);                                             //  check if file exists
 
1931
   if (! err) {
 
1932
      yn = zmessageYN(ZTX("Overwrite file? \n %s"),outfile);               //  confirm overwrite
 
1933
      if (! yn) {
 
1934
         zfree(outfile);
 
1935
         return;
 
1936
      }
 
1937
   }
 
1938
   
 
1939
   f_save(outfile,format);
 
1940
   zfree(outfile);
 
1941
   return;
 
1942
}
 
1943
 
 
1944
 
 
1945
/**************************************************************************/
 
1946
 
 
1947
//  file save helper function - do the actual file save
 
1948
 
 
1949
void f_save(const char *outfile, const char *format)                       //  code simplification   v.8.6
 
1950
{
 
1951
   FIB            *fib;
 
1952
   PXB            *pxb;
 
1953
   RGB            *rgb24, *rgb48;
 
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];
 
1958
   int            err, status;
 
1959
   struct stat    fstat;
 
1960
 
 
1961
   update_filetags(image_file);                                            //  commit poss. tag changes   v.8.3
 
1962
 
 
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);
 
1968
 
 
1969
   status = 0;
 
1970
   fib = 0;
 
1971
   pxb = 0;
 
1972
   
 
1973
   gdk_window_set_cursor(drWin->window,busycursor);                        //  set function busy cursor  v.8.4
 
1974
   zmainloop();
 
1975
   
 
1976
   if (strEqu(format,"jpeg")) {                                            //  save as JPEG file
 
1977
      if (Frgb48) {
 
1978
         rgb24 = RGB_convbpp(Frgb48);
 
1979
         pxb = RGB_PXB(rgb24);
 
1980
         RGB_free(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);
 
1984
   }
 
1985
   
 
1986
   if (strEqu(format,"tiff-24")) {                                         //  save as TIFF-24 file
 
1987
      if (Frgb48) {
 
1988
         rgb24 = RGB_convbpp(Frgb48);
 
1989
         fib = RGB_FIB(rgb24);
 
1990
         RGB_free(rgb24);
 
1991
      }
 
1992
      else  fib = RGB_FIB(Frgb24);
 
1993
      status = FreeImage_Save(FIF_TIFF,fib,tempfile,0);
 
1994
   }
 
1995
   
 
1996
   if (strEqu(format,"tiff-48")) {                                         //  save as TIFF-48 file
 
1997
      if (Frgb48) fib = RGB_FIB(Frgb48);
 
1998
      else {
 
1999
         if (file_bpp == 48) rgb48 = image_load(image_file,48);            //  use original 48 bpp file
 
2000
         else  rgb48 = RGB_convbpp(Frgb24);
 
2001
         if (rgb48) {
 
2002
            fib = RGB_FIB(rgb48);
 
2003
            RGB_free(rgb48);
 
2004
         }
 
2005
      }
 
2006
      status = FreeImage_Save(FIF_TIFF,fib,tempfile,0);
 
2007
   }
 
2008
 
 
2009
   gdk_window_set_cursor(drWin->window,0);                                 //  restore normal cursor
 
2010
 
 
2011
   if (fib) FreeImage_Unload(fib);
 
2012
   if (pxb) g_object_unref(pxb);
 
2013
 
 
2014
   if (! status) {
 
2015
      zmessageACK(ZTX("Unable to save image"));
 
2016
      snprintf(command,1999,"rm \"%s\"",tempfile);                         //  clean up
 
2017
      err = system(command);
 
2018
      zfree(tempfile);
 
2019
      return;
 
2020
   }
 
2021
 
 
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"));
 
2029
   
 
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));
 
2033
 
 
2034
   snprintf(command,1999,"rm \"%s\"",tempfile);                            //  clean up
 
2035
   err = system(command);
 
2036
   zfree(tempfile);
 
2037
   
 
2038
   load_filetags(outfile);                                                 //  update assigned tags file
 
2039
   update_asstags(outfile);
 
2040
   
 
2041
   Fmodified = Fimageturned = 0;                                           //  reset file modified status
 
2042
   Fsaved = Pundo;                                                         //  note which mods are saved    v.8.3
 
2043
 
 
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
 
2048
   }
 
2049
 
 
2050
   stat(outfile,&fstat);                                                   //  update file size    v.8.3
 
2051
   file_MB = 1.0 * fstat.st_size / mega;
 
2052
   SBupdate++;                                                             //  update status bar
 
2053
 
 
2054
   add_recent_file(outfile);                                               //  first in recent files list   v.8.4
 
2055
   return;
 
2056
}
 
2057
 
 
2058
 
 
2059
/**************************************************************************/
 
2060
 
 
2061
//  print image files
 
2062
 
 
2063
void m_print(GtkWidget *, const char *)
 
2064
{
 
2065
   int      err;
 
2066
   char     command[1000];
 
2067
   
 
2068
   if (! image_file) return;
 
2069
 
 
2070
   if (! Fprintoxx) {
 
2071
      zmessageACK(ZTX("printoxx program not found (see user guide)"));
 
2072
      return;
 
2073
   }
 
2074
 
 
2075
   snprintf(command,999,"printoxx \"%s\" &",image_file);                   //  send curr. file to printoxx
 
2076
   err = system(command);
 
2077
   if (err) zmessLogACK(wstrerror(err));
 
2078
   return;
 
2079
}
 
2080
 
 
2081
 
 
2082
/**************************************************************************/
 
2083
 
 
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.
 
2087
 
 
2088
void m_trash(GtkWidget *, const char *)
 
2089
{
 
2090
   int            err, yn;
 
2091
   char           command[1000], trashdir[100];
 
2092
   struct stat    trstat;
 
2093
 
 
2094
   if (! image_file) return;                                               //  nothing to trash
 
2095
   
 
2096
   err = stat(image_file,&trstat);                                         //  get file status
 
2097
   if (err) {
 
2098
      zmessLogACK(strerror(errno));
 
2099
      return;
 
2100
   }
 
2101
 
 
2102
   if (! (trstat.st_mode & S_IWUSR)) {                                     //  check permission
 
2103
      yn = zmessageYN(ZTX("Move read-only file to trash?"));
 
2104
      if (! yn) return;
 
2105
      trstat.st_mode |= S_IWUSR;
 
2106
      chmod(image_file,trstat.st_mode);
 
2107
   }
 
2108
   
 
2109
   snprintf(trashdir,99,"%s/%s",getenv("HOME"),ftrash);                    //  get full fotoxx trash file
 
2110
   
 
2111
   trstat.st_mode = 0;
 
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);
 
2116
      if (err) {
 
2117
         zmessLogACK(ZTX("Cannot create trash folder: %s"),wstrerror(err));
 
2118
         return;
 
2119
      }
 
2120
   }
 
2121
 
 
2122
   snprintf(command,999,"cp \"%s\" \"%s\" ",image_file,trashdir);          //  copy image file to trash
 
2123
   err = system(command);
 
2124
   if (err) {
 
2125
      zmessLogACK(ZTX("error: %s"),wstrerror(err));
 
2126
      return;
 
2127
   }
 
2128
 
 
2129
   snprintf(command,999,"rm \"%s\"",image_file);                           //  delete image file
 
2130
   err = system(command);
 
2131
   if (err) {
 
2132
      zmessLogACK(ZTX("error: %s"),wstrerror(err));
 
2133
      return;
 
2134
   }
 
2135
   
 
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
 
2140
 
 
2141
   return;
 
2142
}
 
2143
 
 
2144
 
 
2145
/**************************************************************************/
 
2146
 
 
2147
//  rename menu function
 
2148
 
 
2149
char     rename_old[100] = "";
 
2150
char     rename_new[100] = "";
 
2151
 
 
2152
void  m_rename(GtkWidget *, const char *)
 
2153
{
 
2154
   rename_dialog();                                                        //  activate rename dialog
 
2155
   return;
 
2156
}
 
2157
   
 
2158
 
 
2159
//  activate rename dialog, stuff data from current file
 
2160
 
 
2161
void rename_dialog()
 
2162
{
 
2163
   int rename_dialog_event(zdialog *zd, const char *event);
 
2164
   int rename_dialog_compl(zdialog *zd, int zstat);
 
2165
   
 
2166
   char     *pdir, *pfile, *pext;
 
2167
 
 
2168
   if (! image_file) return;
 
2169
 
 
2170
   if (! zdrename)                                                         //  restart dialog
 
2171
   {
 
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");
 
2176
 
 
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"));
 
2180
 
 
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 ]
 
2184
 
 
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");
 
2189
 
 
2190
      zdialog_run(zdrename,rename_dialog_event,rename_dialog_compl);       //  start dialog
 
2191
   }
 
2192
 
 
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
 
2198
 
 
2199
   return;
 
2200
}
 
2201
 
 
2202
 
 
2203
//  rename dialog event and completion functions
 
2204
 
 
2205
int rename_dialog_event(zdialog *zd, const char *event)
 
2206
{
 
2207
   char           *pp, *pdir, *pfile, *pext, *pnew, command[2000];
 
2208
   int            nseq, digits, ccp, ccn, ccx, err;
 
2209
   struct stat    statb;
 
2210
   
 
2211
   if (strEqu(event,"Bold"))                                               //  reset to current file name
 
2212
      zdialog_stuff(zd,"Enew",rename_old);
 
2213
 
 
2214
   if (strEqu(event,"Bprev")) {                                            //  previous name >> new name
 
2215
      zdialog_fetch(zd,"Lprev",rename_new,99);
 
2216
      zdialog_stuff(zd,"Enew",rename_new);
 
2217
   }
 
2218
 
 
2219
   if (strEqu(event,"B+1"))                                                //  increment sequence number
 
2220
   {
 
2221
      zdialog_fetch(zd,"Enew",rename_new,94);                              //  get entered filename
 
2222
      pp = rename_new + strlen(rename_new);
 
2223
      digits = 0;
 
2224
      while (pp[-1] >= '0' && pp[-1] <= '9') {
 
2225
         pp--;                                                             //  look for NNN in filenameNNN
 
2226
         digits++;
 
2227
      }
 
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);
 
2235
   }
 
2236
 
 
2237
   if (strEqu(event,"Bnew")) 
 
2238
   {
 
2239
      parsefile(image_file,&pdir,&pfile,&pext);                            //  existing /directories/file.ext
 
2240
 
 
2241
      zdialog_fetch(zd,"Enew",rename_new,94);                              //  new file name from user
 
2242
 
 
2243
      ccp = strlen(pdir);                                                  //  length of /directories/
 
2244
      ccn = strlen(rename_new);                                            //  length of file
 
2245
      if (pext) ccx = strlen(pext);                                        //  length of .ext
 
2246
      else ccx = 0;
 
2247
 
 
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);
 
2252
      
 
2253
      err = stat(pnew,&statb);                                             //  check for new name exists
 
2254
      if (! err) {
 
2255
         zmessageACK(ZTX("The target file already exists"));
 
2256
         zfree(pnew);
 
2257
         return 0;
 
2258
      }
 
2259
      
 
2260
      snprintf(command,1999,"cp \"%s\" \"%s\"",image_file,pnew);           //  copy to new file
 
2261
      err = system(command);
 
2262
      if (err) {
 
2263
         zmessageACK(ZTX("Rename failed \n %s"),wstrerror(err));
 
2264
         zfree(pnew);
 
2265
         return 0;
 
2266
      }
 
2267
 
 
2268
      zdialog_stuff(zd,"Lprev",rename_new);                                //  set previous name in dialog
 
2269
 
 
2270
      load_filetags(pnew);                                                 //  update assigned tags file
 
2271
      update_asstags(pnew);
 
2272
      zfree(pnew);
 
2273
 
 
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);
 
2278
      zfree(pnew);
 
2279
 
 
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
 
2283
   }
 
2284
 
 
2285
   return 0;
 
2286
}
 
2287
 
 
2288
 
 
2289
int rename_dialog_compl(zdialog *zd, int zstat)
 
2290
{
 
2291
   zdialog_free(zdrename);                                                 //  kill dialog
 
2292
   zdrename = null;
 
2293
   return 0;
 
2294
}
 
2295
 
 
2296
 
 
2297
/**************************************************************************/
 
2298
 
 
2299
//  forced quit - can cause running function to crash
 
2300
 
 
2301
void m_quit(GtkWidget *, const char *)
 
2302
{
 
2303
   if (image_file) update_filetags(image_file);                            //  commit tag changes, if any
 
2304
   if (mod_keep()) return;                                                 //  keep or discard pending changes
 
2305
   printf("quit \n");
 
2306
   Fshutdown++;
 
2307
   save_fotoxx_state();                                                    //  save state for next session
 
2308
   free_resources();                                                       //  delete temp files
 
2309
   gtk_main_quit();                                                        //  gone forever
 
2310
   return;
 
2311
}
 
2312
 
 
2313
 
 
2314
/**************************************************************************
 
2315
      tools functions
 
2316
***************************************************************************/
 
2317
 
 
2318
//  set new image zoom level or magnification
 
2319
 
 
2320
void m_zoom(GtkWidget *, const char *menu)
 
2321
{
 
2322
   int      ii, iww, ihh, Dww, Dhh;
 
2323
   char     zoom;
 
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 };
 
2326
   
 
2327
   if (strnEqu(menu,"Zoom",4)) zoom = menu[4];                             //  get + or -
 
2328
   else  zoom = *menu;
 
2329
   
 
2330
   Dww = drWin->allocation.width;                                          //  drawing window size
 
2331
   Dhh = drWin->allocation.height;
 
2332
   
 
2333
   if (E3rgb48) {                                                          //  bugfix  v.8.1
 
2334
      iww = E3ww;
 
2335
      ihh = E3hh;
 
2336
   }
 
2337
   else  {
 
2338
      iww = Fww;
 
2339
      ihh = Fhh;
 
2340
   }
 
2341
 
 
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;
 
2347
   }
 
2348
   else fitscale = 1.0;                                                    //  if image < window use 100%
 
2349
   
 
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;
 
2356
      Fzoom = scales[ii];
 
2357
      if (Fzoom < fitscale) Fzoom = 0;                                     //  image < window
 
2358
   }
 
2359
 
 
2360
   if (zoom == '-') Fzoom = 0;                                             //  zoom to fit window
 
2361
 
 
2362
   if (zoom == 'Z') {
 
2363
      if (Fzoom != 0) Fzoom = 0;                                           //  toggle 100% and fit window
 
2364
      else  Fzoom = 1;
 
2365
   }
 
2366
   
 
2367
   if (! Fzoom) zoomx = zoomy = 0;                                         //  no req. zoom center    v.7.5
 
2368
   
 
2369
   mwpaint2();                                                             //  refresh window
 
2370
   return;
 
2371
}
 
2372
 
 
2373
 
 
2374
/**************************************************************************/
 
2375
 
 
2376
//  monitor test function
 
2377
 
 
2378
void m_montest(GtkWidget *, const char *)
 
2379
{
 
2380
   uint8       *pixel;
 
2381
   int         red, green, blue;
 
2382
   int         row, col, row1, row2;
 
2383
   int         ww = 800, hh = 500;
 
2384
   
 
2385
   if (mod_keep()) return;
 
2386
   if (! menulock(1)) return;
 
2387
 
 
2388
   mutex_lock(&pixmaps_lock);
 
2389
 
 
2390
   RGB_free(Frgb24);
 
2391
   Frgb24 = RGB_make(ww,hh,24);
 
2392
   Fww = ww;
 
2393
   Fhh = hh;
 
2394
   file_bpp = 24;
 
2395
   file_MB = 0;
 
2396
 
 
2397
   for (red = 0; red <= 1; red++)
 
2398
   for (green = 0; green <= 1; green++)
 
2399
   for (blue = 0; blue <= 1; blue++)
 
2400
   {
 
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;
 
2404
      
 
2405
      for (row = row1; row < row2; row++)
 
2406
      for (col = 0; col < ww; col++)
 
2407
      {
 
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;
 
2412
      }
 
2413
   }
 
2414
 
 
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
 
2419
   menulock(0);
 
2420
   return;
 
2421
}
 
2422
 
 
2423
 
 
2424
/**************************************************************************/
 
2425
 
 
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.
 
2429
 
 
2430
struct tt_tagrec {
 
2431
   char        *file;                                                      //  image file filespec
 
2432
   char        *tags;                                                      //  image file tags
 
2433
   char        imagedate[12], filedate[16];                                //  both in one rec.
 
2434
   int         update;
 
2435
};
 
2436
 
 
2437
tt_tagrec   tt_old[max_images];
 
2438
tt_tagrec   tt_new[max_images];
 
2439
 
 
2440
 
 
2441
void m_index_tt(GtkWidget *, const char *)
 
2442
{
 
2443
   FILE           *fid;
 
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 };
 
2450
   cchar          *ppc;
 
2451
   int            Nold = 0, Nnew = 0;
 
2452
   int            comp, orec, nrec;
 
2453
   struct tm      bdt;
 
2454
   struct stat    statb;
 
2455
 
 
2456
   
 
2457
   if (! Fexiftool) {                                                      //  exiftool is required
 
2458
      zmessageACK(Bexiftoolmissing);
 
2459
      return;
 
2460
   }
 
2461
 
 
2462
   pp = zgetfile(ZTX("Select top image directory"),topdirk,"folder");
 
2463
   if (! pp) return;
 
2464
   if (topdirk) zfree(topdirk);
 
2465
   topdirk = pp;
 
2466
 
 
2467
   if (! menulock(1)) return;
 
2468
 
 
2469
   gdk_window_set_cursor(drWin->window,busycursor);                        //  set function busy cursor  v.8.4
 
2470
   zmainloop();
 
2471
 
 
2472
//  read current assigned tags file and build "oldlist" of tags
 
2473
 
 
2474
   fid = fopen(asstagsfile,"r");                                           //  open assigned tags file
 
2475
   if (fid) 
 
2476
   {
 
2477
      while (true)
 
2478
      {
 
2479
         pp = fgets_trim(buff,999,fid);                                    //  read tag and file dates in one rec.
 
2480
         if (! pp) break;
 
2481
         
 
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;
 
2486
 
 
2487
         ppc = strField(buff,' ',2);                                       //  date: yyyy:mm:dd yyyymmddhhmmss
 
2488
         if (ppc) strncpy0(tt_old[Nold].imagedate,ppc,12);
 
2489
 
 
2490
         ppc = strField(buff,' ',3);
 
2491
         if (ppc) strncpy0(tt_old[Nold].filedate,ppc,16);
 
2492
 
 
2493
         pp = fgets_trim(buff,999,fid);                                    //  tags: xxxxx xxxxx xxxxxxx xxxx
 
2494
         if (! pp) break;
 
2495
         tt_old[Nold].tags = strdupz(pp+6);
 
2496
   
 
2497
         pp = fgets_trim(buff,999,fid);                                    //  file: /directory/.../filename.jpg
 
2498
         if (! pp) break;
 
2499
         tt_old[Nold].file = strdupz(pp+6);
 
2500
         
 
2501
         fgets_trim(buff,999,fid);                                         //  read blank separator rec.
 
2502
         
 
2503
         if (++Nold == max_images) 
 
2504
            zappcrash("more than %d image files: %d",max_images);
 
2505
      }
 
2506
      
 
2507
      fclose(fid);
 
2508
   }
 
2509
 
 
2510
   printf("%d current tag records found \n",Nold);
 
2511
   
 
2512
//  find all image files and create "newlist" with no tags
 
2513
 
 
2514
   snprintf(buff,999,"find \"%s\" -type d",topdirk);
 
2515
   contx = 0;
 
2516
 
 
2517
   while ((subdirk = command_output(contx,buff)))                          //  find directories under top directory
 
2518
   {
 
2519
      pp = (char *) strrchr(subdirk,'/');
 
2520
      if (pp && strEqu(pp,"/.thumbnails")) {                               //  ignore .thumbnails
 
2521
         zfree(subdirk);
 
2522
         continue;
 
2523
      }
 
2524
 
 
2525
      image_gallery(subdirk,"init");                                       //  get all image files in directory
 
2526
      filespec1 = image_gallery(subdirk,"first");
 
2527
 
 
2528
      while (filespec1)
 
2529
      {
 
2530
         if (image_file_type(filespec1) == 2) {                            //  construct new tag record
 
2531
            err = stat(filespec1,&statb);
 
2532
            if (err) continue;
 
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);
 
2542
         }
 
2543
         
 
2544
         filespec2 = image_gallery(filespec1,"next");                      //  next image file
 
2545
         zfree(filespec1);
 
2546
         filespec1 = filespec2;
 
2547
      }
 
2548
 
 
2549
      zfree(subdirk);
 
2550
   }
 
2551
   
 
2552
   printf("found %d image files \n",Nnew);
 
2553
 
 
2554
//  sort old and new lists by filespec in preparation for merging them
 
2555
 
 
2556
   int index_tt_comp(cchar *rec1, cchar *rec2);
 
2557
 
 
2558
   HeapSort((char *) tt_old,sizeof(tt_tagrec),Nold,index_tt_comp);
 
2559
   HeapSort((char *) tt_new,sizeof(tt_tagrec),Nnew,index_tt_comp);
 
2560
 
 
2561
//  merge and compare lists
 
2562
//  if filespecs match and have the same date, then "tt_old" tags are OK
 
2563
 
 
2564
   for (orec = nrec = 0; nrec < Nnew; )
 
2565
   {
 
2566
      tt_new[nrec].update = 1;
 
2567
 
 
2568
      if (orec == Nold) comp = +1;
 
2569
      else comp = strcmp(tt_old[orec].file, tt_new[nrec].file);
 
2570
      
 
2571
      if (comp > 0) nrec++;
 
2572
      else if (comp < 0) orec++;
 
2573
 
 
2574
      else {
 
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;
 
2580
         }
 
2581
         nrec++;
 
2582
         orec++;
 
2583
      }
 
2584
   }
 
2585
 
 
2586
//  release old list memory
 
2587
 
 
2588
   for (orec = 0; orec < Nold; orec++)
 
2589
   {
 
2590
      zfree(tt_old[orec].file);
 
2591
      if (tt_old[orec].tags) zfree(tt_old[orec].tags);
 
2592
   }
 
2593
 
 
2594
//  process entries needing update in new list, get updated tags from image file EXIF data
 
2595
 
 
2596
   for (fcount = nrec = 0; nrec < Nnew; nrec++)
 
2597
   {
 
2598
      if (tt_new[nrec].update == 0) continue;
 
2599
 
 
2600
      ppv = exif_get(tt_new[nrec].file,exifkeys,2);
 
2601
      imagedate = ppv[0];
 
2602
      imagetags = ppv[1];
 
2603
      
 
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);
 
2607
      }
 
2608
      else strcpy(tt_new[nrec].imagedate,"null");
 
2609
      
 
2610
      if (imagetags && strlen(imagetags))
 
2611
         tt_new[nrec].tags = strdupz(imagetags);
 
2612
      else tt_new[nrec].tags = strdupz("null");
 
2613
 
 
2614
      if (imagedate) zfree(imagedate);
 
2615
      if (imagetags) zfree(imagetags);
 
2616
 
 
2617
      snprintf(stbartext,199,"%5d %s",++fcount,tt_new[nrec].file);         //  update status bar
 
2618
      stbar_message(STbar,stbartext);
 
2619
   }
 
2620
 
 
2621
//  write new assigned tags file
 
2622
 
 
2623
   fid = fopen(asstagsfile,"w");                                           //  open assigned tags file
 
2624
   if (! fid) zappcrash("cannot write tags file");
 
2625
 
 
2626
   for (nrec = 0; nrec < Nnew; nrec++)
 
2627
   {
 
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);
 
2631
      fprintf(fid,"\n");
 
2632
   }
 
2633
   
 
2634
   fclose(fid);
 
2635
 
 
2636
//  look for missing thumbnails and create them
 
2637
 
 
2638
   for (fcount = nrec = 0; nrec < Nnew; nrec++)
 
2639
   {
 
2640
      pp = image_thumbfile(tt_new[nrec].file);                             //  find/update/create thumbnail
 
2641
      if (pp) zfree(pp);
 
2642
      snprintf(stbartext,199,"%5d %s",++fcount,tt_new[nrec].file);         //  update status bar
 
2643
      stbar_message(STbar,stbartext);
 
2644
   }
 
2645
 
 
2646
//  release new list memory
 
2647
 
 
2648
   for (nrec = 0; nrec < Nnew; nrec++)
 
2649
   {
 
2650
      zfree(tt_new[nrec].file);
 
2651
      if (tt_new[nrec].tags) zfree(tt_new[nrec].tags);
 
2652
   }
 
2653
 
 
2654
   image_gallery(image_file,"init");                                       //  reset image gallery file list
 
2655
   image_gallery(0,"paint2");                                              //  refresh gallery window if active
 
2656
   Fsearchlist = 0;
 
2657
 
 
2658
   gdk_window_set_cursor(drWin->window,0);                                 //  restore normal cursor
 
2659
   menulock(0);
 
2660
   return;
 
2661
}
 
2662
 
 
2663
 
 
2664
//  sort compare function - compare tag record filespecs and return
 
2665
//   <0 | 0 | >0   for   file1 < | == | > file2
 
2666
 
 
2667
int index_tt_comp(cchar *rec1, cchar *rec2)
 
2668
{
 
2669
   char * file1 = ((tt_tagrec *) rec1)->file;
 
2670
   char * file2 = ((tt_tagrec *) rec2)->file;
 
2671
   return strcmp(file1,file2);
 
2672
}
 
2673
 
 
2674
 
 
2675
/**************************************************************************/
 
2676
 
 
2677
//  create or update brightness distribution graph
 
2678
 
 
2679
void m_brightgraph(GtkWidget *, const char *)                              //  menu function
 
2680
{
 
2681
   if (! Drgb24) return;
 
2682
 
 
2683
   if (brightgraph) {
 
2684
      brightgraph_paint();
 
2685
      return;
 
2686
   }
 
2687
 
 
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);
 
2693
   
 
2694
   drbrightgraph = gtk_drawing_area_new();
 
2695
   gtk_container_add(GTK_CONTAINER(brightgraph),drbrightgraph);
 
2696
 
 
2697
   G_SIGNAL(brightgraph,"destroy",brightgraph_destroy,0)
 
2698
   G_SIGNAL(drbrightgraph,"expose-event",brightgraph_paint,0)
 
2699
 
 
2700
   gtk_widget_show_all(brightgraph);
 
2701
   
 
2702
   return;
 
2703
}
 
2704
 
 
2705
 
 
2706
void brightgraph_paint()                                                   //  paint graph window
 
2707
{
 
2708
   int         brdist[20], nbins = 20;
 
2709
   int         px, py, ii;
 
2710
   int         winww, winhh;
 
2711
   int         ww, hh, orgx, orgy;
 
2712
   int         dist_maxbin = 0;
 
2713
   uint8       *pixel;
 
2714
   double      bright;
 
2715
 
 
2716
   if (! brightgraph) return;
 
2717
   if (! Drgb24) return;
 
2718
 
 
2719
   for (ii = 0; ii < nbins; ii++)                                          //  clear brightness distribution
 
2720
      brdist[ii] = 0;
 
2721
 
 
2722
   mutex_lock(&pixmaps_lock);
 
2723
 
 
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
 
2730
   }
 
2731
 
 
2732
   mutex_unlock(&pixmaps_lock);
 
2733
 
 
2734
   gdk_window_clear(drbrightgraph->window);
 
2735
 
 
2736
   winww = drbrightgraph->allocation.width;
 
2737
   winhh = drbrightgraph->allocation.height;
 
2738
   
 
2739
   for (ii = 0; ii < nbins; ii++)
 
2740
      if (brdist[ii] > dist_maxbin) dist_maxbin = brdist[ii];
 
2741
 
 
2742
   for (ii = 0; ii < nbins; ii++)
 
2743
   {
 
2744
      ww = winww / nbins;
 
2745
      hh = int(0.9 * winhh * brdist[ii] / dist_maxbin);
 
2746
      orgx = ii * ww;
 
2747
      orgy = winhh - hh;
 
2748
      gdk_draw_rectangle(drbrightgraph->window,gdkgc,1,orgx,orgy,ww,hh);
 
2749
   }
 
2750
   
 
2751
   return;
 
2752
}
 
2753
 
 
2754
 
 
2755
void brightgraph_destroy()                                                 //  delete window
 
2756
{
 
2757
   if (brightgraph) gtk_widget_destroy(brightgraph);
 
2758
   brightgraph = 0;
 
2759
   return;
 
2760
}
 
2761
 
 
2762
 
 
2763
/**************************************************************************/
 
2764
 
 
2765
//  start a new instance of fotoxx in parallel
 
2766
 
 
2767
void m_clone(GtkWidget *, const char *)
 
2768
{
 
2769
   char     command[300];
 
2770
   int      ignore;
 
2771
 
 
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);
 
2776
   return;
 
2777
}
 
2778
 
 
2779
 
 
2780
/**************************************************************************/
 
2781
 
 
2782
//  enter or leave slideshow mode
 
2783
 
 
2784
void m_slideshow(GtkWidget *, const char *)
 
2785
{
 
2786
   static int     ww, hh;
 
2787
   zdialog        *zd;
 
2788
   int            zstat, secs;
 
2789
   
 
2790
   if (! Fslideshow)
 
2791
   {
 
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));
 
2797
 
 
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);
 
2805
      zdialog_free(zd);
 
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
 
2809
      Fslideshow = 1;
 
2810
   }
 
2811
 
 
2812
   else
 
2813
   {
 
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));
 
2819
      Fslideshow = 0;
 
2820
   }
 
2821
 
 
2822
   Fzoom = 0;                                                              //  fit image to window
 
2823
   Fblowup = Fslideshow;                                                   //  blow-up small images if SS mode
 
2824
   mwpaint2(); 
 
2825
   return;
 
2826
}
 
2827
 
 
2828
 
 
2829
/**************************************************************************/
 
2830
 
 
2831
//  show RGB values for pixel at mouse click
 
2832
 
 
2833
CBfunc   *RGB_func_save;
 
2834
int      RGB_mcap_save;
 
2835
 
 
2836
 
 
2837
void m_RGB(GtkWidget *, const char *)                                      //  menu function
 
2838
{
 
2839
   void RGB_mousefunc();
 
2840
 
 
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
 
2847
   Frgbmode = 1;
 
2848
   return;
 
2849
}
 
2850
 
 
2851
void RGB_mousefunc()                                                       //  mouse function
 
2852
{
 
2853
   int         px, py;
 
2854
   double      red, green, blue;
 
2855
   double      fbright, fred;
 
2856
   char        work[90];
 
2857
   uint8       *ppix24;
 
2858
   uint16      *ppix48;
 
2859
   
 
2860
   if (! Frgb24) return;
 
2861
 
 
2862
   if (LMclick)                                                            //  left mouse click
 
2863
   {
 
2864
      LMclick = 0;
 
2865
      px = Mxclick;                                                        //  click position
 
2866
      py = Myclick;
 
2867
      
 
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);
 
2877
      }
 
2878
 
 
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);
 
2888
      }
 
2889
 
 
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;
 
2894
         red = ppix24[0];
 
2895
         green = ppix24[1];
 
2896
         blue = ppix24[2];
 
2897
         fbright = brightness(ppix24);
 
2898
         fred = redness(ppix24);
 
2899
      }
 
2900
      
 
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);
 
2904
   }
 
2905
   
 
2906
   if (RMclick || KBkey == GDK_Escape)                                     //  escape or right-click   v.7.0
 
2907
   {                                                                       //  leave RGB mode
 
2908
      RMclick = Frgbmode = KBkey = 0;                                      //  v.8.6
 
2909
      Mcapture = RGB_mcap_save;
 
2910
      mouseCBfunc = RGB_func_save;                                         //  disconnect mouse
 
2911
   }
 
2912
   
 
2913
   return;
 
2914
}
 
2915
 
 
2916
 
 
2917
/**************************************************************************/
 
2918
 
 
2919
//  choose or set lens parameters for panoramas
 
2920
 
 
2921
void  m_parms(GtkWidget *, const char *)
 
2922
{
 
2923
   zdialog     *zd;
 
2924
   int         ii, zstat, radb;
 
2925
   char        text[20];
 
2926
   
 
2927
   zd = zdialog_new(ZTX("Lens Parameters"),mWin,Bapply,Bcancel,0);
 
2928
   zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=5");
 
2929
   
 
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");
 
2954
   
 
2955
   for (ii = 0; ii < 4; ii++)                                              //  stuff lens data into dialog
 
2956
   {
 
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]);
 
2963
   }
 
2964
 
 
2965
   snprintf(text,20,"radb%d",curr_lens);                                   //  current lens = selected
 
2966
   zdialog_stuff(zd,text,1);
 
2967
 
 
2968
   zstat = zdialog_run(zd,0,0);                                            //  run dialog, get inputs
 
2969
 
 
2970
   if (zstat != 1) {
 
2971
      zdialog_free(zd);                                                    //  canceled
 
2972
      return;
 
2973
   }
 
2974
   
 
2975
   for (ii = 0; ii < 4; ii++)                                              //  fetch lens data (revisions)
 
2976
   {
 
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;
 
2987
   }
 
2988
   
 
2989
   zdialog_free(zd);
 
2990
   return;
 
2991
}
 
2992
 
 
2993
 
 
2994
/**************************************************************************/
 
2995
 
 
2996
//  set GUI language
 
2997
 
 
2998
void  m_lang(GtkWidget *, const char *)                                    //  v.6.2
 
2999
{
 
3000
   zdialog     *zd;
 
3001
   int         ii, cc, err, zstat;
 
3002
 
 
3003
   char        lang[20], *pp, command[100], locmess[200];
 
3004
 
 
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 };
 
3008
   
 
3009
   strcpy(locmess,ZTX("Available Translations"));
 
3010
   cc = strlen(locmess);
 
3011
 
 
3012
   for (ii = 0; langs[ii]; ii++)
 
3013
   {
 
3014
      strcpy(locmess+cc,"\n ");
 
3015
      strcpy(locmess+cc+2,langs[ii]);
 
3016
      cc += strlen(langs[ii]) + 2;
 
3017
   }
 
3018
   
 
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);
 
3024
 
 
3025
   for (ii = 0; langs[ii]; ii++)                                           //  stuff languages into list
 
3026
      zdialog_cb_app(zd,"combo",langs[ii]);
 
3027
 
 
3028
   zstat = zdialog_run(zd,0,0);                                            //  run dialog
 
3029
 
 
3030
   if (zstat != 1) {
 
3031
      zdialog_free(zd);                                                    //  canceled
 
3032
      return;
 
3033
   }
 
3034
   
 
3035
   zdialog_fetch(zd,"combo",lang,20);                                      //  get user input
 
3036
   zdialog_free(zd);
 
3037
   
 
3038
   pp = strchr(lang,' ');                                                  //  isolate lc_RC part
 
3039
   *pp = 0;
 
3040
 
 
3041
   sprintf(command,"fotoxx -l %s &",lang);                                 //  restart fotoxx
 
3042
   err = system(command);
 
3043
   m_quit(0,0);
 
3044
 
 
3045
   return;
 
3046
}
 
3047
 
 
3048
 
 
3049
/**************************************************************************/
 
3050
 
 
3051
//  create desktop icon / launcher
 
3052
 
 
3053
void  m_launcher(GtkWidget *, const char *)                                //  v.7.0
 
3054
{
 
3055
   zmake_launcher("Graphics","Image Editor");
 
3056
   return;
 
3057
}
 
3058
 
 
3059
 
 
3060
/**************************************************************************/
 
3061
 
 
3062
//  convert multiple RAW files to tiff
 
3063
 
 
3064
void  m_multiraw(GtkWidget *, const char *)                                //  v.7.1
 
3065
{
 
3066
   zdialog        *zd;
 
3067
   char           entraw[200], *pp, *rawspec = 0;
 
3068
   char           *outfile, command[1000];
 
3069
   const char     *rawfile;
 
3070
   int            zstat, ftf, err;
 
3071
   struct stat    fstat;
 
3072
 
 
3073
   if (mod_keep()) return;
 
3074
   if (! menulock(1)) return;
 
3075
 
 
3076
   if (! Fufraw) {
 
3077
      zmessageACK(ZTX("Package ufraw required for this function"));
 
3078
      goto rawdone;
 
3079
   }
 
3080
 
 
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");
 
3085
 
 
3086
   zstat = zdialog_run(zd);                                                //  run dialog (blocking)
 
3087
   zdialog_fetch(zd,"entraw",entraw,199);                                  //  get RAW file spec
 
3088
   zdialog_free(zd);
 
3089
   if (zstat != 1) goto rawdone;
 
3090
   
 
3091
   if (*entraw != '/')                                                     //  needs a directory
 
3092
   {
 
3093
      if (image_file) {
 
3094
         rawspec = strdupz(image_file,200);                                //  use same as image file
 
3095
         pp = (char *) strrchr(rawspec,'/');
 
3096
         if (pp) pp++;
 
3097
         else  pp = rawspec;
 
3098
         strncpy0(pp,entraw,199);
 
3099
      }
 
3100
      else {
 
3101
         pp = getcwd(null,0);                                              //  use curr. directory
 
3102
         rawspec = strdupz(pp,200);
 
3103
         free(pp);
 
3104
         strcat(rawspec,"/");
 
3105
         pp = (char *) strrchr(rawspec,'/');
 
3106
         strncpy0(pp+1,entraw,199);
 
3107
      }
 
3108
   }
 
3109
   
 
3110
   else rawspec = strdupz(entraw);                                         //  absolute path was given
 
3111
 
 
3112
   write_popup_text("open","converting RAW files",500,200);
 
3113
 
 
3114
   gdk_window_set_cursor(drWin->window,busycursor);                        //  set function busy cursor  v.8.4
 
3115
   zmainloop();
 
3116
   
 
3117
   for (ftf = 1; ; )
 
3118
   {
 
3119
      rawfile = SearchWild(rawspec,ftf);                                   //  find all *.RAW files
 
3120
      if (! rawfile) break;
 
3121
      
 
3122
      err = stat(rawfile,&fstat);
 
3123
      if (err) continue;
 
3124
 
 
3125
      outfile = strdupz(rawfile,5);
 
3126
      pp = (char *) strrchr(rawfile,'.');
 
3127
      pp = outfile + (pp - rawfile);
 
3128
      strcpy(pp,".tiff");
 
3129
 
 
3130
      write_popup_text("write",rawfile,0,0);                               //  convert next file
 
3131
      
 
3132
      //  try new ufraw command format first, then old if it fails
 
3133
 
 
3134
      snprintf(command,999,"ufraw-batch --out-type=tiff --out-depth=16"
 
3135
                  " --overwrite --output=\"%s\" \"%s\" ",outfile, rawfile);
 
3136
      err = system(command);
 
3137
 
 
3138
      if (err) {
 
3139
         snprintf(command,999,"ufraw-batch --out-type=tiff16"
 
3140
                  " --overwrite --output=\"%s\" \"%s\" ",outfile, rawfile);
 
3141
         err = system(command);
 
3142
      }
 
3143
 
 
3144
      if (err) {
 
3145
         write_popup_text("write",wstrerror(err),0,0);
 
3146
         zfree(outfile);
 
3147
         continue;
 
3148
      }
 
3149
      
 
3150
      menulock(0);
 
3151
      f_open(outfile);                                                     //  open converted file
 
3152
      menulock(1);
 
3153
      mwpaint2();
 
3154
      zmainloop();
 
3155
 
 
3156
      image_gallery(outfile,"init");                                       //  update image gallery file list
 
3157
      image_gallery(0,"paint2");                                           //  refresh gallery window if active
 
3158
      Fsearchlist = 0;
 
3159
      zfree(outfile);
 
3160
   }
 
3161
   
 
3162
   write_popup_text("close",0,0,0);
 
3163
 
 
3164
   gdk_window_set_cursor(drWin->window,0);                                 //  restore normal cursor
 
3165
 
 
3166
rawdone:
 
3167
   if (rawspec) zfree(rawspec);
 
3168
   menulock(0);
 
3169
   return;
 
3170
}
 
3171
 
 
3172
 
 
3173
/**************************************************************************/
 
3174
 
 
3175
//  burn images to CD/DVD                                                  //  v.7.2
 
3176
 
 
3177
int  burn_showthumb();
 
3178
 
 
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;
 
3184
 
 
3185
void  m_burn(GtkWidget *, const char *)
 
3186
{
 
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 *);
 
3190
 
 
3191
   GdkCursor   *cursor;
 
3192
   GdkWindow   *gdkwin;
 
3193
   
 
3194
   if (! menulock(1)) return;                                              //  lock menus
 
3195
 
 
3196
   m_gallery(0,0);                                                         //  activate image gallery window
 
3197
 
 
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");
 
3209
 
 
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);
 
3214
   
 
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)
 
3218
 
 
3219
   PangoFontDescription *pfontdesc = pango_font_description_from_string(burn_font);
 
3220
   gtk_widget_modify_font(burn_files,pfontdesc);
 
3221
   
 
3222
   zdialog_resize(zdburn,400,0);
 
3223
   zdialog_run(zdburn,burn_dialog_event,burn_dialog_compl);
 
3224
 
 
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);
 
3228
   
 
3229
   burn_cursorpos = 0;
 
3230
   return;
 
3231
}
 
3232
 
 
3233
 
 
3234
//  burn dialog event function
 
3235
 
 
3236
int burn_dialog_event(zdialog *zd, const char *event)
 
3237
{
 
3238
   GtkTextBuffer  *textBuff;
 
3239
   GtkTextIter    iter1, iter2;
 
3240
   static char    *imagefile = 0;
 
3241
   const char     *xfile;
 
3242
   int            line;
 
3243
 
 
3244
   if (strEqu(event,"delete"))                                             //  delete file at cursor position
 
3245
   {
 
3246
      if (imagefile) free(imagefile);
 
3247
      imagefile = 0;
 
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
 
3251
      iter2 = iter1;
 
3252
      gtk_text_iter_forward_to_line_end(&iter2);                           //  iter at line end
 
3253
 
 
3254
      imagefile = gtk_text_buffer_get_text(textBuff,&iter1,&iter2,0);      //  save selected file
 
3255
      if (*imagefile != '/') {
 
3256
         free(imagefile);
 
3257
         imagefile = 0;
 
3258
         return 0;
 
3259
      }
 
3260
 
 
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)
 
3264
 
 
3265
      burn_showthumb();                                                    //  thumbnail = next file
 
3266
   }
 
3267
 
 
3268
   if (strEqu(event,"insert"))                                             //  insert last deleted file
 
3269
   {
 
3270
      if (! imagefile) return 0;                                           //    at current cursor position
 
3271
      burn_insert_file(imagefile);
 
3272
   }
 
3273
 
 
3274
   if (strEqu(event,"addall"))                                             //  insert all files in image gallery
 
3275
   {
 
3276
      if (imagefile) free(imagefile);
 
3277
      imagefile = 0;
 
3278
 
 
3279
      textBuff = gtk_text_view_get_buffer(GTK_TEXT_VIEW(burn_files));
 
3280
      xfile = "first";
 
3281
 
 
3282
      while (true)
 
3283
      {
 
3284
         imagefile = image_gallery(imagefile,xfile,1,0);                   //  next file
 
3285
         if (! imagefile) break;
 
3286
         xfile = "next";
 
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
 
3292
      }
 
3293
   }
 
3294
 
 
3295
   return 0;
 
3296
}
 
3297
 
 
3298
 
 
3299
//  burn dialog completion function - send all selected files to brasero
 
3300
 
 
3301
int burn_dialog_compl(zdialog *zd, int zstat)
 
3302
{
 
3303
   int            line, nlines, cc1, cc2, err;
 
3304
   char           *imagefile = 0;
 
3305
   char           *command;
 
3306
   GtkTextBuffer  *textBuff;
 
3307
   GtkTextIter    iter1, iter2;
 
3308
 
 
3309
   if (zstat != 1) {                                                       //  cancelled
 
3310
      zdialog_free(zdburn);                                                //  kill dialog
 
3311
      zdburn = null;
 
3312
      menulock(0);
 
3313
      return 0;
 
3314
   }
 
3315
 
 
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);
 
3323
 
 
3324
   for (line = 0; line < nlines; line++)
 
3325
   {
 
3326
      gtk_text_buffer_get_iter_at_line(textBuff,&iter1,line);              //  iter at line start
 
3327
      iter2 = iter1;
 
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," \"");
 
3332
         cc2 += 2;
 
3333
         strcpy(command+cc2,imagefile);
 
3334
         cc2 += strlen(imagefile);
 
3335
         strcpy(command+cc2,"\"");
 
3336
         cc2 += 1;
 
3337
         free(imagefile);
 
3338
      }
 
3339
   }
 
3340
   
 
3341
   zdialog_free(zdburn);                                                   //  kill dialog
 
3342
   zdburn = null;
 
3343
   
 
3344
   strcat(command," &");                                                   //  do command in background  v.8.4
 
3345
   err = system(command);                                                  //  start brasero
 
3346
   zfree(command);
 
3347
   menulock(0);
 
3348
   return 0;
 
3349
}
 
3350
 
 
3351
 
 
3352
//  called from image gallery window when a thumbnail is clicked
 
3353
//  add image file to list at current cursor position, set thumbnail = file
 
3354
 
 
3355
void burn_insert_file(const char *imagefile)
 
3356
{
 
3357
   GtkTextIter    iter;
 
3358
   GtkTextBuffer  *textBuff;
 
3359
 
 
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
 
3368
   }
 
3369
 
 
3370
   return;
 
3371
}
 
3372
 
 
3373
 
 
3374
//  process mouse click in files window: 
 
3375
//  set new cursor position and set thumbnail = clicked file
 
3376
 
 
3377
int burn_mouseclick(GtkWidget *, GdkEventButton *event, void *)
 
3378
{
 
3379
   int            mpx, mpy;
 
3380
   GtkWidget      *scrollwin;
 
3381
   GtkAdjustment  *scrolladj;
 
3382
   double         scrollpos;
 
3383
 
 
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
 
3392
   return 0;
 
3393
}
 
3394
 
 
3395
 
 
3396
//  show thumbnail for file at current cursor position
 
3397
 
 
3398
int burn_showthumb()
 
3399
{
 
3400
   int            line;
 
3401
   char           *imagefile;
 
3402
   GtkTextBuffer  *textBuff;
 
3403
   GtkTextIter    iter1, iter2;
 
3404
   GdkPixbuf      *thumbnail = 0;
 
3405
 
 
3406
   gdk_window_clear(burn_drawarea->window);
 
3407
 
 
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
 
3411
   iter2 = iter1;
 
3412
   gtk_text_iter_forward_to_line_end(&iter2);                              //  iter at line end
 
3413
 
 
3414
   imagefile = gtk_text_buffer_get_text(textBuff,&iter1,&iter2,0);         //  get selected file
 
3415
   if (*imagefile != '/') {
 
3416
      free(imagefile);
 
3417
      return 0;
 
3418
   }
 
3419
 
 
3420
   thumbnail = image_thumbnail(imagefile,128);                             //  get thumbnail
 
3421
   free(imagefile);
 
3422
 
 
3423
   if (thumbnail) {
 
3424
      gdk_draw_pixbuf(burn_drawarea->window,0,thumbnail,0,0,0,0,-1,-1,nodither,0,0);
 
3425
      g_object_unref(thumbnail);
 
3426
   }
 
3427
   return 0;
 
3428
}
 
3429
 
 
3430
 
 
3431
/**************************************************************************
 
3432
      Image Tag and EXIF functions
 
3433
***************************************************************************/
 
3434
 
 
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
 
3441
 
 
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
 
3452
 
 
3453
 
 
3454
/**************************************************************************/
 
3455
 
 
3456
//  edit tags menu function
 
3457
 
 
3458
void m_edit_tags(GtkWidget *, const char *)
 
3459
{
 
3460
   if (! Fexiftool) {                                                      //  exiftool is required
 
3461
      zmessageACK(Bexiftoolmissing);
 
3462
      return;
 
3463
   }
 
3464
   
 
3465
   edit_tags_dialog();
 
3466
   return;
 
3467
}
 
3468
 
 
3469
 
 
3470
//  activate edit tags dialog, stuff data from current file
 
3471
 
 
3472
void edit_tags_dialog()
 
3473
{
 
3474
   int edit_tags_dialog_event(zdialog *zd, const char *event);
 
3475
   int edit_tags_dialog_compl(zdialog *zd, int zstat);
 
3476
 
 
3477
   char     *ppv, pstarsN[12];
 
3478
 
 
3479
   if (! image_file) return;
 
3480
 
 
3481
   if (! zdtags)                                                           //  (re) start tag edit dialog 
 
3482
   {
 
3483
      load_asstags();                                                      //  get all assigned tags
 
3484
 
 
3485
      zdtags = zdialog_new(ZTX("Edit Tags"),mWin,Bdone,Bcancel,0);         //  tag edit dialog
 
3486
 
 
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");
 
3490
 
 
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");
 
3495
 
 
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);
 
3513
 
 
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");
 
3518
 
 
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");
 
3523
 
 
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);
 
3527
 
 
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");
 
3533
 
 
3534
      zdialog_resize(zdtags,400,300);
 
3535
      zdialog_run(zdtags,edit_tags_dialog_event,edit_tags_dialog_compl);   //  start dialog
 
3536
      
 
3537
      edit_tags_fixup("filetags");                                         //  setup for mouse tag selection
 
3538
      edit_tags_fixup("asstags");
 
3539
      edit_tags_fixup("recentags");
 
3540
   }
 
3541
 
 
3542
   load_filetags(image_file);                                              //  get file tags from EXIF data
 
3543
 
 
3544
   ppv = (char *) strrchr(image_file,'/');
 
3545
   zdialog_stuff(zdtags,"file",ppv+1);                                     //  stuff dialog file name
 
3546
 
 
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);
 
3553
 
 
3554
   tags_changed = 0;
 
3555
   return;
 
3556
}
 
3557
 
 
3558
 
 
3559
//  setup tag display widget for tag selection using mouse clicks
 
3560
 
 
3561
void edit_tags_fixup(const char * widgetname)
 
3562
{
 
3563
   GtkWidget         *widget;
 
3564
   GdkWindow         *gdkwin;
 
3565
 
 
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
 
3569
 
 
3570
   gdkwin = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),textwin);       //  cursor for tag selection
 
3571
   gdk_window_set_cursor(gdkwin,arrowcursor);
 
3572
 
 
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)
 
3575
}
 
3576
 
 
3577
 
 
3578
//  edit tags mouse-click event function
 
3579
//  get clicked tag and add to or remove from tags_filetags
 
3580
 
 
3581
void edit_tags_mouse(GtkTextView *widget, GdkEventButton *event, const char *widgetname)
 
3582
{
 
3583
   GtkTextIter    iter;
 
3584
   int            mpx, mpy, tbx, tby, offset, cc;
 
3585
   char           *ptext, *pp1, *pp2;
 
3586
 
 
3587
   if (event->type != GDK_BUTTON_PRESS) return;
 
3588
   mpx = int(event->x);                                                    //  mouse click position
 
3589
   mpy = int(event->y);
 
3590
 
 
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
 
3594
 
 
3595
   ptext = 0;   
 
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;
 
3601
 
 
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++;
 
3606
 
 
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
 
3612
   
 
3613
   if (strEqu(widgetname,"filetags")) {
 
3614
      delete_filetag();                                                    //  remove tag from file tags
 
3615
      zdialog_stuff(zdtags,"filetags",tags_filetags);                      //  update dialog widgets
 
3616
   }
 
3617
   
 
3618
   if (strEqu(widgetname,"asstags")) {
 
3619
      add_new_filetag();                                                   //  add assigned tag to file tags
 
3620
      add_new_recentag();
 
3621
      zdialog_stuff(zdtags,"filetags",tags_filetags);
 
3622
   }
 
3623
 
 
3624
   if (strEqu(widgetname,"recentags")) {
 
3625
      add_new_filetag();                                                   //  add recent tag to file tags
 
3626
      zdialog_stuff(zdtags,"filetags",tags_filetags);
 
3627
   }
 
3628
 
 
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);
 
3633
   }
 
3634
 
 
3635
   return;
 
3636
}
 
3637
 
 
3638
 
 
3639
//  edit tags dialog event and completion functions
 
3640
 
 
3641
int edit_tags_dialog_event(zdialog *zd, const char *event)
 
3642
{
 
3643
   int      err;
 
3644
   
 
3645
   if (strEqu(event,"imdate")) {                                           //  image date revised
 
3646
      err = zdialog_fetch(zd,"imdate",tags_imdate,11);
 
3647
      if (err) return 1;
 
3648
      tags_changed++;
 
3649
   }
 
3650
   
 
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);
 
3655
         tags_changed++;
 
3656
      }
 
3657
   }
 
3658
 
 
3659
   if (strnEqu(event,"pstars",6)) {                                        //  stars revised
 
3660
      tags_stars = event[6] - '0';
 
3661
      tags_changed++;
 
3662
   }
 
3663
 
 
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
 
3667
      add_new_filetag();
 
3668
      add_new_recentag();
 
3669
      zdialog_stuff(zd,"filetags",tags_filetags);                          //  update dialog widgets
 
3670
      zdialog_stuff(zd,"asstags",tags_asstags);
 
3671
      zdialog_stuff(zd,"atag","");
 
3672
   }
 
3673
 
 
3674
   return 0;
 
3675
}
 
3676
 
 
3677
 
 
3678
int edit_tags_dialog_compl(zdialog *zd, int zstat)
 
3679
{
 
3680
   if (zstat == 1) update_filetags(image_file);                            //  done, update file EXIF tags
 
3681
   zdialog_free(zdtags);                                                   //  kill dialog
 
3682
   zdtags = null;
 
3683
   return 0;
 
3684
}
 
3685
 
 
3686
 
 
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
 
3690
 
 
3691
int add_unique_tag(const char *tag, char *taglist, int maxcc)
 
3692
{
 
3693
   char     *pp1, *pp2, *ppv, temptag1[maxtag1], temptag2[maxtag1];
 
3694
   int      atcc, cc1, cc2;
 
3695
 
 
3696
   strncpy0(temptag1,tag,maxtag1);                                         //  remove leading and trailing blanks
 
3697
   atcc = strTrim2(temptag2,temptag1);
 
3698
   if (! atcc) return 0;
 
3699
 
 
3700
   for (ppv = temptag2; *ppv; ppv++)                                       //  replace imbedded blanks with '_'
 
3701
      if (*ppv == ' ') *ppv = '_';
 
3702
   
 
3703
   if (utf8_check(temptag2)) {                                             //  check for valid utf8 encoding
 
3704
      printf("bad utf8 characters: %s \n",temptag2);
 
3705
      return 3;
 
3706
   }
 
3707
   
 
3708
   pp1 = taglist;
 
3709
   cc1 = strlen(temptag2);
 
3710
 
 
3711
   while (true)                                                            //  check if already in tag list
 
3712
   {
 
3713
      while (*pp1 == ' ') pp1++;
 
3714
      if (! *pp1) break;
 
3715
      pp2 = pp1 + 1;
 
3716
      while (*pp2 && *pp2 != ' ') pp2++;
 
3717
      cc2 = pp2 - pp1;
 
3718
      if (cc2 == cc1 && strncaseEqu(temptag2,pp1,cc1)) return 1;
 
3719
      pp1 = pp2;
 
3720
   }
 
3721
   
 
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," ");
 
3726
   return 0;
 
3727
}
 
3728
 
 
3729
 
 
3730
/**************************************************************************/
 
3731
 
 
3732
//  image file EXIF data >> tags_imdate, tags_stars, tags_filetags in memory
 
3733
 
 
3734
void load_filetags(const char *file)
 
3735
{
 
3736
   int         ii, jj, cc, err;
 
3737
   const char  *pp;
 
3738
   const char  *exifkeys[2] = { exif_date_key, exif_tags_key };
 
3739
   char        **ppv, *imagedate, *imagetags;
 
3740
 
 
3741
   *tags_filetags = *tags_imdate = 0;
 
3742
   tags_stars = 0;
 
3743
   
 
3744
   ppv = exif_get(image_file,exifkeys,2);
 
3745
   imagedate = ppv[0];
 
3746
   imagetags = ppv[1];
 
3747
 
 
3748
   if (imagedate) {                       
 
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);
 
3753
         tags_imdate[8] = 0;
 
3754
         strcpy(tags_limdate,tags_imdate);
 
3755
      }
 
3756
      zfree(imagedate);
 
3757
   }
 
3758
 
 
3759
   if (imagetags)
 
3760
   {
 
3761
      for (ii = 1; ; ii++)
 
3762
      {
 
3763
         pp = strField(imagetags,' ',ii);                                  //  assume blank delimited tags
 
3764
         if (! pp) break;
 
3765
         cc = strlen(pp);
 
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;
 
3770
         
 
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;
 
3774
            continue;
 
3775
         }
 
3776
 
 
3777
         strcpy(tags_atag,pp);                                             //  add to file tags if unique
 
3778
         add_new_filetag();
 
3779
      }
 
3780
 
 
3781
      zfree(imagetags);
 
3782
   }
 
3783
   
 
3784
   return;
 
3785
}
 
3786
 
 
3787
 
 
3788
/**************************************************************************/
 
3789
 
 
3790
//  tags_imdate, tags_stars, tags_filetags in memory >> image file EXIF data
 
3791
 
 
3792
void update_filetags(const char *file)
 
3793
{
 
3794
   const char  *exifkeys[2] = { exif_date_key, exif_tags_key };
 
3795
   const char  *exifdata[2];
 
3796
   char        imagedate[12];
 
3797
 
 
3798
   if (! tags_changed) return;
 
3799
 
 
3800
   *imagedate = 0;                                                         //  v.7.5
 
3801
 
 
3802
   if (*tags_imdate) {
 
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] = ':';
 
3809
      imagedate[10] = 0;
 
3810
      strcpy(tags_limdate,tags_imdate);
 
3811
   }
 
3812
 
 
3813
   if (tags_stars > 0) {                                                   //  add "stars=N" tag
 
3814
      sprintf(tags_atag,"stars=%d",tags_stars);
 
3815
      add_new_filetag();
 
3816
   }
 
3817
   
 
3818
   exifdata[0] = imagedate;                                                //  update file EXIF data
 
3819
   exifdata[1] = tags_filetags;
 
3820
   exif_set(file,exifkeys,exifdata,2);
 
3821
   
 
3822
   update_asstags(file);                                                   //  update assigned tags file
 
3823
   tags_changed = 0;
 
3824
   return;
 
3825
}
 
3826
 
 
3827
 
 
3828
//  add new tag to file tags, if not already and enough space.
 
3829
 
 
3830
void add_new_filetag()
 
3831
{
 
3832
   int         err;
 
3833
 
 
3834
   err = add_unique_tag(tags_atag,tags_filetags,maxtag2);
 
3835
   if (err == 2) { 
 
3836
      zmessageACK(ZTX("File tags exceed %d characters"),maxtag2);
 
3837
      return;
 
3838
   }
 
3839
   
 
3840
   tags_changed++;
 
3841
   return;
 
3842
}
 
3843
 
 
3844
 
 
3845
//  add new tag to recent tags, if not already.
 
3846
//  remove oldest to make space if needed.
 
3847
 
 
3848
void add_new_recentag()
 
3849
{
 
3850
   int         err;
 
3851
   char        *ppv, temp_recentags[maxtag5];
 
3852
 
 
3853
   if (strnEqu(tags_atag,"stars=",6)) return;                              //  omit this tag
 
3854
 
 
3855
   err = add_unique_tag(tags_atag,tags_recentags,maxtag5);                 //  add tag to recent tags
 
3856
 
 
3857
   while (err == 2)
 
3858
   {
 
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);
 
3866
   }
 
3867
 
 
3868
   zdialog_stuff(zdtags,"recentags",tags_recentags);                       //  update dialog
 
3869
   return;
 
3870
}
 
3871
 
 
3872
 
 
3873
//  delete a tag from file tags, if present
 
3874
 
 
3875
void delete_filetag()
 
3876
{
 
3877
   int         ii, ftcc, atcc;
 
3878
   char        temp_filetags[maxtag2];
 
3879
   const char  *pp;
 
3880
   
 
3881
   strncpy0(temp_filetags,tags_filetags,maxtag2);
 
3882
   *tags_filetags = 0;
 
3883
   ftcc = 0;
 
3884
   
 
3885
   for (ii = 1; ; ii++)
 
3886
   {
 
3887
      pp = strField(temp_filetags,' ',ii);
 
3888
      if (! pp) break;
 
3889
      if (strcaseEqu(pp,tags_atag)) continue;
 
3890
      atcc = strlen(pp);
 
3891
      strcpy(tags_filetags + ftcc, pp);
 
3892
      ftcc += atcc;
 
3893
      tags_filetags[ftcc] = ' ';
 
3894
      ftcc++;
 
3895
      tags_filetags[ftcc] = 0;
 
3896
   }
 
3897
 
 
3898
   tags_changed++;
 
3899
   return;
 
3900
}
 
3901
 
 
3902
 
 
3903
/**************************************************************************/
 
3904
 
 
3905
//  load assigned tags file >> tags_asstags in memory
 
3906
//  create list of all assigned tags with no duplicates
 
3907
 
 
3908
void load_asstags()
 
3909
{
 
3910
   FILE        *fid;
 
3911
   int         ntags = 0, ntcc, atcc, ii, err;
 
3912
   char        *ppv, buff[1000];
 
3913
   const char  *pp1;
 
3914
   char        *tags[maxntags];
 
3915
   
 
3916
   ntcc = 0;
 
3917
   *tags_asstags = 0;
 
3918
 
 
3919
   fid = fopen(asstagsfile,"r");
 
3920
   if (! fid) return;                                                      //  no tags
 
3921
   
 
3922
   while (true)                                                            //  read assigned tags file
 
3923
   {
 
3924
      ppv = fgets_trim(buff,999,fid);
 
3925
      if (! ppv) break;
 
3926
      if (strnNeq(buff,"tags: ",6)) continue;
 
3927
 
 
3928
      for (ii = 1; ; ii++)                                                 //  add file tags to assigned tags
 
3929
      {                                                                    //    unless already present
 
3930
         pp1 = strField(buff+6,' ',ii);
 
3931
         if (! pp1) break;
 
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;
 
3935
      }
 
3936
   }
 
3937
 
 
3938
   err = fclose(fid);
 
3939
   if (err) goto tagsfileerr;
 
3940
   
 
3941
   for (ii = 1; ; ii++)                                                    //  build sort list
 
3942
   {
 
3943
      pp1 = strField(tags_asstags,' ',ii);
 
3944
      if (! pp1) break;
 
3945
      tags[ntags] = strdupz(pp1);
 
3946
      ntags++;
 
3947
      if (ntags == maxntags) goto toomanytags;
 
3948
   }
 
3949
   
 
3950
   HeapSort(tags,ntags);                                                   //  sort alphabetically
 
3951
   
 
3952
   ntcc = 0;
 
3953
   *tags_asstags = 0;
 
3954
 
 
3955
   for (ii = 0; ii < ntags; ii++)                                          //  build sorted assigned tags list
 
3956
   {
 
3957
      atcc = strlen(tags[ii]);
 
3958
      if (ntcc + atcc + 1 > maxtag3) goto overflow;
 
3959
      strcpy(tags_asstags + ntcc,tags[ii]);
 
3960
      ntcc += atcc;
 
3961
      tags_asstags[ntcc] = ' ';
 
3962
      ntcc++;
 
3963
      zfree(tags[ii]);
 
3964
   }
 
3965
 
 
3966
   tags_asstags[ntcc] = 0;
 
3967
   
 
3968
   return;
 
3969
 
 
3970
overflow:
 
3971
   zmessageACK(ZTX("Total tags exceed %d characters"),maxtag3);
 
3972
   return;
 
3973
 
 
3974
toomanytags:
 
3975
   zmessageACK(ZTX("Too many tags: %d"),maxntags);
 
3976
   return;
 
3977
 
 
3978
tagsfileerr:
 
3979
   zmessLogACK(ZTX("Assigned tags file error: %s"),strerror(errno));
 
3980
   return;
 
3981
}
 
3982
 
 
3983
 
 
3984
/**************************************************************************/
 
3985
 
 
3986
//  update tags_asstags in memory from tags_filetags
 
3987
//  update assigned tags file (add or replace changed file and its tags)
 
3988
 
 
3989
void update_asstags(const char *file, int del)
 
3990
{
 
3991
   char           *ppv, temp_asstagsfile[1000], imagedate[12], filedate[16];
 
3992
   char           datebuff[1000], tagsbuff[1000], filebuff[1000];
 
3993
   const char     *pp1;
 
3994
   int            ii, ntcc, err;
 
3995
   FILE           *fidr, *fidw;
 
3996
   struct stat    statb;
 
3997
   struct tm      bdt;
 
3998
 
 
3999
   ntcc = strlen(tags_asstags);
 
4000
   
 
4001
   if (! del)                                                              //  unless deleted
 
4002
   {
 
4003
      for (ii = 1; ; ii++)                                                 //  add file tags to assigned tags
 
4004
      {                                                                    //    unless already present
 
4005
         pp1 = strField(tags_filetags,' ',ii);
 
4006
         if (! pp1) break;
 
4007
         if (strnEqu(pp1,"stars=",6)) continue;                            //  omit this tag
 
4008
         
 
4009
         err = add_unique_tag(pp1,tags_asstags,maxtag3);
 
4010
         if (err == 2) {
 
4011
            zmessageACK(ZTX("Total tags exceed %d characters"),maxtag3);
 
4012
            break;
 
4013
         }
 
4014
      }
 
4015
   }
 
4016
 
 
4017
   strcpy(temp_asstagsfile,asstagsfile);                                   //  temp tag file
 
4018
   strcat(temp_asstagsfile,"_temp");
 
4019
 
 
4020
   fidr = fopen(asstagsfile,"r");                                          //  read tag file
 
4021
   
 
4022
   fidw = fopen(temp_asstagsfile,"w");                                     //  write temp tag file
 
4023
   if (! fidw) goto tagserror;
 
4024
 
 
4025
   if (fidr) {   
 
4026
      while (true)                                                         //  copy assigned tags file to temp
 
4027
      {                                                                    //    file, omitting this image file
 
4028
         ppv = fgets_trim(datebuff,999,fidr);
 
4029
         if (! ppv) break;
 
4030
         if (strnNeq(datebuff,"date: ",6)) continue;
 
4031
 
 
4032
         ppv = fgets_trim(tagsbuff,999,fidr);
 
4033
         if (! ppv) break;
 
4034
         if (strnNeq(tagsbuff,"tags: ",6)) continue;
 
4035
 
 
4036
         ppv = fgets_trim(filebuff,999,fidr);
 
4037
         if (! ppv) break;
 
4038
         if (strnNeq(filebuff,"file: ",6)) continue;
 
4039
 
 
4040
         if (strEqu(filebuff+6,file)) continue;                            //  if my file, skip copy
 
4041
 
 
4042
         fprintf(fidw,"%s\n",datebuff);                                    //  copy to temp file
 
4043
         fprintf(fidw,"%s\n",tagsbuff);
 
4044
         fprintf(fidw,"%s\n\n",filebuff);
 
4045
      }
 
4046
   }
 
4047
   
 
4048
   if (! del)                                                              //  unless deleted, append 
 
4049
   {                                                                       //    revised file data to temp file
 
4050
      if (*tags_imdate) {
 
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] = ':';
 
4055
         imagedate[10] = 0;
 
4056
      }
 
4057
      else strcpy(imagedate,"null");
 
4058
 
 
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);
 
4064
 
 
4065
      err = fprintf(fidw,"date: %s  %s\n",imagedate,filedate);             //  output tag date and file date
 
4066
      
 
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
 
4069
 
 
4070
      err = fprintf(fidw,"file: %s\n\n",file);                             //  output filespec
 
4071
      if (err <= 0) goto tagserror;
 
4072
   }
 
4073
 
 
4074
   if (fidr) {
 
4075
      err = fclose(fidr);
 
4076
      if (err) goto tagserror;
 
4077
   }
 
4078
 
 
4079
   err = fclose(fidw);
 
4080
   if (err) goto tagserror;
 
4081
   
 
4082
   err = rename(temp_asstagsfile,asstagsfile);                             //  replace tag file with temp file
 
4083
   if (err) goto tagserror;
 
4084
 
 
4085
   return;
 
4086
   
 
4087
tagserror:
 
4088
   zmessLogACK(ZTX("Assigned tags file error: %s"),strerror(errno));
 
4089
   return;
 
4090
}
 
4091
 
 
4092
 
 
4093
/**************************************************************************/
 
4094
 
 
4095
//  search image tags for matching images
 
4096
 
 
4097
char     searchDateFrom[12] = "";                                          //  image search date range
 
4098
char     searchDateTo[12] = "";
 
4099
int      searchStarsFrom = 0;                                              //  image search stars range
 
4100
int      searchStarsTo = 0;
 
4101
 
 
4102
void m_search_tags(GtkWidget *, const char *)
 
4103
{
 
4104
   int search_tags_dialog_event(zdialog*, const char *event);
 
4105
   int search_tags_dialog_compl(zdialog*, int zstat);
 
4106
 
 
4107
   if (zdtags) {
 
4108
      zdialog_free(zdtags);
 
4109
      zdtags = null;
 
4110
   }
 
4111
   
 
4112
   zdtags = zdialog_new(ZTX("Search Tags"),mWin,Bsearch,Bcancel,0);
 
4113
   
 
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");
 
4117
 
 
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"));
 
4122
 
 
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
 
4127
 
 
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
 
4131
 
 
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);
 
4135
 
 
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);
 
4139
 
 
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"));
 
4144
 
 
4145
   zdialog_add_widget(zdtags,"hbox","hbsp","dialog",0,"space=1");
 
4146
 
 
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");
 
4151
 
 
4152
   zdialog_resize(zdtags,400,0);                                           //  start dialog
 
4153
   zdialog_run(zdtags,search_tags_dialog_event,search_tags_dialog_compl);
 
4154
 
 
4155
   edit_tags_fixup("asstags2");                                            //  setup tag selection via mouse
 
4156
   
 
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);
 
4165
   
 
4166
   return;
 
4167
}
 
4168
 
 
4169
  
 
4170
int search_tags_dialog_event(zdialog *zd, const char *event)               //  dialog event function
 
4171
{
 
4172
   if (strEqu(event,"fileclear"))
 
4173
      zdialog_stuff(zd,"searchfile","");
 
4174
 
 
4175
   if (strEqu(event,"tagsclear")) 
 
4176
      zdialog_stuff(zd,"searchtags","");
 
4177
 
 
4178
   return 0;
 
4179
}
 
4180
 
 
4181
 
 
4182
int search_tags_dialog_compl(zdialog *zd, int zstat)                       //  dialog completion function
 
4183
{
 
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;
 
4190
   FILE        *fidr, *fidw;
 
4191
   struct stat statbuf;
 
4192
 
 
4193
   if (zstat == 1) {
 
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
 
4201
   }
 
4202
   
 
4203
   zdialog_free(zdtags);                                                   //  kill dialog
 
4204
   zdtags = null;
 
4205
   if (zstat != 1) return 0;                                               //  cancelled
 
4206
 
 
4207
   strcpy(date1,"0000");                                                   //  defaults for missing dates  v.6.6
 
4208
   strcpy(date2,"9999");                                                   //  (year 0000 to year 9999)
 
4209
   date1cc = date2cc = 4;
 
4210
   Fdates = 0;
 
4211
 
 
4212
   if (*searchDateFrom) {                                                  //  date from is given
 
4213
      Fdates++;
 
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);
 
4219
   }
 
4220
   if (*searchDateTo) {                                                    //  date to is given
 
4221
      Fdates++;
 
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);
 
4227
   }
 
4228
 
 
4229
   Fstars = 0;
 
4230
   if (searchStarsFrom || searchStarsTo) Fstars = 1;                       //  stars given
 
4231
   
 
4232
   Ffiles = 0;
 
4233
   if (! blank_null(tags_searchfile)) Ffiles = 1;                          //  search file* given     v.6.6
 
4234
 
 
4235
   Ftags = 0;
 
4236
   if (! blank_null(tags_searchtags)) Ftags = 1;                           //  search tags given
 
4237
 
 
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
 
4240
      Ftags = 1;
 
4241
   }
 
4242
   
 
4243
   if (Ffiles) strToLower(tags_searchfile);                                //  v.6.6
 
4244
   if (Ftags) strToLower(tags_searchtags);
 
4245
   
 
4246
   snprintf(resultsfile,199,"%s/search_results",get_zuserdir());
 
4247
   fidw = fopen(resultsfile,"w");                                          //  search results output file
 
4248
   if (! fidw) goto writerror;
 
4249
 
 
4250
   fidr = fopen(asstagsfile,"r");                                          //  read assigned tags file
 
4251
   if (! fidr) goto noasstags;
 
4252
   
 
4253
   nfiles = 0;                                                             //  count matching files found
 
4254
 
 
4255
   while (true)
 
4256
   {
 
4257
      ppv = fgets_trim(rbuff,999,fidr);                                    //  next assigned tags record
 
4258
      if (! ppv) break;
 
4259
      if (! strnEqu(ppv,"date: ",6)) continue;                             //  date: yyyy:mm:dd 
 
4260
 
 
4261
      if (Fdates) {      
 
4262
         if (strncmp(ppv+6,date1,date1cc) < 0) continue;                   //  check search date range
 
4263
         if (strncmp(ppv+6,date2,date2cc) > 0) continue;
 
4264
      }
 
4265
 
 
4266
      ppv = fgets_trim(rbuff,999,fidr);                                    //  next record
 
4267
      if (! ppv) break;
 
4268
      if (! strnEqu(ppv,"tags: ",6)) continue;                             //  tags: xxxx xxxxx ...
 
4269
 
 
4270
      if (Ftags)
 
4271
      {                                                                    //  tag search
 
4272
         tags = ppv + 6;
 
4273
         strToLower(tags);                                                 //  v.6.6
 
4274
 
 
4275
         nmatch = nfail = 0;
 
4276
 
 
4277
         for (iis = 1; ; iis++)
 
4278
         {
 
4279
            pps = strField(tags_searchtags,' ',iis);                       //  step thru search tags
 
4280
            if (! pps) break;
 
4281
 
 
4282
            for (iif = 1; ; iif++)                                         //  step thru file tags
 
4283
            {
 
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
 
4287
            }
 
4288
         }
 
4289
 
 
4290
         if (nmatch == 0) continue;                                        //  no match to any tag
 
4291
         if (Fmall && nfail) continue;                                     //  no match to all tags
 
4292
      }
 
4293
      
 
4294
      if (Fstars)
 
4295
      {                                                                    //  stars search
 
4296
         nfail = 0;
 
4297
 
 
4298
         for (iif = 1; ; iif++)                                            //  step thru file tags
 
4299
         {
 
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++;
 
4306
            break;
 
4307
         }
 
4308
 
 
4309
         if (nfail) continue;
 
4310
      }
 
4311
 
 
4312
      ppv = fgets_trim(rbuff,999,fidr,1);                                  //  next record
 
4313
      if (! ppv) break;
 
4314
      if (! strnEqu(ppv,"file: ",6)) continue;                             //  file: /dirks.../file.jpg
 
4315
      file = ppv+6;
 
4316
      strToLower(lcfile,file);                                             //  v.6.6
 
4317
      
 
4318
      if (Ffiles)                                                          //  test for path*/file* match
 
4319
      {                                                                    //  v.6.6
 
4320
         nmatch = 0;
 
4321
 
 
4322
         for (iis = 1; ; iis++)
 
4323
         {
 
4324
            pps = strField(tags_searchfile,' ',iis);                       //  step thru search files
 
4325
            if (! pps) break;
 
4326
            if (MatchWild(pps,lcfile) == 0) { nmatch++; break; }
 
4327
         }
 
4328
         
 
4329
         if (nmatch == 0) continue;
 
4330
      }
 
4331
 
 
4332
      err=stat(file,&statbuf);                                             //  check file exists
 
4333
      if (err) continue;
 
4334
      if (! S_ISREG(statbuf.st_mode)) continue;
 
4335
 
 
4336
      fprintf(fidw,"%s\n",file);                                           //  write matching file
 
4337
      nfiles++;
 
4338
   }
 
4339
 
 
4340
   fclose(fidr);
 
4341
   
 
4342
   err = fclose(fidw);
 
4343
   if (err) goto writerror;
 
4344
   
 
4345
   if (! nfiles) {
 
4346
      zmessageACK(ZTX("No matching images found"));
 
4347
      return 0;
 
4348
   }
 
4349
   
 
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
 
4355
 
 
4356
   return 0;
 
4357
 
 
4358
noasstags:
 
4359
   zmessageACK(ZTX("No assigned tags index file"));
 
4360
   return 0;
 
4361
 
 
4362
writerror:
 
4363
   zmessLogACK(ZTX("Search results file error %s"),strerror(errno));
 
4364
   return 0;
 
4365
}
 
4366
 
 
4367
 
 
4368
/**************************************************************************/
 
4369
 
 
4370
//  list available EXIF data to popup window
 
4371
 
 
4372
void m_exif_list(GtkWidget *, const char *menu)
 
4373
{
 
4374
   char        command[1000];
 
4375
   const char  *basic = "-common -focallengthin35mmformat";
 
4376
 
 
4377
   if (! image_file) return;
 
4378
 
 
4379
   if (! Fexiftool) {                                                      //  exiftool is required
 
4380
      zmessageACK(Bexiftoolmissing);
 
4381
      return;
 
4382
   }
 
4383
   
 
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);
 
4387
   }
 
4388
 
 
4389
   if (strEqu(menu,ZTX("All EXIF data"))) {
 
4390
      snprintf(command,999,"exiftool -e \"%s\" ",image_file);
 
4391
      popup_command(command,500,500);
 
4392
   }
 
4393
   
 
4394
   return;
 
4395
}
 
4396
 
 
4397
 
 
4398
/**************************************************************************/
 
4399
 
 
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
 
4405
//  EXIF command: 
 
4406
//       exiftool -exif:keyname1 -exif:keyname2 ... "file"
 
4407
//  command output: 
 
4408
//       keyname1: keyvalue1
 
4409
//       keyname2: keyvalue2
 
4410
//       ...
 
4411
 
 
4412
char ** exif_get(cchar *file, cchar **keys, int nkeys)
 
4413
{
 
4414
   char           command[1000], *pp;
 
4415
   static char    *rettext[10];
 
4416
   int            contx = 0, err, ii;
 
4417
   uint           cc;
 
4418
   
 
4419
   if (nkeys < 1 || nkeys > 9) appcrash("exif_get nkeys: %d",nkeys);
 
4420
 
 
4421
   strcpy(command,"exiftool -m -q -S -fast");
 
4422
   
 
4423
   for (ii = 0; ii < nkeys; ii++)
 
4424
   {
 
4425
      rettext[ii] = null;
 
4426
      strncatv(command,999," -exif:",keys[ii],null);
 
4427
   }
 
4428
 
 
4429
   strncatv(command,999," \"",file,"\"",null);
 
4430
 
 
4431
   while (true)
 
4432
   {
 
4433
      pp = command_output(contx,command);
 
4434
      if (! pp) break;
 
4435
 
 
4436
      for (ii = 0; ii < nkeys; ii++)
 
4437
      {
 
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
 
4441
      }
 
4442
 
 
4443
      zfree(pp);
 
4444
   }
 
4445
   
 
4446
   err = command_status(contx);
 
4447
   if (err) printf("exif_get failed \n");                                  //  v.6.9
 
4448
 
 
4449
   return rettext;
 
4450
}
 
4451
 
 
4452
 
 
4453
/**************************************************************************/
 
4454
 
 
4455
//  create or change EXIF metadata for given image file and key(s)
 
4456
//  up to 9 keys may be processed
 
4457
//  EXIF command: 
 
4458
//    exiftool -overwrite_original -exif:keyname="keyvalue" ... "file"
 
4459
 
 
4460
int exif_set(cchar *file, cchar **keys, cchar **text, int nkeys)
 
4461
{
 
4462
   char     command[1000];
 
4463
   int      ii, err;
 
4464
   
 
4465
   if (nkeys < 1 || nkeys > 9) appcrash("exif_set nkeys: %d",nkeys);
 
4466
   
 
4467
   strcpy(command,"exiftool -m -q -overwrite_original");
 
4468
   
 
4469
   for (ii = 0; ii < nkeys; ii++)
 
4470
      strncatv(command,999," -exif:",keys[ii],"=\"",text[ii],"\"",null);
 
4471
   strncatv(command,999," \"",file,"\"",null);
 
4472
 
 
4473
   err = system(command);
 
4474
   if (err) printf(" exif_set: %s \n",wstrerror(err));
 
4475
   return err;
 
4476
}
 
4477
 
 
4478
 
 
4479
/**************************************************************************/
 
4480
 
 
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
 
4483
//  EXIF command:
 
4484
//    exiftool -tagsfromfile "file1" -exif:keyname="keyvalue" ... "file2"
 
4485
 
 
4486
int exif_copy(cchar *file1, cchar *file2, cchar **keys, cchar **text, int nkeys)
 
4487
{
 
4488
   char     command[2000];
 
4489
   int      ii, err;
 
4490
   
 
4491
   strcpy(command,"exiftool -m -q -overwrite_original -tagsfromfile");
 
4492
   strncatv(command,1999," \"",file1,"\"",null);
 
4493
 
 
4494
   for (ii = 0; ii < nkeys; ii++)
 
4495
      strncatv(command,1999," -exif:",keys[ii],"=\"",text[ii],"\"",null);
 
4496
 
 
4497
   strncatv(command,1999," \"",file2,"\"",null);
 
4498
 
 
4499
   err = system(command);
 
4500
   if (err) printf(" exif_copy: %s \n",wstrerror(err));
 
4501
   return err;
 
4502
}
 
4503
 
 
4504
 
 
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
***************************************************************************/
 
4510
 
 
4511
//  data defining a selected area (either mouse or color select)
 
4512
 
 
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
 
4521
 
 
4522
 
 
4523
/**************************************************************************/
 
4524
 
 
4525
//  outline an image area using the mouse
 
4526
//  may run parallel with edit functions
 
4527
 
 
4528
int   sam_dialog_event(zdialog*, const char *event);                       //  dialog event and completion funcs
 
4529
int   sam_dialog_compl(zdialog*, int zstat);
 
4530
 
 
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();
 
4537
 
 
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);
 
4545
 
 
4546
int   sam_npg, sam_maxpg = 1000, sam_pgx[1000], sam_pgy[1000];             //  polygon nodes
 
4547
 
 
4548
 
 
4549
void m_select_mouse(GtkWidget *, const char *)                             //  menu function
 
4550
{
 
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");
 
4554
 
 
4555
   if (! image_file) return;                                               //  no image
 
4556
   if (zdsela) return;                                                     //  already active
 
4557
   
 
4558
   if (Fpreview) edit_fullsize();                                          //  use full-size pixmaps
 
4559
 
 
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");
 
4572
   
 
4573
   zdialog_run(zdsela,sam_dialog_event,sam_dialog_compl);                  //  run dialog - parallel
 
4574
 
 
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
 
4578
   sa_blend = 0;
 
4579
   sa_calced = 0;                                                          //  v.8.6.1
 
4580
   m_select_show(0,0);                                                     //  show area
 
4581
   return;
 
4582
}
 
4583
 
 
4584
 
 
4585
int sam_dialog_compl(zdialog *zd, int zstat)
 
4586
{
 
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
 
4590
   Mcapture = 0;
 
4591
   zdialog_free(zdsela);                                                   //  kill dialog
 
4592
   zdsela = null;
 
4593
   return 0;
 
4594
}
 
4595
 
 
4596
 
 
4597
int sam_dialog_event(zdialog *zd, const char *event)
 
4598
{
 
4599
   if (strEqu(event,"start"))                                              //  start or resume
 
4600
   {
 
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
 
4609
   }
 
4610
 
 
4611
   if (strEqu(event,"show-hide")) {                                        //  toggle show/hide
 
4612
      if (! Fshowarea) {
 
4613
         Fshowarea++;
 
4614
         zdialog_stuff(zdsela,"show-hide",Bhide);
 
4615
      }
 
4616
      else {
 
4617
         Fshowarea = 0;
 
4618
         zdialog_stuff(zdsela,"show-hide",Bshow);
 
4619
      }
 
4620
   }
 
4621
   
 
4622
   if (strEqu(event,"susp-resm")) {                                        //  toggle suspend/resume
 
4623
      if (sa_stat == 1) {
 
4624
         gdk_window_set_cursor(drWin->window,0);                           //  restore normal cursor
 
4625
         mouseCBfunc = 0;                                                  //  disconnect mouse function
 
4626
         Mcapture = 0;
 
4627
         zdialog_stuff(zdsela,"susp-resm",Bresume);
 
4628
         sa_stat = 2;
 
4629
      }
 
4630
      else if (sa_stat == 2) {
 
4631
         gdk_window_set_cursor(drWin->window,dragcursor);                  //  set drag cursor
 
4632
         mouseCBfunc = sam_mousefunc;                                      //  connect mouse function
 
4633
         Mcapture++;
 
4634
         zdialog_stuff(zdsela,"susp-resm",Bsuspend);
 
4635
         sa_stat = 1;
 
4636
      }
 
4637
   }
 
4638
 
 
4639
   if (strEqu(event,"finish"))                                             //  finish area
 
4640
   {
 
4641
      mouseCBfunc = 0;                                                     //  disconnect mouse function
 
4642
      Mcapture = 0;
 
4643
      gdk_window_set_cursor(drWin->window,0);                              //  restore normal cursor
 
4644
      Fshowarea++;
 
4645
      zdialog_stuff(zdsela,"show-hide",Bhide);
 
4646
      sam_select_finish();
 
4647
   }
 
4648
 
 
4649
   if (strEqu(event,"delete")) {                                           //  delete area
 
4650
      mouseCBfunc = 0;                                                     //  disconnect mouse function
 
4651
      Mcapture = 0;
 
4652
      gdk_window_set_cursor(drWin->window,0);                              //  restore normal cursor
 
4653
      select_delete();
 
4654
   }
 
4655
   
 
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
 
4659
         else {
 
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
 
4663
         }
 
4664
      }
 
4665
   }
 
4666
 
 
4667
   mwpaint2();                                                             //  update window
 
4668
   return 0;
 
4669
}
 
4670
 
 
4671
 
 
4672
//  show select area polygon outline
 
4673
 
 
4674
void sam_select_show()
 
4675
{
 
4676
   int      ii, jj;
 
4677
 
 
4678
   for (ii = 0; ii < sam_npg-1; ii++)
 
4679
   {
 
4680
      jj = ii + 1;
 
4681
      draw_line(sam_pgx[ii],sam_pgy[ii],sam_pgx[jj],sam_pgy[jj]);
 
4682
   }
 
4683
 
 
4684
   return;
 
4685
}
 
4686
 
 
4687
 
 
4688
//  finish select area - map pixels enclosed by polygon into sa_area[]
 
4689
 
 
4690
void sam_select_finish()
 
4691
{
 
4692
   int         ii, inside, px, py, cc;
 
4693
   int         minpgx, minpgy, maxpgx, maxpgy;
 
4694
   int         Nrect, Ndone, npix;
 
4695
   
 
4696
   if (sa_Npixel) return;                                                  //  already finished
 
4697
   if (sam_npg < 3) return;                                                //  not enough points
 
4698
 
 
4699
   gdk_window_set_cursor(drWin->window,busycursor);                        //  set function busy cursor  v.8.4
 
4700
   zmainloop();
 
4701
 
 
4702
   ii = sam_npg - 1;
 
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];
 
4707
      sam_npg++;
 
4708
   }
 
4709
 
 
4710
   minpgx = maxpgx = sam_pgx[0];
 
4711
   minpgy = maxpgy = sam_pgy[0];
 
4712
 
 
4713
   for (ii = 0; ii < sam_npg; ii++)                                        //  get rectangle enclosing polygon
 
4714
   {
 
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];
 
4719
   }
 
4720
   
 
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);
 
4725
 
 
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);
 
4730
 
 
4731
   Ndone = npix = 0;
 
4732
 
 
4733
   for (py = minpgy; py < maxpgy; py++)                                    //  loop all pixels in rectangle
 
4734
   for (px = minpgx; px < maxpgx; px++)
 
4735
   {
 
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
 
4739
      sa_pixisin[ii] = 1;
 
4740
      sa_pixel[npix].px = px;
 
4741
      sa_pixel[npix].py = py;                                              //  save pixel
 
4742
      sa_pixel[npix].dist = 0;
 
4743
      npix++;
 
4744
   }
 
4745
 
 
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
 
4749
 
 
4750
   mutex_unlock(&pixmaps_lock);
 
4751
   gdk_window_set_cursor(drWin->window,0);                                 //  restore normal cursor
 
4752
   return;
 
4753
}
 
4754
 
 
4755
 
 
4756
//  determine if pixel at (px,py) is inside the area selection polygon
 
4757
 
 
4758
int sam_select_within(int px, int py)
 
4759
{
 
4760
   int sam_select_within2(int px, int py, int nth);
 
4761
 
 
4762
   int      ii, inside = 0;
 
4763
 
 
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;
 
4767
 
 
4768
   return inside;
 
4769
}
 
4770
 
 
4771
 
 
4772
int sam_select_within2(int px, int py, int nth)                            //  determine if point (px,py) is
 
4773
{                                                                          //    above or below nth polygon face
 
4774
   int      x1, y1, x2, y2;
 
4775
   double   yp;
 
4776
   
 
4777
   x1 = sam_pgx[nth];                                                      //  line segment of nth face
 
4778
   y1 = sam_pgy[nth];
 
4779
   x2 = sam_pgx[nth+1];
 
4780
   y2 = sam_pgy[nth+1];
 
4781
   
 
4782
   if (x1 == x2) return 0;                                                 //  line is vertical
 
4783
 
 
4784
   if (x1 < x2) {
 
4785
      if (px <= x1) return 0;                                              //  point is left or right of line
 
4786
      if (px > x2) return 0;
 
4787
   }
 
4788
   else { 
 
4789
      if (px <= x2) return 0;
 
4790
      if (px > x1) return 0;
 
4791
   }
 
4792
 
 
4793
   if (py <= y1 && py <= y2) return 1;                                     //  point is above or below both ends
 
4794
   if (py >= y1 && py >= y2) return 2;
 
4795
   
 
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
 
4800
}
 
4801
 
 
4802
 
 
4803
//  calculate distance of all pixels in area to nearest edge
 
4804
 
 
4805
void sam_select_edgecalc()
 
4806
{
 
4807
   int      ii, kk, px, py, dd, mindd;
 
4808
   
 
4809
   sam_select_finish();                                                    //  finish if needed
 
4810
   if (! sa_Npixel) return;
 
4811
   
 
4812
   for (ii = 0; ii < sa_Npixel; ii++)
 
4813
   {
 
4814
      edit_progress(ii,sa_Npixel);                                         //  track progress
 
4815
      
 
4816
      px = sa_pixel[ii].px;
 
4817
      py = sa_pixel[ii].py;
 
4818
      mindd = sam_select_edgecalc2(px,py,0);
 
4819
      
 
4820
      for (kk = 1; kk < sam_npg-1; kk++)
 
4821
      {
 
4822
         dd = sam_select_edgecalc2(px,py,kk);                              //  distance^2 to Nth polygon face
 
4823
         if (dd < mindd) mindd = dd;                                       //  remember smallest distance^2
 
4824
      }
 
4825
      
 
4826
      sa_pixel[ii].dist = int(sqrt(mindd));                                //  use smallest distance
 
4827
   }
 
4828
 
 
4829
   edit_progress(0,0);
 
4830
   sa_calced = 1;                                                          //  v.8.6.1
 
4831
   return;
 
4832
}
 
4833
 
 
4834
 
 
4835
int sam_select_edgecalc2(int px, int py, int nth)                          //  get distance^2 from point (px,py)
 
4836
{                                                                          //    to nth polygon face
 
4837
   int      x1, y1, x2, y2;
 
4838
   int      a, b, c, d;
 
4839
   int      h1, h2, ls, temp;
 
4840
   int      dist2;
 
4841
   int      huge = 1000000;                                                //  distance = 1000
 
4842
 
 
4843
   x1 = sam_pgx[nth];                                                      //  line segment of nth face
 
4844
   y1 = sam_pgy[nth];
 
4845
   x2 = sam_pgx[nth+1];
 
4846
   y2 = sam_pgy[nth+1];
 
4847
   
 
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;
 
4852
   
 
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)
 
4858
   
 
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
 
4863
   
 
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");
 
4869
      exit(0);
 
4870
   }
 
4871
 
 
4872
   if (h2 - dist2 < ls) return dist2;                                      //  intersect in segment, use dist2
 
4873
   else return h1;                                                         //  distance ^2 to nearest end point
 
4874
}
 
4875
 
 
4876
 
 
4877
//  invert selected area
 
4878
 
 
4879
void sam_select_invert()                                                   //  v.8.5
 
4880
{
 
4881
   int      ii, kk, npix, cc, px, py;
 
4882
   
 
4883
   sam_select_finish();                                                    //  finish if needed
 
4884
   if (! sa_Npixel) return;
 
4885
   
 
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);
 
4890
   
 
4891
   for (ii = kk = 0; ii < Fww * Fhh; ii++)                                 //  reverse member pixels
 
4892
   {
 
4893
      if (sa_pixisin[ii]) sa_pixisin[ii] = 0;
 
4894
      else {
 
4895
         sa_pixisin[ii] = 1;
 
4896
         py = ii / Fww;
 
4897
         px = ii - Fww * py;
 
4898
         sa_pixel[kk].px = px;
 
4899
         sa_pixel[kk].py = py;
 
4900
         sa_pixel[kk].dist = 0;
 
4901
         kk++;
 
4902
      }
 
4903
   }
 
4904
 
 
4905
   if (npix != kk) zappcrash("invert area bug: %d %d",kk,npix);
 
4906
   sa_Npixel = npix;
 
4907
   Fshowarea++;
 
4908
   sa_calced = 0;                                                          //  no edge calculation   v.8.6.1
 
4909
   return;
 
4910
}
 
4911
 
 
4912
 
 
4913
//  select area mouse function - add polygon points, draw lines
 
4914
//  overhauled v.8.3 - add or move points anywhere around the polygon
 
4915
 
 
4916
void sam_mousefunc()
 
4917
{
 
4918
   static int     node, dist, newdrag;
 
4919
   static int     mdx0, mdy0;
 
4920
   int            thresh = 3.0 / Mscale;                                   //  capture node mouse threshold
 
4921
 
 
4922
   if (LMclick)                                                            //  left mouse click
 
4923
   {
 
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
 
4927
   }
 
4928
   
 
4929
   else if (RMclick)                                                       //  right mouse click
 
4930
   {
 
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
 
4935
   }
 
4936
   
 
4937
   else if (Mxdrag || Mydrag)                                              //  mouse drag underway
 
4938
   {
 
4939
      if (Mxdown != mdx0 || Mydown != mdy0) {                              //  new drag start position?
 
4940
         newdrag = 1;                                                      //  yes, new drag start
 
4941
         mdx0 = Mxdown;                                                    //  save start position
 
4942
         mdy0 = Mydown;
 
4943
      }
 
4944
 
 
4945
      if (Mxdrag == mdx0 && Mydrag == mdy0) return;                        //  no progress
 
4946
      if (sam_duplicate_node(Mxdrag,Mydrag)) return;                       //  defer recognition
 
4947
      
 
4948
      if (newdrag) {                                                       //  new drag start
 
4949
         newdrag = 0;
 
4950
         if (sam_npg < 2) sam_append_node(mdx0,mdy0,node);                 //  < 2 nodes, add next node
 
4951
         else {
 
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
 
4955
         }
 
4956
      }
 
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
 
4960
      }
 
4961
   }
 
4962
 
 
4963
   return;
 
4964
}
 
4965
 
 
4966
 
 
4967
//  find nearest node to mouse position, return node and distance
 
4968
 
 
4969
void sam_nearest_node(int mx, int my, int &node, int &dist)
 
4970
{
 
4971
   int      ii, dx, dy, tdist;
 
4972
   int      mindist = 99999999, minii = -1;
 
4973
   
 
4974
   if (sam_npg == 0) zappcrash("sam_nearest_node() no nodes");
 
4975
 
 
4976
   for (ii = 0; ii < sam_npg; ii++)
 
4977
   {
 
4978
      dx = sam_pgx[ii] - mx;
 
4979
      dy = sam_pgy[ii] - my;
 
4980
      tdist = dx*dx + dy*dy;
 
4981
      if (tdist < mindist) {
 
4982
         mindist = tdist;
 
4983
         minii = ii;
 
4984
      }
 
4985
   }
 
4986
   
 
4987
   node = minii;
 
4988
   dist = int(sqrt(mindist) + 0.5);
 
4989
   return;
 
4990
}
 
4991
 
 
4992
 
 
4993
//  append new last node
 
4994
 
 
4995
void sam_append_node(int mx, int my, int &node)
 
4996
{
 
4997
   int      ii, jj;
 
4998
 
 
4999
   if (sam_npg > sam_maxpg-2) {
 
5000
      zmessageACK(ZTX("Too many points"));
 
5001
      node = sam_npg - 1;
 
5002
      return;
 
5003
   }
 
5004
   
 
5005
   if (sam_npg == 0) {                                                     //  new node is 1st node
 
5006
      sam_pgx[0] = mx;
 
5007
      sam_pgy[0] = my;
 
5008
      sam_npg = 1;
 
5009
      node = 0;
 
5010
      return;
 
5011
   }
 
5012
   
 
5013
   sam_npg++;                                                              //  add new last node   
 
5014
   ii = sam_npg - 1;
 
5015
   jj = ii - 1;
 
5016
   sam_pgx[ii] = mx;
 
5017
   sam_pgy[ii] = my;
 
5018
   draw_line(sam_pgx[jj],sam_pgy[jj],sam_pgx[ii],sam_pgy[ii]);
 
5019
   node = ii;
 
5020
   return;
 
5021
}
 
5022
 
 
5023
 
 
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
 
5028
 
 
5029
void sam_insert_node(int mx, int my, int &node)
 
5030
{
 
5031
   int      ii, jj, kk, dist, mindist = 999999999;
 
5032
   int      thresh = 5.0 / Mscale;                                         //  insert new node mouse threshold
 
5033
 
 
5034
   if (sam_npg < 2) zappcrash("sam_insert_node() < 2 nodes");
 
5035
 
 
5036
   if (sam_npg > sam_maxpg-2) {
 
5037
      zmessageACK(ZTX("Too many points"));
 
5038
      return;
 
5039
   }
 
5040
  
 
5041
   for (ii = 0; ii < sam_npg-1; ii++)
 
5042
   {
 
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
 
5047
      }
 
5048
   }
 
5049
 
 
5050
   node += 1;                                                              //  new node number
 
5051
 
 
5052
   mindist = int(sqrt(mindist) + 0.5);
 
5053
   if (mindist > thresh) {                                                 //  too far
 
5054
      sam_append_node(mx,my,node);                                         //  append new last node
 
5055
      return;
 
5056
   }
 
5057
   
 
5058
   for (ii = sam_npg; ii > node; ii--)                                     //  make hole for inserted node
 
5059
   {
 
5060
      sam_pgx[ii] = sam_pgx[ii-1];
 
5061
      sam_pgy[ii] = sam_pgy[ii-1];
 
5062
   }
 
5063
   
 
5064
   sam_pgx[node] = mx;                                                     //  insert node
 
5065
   sam_pgy[node] = my;
 
5066
   sam_npg++;
 
5067
 
 
5068
   ii = node - 1;                                                          //  prior node
 
5069
   jj = node;
 
5070
   kk = node + 1;                                                          //  next node
 
5071
   
 
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
 
5075
 
 
5076
   return;
 
5077
}
 
5078
 
 
5079
 
 
5080
//  move given node to mouse position
 
5081
 
 
5082
void sam_move_node(int mx, int my, int node)
 
5083
{
 
5084
   int      ii, jj, kk;
 
5085
   
 
5086
   ii = node - 1;
 
5087
   jj = node;
 
5088
   kk = node + 1;
 
5089
   
 
5090
   if (sam_npg < 2) {                                                      //  only one node exists
 
5091
      sam_pgx[jj] = mx;
 
5092
      sam_pgy[jj] = my;
 
5093
      return;
 
5094
   }
 
5095
   
 
5096
   if (node == 0) {                                                        //  move node 0
 
5097
      erase_line(sam_pgx[jj],sam_pgy[jj],sam_pgx[kk],sam_pgy[kk]);
 
5098
      sam_pgx[jj] = mx;
 
5099
      sam_pgy[jj] = my;
 
5100
      draw_line(sam_pgx[jj],sam_pgy[jj],sam_pgx[kk],sam_pgy[kk]);
 
5101
      return;
 
5102
   }
 
5103
 
 
5104
   if (node == sam_npg-1) {                                                //  move last node
 
5105
      erase_line(sam_pgx[ii],sam_pgy[ii],sam_pgx[jj],sam_pgy[jj]);
 
5106
      sam_pgx[jj] = mx;
 
5107
      sam_pgy[jj] = my;
 
5108
      draw_line(sam_pgx[ii],sam_pgy[ii],sam_pgx[jj],sam_pgy[jj]);
 
5109
      return;
 
5110
   }
 
5111
   
 
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]);
 
5114
   sam_pgx[jj] = mx;
 
5115
   sam_pgy[jj] = my;
 
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]);
 
5118
   return;
 
5119
}
 
5120
 
 
5121
 
 
5122
//  remove given node
 
5123
 
 
5124
void sam_remove_node(int node)
 
5125
{
 
5126
   int      ii, jj, kk;
 
5127
   
 
5128
   if (node > sam_npg-1) zappcrash("sam_remove_node() invalid node");
 
5129
 
 
5130
   ii = node - 1;
 
5131
   jj = node;
 
5132
   kk = node + 1;
 
5133
   
 
5134
   if (ii >= 0)                                                            //  erase line from predecessor
 
5135
      erase_line(sam_pgx[ii],sam_pgy[ii],sam_pgx[jj],sam_pgy[jj]);
 
5136
   
 
5137
   if (kk < sam_npg)                                                       //  erase line to successor
 
5138
      erase_line(sam_pgx[jj],sam_pgy[jj],sam_pgx[kk],sam_pgy[kk]);
 
5139
 
 
5140
   for (int ii = node; ii < sam_npg-1; ii++)                               //  remove node
 
5141
   {
 
5142
      sam_pgx[ii] = sam_pgx[ii+1];
 
5143
      sam_pgy[ii] = sam_pgy[ii+1];
 
5144
   }
 
5145
   sam_npg--;
 
5146
   if (sam_npg == 1) sam_npg = 0;                                          //  if one left, remove it
 
5147
 
 
5148
   ii = node - 1;
 
5149
   jj = node;
 
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
 
5152
 
 
5153
   return;
 
5154
}
 
5155
 
 
5156
 
 
5157
//  detect if proposed node is a duplicate of an existing node
 
5158
 
 
5159
int sam_duplicate_node(int mx, int my)
 
5160
{
 
5161
   for (int ii = 0; ii < sam_npg; ii++)
 
5162
      if (mx == sam_pgx[ii] && my == sam_pgy[ii]) return 1;
 
5163
 
 
5164
   return 0;
 
5165
}
 
5166
 
 
5167
 
 
5168
/**************************************************************************/
 
5169
 
 
5170
//  select an image area by clicking and mapping colors                    //  new  v.8.5
 
5171
//  may run parallel with edit functions
 
5172
 
 
5173
int   sac_dialog_event(zdialog*, const char *event);                       //  dialog event and completion funcs
 
5174
int   sac_dialog_compl(zdialog*, int zstat);
 
5175
 
 
5176
void  sac_select_show();
 
5177
void  sac_select_finish();
 
5178
void  sac_select_edgecalc();
 
5179
void  sac_select_invert();
 
5180
 
 
5181
void  sac_mousefunc();
 
5182
void  sac_select_pixels();
 
5183
void  sac_unselect_pixels();
 
5184
 
 
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)
 
5189
 
 
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
 
5193
 
 
5194
int         sac_stackii[1000000];                                          //  pixel search stack
 
5195
char        sac_stackdir[1000000];                                         //  1M pixel limit
 
5196
int         sac_maxstack = 1000000;
 
5197
int         sac_Nstack = 0;
 
5198
 
 
5199
int         sac_seldist_busy = 0;                                          //  thread controls for 
 
5200
int         sac_seldist_kill = 0;                                          //    sac_select_edgecalc()
 
5201
int         sac_seldist_pixdone = 0;
 
5202
 
 
5203
 
 
5204
void m_select_color(GtkWidget *, const char *)                             //  menu function
 
5205
{
 
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.");
 
5210
 
 
5211
   if (! image_file) return;                                               //  no image
 
5212
   if (zdsela) return;                                                     //  already active
 
5213
 
 
5214
   if (Fpreview) edit_fullsize();                                          //  use full-size pixmaps
 
5215
   
 
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;
 
5221
      update_statusbar();
 
5222
   }
 
5223
 
 
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");
 
5238
   
 
5239
   zdialog_run(zdsela,sac_dialog_event,sac_dialog_compl);                  //  run dialog - parallel
 
5240
   
 
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
 
5244
   sa_blend = 0;
 
5245
   sa_calced = 0;                                                          //  v.8.6.1
 
5246
   m_select_show(0,0);                                                     //  show area
 
5247
   return;
 
5248
}
 
5249
 
 
5250
 
 
5251
int sac_dialog_compl(zdialog *zd, int zstat)
 
5252
{
 
5253
   if (zstat != 1) {                                                       //  kill, cancel
 
5254
      if (sac_seldist_busy) sac_seldist_kill++;
 
5255
      else m_select_delete(0,0);
 
5256
   }
 
5257
   gdk_window_set_cursor(drWin->window,0);                                 //  restore normal cursor
 
5258
   mouseCBfunc = 0;                                                        //  disconnect mouse function
 
5259
   Mcapture = 0;
 
5260
   zdialog_free(zdsela);                                                   //  kill dialog
 
5261
   zdsela = null;
 
5262
   return 0;
 
5263
}
 
5264
 
 
5265
 
 
5266
int sac_dialog_event(zdialog *zd, const char *event)
 
5267
{
 
5268
   if (strEqu(event,"start"))                                              //  start or resume
 
5269
   {
 
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
 
5277
      if (! sac_pixseq) {
 
5278
         int cc = Fww * Fhh;   
 
5279
         sac_pixseq = (int16 *) zmalloc(cc*2);                             //  maps pixels to selection sequence
 
5280
         memset(sac_pixseq,0,cc*2);
 
5281
         sac_currseq = 0;
 
5282
      }
 
5283
      sa_stat = 1;                                                         //  edit is active
 
5284
   }
 
5285
   
 
5286
   if (strEqu(event,"show-hide")) {                                        //  toggle show/hide
 
5287
      if (! Fshowarea) {
 
5288
         Fshowarea++;
 
5289
         zdialog_stuff(zdsela,"show-hide",Bhide);
 
5290
      }
 
5291
      else {
 
5292
         Fshowarea = 0;
 
5293
         zdialog_stuff(zdsela,"show-hide",Bshow);
 
5294
      }
 
5295
   }
 
5296
   
 
5297
   if (strEqu(event,"susp-resm")) {                                        //  toggle suspend/resume
 
5298
      if (sa_stat == 1) {
 
5299
         gdk_window_set_cursor(drWin->window,0);                           //  restore normal cursor
 
5300
         mouseCBfunc = 0;                                                  //  disconnect mouse function
 
5301
         Mcapture = 0;
 
5302
         zdialog_stuff(zdsela,"susp-resm",Bresume);
 
5303
         sa_stat = 2;
 
5304
      }
 
5305
      else if (sa_stat == 2) {
 
5306
         gdk_window_set_cursor(drWin->window,dragcursor);                  //  set drag cursor
 
5307
         mouseCBfunc = sac_mousefunc;                                      //  connect mouse function
 
5308
         Mcapture++;
 
5309
         zdialog_stuff(zdsela,"susp-resm",Bsuspend);
 
5310
         sa_stat = 1;
 
5311
      }
 
5312
   }
 
5313
 
 
5314
   if (strEqu(event,"finish")) {                                           //  finish area
 
5315
      mouseCBfunc = 0;                                                     //  disconnect mouse function
 
5316
      Mcapture = 0;
 
5317
      gdk_window_set_cursor(drWin->window,0);                              //  restore normal cursor
 
5318
      Fshowarea++;
 
5319
      zdialog_stuff(zdsela,"show-hide",Bhide);
 
5320
      sac_select_finish();
 
5321
   }
 
5322
   
 
5323
   if (strEqu(event,"delete")) {                                           //  delete area
 
5324
      mouseCBfunc = 0;                                                     //  disconnect mouse function
 
5325
      Mcapture = 0;
 
5326
      gdk_window_set_cursor(drWin->window,0);                              //  restore normal cursor
 
5327
      select_delete();
 
5328
   }
 
5329
 
 
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
 
5333
         else {
 
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
 
5337
         }
 
5338
      }
 
5339
   }
 
5340
 
 
5341
   mwpaint2();                                                             //  update window
 
5342
   return 0;
 
5343
}
 
5344
 
 
5345
 
 
5346
//  find pixels at edge of area and paint red
 
5347
 
 
5348
void sac_select_show()
 
5349
{
 
5350
   int      px, py, ii, kk, qx, qy;
 
5351
   
 
5352
   if (! sac_pixseq) return;
 
5353
 
 
5354
   for (py = 1; py < Fhh-2; py++)                                          //  find pixels in area
 
5355
   for (px = 1; px < Fww-2; px++)
 
5356
   {
 
5357
      ii = py * Fww + px;
 
5358
      if (! sac_pixseq[ii]) continue;                                      //  outside of area
 
5359
 
 
5360
      if (! sac_pixseq[ii-1] || ! sac_pixseq[ii+1]) goto edgepixel;        //  check 8 neighbor pixels
 
5361
      kk = ii - Fww;
 
5362
      if (! sac_pixseq[kk] || ! sac_pixseq[kk-1] || ! sac_pixseq[kk+1]) goto edgepixel;
 
5363
      kk = ii + Fww;
 
5364
      if (! sac_pixseq[kk] || ! sac_pixseq[kk-1] || ! sac_pixseq[kk+1]) goto edgepixel;
 
5365
      continue;
 
5366
 
 
5367
   edgepixel:
 
5368
      qx = Mscale * (px-Iorgx);                                            //  image to window space
 
5369
      qy = Mscale * (py-Iorgy);
 
5370
      kk = (qx + qy) % 6;
 
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
 
5374
   }
 
5375
 
 
5376
   gdk_gc_set_foreground(gdkgc,&black);
 
5377
 
 
5378
   return;
 
5379
}
 
5380
 
 
5381
 
 
5382
//  construct sa_pixel[] { px, py, dist } from selected pixels in sac_pixseq[]
 
5383
 
 
5384
void sac_select_finish()
 
5385
{
 
5386
   int      ii, kk, cc, npix, px, py;
 
5387
 
 
5388
   if (sa_Npixel) return;                                                  //  already finished   
 
5389
   if (! sac_pixseq) return;                                               //  no pixels selected
 
5390
 
 
5391
   for (ii = npix = 0; ii < Fww * Fhh; ii++)                               //  count selected pixels
 
5392
      if (sac_pixseq[ii]) npix++;
 
5393
   if (npix < 10) return;
 
5394
   
 
5395
   gdk_window_set_cursor(drWin->window,busycursor);                        //  set function busy cursor
 
5396
   zmainloop();
 
5397
 
 
5398
   for (px = 0; px < Fww; px += 1)                                         //  image top & bottom edges
 
5399
   for (py = 0; py < Fhh; py += Fhh-1)
 
5400
   {
 
5401
      ii = py * Fww + px;
 
5402
      if (sac_pixseq[ii]) sac_pixseq[ii] = 2;                              //  if selected, force non-edge
 
5403
   }
 
5404
 
 
5405
   for (px = 0; px < Fww; px += Fww-1)                                     //  image left and right edges
 
5406
   for (py = 0; py < Fhh; py += 1)
 
5407
   {
 
5408
      ii = py * Fww + px;
 
5409
      if (sac_pixseq[ii]) sac_pixseq[ii] = 2;                              //  if selected, force non-edge
 
5410
   }
 
5411
 
 
5412
   for (py = 1; py < Fhh-1; py++)                                          //  check all other pixels
 
5413
   for (px = 1; px < Fww-1; px++)
 
5414
   {
 
5415
      ii = py * Fww + px;
 
5416
      if (! sac_pixseq[ii]) continue;                                      //  outside of selected area
 
5417
 
 
5418
      if (! sac_pixseq[ii-1] || ! sac_pixseq[ii+1]) goto edgepixel;        //  check 8 neighbor pixels
 
5419
      kk = ii - Fww;
 
5420
      if (! sac_pixseq[kk] || ! sac_pixseq[kk-1] || ! sac_pixseq[kk+1]) goto edgepixel;
 
5421
      kk = ii + Fww;
 
5422
      if (! sac_pixseq[kk] || ! sac_pixseq[kk-1] || ! sac_pixseq[kk+1]) goto edgepixel;
 
5423
      sac_pixseq[ii] = 2;                                                  //  non-edge pixel
 
5424
      continue;
 
5425
   edgepixel:
 
5426
      sac_pixseq[ii] = 1;                                                  //  edge pixel
 
5427
   }
 
5428
   
 
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);
 
5432
 
 
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);
 
5437
   
 
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
 
5443
      px = ii - Fww * py;
 
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
 
5447
      kk++;
 
5448
   }
 
5449
 
 
5450
   sa_Npixel = npix;
 
5451
   sa_stat = 3;
 
5452
   sa_calced = 0;                                                          //  v.8.6.1
 
5453
 
 
5454
   gdk_window_set_cursor(drWin->window,0);                                 //  restore normal cursor
 
5455
   return;
 
5456
}
 
5457
 
 
5458
 
 
5459
//  calculate distance for all pixels inside the area to the 
 
5460
//    nearest edge pixel of the area
 
5461
 
 
5462
void sac_select_edgecalc()
 
5463
{
 
5464
   void * sac_seldist_wthread(void *);
 
5465
   
 
5466
   sac_select_finish();                                                    //  finish if needed
 
5467
   if (! sa_Npixel) return;
 
5468
 
 
5469
   sac_seldist_pixdone = 0;
 
5470
 
 
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);
 
5474
 
 
5475
   while (sac_seldist_busy)
 
5476
   {
 
5477
      edit_progress(sac_seldist_pixdone,sa_Npixel);                        //  monitor progress
 
5478
      zsleep(0.1);
 
5479
      zmainloop();                                                         //  allow kill
 
5480
   }
 
5481
 
 
5482
   edit_progress(0,0);
 
5483
   sa_calced = 1;                                                          //  v.8.6.1
 
5484
   if (sac_seldist_kill) select_disable();
 
5485
   sac_seldist_kill = 0;
 
5486
   return;
 
5487
}
 
5488
 
 
5489
 
 
5490
void * sac_seldist_wthread(void *arg)                                      //  worker thread function
 
5491
{
 
5492
   int      index = *((int *) (arg));
 
5493
   int      ii, kk, npix = sa_Npixel;
 
5494
   int      px1, py1, px2, py2;
 
5495
   int      distx, disty, dist, mindist;
 
5496
 
 
5497
   for (ii = index; ii < npix; ii += NWthreads)
 
5498
   {
 
5499
      if (! sa_pixel[ii].dist) continue;                                   //  find non-edge pixels
 
5500
      px1 = sa_pixel[ii].px;
 
5501
      py1 = sa_pixel[ii].py;
 
5502
      mindist = 300;
 
5503
      
 
5504
      for (kk = 0; kk < npix; kk++)
 
5505
      {
 
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
 
5514
      }
 
5515
      
 
5516
      sa_pixel[ii].dist = mindist;                                         //  distance to nearest edge pixel
 
5517
      sac_seldist_pixdone++;
 
5518
      if (sac_seldist_kill) break;
 
5519
   }
 
5520
 
 
5521
   zadd_locked(sac_seldist_busy,-1);
 
5522
   pthread_exit(0);
 
5523
}
 
5524
 
 
5525
 
 
5526
//  invert a selected area
 
5527
 
 
5528
void sac_select_invert()                                                   //  v.8.5
 
5529
{
 
5530
   int      ii, kk, npix, cc, px, py;
 
5531
   
 
5532
   sac_select_finish();                                                    //  finish if needed
 
5533
   if (! sa_Npixel) return;
 
5534
   
 
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]);
 
5538
 
 
5539
   for (ii = npix = 0; ii < Fww * Fhh; ii++)                               //  count pixels for inverted area
 
5540
      if (sac_pixseq[ii] < 2) npix++;
 
5541
 
 
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);
 
5545
   
 
5546
   for (ii = kk = 0; ii < Fww * Fhh; ii++)
 
5547
   {
 
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
 
5551
         py = ii / Fww;
 
5552
         px = ii - Fww * py;
 
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
 
5556
         kk++;
 
5557
      }
 
5558
   }
 
5559
   
 
5560
   if (npix != kk) zappcrash("invert area bug 2 %d %d",kk,npix);
 
5561
   sa_Npixel = npix;
 
5562
   sa_calced = 0;                                                          //  v.8.6.1
 
5563
   Fshowarea++;
 
5564
   return;
 
5565
}
 
5566
 
 
5567
 
 
5568
//  select area by color - mouse function
 
5569
 
 
5570
void sac_mousefunc()
 
5571
{
 
5572
   static int  mxdown, mydown;
 
5573
   
 
5574
   sac_mousex = sac_mousey = 0;
 
5575
 
 
5576
   if (LMclick) {                                                          //  get mouse position at click
 
5577
      sac_mousex = Mxclick;
 
5578
      sac_mousey = Myclick;
 
5579
      LMclick = 0;
 
5580
      sac_currseq++;                                                       //  new sequence number for undo
 
5581
   }
 
5582
      
 
5583
   if (Mxdrag || Mydrag) {                                                 //  get mouse drag position
 
5584
      sac_mousex = Mxdrag;
 
5585
      sac_mousey = Mydrag;
 
5586
      Mxdrag = Mydrag = 0;
 
5587
 
 
5588
      if (Mxdown != mxdown || Mydown != mydown) {                          //  detect if new drag started
 
5589
         mxdown = Mxdown;
 
5590
         mydown = Mydown;
 
5591
         sac_currseq++;                                                    //  yes - new sequence number
 
5592
      }
 
5593
   }
 
5594
   
 
5595
   if (sac_mousex || sac_mousey) {
 
5596
      sac_select_pixels();                                                 //  accumulate pixels
 
5597
      mwpaint2();
 
5598
   }
 
5599
 
 
5600
 
 
5601
   if (RMclick) {
 
5602
      RMclick = 0;
 
5603
      if (sac_currseq) {                                                   //  remove selected pixels having
 
5604
         sac_unselect_pixels();                                            //    current sequence number
 
5605
         sac_currseq--;
 
5606
      }
 
5607
      mwpaint2();
 
5608
   }
 
5609
 
 
5610
   return;
 
5611
}
 
5612
 
 
5613
 
 
5614
//  find all contiguous pixels within the specified color range
 
5615
 
 
5616
void sac_select_pixels()
 
5617
{
 
5618
   void  sac_select_stack(int px, int py, char direc);
 
5619
 
 
5620
   int      ii, kk, px, py;
 
5621
   char     direc;
 
5622
   
 
5623
   if (! sac_pixseq) return;
 
5624
 
 
5625
   px = sac_mousex;                                                        //  mouse position in image
 
5626
   py = sac_mousey;
 
5627
 
 
5628
   ii = Fww * py + px;
 
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;
 
5635
      ii = Fww * py + px;
 
5636
      if (sac_pixseq[ii] == 0) goto gotpix;
 
5637
   }
 
5638
   gotpix:
 
5639
   if (sac_pixseq[ii] > 0) return;                                         //  nothing will be selected
 
5640
 
 
5641
   sac_pixseq[ii] = sac_currseq;                                           //  map pixel to current sequence
 
5642
   sac_Ncurrseq = 1;                                                       //  current sequence pixel count
 
5643
 
 
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];
 
5648
   
 
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
 
5651
 
 
5652
   sac_stackii[0] = ii;                                                    //  put 1st pixel into stack
 
5653
   sac_stackdir[0] = 'r';                                                  //  direction = right
 
5654
   sac_Nstack = 1;                                                         //  stack count
 
5655
 
 
5656
   while (sac_Nstack)
 
5657
   {
 
5658
      kk = sac_Nstack - 1;                                                 //  get last pixel in stack
 
5659
      ii = sac_stackii[kk];
 
5660
      direc = sac_stackdir[kk];
 
5661
      
 
5662
      py = ii / Fww;                                                       //  reconstruct px, py
 
5663
      px = ii - Fww * py;
 
5664
 
 
5665
      if (direc == 'x') {                                                  //  no neighbors left to check
 
5666
         sac_Nstack--;
 
5667
         continue;
 
5668
      }
 
5669
      
 
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
 
5673
         continue;
 
5674
      }
 
5675
 
 
5676
      if (direc == 'l') {                                                  //  or next left pixel
 
5677
         sac_select_stack(px,py,'l');
 
5678
         sac_stackdir[kk] = 'a';
 
5679
         continue;
 
5680
      }
 
5681
 
 
5682
      if (direc == 'a') {                                                  //  or next ahead pixel
 
5683
         sac_select_stack(px,py,'a');
 
5684
         sac_stackdir[kk] = 'x';
 
5685
         continue;
 
5686
      }
 
5687
   }
 
5688
 
 
5689
   return;
 
5690
}      
 
5691
 
 
5692
 
 
5693
//  un-select all pixels mapped to current sequence number
 
5694
 
 
5695
void sac_unselect_pixels()
 
5696
{
 
5697
   if (! sac_currseq) return;
 
5698
   
 
5699
   for (int ii = 0; ii < Fww * Fhh; ii++)
 
5700
   {
 
5701
      if (sac_pixseq[ii] != sac_currseq) continue;
 
5702
      sac_pixseq[ii] = 0;
 
5703
   }
 
5704
   
 
5705
   sac_Ncurrseq = 0;
 
5706
   return;
 
5707
}
 
5708
 
 
5709
 
 
5710
//  push pixel into stack memory if its color is within range
 
5711
//  and not already mapped to a prior sequence number
 
5712
 
 
5713
void sac_select_stack(int px, int py, char direc)
 
5714
{
 
5715
   int         ii, kk, ppx, ppy, npx, npy;
 
5716
   uint16      *matchpix;
 
5717
   double      match, ff = 1.0 / 65536.0;
 
5718
   double      dred, dgreen, dblue;
 
5719
 
 
5720
   if (sac_Nstack > 1) {
 
5721
      kk = sac_Nstack - 2;                                                 //  get prior pixel in stack
 
5722
      ii = sac_stackii[kk];
 
5723
      ppy = ii / Fww;
 
5724
      ppx = ii - ppy * Fww;
 
5725
   }
 
5726
   else {
 
5727
      ppx = px - 1;                                                        //  if only one, assume prior = left
 
5728
      ppy = py;
 
5729
   }
 
5730
   
 
5731
   if (direc == 'r') {                                                     //  get pixel in direction right
 
5732
      npx = px + ppy - py;
 
5733
      npy = py + px - ppx;
 
5734
   }
 
5735
   else if (direc == 'l') {                                                //  or left
 
5736
      npx = px + py - ppy;
 
5737
      npy = py + ppx - px;
 
5738
   }
 
5739
   else if (direc == 'a') {                                                //  or ahead
 
5740
      npx = px + px - ppx;
 
5741
      npy = py + py - ppy;
 
5742
   }
 
5743
   else npx = npy = -1;                                                    //  stop warning
 
5744
   
 
5745
   if (npx < 0 || npx >= Fww) return;                                      //  pixel off the edge
 
5746
   if (npy < 0 || npy >= Fhh) return;
 
5747
   
 
5748
   ii = npy * Fww + npx;
 
5749
   if (sac_pixseq[ii]) return;                                             //  pixel already mapped
 
5750
 
 
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
 
5757
 
 
5758
   if (sac_Nstack == sac_maxstack) return;                                 //  stack is full
 
5759
 
 
5760
   sac_pixseq[ii] = sac_currseq;                                           //  map pixel to curr. sequence
 
5761
   sac_Ncurrseq++;
 
5762
   
 
5763
   kk = sac_Nstack++;                                                      //  put pixel into stack
 
5764
   sac_stackii[kk] = ii;
 
5765
   sac_stackdir[kk] = 'r';                                                 //  direction = right
 
5766
 
 
5767
   return;
 
5768
}
 
5769
 
 
5770
 
 
5771
/**************************************************************************
 
5772
   functions common to both mouse-selected and color-selected areas
 
5773
***************************************************************************/
 
5774
 
 
5775
//  show outline of selected area - also called when window is repainted
 
5776
 
 
5777
void m_select_show(GtkWidget *, const char *)                              //  v.8.5
 
5778
{
 
5779
   Fshowarea = 1;
 
5780
   if (sa_type == 1) sam_select_show();
 
5781
   if (sa_type == 2) sac_select_show();
 
5782
   return;
 
5783
}
 
5784
 
 
5785
 
 
5786
//  hide select area
 
5787
 
 
5788
void m_select_hide(GtkWidget *, const char *)                              //  v.8.5
 
5789
{
 
5790
   Fshowarea = 0;
 
5791
   mwpaint2();
 
5792
}
 
5793
 
 
5794
 
 
5795
//  compute distance from all pixels in area to nearest edge
 
5796
 
 
5797
void m_select_edgecalc(GtkWidget *, const char *)                          //  v.8.5
 
5798
{
 
5799
   int      warn = 0, yn;
 
5800
   double   limit1 = 100 * mega, limit2 = 0.1 * mega;
 
5801
   
 
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
 
5805
   
 
5806
   if (sa_type == 1 && (1.0 * sam_npg * sa_Npixel) > limit1) warn = 1;
 
5807
   if (sa_type == 2 && sa_Npixel > limit2) warn = 1;
 
5808
 
 
5809
   if (warn) {
 
5810
      yn = zmessageYN(ZTX("Distance calculation needs a long time.\n"
 
5811
                          " Do you want to continue?"));
 
5812
      if (! yn) return;
 
5813
   }
 
5814
 
 
5815
   gdk_window_set_cursor(drWin->window,busycursor);                        //  set busy cursor
 
5816
   zmainloop();
 
5817
   
 
5818
   if (sa_type == 1) sam_select_edgecalc();                                //  mouse select, polygon
 
5819
   if (sa_type == 2) sac_select_edgecalc();                                //  color select
 
5820
 
 
5821
   gdk_window_set_cursor(drWin->window,0);                                 //  restore normal cursor
 
5822
   Fshowarea++;
 
5823
   return;
 
5824
}
 
5825
 
 
5826
 
 
5827
//  invert a selected area
 
5828
 
 
5829
void m_select_invert(GtkWidget *, const char *)                            //  v.8.5
 
5830
{
 
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
 
5834
 
 
5835
   gdk_window_set_cursor(drWin->window,busycursor);                        //  set busy cursor
 
5836
   zmainloop();
 
5837
 
 
5838
   if (sa_type == 1) sam_select_invert();
 
5839
   if (sa_type == 2) sac_select_invert();
 
5840
 
 
5841
   gdk_window_set_cursor(drWin->window,0);                                 //  restore normal cursor
 
5842
   Fshowarea++;
 
5843
   return;
 
5844
}
 
5845
 
 
5846
 
 
5847
//  menu function - clear selected image area, free memory
 
5848
 
 
5849
void m_select_delete(GtkWidget *, const char *)                            //  v.8.5
 
5850
{
 
5851
   int      yn;
 
5852
   
 
5853
   if (! sam_npg && ! sac_pixseq) return;                                  //  nothing to delete
 
5854
   m_select_show(0,0);
 
5855
   yn = zmessageYN(Bdeletearea);
 
5856
   if (! yn) return;
 
5857
   select_delete();
 
5858
   return;
 
5859
}
 
5860
 
 
5861
 
 
5862
//  menu function - disable selected image area, re-edit possible
 
5863
 
 
5864
void m_select_disable(GtkWidget *, const char *)                           //  v.8.5
 
5865
{
 
5866
   select_disable();
 
5867
   return;
 
5868
}
 
5869
 
 
5870
 
 
5871
//  completely delete both kinds of select area
 
5872
 
 
5873
void select_delete()                                                       //  v.8.5
 
5874
{
 
5875
   select_disable();
 
5876
   sam_npg = sac_currseq = sa_stat = 0;
 
5877
   if (sac_pixseq) zfree(sac_pixseq);
 
5878
   sac_pixseq = 0;
 
5879
   return;
 
5880
}
 
5881
 
 
5882
 
 
5883
//  disable area so that re-edit is possible
 
5884
 
 
5885
void select_disable()                                                      //  v.8.5
 
5886
{
 
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);
 
5894
   sa_pixel = 0;
 
5895
   sa_pixisin = 0;
 
5896
   sa_calced = 0;                                                          //  v.8.6.1
 
5897
   mwpaint2();
 
5898
   return;
 
5899
}
 
5900
 
 
5901
 
 
5902
/**************************************************************************
 
5903
      begin image edit functions
 
5904
***************************************************************************/
 
5905
 
 
5906
 
 
5907
//  adjust white balance
 
5908
 
 
5909
double   whitebal_red, whitebal_green, whitebal_blue;
 
5910
int      whitebal_busy = 0;
 
5911
 
 
5912
 
 
5913
void m_whitebal(GtkWidget *, const char *)                                 //  v.8.6
 
5914
{
 
5915
   void  whitebal_mousefunc();
 
5916
   int   whitebal_dialog_compl(zdialog* zd, int zstat);
 
5917
   void  *whitebal_thread(void *);
 
5918
 
 
5919
   const char  *wbtitle = ZTX("Adjust White Balance");
 
5920
   const char  *wbhelp = ZTX("Click white or gray image location");
 
5921
 
 
5922
   if (! edit_setup(1,2)) return;                                          //  setup edit: preview
 
5923
 
 
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");
 
5932
 
 
5933
   zdialog_run(zdedit,0,whitebal_dialog_compl);                            //  run dialog - parallel
 
5934
 
 
5935
   whitebal_red = whitebal_green = whitebal_blue = 1.0;
 
5936
   start_thread(whitebal_thread,0);                                        //  start working thread
 
5937
 
 
5938
   mouseCBfunc = whitebal_mousefunc;                                       //  connect mouse function
 
5939
   Mcapture = 1;                                                           //  capture mouse clicks
 
5940
   Frgbmode = 0;
 
5941
   return;
 
5942
}
 
5943
 
 
5944
 
 
5945
int whitebal_dialog_compl(zdialog *zd, int zstat)                          //  dialog completion function
 
5946
{
 
5947
   mouseCBfunc = 0;                                                        //  disconnect mouse
 
5948
   Mcapture = 0;
 
5949
   if (zstat == 1) edit_done();                                            //  done
 
5950
   else edit_cancel();                                                     //  cancel or destroy
 
5951
   return 0;
 
5952
}
 
5953
 
 
5954
 
 
5955
void whitebal_mousefunc()                                                  //  mouse function
 
5956
{
 
5957
   int         px, py, dx, dy;
 
5958
   double      red, green, blue, rgbmean;
 
5959
   char        work[40];
 
5960
   uint16      *ppix48;
 
5961
   
 
5962
   if (! LMclick) return;
 
5963
   
 
5964
   LMclick = 0;
 
5965
   px = Mxclick;                                                           //  mouse click position
 
5966
   py = Myclick;
 
5967
   
 
5968
   if (px < 2) px = 2;                                                     //  pull back from edge
 
5969
   if (px > E3ww-3) px = E3ww-3;
 
5970
   if (py < 2) py = 2;
 
5971
   if (py > E3hh-3) py = E3hh-3;
 
5972
   
 
5973
   red = green = blue = 0;
 
5974
 
 
5975
   for (dy = -2; dy <= 2; dy++)                                            //  5x5 block around mouse position
 
5976
   for (dx = -2; dx <= 2; dx++)
 
5977
   {
 
5978
      ppix48 = bmpixel(E1rgb48,px+dx,py+dy);                               //  input image
 
5979
      red += ppix48[0];
 
5980
      green += ppix48[1];
 
5981
      blue += ppix48[2];
 
5982
   }
 
5983
   
 
5984
   red = red / 25.0;                                                       //  mean RGB levels 
 
5985
   green = green / 25.0;
 
5986
   blue = blue / 25.0;
 
5987
   rgbmean = (red + green + blue) / 3.0;
 
5988
 
 
5989
   whitebal_red = rgbmean / red;
 
5990
   whitebal_green = rgbmean / green;
 
5991
   whitebal_blue = rgbmean / blue;
 
5992
 
 
5993
   signal_thread();                                                        //  trigger image update
 
5994
 
 
5995
   snprintf(work,40,"%d %d",px,py);
 
5996
   zdialog_stuff(zdedit,"pixel",work);
 
5997
 
 
5998
   snprintf(work,40,"%7.3f %7.3f %7.3f",red/256,green/256,blue/256);
 
5999
   zdialog_stuff(zdedit,"rgb",work);
 
6000
   
 
6001
   return;
 
6002
}
 
6003
 
 
6004
 
 
6005
//  Update image based on neutral pixel that was clicked
 
6006
 
 
6007
void * whitebal_thread(void *)
 
6008
{
 
6009
   void * whitebal_wthread(void *arg);
 
6010
 
 
6011
   while (true)
 
6012
   {
 
6013
      thread_idle_loop();                                                  //  wait for work or exit request
 
6014
      
 
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);
 
6018
      
 
6019
      while (whitebal_busy) zsleep(0.01);                                  //  wait for completion
 
6020
 
 
6021
      Fmodified = 1;
 
6022
      mwpaint2();                                                          //  update window
 
6023
   }
 
6024
 
 
6025
   return 0;                                                               //  not executed, stop g++ warning
 
6026
}
 
6027
 
 
6028
 
 
6029
void * whitebal_wthread(void *arg)                                         //  worker thread function
 
6030
{
 
6031
   void  whitebalpix(int px, int py, int dist);
 
6032
 
 
6033
   int      px, py, ii, dist;
 
6034
   int      index = *((int *) arg);
 
6035
 
 
6036
   if (sa_Npixel)                                                          //  process selected area
 
6037
   {
 
6038
      for (ii = index; ii < sa_Npixel; ii += NWthreads)                    //  process all enclosed pixels
 
6039
      {
 
6040
         px = sa_pixel[ii].px;
 
6041
         py = sa_pixel[ii].py;
 
6042
         dist = sa_pixel[ii].dist;
 
6043
         whitebalpix(px,py,dist);
 
6044
      }
 
6045
   }
 
6046
 
 
6047
   else                                                                    //  process whole image
 
6048
   {
 
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);
 
6053
   }
 
6054
 
 
6055
   zadd_locked(whitebal_busy,-1);
 
6056
   pthread_exit(0);
 
6057
}
 
6058
 
 
6059
 
 
6060
void whitebalpix(int px, int py, int dist)                                 //  process one pixel
 
6061
{
 
6062
   uint16      *pix1, *pix3;
 
6063
   double      red1, green1, blue1;
 
6064
   double      red3, green3, blue3;
 
6065
   double      brmax, dold, dnew;
 
6066
 
 
6067
   pix1 = bmpixel(E1rgb48,px,py);                                          //  input pixel
 
6068
   pix3 = bmpixel(E3rgb48,px,py);                                          //  output pixel
 
6069
   
 
6070
   red1 = pix1[0];
 
6071
   green1 = pix1[1];
 
6072
   blue1 = pix1[2];
 
6073
   
 
6074
   red3 = whitebal_red * red1;                                             //  change color ratios
 
6075
   green3 = whitebal_green * green1;
 
6076
   blue3 = whitebal_blue * blue1;
 
6077
 
 
6078
   if (dist < sa_blend) {                                                  //  blend select area if req.
 
6079
      dnew = 1.0 * dist / sa_blend;
 
6080
      dold = 1.0 - dnew;
 
6081
      red3 = dnew * red3 + dold * red1;
 
6082
      green3 = dnew * green3 + dold * green1;
 
6083
      blue3 = dnew * blue3 + dold * blue1;
 
6084
   }
 
6085
   
 
6086
   brmax = red3;                                                           //  brmax = brightest color
 
6087
   if (green3 > brmax) brmax = green3;
 
6088
   if (blue3 > brmax) brmax = blue3;
 
6089
   
 
6090
   if (brmax > 65535) {                                                    //  if overflow, reduce
 
6091
      brmax = 65535 / brmax;
 
6092
      red3 = red3 * brmax;
 
6093
      green3 = green3 * brmax;
 
6094
      blue3 = blue3 * brmax;
 
6095
   }
 
6096
 
 
6097
   pix3[0] = int(red3);
 
6098
   pix3[1] = int(green3);
 
6099
   pix3[2] = int(blue3);
 
6100
   
 
6101
   return;
 
6102
}
 
6103
 
 
6104
 
 
6105
/**************************************************************************/
 
6106
 
 
6107
//  flatten brightness distribution
 
6108
 
 
6109
int      flatten_busy = 0;
 
6110
double   flatten_value = 0;                                                //  flatten value, 0 - 100%
 
6111
double   flatten_brdist[65536];
 
6112
 
 
6113
void m_flatten(GtkWidget *, const char *)
 
6114
{
 
6115
   int    flatten_dialog_event(zdialog* zd, const char *event);
 
6116
   int    flatten_dialog_compl(zdialog* zd, int zstat);
 
6117
   void * flatten_thread(void *);
 
6118
 
 
6119
   const char  *title = ZTX("Flatten Brightness Distribution");
 
6120
 
 
6121
   if (! edit_setup(1,2)) return;                                          //  setup edit: preview
 
6122
 
 
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");
 
6127
 
 
6128
   zdialog_resize(zdedit,300,0);
 
6129
   zdialog_run(zdedit,flatten_dialog_event,flatten_dialog_compl);          //  run dialog - parallel
 
6130
   
 
6131
   flatten_value = 0;
 
6132
   start_thread(flatten_thread,0);                                         //  start working thread
 
6133
   return;
 
6134
}
 
6135
 
 
6136
 
 
6137
//  flatten dialog event and completion functions
 
6138
 
 
6139
int flatten_dialog_event(zdialog *zd, const char *event)                   //  flatten dialog event function
 
6140
{
 
6141
   zdialog_fetch(zd,"flatten",flatten_value);                              //  get slider value
 
6142
   signal_thread();                                                        //  trigger update thread
 
6143
   return 1;
 
6144
}
 
6145
 
 
6146
 
 
6147
int flatten_dialog_compl(zdialog *zd, int zstat)                           //  flatten dialog completion function
 
6148
{
 
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
 
6153
   return 0;
 
6154
}
 
6155
 
 
6156
 
 
6157
//  thread function - use multiple working threads
 
6158
 
 
6159
void * flatten_thread(void *)
 
6160
{
 
6161
   void  * flatten_wthread(void *arg);
 
6162
 
 
6163
   int         px, py, ii;
 
6164
   double      bright1;
 
6165
   uint16      *pix1;
 
6166
 
 
6167
   while (true)
 
6168
   {
 
6169
      thread_idle_loop();                                                  //  wait for work or exit request
 
6170
 
 
6171
      for (ii = 0; ii < 65536; ii++)                                       //  clear brightness distribution data
 
6172
         flatten_brdist[ii] = 0;
 
6173
 
 
6174
      if (sa_Npixel)                                                       //  process selected area
 
6175
      {
 
6176
         for (ii = 0; ii < sa_Npixel; ii++)                                //  process enclosed pixels
 
6177
         {
 
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)]++;
 
6183
         }
 
6184
         
 
6185
         for (ii = 1; ii < 65536; ii++)                                    //  cumulative brightness distribution
 
6186
            flatten_brdist[ii] += flatten_brdist[ii-1];                    //   0 ... sa_Npixel
 
6187
 
 
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);
 
6191
      }
 
6192
 
 
6193
      else                                                                 //  process whole image
 
6194
      {
 
6195
         for (py = 0; py < E1hh; py++)                                     //  compute brightness distribution
 
6196
         for (px = 0; px < E1ww; px++)
 
6197
         {
 
6198
            pix1 = bmpixel(E1rgb48,px,py);
 
6199
            bright1 = brightness(pix1);
 
6200
            flatten_brdist[int(bright1)]++;
 
6201
         }
 
6202
         
 
6203
         for (ii = 1; ii < 65536; ii++)                                    //  cumulative brightness distribution
 
6204
            flatten_brdist[ii] += flatten_brdist[ii-1];                    //   0 ... (ww1 * hh1)
 
6205
 
 
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);
 
6209
      }
 
6210
      
 
6211
      for (ii = 0; ii < NWthreads; ii++)                                   //  start worker threads
 
6212
         start_detached_thread(flatten_wthread,&wtindex[ii]);
 
6213
      zadd_locked(flatten_busy,+NWthreads);
 
6214
 
 
6215
      while (flatten_busy) zsleep(0.004);                                  //  wait for completion
 
6216
 
 
6217
      Fmodified = 1;
 
6218
      mwpaint2();                                                          //  update window
 
6219
   }
 
6220
 
 
6221
   return 0;                                                               //  not executed, stop g++ warning
 
6222
}
 
6223
 
 
6224
 
 
6225
void * flatten_wthread(void *arg)                                          //  worker thread function
 
6226
{
 
6227
   void  flatten_1pix(int px, int py, int dist);
 
6228
   
 
6229
   int         index = *((int *) (arg));
 
6230
   int         px, py, ii, dist;
 
6231
 
 
6232
   if (sa_Npixel)                                                          //  process selected area
 
6233
   {
 
6234
      for (ii = index; ii < sa_Npixel; ii += NWthreads)                    //  process all enclosed pixels
 
6235
      {
 
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);
 
6240
      }
 
6241
   }
 
6242
 
 
6243
   else
 
6244
   {
 
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);
 
6249
   }
 
6250
 
 
6251
   zadd_locked(flatten_busy,-1);
 
6252
   pthread_exit(0);
 
6253
}
 
6254
 
 
6255
 
 
6256
void flatten_1pix(int px, int py, int dist)
 
6257
{
 
6258
   uint16      *pix1, *pix3;
 
6259
   double      fold, fnew, dold, dnew, cmax;
 
6260
   double      red1, green1, blue1, red3, green3, blue3;
 
6261
   double      bright1, bright2;
 
6262
 
 
6263
   pix1 = bmpixel(E1rgb48,px,py);                                          //  input pixel
 
6264
   pix3 = bmpixel(E3rgb48,px,py);                                          //  output pixel
 
6265
      
 
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
 
6268
 
 
6269
   red1 = pix1[0];
 
6270
   green1 = pix1[1];
 
6271
   blue1 = pix1[2];
 
6272
 
 
6273
   bright1 = brightness(pix1);                                             //  input brightness
 
6274
   bright2 = flatten_brdist[int(bright1)];                                 //  output brightness adjustment
 
6275
 
 
6276
   red3 = bright2 * red1;                                                  //  flattened brightness
 
6277
   green3 = bright2 * green1;
 
6278
   blue3 = bright2 * blue1;
 
6279
 
 
6280
   red3 = fnew * red3 + fold * red1;                                       //  blend new and old brightness
 
6281
   green3 = fnew * green3 + fold * green1;
 
6282
   blue3 = fnew * blue3 + fold * blue1;
 
6283
 
 
6284
   if (dist < sa_blend) {                                                  //  blend over distance sa_blend
 
6285
      dnew = 1.0 * dist / sa_blend;
 
6286
      dold = 1.0 - dnew;
 
6287
      red3 = dnew * red3 + dold * red1;
 
6288
      green3 = dnew * green3 + dold * green1;
 
6289
      blue3 = dnew * blue3 + dold * blue1;
 
6290
   }
 
6291
 
 
6292
   cmax = red3;                                                            //  stop overflow, keep color balance
 
6293
   if (green3 > cmax) cmax = green3;
 
6294
   if (blue3 > cmax) cmax = blue3;
 
6295
   if (cmax > 65535) {
 
6296
      cmax = 65535 / cmax;
 
6297
      red3 = red3 * cmax;
 
6298
      green3 = green3 * cmax;
 
6299
      blue3 = blue3 * cmax;
 
6300
   }
 
6301
 
 
6302
   pix3[0] = int(red3 + 0.5);
 
6303
   pix3[1] = int(green3 + 0.5);
 
6304
   pix3[2] = int(blue3 + 0.5);
 
6305
   return;
 
6306
}
 
6307
 
 
6308
 
 
6309
/**************************************************************************/
 
6310
 
 
6311
//  brightness / color / contrast adjustment
 
6312
 
 
6313
int   tune_curve_adjust(void *,GdkEventButton *);                          //  mouse events in drawing area
 
6314
int   tune_curve_draw();                                                   //  draw curve in drawing area
 
6315
 
 
6316
GtkWidget   *tune_drawarea;                                                //  drawing area for curve
 
6317
int         tune_busy;
 
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
 
6322
 
 
6323
 
 
6324
void m_tune(GtkWidget *, const char *)                                     //  overhauled  v.6.8
 
6325
{
 
6326
   int   tune_dialog_event(zdialog *zd, cchar *event);
 
6327
   int   tune_dialog_compl(zdialog *zd, int zstat);
 
6328
   void  *tune_thread(void *);
 
6329
 
 
6330
   const char  *title = ZTX("Adjust Brightness and Color");
 
6331
 
 
6332
   if (! edit_setup(1,2)) return;                                          //  setup edit: preview
 
6333
   
 
6334
   for (int ii = 0; ii < 7; ii++)
 
6335
   {                                                                       //  initz. all curves to flat
 
6336
      tune_nap[ii] = 3;
 
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;
 
6343
      
 
6344
      for (int jj = 0; jj < 100; jj++)                                     //  initz. curve data points
 
6345
         tune_dat[ii][jj] = 50;
 
6346
   }
 
6347
   
 
6348
   tune_ii = 0;                                                            //  default curve = brightness
 
6349
 
 
6350
/***
 
6351
       --------------------------------------------
 
6352
      |                                            |
 
6353
      |           curve drawing area               |
 
6354
      |                                            |
 
6355
       --------------------------------------------
 
6356
       darker areas                   lighter areas
 
6357
 
 
6358
      [+++] [---] [+ -] [- +] [+-+] [-+-]
 
6359
 
 
6360
      (o) brightness
 
6361
      (o) defog
 
6362
      (o) color intensity    [reset 1]  [reset all]
 
6363
      (o) color saturation
 
6364
      (o) color balance    (o) red  (o) green  (o) blue
 
6365
 
 
6366
***/
 
6367
 
 
6368
   zdedit = zdialog_new(title,mWin,Bundo,Bredo,Bdone,Bcancel,null);        //  create dialog
 
6369
 
 
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");
 
6399
   
 
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);
 
6403
 
 
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)
 
6410
   
 
6411
   zdialog_stuff(zdedit,"radbri",1);                                       //  stuff defaults
 
6412
   zdialog_stuff(zdedit,"radR",1);
 
6413
   
 
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
 
6417
   return;
 
6418
}
 
6419
 
 
6420
 
 
6421
//  tune dialog event and completion functions
 
6422
 
 
6423
int tune_dialog_event(zdialog *zd, const char *event)
 
6424
{
 
6425
   int      ii, jj, curve = -1;
 
6426
   double   px, py;
 
6427
   
 
6428
   if (strnEqu(event,"rad",3))                                             //  new choice of curve
 
6429
   {
 
6430
      ii = strcmpv(event,"radbri","radfog","radcol","radsat","radR","radG","radB",0);
 
6431
      if (ii > 0) curve = ii - 1;
 
6432
      
 
6433
      if (strstr("radR radG radB",event))                                  //  if RGB, set color balance
 
6434
         zdialog_stuff(zd,"radbal",1);
 
6435
      
 
6436
      if (strEqu(event,"radbal")) {                                        //  if color balance,
 
6437
         zdialog_fetch(zd,"radR",ii);                                      //    get current RGB selection
 
6438
         if (ii) curve = 4;
 
6439
         zdialog_fetch(zd,"radG",ii);
 
6440
         if (ii) curve = 5;
 
6441
         zdialog_fetch(zd,"radB",ii);
 
6442
         if (ii) curve = 6;
 
6443
      }
 
6444
 
 
6445
      if (curve >= 0 && curve != tune_ii) {
 
6446
         tune_ii = curve;                                                  //  set new curve
 
6447
         tune_curve_draw();                                                //  redraw curve
 
6448
      }
 
6449
   }
 
6450
   
 
6451
   if (strnEqu(event,"b ",2))                                              //  button to move entire curve
 
6452
   {
 
6453
      ii = tune_ii;
 
6454
      
 
6455
      for (jj = 0; jj < tune_nap[ii]; jj++)
 
6456
      {
 
6457
         px = tune_apx[ii][jj];
 
6458
         py = tune_apy[ii][jj];
 
6459
      
 
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);
 
6466
 
 
6467
         if (py > 100) py = 100;
 
6468
         if (py < 0) py = 0;
 
6469
         tune_apy[ii][jj] = py;
 
6470
      }
 
6471
 
 
6472
      tune_curve_draw();                                                   //  redraw curve
 
6473
      signal_thread();                                                     //  trigger image update
 
6474
   }
 
6475
   
 
6476
   if (strEqu(event,"reset1")) 
 
6477
   {
 
6478
      ii = tune_ii;                                                        //  current curve
 
6479
      tune_nap[ii] = 3;
 
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;
 
6486
      
 
6487
      for (int jj = 0; jj < 100; jj++)                                     //  initz. curve data points
 
6488
         tune_dat[ii][jj] = 50;
 
6489
      
 
6490
      tune_curve_draw();                                                   //  update dialog curve
 
6491
      signal_thread();                                                     //  update image
 
6492
   }
 
6493
   
 
6494
   if (strEqu(event,"resetA")) 
 
6495
   {
 
6496
      for (int ii = 0; ii < 7; ii++)                                       //  do all curves
 
6497
      {
 
6498
         tune_nap[ii] = 3;
 
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;
 
6505
         
 
6506
         for (int jj = 0; jj < 100; jj++)
 
6507
            tune_dat[ii][jj] = 50;
 
6508
      }
 
6509
      
 
6510
      tune_curve_draw();
 
6511
      signal_thread();
 
6512
   }
 
6513
   
 
6514
   if (strEqu(event,"blendwidth")) signal_thread();                        //  select area blend width change
 
6515
 
 
6516
   return 1;
 
6517
}
 
6518
 
 
6519
 
 
6520
int tune_dialog_compl(zdialog *zd, int zstat)                              //  tune dialog completion function
 
6521
{
 
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
 
6526
   return 0;
 
6527
}
 
6528
 
 
6529
 
 
6530
//  Add, delete, or move anchor points in curve using mouse.
 
6531
 
 
6532
int tune_curve_adjust(void *,GdkEventButton *event)
 
6533
{
 
6534
   int         ww, hh, px, py;
 
6535
   int         kk, ii, jj, newjj, minjj = -1;
 
6536
   int         mx, my, button, evtype;
 
6537
   double      dist2, mindist2 = 1000000;
 
6538
   double      xval, yval;
 
6539
   
 
6540
   ii = tune_ii;                                                           //  get current curve
 
6541
 
 
6542
   if (tune_nap[ii] > 49) {
 
6543
      zmessageACK(ZTX("Exceed 50 anchor points"));
 
6544
      return 0;
 
6545
   }
 
6546
   
 
6547
   mx = int(event->x);                                                     //  mouse position in drawing area
 
6548
   my = int(event->y);
 
6549
   evtype = event->type;
 
6550
   button = event->button;
 
6551
   ww = tune_drawarea->allocation.width;                                   //  drawing area size
 
6552
   hh = tune_drawarea->allocation.height;
 
6553
   
 
6554
   for (jj = 0; jj < tune_nap[ii]; jj++)                                   //  find closest anchor point
 
6555
   {
 
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) {
 
6562
         mindist2 = dist2;
 
6563
         minjj = jj;
 
6564
      }
 
6565
   }
 
6566
 
 
6567
   if (minjj < 0) return 0;                                                //  huh?
 
6568
   
 
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];
 
6575
      }
 
6576
      tune_nap[ii]--;
 
6577
 
 
6578
      tune_curve_draw();                                                   //  regen and redraw curve
 
6579
      signal_thread();                                                     //  trigger image update
 
6580
      return 0;
 
6581
   }
 
6582
 
 
6583
//  drag or left click, move nearby anchor point to mouse position,
 
6584
//  or add a new anchor point if nothing near enough
 
6585
 
 
6586
   xval = 100.0 * mx / ww;                                                 //  0 - 100
 
6587
   yval = 100.0 * (hh - my) / hh;                                          //  0 - 100
 
6588
 
 
6589
   if (xval < 0 || xval > 100) return 0;                                   //  v.6.8
 
6590
   if (yval < 0 || yval > 100) return 0;
 
6591
 
 
6592
   if (mindist2 < 100) {                                                   //  existing point < 10 pixels away
 
6593
      jj = minjj;
 
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
 
6598
   }
 
6599
   else                                                                    //  > 10 pixels, add a point
 
6600
   {
 
6601
      for (jj = 0; jj < tune_nap[ii]; jj++)
 
6602
         if (xval <= tune_apx[ii][jj]) break;                              //  find point with next higher x
 
6603
 
 
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
 
6606
 
 
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];
 
6610
      }
 
6611
 
 
6612
      tune_nap[ii]++;                                                      //  up point count
 
6613
      newjj = jj;                                                          //  point to be added
 
6614
   }
 
6615
 
 
6616
   tune_apx[ii][newjj] = xval;                                             //  coordinates of new or moved point
 
6617
   tune_apy[ii][newjj] = yval;
 
6618
   
 
6619
   tune_curve_draw();                                                      //  regen and redraw the curve
 
6620
   signal_thread();                                                        //  trigger image update
 
6621
   return 0;
 
6622
}
 
6623
 
 
6624
 
 
6625
//  Draw brightness curve based on defined spline anchor points.
 
6626
 
 
6627
int tune_curve_draw()
 
6628
{
 
6629
   int         ww, hh, px, py;
 
6630
   int         ii, jj, jjx, jjy;
 
6631
   double      xval, yval;
 
6632
   
 
6633
   ii = tune_ii;                                                           //  current curve
 
6634
 
 
6635
   ww = tune_drawarea->allocation.width;                                   //  drawing area size
 
6636
   hh = tune_drawarea->allocation.height;
 
6637
   if (ww < 50 || hh < 20) return 0;
 
6638
 
 
6639
   spline1(tune_nap[ii],tune_apx[ii],tune_apy[ii]);                        //  make curve fitting anchor points
 
6640
 
 
6641
   gdk_window_clear(tune_drawarea->window);                                //  clear window
 
6642
 
 
6643
   for (px = 0; px < ww; px++)                                             //  generate all points for curve
 
6644
   {
 
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);
 
6649
   }
 
6650
   
 
6651
   for (jj = 0; jj < tune_nap[ii]; jj++)                                   //  draw boxes at anchor points
 
6652
   {
 
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);
 
6662
      }
 
6663
   }
 
6664
 
 
6665
   for (jj = 0; jj < 100; jj++)                                            //  save 100 curve data points
 
6666
      tune_dat[ii][jj] = spline2(jj);
 
6667
 
 
6668
   return 0;
 
6669
}
 
6670
 
 
6671
 
 
6672
//  Update image based on latest settings of all dialog controls.
 
6673
 
 
6674
void * tune_thread(void *)
 
6675
{
 
6676
   void * tune_wthread(void *arg);
 
6677
 
 
6678
   while (true)
 
6679
   {
 
6680
      thread_idle_loop();                                                  //  wait for work or exit request
 
6681
      
 
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);
 
6685
      
 
6686
      while (tune_busy) zsleep(0.004);                                     //  wait for completion
 
6687
 
 
6688
      Fmodified = 1;
 
6689
      mwpaint2();                                                          //  update window
 
6690
   }
 
6691
 
 
6692
   return 0;                                                               //  not executed, stop g++ warning
 
6693
}
 
6694
 
 
6695
 
 
6696
void * tune_wthread(void *arg)                                             //  worker thread function
 
6697
{
 
6698
   void  tune1pix(int px, int py, int dist);
 
6699
 
 
6700
   int      px, py, ii, dist;
 
6701
   int      index = *((int *) arg);
 
6702
 
 
6703
   if (sa_Npixel)                                                          //  process selected area
 
6704
   {
 
6705
      for (ii = index; ii < sa_Npixel; ii += NWthreads)                    //  process all enclosed pixels
 
6706
      {
 
6707
         px = sa_pixel[ii].px;
 
6708
         py = sa_pixel[ii].py;
 
6709
         dist = sa_pixel[ii].dist;
 
6710
         tune1pix(px,py,dist);
 
6711
      }
 
6712
   }
 
6713
 
 
6714
   else                                                                    //  process whole image
 
6715
   {
 
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);
 
6720
   }
 
6721
 
 
6722
   zadd_locked(tune_busy,-1);
 
6723
   pthread_exit(0);
 
6724
}
 
6725
 
 
6726
 
 
6727
void tune1pix(int px, int py, int dist)                                    //  process one pixel
 
6728
{
 
6729
   uint16      *pix1, *pix3;
 
6730
   double      red1, green1, blue1, red3, green3, blue3;
 
6731
   double      brmin, brmax, brout;
 
6732
   int         curveindex;
 
6733
 
 
6734
   pix1 = bmpixel(E1rgb48,px,py);                                          //  input pixel
 
6735
   pix3 = bmpixel(E3rgb48,px,py);                                          //  output pixel
 
6736
   
 
6737
   red1 = red3 = pix1[0];
 
6738
   green1 = green3 = pix1[1];
 
6739
   blue1 = blue3 = pix1[2];
 
6740
 
 
6741
   brmax = red1;                                                           //  brmax = brightest color
 
6742
   if (green1 > brmax) brmax = green1;
 
6743
   if (blue1 > brmax) brmax = blue1;
 
6744
   
 
6745
   curveindex = int(brmax/656);                                            //  index into curve data, 0-99
 
6746
 
 
6747
/* ------------------------------------------------------------------------
 
6748
 
 
6749
      brightness curve values:
 
6750
           0 = dark
 
6751
          50 = normal, unchanged
 
6752
         100 = 200% brightness, clipped
 
6753
*/
 
6754
 
 
6755
   brout = tune_dat[0][curveindex];                                        //  brightness factor, 0 - 99
 
6756
   
 
6757
   if (brout < 49 || brout > 51)
 
6758
   {
 
6759
      brout = brout / 50.0;                                                //  0 - 2.0
 
6760
      if (brout * brmax > 65535.0) brout = 65535.0 / brmax;                //  reduce if necessary
 
6761
      
 
6762
      red3 = red3 * brout;                                                 //  apply to all colors
 
6763
      green3 = green3 * brout;
 
6764
      blue3 = blue3 * brout;
 
6765
   }
 
6766
      
 
6767
/* ------------------------------------------------------------------------
 
6768
 
 
6769
      defog (whiteness) curve values:                                      //  v.8.2
 
6770
           0 = zero whiteness
 
6771
          50 = normal, unchanged
 
6772
         100 = double whiteness, clipped
 
6773
*/
 
6774
 
 
6775
   brout = tune_dat[1][curveindex];                                        //  whiteness factor, 0 - 99
 
6776
   
 
6777
   if (brout < 49 || brout > 51)
 
6778
   {
 
6779
      brmin = red3;                                                        //  brmin = darkest color
 
6780
      if (green3 < brmin) brmin = green3;
 
6781
      if (blue3 < brmin) brmin = blue3;
 
6782
 
 
6783
      brout = brout / 50.0 - 1.0;                                          //  range -1 .. +1
 
6784
      brmin = brmin * brout;                                               //  range -brmin .. +brmin
 
6785
 
 
6786
      if (brmin > 0) 
 
6787
         if (brmax + brmin > 65535.0) brmin = 65535.0 - brmax;             //  prevent overflow
 
6788
      
 
6789
      red3 = red3 + brmin;                                                 //  reduce or add whiteness
 
6790
      green3 = green3 + brmin;
 
6791
      blue3 = blue3 + brmin;
 
6792
   }
 
6793
      
 
6794
/* ------------------------------------------------------------------------
 
6795
 
 
6796
      color intensity curve values:
 
6797
           0 = no color (grey scale)  
 
6798
          50 = normal, unchanged
 
6799
         100 = highest color
 
6800
 
 
6801
      50 >> 0: move all RGB values to their mean: (R+G+B)/3
 
6802
      50 >> 100: increase all RGB values by same factor
 
6803
      
 
6804
      In the 2nd case, the movement is greater for darker pixels
 
6805
*/
 
6806
 
 
6807
   double   red50, green50, blue50, red100, green100, blue100;
 
6808
   double   rgb0, max50, min50, color, bright, ramper;
 
6809
   
 
6810
   brout = tune_dat[2][curveindex];                                        //  brightness factor, 0 - 99
 
6811
   
 
6812
   if (brout < 49 || brout > 51)
 
6813
   {
 
6814
      red50 = red3;                                                        //  50%  color values (normal)
 
6815
      green50 = green3;
 
6816
      blue50 = blue3;
 
6817
      
 
6818
      rgb0 = (red50 + green50 + blue50) / 3;                               //  0%  color values (grey scale)
 
6819
         
 
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;
 
6825
      
 
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
 
6832
 
 
6833
      red100 = int(red50 * ramper);                                        //  100%  color values (max)
 
6834
      green100 = int(green50 * ramper);
 
6835
      blue100 = int(blue50 * ramper);
 
6836
      
 
6837
      if (brout < 50) 
 
6838
      {
 
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);
 
6842
      }
 
6843
 
 
6844
      if (brout > 50)
 
6845
      {
 
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);
 
6849
      }
 
6850
   }
 
6851
 
 
6852
/* ------------------------------------------------------------------------
 
6853
 
 
6854
      color saturation curve values:
 
6855
           0 = no color saturation (gray scale)
 
6856
          50 = normal (initial unmodified RGB)
 
6857
         100 = max. color saturation
 
6858
 
 
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
 
6861
 
 
6862
      In both cases, the average of RGB is not changed.
 
6863
*/
 
6864
 
 
6865
   double   rinc, ginc, binc, scale;
 
6866
   double   spread, spread1, spread2;
 
6867
   int      againlimit = 10;
 
6868
   
 
6869
   brout = tune_dat[3][curveindex];                                        //  saturation factor, 0 - 99
 
6870
   
 
6871
   if (brout < 49 || brout > 51)
 
6872
   {
 
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
 
6876
 
 
6877
      red50 = red3;                                                        //  50%  color values (normal)
 
6878
      green50 = green3;
 
6879
      blue50 = blue3;
 
6880
      
 
6881
      rgb0 = (red50 + green50 + blue50 + 1) / 3;
 
6882
 
 
6883
      rinc = red50 - rgb0;
 
6884
      ginc = green50 - rgb0;
 
6885
      binc = blue50 - rgb0;
 
6886
      
 
6887
      scale = 1.0;
 
6888
 
 
6889
   again:
 
6890
      rinc = scale * rinc;
 
6891
      ginc = scale * ginc;
 
6892
      binc = scale * binc;
 
6893
 
 
6894
      red100 = red50 + rinc;
 
6895
      green100 = green50 + ginc;
 
6896
      blue100 = blue50 + binc;
 
6897
      
 
6898
      if (--againlimit > 0)                                                //  prevent loops   v.8.5.2
 
6899
      {
 
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; }
 
6906
      }
 
6907
      
 
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));
 
6912
      }
 
6913
      else {
 
6914
         red3 = int(red50 + spread2 * (red100 - red50));
 
6915
         green3 = int(green50 + spread2 * (green100 - green50));
 
6916
         blue3 = int(blue50 + spread2 * (blue100 - blue50));
 
6917
      }
 
6918
   }
 
6919
 
 
6920
/* ------------------------------------------------------------------------
 
6921
 
 
6922
      color balance curve values:
 
6923
           0 = 0.5 * original color
 
6924
          50 = unmodified
 
6925
         100 = 1.5 * original color, clipped
 
6926
*/
 
6927
 
 
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);
 
6934
 
 
6935
 
 
6936
/* ------------------------------------------------------------------------
 
6937
   
 
6938
   if working within a select area, blend changes over distance from edge
 
6939
 
 
6940
*/
 
6941
      
 
6942
   double      dold, dnew;
 
6943
 
 
6944
   if (dist < sa_blend) {
 
6945
      dnew = 1.0 * dist / sa_blend;
 
6946
      dold = 1.0 - dnew;
 
6947
      red3 = dnew * red3 + dold * red1;
 
6948
      green3 = dnew * green3 + dold * green1;
 
6949
      blue3 = dnew * blue3 + dold * blue1;
 
6950
   }
 
6951
   
 
6952
   if (red3 > 65535) red3 = 65535;                                         //  clip overflows
 
6953
   if (green3 > 65535) green3 = 65535;
 
6954
   if (blue3 > 65535) blue3 = 65535;
 
6955
 
 
6956
   pix3[0] = int(red3);
 
6957
   pix3[1] = int(green3);
 
6958
   pix3[2] = int(blue3);
 
6959
   
 
6960
   return;
 
6961
}
 
6962
 
 
6963
 
 
6964
/**************************************************************************/
 
6965
 
 
6966
//  red eye removal function
 
6967
 
 
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;
 
6972
};
 
6973
sredmem  redmem[100];                                                      //  store up to 100 red-eyes
 
6974
 
 
6975
int      Nredmem = 0, maxredmem = 100;
 
6976
 
 
6977
 
 
6978
void m_redeye(GtkWidget *, const char *)
 
6979
{
 
6980
   void     redeye_mousefunc();
 
6981
   int      redeye_dialog_compl(zdialog *zd, int zstat);
 
6982
 
 
6983
   const char  *redeye_message 
 
6984
         = ZTX("Method 1:\n"
 
6985
               "  Left-click on red-eye to darken.\n"
 
6986
               "Method 2:\n"
 
6987
               "  Drag down and right to enclose red-eye.\n"
 
6988
               "  Left-click on red-eye to darken.\n"
 
6989
               "Undo red-eye:\n"
 
6990
               "  Right-click on red-eye.");
 
6991
 
 
6992
   if (! edit_setup(0,1)) return;                                          //  setup edit: no preview
 
6993
 
 
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
 
6997
 
 
6998
   Nredmem = 0;
 
6999
   mouseCBfunc = redeye_mousefunc;                                         //  connect mouse function
 
7000
   Mcapture = 1;                                                           //  capture mouse clicks
 
7001
   return;
 
7002
}
 
7003
 
 
7004
 
 
7005
//  dialog completion callback function
 
7006
 
 
7007
int redeye_dialog_compl(zdialog *zd, int zstat)
 
7008
{
 
7009
   mouseCBfunc = 0;                                                        //  disconnect mouse
 
7010
   Mcapture = 0;
 
7011
   if (Nredmem > 0) Fmodified = 1;
 
7012
   if (zstat == 1) edit_done();
 
7013
   else edit_cancel();
 
7014
   toparc = ptoparc = 0;
 
7015
   return 0;
 
7016
}
 
7017
 
 
7018
 
 
7019
//  mouse functions to define, darken, and undo red-eyes
 
7020
 
 
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
 
7028
 
 
7029
 
 
7030
void redeye_mousefunc()
 
7031
{
 
7032
   int         ii, px, py, ww, hh;
 
7033
 
 
7034
   if (Nredmem == maxredmem) {
 
7035
      zmessageACK("%d red-eye limit reached",maxredmem);                   //  too many red-eyes
 
7036
      return;
 
7037
   }
 
7038
 
 
7039
   if (LMclick)                                                            //  left mouse click
 
7040
   {
 
7041
      LMclick = 0;
 
7042
 
 
7043
      px = Mxclick;                                                        //  click position
 
7044
      py = Myclick;
 
7045
      if (px < 0 || px > E3ww-1 || py < 0 || py > E3hh-1) return;          //  outside image area
 
7046
 
 
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
 
7050
   }
 
7051
   
 
7052
   if (RMclick)                                                            //  right mouse click
 
7053
   {
 
7054
      RMclick = 0;
 
7055
      px = Mxclick;                                                        //  click position
 
7056
      py = Myclick;
 
7057
      ii = redeye_find(px,py);                                             //  find red-eye
 
7058
      if (ii >= 0) redeye_remove(ii);                                      //  if found, remove
 
7059
   }
 
7060
 
 
7061
   if (Mxdrag || Mydrag)                                                   //  mouse drag underway
 
7062
   {
 
7063
      px = Mxdown;                                                         //  initial position
 
7064
      py = Mydown;
 
7065
      ww = Mxdrag - Mxdown;                                                //  increment
 
7066
      hh = Mydrag - Mydown;
 
7067
      if (ww < 2 && hh < 2) return;
 
7068
      if (ww < 2) ww = 2;
 
7069
      if (hh < 2) hh = 2;
 
7070
      if (px < 1) px = 1;                                                  //  keep within image area
 
7071
      if (py < 1) py = 1;      
 
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
 
7077
   }
 
7078
 
 
7079
   mwpaint2();
 
7080
   return;
 
7081
}
 
7082
 
 
7083
 
 
7084
//  create type F redeye (1-click automatic)
 
7085
 
 
7086
int redeye_createF(int cx, int cy)
 
7087
{
 
7088
   int         cx0, cy0, cx1, cy1, px, py, rad, radlim;
 
7089
   int         loops, ii;
 
7090
   int         Tnpix, Rnpix, R2npix;
 
7091
   double      rd, rcx, rcy, redpart;
 
7092
   double      Tsum, Rsum, R2sum, Tavg, Ravg, R2avg;
 
7093
   double      sumx, sumy, sumr;
 
7094
   uint16      *ppix;
 
7095
   
 
7096
   cx0 = cx;
 
7097
   cy0 = cy;
 
7098
   
 
7099
   for (loops = 0; loops < 8; loops++)
 
7100
   {
 
7101
      cx1 = cx;
 
7102
      cy1 = cy;
 
7103
 
 
7104
      radlim = redeye_radlim(cx,cy);                                       //  radius limit (image edge)
 
7105
      Tsum = Tavg = Ravg = Tnpix = 0;
 
7106
 
 
7107
      for (rad = 0; rad < radlim-2; rad++)                                 //  find red-eye radius from (cx,cy)
 
7108
      {
 
7109
         Rsum = Rnpix = 0;
 
7110
         R2sum = R2npix = 0;
 
7111
 
 
7112
         for (py = cy-rad-2; py <= cy+rad+2; py++)
 
7113
         for (px = cx-rad-2; px <= cx+rad+2; px++)
 
7114
         {
 
7115
            rd = sqrt((px-cx)*(px-cx) + (py-cy)*(py-cy));
 
7116
            ppix = bmpixel(E3rgb48,px,py);
 
7117
            redpart = redness(ppix);
 
7118
 
 
7119
            if (rd <= rad + 0.5 && rd > rad - 0.5) {                       //  accum. redness at rad
 
7120
               Rsum += redpart;
 
7121
               Rnpix++;
 
7122
            }
 
7123
            else if (rd <= rad + 2.5 && rd > rad + 1.5) {                  //  accum. redness at rad+2
 
7124
               R2sum += redpart;
 
7125
               R2npix++;
 
7126
            }
 
7127
         }
 
7128
         
 
7129
         Tsum += Rsum;
 
7130
         Tnpix += Rnpix;
 
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
 
7136
      }
 
7137
      
 
7138
      sumx = sumy = sumr = 0;
 
7139
      rad = int(1.2 * rad + 1);
 
7140
      if (rad > radlim) rad = radlim;
 
7141
      
 
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)
 
7144
      {
 
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);
 
7151
         sumr += redpart;
 
7152
      }
 
7153
 
 
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
 
7161
   }
 
7162
 
 
7163
   radlim = redeye_radlim(cx,cy);
 
7164
   if (rad > radlim) rad = radlim;
 
7165
 
 
7166
   ii = Nredmem++;                                                         //  add red-eye to memory
 
7167
   redmem[ii].type = 'F';
 
7168
   redmem[ii].cx = cx;
 
7169
   redmem[ii].cy = cy;
 
7170
   redmem[ii].rad = rad;
 
7171
   redmem[ii].clicks = 0;
 
7172
   redmem[ii].thresh = 0;
 
7173
   return ii;
 
7174
}
 
7175
 
 
7176
 
 
7177
//  create type R red-eye (drag an ellipse over red-eye area)
 
7178
 
 
7179
int redeye_createR(int cx, int cy, int ww, int hh)
 
7180
{
 
7181
   int      rad, radlim;
 
7182
 
 
7183
   toparc = 1;                                                             //  paint ellipse over image
 
7184
   toparcx = cx - ww;                                                      //  v.8.3
 
7185
   toparcy = cy - hh;
 
7186
   toparcw = 2 * ww;
 
7187
   toparch = 2 * hh;
 
7188
 
 
7189
   if (ww > hh) rad = ww;
 
7190
   else rad = hh;
 
7191
   radlim = redeye_radlim(cx,cy);
 
7192
   if (rad > radlim) rad = radlim;
 
7193
 
 
7194
   int ii = Nredmem++;                                                     //  add red-eye to memory
 
7195
   redmem[ii].type = 'R';
 
7196
   redmem[ii].cx = cx;
 
7197
   redmem[ii].cy = cy;
 
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;
 
7203
   return ii;
 
7204
}
 
7205
 
 
7206
 
 
7207
//  darken a red-eye and increase click count
 
7208
 
 
7209
void redeye_darken(int ii)
 
7210
{
 
7211
   int         cx, cy, ww, hh, px, py, rad, clicks;
 
7212
   double      rd, thresh, tstep;
 
7213
   char        type;
 
7214
   uint16      *ppix;
 
7215
 
 
7216
   type = redmem[ii].type;
 
7217
   cx = redmem[ii].cx;
 
7218
   cy = redmem[ii].cy;
 
7219
   ww = redmem[ii].ww;
 
7220
   hh = redmem[ii].hh;
 
7221
   rad = redmem[ii].rad;
 
7222
   thresh = redmem[ii].thresh;
 
7223
   tstep = redmem[ii].tstep;
 
7224
   clicks = redmem[ii].clicks++;
 
7225
   
 
7226
   if (thresh == 0)                                                        //  1st click 
 
7227
   {
 
7228
      redeye_distr(ii);                                                    //  get pixel redness distribution
 
7229
      thresh = redmem[ii].thresh;                                          //  initial redness threshhold
 
7230
      tstep = redmem[ii].tstep;                                            //  redness step size
 
7231
      toparc = 0;
 
7232
   }
 
7233
 
 
7234
   tstep = (thresh - tstep) / thresh;                                      //  convert to reduction factor
 
7235
   thresh = thresh * pow(tstep,clicks);                                    //  reduce threshhold by total clicks
 
7236
 
 
7237
   for (py = cy-rad; py <= cy+rad; py++)                                   //  darken pixels over threshhold
 
7238
   for (px = cx-rad; px <= cx+rad; px++)
 
7239
   {
 
7240
      if (type == 'R') {
 
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;
 
7245
      }
 
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));
 
7251
   }
 
7252
 
 
7253
   return;
 
7254
}
 
7255
 
 
7256
 
 
7257
//  Build a distribution of redness for a red-eye. Use this information 
 
7258
//  to set initial threshhold and step size for stepwise darkening.
 
7259
 
 
7260
void redeye_distr(int ii)
 
7261
{
 
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;
 
7265
   char        type;
 
7266
   uint16      *ppix;
 
7267
   
 
7268
   type = redmem[ii].type;
 
7269
   cx = redmem[ii].cx;
 
7270
   cy = redmem[ii].cy;
 
7271
   ww = redmem[ii].ww;
 
7272
   hh = redmem[ii].hh;
 
7273
   rad = redmem[ii].rad;
 
7274
   
 
7275
   maxred = 0;
 
7276
   minred = 100;
 
7277
 
 
7278
   for (py = cy-rad; py <= cy+rad; py++)
 
7279
   for (px = cx-rad; px <= cx+rad; px++)
 
7280
   {
 
7281
      if (type == 'R') {
 
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;
 
7286
      }
 
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;
 
7293
   }
 
7294
   
 
7295
   dbase = minred;
 
7296
   dstep = (maxred - minred) / 19.99;
 
7297
 
 
7298
   for (bin = 0; bin < 20; bin++) dbins[bin] = 0;
 
7299
   npix = 0;
 
7300
 
 
7301
   for (py = cy-rad; py <= cy+rad; py++)
 
7302
   for (px = cx-rad; px <= cx+rad; px++)
 
7303
   {
 
7304
      if (type == 'R') {
 
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;
 
7309
      }
 
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);
 
7315
      ++dbins[bin];
 
7316
      ++npix;
 
7317
   }
 
7318
   
 
7319
   bsum = 0;
 
7320
   blim = int(0.5 * npix);
 
7321
 
 
7322
   for (bin = 0; bin < 20; bin++)                                          //  find redness level for 50% of
 
7323
   {                                                                       //    pixels within red-eye radius
 
7324
      bsum += dbins[bin];
 
7325
      if (bsum > blim) break;
 
7326
   }
 
7327
 
 
7328
   redmem[ii].thresh = dbase + dstep * bin;                                //  initial redness threshhold
 
7329
   redmem[ii].tstep = dstep;                                               //  redness step (5% of range)   v.6.9
 
7330
 
 
7331
   return;
 
7332
}
 
7333
 
 
7334
 
 
7335
//  find a red-eye (nearly) overlapping the mouse click position
 
7336
 
 
7337
int redeye_find(int cx, int cy)
 
7338
{
 
7339
   for (int ii = 0; ii < Nredmem; ii++)
 
7340
   {
 
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) 
 
7345
            return ii;                                                     //  found
 
7346
   }
 
7347
   return -1;                                                              //  not found
 
7348
}
 
7349
 
 
7350
 
 
7351
//  remove a red-eye from memory
 
7352
 
 
7353
void redeye_remove(int ii)
 
7354
{
 
7355
   int      cx, cy, rad, px, py;
 
7356
   uint16   *pix1, *pix3;
 
7357
 
 
7358
   cx = redmem[ii].cx;
 
7359
   cy = redmem[ii].cy;
 
7360
   rad = redmem[ii].rad;
 
7361
 
 
7362
   for (px = cx-rad; px <= cx+rad; px++)
 
7363
   for (py = cy-rad; py <= cy+rad; py++)
 
7364
   {
 
7365
      pix1 = bmpixel(E1rgb48,px,py);
 
7366
      pix3 = bmpixel(E3rgb48,px,py);
 
7367
      pix3[0] = pix1[0];
 
7368
      pix3[1] = pix1[1];
 
7369
      pix3[2] = pix1[2];
 
7370
   }
 
7371
   
 
7372
   for (ii++; ii < Nredmem; ii++) 
 
7373
      redmem[ii-1] = redmem[ii];
 
7374
   Nredmem--;
 
7375
   
 
7376
   toparc = 0;
 
7377
   return;
 
7378
}
 
7379
 
 
7380
 
 
7381
//  compute red-eye radius limit: smaller of 100 and nearest image edge
 
7382
 
 
7383
int redeye_radlim(int cx, int cy)
 
7384
{
 
7385
   int radlim = 100;
 
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;
 
7390
   return radlim;
 
7391
}
 
7392
 
 
7393
 
 
7394
/**************************************************************************/
 
7395
 
 
7396
//  image blur function 
 
7397
 
 
7398
int         blur_busy = 0;
 
7399
int         blur_radius;
 
7400
double      blur_weight[100][100];                                         //  up to blur radius = 99   v.6.3
 
7401
int         blur_Npixels, blur_pixdone;
 
7402
 
 
7403
 
 
7404
void m_blur(GtkWidget *, const char *)
 
7405
{
 
7406
   int    blur_dialog_event(zdialog *zd, const char *event);
 
7407
   int    blur_dialog_compl(zdialog *zd, int zstat);
 
7408
   void * blur_thread(void *);
 
7409
 
 
7410
   if (! edit_setup(0,2)) return;                                          //  setup edit: no preview
 
7411
 
 
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");
 
7417
 
 
7418
   zdialog_run(zdedit,blur_dialog_event,blur_dialog_compl);                //  start dialog
 
7419
   
 
7420
   blur_radius = 1;
 
7421
   start_thread(blur_thread,0);                                            //  start working thread
 
7422
   return;
 
7423
}
 
7424
 
 
7425
 
 
7426
//  blur dialog event and completion callback functions
 
7427
 
 
7428
int blur_dialog_compl(zdialog * zd, int zstat)
 
7429
{
 
7430
   if (zstat == 1) edit_done();                                            //  done
 
7431
   else edit_cancel();                                                     //  cancel or destroy
 
7432
   return 0;
 
7433
}
 
7434
 
 
7435
 
 
7436
int blur_dialog_event(zdialog * zd, const char *event)
 
7437
{
 
7438
   if (strNeq(event,"apply")) return 0;
 
7439
 
 
7440
   zdialog_fetch(zd,"radius",blur_radius);                                 //  get blur radius
 
7441
 
 
7442
   if (blur_radius == 0) {
 
7443
      if (Fmodified) edit_undo();                                          //  restore original image
 
7444
      Fmodified = 0;
 
7445
      return 0;
 
7446
   }
 
7447
 
 
7448
   signal_thread();                                                        //  trigger working thread
 
7449
   wait_thread_idle();                                                     //  wait for completion
 
7450
   mwpaint2(); 
 
7451
   return 1;
 
7452
}
 
7453
 
 
7454
 
 
7455
//  image blur thread function
 
7456
 
 
7457
void * blur_thread(void *)
 
7458
{
 
7459
   void * blur_wthread(void *arg);
 
7460
 
 
7461
   int      dx, dy, rad, rad2;
 
7462
   double   m, d, w, sum;
 
7463
  
 
7464
   while (true)
 
7465
   {
 
7466
      thread_idle_loop();                                                  //  wait for work or exit request
 
7467
 
 
7468
      rad = blur_radius;
 
7469
      rad2 = rad * rad;
 
7470
 
 
7471
      for (dx = 0; dx <= rad; dx++)                                        //  clear weights array
 
7472
      for (dy = 0; dy <= rad; dy++)
 
7473
         blur_weight[dx][dy] = 0;
 
7474
 
 
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)];
 
7478
 
 
7479
      m = sqrt(rad2 + rad2);                                               //  corner pixel distance from center
 
7480
      sum = 0;
 
7481
 
 
7482
      for (dx = 0; dx <= rad; dx++)                                        //  compute weight of pixel
 
7483
      for (dy = 0; dy <= rad; dy++)                                        //    at distance dx, dy
 
7484
      {
 
7485
         d = sqrt(dx*dx + dy*dy);
 
7486
         w = (m + 1 - d) / m;
 
7487
         w = w * w;
 
7488
         sum += blur_weight[dx][dy] * w;
 
7489
         blur_weight[dx][dy] = w;
 
7490
      }
 
7491
 
 
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;
 
7495
         
 
7496
      if (sa_Npixel) blur_Npixels = sa_Npixel;
 
7497
      else  blur_Npixels = E3ww * E3hh;
 
7498
      blur_pixdone = 0;
 
7499
 
 
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);
 
7503
 
 
7504
      while (blur_busy)                                                    //  wait for completion
 
7505
      {
 
7506
         zsleep(0.01);
 
7507
         edit_progress(blur_pixdone,blur_Npixels);                         //  show progress counter
 
7508
      }
 
7509
 
 
7510
      edit_progress(0,0);
 
7511
      Fmodified = 1;
 
7512
      mwpaint2();                                                          //  update window
 
7513
   }
 
7514
 
 
7515
   return 0;                                                               //  not executed, stop g++ warning
 
7516
}
 
7517
 
 
7518
 
 
7519
void * blur_wthread(void *arg)                                             //  worker thread function
 
7520
{
 
7521
   void  blur_pixel(int px, int py, int dist);
 
7522
 
 
7523
   int      index = *((int *) arg);
 
7524
   int      ii, px, py, dist = 0;
 
7525
   
 
7526
   if (! sa_Npixel)                                                        //  process entire image
 
7527
   {
 
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);
 
7531
   }
 
7532
 
 
7533
   if (sa_Npixel)                                                          //  process selected area
 
7534
   {
 
7535
      for (ii = index; ii < sa_Npixel; ii += NWthreads)                    //  process all enclosed pixels
 
7536
      {
 
7537
         px = sa_pixel[ii].px;
 
7538
         py = sa_pixel[ii].py;
 
7539
         dist = sa_pixel[ii].dist;
 
7540
         blur_pixel(px,py,dist);
 
7541
      }
 
7542
   }
 
7543
 
 
7544
   zadd_locked(blur_busy,-1);
 
7545
   pthread_exit(0);
 
7546
}
 
7547
 
 
7548
 
 
7549
void blur_pixel(int px, int py, int dist)
 
7550
{
 
7551
   int         jj, dx, dy, adx, ady, rad;
 
7552
   double      red, green, blue;
 
7553
   double      weight1, weight2, f1, f2;
 
7554
   uint16      *pix1, *pix3, *pixN;
 
7555
 
 
7556
   pix1 = bmpixel(E1rgb48,px,py);                                          //  source pixel
 
7557
   pix3 = bmpixel(E3rgb48,px,py);                                          //  target pixel
 
7558
   
 
7559
   rad = blur_radius;
 
7560
   red = green = blue = 0;
 
7561
   weight2 = 0.0;
 
7562
   
 
7563
   if (! sa_Npixel) 
 
7564
   {
 
7565
      for (dy = -rad; dy <= rad; dy++)                                     //  loop neighbor pixels within radius
 
7566
      for (dx = -rad; dx <= rad; dx++)
 
7567
      {
 
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;
 
7570
         adx = abs(dx);
 
7571
         ady = abs(dy);
 
7572
         pixN = pix1 + (dy * E3ww + dx) * 3;
 
7573
         weight1 = blur_weight[adx][ady];                                  //  weight at distance (dx,dy)
 
7574
         weight2 += weight1;
 
7575
         red += pixN[0] * weight1;                                         //  accumulate contributions
 
7576
         green += pixN[1] * weight1;
 
7577
         blue += pixN[2] * weight1;
 
7578
      }
 
7579
 
 
7580
      red = red / weight2;                                                 //  weighted average   v.6.3
 
7581
      green = green / weight2;
 
7582
      blue = blue / weight2;
 
7583
 
 
7584
      pix3[0] = int(red);
 
7585
      pix3[1] = int(green);
 
7586
      pix3[2] = int(blue);
 
7587
   }
 
7588
   
 
7589
   if (sa_Npixel)
 
7590
   {
 
7591
      for (dy = -rad; dy <= rad; dy++)                                     //  loop neighbor pixels within radius
 
7592
      for (dx = -rad; dx <= rad; dx++)
 
7593
      {
 
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
 
7598
         adx = abs(dx);
 
7599
         ady = abs(dy);
 
7600
         pixN = pix1 + (dy * E3ww + dx) * 3;
 
7601
         weight1 = blur_weight[adx][ady];                                  //  weight at distance (dx,dy)
 
7602
         weight2 += weight1;
 
7603
         red += pixN[0] * weight1;                                         //  accumulate contributions
 
7604
         green += pixN[1] * weight1;
 
7605
         blue += pixN[2] * weight1;
 
7606
      }
 
7607
      
 
7608
      red = red / weight2;                                                 //  weighted average
 
7609
      green = green / weight2;
 
7610
      blue = blue / weight2;
 
7611
 
 
7612
      if (dist < sa_blend) {                                               //  blend changes over sa_blend
 
7613
         f1 = 1.0 * dist / sa_blend;
 
7614
         f2 = 1.0 - f1;
 
7615
         red = f1 * red + f2 * pix1[0];
 
7616
         green = f1 * green + f2 * pix1[1];
 
7617
         blue = f1 * blue + f2 * pix1[2];
 
7618
      }
 
7619
 
 
7620
      pix3[0] = int(red);
 
7621
      pix3[1] = int(green);
 
7622
      pix3[2] = int(blue);
 
7623
   }
 
7624
 
 
7625
   blur_pixdone++;
 
7626
   return;
 
7627
}
 
7628
 
 
7629
 
 
7630
/**************************************************************************/
 
7631
 
 
7632
//  image sharpening function
 
7633
 
 
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];
 
7644
int      sharp_busy;
 
7645
int      sharp_Npixels, sharp_Ndone;
 
7646
 
 
7647
 
 
7648
void m_sharpen(GtkWidget *, const char *)
 
7649
{
 
7650
   int      sharp_dialog_event(zdialog *zd, const char *event);
 
7651
   int      sharp_dialog_compl(zdialog *zd, int zstat);
 
7652
   void *   sharp_thread(void *);
 
7653
 
 
7654
   if (! edit_setup(0,2)) return;                                          //  setup edit: no preview
 
7655
 
 
7656
   zdedit = zdialog_new(ZTX("Sharpen Image"),mWin,Bundo,Bdone,Bcancel,null); 
 
7657
 
 
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");
 
7669
 
 
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");
 
7682
 
 
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");
 
7693
 
 
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
 
7697
   return;
 
7698
}
 
7699
 
 
7700
 
 
7701
//  sharpen dialog event and completion callback functions
 
7702
 
 
7703
int sharp_dialog_compl(zdialog *zd, int zstat)
 
7704
{
 
7705
   if (zstat == 1) edit_undo();
 
7706
   else if (zstat == 2) edit_done();
 
7707
   else edit_cancel();
 
7708
   return 0;
 
7709
}
 
7710
 
 
7711
 
 
7712
int sharp_dialog_event(zdialog *zd, const char *event)
 
7713
{
 
7714
   edit_undo();                                                            //  restore original image
 
7715
 
 
7716
   if (strcmpv(event,"ED","UM","LP",null))
 
7717
   {
 
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);
 
7726
 
 
7727
      strcpy(sharp_function,event);                                        //  pass to working thread
 
7728
      signal_thread();
 
7729
      wait_thread_idle();
 
7730
   }
 
7731
 
 
7732
   return 0;
 
7733
}
 
7734
 
 
7735
 
 
7736
//  sharpen image thread function
 
7737
 
 
7738
void * sharp_thread(void *)
 
7739
{
 
7740
   int    sharp_ED();
 
7741
   int    sharp_UM();
 
7742
   int    sharp_LP();
 
7743
   
 
7744
   while (true)
 
7745
   {
 
7746
      thread_idle_loop();                                                  //  wait for work or exit request
 
7747
      
 
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();
 
7751
 
 
7752
      Fmodified = 1;
 
7753
      mwpaint2(); 
 
7754
   }
 
7755
 
 
7756
   return 0;                                                               //  not executed, stop g++ warning
 
7757
}
 
7758
 
 
7759
 
 
7760
//  image sharpen function by edge detection and compression
 
7761
 
 
7762
int sharp_ED()
 
7763
{
 
7764
   void  sharp_pixel_ED(int px, int py, int dist, int thresh);
 
7765
 
 
7766
   int      sharp_thresh1 = 100;                                           //  initial threshold
 
7767
   double   sharp_thresh2 = 0.01 * sharp_ED_reduce;                        //  decline rate
 
7768
   int      px, py, dist, thresh;
 
7769
   int      ii, cycles;
 
7770
   
 
7771
   thresh = sharp_thresh1;
 
7772
    
 
7773
   for (cycles = 0; cycles < sharp_ED_cycles; cycles++)
 
7774
   {
 
7775
      if (cycles > 0) thresh = int(thresh * sharp_thresh2);
 
7776
 
 
7777
      if (! sa_Npixel)                                                     //  process entire image
 
7778
      {
 
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);
 
7783
      }
 
7784
 
 
7785
      if (sa_Npixel)                                                       //  selected area to sharpen
 
7786
      {
 
7787
         for (ii = 0; ii < sa_Npixel; ii++)                                //  process all enclosed pixels
 
7788
         {
 
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);
 
7795
         }
 
7796
      }
 
7797
 
 
7798
      edit_progress(cycles,sharp_ED_cycles);                               //  v.6.3
 
7799
   }
 
7800
 
 
7801
   edit_progress(0,0);
 
7802
   return 1;
 
7803
}
 
7804
 
 
7805
 
 
7806
void sharp_pixel_ED(int px, int py, int dist, int thresh)
 
7807
{
 
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;
 
7814
   double   f1, f2;
 
7815
   
 
7816
   pthresh = sharp_ED_thresh;                                              //  pthresh = larger
 
7817
   if (thresh > pthresh) pthresh = thresh;
 
7818
 
 
7819
   pix1 = bmpixel(E1rgb48,px,py);                                          //  input pixel
 
7820
   pix3 = bmpixel(E3rgb48,px,py);                                          //  output pixel
 
7821
 
 
7822
   for (dd = 0; dd < 4; dd++)                                              //  4 directions
 
7823
   {
 
7824
      pix3u = pix3 + (dy[dd] * E3ww + dx[dd]) * 3;                         //  upstream pixel
 
7825
      pix3d = pix3 - (dy[dd] * E3ww - dx[dd]) * 3;                         //  downstream pixel
 
7826
 
 
7827
      for (rgb = 0; rgb < 3; rgb++)                                        //  loop 3 RGB colors
 
7828
      {
 
7829
         pv2 = pix3[rgb];
 
7830
         pv2u = pix3u[rgb];                                                //  brightness difference
 
7831
         pv2d = pix3d[rgb];                                                //    across target pixel
 
7832
 
 
7833
         pvdiff = pv2d - pv2u;
 
7834
         if (pvdiff < 0) pvdiff = -pvdiff;
 
7835
         if (pvdiff < 256 * pthresh) continue;                             //  brightness slope < threshold
 
7836
 
 
7837
         if (pv2u < pv2 && pv2 < pv2d)                                     //  slope up, monotone
 
7838
         {
 
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];
 
7843
 
 
7844
            if (pv2uu >= pv2u) {                                           //  shift focus of changes to
 
7845
               pix3u = pix3;                                               //    avoid up/down/up jaggies
 
7846
               pv2u = pv2;
 
7847
            }
 
7848
            
 
7849
            if (pv2dd <= pv2d) {
 
7850
               pix3d = pix3;
 
7851
               pv2d = pv2;
 
7852
            }
 
7853
               
 
7854
            if (pv2u > 256) pv2u -= 256;
 
7855
            if (pv2d < 65279) pv2d += 256;
 
7856
         }
 
7857
         
 
7858
         else if (pv2u > pv2 && pv2 > pv2d)                                //  slope down, monotone
 
7859
         {
 
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];
 
7864
 
 
7865
            if (pv2uu <= pv2u) {
 
7866
               pix3u = pix3;
 
7867
               pv2u = pv2;
 
7868
            }
 
7869
            
 
7870
            if (pv2dd >= pv2d) {
 
7871
               pix3d = pix3;
 
7872
               pv2d = pv2;
 
7873
            }
 
7874
 
 
7875
            if (pv2d > 256) pv2d -= 256;
 
7876
            if (pv2u < 65279) pv2u += 256;
 
7877
         }
 
7878
 
 
7879
         else continue;                                                    //  slope too small
 
7880
 
 
7881
         if (sa_Npixel && dist < sa_blend) {                               //  if area selection, blend pixel
 
7882
            f1 = 1.0 * dist / sa_blend;                                    //    changes over sa_blend
 
7883
            f2 = 1.0 - f1;
 
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]);
 
7888
         }
 
7889
 
 
7890
         pix3u[rgb] = pv2u;                                                //  modified brightness values
 
7891
         pix3d[rgb] = pv2d;                                                //    >> image3 pixel
 
7892
      }
 
7893
   }
 
7894
 
 
7895
   return;
 
7896
}
 
7897
 
 
7898
 
 
7899
//  image sharpen function using unsharp mask
 
7900
 
 
7901
int sharp_UM()
 
7902
{
 
7903
   void * sharp_UM_wthread(void *arg);
 
7904
   
 
7905
   if (sa_Npixel) sharp_Npixels = sa_Npixel;
 
7906
   else  sharp_Npixels = E3ww * E3hh;
 
7907
   sharp_Ndone = 0;
 
7908
 
 
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);
 
7912
 
 
7913
   while (sharp_busy)                                                      //  wait for completion
 
7914
   {
 
7915
      zsleep(0.01);
 
7916
      edit_progress(sharp_Ndone,sharp_Npixels);                            //  show progress counter
 
7917
   }
 
7918
 
 
7919
   edit_progress(0,0);
 
7920
   return 1;
 
7921
}
 
7922
 
 
7923
 
 
7924
void * sharp_UM_wthread(void *arg)                                         //  worker thread function   v.7.7
 
7925
{
 
7926
   void  sharp_pixel_UM(int px, int py, int dist);
 
7927
 
 
7928
   int      index = *((int *) arg);
 
7929
   int      ii, px, py, dist, rad;
 
7930
   
 
7931
   rad = sharp_UM_radius;
 
7932
 
 
7933
   if (! sa_Npixel)                                                        //  process entire image
 
7934
   {
 
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
 
7938
      {
 
7939
         sharp_pixel_UM(px,py,dist);
 
7940
         sharp_Ndone++;
 
7941
      }
 
7942
   }
 
7943
 
 
7944
   if (sa_Npixel)                                                          //  selected area to sharpen
 
7945
   {
 
7946
      for (ii = index; ii < sa_Npixel; ii += NWthreads)                    //  process all enclosed pixels
 
7947
      {
 
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);
 
7954
         sharp_Ndone++;
 
7955
      }
 
7956
   }
 
7957
 
 
7958
   zadd_locked(sharp_busy,-1);
 
7959
   pthread_exit(0);
 
7960
}
 
7961
 
 
7962
 
 
7963
void sharp_pixel_UM(int px, int py, int dist)                              //  process one pixel
 
7964
{
 
7965
   int         rad, dx, dy, rgb;
 
7966
   int         incr, cval, mean;
 
7967
   uint16      *pix1, *pix3, *pixN;
 
7968
   double      rad2, f1, f2;
 
7969
 
 
7970
   pix1 = bmpixel(E1rgb48,px,py);                                          //  input pixel
 
7971
   pix3 = bmpixel(E3rgb48,px,py);                                          //  output pixel
 
7972
 
 
7973
   rad = sharp_UM_radius;
 
7974
   rad2 = 2 * rad + 1;
 
7975
   rad2 = 1.0 / (rad2 * rad2);                                             //  1 / area of unsharp mask
 
7976
 
 
7977
   for (rgb = 0; rgb < 3; rgb++)                                           //  loop 3 RGB colors
 
7978
   {
 
7979
      mean = 0;
 
7980
 
 
7981
      for (dy = -rad; dy <= rad; dy++)                                     //  loop neighbor pixels within radius
 
7982
      for (dx = -rad; dx <= rad; dx++)                                     //  outer loop y
 
7983
      {
 
7984
         pixN = pix1 + (dy * E3ww + dx) * 3;                               //  compute mean brightness
 
7985
         mean += pixN[rgb];
 
7986
      }
 
7987
 
 
7988
      mean = int(mean * rad2 + 0.5);
 
7989
      
 
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;
 
7997
      
 
7998
      if (sa_Npixel && dist < sa_blend) {                                  //  if area selection, blend pixel
 
7999
         f1 = 1.0 * dist / sa_blend;                                       //    changes over sa_blend
 
8000
         f2 = 1.0 - f1;
 
8001
         cval = int(f1 * cval + f2 * pix1[rgb]);
 
8002
      }
 
8003
      
 
8004
      pix3[rgb] = cval;
 
8005
   }
 
8006
   
 
8007
   return;
 
8008
}
 
8009
 
 
8010
 
 
8011
//  image sharpen function using Lapacian method
 
8012
 
 
8013
int sharp_LP()
 
8014
{
 
8015
   void * sharp_LP_wthread(void *arg);
 
8016
 
 
8017
   int         rad, ii, jj, dx, dy;
 
8018
   double      rad2, kern, kernsum;
 
8019
 
 
8020
   rad = sharp_LP_radius;
 
8021
   rad2 = rad * rad;
 
8022
   kernsum = 0;
 
8023
 
 
8024
   for (dy = -rad; dy <= rad; dy++)                                        //  kernel, Gaussian distribution
 
8025
   for (dx = -rad; dx <= rad; dx++)
 
8026
   {
 
8027
      ii = dx + rad;
 
8028
      jj = dy + rad;
 
8029
      kern = (0.5 / rad2) * exp( -(dx*dx + dy*dy) / (2 * rad2));
 
8030
      if (dx || dy) {
 
8031
         sharp_kernel[ii][jj] = -kern;                                     //  surrounding cells < 0
 
8032
         kernsum += kern;
 
8033
      }
 
8034
   }
 
8035
   
 
8036
   sharp_kernel[rad][rad] = kernsum + 1.0;                                 //  middle cell, total = +1
 
8037
 
 
8038
   if (sa_Npixel) sharp_Npixels = sa_Npixel;
 
8039
   else  sharp_Npixels = E3ww * E3hh;
 
8040
   sharp_Ndone = 0;
 
8041
 
 
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);
 
8045
 
 
8046
   while (sharp_busy)                                                      //  wait for completion
 
8047
   {
 
8048
      zsleep(0.01);
 
8049
      edit_progress(sharp_Ndone,sharp_Npixels);                            //  show progress counter
 
8050
   }
 
8051
 
 
8052
   edit_progress(0,0);
 
8053
   return 1;
 
8054
}
 
8055
 
 
8056
 
 
8057
void * sharp_LP_wthread(void *arg)                                         //  worker thread function   v.7.7
 
8058
{
 
8059
   void  sharp_pixel_LP(int px, int py, int dist);
 
8060
 
 
8061
   int         index = *((int *) arg);
 
8062
   int         ii, rad, px, py, dist;
 
8063
 
 
8064
   rad = sharp_LP_radius;
 
8065
 
 
8066
   if (! sa_Npixel)                                                        //  process entire image
 
8067
   {
 
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
 
8071
      {
 
8072
         sharp_pixel_LP(px,py,dist);
 
8073
         sharp_Ndone++;
 
8074
      }
 
8075
   }
 
8076
 
 
8077
   if (sa_Npixel)                                                          //  selected area to sharpen
 
8078
   {
 
8079
      for (ii = index; ii < sa_Npixel; ii += NWthreads)                    //  process all enclosed pixels
 
8080
      {
 
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);
 
8087
         sharp_Ndone++;
 
8088
      }
 
8089
   }
 
8090
 
 
8091
   zadd_locked(sharp_busy,-1);
 
8092
   pthread_exit(0);
 
8093
}
 
8094
 
 
8095
 
 
8096
void sharp_pixel_LP(int px, int py, int dist)                              //  process one pixel
 
8097
{
 
8098
   uint16      *pix1, *pix3, *pixN;
 
8099
 
 
8100
   int         rad, dx, dy, rgb;
 
8101
   int         sumpix[3], maxrgb;
 
8102
   double      kern, f1, f2;
 
8103
   double      scale, scale1, scale2;
 
8104
 
 
8105
   pix1 = bmpixel(E1rgb48,px,py);                                          //  input pixel
 
8106
   pix3 = bmpixel(E3rgb48,px,py);                                          //  output pixel
 
8107
 
 
8108
   rad = sharp_LP_radius;
 
8109
   sumpix[0] = sumpix[1] = sumpix[2] = 0;      
 
8110
 
 
8111
   for (dy = -rad; dy <= rad; dy++)                                        //  loop surrounding block of pixels
 
8112
   for (dx = -rad; dx <= rad; dx++)                                        //  outer loop y
 
8113
   {
 
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
 
8118
   }
 
8119
   
 
8120
   maxrgb = sumpix[0];
 
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;
 
8126
   
 
8127
   if (maxrgb > 65535) scale = 65535.0 / maxrgb;
 
8128
   else scale = 1.0;
 
8129
   scale1 = (100 - sharp_LP_amount) / 100.0;
 
8130
   scale2 = (1.0 - scale1) * scale;
 
8131
 
 
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]);
 
8135
   
 
8136
   if (sa_Npixel && dist < sa_blend) {                                     //  if area selection, blend pixel
 
8137
      f1 = 1.0 * dist / sa_blend;                                          //    changes over sa_blend
 
8138
      f2 = 1.0 - f1;
 
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]);
 
8142
   }
 
8143
 
 
8144
   return;
 
8145
}
 
8146
 
 
8147
 
 
8148
/**************************************************************************/
 
8149
 
 
8150
//  image noise reduction
 
8151
 
 
8152
int      denoise_method = 5;
 
8153
int      denoise_radius = 4;
 
8154
int      denoise_busy;
 
8155
int      denoise_Npixels, denoise_Ndone;
 
8156
 
 
8157
void m_denoise(GtkWidget *, const char *)
 
8158
{
 
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 *);
 
8162
 
 
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.");
 
8166
 
 
8167
   if (! edit_setup(0,2)) return;                                          //  setup edit: no preview
 
8168
 
 
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");
 
8180
   
 
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"));
 
8185
 
 
8186
   zdialog_stuff(zdedit,"method",ZTX("top hat filter by color"));          //  default
 
8187
 
 
8188
   zdialog_run(zdedit,denoise_dialog_event,denoise_dialog_compl);          //  run dialog
 
8189
   start_thread(denoise_thread,0);                                         //  start working thread
 
8190
   return;
 
8191
}
 
8192
 
 
8193
 
 
8194
//  denoise dialog event and completion callback functions
 
8195
 
 
8196
int denoise_dialog_compl(zdialog *zd, int zstat)
 
8197
{
 
8198
   if (zstat == 1) edit_done();                                            //  done
 
8199
   else edit_cancel();                                                     //  cancel or destroy
 
8200
   return 0;
 
8201
}
 
8202
 
 
8203
 
 
8204
int denoise_dialog_event(zdialog * zd, const char *event)
 
8205
{
 
8206
   char     method[40];
 
8207
 
 
8208
   if (strEqu(event,"radius")) 
 
8209
      zdialog_fetch(zd,"radius",denoise_radius);
 
8210
 
 
8211
   if (strEqu(event,"method")) 
 
8212
   {
 
8213
      zdialog_fetch(zd,"method",method,39);
 
8214
 
 
8215
      if (strEqu(method,"flatten outliers by color (1)")) {
 
8216
         denoise_method = 1;
 
8217
         denoise_radius = 1;
 
8218
      }
 
8219
 
 
8220
      if (strEqu(method,"flatten outliers by color (2)")) {
 
8221
         denoise_method = 2;
 
8222
         denoise_radius = 3;
 
8223
      }
 
8224
 
 
8225
      if (strEqu(method,"set median brightness by color")) {
 
8226
         denoise_method = 4;
 
8227
         denoise_radius = 2;
 
8228
      }
 
8229
 
 
8230
      if (strEqu(method,"top hat filter by color")) {
 
8231
         denoise_method = 5;
 
8232
         denoise_radius = 4;
 
8233
      }
 
8234
      
 
8235
      zdialog_stuff(zd,"radius",denoise_radius);
 
8236
   }
 
8237
   
 
8238
   if (strEqu(event,"reduce")) {
 
8239
      signal_thread();                                                     //  trigger update thread
 
8240
      wait_thread_idle();                                                  //  wait for thread done
 
8241
   }
 
8242
 
 
8243
   if (strEqu(event,"Undo")) edit_undo();
 
8244
   return 1;
 
8245
}
 
8246
 
 
8247
 
 
8248
//  image noise reduction thread
 
8249
 
 
8250
void * denoise_thread(void *)
 
8251
{
 
8252
   void * denoise_wthread(void *arg);
 
8253
 
 
8254
   while (true)
 
8255
   {
 
8256
      thread_idle_loop();                                                  //  wait for work or exit request
 
8257
 
 
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;
 
8262
      denoise_Ndone = 0;
 
8263
 
 
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);
 
8267
 
 
8268
      while (denoise_busy)                                                 //  wait for completion
 
8269
      {
 
8270
         zsleep(0.01);
 
8271
         edit_progress(denoise_Ndone,denoise_Npixels);                     //  show progress counter
 
8272
      }
 
8273
 
 
8274
      edit_progress(0,0);
 
8275
 
 
8276
      mutex_lock(&pixmaps_lock);
 
8277
      RGB_free(E3rgb48);                                                   //  image9 >> image3
 
8278
      E3rgb48 = E9rgb48;
 
8279
      E9rgb48 = 0;
 
8280
      mutex_unlock(&pixmaps_lock);
 
8281
 
 
8282
      Fmodified = 1;
 
8283
      mwpaint2();                                                          //  update window
 
8284
   }
 
8285
 
 
8286
   return 0;                                                               //  not executed, stop g++ warning
 
8287
}
 
8288
 
 
8289
 
 
8290
void * denoise_wthread(void *arg)                                          //  worker thread function   v.7.7
 
8291
{
 
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);
 
8296
   
 
8297
   int         index = *((int *) arg);
 
8298
   int         ii, px, py, dist, rad;
 
8299
   double      f1, f2;
 
8300
   uint16      *pix1, *pix3, *pix9;
 
8301
 
 
8302
   rad = denoise_radius;
 
8303
 
 
8304
   if (sa_Npixel)
 
8305
   {
 
8306
      for (ii = index; ii < sa_Npixel; ii += NWthreads)                    //  process pixels in select area
 
8307
      {
 
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);
 
8320
 
 
8321
         if (dist < sa_blend) {                                            //  blend changes over sa_blend
 
8322
            f1 = 1.0 * dist / sa_blend;
 
8323
            f2 = 1.0 - f1;
 
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]);
 
8327
         }
 
8328
 
 
8329
         denoise_Ndone++;
 
8330
      }
 
8331
   }
 
8332
 
 
8333
   if (! sa_Npixel)
 
8334
   {
 
8335
      for (py = index+rad; py < E3hh-rad; py += NWthreads)                 //  loop all image3 pixels
 
8336
      for (px = rad; px < E3ww-rad; px++)
 
8337
      {
 
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);
 
8344
         denoise_Ndone++;
 
8345
      }
 
8346
   }
 
8347
 
 
8348
   zadd_locked(denoise_busy,-1);
 
8349
   pthread_exit(0);
 
8350
}
 
8351
 
 
8352
 
 
8353
//  flatten outliers within radius, by color 
 
8354
//  an outlier is the max or min value within a radius
 
8355
 
 
8356
void denoise_func1(uint16 *pix3, uint16 *pix9)
 
8357
{
 
8358
   int         dy, dx, rad;
 
8359
   int         min0, min1, min2, max0, max1, max2;
 
8360
   uint16      *pixN;
 
8361
 
 
8362
   min0 = min1 = min2 = 65535;
 
8363
   max0 = max1 = max2 = 0;
 
8364
   rad = denoise_radius;
 
8365
 
 
8366
   for (dy = -rad; dy <= rad; dy++)                                        //  loop surrounding pixels
 
8367
   for (dx = -rad; dx <= rad; dx++)
 
8368
   {
 
8369
      if (dy == 0 && dx == 0) continue;                                    //  skip self
 
8370
 
 
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];
 
8378
   }
 
8379
   
 
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;
 
8386
   
 
8387
   return;
 
8388
}
 
8389
 
 
8390
 
 
8391
//  flatten outliers
 
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.
 
8394
 
 
8395
void denoise_func2(uint16 *pix3, uint16 *pix9)                             //  v.8.5
 
8396
{
 
8397
   int         rgb, dy, dx, rad, nn;
 
8398
   double      nn1, val, sum, sum2, mean, variance, sigma;
 
8399
   uint16      *pixN;
 
8400
 
 
8401
   rad = denoise_radius;
 
8402
   nn = (rad * 2 + 1);
 
8403
   nn = nn * nn - 1;
 
8404
   nn1 = 1.0 / nn;
 
8405
 
 
8406
   for (rgb = 0; rgb < 3; rgb++)                                           //  loop RGB color
 
8407
   {
 
8408
      sum = sum2 = 0;
 
8409
 
 
8410
      for (dy = -rad; dy <= rad; dy++)                                     //  loop surrounding pixels
 
8411
      for (dx = -rad; dx <= rad; dx++)
 
8412
      {
 
8413
         if (dy == 0 && dx == 0) continue;                                 //  skip self
 
8414
         pixN = pix3 + (dy * E3ww + dx) * 3;
 
8415
         val = pixN[rgb];
 
8416
         sum += val;
 
8417
         sum2 += val * val;
 
8418
      }
 
8419
      
 
8420
      mean = nn1 * sum;
 
8421
      variance = nn1 * (sum2 - 2.0 * mean * sum) + mean * mean;
 
8422
      sigma = sqrt(variance);
 
8423
 
 
8424
      val = pix3[rgb];      
 
8425
      if (val > mean + sigma) {                                            //  move value to mean +/- sigma
 
8426
         val = mean + sigma;                                               //  v.8.6
 
8427
         pix9[rgb] = val;
 
8428
      }
 
8429
      else if (val < mean - sigma) {
 
8430
         val = mean - sigma;
 
8431
         pix9[rgb] = val;
 
8432
      }
 
8433
   }
 
8434
   
 
8435
   return;
 
8436
}
 
8437
 
 
8438
 
 
8439
//  use median brightness for pixels within radius
 
8440
 
 
8441
void denoise_func4(uint16 *pix3, uint16 *pix9)
 
8442
{
 
8443
   int         dy, dx, rad;
 
8444
   int         ns, rgb, bsortN[400];
 
8445
   uint16      *pixN;
 
8446
 
 
8447
   rad = denoise_radius;
 
8448
 
 
8449
   for (rgb = 0; rgb < 3; rgb++)                                           //  loop all RGB colors
 
8450
   {
 
8451
      ns = 0;
 
8452
 
 
8453
      for (dy = -rad; dy <= rad; dy++)                                     //  loop surrounding pixels
 
8454
      for (dx = -rad; dx <= rad; dx++)                                     //  get brightness values
 
8455
      {
 
8456
         pixN = pix3 + (dy * E3ww + dx) * 3;
 
8457
         bsortN[ns] = pixN[rgb];
 
8458
         ns++;
 
8459
      }
 
8460
 
 
8461
      HeapSort(bsortN,ns);
 
8462
      pix9[rgb] = bsortN[ns/2];                                            //  median brightness of ns pixels
 
8463
   }
 
8464
 
 
8465
   return;
 
8466
}
 
8467
 
 
8468
 
 
8469
//  modified top hat filter: execute with increasing radius from 1 to limit
 
8470
//  detect outlier by comparing with pixels in outer radius
 
8471
 
 
8472
void denoise_func5(uint16 *pix3, uint16 *pix9)
 
8473
{
 
8474
   int         dy, dx, rad;
 
8475
   int         min0, min1, min2, max0, max1, max2;
 
8476
   uint16      *pixN;
 
8477
 
 
8478
   for (rad = 1; rad <= denoise_radius; rad++)
 
8479
   for (int loops = 0; loops < 2; loops++)
 
8480
   {
 
8481
      min0 = min1 = min2 = 65535;
 
8482
      max0 = max1 = max2 = 0;
 
8483
 
 
8484
      for (dy = -rad; dy <= rad; dy++)                                     //  loop all pixels within rad
 
8485
      for (dx = -rad; dx <= rad; dx++)
 
8486
      {
 
8487
         if (dx > -rad && dx < rad) continue;                              //  skip inner pixels
 
8488
         if (dy > -rad && dy < rad) continue;
 
8489
 
 
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];
 
8497
      }
 
8498
      
 
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;
 
8505
   }
 
8506
 
 
8507
   return;
 
8508
}
 
8509
 
 
8510
 
 
8511
/**************************************************************************/
 
8512
 
 
8513
//  trim image - use mouse to select image region to retain
 
8514
 
 
8515
int      trimx1, trimy1, trimx2, trimy2;                                   //  trim rectangle
 
8516
int      trimww, trimhh;
 
8517
double   trimR;                                                                                                                                                         //  trim aspect ratio
 
8518
int      trim_status = 0;
 
8519
 
 
8520
 
 
8521
void m_trim(GtkWidget *, const char *)
 
8522
{
 
8523
   void     trim_mousefunc();
 
8524
   int      trim_dialog_compl(zdialog *zd, int zstat);
 
8525
   void   * trim_thread(void *);
 
8526
 
 
8527
   const char  *trim_message = ZTX("Drag middle to move \n"
 
8528
                                   "Drag corners to resize");
 
8529
   char        text[40];
 
8530
   
 
8531
   if (! edit_setup(1,0)) return;                                          //  setup edit: use preview   v.8.4.1
 
8532
 
 
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
 
8541
 
 
8542
   zdialog_run(zdedit,0,trim_dialog_compl);                                //  run dialog, parallel
 
8543
 
 
8544
   mouseCBfunc = trim_mousefunc;                                           //  connect mouse function
 
8545
   Mcapture++;
 
8546
   
 
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);
 
8551
 
 
8552
   trimww = (trimx2 - trimx1);
 
8553
   trimhh = (trimy2 - trimy1);
 
8554
   trimR = 1.0 * trimww / trimhh;
 
8555
   
 
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);
 
8559
 
 
8560
   trim_status = 0;
 
8561
   start_thread(trim_thread,0);                                            //  start working thread
 
8562
   signal_thread();
 
8563
   return;
 
8564
}
 
8565
 
 
8566
 
 
8567
//  trim dialog completion callback function
 
8568
 
 
8569
int trim_dialog_compl(zdialog *zd, int zstat)
 
8570
{
 
8571
   mouseCBfunc = 0;                                                        //  disconnect mouse
 
8572
   Mcapture = 0;
 
8573
   
 
8574
   if (zstat != 1) {
 
8575
      edit_cancel();
 
8576
      return 0;
 
8577
   }
 
8578
   
 
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;
 
8585
 
 
8586
   trim_status = 1;                                                        //  trim full image
 
8587
   Fmodified = 1;
 
8588
   edit_done();
 
8589
   return 0;
 
8590
}
 
8591
 
 
8592
 
 
8593
//  trim mouse function                                                    //  overhauled  v.8.4.1
 
8594
 
 
8595
void trim_mousefunc()
 
8596
{
 
8597
   int         mpx, mpy, rlock;
 
8598
   int         corner, moveall = 0;
 
8599
   int         dx, dy, dd, d1, d2, d3, d4;
 
8600
   char        text[40];
 
8601
   double      drr;
 
8602
   
 
8603
   if (LMclick || Mxdrag || Mydrag)                                        //  mouse click or drag
 
8604
   {
 
8605
      if (LMclick) {
 
8606
         mpx = Mxclick;
 
8607
         mpy = Myclick;
 
8608
         LMclick = 0;
 
8609
      }
 
8610
      else {
 
8611
         mpx = Mxdrag;
 
8612
         mpy = Mydrag;
 
8613
      }
 
8614
      
 
8615
      if (Mxdrag || Mydrag) {
 
8616
         moveall = 1;
 
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;
 
8623
      }
 
8624
 
 
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
 
8631
         Mydown = Mydrag;
 
8632
         corner = 0;
 
8633
      }
 
8634
 
 
8635
      else {                                                               //  no, find closest corner
 
8636
         dx = mpx - trimx1;
 
8637
         dy = mpy - trimy1;
 
8638
         d1 = sqrt(dx*dx + dy*dy);
 
8639
         
 
8640
         dx = mpx - trimx2;
 
8641
         dy = mpy - trimy1;
 
8642
         d2 = sqrt(dx*dx + dy*dy);
 
8643
         
 
8644
         dx = mpx - trimx2;
 
8645
         dy = mpy - trimy2;
 
8646
         d3 = sqrt(dx*dx + dy*dy);
 
8647
         
 
8648
         dx = mpx - trimx1;
 
8649
         dy = mpy - trimy2;
 
8650
         d4 = sqrt(dx*dx + dy*dy);
 
8651
         
 
8652
         corner = 1;
 
8653
         dd = d1;
 
8654
         if (d2 < dd) { corner = 2; dd = d2; }
 
8655
         if (d3 < dd) { corner = 3; dd = d3; }
 
8656
         if (d4 < dd) { corner = 4; dd = d4; }
 
8657
         
 
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; }
 
8662
      }
 
8663
 
 
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;
 
8670
 
 
8671
      zdialog_fetch(zdedit,"lockwh",rlock);                                //  w/h ratio locked
 
8672
      if (rlock && corner) {
 
8673
         if (corner < 3)
 
8674
            trimy2 = trimy1 + 1.0 * (trimx2 - trimx1) / trimR;
 
8675
         else
 
8676
            trimy1 = trimy2 - 1.0 * (trimx2 - trimx1) / trimR;
 
8677
      }
 
8678
 
 
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;
 
8685
 
 
8686
      trimww = trimx2 - trimx1;                                            //  new rectangle dimensions
 
8687
      trimhh = trimy2 - trimy1;
 
8688
 
 
8689
      drr = 1.0 * trimww / trimhh;                                         //  new w/h ratio
 
8690
      if (! rlock) trimR = drr;
 
8691
 
 
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);
 
8695
      
 
8696
      signal_thread();                                                     //  trigger update thread
 
8697
   }
 
8698
 
 
8699
   return;
 
8700
}
 
8701
 
 
8702
 
 
8703
//  trim thread function
 
8704
 
 
8705
void * trim_thread(void *)
 
8706
{
 
8707
   int      px1, py1, px2, py2;
 
8708
   uint16   *pix1, *pix3;
 
8709
   
 
8710
   while (true)
 
8711
   {
 
8712
      thread_idle_loop();                                                  //  wait for work or exit request
 
8713
      
 
8714
      if (trim_status == 0)                                                //  darken margins   v.8.4
 
8715
      {
 
8716
         for (py1 = 0; py1 < E3hh; py1++)                                  //  copy pixels E1 >> E3
 
8717
         for (px1 = 0; px1 < E3ww; px1++)
 
8718
         {
 
8719
            pix1 = bmpixel(E1rgb48,px1,py1);
 
8720
            pix3 = bmpixel(E3rgb48,px1,py1);
 
8721
 
 
8722
            if (px1 < trimx1 || px1 > trimx2 || py1 < trimy1 || py1 > trimy2)
 
8723
            {
 
8724
               pix3[0] = pix1[0] / 2;
 
8725
               pix3[1] = pix1[1] / 2;
 
8726
               pix3[2] = pix1[2] / 2;
 
8727
            }
 
8728
 
 
8729
            else
 
8730
            {
 
8731
               pix3[0] = pix1[0];
 
8732
               pix3[1] = pix1[1];
 
8733
               pix3[2] = pix1[2];
 
8734
            }
 
8735
         }
 
8736
 
 
8737
         mwpaint2();                                                       //  update window
 
8738
      }
 
8739
      
 
8740
      if (trim_status == 1)                                                //  do the trim
 
8741
      {
 
8742
         mutex_lock(&pixmaps_lock);
 
8743
         RGB_free(E3rgb48);
 
8744
         E3rgb48 = RGB_make(trimww,trimhh,48);                             //  new pixmap with requested size
 
8745
         E3ww = trimww;
 
8746
         E3hh = trimhh;
 
8747
         
 
8748
         for (py1 = trimy1; py1 < trimy2; py1++)                           //  copy pixels
 
8749
         for (px1 = trimx1; px1 < trimx2; px1++)
 
8750
         {
 
8751
            px2 = px1 - trimx1;
 
8752
            py2 = py1 - trimy1;
 
8753
            pix1 = bmpixel(E1rgb48,px1,py1);
 
8754
            pix3 = bmpixel(E3rgb48,px2,py2);
 
8755
            pix3[0] = pix1[0];
 
8756
            pix3[1] = pix1[1];
 
8757
            pix3[2] = pix1[2];
 
8758
         }
 
8759
 
 
8760
         Fmodified = 1;
 
8761
         mutex_unlock(&pixmaps_lock);
 
8762
         mwpaint2();                                                       //  update window
 
8763
      }
 
8764
   }
 
8765
 
 
8766
   return 0;                                                               //  not executed, stop g++ warning
 
8767
}
 
8768
 
 
8769
 
 
8770
/**************************************************************************/
 
8771
 
 
8772
//  Resize (rescale) image
 
8773
//
 
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.
 
8777
 
 
8778
 
 
8779
int      resize_ww0, resize_hh0, resize_ww1, resize_hh1;
 
8780
 
 
8781
 
 
8782
void m_resize(GtkWidget *, const char *)
 
8783
{
 
8784
   int      resize_dialog_event(zdialog *zd, cchar * event);
 
8785
   int      resize_dialog_compl(zdialog *zd, int zstat);
 
8786
   void   * resize_thread(void *);
 
8787
 
 
8788
   const char  *lockmess = ZTX("Lock aspect ratio");
 
8789
 
 
8790
   if (! edit_setup(0,0)) return;                                          //  setup edit: no preview
 
8791
 
 
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);
 
8814
 
 
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);
 
8820
   
 
8821
   zdialog_run(zdedit,resize_dialog_event,resize_dialog_compl);            //  run dialog - parallel
 
8822
   start_thread(resize_thread,0);                                          //  start working thread
 
8823
   return;
 
8824
}
 
8825
 
 
8826
 
 
8827
//  resize dialog event function
 
8828
 
 
8829
int resize_dialog_event(zdialog *zd, const char * event)
 
8830
{
 
8831
   int         lock;
 
8832
   double      wpct1, hpct1;
 
8833
 
 
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);
 
8839
   
 
8840
   if (strEqu(event,"b 3/4")) {
 
8841
      resize_ww1 = (3 * resize_ww0 + 3) / 4;
 
8842
      resize_hh1 = (3 * resize_hh0 + 3) / 4;
 
8843
   }
 
8844
   
 
8845
   if (strEqu(event,"b 2/3")) {
 
8846
      resize_ww1 = (2 * resize_ww0 + 2) / 3;
 
8847
      resize_hh1 = (2 * resize_hh0 + 2) / 3;
 
8848
   }
 
8849
   
 
8850
   if (strEqu(event,"b 1/2")) {
 
8851
      resize_ww1 = (resize_ww0 + 1) / 2;
 
8852
      resize_hh1 = (resize_hh0 + 1) / 2;
 
8853
   }
 
8854
   
 
8855
   if (strEqu(event,"b 1/3")) {
 
8856
      resize_ww1 = (resize_ww0 + 2) / 3;
 
8857
      resize_hh1 = (resize_hh0 + 2) / 3;
 
8858
   }
 
8859
   
 
8860
   if (strEqu(event,"b 1/4")) {
 
8861
      resize_ww1 = (resize_ww0 + 3) / 4;
 
8862
      resize_hh1 = (resize_hh0 + 3) / 4;
 
8863
   }
 
8864
 
 
8865
   if (strEqu(event,"wpct"))                                               //  width % - set pixel width
 
8866
      resize_ww1 = int(wpct1 / 100.0 * resize_ww0 + 0.5);
 
8867
 
 
8868
   if (strEqu(event,"hpct"))                                               //  height % - set pixel height
 
8869
      resize_hh1 = int(hpct1 / 100.0 * resize_hh0 + 0.5);
 
8870
   
 
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);
 
8875
   
 
8876
   hpct1 = 100.0 * resize_hh1 / resize_hh0;                                //  set percents to match pixels
 
8877
   wpct1 = 100.0 * resize_ww1 / resize_ww0;
 
8878
   
 
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);
 
8883
   
 
8884
   return 1;
 
8885
}
 
8886
 
 
8887
 
 
8888
//  resize dialog completion function
 
8889
 
 
8890
int resize_dialog_compl(zdialog *zd, int zstat)
 
8891
{
 
8892
   if (zstat == 1) {
 
8893
      signal_thread();
 
8894
      edit_done();
 
8895
   }
 
8896
   else edit_cancel();
 
8897
   return 0;
 
8898
}
 
8899
 
 
8900
 
 
8901
//  resize image based on dialog controls.
 
8902
 
 
8903
void * resize_thread(void *)
 
8904
{
 
8905
   while (true)
 
8906
   {
 
8907
      thread_idle_loop();                                                  //  wait for work or exit request
 
8908
 
 
8909
      if (resize_ww1 != resize_ww0 || resize_hh1 != resize_hh0) 
 
8910
      {                                                                    //  rescale to target size
 
8911
         mutex_lock(&pixmaps_lock);
 
8912
         RGB_free(E3rgb48);
 
8913
         E3rgb48 = RGB_rescale(Frgb48,resize_ww1,resize_hh1);
 
8914
         E3ww = resize_ww1;
 
8915
         E3hh = resize_hh1;
 
8916
         Fmodified = 1;
 
8917
         mutex_unlock(&pixmaps_lock);
 
8918
      }
 
8919
 
 
8920
      mwpaint2();                                                          //  update window
 
8921
   }
 
8922
 
 
8923
   return 0;                                                               //  not executed, stop g++ warning
 
8924
}
 
8925
 
 
8926
 
 
8927
/**************************************************************************/
 
8928
 
 
8929
//  rotate image through any arbitrary angle
 
8930
 
 
8931
double      rotate_angle = 0;                                              //  E3 rotatation vs. F
 
8932
double      rotate_delta = 0;
 
8933
int         rotate_trim = 0;
 
8934
 
 
8935
 
 
8936
void m_rotate(GtkWidget *, const char *menu)                               //  menu function
 
8937
{
 
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();
 
8942
   
 
8943
   const char  *rotmess = ZTX("Use buttons or drag right edge with mouse");
 
8944
 
 
8945
   if (! edit_setup(1,0)) return;                                          //  setup edit: use preview   v.6.2
 
8946
 
 
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");
 
8965
 
 
8966
   zdialog_run(zdedit,rotate_dialog_event,rotate_dialog_compl);            //  run dialog - parallel
 
8967
 
 
8968
   mouseCBfunc = rotate_mousefunc;                                         //  connect mouse function
 
8969
   Mcapture++;
 
8970
 
 
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
 
8974
   return;
 
8975
}
 
8976
 
 
8977
 
 
8978
//  rotate dialog event and completion callback functions
 
8979
 
 
8980
int rotate_dialog_event(zdialog *zd, const char * event)
 
8981
{
 
8982
   int         err;
 
8983
   double      incr;
 
8984
   char        text[20];
 
8985
   
 
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"));
 
8990
   }
 
8991
   
 
8992
   if (strpbrk(event,"+-")) {
 
8993
      err = convSD(event,incr);                                            //  button name is increment to use
 
8994
      if (err) return 0;
 
8995
      rotate_delta += incr;
 
8996
   }
 
8997
 
 
8998
   zdialog_stuff(zd,"labdeg","computing");
 
8999
   signal_thread();
 
9000
   wait_thread_idle();
 
9001
 
 
9002
   gdk_window_set_cursor(drWin->window,dragcursor);                        //  set drag cursor   v.8.4
 
9003
 
 
9004
   sprintf(text,ZTX("degrees: %.1f"),rotate_angle);                        //  update dialog angle display
 
9005
   zdialog_stuff(zd,"labdeg",text);
 
9006
   return 1;
 
9007
}
 
9008
 
 
9009
 
 
9010
int rotate_dialog_compl(zdialog *zd, int zstat)
 
9011
{
 
9012
   if (zstat == 1) {                                                       //  undo, dialog stays active
 
9013
      edit_undo();                                                         //     v.6.1.2
 
9014
      rotate_angle = rotate_delta = rotate_trim = 0;
 
9015
      return 0;
 
9016
   }
 
9017
 
 
9018
   if (zstat == 2) {
 
9019
      rotate_delta = rotate_angle;                                         //  rotate main image   v.6.2
 
9020
      rotate_angle = 0;
 
9021
      edit_done();
 
9022
   }
 
9023
 
 
9024
   else edit_cancel();
 
9025
 
 
9026
   rotate_angle = rotate_delta = rotate_trim = 0;
 
9027
 
 
9028
   gdk_window_set_cursor(drWin->window,0);                                 //  restore normal cursor
 
9029
   mouseCBfunc = 0;                                                        //  disconnect mouse
 
9030
   Mcapture = 0;
 
9031
   return 0;
 
9032
}
 
9033
 
 
9034
 
 
9035
//  rotate mouse function - drag right edge of image up/down for rotation
 
9036
 
 
9037
void rotate_mousefunc()
 
9038
{
 
9039
   static int     mpx0 = 0, mpy0 = 0;
 
9040
   static int     mpy1, mpy2, dist;
 
9041
 
 
9042
   if (! Mxdrag && ! Mydrag) return;                                       //  no drag underway
 
9043
   if (Mxdrag < 0.8 * E3ww) return;                                        //  not right edge of image
 
9044
 
 
9045
   if (Mxdown != mpx0 || Mydown != mpy0) {
 
9046
      mpx0 = Mxdown;                                                       //  new drag started
 
9047
      mpy0 = mpy1 = Mydown;
 
9048
   }
 
9049
   
 
9050
   mpy2 = Mydrag;
 
9051
   dist = mpy2 - mpy1;                                                     //  drag distance
 
9052
   mpy1 = mpy2;                                                            //  reset origin for next time
 
9053
   if (! dist) return;
 
9054
 
 
9055
   rotate_delta = 30.0 * dist / E3ww;                                      //  convert to angle
 
9056
   rotate_dialog_event(zdedit,"mouse");
 
9057
   return;
 
9058
}
 
9059
 
 
9060
 
 
9061
//  rotate thread function
 
9062
 
 
9063
void * rotate_thread(void *)
 
9064
{
 
9065
   int         px3, py3, px9, py9;
 
9066
   int         wwcut, hhcut, ww, hh;
 
9067
   double      trim_angle, radians;
 
9068
   uint16      *pix3, *pix9;
 
9069
   
 
9070
   while (true)
 
9071
   {
 
9072
      thread_idle_loop();                                                  //  wait for work or exit request
 
9073
 
 
9074
      mutex_lock(&pixmaps_lock);
 
9075
 
 
9076
      rotate_angle += rotate_delta;                                        //  accum. net rotation   
 
9077
      rotate_delta = 0;                                                    //    from dialog widget
 
9078
      
 
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;
 
9082
 
 
9083
      if (! rotate_angle) {
 
9084
         RGB_free(E3rgb48);                                                //  E1 >> E3
 
9085
         E3rgb48 = RGB_copy(E1rgb48);
 
9086
         E3ww = E1ww;
 
9087
         E3hh = E1hh;
 
9088
         Fmodified = 0;
 
9089
      }
 
9090
      
 
9091
      if (rotate_angle) {
 
9092
         RGB_free(E3rgb48);
 
9093
         E3rgb48 = RGB_rotate(E1rgb48,rotate_angle);                       //  E3 is rotated E1
 
9094
         E3ww = E3rgb48->ww;
 
9095
         E3hh = E3rgb48->hh;
 
9096
         Fmodified = 1;
 
9097
      }
 
9098
 
 
9099
      if (rotate_trim)
 
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);
 
9110
            
 
9111
            for (py3 = hhcut; py3 < E3hh-hhcut; py3++)                     //  E9 = trimmed E3
 
9112
            for (px3 = wwcut; px3 < E3ww-wwcut; px3++)
 
9113
            {
 
9114
               px9 = px3 - wwcut;
 
9115
               py9 = py3 - hhcut;
 
9116
               pix3 = bmpixel(E3rgb48,px3,py3);
 
9117
               pix9 = bmpixel(E9rgb48,px9,py9);
 
9118
               pix9[0] = pix3[0];
 
9119
               pix9[1] = pix3[1];
 
9120
               pix9[2] = pix3[2];
 
9121
            }
 
9122
 
 
9123
            RGB_free(E3rgb48);                                             //  E3 = E9
 
9124
            E3rgb48 = E9rgb48;
 
9125
            E9rgb48 = 0;
 
9126
            E3ww = ww;
 
9127
            E3hh = hh;
 
9128
         }
 
9129
      }
 
9130
      
 
9131
      mutex_unlock(&pixmaps_lock);
 
9132
      mwpaint2();                                                          //  update window
 
9133
   }
 
9134
 
 
9135
   return 0;                                                               //  not executed, stop g++ warning
 
9136
}
 
9137
 
 
9138
 
 
9139
/**************************************************************************/
 
9140
 
 
9141
//  unbend an image
 
9142
//  straighten curvature added by pano or improve perspective
 
9143
 
 
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
 
9147
int      unbend_busy;
 
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;
 
9151
 
 
9152
 
 
9153
//  menu function
 
9154
 
 
9155
void m_unbend(GtkWidget *, const char *)                                   //  overhaul for preview   v.6.2
 
9156
{
 
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();
 
9161
 
 
9162
   if (! edit_setup(1,0)) return;                                          //  setup edit: preview
 
9163
 
 
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"));
 
9172
   
 
9173
   zdialog_resize(zdedit,260,0);
 
9174
   zdialog_run(zdedit,unbend_dialog_event,unbend_dialog_compl);            //  run dialog, parallel
 
9175
 
 
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
 
9178
 
 
9179
   Mcapture = 1;
 
9180
   mouseCBfunc = unbend_mousefunc;                                         //  connect mouse function
 
9181
   
 
9182
   start_thread(unbend_thread,0);                                          //  start working thread
 
9183
   signal_thread();
 
9184
 
 
9185
   return;
 
9186
}
 
9187
 
 
9188
 
 
9189
//  dialog event and completion functions
 
9190
 
 
9191
int unbend_dialog_event(zdialog *zd, const char *event)
 
9192
{
 
9193
   zdialog_fetch(zd,"spvert",unbend_vert);                                 //  get new unbend values
 
9194
   zdialog_fetch(zd,"sphorz",unbend_horz);
 
9195
   signal_thread();                                                        //  trigger thread
 
9196
   return 1;
 
9197
}
 
9198
 
 
9199
 
 
9200
int unbend_dialog_compl(zdialog *zd, int zstat)
 
9201
{
 
9202
   paint_toplines(2);                                                      //  erase axes-lines       v.8.4.3
 
9203
   mouseCBfunc = 0;                                                        //  disconnect mouse
 
9204
   Mcapture = 0;
 
9205
   LMclick = RMclick = 0;
 
9206
 
 
9207
   if (zstat != 1) {
 
9208
      edit_cancel();                                                       //  canceled
 
9209
      return 0;
 
9210
   }
 
9211
 
 
9212
   if (unbend_vert || unbend_horz) Fmodified = 1;                          //  image3 modified
 
9213
   else Fmodified = 0;
 
9214
   edit_done();                                                            //  commit changes to image3
 
9215
   return 1;
 
9216
}
 
9217
 
 
9218
 
 
9219
//  unbend mouse function                                                  //  adjustable axes
 
9220
 
 
9221
void unbend_mousefunc()   
 
9222
{
 
9223
   const char  *close;
 
9224
   double      dist1, dist2;
 
9225
   double      mpx = 0, mpy = 0;
 
9226
   
 
9227
   if (LMclick) {                                                          //  left mouse click   v.7.5
 
9228
      LMclick = 0;
 
9229
      mpx = Mxclick;
 
9230
      mpy = Myclick;
 
9231
   }
 
9232
   
 
9233
   if (Mxdrag || Mydrag) {                                                 //  mouse dragged
 
9234
      mpx = Mxdrag;
 
9235
      mpy = Mydrag;
 
9236
   }
 
9237
   
 
9238
   if (! mpx && ! mpy) return;
 
9239
 
 
9240
   mpx = 1.0 * mpx / E3ww;                                                 //  scale mouse position 0 to 1
 
9241
   mpy = 1.0 * mpy / E3hh;
 
9242
 
 
9243
   if (mpx < 0.2 || mpx > 0.8 ) {                                          //  check reasonable position
 
9244
      if (mpy < 0.1 || mpy > 0.9) return;
 
9245
   }
 
9246
   else if (mpy < 0.2 || mpy > 0.8) {
 
9247
      if (mpx < 0.1 || mpx > 0.9) return;
 
9248
   }
 
9249
   else return;
 
9250
 
 
9251
   close = "?";                                                            //  find closest axis end-point
 
9252
   dist1 = 2;
 
9253
 
 
9254
   dist2 = mpx * mpx + (mpy-unbend_y1) * (mpy-unbend_y1);
 
9255
   if (dist2 < dist1) {
 
9256
      dist1 = dist2;
 
9257
      close = "left";
 
9258
   }
 
9259
 
 
9260
   dist2 = (1-mpx) * (1-mpx) + (mpy-unbend_y2) * (mpy-unbend_y2);
 
9261
   if (dist2 < dist1) {
 
9262
      dist1 = dist2;
 
9263
      close = "right";
 
9264
   }
 
9265
 
 
9266
   dist2 = (mpx-unbend_x1) * (mpx-unbend_x1) + mpy * mpy;
 
9267
   if (dist2 < dist1) {
 
9268
      dist1 = dist2;
 
9269
      close = "top";
 
9270
   }
 
9271
 
 
9272
   dist2 = (mpx-unbend_x2) * (mpx-unbend_x2) + (1-mpy) * (1-mpy);
 
9273
   if (dist2 < dist1) {
 
9274
      dist1 = dist2;
 
9275
      close = "bottom";
 
9276
   }
 
9277
   
 
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;
 
9282
 
 
9283
   signal_thread();                                                        //  trigger thread 
 
9284
 
 
9285
   return ;
 
9286
}
 
9287
 
 
9288
 
 
9289
//  unbend thread function
 
9290
 
 
9291
void * unbend_thread(void *arg)
 
9292
{
 
9293
   void * unbend_wthread(void *);
 
9294
 
 
9295
   while (true)
 
9296
   {
 
9297
      thread_idle_loop();                                                  //  wait for work or exit request
 
9298
 
 
9299
      unbend_vert2 = int(unbend_vert * 0.01 * E3hh);                       //  convert % to pixels
 
9300
      unbend_horz2 = int(unbend_horz * 0.005 * E3ww);
 
9301
 
 
9302
      unbend_hx1 = 0;                                                      //  scale axes to E3ww/hh
 
9303
      unbend_hy1 = unbend_y1 * E3hh;
 
9304
      unbend_hx2 = E3ww;
 
9305
      unbend_hy2 = unbend_y2 * E3hh;
 
9306
 
 
9307
      unbend_vx1 = unbend_x1 * E3ww;
 
9308
      unbend_vy1 = 0;
 
9309
      unbend_vx2 = unbend_x2 * E3ww;
 
9310
      unbend_vy2 = E3hh;
 
9311
 
 
9312
      if (Fpreview) {                                                      //  omit for final unbend   v.8.4.3
 
9313
         Ntoplines = 2;
 
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;
 
9322
      }
 
9323
      
 
9324
      unbend_Npixels = E3ww * E3hh;
 
9325
      unbend_Ndone = 0;
 
9326
 
 
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);
 
9330
 
 
9331
      while (unbend_busy)                                                  //  wait for completion
 
9332
      {
 
9333
         zsleep(0.01);
 
9334
         edit_progress(unbend_Ndone,unbend_Npixels);                       //  show progress counter
 
9335
      }
 
9336
 
 
9337
      edit_progress(0,0);
 
9338
      mwpaint2();                                                          //  update window
 
9339
   }
 
9340
 
 
9341
   return 0;                                                               //  not executed, stop g++ warning
 
9342
}
 
9343
 
 
9344
   
 
9345
void * unbend_wthread(void *arg)                                           //  worker thread function   v.7.7
 
9346
{
 
9347
   int         index = *((int *) arg);
 
9348
   int         vstat, px3, py3, cx3, cy3;
 
9349
   double      px1, py1, dispx, dispx2, dispy;
 
9350
   uint16      vpix[3], *pix3;
 
9351
 
 
9352
   for (py3 = index; py3 < E3hh; py3 += NWthreads)                         //  step through F3 pixels
 
9353
   for (px3 = 0; px3 < E3ww; px3++)
 
9354
   {
 
9355
      pix3 = bmpixel(E3rgb48,px3,py3);                                     //  output pixel
 
9356
 
 
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
 
9362
 
 
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
 
9366
 
 
9367
      if (vstat) {
 
9368
         pix3[0] = vpix[0];                                                //  input pixel >> output pixel
 
9369
         pix3[1] = vpix[1];
 
9370
         pix3[2] = vpix[2];
 
9371
      }
 
9372
      else pix3[0] = pix3[1] = pix3[2] = 0;
 
9373
      
 
9374
      unbend_Ndone++;
 
9375
   }
 
9376
      
 
9377
   zadd_locked(unbend_busy,-1);
 
9378
   pthread_exit(0);
 
9379
}
 
9380
 
 
9381
 
 
9382
/**************************************************************************/
 
9383
 
 
9384
//  warp/distort area - select image area and pull with mouse
 
9385
 
 
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
 
9389
 
 
9390
void  WarpA_warpfunc(float wdx, float wdy, float wdw, float wdh, int acc);
 
9391
 
 
9392
 
 
9393
//  menu function
 
9394
 
 
9395
void m_WarpA(GtkWidget *, const char *)
 
9396
{
 
9397
   int      WarpA_dialog_event(zdialog *zd, const char *event);
 
9398
   int      WarpA_dialog_compl(zdialog *zd, int zstat);
 
9399
 
 
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]."); 
 
9405
   
 
9406
   int         px, py, ii;
 
9407
 
 
9408
   if (! edit_setup(0,2)) return;                                          //  setup edit: no preview
 
9409
 
 
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");
 
9416
 
 
9417
   zdialog_run(zdedit,WarpA_dialog_event,WarpA_dialog_compl);              //  run dialog
 
9418
 
 
9419
   WarpAx = (float *) zmalloc(E3ww * E3hh * sizeof(float));                //  get memory for pixel displacements
 
9420
   WarpAy = (float *) zmalloc(E3ww * E3hh * sizeof(float));
 
9421
   
 
9422
   NWarpA = 0;                                                             //  no warp data
 
9423
 
 
9424
   for (py = 0; py < E3hh; py++)                                           //  no pixel displacements
 
9425
   for (px = 0; px < E3ww; px++)
 
9426
   {
 
9427
      ii = py * E3ww + px;
 
9428
      WarpAx[ii] = WarpAy[ii] = 0.0;
 
9429
   }
 
9430
   
 
9431
   return;
 
9432
}
 
9433
 
 
9434
 
 
9435
//  warp dialog event and completion callback functions
 
9436
 
 
9437
int WarpA_dialog_compl(zdialog * zd, int zstat)
 
9438
{
 
9439
   if (NWarpA) Fmodified = 1;
 
9440
   if (zstat == 1) edit_done();
 
9441
   else edit_cancel();
 
9442
 
 
9443
   mouseCBfunc = 0;                                                        //  disconnect mouse
 
9444
   Mcapture = 0;
 
9445
   gdk_window_set_cursor(drWin->window,0);                                 //  restore normal cursor
 
9446
   zfree(WarpAx);                                                          //  release undo memory
 
9447
   zfree(WarpAy);
 
9448
   return 0;
 
9449
}
 
9450
 
 
9451
 
 
9452
int WarpA_dialog_event(zdialog * zd, const char *event)
 
9453
{
 
9454
   void     WarpA_mousefunc(void);
 
9455
 
 
9456
   int         px, py, ii;
 
9457
   float       wdx, wdy, wdw, wdh;
 
9458
   uint16      *pix1, *pix3;
 
9459
 
 
9460
   if (strEqu(event,"swarp")) {                                            //  start warp
 
9461
      if (! sa_Npixel) {
 
9462
         zmessageACK(ZTX("Must select area first"));
 
9463
         return 0;
 
9464
      }
 
9465
 
 
9466
      gdk_window_set_cursor(drWin->window,dragcursor);                     //  set drag cursor
 
9467
      mouseCBfunc = WarpA_mousefunc;                                       //  connect mouse function
 
9468
      Mcapture++;
 
9469
   }
 
9470
 
 
9471
   if (strEqu(event,"undlast")) {
 
9472
      if (NWarpA) {                                                        //  undo most recent warp
 
9473
         ii = --NWarpA;
 
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
 
9480
      }
 
9481
   }
 
9482
 
 
9483
   if (strEqu(event,"undall")) {                                           //  undo all warps
 
9484
      for (ii = 0; ii < sa_Npixel; ii++)                                   //  process all enclosed pixels
 
9485
      {
 
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);
 
9490
         pix3[0] = pix1[0];
 
9491
         pix3[1] = pix1[1];
 
9492
         pix3[2] = pix1[2];
 
9493
      }
 
9494
 
 
9495
      for (py = 0; py < E3hh; py++)                                        //  reset pixel displacements
 
9496
      for (px = 0; px < E3ww; px++)
 
9497
      {
 
9498
         ii = py * E3ww + px;
 
9499
         WarpAx[ii] = WarpAy[ii] = 0.0;
 
9500
      }
 
9501
 
 
9502
      NWarpA = 0;                                                          //  erase undo memory
 
9503
      mwpaint2(); 
 
9504
   }
 
9505
 
 
9506
   return 1;
 
9507
}
 
9508
 
 
9509
 
 
9510
//  warp mouse function
 
9511
 
 
9512
void  WarpA_mousefunc(void)
 
9513
{
 
9514
   static float   wdx, wdy, wdw, wdh;
 
9515
   static int     ii, warped = 0;
 
9516
 
 
9517
   if (Mxdrag || Mydrag)                                                   //  mouse drag underway
 
9518
   {
 
9519
      wdx = Mxdown;                                                        //  drag origin, image coordinates
 
9520
      wdy = Mydown;
 
9521
      wdw = Mxdrag - Mxdown;                                               //  drag increment
 
9522
      wdh = Mydrag - Mydown;
 
9523
      WarpA_warpfunc(wdx,wdy,wdw,wdh,0);                                   //  warp image
 
9524
      warped = 1;
 
9525
      return;
 
9526
   }
 
9527
   
 
9528
   else if (warped) 
 
9529
   {
 
9530
      warped = 0;
 
9531
      WarpA_warpfunc(wdx,wdy,wdw,wdh,1);                                   //  drag done, add to warp memory
 
9532
 
 
9533
      if (NWarpA == 100)                                                   //  if full, throw away oldest
 
9534
      {
 
9535
         NWarpA = 99;
 
9536
         for (ii = 0; ii < NWarpA; ii++)
 
9537
         {
 
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];
 
9542
         }
 
9543
      }
 
9544
 
 
9545
      ii = NWarpA;
 
9546
      WarpAmem[0][ii] = wdx;                                               //  save warp for undo
 
9547
      WarpAmem[1][ii] = wdy;
 
9548
      WarpAmem[2][ii] = wdw;
 
9549
      WarpAmem[3][ii] = wdh;
 
9550
      NWarpA++;
 
9551
   }
 
9552
   
 
9553
   return;
 
9554
}
 
9555
 
 
9556
 
 
9557
//  warp image and accumulate warp memory
 
9558
 
 
9559
void  WarpA_warpfunc(float wdx, float wdy, float wdw, float wdh, int acc)
 
9560
{
 
9561
   int            ii, jj, px, py, vstat;
 
9562
   double         ddx, ddy, dpe, dpm, mag, dispx, dispy;
 
9563
   uint16         vpix[3], *pix3;
 
9564
 
 
9565
   for (ii = 0; ii < sa_Npixel; ii++)                                      //  process all enclosed pixels
 
9566
   {
 
9567
      px = sa_pixel[ii].px;
 
9568
      py = sa_pixel[ii].py;
 
9569
      dpe = sa_pixel[ii].dist;                                             //  distance from area edge
 
9570
 
 
9571
      ddx = (px - wdx);                                                    //  distance from drag origin
 
9572
      ddy = (py - wdy);
 
9573
      dpm = sqrt(ddx*ddx + ddy*ddy);
 
9574
 
 
9575
      if (dpm < 1) mag = 1;
 
9576
      else mag = dpe / (dpe + dpm);                                        //  magnification, 0...1
 
9577
      mag = mag * mag;
 
9578
 
 
9579
      dispx = -wdw * mag;                                                  //  warp = drag * magnification
 
9580
      dispy = -wdh * mag;
 
9581
      
 
9582
      jj = py * E3ww + px;
 
9583
 
 
9584
      if (acc) {                                                           //  mouse drag done,
 
9585
         WarpAx[jj] += dispx;                                              //    accumulate warp memory
 
9586
         WarpAy[jj] += dispy;
 
9587
         continue;
 
9588
      }
 
9589
 
 
9590
      dispx += WarpAx[jj];                                                 //  add this warp to prior
 
9591
      dispy += WarpAy[jj];
 
9592
 
 
9593
      vstat = vpixel(E1rgb48,px+dispx,py+dispy,vpix);                      //  input virtual pixel
 
9594
      if (vstat) {
 
9595
         pix3 = bmpixel(E3rgb48,px,py);                                    //  output pixel
 
9596
         pix3[0] = vpix[0];
 
9597
         pix3[1] = vpix[1];
 
9598
         pix3[2] = vpix[2];
 
9599
      }
 
9600
   }
 
9601
 
 
9602
   mwpaint2();                                                             //  update window
 
9603
   return;
 
9604
}
 
9605
 
 
9606
 
 
9607
/**************************************************************************/
 
9608
 
 
9609
//  warp/distort whole image
 
9610
//  fix perspective problems (e.g. curved walls, leaning buildings)
 
9611
 
 
9612
float       *WarpIx, *WarpIy;                                              //  memory of all dragged pixels
 
9613
float       WarpImem[4][100];                                              //  undo memory, last 100 drags
 
9614
int         NWarpI;                                                        //  WarpImem count
 
9615
int         WarpIdrag;
 
9616
int         WarpIww, WarpIhh;
 
9617
 
 
9618
void  WarpI_warpfunc(float wdx, float wdy, float wdw, float wdh, int acc);
 
9619
 
 
9620
 
 
9621
//  menu function
 
9622
 
 
9623
void m_WarpI(GtkWidget *, const char *)                                    //  v.6.1
 
9624
{
 
9625
   int      WarpI_dialog_event(zdialog *zd, const char *event);
 
9626
   int      WarpI_dialog_compl(zdialog *zd, int zstat);
 
9627
   void     WarpI_mousefunc(void);
 
9628
 
 
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]."); 
 
9633
   
 
9634
   int         px, py, ii;
 
9635
 
 
9636
   if (! edit_setup(1,0)) return;                                          //  setup edit: use preview
 
9637
 
 
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");
 
9643
 
 
9644
   zdialog_run(zdedit,WarpI_dialog_event,WarpI_dialog_compl);              //  run dialog
 
9645
 
 
9646
   NWarpI = WarpIdrag = 0;                                                 //  no drag data
 
9647
 
 
9648
   WarpIx = (float *) zmalloc(E3ww * E3hh * sizeof(float));                //  get memory for pixel displacements
 
9649
   WarpIy = (float *) zmalloc(E3ww * E3hh * sizeof(float));
 
9650
   
 
9651
   for (py = 0; py < E3hh; py++)                                           //  no pixel displacements
 
9652
   for (px = 0; px < E3ww; px++)
 
9653
   {
 
9654
      ii = py * E3ww + px;
 
9655
      WarpIx[ii] = WarpIy[ii] = 0.0;
 
9656
   }
 
9657
   
 
9658
   WarpIww = E3ww;                                                         //  preview dimensions
 
9659
   WarpIhh = E3hh;
 
9660
   
 
9661
   gdk_window_set_cursor(drWin->window,dragcursor);                        //  set drag cursor
 
9662
   mouseCBfunc = WarpI_mousefunc;                                          //  connect mouse function
 
9663
   Mcapture++;
 
9664
 
 
9665
   return;
 
9666
}
 
9667
 
 
9668
 
 
9669
//  WarpI dialog event and completion callback functions
 
9670
 
 
9671
int WarpI_dialog_compl(zdialog * zd, int zstat)
 
9672
{
 
9673
   int         fpx, fpy, epx, epy, ii, vstat;
 
9674
   double      scale, dispx, dispy;
 
9675
   uint16      vpix[3], *pix3;
 
9676
 
 
9677
   if (zstat != 1) edit_cancel();
 
9678
   else if (NWarpI == 0) edit_cancel();
 
9679
   
 
9680
   else 
 
9681
   {
 
9682
      edit_fullsize();                                                     //  get full-size E1/E3
 
9683
 
 
9684
      scale = 1.0 * (E3ww + E3hh) / (WarpIww + WarpIhh);
 
9685
 
 
9686
      for (fpy = 0; fpy < E3hh; fpy++)                                     //  scale net pixel displacements
 
9687
      for (fpx = 0; fpx < E3ww; fpx++)                                     //    to full image size
 
9688
      {
 
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;
 
9694
 
 
9695
         vstat = vpixel(E1rgb48,fpx+dispx,fpy+dispy,vpix);                 //  input virtual pixel
 
9696
         pix3 = bmpixel(E3rgb48,fpx,fpy);                                  //  output pixel
 
9697
         if (vstat) {
 
9698
            pix3[0] = vpix[0];
 
9699
            pix3[1] = vpix[1];
 
9700
            pix3[2] = vpix[2];
 
9701
         }
 
9702
         else pix3[0] = pix3[1] = pix3[2] = 0;
 
9703
      }
 
9704
 
 
9705
      edit_done();
 
9706
   }
 
9707
 
 
9708
   mouseCBfunc = 0;                                                        //  disconnect mouse
 
9709
   Mcapture = 0;
 
9710
   gdk_window_set_cursor(drWin->window,0);                                 //  restore normal cursor
 
9711
   zfree(WarpIx);                                                          //  release memory
 
9712
   zfree(WarpIy);
 
9713
   return 0;
 
9714
}
 
9715
 
 
9716
 
 
9717
int WarpI_dialog_event(zdialog * zd, const char *event)
 
9718
{
 
9719
   int         px, py, ii;
 
9720
   float       wdx, wdy, wdw, wdh;
 
9721
   
 
9722
   if (strEqu(event,"undlast")) 
 
9723
   {
 
9724
      if (NWarpI == 1) event = "undall";
 
9725
      else if (NWarpI) {                                                   //  undo most recent drag
 
9726
         ii = --NWarpI;
 
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
 
9733
      }
 
9734
   }
 
9735
 
 
9736
   if (strEqu(event,"undall"))                                             //  undo all drags
 
9737
   {
 
9738
      NWarpI = 0;                                                          //  erase undo memory
 
9739
 
 
9740
      for (py = 0; py < E3hh; py++)                                        //  reset pixel displacements
 
9741
      for (px = 0; px < E3ww; px++)
 
9742
      {
 
9743
         ii = py * E3ww + px;
 
9744
         WarpIx[ii] = WarpIy[ii] = 0.0;
 
9745
      }
 
9746
 
 
9747
      edit_undo();                                                         //  restore image1
 
9748
      Fmodified = 0;                                                       //  v.6.3
 
9749
   }
 
9750
 
 
9751
   return 1;
 
9752
}
 
9753
 
 
9754
 
 
9755
//  WarpI mouse function
 
9756
 
 
9757
void  WarpI_mousefunc(void)
 
9758
{
 
9759
   static float   wdx, wdy, wdw, wdh;
 
9760
   int            ii;
 
9761
 
 
9762
   if (Mxdrag || Mydrag)                                                   //  mouse drag underway
 
9763
   {
 
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
 
9769
      WarpIdrag = 1;
 
9770
      return;
 
9771
   }
 
9772
   
 
9773
   else if (WarpIdrag) 
 
9774
   {
 
9775
      WarpIdrag = 0;
 
9776
      WarpI_warpfunc(wdx,wdy,wdw,wdh,1);                                   //  drag done, add to memory
 
9777
 
 
9778
      if (NWarpI == 100)                                                   //  if full, throw away oldest
 
9779
      {
 
9780
         NWarpI = 99;
 
9781
         for (ii = 0; ii < NWarpI; ii++)
 
9782
         {
 
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];
 
9787
         }
 
9788
      }
 
9789
 
 
9790
      ii = NWarpI;
 
9791
      WarpImem[0][ii] = wdx;                                               //  save drag for undo
 
9792
      WarpImem[1][ii] = wdy;
 
9793
      WarpImem[2][ii] = wdw;
 
9794
      WarpImem[3][ii] = wdh;
 
9795
      NWarpI++;
 
9796
   }
 
9797
   
 
9798
   return;
 
9799
}
 
9800
 
 
9801
 
 
9802
//  warp image and accumulate warp memory
 
9803
//  mouse at (mx,my) is moved (mw,mh) pixels
 
9804
 
 
9805
void  WarpI_warpfunc(float mx, float my, float mw, float mh, int acc)
 
9806
{
 
9807
   int         ii, px, py, vstat;
 
9808
   double      mag, dispx, dispy;
 
9809
   double      d1, d2;
 
9810
   uint16      vpix[3], *pix3;
 
9811
   
 
9812
   d1 = E3ww * E3ww + E3hh * E3hh;
 
9813
   
 
9814
   for (py = 0; py < E3hh; py++)                                           //  process all pixels
 
9815
   for (px = 0; px < E3ww; px++)
 
9816
   {
 
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);
 
9820
      mag = mag * mag;
 
9821
      mag = mag * mag;
 
9822
 
 
9823
      dispx = -mw * mag;                                                   //  displacement = drag * mag
 
9824
      dispy = -mh * mag;
 
9825
      
 
9826
      ii = py * E3ww + px;
 
9827
 
 
9828
      if (acc) {                                                           //  drag done, accumulate drag sum
 
9829
         WarpIx[ii] += dispx;
 
9830
         WarpIy[ii] += dispy;
 
9831
         continue;
 
9832
      }
 
9833
 
 
9834
      dispx += WarpIx[ii];                                                 //  add this drag to prior sum
 
9835
      dispy += WarpIy[ii];
 
9836
 
 
9837
      vstat = vpixel(E1rgb48,px+dispx,py+dispy,vpix);                      //  input virtual pixel
 
9838
      pix3 = bmpixel(E3rgb48,px,py);                                       //  output pixel
 
9839
      if (vstat) {
 
9840
         pix3[0] = vpix[0];
 
9841
         pix3[1] = vpix[1];
 
9842
         pix3[2] = vpix[2];
 
9843
      }
 
9844
      else pix3[0] = pix3[1] = pix3[2] = 0;
 
9845
   }
 
9846
 
 
9847
   Fmodified = 1;                                                          //  v.6.3
 
9848
   mwpaint2();                                                             //  update window
 
9849
   return;
 
9850
}
 
9851
 
 
9852
 
 
9853
/**************************************************************************/
 
9854
 
 
9855
//  image color-depth reduction
 
9856
 
 
9857
int      colordep_depth = 16;                                              //  bits per RGB color
 
9858
 
 
9859
 
 
9860
void m_colordep(GtkWidget *, const char *)
 
9861
{
 
9862
   int      colordep_dialog_event(zdialog *zd, const char *event);
 
9863
   int      colordep_dialog_compl(zdialog *zd, int zstat);
 
9864
   void   * colordep_thread(void *);
 
9865
 
 
9866
   const char  *colmess = ZTX("Set color depth to 1-16 bits");
 
9867
   
 
9868
   if (! edit_setup(1,2)) return;                                          //  setup edit: preview
 
9869
 
 
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");
 
9876
 
 
9877
   zdialog_run(zdedit,colordep_dialog_event,colordep_dialog_compl);        //  run dialog - parallel
 
9878
   
 
9879
   colordep_depth = 16;
 
9880
   start_thread(colordep_thread,0);                                        //  start working thread
 
9881
   return;
 
9882
}
 
9883
 
 
9884
 
 
9885
//  colors dialog event and completion callback functions
 
9886
 
 
9887
int colordep_dialog_event(zdialog *zd, const char *event)
 
9888
{
 
9889
   if (strcmpv(event,"colors","blendwidth",0)) {
 
9890
      zdialog_fetch(zd,"colors",colordep_depth);
 
9891
      signal_thread();
 
9892
   }
 
9893
   
 
9894
   if (strEqu(event,"Undo")) edit_undo();
 
9895
   if (strEqu(event,"Redo")) edit_redo();
 
9896
 
 
9897
   return 0;
 
9898
}
 
9899
 
 
9900
 
 
9901
int colordep_dialog_compl(zdialog * zd, int zstat)
 
9902
{
 
9903
   if (zstat == 1) edit_done();
 
9904
   else edit_cancel();
 
9905
   return 0;
 
9906
}
 
9907
 
 
9908
 
 
9909
//  image color depth thread function
 
9910
 
 
9911
void * colordep_thread(void *)
 
9912
{
 
9913
   int         ii, px, py, rgb, dist;
 
9914
   uint16      m1, m2, val1, val3;
 
9915
   uint16      *pix1, *pix3;
 
9916
   double      fmag, f1, f2;
 
9917
   
 
9918
   while (true)
 
9919
   {
 
9920
      thread_idle_loop();                                                  //  wait for work or exit request
 
9921
   
 
9922
      m1 = 0xFFFF << (16 - colordep_depth);                                //  5 > 1111100000000000
 
9923
      m2 = 0x8000 >> colordep_depth;                                       //  5 > 0000010000000000
 
9924
      
 
9925
      fmag = 65535.0 / m1;                                                 //  full brightness range  v.7.0
 
9926
 
 
9927
      if (! sa_Npixel)                                                     //  process entire image
 
9928
      {
 
9929
         for (py = 0; py < E3hh; py++)
 
9930
         for (px = 0; px < E3ww; px++)
 
9931
         {
 
9932
            pix1 = bmpixel(E1rgb48,px,py);                                 //  input pixel
 
9933
            pix3 = bmpixel(E3rgb48,px,py);                                 //  output pixel
 
9934
            
 
9935
            for (rgb = 0; rgb < 3; rgb++)
 
9936
            {
 
9937
               val1 = pix1[rgb];
 
9938
               if (val1 < m1) val3 = (val1 + m2) & m1;                     //  round   v.7.0
 
9939
               else val3 = m1;
 
9940
               val3 = uint(val3 * fmag);
 
9941
               pix3[rgb] = val3;
 
9942
            }
 
9943
         }
 
9944
      }
 
9945
 
 
9946
      if (sa_Npixel)                                                       //  process select area
 
9947
      {
 
9948
         for (ii = 0; ii < sa_Npixel; ii++)
 
9949
         {
 
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
 
9955
 
 
9956
            for (rgb = 0; rgb < 3; rgb++)
 
9957
            {
 
9958
               val1 = pix1[rgb];
 
9959
               if (val1 < m1) val3 = (val1 + m2) & m1;
 
9960
               else val3 = m1;
 
9961
               val3 = uint(val3 * fmag);
 
9962
 
 
9963
               if (dist < sa_blend) {                                      //  if area selection, blend pixel
 
9964
                  f2 = 1.0 * dist / sa_blend;                              //    changes over distance sa_blend
 
9965
                  f1 = 1.0 - f2;
 
9966
                  val3 = int(f1 * val1 + f2 * val3);
 
9967
               }
 
9968
               pix3[rgb] = val3;
 
9969
            }
 
9970
         }
 
9971
      }
 
9972
 
 
9973
      Fmodified = 1;
 
9974
      mwpaint2();                                                          //  update window
 
9975
   }
 
9976
 
 
9977
   return 0;                                                               //  not executed, stop g++ warning
 
9978
}
 
9979
 
 
9980
 
 
9981
/**************************************************************************/
 
9982
 
 
9983
//  convert image to simulate a drawing
 
9984
 
 
9985
int      draw_busy = 0;
 
9986
int      draw_contrast;
 
9987
int      draw_threshold;
 
9988
int      draw_pixcon;
 
9989
int      draw_reverse;
 
9990
double   draw_trfunc[256];
 
9991
double   draw_pixcon3;
 
9992
uint8    *draw_pixcon_map = 0;
 
9993
 
 
9994
void m_draw(GtkWidget *, const char *)                                     //  v.6.7
 
9995
{
 
9996
   int    draw_dialog_event(zdialog* zd, const char *event);
 
9997
   int    draw_dialog_compl(zdialog* zd, int zstat);
 
9998
   void * draw_thread(void *);
 
9999
 
 
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;
 
10004
 
 
10005
   if (! edit_setup(0,2)) return;                                          //  setup edit: no preview
 
10006
 
 
10007
   draw_pixcon_map = (uint8 *) zmalloc(E1ww*E1hh);                         //  set up pixel contrast map
 
10008
   memset(draw_pixcon_map,0,E1ww*E1hh);
 
10009
 
 
10010
   for (py = 1; py < E1hh-1; py++)                                         //  scan image pixels
 
10011
   for (px = 1; px < E1ww-1; px++)
 
10012
   {
 
10013
      pix1 = bmpixel(E1rgb48,px,py);                                       //  pixel at (px,py)
 
10014
      red = pix1[0];                                                       //  pixel RGB levels
 
10015
      green = pix1[1];
 
10016
      blue = pix1[2];
 
10017
      maxcon = 0;
 
10018
 
 
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
 
10021
      {
 
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;
 
10025
      }
 
10026
 
 
10027
      ii = py * E1ww + px;
 
10028
      draw_pixcon_map[ii] = (maxcon/3) >> 8;                               //  contrast for (px,py) 0-255
 
10029
   }
 
10030
 
 
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");
 
10044
 
 
10045
   zdialog_run(zdedit,draw_dialog_event,draw_dialog_compl);                //  run dialog - parallel
 
10046
   
 
10047
   start_thread(draw_thread,0);                                            //  start working thread
 
10048
   return;
 
10049
}
 
10050
 
 
10051
 
 
10052
//  draw dialog event and completion functions
 
10053
 
 
10054
int draw_dialog_event(zdialog *zd, const char *event)                      //  draw dialog event function
 
10055
{
 
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
 
10061
   return 1;
 
10062
}
 
10063
 
 
10064
 
 
10065
int draw_dialog_compl(zdialog *zd, int zstat)                              //  draw dialog completion function
 
10066
{
 
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);
 
10072
   return 0;
 
10073
}
 
10074
 
 
10075
 
 
10076
//  thread function - use multiple working threads
 
10077
 
 
10078
void * draw_thread(void *)
 
10079
{
 
10080
   void  * draw_wthread(void *arg);
 
10081
 
 
10082
   int         ii;
 
10083
   double      threshold, contrast, trf;
 
10084
   
 
10085
   while (true)
 
10086
   {
 
10087
      thread_idle_loop();                                                  //  wait for work or exit request
 
10088
 
 
10089
      threshold = 0.01 * draw_threshold;                                   //  range 0 to 1
 
10090
      contrast = 0.01 * draw_contrast;                                     //  range 0 to 1
 
10091
      
 
10092
      for (ii = 0; ii < 256; ii++)                                         //  brightness transfer function
 
10093
      {
 
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;
 
10097
      }
 
10098
      
 
10099
      for (ii = 0; ii < NWthreads; ii++)                                   //  start worker threads
 
10100
         start_detached_thread(draw_wthread,&wtindex[ii]);
 
10101
      zadd_locked(draw_busy,+NWthreads);
 
10102
 
 
10103
      while (draw_busy) zsleep(0.004);                                     //  wait for completion
 
10104
 
 
10105
      Fmodified = 1;
 
10106
      mwpaint2();                                                          //  update window
 
10107
   }
 
10108
 
 
10109
   return 0;                                                               //  not executed, stop g++ warning
 
10110
}
 
10111
 
 
10112
 
 
10113
void * draw_wthread(void *arg)                                             //  worker thread function
 
10114
{
 
10115
   void  draw_1pix(int px, int py, int dist);
 
10116
 
 
10117
   int         index = *((int *) (arg));
 
10118
   int         px, py, ii, dist;
 
10119
   double      pixcon;
 
10120
 
 
10121
   pixcon = draw_pixcon / 255.0;                                           //  0-1 linear ramp
 
10122
   draw_pixcon3 = 255 * pixcon * pixcon * pixcon;                          //  0-255 cubic ramp
 
10123
 
 
10124
   if (sa_Npixel)                                                          //  process selected area
 
10125
   {
 
10126
      for (ii = index; ii < sa_Npixel; ii += NWthreads)                    //  process all enclosed pixels
 
10127
      {
 
10128
         px = sa_pixel[ii].px;
 
10129
         py = sa_pixel[ii].py;
 
10130
         dist = sa_pixel[ii].dist;
 
10131
         draw_1pix(px,py,dist);
 
10132
      }
 
10133
   }
 
10134
 
 
10135
   else
 
10136
   {
 
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);
 
10141
   }
 
10142
 
 
10143
   zadd_locked(draw_busy,-1);
 
10144
   pthread_exit(0);
 
10145
}
 
10146
 
 
10147
 
 
10148
void draw_1pix(int px, int py, int dist)                                   //  process one pixel
 
10149
{
 
10150
   uint16      *pix1, *pix3;
 
10151
   int         bright1, bright2;
 
10152
   int         red1, green1, blue1;
 
10153
   int         red3, green3, blue3;
 
10154
   double      dold, dnew;
 
10155
   double      pixcon = draw_pixcon3;
 
10156
   
 
10157
   if (! px || ! py) return;
 
10158
   if (px > E1ww-2 || py > E1hh-2) return;
 
10159
   
 
10160
   pix1 = bmpixel(E1rgb48,px,py);                                          //  input pixel
 
10161
   pix3 = bmpixel(E3rgb48,px,py);                                          //  output pixel
 
10162
      
 
10163
   red1 = pix1[0];
 
10164
   green1 = pix1[1];
 
10165
   blue1 = pix1[2];
 
10166
   
 
10167
   bright1 = ((red1 + green1 + blue1) / 3) >> 8;                           //  old brightness  0-255
 
10168
   bright2 = bright1 * draw_trfunc[bright1];                               //  new brightness  0-255
 
10169
   
 
10170
   int ii = py * E1ww + px;
 
10171
   if (draw_pixcon_map[ii] < pixcon) bright2 = 255;
 
10172
 
 
10173
   if (pixcon > 1 && bright2 > draw_threshold) bright2 = 255;              //  empirical !!!
 
10174
 
 
10175
   if (draw_reverse) bright2 = 255 - bright2;                              //  negate if "chalk"
 
10176
 
 
10177
   red3 = green3 = blue3 = bright2 << 8;                                   //  gray scale, new brightness
 
10178
 
 
10179
   if (dist < sa_blend) {                                                  //  blend over distance sa_blend
 
10180
      dnew = 1.0 * dist / sa_blend;
 
10181
      dold = 1.0 - dnew;
 
10182
      red3 = dnew * red3 + dold * red1;
 
10183
      green3 = dnew * green3 + dold * green1;
 
10184
      blue3 = dnew * blue3 + dold * blue1;
 
10185
   }
 
10186
 
 
10187
   pix3[0] = red3;
 
10188
   pix3[1] = green3;
 
10189
   pix3[2] = blue3;
 
10190
   
 
10191
   return;
 
10192
}
 
10193
 
 
10194
 
 
10195
/**************************************************************************/
 
10196
 
 
10197
//  convert image to simulate an embossing
 
10198
 
 
10199
int      emboss_busy = 0;
 
10200
int      emboss_radius, emboss_color;
 
10201
double   emboss_depth;
 
10202
double   emboss_kernel[20][20];                                            //  support radius <= 9
 
10203
 
 
10204
void m_emboss(GtkWidget *, const char *)                                   //  v.6.7
 
10205
{
 
10206
   int    emboss_dialog_event(zdialog* zd, const char *event);
 
10207
   int    emboss_dialog_compl(zdialog* zd, int zstat);
 
10208
   void * emboss_thread(void *);
 
10209
 
 
10210
   const char  *title = ZTX("Simulate Embossing");
 
10211
 
 
10212
   if (! edit_setup(0,2)) return;                                          //  setup edit: no preview
 
10213
 
 
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");
 
10221
 
 
10222
   zdialog_run(zdedit,emboss_dialog_event,emboss_dialog_compl);            //  run dialog - parallel
 
10223
   
 
10224
   start_thread(emboss_thread,0);                                          //  start working thread
 
10225
   return;
 
10226
}
 
10227
 
 
10228
 
 
10229
//  emboss dialog event and completion functions
 
10230
 
 
10231
int emboss_dialog_event(zdialog *zd, const char *event)                    //  emboss dialog event function
 
10232
{
 
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
 
10237
   return 1;
 
10238
}
 
10239
 
 
10240
 
 
10241
int emboss_dialog_compl(zdialog *zd, int zstat)                            //  emboss dialog completion function
 
10242
{
 
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
 
10247
   return 0;
 
10248
}
 
10249
 
 
10250
 
 
10251
//  thread function - use multiple working threads
 
10252
 
 
10253
void * emboss_thread(void *)
 
10254
{
 
10255
   void  * emboss_wthread(void *arg);
 
10256
 
 
10257
   int         ii, dx, dy, rad;
 
10258
   double      depth, kern, coeff;
 
10259
   
 
10260
   while (true)
 
10261
   {
 
10262
      thread_idle_loop();                                                  //  wait for work or exit request
 
10263
      
 
10264
      rad = emboss_radius;
 
10265
      depth = emboss_depth;
 
10266
 
 
10267
      coeff = 0.1 * depth / (rad * rad + 1);
 
10268
 
 
10269
      for (dy = -rad; dy <= rad; dy++)                                     //  build kernel with radius and depth
 
10270
      for (dx = -rad; dx <= rad; dx++)
 
10271
      {
 
10272
         kern = coeff * (dx + dy);
 
10273
         emboss_kernel[dx+rad][dy+rad] = kern;
 
10274
      }
 
10275
      
 
10276
      emboss_kernel[rad][rad] = 1;                                         //  kernel center cell = 1
 
10277
 
 
10278
      for (ii = 0; ii < NWthreads; ii++)                                   //  start worker threads
 
10279
         start_detached_thread(emboss_wthread,&wtindex[ii]);
 
10280
      zadd_locked(emboss_busy,+NWthreads);
 
10281
 
 
10282
      while (emboss_busy) zsleep(0.004);                                   //  wait for completion
 
10283
 
 
10284
      Fmodified = 1;
 
10285
      mwpaint2();                                                          //  update window
 
10286
   }
 
10287
 
 
10288
   return 0;                                                               //  not executed, stop g++ warning
 
10289
}
 
10290
 
 
10291
 
 
10292
void * emboss_wthread(void *arg)                                           //  worker thread function
 
10293
{
 
10294
   void  emboss_1pix(int px, int py, int dist);
 
10295
 
 
10296
   int         index = *((int *) (arg));
 
10297
   int         px, py, ii, dist;
 
10298
 
 
10299
   if (sa_Npixel)                                                          //  process selected area
 
10300
   {
 
10301
      for (ii = index; ii < sa_Npixel; ii += NWthreads)                    //  process all enclosed pixels
 
10302
      {
 
10303
         px = sa_pixel[ii].px;
 
10304
         py = sa_pixel[ii].py;
 
10305
         dist = sa_pixel[ii].dist;
 
10306
         emboss_1pix(px,py,dist);
 
10307
      }
 
10308
   }
 
10309
 
 
10310
   else
 
10311
   {
 
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);
 
10316
   }
 
10317
 
 
10318
   zadd_locked(emboss_busy,-1);
 
10319
   pthread_exit(0);
 
10320
}
 
10321
 
 
10322
 
 
10323
void emboss_1pix(int px, int py, int dist)                                 //  process one pixel
 
10324
{
 
10325
   uint16      *pix1, *pix3, *pixN;
 
10326
   int         bright1, bright3;
 
10327
   int         rgb, dx, dy, rad;
 
10328
   double      sumpix, kern, dold, dnew;
 
10329
   
 
10330
   rad = emboss_radius;
 
10331
 
 
10332
   if (px < rad || py < rad) return;
 
10333
   if (px > E3ww-rad-1 || py > E3hh-rad-1) return;
 
10334
   
 
10335
   pix1 = bmpixel(E1rgb48,px,py);                                          //  input pixel
 
10336
   pix3 = bmpixel(E3rgb48,px,py);                                          //  output pixel
 
10337
   
 
10338
   if (emboss_color)                                                       //  keep color   v.6.9
 
10339
   {
 
10340
      for (rgb = 0; rgb < 3; rgb++)
 
10341
      {      
 
10342
         sumpix = 0;
 
10343
         
 
10344
         for (dy = -rad; dy <= rad; dy++)                                  //  loop surrounding block of pixels
 
10345
         for (dx = -rad; dx <= rad; dx++)
 
10346
         {
 
10347
            pixN = pix1 + (dy * E1ww + dx) * 3;
 
10348
            kern = emboss_kernel[dx+rad][dy+rad];
 
10349
            sumpix += kern * pixN[rgb];
 
10350
      
 
10351
            bright1 = pix1[rgb];
 
10352
            bright3 = sumpix;
 
10353
            if (bright3 < 0) bright3 = 0;
 
10354
            if (bright3 > 65535) bright3 = 65535;
 
10355
 
 
10356
            if (dist < sa_blend) {                                         //  blend over distance sa_blend
 
10357
               dnew = 1.0 * dist / sa_blend;
 
10358
               dold = 1.0 - dnew;
 
10359
               bright3 = dnew * bright3 + dold * bright1;
 
10360
            }
 
10361
 
 
10362
            pix3[rgb] = bright3;
 
10363
         }
 
10364
      }
 
10365
   }
 
10366
   
 
10367
   else                                                                    //  use gray scale
 
10368
   {
 
10369
      sumpix = 0;
 
10370
         
 
10371
      for (dy = -rad; dy <= rad; dy++)                                     //  loop surrounding block of pixels
 
10372
      for (dx = -rad; dx <= rad; dx++)
 
10373
      {
 
10374
         pixN = pix1 + (dy * E1ww + dx) * 3;
 
10375
         kern = emboss_kernel[dx+rad][dy+rad];
 
10376
         sumpix += kern * (pixN[0] + pixN[1] + pixN[2]);
 
10377
      }
 
10378
      
 
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;
 
10383
      
 
10384
      if (dist < sa_blend) {                                               //  blend over distance sa_blend
 
10385
         dnew = 1.0 * dist / sa_blend;
 
10386
         dold = 1.0 - dnew;
 
10387
         bright3 = dnew * bright3 + dold * bright1;
 
10388
      }
 
10389
 
 
10390
      pix3[0] = pix3[1] = pix3[2] = bright3;
 
10391
   }
 
10392
 
 
10393
   return;
 
10394
}
 
10395
 
 
10396
 
 
10397
/**************************************************************************/
 
10398
 
 
10399
//  convert image to simulate square tiles
 
10400
 
 
10401
int         tile_size, tile_gap;
 
10402
int         tile_Npixels, tile_pixdone;
 
10403
uint16      *tile_pixmap = 0;
 
10404
 
 
10405
 
 
10406
void m_tiles(GtkWidget *, const char *)                                    //  new  v.6.8
 
10407
{
 
10408
   int    tile_dialog_event(zdialog *zd, const char *event);
 
10409
   int    tile_dialog_compl(zdialog *zd, int zstat);
 
10410
   void * tile_thread(void *);
 
10411
 
 
10412
   if (! edit_setup(0,2)) return;                                          //  setup edit: no preview
 
10413
 
 
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");
 
10422
 
 
10423
   zdialog_run(zdedit,tile_dialog_event,tile_dialog_compl);                //  start dialog
 
10424
 
 
10425
   tile_size = 5;
 
10426
   tile_gap = 1;
 
10427
 
 
10428
   tile_pixmap = (uint16 *) zmalloc(E1ww*E1hh*6);                          //  set up pixel color map
 
10429
   memset(tile_pixmap,0,E1ww*E1hh*6);
 
10430
 
 
10431
   start_thread(tile_thread,0);                                            //  start working thread
 
10432
   return;
 
10433
}
 
10434
 
 
10435
 
 
10436
//  tiles dialog event and completion callback functions
 
10437
 
 
10438
int tile_dialog_compl(zdialog * zd, int zstat)
 
10439
{
 
10440
   if (zstat == 1) edit_done();                                            //  done
 
10441
   else edit_cancel();                                                     //  cancel or destroy
 
10442
   zfree(tile_pixmap);
 
10443
   return 0;
 
10444
}
 
10445
 
 
10446
 
 
10447
int tile_dialog_event(zdialog * zd, const char *event)
 
10448
{
 
10449
   if (strNeq(event,"apply")) return 0;
 
10450
 
 
10451
   zdialog_fetch(zd,"size",tile_size);                                     //  get tile size
 
10452
   zdialog_fetch(zd,"gap",tile_gap);                                       //  get tile gap 
 
10453
 
 
10454
   if (tile_size < 2) {
 
10455
      if (Fmodified) edit_undo();                                          //  restore original image
 
10456
      Fmodified = 0;
 
10457
      return 0;
 
10458
   }
 
10459
   
 
10460
   signal_thread();                                                        //  trigger working thread
 
10461
   wait_thread_idle();                                                     //  wait for completion
 
10462
   mwpaint2(); 
 
10463
   return 1;
 
10464
}
 
10465
 
 
10466
 
 
10467
//  image tiles thread function
 
10468
 
 
10469
void * tile_thread(void *)
 
10470
{
 
10471
   int         sg, gg;
 
10472
   int         sumpix, red, green, blue;
 
10473
   int         ii, jj, px, py, qx, qy, dist;
 
10474
   uint16      *pix1, *pix3;
 
10475
 
 
10476
   while (true)
 
10477
   {
 
10478
      thread_idle_loop();                                                  //  wait for work or exit request
 
10479
 
 
10480
      sg = tile_size + tile_gap;
 
10481
      gg = tile_gap;
 
10482
 
 
10483
      for (py = 0; py < E1hh; py += sg)                                    //  initz. pixel color map for
 
10484
      for (px = 0; px < E1ww; px += sg)                                    //    given pixel size
 
10485
      {
 
10486
         sumpix = red = green = blue = 0;
 
10487
 
 
10488
         for (qy = py + gg; qy < py + sg; qy++)                            //  get mean color for pixel block
 
10489
         for (qx = px + gg; qx < px + sg; qx++)
 
10490
         {
 
10491
            if (qy > E1hh-1 || qx > E1ww-1) continue;
 
10492
 
 
10493
            pix1 = bmpixel(E1rgb48,qx,qy);
 
10494
            red += pix1[0];
 
10495
            green += pix1[1];
 
10496
            blue += pix1[2];
 
10497
            sumpix++;
 
10498
         }
 
10499
 
 
10500
         if (sumpix) {
 
10501
            red = (red / sumpix);
 
10502
            green = (green / sumpix);
 
10503
            blue = (blue / sumpix);
 
10504
         }
 
10505
         
 
10506
         for (qy = py; qy < py + sg; qy++)                                 //  set color for pixels in block
 
10507
         for (qx = px; qx < px + sg; qx++)
 
10508
         {
 
10509
            if (qy > E1hh-1 || qx > E1ww-1) continue;
 
10510
            
 
10511
            jj = (qy * E1ww + qx) * 3;
 
10512
 
 
10513
            if (qx-px < gg || qy-py < gg) {
 
10514
               tile_pixmap[jj] = tile_pixmap[jj+1] = tile_pixmap[jj+2] = 0;
 
10515
               continue;
 
10516
            }
 
10517
 
 
10518
            tile_pixmap[jj] = red;
 
10519
            tile_pixmap[jj+1] = green;
 
10520
            tile_pixmap[jj+2] = blue;
 
10521
         }
 
10522
      }
 
10523
 
 
10524
      if (! sa_Npixel)                                                     //  process entire image
 
10525
      {
 
10526
         for (py = 0; py < E3hh-1; py++)                                   //  loop all image pixels
 
10527
         for (px = 0; px < E3ww-1; px++)
 
10528
         {
 
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];
 
10534
         }
 
10535
      }
 
10536
 
 
10537
      if (sa_Npixel)                                                       //  process selected area
 
10538
      {
 
10539
         for (ii = 0; ii < sa_Npixel; ii++)                                //  process all enclosed pixels
 
10540
         {
 
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];
 
10549
         }
 
10550
      }
 
10551
 
 
10552
      Fmodified = 1;
 
10553
      mwpaint2();                                                          //  update window
 
10554
   }
 
10555
 
 
10556
   return 0;                                                               //  not executed, stop g++ warning
 
10557
}
 
10558
 
 
10559
 
 
10560
/**************************************************************************/
 
10561
 
 
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
 
10564
 
 
10565
namespace paint_namespace 
 
10566
{
 
10567
   int         color_depth;
 
10568
   int         group_area;
 
10569
   double      color_match;
 
10570
   int         borders;
 
10571
 
 
10572
   typedef struct  {
 
10573
      int16       px, py;
 
10574
      char        direc;
 
10575
   }  spixstack;
 
10576
 
 
10577
   int         Nstack;
 
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
 
10581
 
 
10582
   int         group;
 
10583
   char        direc;
 
10584
   uint16      gcolor[3];
 
10585
}
 
10586
 
 
10587
using namespace paint_namespace;
 
10588
 
 
10589
 
 
10590
void m_painting(GtkWidget *, const char *)
 
10591
{
 
10592
   int      painting_dialog_event(zdialog *zd, const char *event);
 
10593
   int      painting_dialog_compl(zdialog *zd, int zstat);
 
10594
   void   * painting_thread(void *);
 
10595
 
 
10596
   if (! edit_setup(0,2)) return;                                          //  setup edit: no preview
 
10597
 
 
10598
   zdedit = zdialog_new(ZTX("Simulate Painting"),mWin,Bdone,Bcancel,null);
 
10599
 
 
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");
 
10603
 
 
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");
 
10607
 
 
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");
 
10611
 
 
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");
 
10615
 
 
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");
 
10619
 
 
10620
   zdialog_run(zdedit,painting_dialog_event,painting_dialog_compl);        //  run dialog - parallel
 
10621
 
 
10622
   start_thread(painting_thread,0);                                        //  start working thread
 
10623
   return;
 
10624
}
 
10625
 
 
10626
 
 
10627
//  painting dialog event and completion callback functions
 
10628
 
 
10629
int painting_dialog_event(zdialog *zd, const char *event)
 
10630
{
 
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
 
10637
      signal_thread();
 
10638
   }
 
10639
   
 
10640
   if (strEqu(event,"undo")) edit_undo();
 
10641
   return 0;
 
10642
}
 
10643
 
 
10644
 
 
10645
int painting_dialog_compl(zdialog * zd, int zstat)                         //  done or cancel button
 
10646
{
 
10647
   if (zstat == 1) edit_done();
 
10648
   else edit_cancel();
 
10649
   return 0;
 
10650
}
 
10651
 
 
10652
 
 
10653
//  painting thread function
 
10654
 
 
10655
void * painting_thread(void *)
 
10656
{
 
10657
   void  paint_colordepth();
 
10658
   void  paint_pixgroups();
 
10659
   void  paint_mergegroups();
 
10660
   void  paint_paintborders();
 
10661
   void  paint_blend();
 
10662
 
 
10663
   while (true)
 
10664
   {
 
10665
      thread_idle_loop();                                                  //  wait for work or exit request
 
10666
      
 
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
 
10672
 
 
10673
      Fmodified = 1;
 
10674
      mwpaint2();                                                          //  update window
 
10675
   }
 
10676
 
 
10677
   return 0;                                                               //  not executed, stop g++ warning
 
10678
}
 
10679
 
 
10680
 
 
10681
//  set the specified color depth, 1-5 bits/color
 
10682
 
 
10683
void paint_colordepth()
 
10684
{
 
10685
   int            ii, px, py, rgb;
 
10686
   double         fmag;
 
10687
   uint16         m1, m2, val1, val3;
 
10688
   uint16         *pix1, *pix3;
 
10689
 
 
10690
   m1 = 0xFFFF << (16 - color_depth);                                      //  5 > 1111100000000000
 
10691
   m2 = 0x8000 >> color_depth;                                             //  5 > 0000010000000000
 
10692
 
 
10693
   fmag = 65535.0 / m1;                                                    //  full brightness range
 
10694
 
 
10695
   if (! sa_Npixel)                                                        //  process entire image
 
10696
   {
 
10697
      for (py = 0; py < E3hh; py++)                                        //  loop all pixels
 
10698
      for (px = 0; px < E3ww; px++)
 
10699
      {
 
10700
         pix1 = bmpixel(E1rgb48,px,py);                                    //  input pixel
 
10701
         pix3 = bmpixel(E3rgb48,px,py);                                    //  output pixel
 
10702
         
 
10703
         for (rgb = 0; rgb < 3; rgb++)
 
10704
         {
 
10705
            val1 = pix1[rgb];
 
10706
            if (val1 < m1) val3 = (val1 + m2) & m1;                        //  round   v.7.0
 
10707
            else val3 = m1;
 
10708
            val3 = uint(val3 * fmag);
 
10709
            pix3[rgb] = val3;
 
10710
         }
 
10711
      }
 
10712
   }
 
10713
 
 
10714
   if (sa_Npixel)                                                          //  process select area
 
10715
   {
 
10716
      for (ii = 0; ii < sa_Npixel; ii++)                                   //  loop all pixels in area
 
10717
      {
 
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
 
10722
 
 
10723
         for (rgb = 0; rgb < 3; rgb++)
 
10724
         {
 
10725
            val1 = pix1[rgb];
 
10726
            if (val1 < m1) val3 = (val1 + m2) & m1;
 
10727
            else val3 = m1;
 
10728
            val3 = uint(val3 * fmag);
 
10729
            pix3[rgb] = val3;
 
10730
         }
 
10731
      }
 
10732
   }
 
10733
   
 
10734
   return;
 
10735
}
 
10736
 
 
10737
 
 
10738
//  find all groups of contiguous pixels with the same color
 
10739
 
 
10740
void paint_pixgroups()
 
10741
{
 
10742
   void  paint_pushpix(int px, int py);
 
10743
 
 
10744
   int            cc1, cc2;
 
10745
   int            ii, kk, px, py;
 
10746
   uint16         *pix3;
 
10747
   
 
10748
   cc1 = E3ww * E3hh;
 
10749
 
 
10750
   cc2 = cc1 * sizeof(int);
 
10751
   pixgroup = (int *) zmalloc(cc2);                                        //  maps pixel to assigned group
 
10752
   memset(pixgroup,0,cc2);
 
10753
   
 
10754
   if (sa_Npixel) cc1 = sa_Npixel;
 
10755
 
 
10756
   cc2 = cc1 * sizeof(spixstack);
 
10757
   pixstack = (spixstack *) zmalloc(cc2);                                  //  memory stack for pixel search
 
10758
   memset(pixstack,0,cc2);
 
10759
   
 
10760
   cc2 = cc1 * sizeof(int);
 
10761
   groupcount = (int *) zmalloc(cc2);                                      //  counts pixels per group
 
10762
   memset(groupcount,0,cc2);
 
10763
   
 
10764
   group = 0;
 
10765
   
 
10766
   if (! sa_Npixel)
 
10767
   {
 
10768
      for (py = 0; py < E3hh; py++)                                        //  loop all pixels
 
10769
      for (px = 0; px < E3ww; px++)
 
10770
      {
 
10771
         kk = py * E3ww + px;
 
10772
         if (pixgroup[kk]) continue;                                       //  already assigned to group
 
10773
 
 
10774
         pixgroup[kk] = ++group;                                           //  assign next group
 
10775
         ++groupcount[group];
 
10776
 
 
10777
         pix3 = bmpixel(E3rgb48,px,py);
 
10778
         gcolor[0] = pix3[0];
 
10779
         gcolor[1] = pix3[1];
 
10780
         gcolor[2] = pix3[2];
 
10781
 
 
10782
         pixstack[0].px = px;                                              //  put pixel into stack with
 
10783
         pixstack[0].py = py;                                              //    direction = right
 
10784
         pixstack[0].direc = 'r';
 
10785
         Nstack = 1;
 
10786
 
 
10787
         while (Nstack)
 
10788
         {
 
10789
            kk = Nstack - 1;                                               //  get last pixel in stack
 
10790
            px = pixstack[kk].px;
 
10791
            py = pixstack[kk].py;
 
10792
            direc = pixstack[kk].direc;
 
10793
            
 
10794
            if (direc == 'x') {
 
10795
               Nstack--;
 
10796
               continue;
 
10797
            }
 
10798
            
 
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
 
10802
               continue;
 
10803
            }
 
10804
 
 
10805
            if (direc == 'l') {                                            //  or next left pixel
 
10806
               paint_pushpix(px,py);
 
10807
               pixstack[kk].direc = 'a';
 
10808
               continue;
 
10809
            }
 
10810
 
 
10811
            if (direc == 'a') {                                            //  or next ahead pixel
 
10812
               paint_pushpix(px,py);
 
10813
               pixstack[kk].direc = 'x';
 
10814
               continue;
 
10815
            }
 
10816
         }
 
10817
      }
 
10818
   }
 
10819
   
 
10820
   if (sa_Npixel)
 
10821
   {
 
10822
      for (ii = 0; ii < sa_Npixel; ii++)                                   //  loop all pixels in area
 
10823
      {
 
10824
         px = sa_pixel[ii].px;
 
10825
         py = sa_pixel[ii].py;
 
10826
 
 
10827
         kk = py * E3ww + px;
 
10828
         if (pixgroup[kk]) continue;                                       //  already assigned to group
 
10829
 
 
10830
         pixgroup[kk] = ++group;                                           //  assign next group
 
10831
         ++groupcount[group];
 
10832
 
 
10833
         pix3 = bmpixel(E3rgb48,px,py);
 
10834
         gcolor[0] = pix3[0];
 
10835
         gcolor[1] = pix3[1];
 
10836
         gcolor[2] = pix3[2];
 
10837
 
 
10838
         pixstack[0].px = px;                                              //  put pixel into stack with
 
10839
         pixstack[0].py = py;                                              //    direction = right
 
10840
         pixstack[0].direc = 'r';
 
10841
         Nstack = 1;
 
10842
 
 
10843
         while (Nstack)
 
10844
         {
 
10845
            kk = Nstack - 1;                                               //  get last pixel in stack
 
10846
            px = pixstack[kk].px;
 
10847
            py = pixstack[kk].py;
 
10848
            direc = pixstack[kk].direc;
 
10849
            
 
10850
            if (direc == 'x') {
 
10851
               Nstack--;
 
10852
               continue;
 
10853
            }
 
10854
            
 
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
 
10858
               continue;
 
10859
            }
 
10860
 
 
10861
            if (direc == 'l') {                                            //  or next left pixel
 
10862
               paint_pushpix(px,py);
 
10863
               pixstack[kk].direc = 'a';
 
10864
               continue;
 
10865
            }
 
10866
 
 
10867
            if (direc == 'a') {                                            //  or next ahead pixel
 
10868
               paint_pushpix(px,py);
 
10869
               pixstack[kk].direc = 'x';
 
10870
               continue;
 
10871
            }
 
10872
         }
 
10873
      }
 
10874
   }
 
10875
 
 
10876
   return;
 
10877
}      
 
10878
 
 
10879
 
 
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
 
10882
 
 
10883
void paint_pushpix(int px, int py)
 
10884
{
 
10885
   int      kk, ppx, ppy, npx, npy;
 
10886
   uint16   *pix3;
 
10887
 
 
10888
   if (Nstack > 1) {
 
10889
      kk = Nstack - 2;                                                     //  get prior pixel in stack
 
10890
      ppx = pixstack[kk].px;
 
10891
      ppy = pixstack[kk].py;
 
10892
   }
 
10893
   else {
 
10894
      ppx = px - 1;                                                        //  if only one, assume prior = left
 
10895
      ppy = py;
 
10896
   }
 
10897
   
 
10898
   if (direc == 'r') {                                                     //  get pixel in direction right
 
10899
      npx = px + ppy - py;
 
10900
      npy = py + px - ppx;
 
10901
   }
 
10902
   else if (direc == 'l') {                                                //  or left
 
10903
      npx = px + py - ppy;
 
10904
      npy = py + ppx - px;
 
10905
   }
 
10906
   else if (direc == 'a') {                                                //  or ahead
 
10907
      npx = px + px - ppx;
 
10908
      npy = py + py - ppy;
 
10909
   }
 
10910
   else npx = npy = -1;                                                    //  stop warning
 
10911
   
 
10912
   if (npx < 0 || npx >= E3ww) return;                                     //  pixel off the edge
 
10913
   if (npy < 0 || npy >= E3hh) return;
 
10914
   
 
10915
   kk = npy * E3ww + npx;
 
10916
 
 
10917
   if (sa_Npixel)
 
10918
      if (! sa_pixisin[kk]) return;                                        //  pixel outside area
 
10919
 
 
10920
   if (pixgroup[kk]) return;                                               //  pixel already assigned
 
10921
 
 
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;
 
10925
   
 
10926
   pixgroup[kk] = group;                                                   //  assign pixel to group
 
10927
   ++groupcount[group];
 
10928
 
 
10929
   kk = Nstack++;                                                          //  put pixel into stack
 
10930
   pixstack[kk].px = npx;
 
10931
   pixstack[kk].py = npy;
 
10932
   pixstack[kk].direc = 'r';                                               //  direction = right
 
10933
   
 
10934
   return;
 
10935
}
 
10936
 
 
10937
 
 
10938
//  merge small pixel groups into adjacent larger groups with best color match
 
10939
 
 
10940
void paint_mergegroups()
 
10941
{
 
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;
 
10949
 
 
10950
   typedef struct  {
 
10951
      int         group;
 
10952
      double      match;
 
10953
      uint16      pixM[3];
 
10954
   }  snewgroup;
 
10955
 
 
10956
   snewgroup      *newgroup;
 
10957
   
 
10958
   nccc = (group + 1) * sizeof(snewgroup);
 
10959
   newgroup = (snewgroup *) zmalloc(nccc);
 
10960
   
 
10961
   if (! sa_Npixel)
 
10962
   {
 
10963
      while (true)
 
10964
      {
 
10965
         memset(newgroup,0,nccc);
 
10966
 
 
10967
         for (py = 0; py < E3hh; py++)                                     //  loop all pixels
 
10968
         for (px = 0; px < E3ww; px++)
 
10969
         {
 
10970
            kk = E3ww * py + px;                                           //  get assigned group
 
10971
            group = pixgroup[kk];
 
10972
            if (groupcount[group] >= group_area) continue;                 //  group count large enough
 
10973
 
 
10974
            pix3 = bmpixel(E3rgb48,px,py);
 
10975
 
 
10976
            for (jj = 0; jj < 4; jj++)                                     //  get 4 neighbor pixels
 
10977
            {
 
10978
               npx = px + nnpx[jj];
 
10979
               npy = py + nnpy[jj];
 
10980
 
 
10981
               if (npx < 0 || npx >= E3ww) continue;                       //  off the edge
 
10982
               if (npy < 0 || npy >= E3hh) continue;
 
10983
               
 
10984
               kk = E3ww * npy + npx;
 
10985
               if (pixgroup[kk] == group) continue;                        //  in same group
 
10986
 
 
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;
 
10993
 
 
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];
 
11000
               }
 
11001
            }
 
11002
         }
 
11003
 
 
11004
         mcount = 0;
 
11005
 
 
11006
         for (py = 0; py < E3hh; py++)                                     //  loop all pixels
 
11007
         for (px = 0; px < E3ww; px++)
 
11008
         {
 
11009
            kk = E3ww * py + px;
 
11010
            group = pixgroup[kk];                                          //  test for new group assignment
 
11011
            group2 = newgroup[group].group;
 
11012
            if (! group2) continue;
 
11013
            
 
11014
            if (groupcount[group] > groupcount[group2]) continue;          //  accept only bigger new group
 
11015
 
 
11016
            pixgroup[kk] = group2;                                         //  make new group assignment
 
11017
            --groupcount[group];
 
11018
            ++groupcount[group2];
 
11019
 
 
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];
 
11024
 
 
11025
            mcount++;
 
11026
         }
 
11027
         
 
11028
         if (mcount == 0) break;
 
11029
      }
 
11030
   }
 
11031
 
 
11032
   if (sa_Npixel)
 
11033
   {
 
11034
      while (true)
 
11035
      {
 
11036
         memset(newgroup,0,nccc);
 
11037
 
 
11038
         for (ii = 0; ii < sa_Npixel; ii++)                                //  loop all pixels in area
 
11039
         {
 
11040
            px = sa_pixel[ii].px;
 
11041
            py = sa_pixel[ii].py;
 
11042
 
 
11043
            kk = E3ww * py + px;                                           //  get assigned group
 
11044
            group = pixgroup[kk];
 
11045
            if (groupcount[group] >= group_area) continue;                 //  group count large enough
 
11046
 
 
11047
            pix3 = bmpixel(E3rgb48,px,py);
 
11048
 
 
11049
            for (jj = 0; jj < 4; jj++)                                     //  get 4 neighbor pixels
 
11050
            {
 
11051
               npx = px + nnpx[jj];
 
11052
               npy = py + nnpy[jj];
 
11053
 
 
11054
               if (npx < 0 || npx >= E3ww) continue;                       //  off the edge
 
11055
               if (npy < 0 || npy >= E3hh) continue;
 
11056
 
 
11057
               kk = E3ww * npy + npx;
 
11058
               if (! sa_pixisin[kk]) continue;                             //  pixel outside area
 
11059
               if (pixgroup[kk] == group) continue;                        //  already in same group
 
11060
               
 
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;
 
11067
 
 
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];
 
11074
               }
 
11075
            }
 
11076
         }
 
11077
 
 
11078
         mcount = 0;
 
11079
 
 
11080
         for (ii = 0; ii < sa_Npixel; ii++)                                //  loop all pixels in area
 
11081
         {
 
11082
            px = sa_pixel[ii].px;
 
11083
            py = sa_pixel[ii].py;
 
11084
 
 
11085
            kk = E3ww * py + px;
 
11086
            group = pixgroup[kk];                                          //  test for new group assignment
 
11087
            group2 = newgroup[group].group;
 
11088
            if (! group2) continue;
 
11089
            
 
11090
            if (groupcount[group] > groupcount[group2]) continue;          //  accept only bigger new group
 
11091
 
 
11092
            pixgroup[kk] = group2;                                         //  make new group assignment
 
11093
            --groupcount[group];
 
11094
            ++groupcount[group2];
 
11095
 
 
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];
 
11100
 
 
11101
            mcount++;
 
11102
         }
 
11103
         
 
11104
         if (mcount == 0) break;
 
11105
      }
 
11106
   }
 
11107
 
 
11108
   zfree(pixgroup);
 
11109
   zfree(pixstack);
 
11110
   zfree(groupcount);
 
11111
   zfree(newgroup);
 
11112
 
 
11113
   return;
 
11114
}
 
11115
 
 
11116
 
 
11117
//  paint borders between the groups of contiguous pixels
 
11118
 
 
11119
void paint_paintborders()
 
11120
{
 
11121
   int            ii, kk, px, py, cc;
 
11122
   uint16         *pix3, *pixL, *pixA;
 
11123
   
 
11124
   if (! borders) return;
 
11125
   
 
11126
   cc = E3ww * E3hh;
 
11127
   char * pixblack = zmalloc(cc);
 
11128
   memset(pixblack,0,cc);
 
11129
 
 
11130
   if (! sa_Npixel)
 
11131
   {
 
11132
      for (py = 1; py < E3hh; py++)                                        //  loop all pixels
 
11133
      for (px = 1; px < E3ww; px++)                                        //  omit top and left
 
11134
      {
 
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
 
11138
         
 
11139
         if (pix3[0] != pixL[0] || pix3[1] != pixL[1] || pix3[2] != pixL[2])
 
11140
         {
 
11141
            kk = E3ww * py + px-1;                                         //  have horiz. transition
 
11142
            if (pixblack[kk]) continue;
 
11143
            kk += 1;
 
11144
            pixblack[kk] = 1;
 
11145
            continue;
 
11146
         }
 
11147
 
 
11148
         if (pix3[0] != pixA[0] || pix3[1] != pixA[1] || pix3[2] != pixA[2])
 
11149
         {
 
11150
            kk = E3ww * (py-1) + px;                                       //  have vertical transition
 
11151
            if (pixblack[kk]) continue;
 
11152
            kk += E3ww;
 
11153
            pixblack[kk] = 1;
 
11154
         }
 
11155
      }
 
11156
 
 
11157
      for (py = 1; py < E3hh; py++)
 
11158
      for (px = 1; px < E3ww; px++)
 
11159
      {
 
11160
         kk = E3ww * py + px;
 
11161
         if (! pixblack[kk]) continue;
 
11162
         pix3 = bmpixel(E3rgb48,px,py);
 
11163
         pix3[0] = pix3[1] = pix3[2] = 0;
 
11164
      }
 
11165
   }
 
11166
   
 
11167
   if (sa_Npixel)
 
11168
   {
 
11169
      for (ii = 0; ii < sa_Npixel; ii++)
 
11170
      {
 
11171
         px = sa_pixel[ii].px;
 
11172
         py = sa_pixel[ii].py;
 
11173
         if (px < 1 || py < 1) continue;
 
11174
 
 
11175
         pix3 = bmpixel(E3rgb48,px,py);
 
11176
         pixL = bmpixel(E3rgb48,px-1,py);
 
11177
         pixA = bmpixel(E3rgb48,px,py-1);
 
11178
         
 
11179
         if (pix3[0] != pixL[0] || pix3[1] != pixL[1] || pix3[2] != pixL[2])
 
11180
         {
 
11181
            kk = E3ww * py + px-1;
 
11182
            if (pixblack[kk]) continue;
 
11183
            kk += 1;
 
11184
            pixblack[kk] = 1;
 
11185
            continue;
 
11186
         }
 
11187
 
 
11188
         if (pix3[0] != pixA[0] || pix3[1] != pixA[1] || pix3[2] != pixA[2])
 
11189
         {
 
11190
            kk = E3ww * (py-1) + px;
 
11191
            if (pixblack[kk]) continue;
 
11192
            kk += E3ww;
 
11193
            pixblack[kk] = 1;
 
11194
         }
 
11195
      }
 
11196
 
 
11197
      for (ii = 0; ii < sa_Npixel; ii++)
 
11198
      {
 
11199
         px = sa_pixel[ii].px;
 
11200
         py = sa_pixel[ii].py;
 
11201
         if (px < 1 || py < 1) continue;
 
11202
 
 
11203
         kk = E3ww * py + px;
 
11204
         if (! pixblack[kk]) continue;
 
11205
         pix3 = bmpixel(E3rgb48,px,py);
 
11206
         pix3[0] = pix3[1] = pix3[2] = 0;
 
11207
      }
 
11208
   }
 
11209
         
 
11210
   zfree(pixblack);
 
11211
   return;
 
11212
}
 
11213
 
 
11214
 
 
11215
//  blend edges of selected area
 
11216
 
 
11217
void paint_blend()
 
11218
{
 
11219
   int         ii, px, py, rgb, dist;
 
11220
   uint16      *pix1, *pix3;
 
11221
   double      f1, f2;
 
11222
   
 
11223
   if (sa_Npixel && sa_blend > 0)
 
11224
   {
 
11225
      for (ii = 0; ii < sa_Npixel; ii++)                                   //  loop all pixels in area
 
11226
      {
 
11227
         dist = sa_pixel[ii].dist;
 
11228
         if (dist >= sa_blend) continue;
 
11229
 
 
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
 
11234
 
 
11235
         f2 = 1.0 * dist / sa_blend;                                       //  changes over distance sa_blend
 
11236
         f1 = 1.0 - f2;
 
11237
 
 
11238
         for (rgb = 0; rgb < 3; rgb++)
 
11239
            pix3[rgb] = int(f1 * pix1[rgb] + f2 * pix3[rgb]);
 
11240
      }
 
11241
   }
 
11242
 
 
11243
   return;
 
11244
}
 
11245
 
 
11246
 
 
11247
/**************************************************************************/
 
11248
 
 
11249
//  pixel edit function - edit individual pixels
 
11250
 
 
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();
 
11256
 
 
11257
int      pixed_RGB[3];
 
11258
int      pixed_mode;
 
11259
int      pixed_suspend;
 
11260
int      pixed_radius;
 
11261
double   pixed_kernel[200][200];                                           //  radius <= 99
 
11262
 
 
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
 
11267
 
 
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] 
 
11274
}  pixed_savepix;
 
11275
 
 
11276
pixed_savepix   **pixed_undopixmem = 0;                                    //  array of *pixed_savepix
 
11277
 
 
11278
 
 
11279
void m_pixedit(GtkWidget *, const char *)
 
11280
{
 
11281
   int   pixed_dialog_event(zdialog* zd, const char *event);
 
11282
   int   pixed_dialog_compl(zdialog* zd, int zstat);
 
11283
 
 
11284
   char        undomemmessage[100];
 
11285
 
 
11286
   if (! edit_setup(0,1)) return;                                          //  setup edit: no preview
 
11287
 
 
11288
   strncpy0(pixed_undomemmessage,ZTX("Undo Memory %d%c"),99);              //  translate undo memory message
 
11289
 
 
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");
 
11315
 
 
11316
   zdialog_run(zdedit,pixed_dialog_event,pixed_dialog_compl);              //  run dialog - parallel
 
11317
 
 
11318
   zdialog_send_event(zdedit,"radius");                                    //  get kernel initialized
 
11319
 
 
11320
   snprintf(undomemmessage,99,pixed_undomemmessage,0,'%');                 //  stuff undo memory status
 
11321
   zdialog_stuff(zdedit,"labmem",undomemmessage);
 
11322
   
 
11323
   pixed_RGB[0] = pixed_RGB[1] = pixed_RGB[2] = 100;                       //  initialize color
 
11324
   
 
11325
   pixed_mode = 1;                                                         //  mode = pick color
 
11326
   pixed_suspend = 0;                                                      //  not suspended
 
11327
 
 
11328
   pixed_undopixmem = 0;                                                   //  no undo data
 
11329
   pixed_undototpix = 0;
 
11330
   pixed_undototmem = 0;
 
11331
   pixed_undoseq = 0;
 
11332
 
 
11333
   mouseCBfunc = pixed_mousefunc;                                          //  connect mouse function
 
11334
   Mcapture = 1;                                                           //  capture mouse clicks
 
11335
   return;
 
11336
}
 
11337
 
 
11338
 
 
11339
//  pixedit dialog event and completion functions
 
11340
 
 
11341
int pixed_dialog_compl(zdialog *zd, int zstat)                             //  pixedit dialog completion function
 
11342
{
 
11343
   if (zstat == 1) edit_done();                                            //  done
 
11344
   else edit_cancel();                                                     //  cancel or destroy
 
11345
 
 
11346
   paint_toparc(2);                                                        //  remove brush outline      v.8.3
 
11347
   Mcapture = 0;                                                           //  disconnect mouse
 
11348
   mouseCBfunc = 0;
 
11349
   pixed_freeundo();                                                       //  free undo memory
 
11350
   return 0;
 
11351
}
 
11352
 
 
11353
 
 
11354
int pixed_dialog_event(zdialog *zd, const char *event)                     //  pixedit dialog event function
 
11355
{
 
11356
   char        color[20];
 
11357
   const char  *pp;
 
11358
   int         radius, dx, dy, brad;
 
11359
   double      rad, kern, trcent, tredge;
 
11360
   
 
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;
 
11367
   
 
11368
   if (strEqu(event,"color")) 
 
11369
   {
 
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);
 
11377
   }
 
11378
   
 
11379
   if (strstr("radius trcent tredge",event))                               //  get new brush attributes
 
11380
   {
 
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
 
11384
 
 
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);
 
11392
 
 
11393
      for (dy = -radius; dy <= radius; dy++)                               //  build kernel
 
11394
      for (dx = -radius; dx <= radius; dx++)
 
11395
      {
 
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;
 
11403
      }
 
11404
   }
 
11405
   
 
11406
   if (strEqu(event,"undlast"))                                            //  undo last edit (click or drag)
 
11407
      pixed_undo1();                                                       //  v.7.8
 
11408
 
 
11409
   if (strEqu(event,"undall")) {                                           //  undo all edits      v.7.8
 
11410
      edit_undo();
 
11411
      pixed_freeundo();
 
11412
   }
 
11413
 
 
11414
   if (strEqu(event,"susp-resm"))                                          //  toggle suspend / resume   v.7.8
 
11415
   {
 
11416
      if (pixed_suspend) {
 
11417
         pixed_suspend = 0;
 
11418
         mouseCBfunc = pixed_mousefunc;                                    //  connect mouse function
 
11419
         Mcapture++;
 
11420
         zdialog_stuff(zd,"susp-resm",Bsuspend);
 
11421
      }
 
11422
      else  {
 
11423
         pixed_suspend = 1;
 
11424
         mouseCBfunc = 0;                                                  //  disconnect mouse function
 
11425
         Mcapture = 0;
 
11426
         zdialog_stuff(zd,"susp-resm",Bresume);
 
11427
      }
 
11428
   }
 
11429
 
 
11430
   return 1;
 
11431
}
 
11432
 
 
11433
 
 
11434
//  pixel edit mouse function
 
11435
 
 
11436
void pixed_mousefunc()
 
11437
{
 
11438
   static int  pmxdown = 0, pmydown = 0;
 
11439
   int         px, py;
 
11440
   char        color[20];
 
11441
   uint16      *ppix3;
 
11442
   
 
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;
 
11447
   else toparc = 1;
 
11448
   if (toparc) paint_toparc(3);
 
11449
   
 
11450
   if (LMclick)                                                            //  left mouse click
 
11451
   {
 
11452
      LMclick = 0;
 
11453
      px = Mxclick;
 
11454
      py = Myclick;
 
11455
 
 
11456
      if (pixed_mode == 1)                                                 //  pick new color from image
 
11457
      {
 
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);
 
11464
      }
 
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
 
11469
      }
 
11470
   }
 
11471
   
 
11472
   if (Mxdrag || Mydrag)                                                   //  drag in progress
 
11473
   {
 
11474
      px = Mxdrag;
 
11475
      py = Mydrag;
 
11476
      Mxdrag = Mydrag = 0;
 
11477
 
 
11478
      if (Mxdown != pmxdown || Mydown != pmydown) {                        //  new drag
 
11479
         pixed_undoseq++;                                                  //  new undo seq. no.
 
11480
         pmxdown = Mxdown;
 
11481
         pmydown = Mydown;
 
11482
      }
 
11483
      pixed_saveundo(px,py);                                               //  save for poss. undo
 
11484
      pixed_dopixels(px,py);                                               //  do 1 block of pixels
 
11485
   }
 
11486
   
 
11487
   return;
 
11488
}
 
11489
 
 
11490
 
 
11491
//  paint or erase 1 block of pixels within radius of px, py
 
11492
 
 
11493
void pixed_dopixels(int px, int py)
 
11494
{
 
11495
   uint16      *ppix1, *ppix3;
 
11496
   int         radius, dx, dy;
 
11497
   int         red, green, blue;
 
11498
   double      kern;
 
11499
 
 
11500
   radius = pixed_radius;
 
11501
 
 
11502
   red = 256 * pixed_RGB[0];
 
11503
   green = 256 * pixed_RGB[1];
 
11504
   blue = 256 * pixed_RGB[2];
 
11505
 
 
11506
   for (dy = -radius; dy <= radius; dy++)                                  //  loop surrounding block of pixels
 
11507
   for (dx = -radius; dx <= radius; dx++)
 
11508
   {
 
11509
      if (px + dx < 0 || px + dx > E3ww-1) continue;                       //  v.7.5
 
11510
      if (py + dy < 0 || py + dy > E3hh-1) continue;
 
11511
      
 
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
 
11515
 
 
11516
      if (pixed_mode == 2)                                                 //  color pixels transparently
 
11517
      {
 
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];
 
11521
         Fmodified = 1;
 
11522
      }
 
11523
 
 
11524
      if (pixed_mode == 3)                                                 //  restore org. pixels transparently
 
11525
      {
 
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];
 
11529
      }
 
11530
   }
 
11531
 
 
11532
   mwpaint2();
 
11533
   return;
 
11534
}
 
11535
 
 
11536
 
 
11537
//  save 1 block of pixels for possible undo
 
11538
 
 
11539
void pixed_saveundo(int px, int py)
 
11540
{
 
11541
   int            npix, radius, dx, dy;
 
11542
   uint16         *ppix3;
 
11543
   pixed_savepix  *ppixsave1;
 
11544
   char           undomemmessage[100];
 
11545
   int            mempercent;
 
11546
   static int     ppercent = 0;
 
11547
 
 
11548
   if (! pixed_undopixmem)                                                 //  first call
 
11549
   {
 
11550
      pixed_undopixmem = (pixed_savepix **) zmalloc(pixed_undomaxpix * sizeof(void *));
 
11551
      pixed_undototpix = 0;
 
11552
      pixed_undototmem = 0;
 
11553
   }
 
11554
   
 
11555
   if (pixed_undototmem > pixed_undomaxmem) 
 
11556
   {
 
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
 
11560
      return;
 
11561
   }
 
11562
 
 
11563
   radius = pixed_radius;
 
11564
   npix = 0;
 
11565
 
 
11566
   for (dy = -radius; dy <= radius; dy++)                                  //  count pixels in block
 
11567
   for (dx = -radius; dx <= radius; dx++)
 
11568
   {
 
11569
      if (px + dx < 0 || px + dx > E3ww-1) continue;
 
11570
      if (py + dy < 0 || py + dy > E3hh-1) continue;
 
11571
      npix++;
 
11572
   }
 
11573
   
 
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;
 
11578
   
 
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;
 
11584
 
 
11585
   npix = 0;
 
11586
 
 
11587
   for (dy = -radius; dy <= radius; dy++)                                  //  save pixels in block
 
11588
   for (dx = -radius; dx <= radius; dx++)
 
11589
   {
 
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];
 
11596
      npix++;
 
11597
   }
 
11598
 
 
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);
 
11604
   }
 
11605
 
 
11606
   return;
 
11607
}
 
11608
 
 
11609
 
 
11610
//  undo last undo sequence number
 
11611
 
 
11612
void pixed_undo1()
 
11613
{
 
11614
   int            pindex, npix, radius, px, py, dx, dy;
 
11615
   uint16         *ppix3;
 
11616
   pixed_savepix  *ppixsave1;
 
11617
   char           undomemmessage[100];
 
11618
   int            mempercent;
 
11619
   
 
11620
   pindex = pixed_undototpix;
 
11621
   
 
11622
   while (pindex > 0)
 
11623
   {
 
11624
      --pindex;
 
11625
      ppixsave1 = pixed_undopixmem[pindex];
 
11626
      if (ppixsave1->seq != pixed_undoseq) break;
 
11627
      px = ppixsave1->px;
 
11628
      py = ppixsave1->py;
 
11629
      radius = ppixsave1->radius;
 
11630
 
 
11631
      npix = 0;
 
11632
 
 
11633
      for (dy = -radius; dy <= radius; dy++)
 
11634
      for (dx = -radius; dx <= radius; dx++)
 
11635
      {
 
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];
 
11642
         npix++;
 
11643
      }
 
11644
 
 
11645
      npix = ppixsave1->npix;
 
11646
      zfree(ppixsave1);
 
11647
      pixed_undopixmem[pindex] = 0;
 
11648
      pixed_undototmem -= (npix * 6 + 12);
 
11649
      --pixed_undototpix;
 
11650
   }
 
11651
   
 
11652
   if (pixed_undoseq > 0) --pixed_undoseq;
 
11653
 
 
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);
 
11657
 
 
11658
   mwpaint2();
 
11659
   return;
 
11660
}
 
11661
 
 
11662
 
 
11663
//  free all undo memory
 
11664
 
 
11665
void pixed_freeundo()
 
11666
{
 
11667
   int            pindex;
 
11668
   pixed_savepix  *ppixsave1;
 
11669
   char           undomemmessage[100];
 
11670
 
 
11671
   pindex = pixed_undototpix;
 
11672
   
 
11673
   while (pindex > 0)
 
11674
   {
 
11675
      --pindex;
 
11676
      ppixsave1 = pixed_undopixmem[pindex];
 
11677
      zfree(ppixsave1);
 
11678
   }
 
11679
   
 
11680
   if (pixed_undopixmem) zfree(pixed_undopixmem);
 
11681
   pixed_undopixmem = 0;
 
11682
   
 
11683
   pixed_undoseq = 0;
 
11684
   pixed_undototpix = 0;
 
11685
   pixed_undototmem = 0;
 
11686
 
 
11687
   if (zdedit) {
 
11688
      snprintf(undomemmessage,99,pixed_undomemmessage,0,'%');              //  undo memory = 0%
 
11689
      zdialog_stuff(zdedit,"labmem",undomemmessage);
 
11690
   }
 
11691
 
 
11692
   return;
 
11693
}
 
11694
 
 
11695
 
 
11696
/**************************************************************************/
 
11697
 
 
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.
 
11701
 
 
11702
 
 
11703
void *   HDR_align_thread(void *);
 
11704
void *   HDR_dialog_thread(void *);
 
11705
void     HDR_combine(int ww);
 
11706
 
 
11707
int      HDR_brcurve_adjust(GtkWidget *, GdkEventButton *);
 
11708
int      HDR_brcurve_draw(GtkWidget *);
 
11709
 
 
11710
int      HDR_align_stat = 0;
 
11711
 
 
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;
 
11716
 
 
11717
GtkWidget  *HDR_brcurve;
 
11718
 
 
11719
 
 
11720
//  menu function
 
11721
 
 
11722
void m_HDR(GtkWidget *, const char *)
 
11723
{
 
11724
   char        *file2 = 0;
 
11725
 
 
11726
   if (! edit_setup(0,0)) return;                                          //  setup edit: no preview
 
11727
   
 
11728
   file2 = zgetfile(ZTX("Select image to combine"),image_file,"open");     //  get 2nd HDR image
 
11729
   if (! file2) {
 
11730
      edit_cancel();
 
11731
      return;
 
11732
   }
 
11733
 
 
11734
   Grgb48 = image_load(file2,48);                                          //  load and validate image
 
11735
   if (! Grgb48) {
 
11736
      edit_cancel();
 
11737
      return;
 
11738
   }
 
11739
 
 
11740
   Gww = Grgb48->ww;
 
11741
   Ghh = Grgb48->hh;
 
11742
   
 
11743
   if (Fww != Gww || Fhh != Ghh) {
 
11744
      zmessageACK(ZTX("2nd image not same size as 1st image"));
 
11745
      RGB_free(Grgb48);
 
11746
      Grgb48 = 0;
 
11747
      edit_cancel();
 
11748
      return;
 
11749
   }
 
11750
 
 
11751
   start_thread(HDR_align_thread,0);                                       //  start thread to align images
 
11752
   wrapup_thread(0);                                                       //  wait for thread exit
 
11753
 
 
11754
   if (HDR_align_stat != 1) {                                              //  check thread exit status
 
11755
      edit_cancel();
 
11756
      Nalign = 0;                                                          //  reset align counter
 
11757
      RGB_free(A1rgb48);                                                   //  free alignment images
 
11758
      RGB_free(A2rgb48);
 
11759
      RGB_free(Grgb48);                                                    //  free 2nd input image pixmap
 
11760
      if (redpixels) zfree(redpixels);                                     //  free edge-pixel flags
 
11761
      redpixels = 0;
 
11762
      return;
 
11763
   }   
 
11764
   
 
11765
   int    HDR_dialog_event(zdialog *zd, const char *name);                 //  dialog for user adjustment
 
11766
   int    HDR_dialog_compl(zdialog *zd, int zstat);
 
11767
 
 
11768
   const char  *weightmess = ZTX("Image Weights per Brightness Level");
 
11769
 
 
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");
 
11775
 
 
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"," ");
 
11789
 
 
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);
 
11796
   
 
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","   ");
 
11803
 
 
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;
 
11808
 
 
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)
 
11815
 
 
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;
 
11820
   
 
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;
 
11825
 
 
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;
 
11830
   }
 
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;
 
11835
   }
 
11836
   
 
11837
   spline1(HDR_br_np,HDR_br_px,HDR_br_py);                                 //  make curve fitting anchor points
 
11838
   
 
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
 
11842
   signal_thread();
 
11843
   return;
 
11844
}
 
11845
 
 
11846
 
 
11847
//  dialog event and completion functions for HDR image level adjustment
 
11848
 
 
11849
int HDR_dialog_event(zdialog *zd, const char *event)
 
11850
{
 
11851
   if (! event || *event != 'b') return 0;
 
11852
 
 
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;
 
11857
 
 
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;
 
11860
 
 
11861
   if (strEqu(event,"b0/100"))                                             //  100% image 2
 
11862
      HDR_br_py[0] = HDR_br_py[1] = HDR_br_py[2] = 0.02;
 
11863
 
 
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;
 
11869
      }
 
11870
      else {
 
11871
         HDR_br_py[0] = 0.98;                                              //  ramp down
 
11872
         HDR_br_py[1] = 0.50;
 
11873
         HDR_br_py[2] = 0.02;
 
11874
      }
 
11875
   }
 
11876
 
 
11877
   HDR_brcurve_draw(HDR_brcurve);                                          //  regen and redraw the curve
 
11878
   
 
11879
   return 1;
 
11880
}
 
11881
 
 
11882
 
 
11883
int HDR_dialog_compl(zdialog *zd, int zstat)
 
11884
{
 
11885
   if (zstat != 1) edit_cancel();                                          //  user cancel
 
11886
   else edit_done();
 
11887
   Nalign = 0;                                                             //  reset align counter
 
11888
   RGB_free(A1rgb48);                                                      //  free alignment images
 
11889
   RGB_free(A2rgb48);
 
11890
   RGB_free(Grgb48);                                                       //  free 2nd input image pixmap
 
11891
   return 0;
 
11892
}
 
11893
 
 
11894
 
 
11895
//  thread to update image brightness levels
 
11896
//  runs asynchronously to dialog updates
 
11897
 
 
11898
void * HDR_dialog_thread(void *)
 
11899
{
 
11900
   while (true)
 
11901
   {
 
11902
      thread_idle_loop();                                                  //  wait for work or exit request
 
11903
      HDR_combine(1);                                                      //  combine images >> E3rgb48
 
11904
   }
 
11905
   
 
11906
   return 0;                                                               //  not executed, stop g++ warning
 
11907
}
 
11908
 
 
11909
 
 
11910
//  Add, delete, or move anchor points to weights curve using mouse.
 
11911
 
 
11912
int HDR_brcurve_adjust(GtkWidget *brcurve, GdkEventButton *event)
 
11913
{
 
11914
   int         ww, hh, px, py;
 
11915
   int         kk, ii, newii, minii = -1;
 
11916
   int         mx, my, button, evtype;
 
11917
   double      dist2, mindist2 = 1000000;
 
11918
   double      xval, yval;
 
11919
   
 
11920
   if (HDR_br_np > 49) {
 
11921
      zmessageACK(ZTX("Exceed 50 anchor points"));
 
11922
      return 0;
 
11923
   }
 
11924
   
 
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;
 
11931
   
 
11932
   for (ii = 0; ii < HDR_br_np; ii++)                                      //  find closest anchor point
 
11933
   {
 
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) {
 
11940
         mindist2 = dist2;
 
11941
         minii = ii;
 
11942
      }
 
11943
   }
 
11944
 
 
11945
   if (minii < 0) return 0;                                                //  huh?
 
11946
   
 
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];
 
11953
      }
 
11954
      HDR_br_np--;
 
11955
 
 
11956
      HDR_brcurve_draw(brcurve);                                           //  regen and redraw the curve
 
11957
      return 0;
 
11958
   }
 
11959
 
 
11960
//  drag or left click, move nearby anchor point to mouse position,
 
11961
//  or add a new anchor point if nothing near enough
 
11962
 
 
11963
   xval = 255.0 * mx / ww;                                                 //  0 - 255
 
11964
   yval = 1.0 * (hh - my) / hh;                                            //  0 - 1.0
 
11965
 
 
11966
   if (xval < 0 || xval > 255) return 0;                                   //  v.6.8
 
11967
   if (yval < 0 || yval > 1.0) return 0;
 
11968
 
 
11969
   if (mindist2 < 100) {                                                   //  existing point < 10 pixels away
 
11970
      ii = minii;
 
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
 
11974
   }
 
11975
   else                                                                    //  > 10 pixels, add a point
 
11976
   {
 
11977
      for (ii = 0; ii < HDR_br_np; ii++)
 
11978
         if (xval <= HDR_br_px[ii]) break;                                 //  find point with next higher x
 
11979
 
 
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
 
11982
 
 
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];
 
11986
      }
 
11987
 
 
11988
      HDR_br_np++;                                                         //  up point count
 
11989
      newii = ii;                                                          //  this point to be added
 
11990
   }
 
11991
 
 
11992
   HDR_br_px[newii] = xval;                                                //  coordinates of new or moved point
 
11993
   HDR_br_py[newii] = yval;
 
11994
   
 
11995
   HDR_brcurve_draw(brcurve);                                              //  regen and redraw the curve
 
11996
   return 0;
 
11997
}
 
11998
 
 
11999
 
 
12000
//  Draw brightness curve based on defined spline anchor points.
 
12001
 
 
12002
int HDR_brcurve_draw(GtkWidget *brcurve)
 
12003
{
 
12004
   int         ww, hh, px, py;
 
12005
   int         ii, iix, iiy;
 
12006
   double      xval, yval;
 
12007
 
 
12008
   ww = brcurve->allocation.width;                                         //  drawing area size
 
12009
   hh = brcurve->allocation.height;
 
12010
   if (ww < 50 || hh < 20) return 0;
 
12011
 
 
12012
   spline1(HDR_br_np,HDR_br_px,HDR_br_py);                                 //  make curve fitting anchor points
 
12013
 
 
12014
   gdk_window_clear(brcurve->window);                                      //  clear window
 
12015
 
 
12016
   for (px = 0; px < ww; px++)                                             //  generate all points for curve
 
12017
   {
 
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);
 
12022
   }
 
12023
   
 
12024
   for (ii = 0; ii < HDR_br_np; ii++)                                      //  draw boxes at anchor points
 
12025
   {
 
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);
 
12035
      }
 
12036
   }
 
12037
 
 
12038
   for (int ii = 0; ii < 256; ii++)                                        //  compute new weight curve for 
 
12039
      HDR_weights[ii] = spline2(ii);                                       //    all 256 brightness levels
 
12040
   
 
12041
   signal_thread();                                                        //  signal thread to update
 
12042
   return 0;
 
12043
}
 
12044
 
 
12045
 
 
12046
//  HDR image align thread, combine Frgb48 + Grgb48 >> E3rgb48
 
12047
 
 
12048
void * HDR_align_thread(void *)
 
12049
{
 
12050
   double      xfL, xfH, yfL, yfH, tfL, tfH;
 
12051
   double      xystep, xylim, tstep, tlim;
 
12052
   int         firstpass, lastpass;
 
12053
 
 
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
 
12062
   firstpass = 1;
 
12063
   lastpass = 0;
 
12064
   
 
12065
   fullSize = Fww;                                                         //  full image size
 
12066
   if (Fhh > Fww) fullSize = Fhh;                                          //  (largest dimension)
 
12067
 
 
12068
   alignSize = 140;                                                        //  initial alignment image size
 
12069
   if (alignSize > fullSize) alignSize = fullSize;
 
12070
   A1rgb48 = A2rgb48 = 0;
 
12071
 
 
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;
 
12076
 
 
12077
   while (true)                                                            //  next alignment stage / image size
 
12078
   {   
 
12079
      A1ww = Fww * alignSize / fullSize;                                   //  align width, height in same ratio
 
12080
      A1hh = Fhh * alignSize / fullSize;
 
12081
      A2ww = A1ww;
 
12082
      A2hh = A1hh;
 
12083
      
 
12084
      if (! lastpass) 
 
12085
      {
 
12086
         RGB_free(A1rgb48);                                                //  align images = scaled input images
 
12087
         RGB_free(A2rgb48);
 
12088
         A1rgb48 = RGB_rescale(Frgb48,A1ww,A1hh);
 
12089
         A2rgb48 = RGB_rescale(Grgb48,A2ww,A2hh);
 
12090
         
 
12091
         alignWidth = A1ww;                                                //  use full image for alignment
 
12092
         alignHeight = A1hh;                                               //  v.8.0
 
12093
 
 
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
 
12098
 
 
12099
         mutex_lock(&pixmaps_lock);
 
12100
         RGB_free(E3rgb48);                                                //  resize output image
 
12101
         E3rgb48 = RGB_make(A1ww,A1hh,48);
 
12102
         E3ww = A1ww;
 
12103
         E3hh = A1hh;
 
12104
         mutex_unlock(&pixmaps_lock);
 
12105
      }
 
12106
 
 
12107
      xylim = 2;                                                           //  search range from prior stage:
 
12108
      xystep = 1;                                                          //    -2 -1 0 +1 +2 pixels
 
12109
 
 
12110
      if (firstpass) xylim = 0.05 * alignSize;                             //  1st stage search range, huge
 
12111
 
 
12112
      if (lastpass) {
 
12113
         xylim = 1;                                                        //  final stage search range:
 
12114
         xystep = 0.5;                                                     //    -1.0 -0.5 0.0 +0.5 +1.0
 
12115
      }
 
12116
 
 
12117
      tlim = xylim / alignSize / 2;                                        //  theta max offset, radians
 
12118
      tstep = xystep / alignSize / 2;                                      //  theta step size
 
12119
 
 
12120
      xfL = xoff - xylim;
 
12121
      xfH = xoff + xylim + xystep/2;
 
12122
      yfL = yoff - xylim;
 
12123
      yfH = yoff + xylim + xystep/2;
 
12124
      tfL = toff - tlim;
 
12125
      tfH = toff + tlim + tstep/2;
 
12126
 
 
12127
      xoffB = xoff;
 
12128
      yoffB = yoff;
 
12129
      toffB = toff;
 
12130
      
 
12131
      matchB = matchImages();                                              //  set base match level
 
12132
      HDR_combine(0);                                                      //  v.8.4
 
12133
 
 
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)
 
12137
      {
 
12138
         matchlev = matchImages();
 
12139
         if (sigdiff(matchlev,matchB,0.00001) > 0) {
 
12140
            matchB = matchlev;
 
12141
            xoffB = xoff;
 
12142
            yoffB = yoff;
 
12143
            toffB = toff;
 
12144
         }
 
12145
 
 
12146
         Nalign++;                                                         //  count alignment tests
 
12147
         SBupdate++;                                                       //  update status bar
 
12148
      }
 
12149
 
 
12150
      xoff = xoffB;                                                        //  recover best offsets
 
12151
      yoff = yoffB;
 
12152
      toff = toffB;
 
12153
 
 
12154
      HDR_combine(0);                                                      //  combine images >> E3rgb48
 
12155
     
 
12156
      firstpass = 0;
 
12157
      if (lastpass) break;                                                 //  done
 
12158
 
 
12159
      if (alignSize == fullSize) {                                         //  full size image was aligned
 
12160
         lastpass++;                                                       //  one more pass
 
12161
         continue;
 
12162
      }
 
12163
 
 
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
 
12169
      yoff = R * yoff;
 
12170
   }
 
12171
 
 
12172
   zfree(redpixels);                                                       //  free edge-pixel flags
 
12173
   redpixels = 0;
 
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
 
12178
   exit_thread();
 
12179
   return 0;                                                               //  never executed, stop g++ warning
 
12180
}
 
12181
 
 
12182
 
 
12183
//  Combine images A1rgb48 and A2rgb48 using weights in HDR_weights[256].
 
12184
//  Output is to E3rgb48 (not reallocated). Update window.
 
12185
 
 
12186
void HDR_combine(int weight)
 
12187
{
 
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;
 
12193
   
 
12194
   for (py3 = 0; py3 < A1hh; py3++)                                        //  step through A1rgb48 pixels
 
12195
   for (px3 = 0; px3 < A1ww; px3++)
 
12196
   {
 
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);
 
12200
 
 
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);
 
12204
 
 
12205
      pix3 = bmpixel(E3rgb48,px3,py3);                                     //  output pixel
 
12206
 
 
12207
      if (! vstat1 || ! vstat2) {                                          //  if non-overlapping pixel,
 
12208
         pix3[0] = pix3[1] = pix3[2] = 0;                                  //    set output pixel black
 
12209
         continue;
 
12210
      }
 
12211
      
 
12212
      if (weight) {
 
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
 
12218
         
 
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);
 
12222
      }
 
12223
 
 
12224
      else {
 
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;
 
12228
      }
 
12229
 
 
12230
      if (showRedpix && vstat2) {                                          //  highlight alignment pixels
 
12231
         ii = py3 * A1ww + px3;
 
12232
         if (redpixels[ii]) {
 
12233
            pix3[0] = 65535;
 
12234
            pix3[1] = pix3[2] = 0;
 
12235
         }
 
12236
      }
 
12237
   }
 
12238
 
 
12239
   mwpaint2();                                                             //  update window
 
12240
   return;
 
12241
}
 
12242
 
 
12243
 
 
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
**************************************************************************/
 
12252
 
 
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);
 
12259
 
 
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];
 
12265
int      HDF_image;
 
12266
int      HDF_brush;
 
12267
int      HDF_suspend;
 
12268
 
 
12269
 
 
12270
//  menu function
 
12271
 
 
12272
void m_HDF(GtkWidget *, const char *)
 
12273
{
 
12274
   char     *file2 = 0;
 
12275
 
 
12276
   if (! edit_setup(0,0)) return;                                          //  setup edit: no preview
 
12277
   
 
12278
   file2 = zgetfile(ZTX("Select image to combine"),image_file,"open");     //  get 2nd HDF image
 
12279
   if (! file2) {
 
12280
      edit_cancel();
 
12281
      return;
 
12282
   }
 
12283
 
 
12284
   Grgb48 = image_load(file2,48);                                          //  load and validate image
 
12285
   if (! Grgb48) {
 
12286
      edit_cancel();
 
12287
      return;
 
12288
   }
 
12289
 
 
12290
   Gww = Grgb48->ww;
 
12291
   Ghh = Grgb48->hh;
 
12292
 
 
12293
   start_thread(HDF_align_thread,0);                                       //  start thread to align images
 
12294
   wrapup_thread(0);                                                       //  wait for thread exit
 
12295
 
 
12296
   if (HDF_align_stat != 1) {
 
12297
      edit_cancel();                                                       //  failure
 
12298
      return;
 
12299
   }   
 
12300
   
 
12301
   HDF_combine(1);                                                         //  combine with color comp.
 
12302
   Fmodified = 1;
 
12303
 
 
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");
 
12312
   
 
12313
   zdialog_stuff(zdedit,"radio1",1);
 
12314
   HDF_image = 1;
 
12315
   HDF_brush = 10;
 
12316
   HDF_suspend = 0;
 
12317
 
 
12318
   mouseCBfunc = HDF_mousefunc;                                            //  connect mouse function
 
12319
   Mcapture = 1;                                                           //  capture mouse clicks
 
12320
 
 
12321
   zdialog_run(zdedit,HDF_dialog_event,HDF_dialog_compl);                  //  run dialog, parallel
 
12322
   return;
 
12323
}
 
12324
 
 
12325
 
 
12326
//  dialog event function
 
12327
 
 
12328
int HDF_dialog_event(zdialog *zd, const char *event)
 
12329
{
 
12330
   int      ii;
 
12331
 
 
12332
   zdialog_fetch(zd,"radio1",ii);
 
12333
   if (ii) HDF_image = 1;
 
12334
   else HDF_image = 2;
 
12335
   
 
12336
   if (strEqu(event,"radius"))
 
12337
      zdialog_fetch(zd,"radius",HDF_brush);
 
12338
 
 
12339
   if (strEqu(event,"susp-resm"))                                          //  toggle suspend / resume
 
12340
   {
 
12341
      if (HDF_suspend) {
 
12342
         HDF_suspend = 0;
 
12343
         paint_toparc(3);                                                  //  start brush outline       v.8.3
 
12344
         mouseCBfunc = HDF_mousefunc;                                      //  connect mouse function
 
12345
         Mcapture++;
 
12346
         zdialog_stuff(zd,"susp-resm",Bsuspend);
 
12347
      }
 
12348
      else  {
 
12349
         HDF_suspend = 1;
 
12350
         paint_toparc(2);                                                  //  stop brush outline        v.8.3
 
12351
         mouseCBfunc = 0;                                                  //  disconnect mouse function
 
12352
         Mcapture = 0;
 
12353
         zdialog_stuff(zd,"susp-resm",Bresume);
 
12354
      }
 
12355
   }
 
12356
 
 
12357
   return 1;
 
12358
}
 
12359
 
 
12360
 
 
12361
//  dialog completion function
 
12362
 
 
12363
int HDF_dialog_compl(zdialog *zd, int zstat)
 
12364
{
 
12365
   if (zstat != 1) edit_cancel();                                          //  user cancel
 
12366
   else edit_done();
 
12367
   RGB_free(A1rgb48);                                                      //  free memory
 
12368
   RGB_free(A2rgb48);
 
12369
   paint_toparc(2);                                                        //  stop brush outline        v.8.3
 
12370
   Mcapture = 0;                                                           //  disconnect mouse
 
12371
   mouseCBfunc = 0;
 
12372
   return 0;
 
12373
}
 
12374
 
 
12375
 
 
12376
//  dialog mouse function
 
12377
 
 
12378
void HDF_mousefunc()
 
12379
{
 
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);
 
12386
 
 
12387
   radius = HDF_brush;
 
12388
   radius2 = radius * radius;
 
12389
 
 
12390
   toparcx = Mxposn - radius;                                              //  paint brush outline circle
 
12391
   toparcy = Myposn - radius;                                              //  v.8.3
 
12392
   toparcw = toparch = 2 * radius;
 
12393
   toparc = 1;
 
12394
   paint_toparc(3);
 
12395
 
 
12396
   if (LMclick) {                                                          //  mouse click
 
12397
      mx = Mxclick;
 
12398
      my = Myclick;
 
12399
   }
 
12400
 
 
12401
   else if (Mxdrag || Mydrag) {                                            //  drag in progress
 
12402
      mx = Mxdrag;
 
12403
      my = Mydrag;
 
12404
   }
 
12405
   
 
12406
   else return;
 
12407
 
 
12408
   LMclick = RMclick = 0;
 
12409
 
 
12410
   if (mx < 0 || mx > E3ww-1 || my < 0 || my > E3hh-1)                     //  outside image area
 
12411
      return;
 
12412
 
 
12413
   for (dy = -radius; dy <= radius; dy++)                                  //  loop surrounding block of pixels
 
12414
   for (dx = -radius; dx <= radius; dx++)
 
12415
   {
 
12416
      px3 = mx + dx;
 
12417
      py3 = my + dy;
 
12418
      
 
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
 
12422
 
 
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]]);
 
12431
      }
 
12432
 
 
12433
      else {
 
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]]);
 
12441
      }
 
12442
 
 
12443
      if (red > 65535 || green > 65535 || blue > 65535) {                  //  fix overflow
 
12444
         max = red;
 
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);
 
12451
      }
 
12452
      
 
12453
      ppix3 = bmpixel(E3rgb48,px3,py3);                                    //  image3 real pixel
 
12454
      ppix3[0] = red;
 
12455
      ppix3[1] = green;
 
12456
      ppix3[2] = blue;
 
12457
   }
 
12458
   
 
12459
   mwpaint2();
 
12460
   return;
 
12461
}
 
12462
 
 
12463
 
 
12464
//  image align thread, combine Frgb48 + Grgb48 >> E3rgb48
 
12465
 
 
12466
void * HDF_align_thread(void *)
 
12467
{
 
12468
   double      xfL, xfH, yfL, yfH, xystep, xylim;
 
12469
   double      tfL, tfH, tstep, tlim;
 
12470
   double      zlim, zoffx0, zoffy0;
 
12471
   double      xyrange;
 
12472
   double      eighth = 0.7854;                                            //  1/8 of circle in radians
 
12473
   int         ii, jj, lastpass;
 
12474
 
 
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;
 
12486
 
 
12487
   A1rgb48 = A2rgb48 = A2rgb48cache = 0;
 
12488
 
 
12489
   fullSize = Fww;                                                         //  full image size
 
12490
   if (Fhh > Fww) fullSize = Fhh;                                          //  (largest dimension)
 
12491
 
 
12492
   alignSize = 200;                                                        //  initial alignment image size
 
12493
   if (alignSize > fullSize) alignSize = fullSize;
 
12494
 
 
12495
   lastpass = 0;
 
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;
 
12500
 
 
12501
   while (true)                                                            //  next alignment stage / image size
 
12502
   {   
 
12503
      A1ww = Fww * alignSize / fullSize;                                   //  align width, height in same ratio
 
12504
      A1hh = Fhh * alignSize / fullSize;
 
12505
      A2ww = A1ww;
 
12506
      A2hh = A1hh;
 
12507
      
 
12508
      RGB_free(A1rgb48);
 
12509
      RGB_free(A2rgb48cache);
 
12510
 
 
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
 
12514
      }
 
12515
      else {
 
12516
         A1rgb48 = RGB_copy(Frgb48);                                       //  full size, copy input images
 
12517
         A2rgb48cache = RGB_copy(Grgb48);
 
12518
      }
 
12519
 
 
12520
      RGB_free(A2rgb48);                                                   //  distort image2 using current
 
12521
      HDF_distort();                                                       //    zoffx/y settings
 
12522
 
 
12523
      mutex_lock(&pixmaps_lock);
 
12524
      RGB_free(E3rgb48);                                                   //  prepare new output RGB pixmap
 
12525
      E3rgb48 = RGB_make(A1ww,A1hh,48);
 
12526
      E3ww = A1ww;
 
12527
      E3hh = A1hh;
 
12528
      mutex_unlock(&pixmaps_lock);
 
12529
 
 
12530
      alignWidth = A1ww;
 
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
 
12536
 
 
12537
      HDF_combine(0);                                                      //  combine and update window
 
12538
 
 
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
 
12544
 
 
12545
//  find best alignment based on xoff, yoff, toff
 
12546
 
 
12547
      matchB = 0;      
 
12548
 
 
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;
 
12553
      tfL = toff - tlim;
 
12554
      tfH = toff + tlim + tstep/2;
 
12555
 
 
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)
 
12559
      {
 
12560
         matchlev = matchImages();
 
12561
         if (sigdiff(matchlev,matchB,0.00001) > 0) {                       //  remember best match
 
12562
            matchB = matchlev;
 
12563
            xoffB = xoff;
 
12564
            yoffB = yoff;
 
12565
            toffB = toff;
 
12566
            HDF_combine(0);                                                //  combine and update window
 
12567
         }
 
12568
 
 
12569
         Nalign++;                                                         //  count alignment tests
 
12570
         SBupdate++;                                                       //  update status bar
 
12571
      }
 
12572
 
 
12573
      xoff = xoffB;                                                        //  recover best offsets
 
12574
      yoff = yoffB;
 
12575
      toff = toffB;
 
12576
 
 
12577
//  find best distortion settings at the four corners
 
12578
 
 
12579
      for (int mpass = 1; mpass <= 2; mpass++)
 
12580
      {
 
12581
         for (ii = 0; ii < 4; ii++)                                        //  corner NW NE SE SW
 
12582
         {
 
12583
            HDF_zoffxB[ii] = HDF_zoffx[ii];                                //  save baseline match level
 
12584
            HDF_zoffyB[ii] = HDF_zoffy[ii];
 
12585
         
 
12586
            zoffx0 = HDF_zoffx[ii];                                        //  current setting
 
12587
            zoffy0 = HDF_zoffy[ii];
 
12588
 
 
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);
 
12593
 
 
12594
               RGB_free(A2rgb48);                                          //  distort image2
 
12595
               HDF_distort();
 
12596
 
 
12597
               matchlev = matchImages();
 
12598
               if (sigdiff(matchlev,matchB,0.00001) > 0) {                 //  remember best match
 
12599
                  matchB = matchlev;
 
12600
                  HDF_zoffxB[ii] = HDF_zoffx[ii];
 
12601
                  HDF_zoffyB[ii] = HDF_zoffy[ii];
 
12602
                  HDF_combine(0);                                          //  combine and update window
 
12603
               }
 
12604
               
 
12605
               Nalign++;                                                   //  count alignment tests
 
12606
               SBupdate++;                                                 //  update status bar
 
12607
            }
 
12608
 
 
12609
            HDF_zoffx[ii] = HDF_zoffxB[ii];                                //  recover best offset
 
12610
            HDF_zoffy[ii] = HDF_zoffyB[ii];
 
12611
         }
 
12612
      }
 
12613
 
 
12614
// set up for next pass
 
12615
 
 
12616
      if (lastpass) break;                                                 //  done
 
12617
 
 
12618
      if (alignSize == fullSize) {                                         //  full size image was aligned
 
12619
         lastpass++;                                                       //  one more pass
 
12620
         xyrange = 0.5;
 
12621
         continue;
 
12622
      }
 
12623
 
 
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
 
12629
      yoff = R * yoff;
 
12630
      for (ii = 0; ii < 4; ii++) {
 
12631
         HDF_zoffx[ii] = R * HDF_zoffx[ii];
 
12632
         HDF_zoffy[ii] = R * HDF_zoffy[ii];
 
12633
      }
 
12634
 
 
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
 
12638
   }
 
12639
   
 
12640
   RGB_free(A2rgb48cache);                                                 //  free memory
 
12641
   RGB_free(Grgb48);                                                       //  A1/A2rgb48 still needed
 
12642
 
 
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
 
12648
   redpixels = 0;
 
12649
   HDF_align_stat = 1;                                                     //  signal success
 
12650
   exit_thread();
 
12651
   return 0;                                                               //  not executed, stop g++ warning
 
12652
}
 
12653
 
 
12654
 
 
12655
//  Combine images A1rgb48 and A2rgb48 using brightness adjustments.
 
12656
//  Output is to E3rgb48 (not reallocated). Update main window.
 
12657
 
 
12658
void HDF_combine(int Fcolor)
 
12659
{
 
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;
 
12665
   
 
12666
   Radjust = Gadjust = Badjust = 1.0;
 
12667
 
 
12668
   for (py3 = 1; py3 < E3hh-1; py3++)                                      //  step through output pixels
 
12669
   for (px3 = 1; px3 < E3ww-1; px3++)
 
12670
   {
 
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);
 
12674
 
 
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);
 
12678
 
 
12679
      pix3 = bmpixel(E3rgb48,px3,py3);                                     //  output pixel
 
12680
 
 
12681
      if (! vstat1 || ! vstat2) {                                          //  no overlap
 
12682
         pix3[0] = pix3[1] = pix3[2] = 0;                                  //  output pixel is black
 
12683
         continue;
 
12684
      }
 
12685
 
 
12686
      if (showRedpix) {                                                    //  show alignment pixels in red
 
12687
         ii = py3 * A1ww + px3;
 
12688
         if (redpixels[ii]) {
 
12689
            pix3[0] = 65535;
 
12690
            pix3[1] = pix3[2] = 0;
 
12691
            continue;
 
12692
         }
 
12693
      }
 
12694
 
 
12695
      red1 = vpix1[0];                                                     //  image1 and image2 pixels
 
12696
      green1 = vpix1[1];
 
12697
      blue1 = vpix1[2];
 
12698
 
 
12699
      red2 = vpix2[0];
 
12700
      green2 = vpix2[1];
 
12701
      blue2 = vpix2[2];
 
12702
 
 
12703
      if (Fcolor)
 
12704
      {
 
12705
         red1 = int(R12match[red1]);                                       //  compensate color
 
12706
         green1 = int(G12match[green1]);
 
12707
         blue1 = int(B12match[blue1]);
 
12708
 
 
12709
         red2 = int(R21match[red2]);
 
12710
         green2 = int(G21match[green2]);
 
12711
         blue2 = int(B21match[blue2]);
 
12712
 
 
12713
         if (red1 > 65535 || green1 > 65535 || blue1 > 65535) {            //  fix overflow
 
12714
            max = red1;
 
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);
 
12721
         }
 
12722
 
 
12723
         if (red2 > 65535 || green2 > 65535 || blue2 > 65535) {
 
12724
            max = red2;
 
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);
 
12731
         }
 
12732
      }
 
12733
      
 
12734
      pix3[0] = (red1 + red2) / 2;                                         //  output = combined inputs
 
12735
      pix3[1] = (green1 + green2) / 2;
 
12736
      pix3[2] = (blue1 + blue2) / 2;
 
12737
   }
 
12738
 
 
12739
   mwpaint2();                                                             //  update window
 
12740
   return;
 
12741
}
 
12742
 
 
12743
 
 
12744
//  Distort A2rgb48cache, returning A2rgb48
 
12745
//  4 corners move HDF_zoffx[ii], HDF_zoffy[ii] pixels
 
12746
//  and center does not move, 
 
12747
 
 
12748
void HDF_distort()
 
12749
{
 
12750
   void * HDF_distort_wthread(void *arg);
 
12751
 
 
12752
   RGB         *rgbin, *rgbout;
 
12753
   int         ii, ww, hh;
 
12754
 
 
12755
   rgbin = A2rgb48cache;
 
12756
   ww = rgbin->ww;                                                         //  create output RGB pixmap
 
12757
   hh = rgbin->hh;
 
12758
   rgbout = RGB_make(ww,hh,48);
 
12759
   A2rgb48 = rgbout;
 
12760
   
 
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);
 
12764
 
 
12765
   while (HDF_distort_busy)                                                //  wait for completion
 
12766
      zsleep(0.01);
 
12767
 
 
12768
   return;
 
12769
}
 
12770
 
 
12771
void * HDF_distort_wthread(void *arg)                                      //  worker thread function
 
12772
{
 
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;
 
12781
   
 
12782
   rgbin = A2rgb48cache;
 
12783
   rgbout = A2rgb48;
 
12784
 
 
12785
   ww = rgbin->ww;                                                         //  create output RGB pixmap
 
12786
   hh = rgbin->hh;
 
12787
   diag = sqrt(ww*ww + hh*hh);
 
12788
   
 
12789
   pxm = ww/2;                                                             //  center pixel
 
12790
   pym = hh/2;
 
12791
   
 
12792
   disp0 = (1 - pxm/diag) * (1 - pym/diag);
 
12793
   disp0 = disp0 * disp0;
 
12794
   disp0x = disp0 * HDF_zoffx[0];
 
12795
   disp0y = disp0 * HDF_zoffy[0];
 
12796
   
 
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];
 
12801
 
 
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];
 
12806
   
 
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];
 
12811
   
 
12812
   dispx = +disp0x - disp1x - disp2x + disp3x;                             //  center pixel displacement
 
12813
   dispy = +disp0y + disp1y - disp2y - disp3y;
 
12814
   
 
12815
   dispx0 = -dispx;                                                        //  anti-displacement
 
12816
   dispy0 = -dispy;
 
12817
 
 
12818
   for (pym = index; pym < hh; pym += NWthreads)                           //  loop all pixels
 
12819
   for (pxm = 0; pxm < ww; pxm++)
 
12820
   {
 
12821
      disp0 = (1 - pxm/diag) * (1 - pym/diag);
 
12822
      disp0 = disp0 * disp0;
 
12823
      disp0x = disp0 * HDF_zoffx[0];
 
12824
      disp0y = disp0 * HDF_zoffy[0];
 
12825
      
 
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];
 
12830
 
 
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];
 
12835
      
 
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];
 
12840
      
 
12841
      dispx = +disp0x - disp1x - disp2x + disp3x;                          //  (pxm,pym) displacement
 
12842
      dispy = +disp0y + disp1y - disp2y - disp3y;
 
12843
      
 
12844
      dispx += dispx0;                                                     //  relative to center pixel
 
12845
      dispy += dispy0;
 
12846
 
 
12847
      px = pxm + dispx;                                                    //  source pixel location
 
12848
      py = pym + dispy;
 
12849
 
 
12850
      vstat = vpixel(rgbin,px,py,vpix);                                    //  input virtual pixel
 
12851
      pixm = bmpixel(rgbout,pxm,pym);                                      //  output real pixel
 
12852
 
 
12853
      if (vstat) {  
 
12854
         pixm[0] = vpix[0];
 
12855
         pixm[1] = vpix[1];
 
12856
         pixm[2] = vpix[2];
 
12857
      }
 
12858
      else pixm[0] = pixm[1] = pixm[2] = 0;
 
12859
   }
 
12860
   
 
12861
   zadd_locked(HDF_distort_busy,-1);
 
12862
   pthread_exit(0);
 
12863
}
 
12864
 
 
12865
 
 
12866
/**************************************************************************/
 
12867
 
 
12868
//  panorama function - combine left and right images into a wide image
 
12869
 
 
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
 
12876
 
 
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
 
12881
 
 
12882
 
 
12883
void m_pano(GtkWidget *, const char *)
 
12884
{
 
12885
   char        *file2 = 0;
 
12886
 
 
12887
   Grgb48 = A1rgb48 = A2rgb48 = pano_A1cache = pano_A2cache = 0;
 
12888
 
 
12889
   if (! edit_setup(0,0)) return;                                          //  setup edit: no preview
 
12890
   
 
12891
   file2 = zgetfile(ZTX("Select image to combine"),image_file,"open");     //  2nd or next pano file
 
12892
   if (! file2) goto pano_cancel;
 
12893
 
 
12894
   Grgb48 = image_load(file2,48);                                          //  load and validate image
 
12895
   if (! Grgb48) goto pano_cancel;
 
12896
   Gww = Grgb48->ww;
 
12897
   Ghh = Grgb48->hh;
 
12898
   
 
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;
 
12902
 
 
12903
   pano_prealign();                                                        //  do manual pre-align
 
12904
   if (pano_stat != 1) goto pano_cancel;
 
12905
 
 
12906
   if (overlapixs < alignSize * 20) {                                      //  need > 20 pixel overlap
 
12907
      zmessageACK(ZTX("Too little overlap, cannot align"));
 
12908
      goto pano_cancel;
 
12909
   }
 
12910
 
 
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;
 
12914
   
 
12915
   pano_final_adjust();                                                    //  do manual final adjustments
 
12916
   if (pano_stat != 1) goto pano_cancel;
 
12917
   Fmodified = 1;
 
12918
   edit_done();
 
12919
   goto pano_cleanup;
 
12920
   
 
12921
pano_cancel:
 
12922
   edit_cancel();
 
12923
 
 
12924
pano_cleanup:
 
12925
   Nalign = 0;
 
12926
   if (file2) zfree(file2);
 
12927
   RGB_free(Grgb48);
 
12928
   RGB_free(A1rgb48);
 
12929
   RGB_free(A2rgb48);
 
12930
   RGB_free(pano_A1cache);
 
12931
   RGB_free(pano_A2cache);
 
12932
   return;
 
12933
}
 
12934
 
 
12935
 
 
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
 
12939
 
 
12940
void pano_prealign()   
 
12941
{  
 
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
 
12945
 
 
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");
 
12950
 
 
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);
 
12965
   
 
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);
 
12970
 
 
12971
   zdialog_run(zdedit,pano_prealign_event,pano_prealign_compl);            //  run dialog, parallel
 
12972
   
 
12973
   pano_stat = -1;
 
12974
   start_thread(pano_prealign_thread,0);                                   //  start working thread
 
12975
   wrapup_thread(0);                                                       //  wait for completion
 
12976
   return;
 
12977
}
 
12978
 
 
12979
 
 
12980
int pano_prealign_event(zdialog *zd, const char *event)                    //  dialog event function
 
12981
{
 
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);
 
12985
   }
 
12986
 
 
12987
   if (strEqu(event,"search")) Fautolens = 1;                              //  trigger auto-lens function
 
12988
 
 
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
 
12993
      zdedit = null;
 
12994
   }
 
12995
   return 0;
 
12996
}
 
12997
 
 
12998
 
 
12999
int pano_prealign_compl(zdialog *zd, int zstat)                            //  dialog completion function
 
13000
{                                                                          //  (cancel only)
 
13001
   pano_stat = 0;                                                          //  signal cancel
 
13002
   wrapup_thread(0);                                                       //  wait for thread exit
 
13003
   zdialog_free(zdedit);                                                   //  kill dialog
 
13004
   zdedit = null;
 
13005
   return 0;
 
13006
}
 
13007
 
 
13008
 
 
13009
void * pano_prealign_thread(void *)                                        //  prealign working thread
 
13010
{
 
13011
   int         mx0, my0, mx, my;                                           //  mouse drag origin, position
 
13012
   double      lens_mm0, lens_bow0;
 
13013
   double      mlever = 0.5;
 
13014
   double      dtoff;
 
13015
   int         majupdate = 1;
 
13016
 
 
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
 
13023
 
 
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
 
13027
 
 
13028
   xoff = xoffB = 0.8 * A1ww;                                              //  initial x offset (20% overlap)
 
13029
 
 
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
 
13034
 
 
13035
   lens_mm0 = lens_mm;                                                     //  to detect changes
 
13036
   lens_bow0 = lens_bow;
 
13037
 
 
13038
   mx0 = my0 = 0;                                                          //  no drag in progress
 
13039
   Mcapture = KBcapture = 1;                                               //  capture mouse drag and KB keys
 
13040
 
 
13041
   while (pano_stat == -1)                                                 //  loop and align until done
 
13042
   {
 
13043
      zsleep(0.05);                                                        //  logic simplified   v.7.5
 
13044
      
 
13045
      if (Fautolens) {
 
13046
         pano_autolens();                                                  //  get lens parameters
 
13047
         zdialog_stuff(zdedit,"spmm",lens_mm);                             //  update dialog
 
13048
         zdialog_stuff(zdedit,"spbow",lens_bow);
 
13049
         majupdate++;
 
13050
      }
 
13051
         
 
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);
 
13056
         majupdate++;
 
13057
      }
 
13058
      
 
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;
 
13066
         KBkey = 0;
 
13067
 
 
13068
         alignWidth = int(A1ww - xoff);                                    //  entire overlap
 
13069
         alignHeight = A1hh;                                               //  v.8.0
 
13070
         getAlignArea();
 
13071
         pano_combine(0);                                                  //  show combined images
 
13072
         majupdate++;
 
13073
         continue;
 
13074
      }
 
13075
      
 
13076
      if (! Mxdrag && ! Mydrag) mx0 = my0 = 0;                             //  no drag in progress
 
13077
 
 
13078
      if (Mxdrag || Mydrag)                                                //  mouse drag underway
 
13079
      {
 
13080
         mx = Mxdrag;                                                      //  mouse position in image
 
13081
         my = Mydrag;
 
13082
 
 
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
 
13085
            continue;
 
13086
         }
 
13087
         
 
13088
         if (! mx0 && ! my0) {                                             //  new drag, set drag origin
 
13089
            mx0 = mx;
 
13090
            my0 = my;
 
13091
         }
 
13092
         
 
13093
         if (mx != mx0 || my != my0)                                       //  drag is progressing
 
13094
         {
 
13095
            if (mx > xoff + 0.8 * A2ww) {                                  //  near right edge, theta drag 
 
13096
               dtoff = mlever * (my - my0) / A2ww;                         //  delta theta, radians
 
13097
               toff += dtoff;
 
13098
               xoff += dtoff * (A1hh + yoff);                              //  change center of rotation
 
13099
               yoff -= dtoff * (A1ww - xoff);                              //    to middle of overlap area
 
13100
            }
 
13101
            else  {                                                        //  x/y drag
 
13102
               xoff += mlever * (mx - mx0);                                //  image2 offsets / mouse leverage
 
13103
               yoff += mlever * (my - my0);
 
13104
            }
 
13105
 
 
13106
            mx0 = mx;                                                      //  next drag origin = current mouse
 
13107
            my0 = my;
 
13108
 
 
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;
 
13115
 
 
13116
            alignWidth = int(A1ww - xoff);                                 //  entire overlap
 
13117
            getAlignArea();
 
13118
            pano_combine(0);                                               //  show combined images
 
13119
            majupdate++;
 
13120
            continue;
 
13121
         }
 
13122
      }
 
13123
 
 
13124
      if (majupdate) {                                                     //  do major update
 
13125
         majupdate = 0;
 
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
 
13133
         yoffB = yoff;
 
13134
         toffB = toff;
 
13135
         pano_combine(0);                                                  //  show combined images
 
13136
         SBupdate++;                                                       //  update status bar
 
13137
      }
 
13138
   }
 
13139
 
 
13140
   Fzoom = 0;                                                              //  reset in case user zoomed in
 
13141
   KBcapture = Mcapture = 0;
 
13142
   exit_thread();
 
13143
   return 0;                                                               //  never executed, stop g++ warning
 
13144
}
 
13145
 
 
13146
 
 
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
 
13150
 
 
13151
void pano_autolens()
 
13152
{
 
13153
   double   mm_range, bow_range, xoff_range, yoff_range, toff_range;
 
13154
   double   squeeze, xoff_rfinal, rnum;
 
13155
   double   lens_mmB, lens_bowB;
 
13156
   int      counter = 0;
 
13157
   
 
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;
 
13161
   xoff_range = 7;
 
13162
   yoff_range = 7;
 
13163
   toff_range = 0.01;
 
13164
 
 
13165
   xoff_rfinal = 0.2;                                                      //  final xoff range - when to quit
 
13166
   
 
13167
   Nalign = 1;
 
13168
   aligntype = 3;
 
13169
   showRedpix = 1;
 
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
 
13176
   getAlignArea();
 
13177
   getBrightRatios();
 
13178
   setColorfixFactors(1);
 
13179
   flagEdgePixels();
 
13180
   pano_combine(0);
 
13181
 
 
13182
   lens_mmB = lens_mm;                                                     //  initial best fit = current data
 
13183
   lens_bowB = lens_bow;
 
13184
   xoffB = xoff;
 
13185
   yoffB = yoff;
 
13186
   toffB = toff;
 
13187
   matchB = matchImages();
 
13188
 
 
13189
   while (true)
 
13190
   {
 
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
 
13196
      getBrightRatios();
 
13197
      setColorfixFactors(1);
 
13198
      flagEdgePixels();
 
13199
      squeeze = 0.95;                                                      //  search range reduction
 
13200
         
 
13201
      for (int ii = 0; ii < 500; ii++)                                     //  loop random alignments  v.8.5
 
13202
      {                                                                    
 
13203
         rnum = drand48();
 
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);
 
13208
         else
 
13209
            toff = toffB + toff_range * (drand48() - 0.5);
 
13210
      
 
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)
 
13216
            yoffB = yoff;
 
13217
            toffB = toff;
 
13218
            matchB = matchlev;                                             //  save new best fit
 
13219
            pano_combine(0);
 
13220
            squeeze = 1;                                                   //  keep same search range as long
 
13221
            break;                                                         //    as improvements are found
 
13222
         }
 
13223
 
 
13224
         Nalign++;
 
13225
         SBupdate++;
 
13226
         if (pano_stat != -1) goto done;
 
13227
      }
 
13228
      
 
13229
      if (xoff_range < xoff_rfinal) goto done;
 
13230
 
 
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;
 
13239
   }
 
13240
 
 
13241
done:
 
13242
   lens_mm = lens_mmB;                                                     //  set best alignment found
 
13243
   lens_bow = lens_bowB;
 
13244
   xoff = xoffB;
 
13245
   yoff = yoffB;
 
13246
   toff = toffB;
 
13247
   Fautolens = 0;
 
13248
   showRedpix = 0;
 
13249
   pano_combine(0);
 
13250
   SBupdate++;
 
13251
   return;
 
13252
}
 
13253
 
 
13254
 
 
13255
//  Thread function for combining A1 + A2 >> E3
 
13256
 
 
13257
void * pano_align_thread(void *)
 
13258
{
 
13259
   int         firstpass, lastpass;
 
13260
   double      xystep, xylim, tstep, tlim;
 
13261
   double      xfL, xfH, yfL, yfH, tfL, tfH;
 
13262
   double      wxL, wxH, wyL, wyH;
 
13263
   double      alignR;
 
13264
   
 
13265
   Nalign = 1;                                                             //  alignment in progress
 
13266
   aligntype = 3;                                                          //  pano
 
13267
   pano_stat = 0;
 
13268
   Fzoom = 0;                                                              //  fit to window if big
 
13269
   Fblowup = 1;                                                            //  scale up to window if small
 
13270
   showRedpix = 1;                                                         //  highlight alignment pixels
 
13271
   firstpass = 1;
 
13272
   lastpass = 0;
 
13273
 
 
13274
   while (true)
 
13275
   {
 
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
 
13282
 
 
13283
      xoff = alignR * xoff;                                                //  adjust offsets for new image size
 
13284
      yoff = alignR * yoff;
 
13285
      toff = toff;
 
13286
 
 
13287
      warpxu = alignR * warpxu;                                            //  adjust warp values
 
13288
      warpyu = alignR * warpyu;
 
13289
      warpxl = alignR * warpxl;
 
13290
      warpyl = alignR * warpyl;
 
13291
      
 
13292
      if (! lastpass) pano_get_align_images(1,0);                          //  get new alignment images
 
13293
 
 
13294
      if (firstpass) {
 
13295
         alignWidth = int(A1ww - xoff);                                    //  set new alignment area
 
13296
         if (alignWidth > A1ww/3) alignWidth = A1ww/3;
 
13297
      }
 
13298
      else  {
 
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);
 
13304
      }
 
13305
 
 
13306
      alignHeight = A1hh;
 
13307
 
 
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
 
13312
 
 
13313
      xylim = 2;                                                           //  +/- search range, centered on
 
13314
      xystep = 0.571;                                                      //    results from prior stage
 
13315
      
 
13316
      if (firstpass) {
 
13317
         xylim = alignSize * 0.03;                                         //  3% error tolerance in pre-alignment
 
13318
         xystep = 0.5; 
 
13319
      }
 
13320
 
 
13321
      if (lastpass) {
 
13322
         xylim = 1;                                                        //  final stage pixel search steps
 
13323
         xystep = 0.5;                                                     //   -1.0 -0.5 0.0 +0.5 +1.0
 
13324
      }
 
13325
 
 
13326
      tlim = xylim / alignSize / 2;                                        //  theta max offset, radians
 
13327
      tstep = xystep / alignSize / 2;                                      //  theta step size
 
13328
 
 
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;
 
13333
      tfL = toff - tlim;
 
13334
      tfH = toff + tlim + tstep/2;
 
13335
 
 
13336
      xoffB = xoff;                                                        //  initial offsets = best so far
 
13337
      yoffB = yoff;
 
13338
      toffB = toff;
 
13339
      
 
13340
      warpxuB = warpxu;                                                    //  initial warp values
 
13341
      warpyuB = warpyu;
 
13342
      warpxlB = warpxl;
 
13343
      warpylB = warpyl;
 
13344
 
 
13345
      matchB = matchImages();                                              //  set base match level
 
13346
      pano_combine(0);                                                     //  v.8.4
 
13347
 
 
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)
 
13351
      {
 
13352
         matchlev = matchImages();
 
13353
         if (sigdiff(matchlev,matchB,0.00001) > 0) {                       //  remember best alignment and offsets
 
13354
            matchB = matchlev;
 
13355
            xoffB = xoff;
 
13356
            yoffB = yoff;
 
13357
            toffB = toff;
 
13358
         }
 
13359
 
 
13360
         Nalign++;
 
13361
         SBupdate++;                                                       //  update status bar
 
13362
      }
 
13363
      
 
13364
      xoff = xoffB;                                                        //  recover best offsets
 
13365
      yoff = yoffB;
 
13366
      toff = toffB;
 
13367
      
 
13368
      if (! firstpass)
 
13369
      {
 
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;
 
13374
         
 
13375
         for (warpxu = wxL; warpxu < wxH; warpxu += xystep)                //  search upper warp
 
13376
         for (warpyu = wyL; warpyu < wyH; warpyu += xystep)
 
13377
         {
 
13378
            pano_get_align_images(0,1);                                    //  curve and warp
 
13379
            matchlev = matchImages();
 
13380
            if (sigdiff(matchlev,matchB,0.00001) > 0) {                    //  remember best warp
 
13381
               matchB = matchlev;
 
13382
               warpxuB = warpxu;
 
13383
               warpyuB = warpyu;
 
13384
            }
 
13385
 
 
13386
            Nalign++;
 
13387
            SBupdate++;
 
13388
         }
 
13389
 
 
13390
         warpxu = warpxuB;                                                 //  restore best warp
 
13391
         warpyu = warpyuB;
 
13392
         pano_get_align_images(0,0);
 
13393
 
 
13394
         wxL = warpxlB - xylim;
 
13395
         wxH = warpxlB + xylim + xystep/2;
 
13396
         wyL = warpylB - xylim;
 
13397
         wyH = warpylB + xylim + xystep/2;
 
13398
         
 
13399
         for (warpxl = wxL; warpxl < wxH; warpxl += xystep)                //  search lower warp
 
13400
         for (warpyl = wyL; warpyl < wyH; warpyl += xystep)
 
13401
         {
 
13402
            pano_get_align_images(0,2);
 
13403
            matchlev = matchImages();
 
13404
            if (sigdiff(matchlev,matchB,0.00001) > 0) {
 
13405
               matchB = matchlev;
 
13406
               warpxlB = warpxl;
 
13407
               warpylB = warpyl;
 
13408
            }
 
13409
 
 
13410
            Nalign++;
 
13411
            SBupdate++;
 
13412
         }
 
13413
         
 
13414
         warpxl = warpxlB;
 
13415
         warpyl = warpylB;
 
13416
         pano_get_align_images(0,0);
 
13417
      }
 
13418
 
 
13419
      pano_combine(0);                                                     //  combine images and update window
 
13420
 
 
13421
      firstpass = 0;
 
13422
      if (lastpass) break;
 
13423
      if (alignSize == fullSize) lastpass = 1;                             //  one more pass, reduced step size
 
13424
   }
 
13425
 
 
13426
   pano_stat = 1;                                                          //  signal success
 
13427
   showRedpix = 0;                                                         //  pixel highlights off
 
13428
   Fzoom = Fblowup = 0;                                                    //  reset image scaling
 
13429
   exit_thread();
 
13430
   return 0;                                                               //  never executed, stop g++ warning
 
13431
}
 
13432
 
 
13433
 
 
13434
//  do manual adjustment of brightness, color, blend width
 
13435
 
 
13436
void pano_final_adjust()
 
13437
{
 
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 *);
 
13441
   
 
13442
   int      zstat;
 
13443
 
 
13444
   const char  *adjmessage = ZTX("\n Match Brightness and Color");
 
13445
   
 
13446
   pano_automatch = 1;                                                     //  init. auto color match on
 
13447
   alignWidth = 1;
 
13448
   
 
13449
   getAlignArea();
 
13450
   getBrightRatios();                                                      //  get color brightness ratios
 
13451
   setColorfixFactors(pano_automatch);                                     //  set color matching factors
 
13452
   pano_combine(1);                                                        //  show final results
 
13453
 
 
13454
   zdedit = zdialog_new(ZTX("Match Images"),mWin,Bdone,Bcancel,null);      //  color adjustment dialog
 
13455
 
 
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");
 
13474
   
 
13475
   zstat = zdialog_run(zdedit,pano_adjust_event,pano_adjust_compl);        //  run dialog, parallel
 
13476
   
 
13477
   start_thread(pano_adjust_thread,0);                                     //  start thread
 
13478
   wrapup_thread(0);                                                       //  wait for completion
 
13479
   return;
 
13480
}
 
13481
 
 
13482
 
 
13483
//  thread function - stall return from final_adjust until dialog done
 
13484
 
 
13485
void * pano_adjust_thread(void *)
 
13486
{
 
13487
   while (true) thread_idle_loop();                                        //  wait for work or exit request
 
13488
   return 0;                                                               //  not executed, stop g++ warning
 
13489
}
 
13490
 
 
13491
 
 
13492
//  dialog completion function
 
13493
 
 
13494
int pano_adjust_compl(zdialog *zd, int zstat)
 
13495
{
 
13496
   zdialog_free(zdedit);                                                   //  kill dialog
 
13497
   zdedit = null;
 
13498
   if (zstat == 1) pano_stat = 1;
 
13499
   else pano_stat = 0;
 
13500
   wrapup_thread(8);                                                       //  kill thread
 
13501
   return 0;
 
13502
}
 
13503
 
 
13504
 
 
13505
//  dialog event function
 
13506
//  A1 + A2 >> E3 under control of spin buttons
 
13507
 
 
13508
int pano_adjust_event(zdialog *zd, const char *event)
 
13509
{
 
13510
   double      red, green, blue, bright, bright2;
 
13511
   
 
13512
   if (strEqu(event,"auto")) 
 
13513
   {
 
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");
 
13519
      return 1;
 
13520
   }
 
13521
 
 
13522
   if (strNeq(event,"apply")) return 0;                                    //  wait for apply button
 
13523
 
 
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
 
13529
 
 
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;
 
13535
   
 
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);
 
13541
 
 
13542
   Radjust = red / 100;                                                    //  normalize 0.5 ... 2.0
 
13543
   Gadjust = green / 100;
 
13544
   Badjust = blue / 100;
 
13545
 
 
13546
   getAlignArea();
 
13547
   getBrightRatios();                                                      //  get color brightness ratios
 
13548
   setColorfixFactors(pano_automatch);                                     //  set color matching factors
 
13549
   pano_combine(1);                                                        //  combine and update window
 
13550
 
 
13551
   return 1;
 
13552
}
 
13553
 
 
13554
 
 
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
 
13561
 
 
13562
void pano_get_align_images(int newf, int warp)
 
13563
{
 
13564
   void  pano_curve_image(RGB *rgbin, RGB *rgbout, int curve, int warp);
 
13565
 
 
13566
   double      lens_curve, R;
 
13567
 
 
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 };
 
13570
 
 
13571
   if (newf) 
 
13572
   {
 
13573
      A1ww = Fww * alignSize / fullSize;                                   //  size of alignment images
 
13574
      A1hh = Fhh * alignSize / fullSize;
 
13575
      A2ww = Gww * alignSize / fullSize;
 
13576
      A2hh = Ghh * alignSize / fullSize;
 
13577
      
 
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);
 
13582
 
 
13583
      RGB_free(A1rgb48);                                                   //  A1/A2 will be curved
 
13584
      RGB_free(A2rgb48);
 
13585
      A1rgb48 = A2rgb48 = 0;
 
13586
 
 
13587
      spline1(12,mm,curve);                                                //  always initialize   bugfix v.6.2
 
13588
   }
 
13589
   
 
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;
 
13595
 
 
13596
   R = 1.0 * A2hh / A2ww;
 
13597
   if (R > 1) pano_curve = pano_curve / R / R;                             //  adjust for vertical format
 
13598
   
 
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
 
13603
      }
 
13604
      else {
 
13605
         if (! A1rgb48) A1rgb48 = RGB_make(A1ww,A1hh,48);                  //  curve image1, both halves, no warp
 
13606
         pano_curve_image(pano_A1cache,A1rgb48,2,0);
 
13607
      }
 
13608
   }
 
13609
 
 
13610
   if (! A2rgb48) A2rgb48 = RGB_make(A2ww,A2hh,48);
 
13611
 
 
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
 
13615
 
 
13616
   return;
 
13617
}
 
13618
 
 
13619
 
 
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
 
13623
//
 
13624
//  global variables:
 
13625
//    warpxu/yu:    upper left corner warp displacement
 
13626
//    warpxl/yl:    lower left corner warp displacement
 
13627
 
 
13628
void  pano_curve_image(RGB *rgbin, RGB *rgbout, int curve, int warp)       //  overhauled  v.8.5
 
13629
{
 
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;
 
13634
   
 
13635
   ww = rgbout->ww;
 
13636
   hh = rgbout->hh;
 
13637
   
 
13638
   pxcL = 0;
 
13639
   pxcH = ww;
 
13640
 
 
13641
   if (curve == 1) {                                                       //  curve left half only
 
13642
      pxcL = pxmL - xoff;                                                  //  from blend stripe to middle
 
13643
      pxcH = pxmH - xoff;                                                  //  v.7.7
 
13644
   }
 
13645
 
 
13646
   pycL = 0;
 
13647
   pycH = hh;
 
13648
   if (warp == 1) pycH = hh / 2;                                           //  upper half only
 
13649
   if (warp == 2) pycL = hh / 2;                                           //  lower half only
 
13650
 
 
13651
   for (pyc = pycL; pyc < pycH; pyc++)
 
13652
   for (pxc = pxcL; pxc < pxcH; pxc++)
 
13653
   {
 
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;
 
13658
 
 
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
 
13662
 
 
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
 
13667
         }
 
13668
         else {
 
13669
            px += warpxl * xdisp;                                          //  warp lower-left
 
13670
            py += warpyl * ydisp * xdisp;
 
13671
         }
 
13672
      }
 
13673
 
 
13674
      vstat = vpixel(rgbin,px,py,vpix);                                    //  input virtual pixel
 
13675
      pixc = bmpixel(rgbout,pxc,pyc);                                      //  output real pixel
 
13676
      if (vstat) {  
 
13677
         pixc[0] = vpix[0];
 
13678
         pixc[1] = vpix[1];
 
13679
         pixc[2] = vpix[2];
 
13680
      }
 
13681
      else pixc[0] = pixc[1] = pixc[2] = 0;
 
13682
   }
 
13683
   
 
13684
   return;
 
13685
}
 
13686
 
 
13687
 
 
13688
//  combine and images: A1rgb48 + A2rgb48  >>  E3rgb48
 
13689
//  update window showing current progress
 
13690
 
 
13691
void pano_combine(int fcolor)
 
13692
{
 
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);
 
13700
 
 
13701
   mutex_lock(&pixmaps_lock);
 
13702
   
 
13703
   ww = xoff + A2ww;                                                       //  combined width
 
13704
   if (toff < 0) ww -= A2hh * toff;                                        //  adjust for theta
 
13705
   E3ww = int(ww+1);
 
13706
   E3hh = A1rgb48->hh;
 
13707
   
 
13708
   RGB_free(E3rgb48);                                                      //  allocate output pixmap
 
13709
   E3rgb48 = RGB_make(E3ww,E3hh,48);
 
13710
   
 
13711
   overlapixs = 0;                                                         //  counts overlapping pixels
 
13712
   red1 = green1 = blue1 = 0;                                              //  suppress compiler warnings
 
13713
   red2 = green2 = blue2 = 0;
 
13714
   
 
13715
   for (py3 = 0; py3 < E3hh; py3++)                                        //  step through E3 rows
 
13716
   for (px3 = 0; px3 < E3ww; px3++)                                        //  step through E3 pixels in row
 
13717
   {
 
13718
      vstat1 = vstat2 = 0;
 
13719
      red3 = green3 = blue3 = 0;
 
13720
 
 
13721
      if (px3 < pxmH) {
 
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);
 
13725
      }
 
13726
 
 
13727
      if (px3 >= pxmL) {
 
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);
 
13731
      }
 
13732
 
 
13733
      if (vstat1) {
 
13734
         red1 = vpix1[0];
 
13735
         green1 = vpix1[1];
 
13736
         blue1 = vpix1[2];
 
13737
         if (!red1 && !green1 && !blue1) vstat1 = 0;                       //  ignore black pixels
 
13738
      }
 
13739
 
 
13740
      if (vstat2) {
 
13741
         red2 = vpix2[0];
 
13742
         green2 = vpix2[1];
 
13743
         blue2 = vpix2[2];
 
13744
         if (!red2 && !green2 && !blue2) vstat2 = 0;
 
13745
      }
 
13746
 
 
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) {
 
13753
               max = red1;
 
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);
 
13760
            }
 
13761
         }
 
13762
 
 
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) {
 
13768
               max = red2;
 
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);
 
13775
            }
 
13776
         }
 
13777
      }
 
13778
 
 
13779
      if (vstat1) {
 
13780
         if (! vstat2) {
 
13781
            red3 = red1;                                                   //  use image1 pixel
 
13782
            green3 = green1;
 
13783
            blue3 = blue1; 
 
13784
         }
 
13785
         else {
 
13786
            overlapixs++;                                                  //  count overlapped pixels
 
13787
            if (fcolor) {
 
13788
               if (alignWidth == 0) f1 = 1.0;
 
13789
               else f1 = 1.0 * (pxmH - px3) / alignWidth;                  //  use progressive blend
 
13790
               f2 = 1.0 - f1;
 
13791
               red3 = int(f1 * red1 + f2 * red2);
 
13792
               green3 = int(f1 * green1 + f2 * green2);
 
13793
               blue3 = int(f1 * blue1 + f2 * blue2);
 
13794
            }
 
13795
            else {                                                         //  use 50/50 mix
 
13796
               red3 = (red1 + red2) / 2;
 
13797
               green3 = (green1 + green2) / 2;
 
13798
               blue3 = (blue1 + blue2) / 2;
 
13799
            }
 
13800
         }
 
13801
      }
 
13802
 
 
13803
      else if (vstat2) {
 
13804
         red3 = red2;                                                      //  use image2 pixel
 
13805
         green3 = green2;
 
13806
         blue3 = blue2; 
 
13807
      }
 
13808
 
 
13809
      pix3 = bmpixel(E3rgb48,px3,py3);                                     //  output pixel
 
13810
      pix3[0] = red3;
 
13811
      pix3[1] = green3;
 
13812
      pix3[2] = blue3;
 
13813
      
 
13814
      if (showRedpix && vstat1 && vstat2) {                                //  highlight alignment pixels
 
13815
         ii = py3 * A1ww + px3;
 
13816
         if (redpixels[ii]) {
 
13817
            pix3[0] = 65535;
 
13818
            pix3[1] = pix3[2] = 0;
 
13819
         }
 
13820
      }
 
13821
   }
 
13822
 
 
13823
   mutex_unlock(&pixmaps_lock);
 
13824
   mwpaint2();                                                             //  update window
 
13825
   return;
 
13826
}
 
13827
 
 
13828
 
 
13829
/**************************************************************************
 
13830
    HDR and pano shared functions (image matching, alignment, overlay)
 
13831
***************************************************************************/
 
13832
 
 
13833
//  compare two doubles for significant difference
 
13834
//  return:  0  difference not significant
 
13835
//          +1  d1 > d2
 
13836
//          -1  d1 < d2
 
13837
 
 
13838
int sigdiff(double d1, double d2, double signf)
 
13839
{
 
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;
 
13845
   else return -1;
 
13846
}
 
13847
 
 
13848
 
 
13849
/**************************************************************************/
 
13850
 
 
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
 
13854
 
 
13855
void getAlignArea()
 
13856
{
 
13857
   int         pxL2, pyL2;
 
13858
 
 
13859
   pxL = 0;
 
13860
   if (xoff > 0) pxL = int(xoff);
 
13861
 
 
13862
   pxH = A1ww;
 
13863
   if (pxH > xoff + A2ww) pxH = int(xoff + A2ww);
 
13864
   
 
13865
   pyL = 0;
 
13866
   if (yoff > 0) pyL = int(yoff);
 
13867
   
 
13868
   pyH = A1hh;
 
13869
   if (pyH > yoff + A2hh) pyH = int(yoff + A2hh);
 
13870
 
 
13871
   if (toff > 0) {
 
13872
      pyL2 = int(yoff + toff * (pxH - pxL));
 
13873
      if (pyL2 > pyL) pyL = pyL2;
 
13874
   }
 
13875
 
 
13876
   if (toff < 0) {   
 
13877
      pxL2 = int(xoff - toff * (pyH - pyL));
 
13878
      if (pxL2 > pxL) pxL = pxL2;
 
13879
   }
 
13880
   
 
13881
   if (xshrink > 0) {
 
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;
 
13886
   }
 
13887
 
 
13888
   pxM = (pxL + pxH) / 2;                                                  //  midpoint of overlap
 
13889
   pyM = (pyL + pyH) / 2;
 
13890
 
 
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;
 
13896
   }
 
13897
   else {                                                                  //  use whole range
 
13898
      pxmL = pxL;
 
13899
      pxmH = pxH;
 
13900
   }
 
13901
 
 
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;
 
13907
   }
 
13908
   else {
 
13909
      pymL = pyL;
 
13910
      pymH = pyH;
 
13911
   }
 
13912
 
 
13913
   return;
 
13914
}
 
13915
 
 
13916
 
 
13917
/**************************************************************************/
 
13918
 
 
13919
//  Compute brightness ratio by color for overlapping image areas.
 
13920
//    (image2 is overlayed on image1, offset by xoff, yoff, toff)
 
13921
//
 
13922
//  Outputs: 
 
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
 
13927
 
 
13928
void getBrightRatios()
 
13929
{
 
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;
 
13940
   
 
13941
   for (rgb = 0; rgb < 3; rgb++)                                           //  clear distributions
 
13942
   for (ii = 0; ii < 256; ii++)
 
13943
      brdist1[rgb][ii] = brdist2[rgb][ii] = 0;
 
13944
 
 
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;
 
13949
 
 
13950
   npix = 0;
 
13951
   
 
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
 
13954
   {
 
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
 
13960
 
 
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
 
13966
 
 
13967
      ++npix;                                                              //  count overlapping pixels
 
13968
      
 
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])];
 
13973
      }
 
13974
   }
 
13975
   
 
13976
   npix1 = npix / 256;                                                     //  1/256th of total pixels
 
13977
   
 
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
 
13980
   {                                                                       //      for color rgb
 
13981
      brlev1[rgb][jj] = 0;
 
13982
      npix2 = npix1;                                                       //  1/256th of total pixels
 
13983
 
 
13984
      while (npix2 > 0 && ii < 256)                                        //  next 1/256th group from distr,
 
13985
      {
 
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;
 
13991
         npix2 -= npix3;
 
13992
      }
 
13993
 
 
13994
      brlev1[rgb][jj] = brlev1[rgb][jj] / npix1;                           //  mean brightness for group, 0-255
 
13995
   }
 
13996
 
 
13997
   for (rgb = 0; rgb < 3; rgb++)                                           //  do same for image2
 
13998
   for (ii = jj = 0; jj < 256; jj++)
 
13999
   {
 
14000
      brlev2[rgb][jj] = 0;
 
14001
      npix2 = npix1;
 
14002
 
 
14003
      while (npix2 > 0 && ii < 256)
 
14004
      {
 
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;
 
14010
         npix2 -= npix3;
 
14011
      }
 
14012
 
 
14013
      brlev2[rgb][jj] = brlev2[rgb][jj] / npix1;
 
14014
   }
 
14015
 
 
14016
   for (rgb = 0; rgb < 3; rgb++)                                           //  color
 
14017
   for (ii = jj = 0; ii < 256; ii++)                                       //  brlev1 brightness, 0 to 255
 
14018
   {                                                                       //  bugfix  v.6.4
 
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) {
 
14024
         if (jj > 0) {
 
14025
            a1 = brlev2[rgb][jj-1];                                        //  next lower value
 
14026
            b1 = brlev1[rgb][jj-1];
 
14027
         }
 
14028
         else   a1 = b1 = 0;
 
14029
         if (ii == 0)  bratio = b2 / a2;
 
14030
         else   bratio = (b1 + (ii-a1)/(a2-a1) * (b2-b1)) / ii;            //  interpolate
 
14031
      }
 
14032
 
 
14033
      if (bratio < 0.2) bratio = 0.2;                                      //  contain outliers
 
14034
      if (bratio > 5) bratio = 5;
 
14035
      Bratios2[rgb][ii] = bratio;
 
14036
   }
 
14037
 
 
14038
   for (rgb = 0; rgb < 3; rgb++)                                           //  color
 
14039
   for (ii = jj = 0; ii < 256; ii++)                                       //  brlev2 brightness, 0 to 255
 
14040
   {                                                                       //  bugfix  v.6.4
 
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) {
 
14046
         if (jj > 0) {
 
14047
            a1 = brlev1[rgb][jj-1];                                        //  next lower value
 
14048
            b1 = brlev2[rgb][jj-1];
 
14049
         }
 
14050
         else   a1 = b1 = 0;
 
14051
         if (ii == 0)  bratio = b2 / a2;
 
14052
         else   bratio = (b1 + (ii-a1)/(a2-a1) * (b2-b1)) / ii;            //  interpolate
 
14053
      }
 
14054
 
 
14055
      if (bratio < 0.2) bratio = 0.2;                                      //  contain outliers
 
14056
      if (bratio > 5) bratio = 5;
 
14057
      Bratios1[rgb][ii] = bratio;
 
14058
   }
 
14059
 
 
14060
   return;
 
14061
}
 
14062
 
 
14063
 
 
14064
/**************************************************************************/
 
14065
 
 
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
 
14070
 
 
14071
void setColorfixFactors(int state)
 
14072
{
 
14073
   unsigned      ii, jj;
 
14074
 
 
14075
   if (state) 
 
14076
   {
 
14077
      for (ii = 0; ii < 65536; ii++)
 
14078
      {
 
14079
         jj = ii >> 8;
 
14080
 
 
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
 
14084
 
 
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;
 
14088
      }
 
14089
   }
 
14090
 
 
14091
   else 
 
14092
   {
 
14093
      for (ii = 0; ii < 65536; ii++)
 
14094
      {
 
14095
         R12match[ii] = 1.0 / Radjust * ii;
 
14096
         G12match[ii] = 1.0 / Gadjust * ii;
 
14097
         B12match[ii] = 1.0 / Badjust * ii;
 
14098
 
 
14099
         R21match[ii] = Radjust * ii;
 
14100
         G21match[ii] = Gadjust * ii;
 
14101
         B21match[ii] = Badjust * ii;
 
14102
      }
 
14103
   }
 
14104
   
 
14105
   return;
 
14106
}
 
14107
 
 
14108
 
 
14109
/**************************************************************************/
 
14110
 
 
14111
//  find pixels of greatest contrast within overlap area
 
14112
//  flag high-contrast pixels to use in each image compare region
 
14113
 
 
14114
void flagEdgePixels()
 
14115
{
 
14116
   void  flagEdgePixels2(int pxL, int pxH, int pyL, int pyH, int samp);
 
14117
 
 
14118
   int      samp = pixsamp / 9;
 
14119
   int      pxm1, pxm2, pym1, pym2;
 
14120
   
 
14121
   if (redpixels) zfree(redpixels);                                        //  clear flags for alignment pixels
 
14122
   redpixels = zmalloc(A1ww*A1hh);
 
14123
   memset(redpixels,0,A1ww*A1hh);
 
14124
   
 
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);
 
14129
   
 
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);
 
14139
 
 
14140
   return;
 
14141
}
 
14142
 
 
14143
 
 
14144
//  Find the highest contrast pixels meeting sample size
 
14145
//  within the specified sub-region of image overlap.
 
14146
 
 
14147
void flagEdgePixels2(int pxL, int pxH, int pyL, int pyH, int samp)
 
14148
{
 
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];
 
14156
   
 
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
 
14160
 
 
14161
   Hcon = (uchar *) zmalloc(npix);                                         //  horizontal pixel contrast 0-255
 
14162
   Vcon = (uchar *) zmalloc(npix);                                         //  vertical pixel contrast 0-255
 
14163
 
 
14164
   for (py = pyL; py < pyH; py++)                                          //  scan image pixels in sub-region
 
14165
   for (px = pxL; px < pxH; px++)
 
14166
   {
 
14167
      ii = (py-pyL) * (pxH-pxL) + (px-pxL);
 
14168
      Hcon[ii] = Vcon[ii] = 0;                                             //  horiz. = vert. contrast = 0
 
14169
 
 
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
 
14175
 
 
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;
 
14181
 
 
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;
 
14185
 
 
14186
      vstat3 = vpixel(A1rgb48,px1,py1+4,vpix3);                            //  4 pixels below
 
14187
      if (! vstat3) continue;
 
14188
      if (! vpix3[0] && ! vpix3[1] && ! vpix3[2]) continue;
 
14189
 
 
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;
 
14193
      
 
14194
      vstat3 = vpixel(A2rgb48,px2+4,py2+4,vpix3);
 
14195
      if (! vstat3) continue;
 
14196
      if (!vpix3[0] && !vpix3[1] && !vpix3[2]) continue;
 
14197
 
 
14198
      vstat3 = vpixel(A1rgb48,px1-4,py1-4,vpix3);
 
14199
      if (! vstat3) continue;
 
14200
      if (!vpix3[0] && !vpix3[1] && !vpix3[2]) continue;
 
14201
      
 
14202
      vstat3 = vpixel(A2rgb48,px2-4,py2-4,vpix3);
 
14203
      if (! vstat3) continue;
 
14204
      if (!vpix3[0] && !vpix3[1] && !vpix3[2]) continue;
 
14205
 
 
14206
      red1 = vpix1[0];
 
14207
      green1 = vpix1[1];
 
14208
      blue1 = vpix1[2];
 
14209
 
 
14210
      vstat3 = vpixel(A1rgb48,px1+2,py1,vpix3);                            //  2 pixels to right
 
14211
      red2 = vpix3[0];
 
14212
      green2 = vpix3[1];
 
14213
      blue2 = vpix3[2];
 
14214
      tcon = abs(red1-red2) + abs(green1-green2) + abs(blue1-blue2);       //  horizontal contrast
 
14215
      Hcon[ii] = int(tcon * s8);                                           //    0 - 255
 
14216
 
 
14217
      vstat3 = vpixel(A1rgb48,px1,py1+2,vpix3);                            //  2 pixels below
 
14218
      red2 = vpix3[0];
 
14219
      green2 = vpix3[1];
 
14220
      blue2 = vpix3[2];
 
14221
      tcon = abs(red1-red2) + abs(green1-green2) + abs(blue1-blue2);       //  vertical contrast
 
14222
      Vcon[ii] = int(tcon * s8);
 
14223
   }
 
14224
 
 
14225
   for (ii = 0; ii < 256; ii++) Hdist[ii] = Vdist[ii] = 0;                 //  clear contrast distributions
 
14226
 
 
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);
 
14231
      ++Hdist[Hcon[ii]];
 
14232
      ++Vdist[Vcon[ii]];
 
14233
   }
 
14234
   
 
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; 
 
14239
   }
 
14240
   Hmin = ii; 
 
14241
 
 
14242
   for (npix = 0, ii = 255; ii > 0; ii--)                                  //  (verticle contrast pixels)
 
14243
   {
 
14244
      npix += Vdist[ii];
 
14245
      if (npix > samp) break;
 
14246
   }
 
14247
   Vmin = ii;
 
14248
   
 
14249
   for (py = pyL; py < pyH; py++)                                          //  scan image pixels   v.7.5
 
14250
   for (px = pxL; px < pxH; px++)
 
14251
   {
 
14252
      ii = (py-pyL) * (pxH-pxL) + (px-pxL);
 
14253
      jj = py * A1ww + px;
 
14254
 
 
14255
      if (Hcon[ii] > Hmin) {
 
14256
         redpixels[jj] = 1;                                                //  flag horizontal group of 3
 
14257
         redpixels[jj+1] = 1;
 
14258
         redpixels[jj+2] = 1;
 
14259
      }
 
14260
 
 
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;
 
14265
      }
 
14266
   }
 
14267
   
 
14268
   zfree(Hcon);
 
14269
   zfree(Vcon);
 
14270
   return;
 
14271
}
 
14272
 
 
14273
 
 
14274
/**************************************************************************/
 
14275
 
 
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)
 
14280
 
 
14281
double matchImages()                                                       //  weighting removed   v.8.5
 
14282
{
 
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;
 
14288
   
 
14289
   if (pxM > A1ww || pyM > A1hh) return 0;                                 //  overlap runs off image, no match
 
14290
 
 
14291
   cmatch = maxcmatch = 0;
 
14292
 
 
14293
   for (py = pymL; py < pymH; py++)                                        //  step through image1 pixels, rows
 
14294
   for (px = pxmL; px < pxmH; px++)                                        //  step through image1 pixels, cols
 
14295
   {
 
14296
      ii = py * A1ww + px;                                                 //  skip low-contrast pixels
 
14297
      if (! redpixels[ii]) continue;
 
14298
 
 
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
 
14304
 
 
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;
 
14310
      
 
14311
      match = matchPixels(vpix1,vpix2);                                    //  compare brightness adjusted
 
14312
      cmatch += match;                                                     //  accumulate total match
 
14313
      maxcmatch += 1.0;
 
14314
   }
 
14315
 
 
14316
   return cmatch / maxcmatch;
 
14317
}
 
14318
 
 
14319
 
 
14320
/**************************************************************************/
 
14321
 
 
14322
//  Compare 2 pixels using precalculated brightness ratios
 
14323
//  1.0 = perfect match   0 = total mismatch (black/white)
 
14324
 
 
14325
double matchPixels(uint16 *pix1, uint16 *pix2)
 
14326
{
 
14327
   double      red1, green1, blue1, red2, green2, blue2;
 
14328
   double      reddiff, greendiff, bluediff, match;
 
14329
   double      ff = 1.0 / 65536.0;
 
14330
 
 
14331
   red1 = R12match[pix1[0]];
 
14332
   green1 = G12match[pix1[1]];
 
14333
   blue1 = B12match[pix1[2]];
 
14334
 
 
14335
   red2 = R21match[pix2[0]];
 
14336
   green2 = G21match[pix2[1]];
 
14337
   blue2 = B21match[pix2[2]];
 
14338
 
 
14339
   reddiff = ff * fabs(red1-red2);                                         //  0 = perfect match
 
14340
   greendiff = ff * fabs(green1-green2);                                   //  1 = total mismatch
 
14341
   bluediff = ff * fabs(blue1-blue2);
 
14342
   
 
14343
   match = (1.0 - reddiff) * (1.0 - greendiff) * (1.0 - bluediff);         //  1 = perfect match
 
14344
   return match;
 
14345
}
 
14346
 
 
14347
 
 
14348
/**************************************************************************/
 
14349
 
 
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.
 
14352
 
 
14353
int vpixel(RGB *rgb, double px, double py, uint16 *vpix)                   //  overhauled   v.7.7
 
14354
{
 
14355
   int            ww, hh, px0, py0;
 
14356
   uint16         *ppix, *pix0, *pix1, *pix2, *pix3;
 
14357
   double         f0, f1, f2, f3;
 
14358
   double         red, green, blue;
 
14359
   
 
14360
   ww = rgb->ww;
 
14361
   hh = rgb->hh;
 
14362
   ppix = (uint16 *) rgb->bmp;
 
14363
 
 
14364
   px0 = int(px);                                                          //  pixel containing (px,py)
 
14365
   py0 = int(py);
 
14366
 
 
14367
   if (px0 < 1 || py0 < 1) return 0;
 
14368
   if (px0 > ww-3 || py0 > hh-3) return 0;
 
14369
   
 
14370
   pix0 = ppix + (py0 * ww + px0) * 3;                                     //  4 pixels based at (px0,py0)
 
14371
   pix1 = pix0 + ww * 3;
 
14372
   pix2 = pix0 + 3;
 
14373
   pix3 = pix0 + ww * 3 + 3;
 
14374
 
 
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);
 
14379
   
 
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];
 
14383
   
 
14384
   vpix[0] = int(red);
 
14385
   vpix[1] = int(green);
 
14386
   vpix[2] = int(blue);
 
14387
 
 
14388
   return 1;
 
14389
}
 
14390
 
 
14391
 
 
14392
/**************************************************************************
 
14393
 
 
14394
   edit transaction and thread support functions         overhauled  v.6.2
 
14395
 
 
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
 
14403
 
 
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
 
14410
 
 
14411
   thread function
 
14412
      thread_idle_loop()               wait for pending work, exit if commanded
 
14413
      exit_thread()                    exit thread unconditionally
 
14414
      
 
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
 
14420
 
 
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
 
14425
 
 
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
 
14430
 
 
14431
***************************************************************************/
 
14432
 
 
14433
int      thread_command = 0, thread_status = 0;
 
14434
int      thread_pend = 0, thread_done = 0, thread_hwm = 0;
 
14435
int      edit_action = 0;
 
14436
 
 
14437
 
 
14438
/**************************************************************************
 
14439
 
 
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).
 
14443
 
 
14444
  uprev      0     edit full-size image
 
14445
             1     edit preview image unless area exists and uarea = 2
 
14446
 
 
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)
 
14450
 
 
14451
***************************************************************************/
 
14452
 
 
14453
int edit_setup(int uprev, int uarea)
 
14454
{
 
14455
   if (! image_file) return 0;                                             //  no image file
 
14456
   if (! menulock(1)) return 0;                                            //  lock menu
 
14457
   
 
14458
   if (Pundo > Pundo_max) {
 
14459
      zmessageACK(ZTX("Too many undo buffers, please save image"));
 
14460
      menulock(0);
 
14461
      return 0;
 
14462
   }
 
14463
   
 
14464
   if (uarea == 0 && sa_stat) {                                            //  v.8.5
 
14465
      if (! zmessageYN(ZTX("Select area cannot be kept.\n"
 
14466
                           "Continue?"))) {
 
14467
         menulock(0);
 
14468
         return 0;
 
14469
      }
 
14470
   }
 
14471
   
 
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?"))) {
 
14475
         menulock(0);
 
14476
         return 0;
 
14477
      }
 
14478
   }
 
14479
   
 
14480
   if (Fimageturned) {
 
14481
      turn_image(-Fimageturned);                                           //  un-turn if needed
 
14482
      uarea = 0;                                                           //  (select area did already)
 
14483
   }
 
14484
   
 
14485
   if (uarea == 0 && sa_stat) {                                            //  delete select area   v.8.5
 
14486
      select_delete();
 
14487
      zdialog_free(zdsela);
 
14488
      zdsela = 0;
 
14489
   }
 
14490
   
 
14491
   Fpreview = 0;                                                           //  use preview image if supported
 
14492
   if (uprev && ! (uarea == 2 && sa_Npixel)) Fpreview = 1;                 //    and select area will not be used
 
14493
 
 
14494
   if (Fpreview && Fzoom) {
 
14495
      Fzoom = 0;
 
14496
      mwpaint();                                                           //  do immediately    v.8.6
 
14497
   }
 
14498
 
 
14499
   if (Fpreview && sa_stat) m_select_hide(0,0);                            //  v.8.5
 
14500
 
 
14501
   mutex_lock(&pixmaps_lock);                                              //  lock pixmaps
 
14502
 
 
14503
   if (! Frgb48) Frgb48 = image_load(image_file,48);                       //  create Frgb48 if not already
 
14504
   if (! Frgb48) {
 
14505
      mutex_unlock(&pixmaps_lock);
 
14506
      menulock(0);
 
14507
      return 0;
 
14508
   }
 
14509
   
 
14510
   RGB_free(E1rgb48);                                                      //  free prior edit pixmaps
 
14511
   RGB_free(E3rgb48);
 
14512
 
 
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
 
14517
 
 
14518
   E1ww = E3ww = E1rgb48->ww;
 
14519
   E1hh = E3hh = E1rgb48->hh;
 
14520
 
 
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
 
14525
 
 
14526
   mutex_unlock(&pixmaps_lock);
 
14527
   mwpaint2();
 
14528
   return 1;
 
14529
}
 
14530
 
 
14531
 
 
14532
/**************************************************************************/
 
14533
 
 
14534
//  process edit cancel
 
14535
 
 
14536
void edit_cancel()
 
14537
{
 
14538
   if (edit_action) return;                                                //  v.6.8
 
14539
   edit_action++;
 
14540
 
 
14541
   wrapup_thread(9);                                                       //  tell thread to quit now
 
14542
 
 
14543
   if (zdedit) {                                                           //  v.6.6
 
14544
      zdialog_free(zdedit);                                                //  kill dialog
 
14545
      zdedit = null;
 
14546
   }
 
14547
 
 
14548
   mutex_lock(&pixmaps_lock);
 
14549
   RGB_free(E1rgb48);                                                      //  free edit pixmaps E1, E3
 
14550
   E1rgb48 = 0;
 
14551
   RGB_free(E3rgb48);
 
14552
   E3rgb48 = 0;
 
14553
   RGB_free(Drgb24);
 
14554
   Drgb24 = 0;
 
14555
   E1ww = E3ww = Dww = 0;
 
14556
 
 
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
 
14563
   edit_action = 0;
 
14564
   return;
 
14565
}   
 
14566
 
 
14567
 
 
14568
/**************************************************************************/
 
14569
 
 
14570
//  process edit dialog [done]  
 
14571
//  E3rgb48 >> Frgb48 >> Frgb24
 
14572
 
 
14573
void edit_done()
 
14574
{
 
14575
   if (edit_action) return;                                                //  v.6.8
 
14576
   edit_action++;
 
14577
 
 
14578
   if (Fpreview && Fmodified) {
 
14579
      Fzoom = 0;                                                           //  v.8.3
 
14580
      edit_fullsize();                                                     //  update full image
 
14581
   }
 
14582
 
 
14583
   wrapup_thread(8);                                                       //  wait for thread done
 
14584
 
 
14585
   if (zdedit) {                                                           //  v.6.6
 
14586
      zdialog_free(zdedit);                                                //  kill dialog
 
14587
      zdedit = null;
 
14588
   }
 
14589
   
 
14590
   mutex_lock(&pixmaps_lock);
 
14591
 
 
14592
   if (Fmodified) {
 
14593
      RGB_free(Frgb48);                                                    //  memory leak   v.6.8
 
14594
      Frgb48 = RGB_copy(E3rgb48);                                          //  E3 >> Frgb48
 
14595
      RGB_free(Frgb24);
 
14596
      Frgb24 = RGB_convbpp(Frgb48);                                        //  Frgb48 >> Frgb24
 
14597
      Fww = Frgb24->ww;
 
14598
      Fhh = Frgb24->hh;
 
14599
      RGB_free(Drgb24);
 
14600
      Drgb24 = 0;
 
14601
      Dww = 0;
 
14602
      Pundo++;
 
14603
      Pumax = Pundo;
 
14604
      save_undo();                                                         //  save next undo state
 
14605
   }
 
14606
 
 
14607
   RGB_free(E1rgb48);                                                      //  free edit pixmaps
 
14608
   RGB_free(E3rgb48);
 
14609
   E1rgb48 = E3rgb48 = 0;
 
14610
   E1ww = E3ww = 0;
 
14611
   
 
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
 
14618
   edit_action = 0;
 
14619
   return;
 
14620
}
 
14621
 
 
14622
 
 
14623
/**************************************************************************/
 
14624
 
 
14625
//  process edit dialog [undo] and [redo] (not the toolbar functions)
 
14626
 
 
14627
void edit_undo()
 
14628
{
 
14629
   uint16   *pix1, *pix3;
 
14630
   int     px, py;
 
14631
   
 
14632
   if (! Fmodified) return;                                                //  v.6.3
 
14633
 
 
14634
   if (edit_action) return;                                                //  v.6.8
 
14635
   edit_action++;
 
14636
   
 
14637
   if (sa_Npixel)                                                          //  select area exists
 
14638
   {
 
14639
      for (int ii = 0; ii < sa_Npixel; ii++)                               //  restore enclosed pixels
 
14640
      {
 
14641
         px = sa_pixel[ii].px;
 
14642
         py = sa_pixel[ii].py;
 
14643
         pix1 = bmpixel(E1rgb48,px,py);
 
14644
         pix3 = bmpixel(E3rgb48,px,py);
 
14645
         pix3[0] = pix1[0];
 
14646
         pix3[1] = pix1[1];
 
14647
         pix3[2] = pix1[2];
 
14648
      }
 
14649
   }
 
14650
 
 
14651
   else 
 
14652
   {                                                                       //  restore entire image
 
14653
      mutex_lock(&pixmaps_lock);
 
14654
      RGB_free(E3rgb48);                                                   //  (size may have changed)
 
14655
      E3rgb48 = RGB_copy(E1rgb48);
 
14656
      E3ww = E1ww;
 
14657
      E3hh = E1hh;
 
14658
      RGB_free(Drgb24);
 
14659
      Drgb24 = 0;
 
14660
      Dww = 0;
 
14661
      Fmodified = 0;                                                       //  reset image modified status
 
14662
      mutex_unlock(&pixmaps_lock);
 
14663
   }
 
14664
 
 
14665
   mwpaint2();                                                             //  refresh window
 
14666
   edit_action = 0;
 
14667
   return;
 
14668
}
 
14669
 
 
14670
 
 
14671
void edit_redo()
 
14672
{
 
14673
   if (edit_action) return;                                                //  v.6.8
 
14674
   edit_action++;
 
14675
   signal_thread();                                                        //  start thread working
 
14676
   Fmodified = 1;
 
14677
   edit_action = 0;
 
14678
   return;
 
14679
}
 
14680
 
 
14681
 
 
14682
/**************************************************************************/
 
14683
 
 
14684
//  Convert from preview mode (window-size pixmaps) to full-size pixmaps.
 
14685
 
 
14686
void edit_fullsize()                                                       //  v.6.2
 
14687
{
 
14688
   if (! Fpreview) return;
 
14689
   Fpreview = 0;
 
14690
 
 
14691
   mutex_lock(&pixmaps_lock);
 
14692
   RGB_free(E1rgb48);                                                      //  free preview pixmaps
 
14693
   RGB_free(E3rgb48);
 
14694
   E1rgb48 = RGB_copy(Frgb48);                                             //  make full-size pixmaps
 
14695
   E3rgb48 = RGB_copy(Frgb48);
 
14696
   E1ww = E3ww = Fww;
 
14697
   E1hh = E3hh = Fhh;
 
14698
   mutex_unlock(&pixmaps_lock);
 
14699
 
 
14700
   signal_thread();                                                        //  reinstate edits
 
14701
   wait_thread_idle();
 
14702
   return;
 
14703
}
 
14704
 
 
14705
 
 
14706
/**************************************************************************/
 
14707
 
 
14708
//  update progress for long-running functions on status bar
 
14709
//  update in steps of 1%
 
14710
//  call with args = 0 when done
 
14711
 
 
14712
void edit_progress(int done, int goal)
 
14713
{
 
14714
   static int     ppercentdone = 0;
 
14715
   
 
14716
   if (goal == 0) {
 
14717
      PercentDone = ppercentdone = 0;
 
14718
      if (pthread_equal(tid_fmain,pthread_self())) update_statusbar();
 
14719
      else SBupdate++;
 
14720
      return;
 
14721
   }
 
14722
 
 
14723
   PercentDone = 100.0 * done / goal;
 
14724
   if (PercentDone - ppercentdone < 1) return;
 
14725
 
 
14726
   ppercentdone = PercentDone;
 
14727
   if (pthread_equal(tid_fmain,pthread_self())) update_statusbar();
 
14728
   else SBupdate++;
 
14729
   return;
 
14730
}
 
14731
 
 
14732
 
 
14733
/**************************************************************************/
 
14734
 
 
14735
//  start thread that does the edit work
 
14736
 
 
14737
void start_thread(threadfunc func, void *arg)
 
14738
{
 
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);
 
14742
   return;
 
14743
}
 
14744
 
 
14745
 
 
14746
//  signal thread that work is pending
 
14747
 
 
14748
void signal_thread()
 
14749
{
 
14750
   if (thread_status > 0) thread_pend++;                                   //  v.6.2
 
14751
   return;
 
14752
}
 
14753
 
 
14754
 
 
14755
//  wait for edit thread to complete pending work and become idle
 
14756
 
 
14757
void wait_thread_idle()
 
14758
{
 
14759
   while (thread_status && thread_pend > thread_done)
 
14760
   {
 
14761
      zmainloop();
 
14762
      zsleep(0.01);
 
14763
   }
 
14764
   
 
14765
   return;
 
14766
}
 
14767
 
 
14768
 
 
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
 
14773
 
 
14774
void wrapup_thread(int command)
 
14775
{
 
14776
   thread_command = command;                                               //  tell thread to quit or finish
 
14777
 
 
14778
   while (thread_status > 0)                                               //  wait for thread to finish
 
14779
   {                                                                       //    pending work and exit
 
14780
      zmainloop();
 
14781
      zsleep(0.01);
 
14782
   }
 
14783
 
 
14784
   return;
 
14785
}
 
14786
 
 
14787
 
 
14788
//  return thread idle/working status
 
14789
 
 
14790
int thread_working()                                                       //  v.8.4
 
14791
{
 
14792
   if (thread_status == 2) return 1;
 
14793
   return 0;
 
14794
}
 
14795
 
 
14796
 
 
14797
//  called only from edit threads
 
14798
//  idle loop - wait for work request or exit command
 
14799
 
 
14800
void thread_idle_loop()
 
14801
{
 
14802
   thread_status = 1;                                                      //  status = idle
 
14803
   thread_done = thread_hwm;                                               //  work done = high-water mark
 
14804
 
 
14805
   while (true)
 
14806
   {
 
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
 
14811
      zsleep(0.01);
 
14812
   }
 
14813
   
 
14814
   thread_hwm = thread_pend;                                               //  set high-water mark
 
14815
   thread_status = 2;                                                      //  thread is working
 
14816
   return;                                                                 //  loop to thread
 
14817
}
 
14818
 
 
14819
 
 
14820
//  called only from edit threads
 
14821
//  exit thread unconditionally
 
14822
 
 
14823
void exit_thread()
 
14824
{
 
14825
   thread_pend = thread_done = thread_hwm = 0;
 
14826
   thread_status = 0;
 
14827
   pthread_exit(0);
 
14828
}
 
14829
 
 
14830
 
 
14831
/**************************************************************************
 
14832
      undo / redo toolbar buttons
 
14833
***************************************************************************/
 
14834
 
 
14835
//  [undo] menu function - reinstate previous edit in undo/redo stack
 
14836
 
 
14837
void m_undo(GtkWidget *, const char *)
 
14838
{
 
14839
   if (Pundo == 0) return;
 
14840
   if (! menulock(1)) return;
 
14841
   Pundo--;
 
14842
   load_undo();
 
14843
   menulock(0);
 
14844
   return;
 
14845
}
 
14846
 
 
14847
 
 
14848
//  [redo] menu function - reinstate next edit in undo/redo stack
 
14849
 
 
14850
void m_redo(GtkWidget *, const char *)
 
14851
{
 
14852
   if (Pundo == Pumax) return;
 
14853
   if (! menulock(1)) return;
 
14854
   Pundo++;
 
14855
   load_undo();
 
14856
   menulock(0);
 
14857
   return;
 
14858
}
 
14859
 
 
14860
 
 
14861
//  Save Frgb48 to undo/redo file stack
 
14862
//  stack position = Pundo
 
14863
 
 
14864
void save_undo()
 
14865
{
 
14866
   char     *pp, buff[24];
 
14867
   int      fid, cc, cc2;
 
14868
 
 
14869
   pp = strstr(undo_files,"_undo_");
 
14870
   if (! pp) zappcrash("undo/redo stack corrupted 1");
 
14871
   snprintf(pp+6,3,"%02d",Pundo);
 
14872
   
 
14873
   fid = open(undo_files,O_WRONLY|O_CREAT|O_TRUNC,0640);
 
14874
   if (! fid) zappcrash("undo/redo stack corrupted 2");
 
14875
 
 
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");
 
14879
   
 
14880
   cc = Fww * Fhh * 6;
 
14881
   cc2 = write(fid,Frgb48->bmp,cc);
 
14882
   if (cc2 != cc) zappcrash("undo/redo stack corrupted 4");
 
14883
 
 
14884
   close(fid);
 
14885
   return;
 
14886
}
 
14887
 
 
14888
 
 
14889
//  Load Frgb48 from undo/redo file stack
 
14890
//  stack position = Pundo
 
14891
 
 
14892
void load_undo()
 
14893
{
 
14894
   char     *pp, buff[24], fotoxx[8];
 
14895
   int      fid, ww, hh, cc, cc2;
 
14896
 
 
14897
   pp = strstr(undo_files,"_undo_");
 
14898
   if (! pp) zappcrash("undo/redo stack corrupted 1");
 
14899
   snprintf(pp+6,3,"%02d",Pundo);
 
14900
   
 
14901
   fid = open(undo_files,O_RDONLY);
 
14902
   if (! fid) zappcrash("undo/redo stack corrupted 2");
 
14903
   
 
14904
   *fotoxx = 0;
 
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");
 
14908
 
 
14909
   mutex_lock(&pixmaps_lock);                                              //  v.6.3
 
14910
 
 
14911
   RGB_free(Frgb48);
 
14912
   Frgb48 = RGB_make(ww,hh,48);
 
14913
   cc = ww * hh * 6;
 
14914
   cc2 = read(fid,Frgb48->bmp,cc);
 
14915
   if (cc2 != cc) zappcrash("undo/redo stack corrupted 5");
 
14916
   close(fid);
 
14917
   
 
14918
   RGB_free(Frgb24);
 
14919
   Frgb24 = RGB_convbpp(Frgb48);
 
14920
   Fww = ww;
 
14921
   Fhh = hh;
 
14922
   RGB_free(Drgb24);                                                       //  v.6.8
 
14923
   Drgb24 = 0;
 
14924
   Dww = 0;
 
14925
 
 
14926
   mutex_unlock(&pixmaps_lock);
 
14927
   mwpaint2();
 
14928
   return;
 
14929
}
 
14930
 
 
14931
 
 
14932
/**************************************************************************
 
14933
      other support functions
 
14934
***************************************************************************/
 
14935
 
 
14936
//  help menu function
 
14937
 
 
14938
void m_help(GtkWidget *, const char *menu)
 
14939
{
 
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);
 
14943
      
 
14944
   if (strEqu(menu,"FreeImage"))
 
14945
      zmessageACK(FreeImage_GetCopyrightMessage());
 
14946
 
 
14947
   if (strEqu(menu,ZTX("User Guide"))) 
 
14948
      showz_userguide();
 
14949
 
 
14950
   if (strEqu(menu,"README"))
 
14951
      showz_readme();
 
14952
 
 
14953
   if (strEqu(menu,ZTX("Change Log")))
 
14954
      showz_changelog();
 
14955
 
 
14956
   if (strEqu(menu,ZTX("Translate")))
 
14957
      showz_translations();
 
14958
      
 
14959
   if (strEqu(menu,ZTX("Home Page")))
 
14960
      showz_html(fhomepage);
 
14961
 
 
14962
   return;
 
14963
}
 
14964
 
 
14965
 
 
14966
/**************************************************************************/
 
14967
 
 
14968
//  restore state data from prior session
 
14969
 
 
14970
int load_fotoxx_state()
 
14971
{
 
14972
   int            ww, hh, ii, np;
 
14973
   FILE           *fid;
 
14974
   char           buff[1000], text[100], *pp;
 
14975
   float          parms[2];
 
14976
   
 
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);
 
14981
   lens4_mm[0] = 30;
 
14982
   lens4_mm[1] = 40;
 
14983
   lens4_mm[2] = 50;
 
14984
   lens4_mm[3] = 60;
 
14985
   lens4_bow[0] = 0;
 
14986
   lens4_bow[1] = 0;
 
14987
   lens4_bow[2] = 0;
 
14988
   lens4_bow[3] = 0;
 
14989
   curr_lens = 1;
 
14990
 
 
14991
   snprintf(buff,999,"%s/saved_state",get_zuserdir());                     //  open saved state file
 
14992
   fid = fopen(buff,"r");
 
14993
   if (! fid) return 0;
 
14994
   
 
14995
   pp = fgets_trim(buff,999,fid,1);                                        //  read last image file
 
14996
 
 
14997
   if (pp && *pp == '/') {
 
14998
      if (image_file) zfree(image_file);
 
14999
      image_file = strdupz(pp);
 
15000
   }
 
15001
 
 
15002
   pp = fgets_trim(buff,999,fid,1);                                        //  top directory
 
15003
   if (pp && *pp == '/') topdirk = strdupz(pp);
 
15004
 
 
15005
   pp = fgets(buff,999,fid);                                               //  main window size
 
15006
   if (pp) {
 
15007
      ww = hh = 0;
 
15008
      sscanf(buff," %d %d ",&ww,&hh);
 
15009
      if (ww > 200 && ww < 3000) Dww = ww;
 
15010
      if (hh > 200 && hh < 2000) Dhh = hh;
 
15011
   }
 
15012
 
 
15013
   pp = fgets(buff,999,fid);                                               //  image gallery window size
 
15014
   if (pp) {
 
15015
      ww = hh = 0;
 
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;
 
15019
   }
 
15020
 
 
15021
   pp = fgets_trim(buff,999,fid,1);                                        //  thumbnail image size
 
15022
   if (pp) {
 
15023
      sscanf(buff," %d ",&ww);
 
15024
      if (ww > 32 && ww < 256) image_navi::thumbsize = ww;
 
15025
   }
 
15026
 
 
15027
   for (ii = 0; ii < 4; ii++)                                              //  4 sets of lens parameters
 
15028
   {   
 
15029
      pp = fgets_trim(buff,999,fid,1);
 
15030
      if (! pp) break;
 
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];
 
15036
   }
 
15037
   
 
15038
   pp = fgets_trim(buff,999,fid,1);                                        //  current lens
 
15039
   if (pp) {
 
15040
      sscanf(buff," %f ",&parms[0]);
 
15041
      if (parms[0] >= 0 && parms[0] < 4) curr_lens = int(parms[0]);
 
15042
   }
 
15043
 
 
15044
   pp = fgets_trim(buff,999,fid,1);                                        //  fotoxx version changed
 
15045
   if (! pp || strNeq(pp,fversion)) { /* do nothing */ }
 
15046
 
 
15047
   fclose(fid);
 
15048
 
 
15049
   for (ii = 0; ii < Nrecentfiles; ii++)                                   //  recent image file list = empty
 
15050
      recentfiles[ii] = 0;
 
15051
 
 
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;
 
15055
   
 
15056
   for (ii = 0; ii < Nrecentfiles; ii++)                                   //  read list of recent files
 
15057
   {
 
15058
      pp = fgets_trim(buff,999,fid,1);
 
15059
      if (! pp) break;
 
15060
      if (*pp == '/') recentfiles[ii] = strdupz(buff);
 
15061
   }
 
15062
 
 
15063
   fclose(fid);
 
15064
 
 
15065
   return 1;
 
15066
}
 
15067
 
 
15068
 
 
15069
/**************************************************************************/
 
15070
 
 
15071
//  save state data for next session
 
15072
 
 
15073
int save_fotoxx_state()
 
15074
{
 
15075
   FILE           *fid;
 
15076
   char           buff[1000];
 
15077
   int            ww, hh, ii;
 
15078
 
 
15079
   snprintf(buff,999,"%s/saved_state",get_zuserdir());                     //  open output file
 
15080
   fid = fopen(buff,"w");
 
15081
   if (! fid) return 0;
 
15082
   
 
15083
   if (image_file && *image_file == '/')                                   //  current image file 
 
15084
      fputs(image_file,fid);
 
15085
   fputs("\n",fid);
 
15086
 
 
15087
   if (topdirk && *topdirk == '/')                                         //  top image directory 
 
15088
      fputs(topdirk,fid);
 
15089
   fputs("\n",fid);
 
15090
 
 
15091
   gtk_window_get_size(GTK_WINDOW(mWin),&ww,&hh);
 
15092
   snprintf(buff,20," %d %d \n",ww,hh);                                    //  window size
 
15093
   fputs(buff,fid);
 
15094
 
 
15095
   snprintf(buff,20," %d %d \n",image_navi::xwinW,image_navi::xwinH);      //  image gallery window size
 
15096
   fputs(buff,fid);
 
15097
 
 
15098
   snprintf(buff,20," %d \n",image_navi::thumbsize);                       //  thumbnail size
 
15099
   fputs(buff,fid);
 
15100
   
 
15101
   for (ii = 0; ii < 4; ii++)                                              //  4 sets of lens parameters
 
15102
   {
 
15103
      snprintf(buff,100," %s %.1f %.2f \n",lens4_name[ii],lens4_mm[ii],lens4_bow[ii]);
 
15104
      fputs(buff,fid);
 
15105
   }
 
15106
   
 
15107
   snprintf(buff,100," %d \n",curr_lens);                                  //  current lens
 
15108
   fputs(buff,fid);
 
15109
 
 
15110
   fputs(fversion,fid);                                                    //  fotoxx version
 
15111
   fputs("\n",fid);
 
15112
   fputs("\n",fid);
 
15113
   
 
15114
   fclose(fid);
 
15115
   
 
15116
   snprintf(buff,999,"%s/recent_files",get_zuserdir());                    //  open output file
 
15117
   fid = fopen(buff,"w");
 
15118
   if (! fid) return 0;
 
15119
   
 
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]);
 
15123
   
 
15124
   fclose(fid);
 
15125
 
 
15126
   return 1;
 
15127
}
 
15128
 
 
15129
 
 
15130
/**************************************************************************/
 
15131
 
 
15132
//  free all resources associated with the current image file
 
15133
 
 
15134
void free_resources()
 
15135
{
 
15136
   char        *pp, command[200];
 
15137
   int         ignore;
 
15138
 
 
15139
   mutex_lock(&pixmaps_lock);                                              //  lock pixmaps
 
15140
   
 
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);
 
15146
 
 
15147
   Fmodified = Pundo = Pumax = Fsaved = 0;                                 //  reset undo/redo stack
 
15148
   Ntoplines = Nptoplines;                                                 //  no image overlay lines
 
15149
   paint_toparc(2);                                                        //  no brush outline
 
15150
   
 
15151
   if (Fshutdown) {                                                        //  stop here if shutdown mode
 
15152
      mutex_unlock(&pixmaps_lock);
 
15153
      return;
 
15154
   }
 
15155
   
 
15156
   if (image_file) {
 
15157
      select_delete();                                                     //  delete select area
 
15158
      zdialog_free(zdsela);                                                //  kill dialog if active   v.8.5
 
15159
      zdsela = null;
 
15160
      update_filetags(image_file);                                         //  commit tag changes, if any
 
15161
      zfree(image_file);                                                   //  free image file
 
15162
      image_file = 0;
 
15163
   }
 
15164
   
 
15165
   RGB_free(Frgb24);
 
15166
   Frgb24 = 0;
 
15167
   RGB_free(Frgb48);
 
15168
   Frgb48 = 0;
 
15169
   RGB_free(E1rgb48);
 
15170
   E1rgb48 = 0;
 
15171
   RGB_free(E3rgb48);
 
15172
   E3rgb48 = 0;
 
15173
   RGB_free(Drgb24);
 
15174
   Drgb24 = 0;
 
15175
 
 
15176
   Fww = E1ww = E3ww = Dww = 0;                                            //  make unusable (crash)
 
15177
 
 
15178
   mutex_unlock(&pixmaps_lock);
 
15179
   return;
 
15180
}
 
15181
 
 
15182
 
 
15183
/**************************************************************************/
 
15184
 
 
15185
//  ask user if modified image should be kept or discarded
 
15186
 
 
15187
int mod_keep()
 
15188
{
 
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
 
15192
   return 1;
 
15193
}
 
15194
 
 
15195
 
 
15196
/**************************************************************************/
 
15197
 
 
15198
//  menu lock/unlock - some functions must not run concurrently
 
15199
 
 
15200
int menulock(int lock)
 
15201
{
 
15202
   static int     mlock = 0;
 
15203
 
 
15204
   if (lock && mlock) {
 
15205
      zmessageACK("please wait for prior function to complete");
 
15206
      return 0;
 
15207
   }
 
15208
   
 
15209
   if (! lock && ! mlock) zappcrash("menu lock error");
 
15210
   
 
15211
   if (lock) mlock++;
 
15212
   else mlock--;
 
15213
   return 1;
 
15214
}
 
15215
 
 
15216
 
 
15217
/**************************************************************************/
 
15218
 
 
15219
//  Upright a turned image - not like an edit.
 
15220
//  Rotate Frgb24 without setting the Fmodified flag.
 
15221
 
 
15222
void  turn_image(int angle)
 
15223
{
 
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;
 
15230
   
 
15231
   mutex_lock(&pixmaps_lock);                                              //  lock pixmaps  v.8.5
 
15232
   RGB * temp_bmp = RGB_rotate(Frgb24,angle);
 
15233
   RGB_free(Frgb24);
 
15234
   Frgb24 = temp_bmp;
 
15235
   Fww = Frgb24->ww;
 
15236
   Fhh = Frgb24->hh;
 
15237
   Fzoom = 0;
 
15238
   mutex_unlock(&pixmaps_lock);
 
15239
 
 
15240
   mwpaint();                                                              //  synch Dww etc.  v.6.8
 
15241
   return;
 
15242
}
 
15243
 
 
15244
 
 
15245
/**************************************************************************/
 
15246
 
 
15247
//  FREEIMAGE error handler
 
15248
 
 
15249
void FI_error(FIF fif, const char *message)
 
15250
{
 
15251
   printf("FreeImage error: %s  file: %s \n",message,image_file);
 
15252
   return;
 
15253
}
 
15254
 
 
15255
 
 
15256
/**************************************************************************
 
15257
      pixmap conversion and rescale functions      revamped v.6.5
 
15258
***************************************************************************/
 
15259
 
 
15260
//  initialize an RGB pixmap - allocate memory
 
15261
 
 
15262
RGB * RGB_make(int ww, int hh, int bpp)
 
15263
{
 
15264
   if (ww < 1 || hh < 1 || (bpp != 24 && bpp != 48))
 
15265
      zappcrash("RGB_make() %d %d %d",ww,hh,bpp);
 
15266
   
 
15267
   RGB *rgb = (RGB *) zmalloc(sizeof(RGB));
 
15268
   rgb->ww = ww;
 
15269
   rgb->hh = hh;
 
15270
   rgb->bpp = bpp;
 
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");
 
15274
   return rgb;
 
15275
}
 
15276
 
 
15277
 
 
15278
//  free RGB pixmap
 
15279
 
 
15280
void RGB_free(RGB *rgb)
 
15281
{
 
15282
   if (! rgb) return;                                                      //  v.6.8
 
15283
   if (! strEqu(rgb->wmi,"rgbrgb")) 
 
15284
      zappcrash("RGB_free(), bad RGB");
 
15285
   strcpy(rgb->wmi,"xxxxxx");
 
15286
   zfree(rgb->bmp);
 
15287
   zfree(rgb);
 
15288
   return;
 
15289
}
 
15290
 
 
15291
 
 
15292
//  create a copy of an RGB pixmap
 
15293
 
 
15294
RGB * RGB_copy(RGB *rgb1)
 
15295
{
 
15296
   int      cc;
 
15297
   RGB      *rgb2;
 
15298
 
 
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
 
15302
   return rgb2;
 
15303
}
 
15304
 
 
15305
 
 
15306
//  create a copy of an RGB area
 
15307
 
 
15308
RGB * RGB_copy_area(RGB *rgb1, int orgx, int orgy, int ww2, int hh2)
 
15309
{
 
15310
   uint8          *bmp1, *pix1, *bmp2, *pix2;
 
15311
   uint16         *bmp3, *pix3, *bmp4, *pix4;
 
15312
   RGB            *rgb2 = 0;
 
15313
   int            ww1, hh1, bpp, px1, py1, px2, py2;
 
15314
 
 
15315
   ww1 = rgb1->ww;
 
15316
   hh1 = rgb1->hh;
 
15317
   bpp = rgb1->bpp;
 
15318
 
 
15319
   if (bpp == 24)
 
15320
   {
 
15321
      rgb2 = RGB_make(ww2,hh2,24);
 
15322
      bmp1 = (uint8 *) rgb1->bmp;
 
15323
      bmp2 = (uint8 *) rgb2->bmp;
 
15324
     
 
15325
      for (py1 = orgy, py2 = 0; py2 < hh2; py1++, py2++) 
 
15326
      {
 
15327
         for (px1 = orgx, px2 = 0; px2 < ww2; px1++, px2++)
 
15328
         {
 
15329
            pix1 = bmp1 + (py1 * ww1 + px1) * 3;
 
15330
            pix2 = bmp2 + (py2 * ww2 + px2) * 3;
 
15331
 
 
15332
            pix2[0] = pix1[0];
 
15333
            pix2[1] = pix1[1];
 
15334
            pix2[2] = pix1[2];
 
15335
            pix1 += 3;
 
15336
            pix2 += 3;
 
15337
         }
 
15338
      }
 
15339
   }
 
15340
 
 
15341
   if (bpp == 48)
 
15342
   {
 
15343
      rgb2 = RGB_make(ww2,hh2,48);
 
15344
      bmp3 = (uint16 *) rgb1->bmp;
 
15345
      bmp4 = (uint16 *) rgb2->bmp;
 
15346
     
 
15347
      for (py1 = orgy, py2 = 0; py2 < hh2; py1++, py2++) 
 
15348
      {
 
15349
         for (px1 = orgx, px2 = 0; px2 < ww2; px1++, px2++)
 
15350
         {
 
15351
            pix3 = bmp3 + (py1 * ww1 + px1) * 3;
 
15352
            pix4 = bmp4 + (py2 * ww2 + px2) * 3;
 
15353
 
 
15354
            pix4[0] = pix3[0];
 
15355
            pix4[1] = pix3[1];
 
15356
            pix4[2] = pix3[2];
 
15357
            pix3 += 3;
 
15358
            pix4 += 3;
 
15359
         }
 
15360
      }
 
15361
   }
 
15362
   
 
15363
   return rgb2;
 
15364
}
 
15365
 
 
15366
 
 
15367
//   convert RGB pixmap from 24/48 to 48/24 bits per pixel
 
15368
 
 
15369
RGB * RGB_convbpp(RGB *rgb1)
 
15370
{
 
15371
   uint8          *bmp8, *pix8;
 
15372
   uint16         *bmp16, *pix16;
 
15373
   RGB            *rgb2 = 0;
 
15374
   int            ww, hh, bpp, px, py;
 
15375
 
 
15376
   ww = rgb1->ww;
 
15377
   hh = rgb1->hh;
 
15378
   bpp = rgb1->bpp;
 
15379
 
 
15380
   if (bpp == 24)
 
15381
   {
 
15382
      rgb2 = RGB_make(ww,hh,48);
 
15383
      bmp8 = (uint8 *) rgb1->bmp;
 
15384
      bmp16 = (uint16 *) rgb2->bmp;
 
15385
     
 
15386
      for (py = 0; py < hh; py++) 
 
15387
      {
 
15388
         pix8 = bmp8 + py * ww * 3;
 
15389
         pix16 = bmp16 + py * ww * 3;
 
15390
 
 
15391
         for (px = 0; px < ww; px++)
 
15392
         {
 
15393
            pix16[0] = pix8[0] << 8;
 
15394
            pix16[1] = pix8[1] << 8;
 
15395
            pix16[2] = pix8[2] << 8;
 
15396
            pix8 += 3;
 
15397
            pix16 += 3;
 
15398
         }
 
15399
      }
 
15400
   }
 
15401
 
 
15402
   if (bpp == 48)
 
15403
   {
 
15404
      rgb2 = RGB_make(ww,hh,24);
 
15405
      bmp8 = (uint8 *) rgb2->bmp;
 
15406
      bmp16 = (uint16 *) rgb1->bmp;
 
15407
     
 
15408
      for (py = 0; py < hh; py++) 
 
15409
      {
 
15410
         pix8 = bmp8 + py * ww * 3;
 
15411
         pix16 = bmp16 + py * ww * 3;
 
15412
 
 
15413
         for (px = 0; px < ww; px++)
 
15414
         {
 
15415
            pix8[0] = pix16[0] >> 8;
 
15416
            pix8[1] = pix16[1] >> 8;
 
15417
            pix8[2] = pix16[2] >> 8;
 
15418
            pix8 += 3;
 
15419
            pix16 += 3;
 
15420
         }
 
15421
      }
 
15422
   }
 
15423
 
 
15424
   return rgb2;
 
15425
}
 
15426
 
 
15427
 
 
15428
//  convert RGB to FI bitmap, inverting row order
 
15429
 
 
15430
FIB * RGB_FIB(RGB *rgb)
 
15431
{
 
15432
   FIB         *fib = 0;
 
15433
   uint8       *bmpf;
 
15434
   uint8       *bmp1, *pix1, *pix2;
 
15435
   uint16      *bmp2, *pix3, *pix4;
 
15436
   int         ww, hh, bpp, px, py, pitch;
 
15437
   
 
15438
   ww = rgb->ww;
 
15439
   hh = rgb->hh;
 
15440
   bpp = rgb->bpp;
 
15441
 
 
15442
   if (bpp == 24)
 
15443
   {   
 
15444
      fib = FreeImage_Allocate(ww,hh,24);
 
15445
      if (! fib) zappcrash("FIB allocation failure");
 
15446
 
 
15447
      bmp1 = (uint8 *) rgb->bmp;
 
15448
      bmpf = FreeImage_GetBits(fib);
 
15449
      pitch = FreeImage_GetPitch(fib);
 
15450
 
 
15451
      for (py = 0; py < hh; py++)
 
15452
      {
 
15453
         pix1 = bmp1 + (hh - py - 1) * ww * 3;
 
15454
         pix2 = bmpf + py * pitch;
 
15455
         
 
15456
         for (px = 0; px < ww; px++)
 
15457
         {
 
15458
            pix2[RR] = pix1[0];
 
15459
            pix2[GG] = pix1[1];
 
15460
            pix2[BB] = pix1[2];
 
15461
            pix1 += 3;
 
15462
            pix2 += 3;
 
15463
         }
 
15464
      }
 
15465
   }
 
15466
 
 
15467
   if (bpp == 48)
 
15468
   {   
 
15469
      fib = FreeImage_AllocateT(FIT_RGB16,ww,hh,48);
 
15470
      if (! fib) zappcrash("FIB allocation failure");
 
15471
 
 
15472
      bmp2 = (uint16 *) rgb->bmp;
 
15473
      bmpf = FreeImage_GetBits(fib);
 
15474
      pitch = FreeImage_GetPitch(fib);
 
15475
      
 
15476
      for (py = 0; py < hh; py++)
 
15477
      {
 
15478
         pix3 = bmp2 + (hh - py - 1) * ww * 3;
 
15479
         pix4 = (uint16 *) (bmpf + py * pitch);
 
15480
         
 
15481
         for (px = 0; px < ww; px++)
 
15482
         {
 
15483
            pix4[0] = pix3[0];
 
15484
            pix4[1] = pix3[1];
 
15485
            pix4[2] = pix3[2];
 
15486
            pix3 += 3;
 
15487
            pix4 += 3;
 
15488
         }
 
15489
      }
 
15490
   }
 
15491
 
 
15492
   return fib;
 
15493
}
 
15494
 
 
15495
 
 
15496
//  convert FI to RGB pixmap, inverting row order
 
15497
 
 
15498
RGB * FIB_RGB(FIB *fib)
 
15499
{
 
15500
   RGB         *rgb;
 
15501
   uint8       *bmpf;
 
15502
   uint8       *bmp1, *pix1, *pix2;
 
15503
   uint16      *bmp2, *pix3, *pix4;
 
15504
   int         ww, hh, bpp, px, py, pitch;
 
15505
 
 
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);
 
15511
   
 
15512
   rgb = RGB_make(ww,hh,bpp);
 
15513
 
 
15514
   if (bpp == 24)
 
15515
   {   
 
15516
      bmp1 = (uint8 *) rgb->bmp;
 
15517
 
 
15518
      for (py = 0; py < hh; py++)
 
15519
      {
 
15520
         pix1 = bmp1 + (hh - py - 1) * ww * 3;
 
15521
         pix2 = bmpf + py * pitch;
 
15522
         
 
15523
         for (px = 0; px < ww; px++)
 
15524
         {
 
15525
            pix1[0] = pix2[RR];
 
15526
            pix1[1] = pix2[GG];
 
15527
            pix1[2] = pix2[BB];
 
15528
            pix1 += 3;
 
15529
            pix2 += 3;
 
15530
         }
 
15531
      }
 
15532
   }
 
15533
 
 
15534
   if (bpp == 48)
 
15535
   {   
 
15536
      bmp2 = (uint16 *) rgb->bmp;
 
15537
 
 
15538
      for (py = 0; py < hh; py++)
 
15539
      {
 
15540
         pix3 = bmp2 + (hh - py - 1) * ww * 3;
 
15541
         pix4 = (uint16 *) (bmpf + py * pitch);
 
15542
         
 
15543
         for (px = 0; px < ww; px++)
 
15544
         {
 
15545
            pix3[0] = pix4[0];
 
15546
            pix3[1] = pix4[1];
 
15547
            pix3[2] = pix4[2];
 
15548
            pix3 += 3;
 
15549
            pix4 += 3;
 
15550
         }
 
15551
      }
 
15552
   }
 
15553
 
 
15554
   return rgb;
 
15555
}
 
15556
 
 
15557
 
 
15558
//  convert RGB-24 pixmap to GDK pixbuf (24)
 
15559
 
 
15560
PXB * RGB_PXB(RGB *rgb)                                                    //  new v.6.7.2
 
15561
{
 
15562
   int      ww, hh, bpp, px, py, rowst;
 
15563
   uint8    *bmp1, *bmp2, *pix1, *pix2;
 
15564
   PXB      *pxb;
 
15565
 
 
15566
   ww = rgb->ww;
 
15567
   hh = rgb->hh;
 
15568
   bpp = rgb->bpp;
 
15569
   if (bpp != 24) zappcrash("RGB_PXB bpp %d",bpp);
 
15570
   
 
15571
   pxb = gdk_pixbuf_new(colorspace,0,8,ww,hh);
 
15572
   if (! pxb) zappcrash("pixbuf allocation failure");
 
15573
 
 
15574
   bmp1 = (uint8 *) rgb->bmp;
 
15575
   bmp2 = gdk_pixbuf_get_pixels(pxb);
 
15576
   rowst = gdk_pixbuf_get_rowstride(pxb);
 
15577
 
 
15578
   for (py = 0; py < hh; py++)
 
15579
   for (px = 0; px < ww; px++)
 
15580
   {
 
15581
      pix1 = bmp1 + (py * ww + px) * 3;
 
15582
      pix2 = bmp2 + rowst * py + 3 * px;
 
15583
      pix2[0] = pix1[0];
 
15584
      pix2[1] = pix1[1];
 
15585
      pix2[2] = pix1[2];
 
15586
   }
 
15587
   
 
15588
   return pxb;
 
15589
}
 
15590
 
 
15591
 
 
15592
//  convert GDK pixbuf (24/32) to RGB-24 pixmap
 
15593
 
 
15594
RGB * PXB_RGB(PXB *pxb)
 
15595
{
 
15596
   RGB         *rgb;
 
15597
   int         ww, hh, px, py, nch, rowst;
 
15598
   uint8       *bmp1, *bmp2, *pix1, *pix2;
 
15599
 
 
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);
 
15605
   
 
15606
   rgb = RGB_make(ww,hh,24);
 
15607
   bmp2 = (uint8 *) rgb->bmp;
 
15608
 
 
15609
   for (py = 0; py < hh; py++)
 
15610
   for (px = 0; px < ww; px++)
 
15611
   {
 
15612
      pix1 = bmp1 + rowst * py + nch * px;
 
15613
      pix2 = bmp2 + (py * ww + px) * 3;
 
15614
      pix2[0] = pix1[0];
 
15615
      pix2[1] = pix1[1];
 
15616
      pix2[2] = pix1[2];
 
15617
   }
 
15618
 
 
15619
   return rgb;
 
15620
}
 
15621
 
 
15622
 
 
15623
//  rescale RGB pixmap to size ww2 x hh2
 
15624
 
 
15625
RGB * RGB_rescale(RGB *rgb1, int ww2, int hh2)
 
15626
{
 
15627
   void RGB_rescale_24(uint8*, uint8*, int, int, int, int);
 
15628
   void RGB_rescale_48(uint16*, uint16*, int, int, int, int);
 
15629
 
 
15630
   RGB      *rgb2;
 
15631
   int      ww1, hh1, bpp;
 
15632
   uint8    *bmp1, *bmp2;
 
15633
   uint16   *bmp3, *bmp4;
 
15634
 
 
15635
   ww1 = rgb1->ww;
 
15636
   hh1 = rgb1->hh;
 
15637
   bpp = rgb1->bpp;
 
15638
   
 
15639
   rgb2 = RGB_make(ww2,hh2,bpp);
 
15640
   
 
15641
   if (bpp == 24) {
 
15642
      bmp1 = (uint8 *) rgb1->bmp;
 
15643
      bmp2 = (uint8 *) rgb2->bmp;
 
15644
      RGB_rescale_24(bmp1,bmp2,ww1,hh1,ww2,hh2);
 
15645
   }
 
15646
 
 
15647
   if (bpp == 48) {
 
15648
      bmp3 = (uint16 *) rgb1->bmp;
 
15649
      bmp4 = (uint16 *) rgb2->bmp;
 
15650
      RGB_rescale_48(bmp3,bmp4,ww1,hh1,ww2,hh2);
 
15651
   }
 
15652
   
 
15653
   return rgb2;
 
15654
}
 
15655
 
 
15656
 
 
15657
//  rotate RGB pixmap through given angle in degrees (+ = clockwise)
 
15658
 
 
15659
RGB * RGB_rotate(RGB *rgb1, double angle)
 
15660
{
 
15661
   RGB * RGB_rotate_24(RGB *, double);
 
15662
   RGB * RGB_rotate_48(RGB *, double);
 
15663
 
 
15664
   RGB      *rgb2 = 0;
 
15665
   int      bpp;
 
15666
   
 
15667
   bpp = rgb1->bpp;
 
15668
   if (bpp == 24) rgb2 = RGB_rotate_24(rgb1,angle);
 
15669
   if (bpp == 48) rgb2 = RGB_rotate_48(rgb1,angle);
 
15670
   return rgb2;
 
15671
}
 
15672
 
 
15673
 
 
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"
 
15679
 
 
15680
RGB * image_load(const char *filespec, int bpp)                            //  revamped   v.6.6.1
 
15681
{
 
15682
   FIF         fif;
 
15683
   FIB         *fib = 0, *fib2 = 0;
 
15684
   int         err, fibpp = 0;                                             //  gcc 4.4   v.7.1.1
 
15685
   RGB         *rgb24 = 0, *rgb48 = 0;
 
15686
   struct stat fstat;
 
15687
   
 
15688
   if (bpp != 24 && bpp != 48)                                             //  requested bpp must be 24 or 48
 
15689
      zappcrash("image_load bpp: %d",bpp);
 
15690
 
 
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
 
15694
 
 
15695
   fif = FreeImage_GetFileType(filespec,0);                                //  unsupported file type
 
15696
   if (fif == FIF_UNKNOWN) goto ILreturn;
 
15697
 
 
15698
   fib = FreeImage_Load(fif,filespec,0);
 
15699
   if (! fib) goto ILreturn;                                               //  cannot load file
 
15700
 
 
15701
   fibpp = FreeImage_GetBPP(fib);                                          //  disk file bits/pixel
 
15702
   
 
15703
   if (fibpp < 24) {
 
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);
 
15708
   }
 
15709
 
 
15710
   if (fibpp == 24) {                                                      //  24
 
15711
      rgb24 = FIB_RGB(fib);
 
15712
      if (bpp == 48) rgb48 = RGB_convbpp(rgb24);
 
15713
   }
 
15714
   
 
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);
 
15720
   }
 
15721
 
 
15722
   if (fibpp == 48) {                                                      //  48
 
15723
      rgb48 = FIB_RGB(fib);
 
15724
      if (bpp == 24) rgb24 = RGB_convbpp(rgb48);
 
15725
   }
 
15726
   
 
15727
ILreturn:
 
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
 
15732
      return 0;
 
15733
   }
 
15734
   
 
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");
 
15740
 
 
15741
   if (bpp == 24) {
 
15742
      RGB_free(rgb48);                                                     //  return RGB-24 pixmap
 
15743
      return rgb24;
 
15744
   }
 
15745
 
 
15746
   RGB_free(rgb24);                                                        //  return RGB-48 pixmap
 
15747
   return rgb48;
 
15748
}
 
15749
 
 
15750
 
 
15751
/**************************************************************************
 
15752
 
 
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.
 
15755
 
 
15756
   Method: 
 
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.
 
15763
 
 
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]
 
15768
 
 
15769
*********/
 
15770
 
 
15771
typedef struct {
 
15772
   uint8    *pixmap1;                                                      //  file scope data for threads
 
15773
   uint8    *pixmap2;
 
15774
   int      ww1;
 
15775
   int      hh1;
 
15776
   int      ww2;
 
15777
   int      hh2;
 
15778
   int      *py1L;
 
15779
   int      *px1L;
 
15780
   float    *pymap;
 
15781
   float    *pxmap;
 
15782
   int      maxmapx;
 
15783
   int      maxmapy;
 
15784
   int      index;
 
15785
   int      done;
 
15786
} rs24poop;
 
15787
 
 
15788
void RGB_rescale_24(uint8 *pixmap1, uint8 *pixmap2, int ww1, int hh1, int ww2, int hh2)
 
15789
{
 
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;
 
15795
   float       fx, fy;
 
15796
 
 
15797
   scalex = 1.0 * ww1 / ww2;                                               //  compute x and y scales
 
15798
   scaley = 1.0 * hh1 / hh2;
 
15799
   
 
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);
 
15805
   maxmapy += 1;
 
15806
   
 
15807
   memset(pixmap2, 0, ww2 * hh2 * 3);                                      //  clear output pixmap
 
15808
   
 
15809
   int *py1L = (int *) zmalloc(hh2 * sizeof(int));                         //  maps first (lowest) input pixel
 
15810
   int *px1L = (int *) zmalloc(ww2 * sizeof(int));                         //    per output pixel
 
15811
 
 
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
 
15814
 
 
15815
   for (py2 = 0; py2 < hh2; py2++)                                         //  loop output y-pixels
 
15816
   {
 
15817
      py1a = py2 * scaley;                                                 //  corresponding input y-pixels
 
15818
      py1b = py1a + scaley;
 
15819
      if (py1b >= hh1) py1b = hh1 - 0.001;                                 //  fix precision limitation
 
15820
      pyl = int(py1a);
 
15821
      py1L[py2] = pyl;                                                     //  1st overlapping input pixel
 
15822
 
 
15823
      for (py1 = pyl, pym = 0; py1 < py1b; py1++, pym++)                   //  loop overlapping input pixels
 
15824
      {
 
15825
         if (py1 < py1a) {                                                 //  compute amount of overlap
 
15826
            if (py1+1 < py1b) fy = py1+1 - py1a;                           //    0.0 to 1.0 
 
15827
            else fy = scaley;
 
15828
         }
 
15829
         else if (py1+1 > py1b) fy = py1b - py1;
 
15830
         else fy = 1;
 
15831
 
 
15832
         ii = py2 * maxmapy + pym;                                         //  save it
 
15833
         pymap[ii] = 0.9999 * fy / scaley;
 
15834
      }
 
15835
      ii = py2 * maxmapy + pym;                                            //  set an end marker after
 
15836
      pymap[ii] = -1;                                                      //    last overlapping pixel
 
15837
   }
 
15838
   
 
15839
   for (px2 = 0; px2 < ww2; px2++)                                         //  do same for x-pixels
 
15840
   {
 
15841
      px1a = px2 * scalex;
 
15842
      px1b = px1a + scalex;
 
15843
      if (px1b >= ww1) px1b = ww1 - 0.001;
 
15844
      pxl = int(px1a);
 
15845
      px1L[px2] = pxl;
 
15846
 
 
15847
      for (px1 = pxl, pxm = 0; px1 < px1b; px1++, pxm++)
 
15848
      {
 
15849
         if (px1 < px1a) {
 
15850
            if (px1+1 < px1b) fx = px1+1 - px1a;
 
15851
            else fx = scalex;
 
15852
         }
 
15853
         else if (px1+1 > px1b) fx = px1b - px1;
 
15854
         else fx = 1;
 
15855
 
 
15856
         ii = px2 * maxmapx + pxm;
 
15857
         pxmap[ii] = 0.9999 * fx / scalex;
 
15858
      }
 
15859
      ii = px2 * maxmapx + pxm;
 
15860
      pxmap[ii] = -1;
 
15861
   }
 
15862
   
 
15863
   rs24poop threaddata[maxWthreads];                                       //  data for threads
 
15864
   void  * RGB_rescale_24_thread(void *arg);
 
15865
 
 
15866
   for (ii = 0; ii < NWthreads; ii++)                                      //  start worker threads
 
15867
   {
 
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]);
 
15883
   }
 
15884
   
 
15885
   for (ii = 0; ii < NWthreads; ii++)                                      //  wait for all done
 
15886
      while (threaddata[ii].done == 0) zsleep(0.004);
 
15887
 
 
15888
   zfree(py1L);
 
15889
   zfree(px1L);
 
15890
   zfree(pymap);
 
15891
   zfree(pxmap);
 
15892
   return;
 
15893
}
 
15894
 
 
15895
void * RGB_rescale_24_thread(void *arg)                                    //  worker thread function
 
15896
{
 
15897
   rs24poop * threaddata = (rs24poop *) arg;
 
15898
 
 
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;
 
15911
   
 
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;
 
15917
 
 
15918
   for (py2 = index; py2 < hh2; py2 += NWthreads)                          //  loop output y-pixels
 
15919
   {
 
15920
      pyl = py1L[py2];                                                     //  corresp. 1st input y-pixel
 
15921
 
 
15922
      for (px2 = 0; px2 < ww2; px2++)                                      //  loop output x-pixels
 
15923
      {
 
15924
         pxl = px1L[px2];                                                  //  corresp. 1st input x-pixel
 
15925
 
 
15926
         red = green = blue = 0;                                           //  initz. output pixel
 
15927
 
 
15928
         for (py1 = pyl, pym = 0; ; py1++, pym++)                          //  loop overlapping input y-pixels
 
15929
         {
 
15930
            ii = py2 * maxmapy + pym;                                      //  get y-overlap
 
15931
            fy = pymap[ii];
 
15932
            if (fy < 0) break;                                             //  no more pixels
 
15933
 
 
15934
            for (px1 = pxl, pxm = 0; ; px1++, pxm++)                       //  loop overlapping input x-pixels
 
15935
            {
 
15936
               ii = px2 * maxmapx + pxm;                                   //  get x-overlap
 
15937
               fx = pxmap[ii];
 
15938
               if (fx < 0) break;                                          //  no more pixels
 
15939
 
 
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;
 
15945
            }
 
15946
 
 
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);
 
15951
         }
 
15952
      }
 
15953
   }
 
15954
 
 
15955
   threaddata->done = 1;
 
15956
   pthread_exit(0);
 
15957
}
 
15958
 
 
15959
 
 
15960
/**************************************************************************
 
15961
 
 
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: 
 
15964
      uint8 >> uint16
 
15965
      xxx24 >> xxx48
 
15966
      3 >> 6
 
15967
      memset 3 >> 6.
 
15968
 
 
15969
*******/
 
15970
 
 
15971
typedef struct {
 
15972
   uint16   *pixmap1;                                                      //  file scope data for threads
 
15973
   uint16   *pixmap2;
 
15974
   int      ww1;
 
15975
   int      hh1;
 
15976
   int      ww2;
 
15977
   int      hh2;
 
15978
   int      *py1L;
 
15979
   int      *px1L;
 
15980
   float    *pymap;
 
15981
   float    *pxmap;
 
15982
   int      maxmapx;
 
15983
   int      maxmapy;
 
15984
   int      index;
 
15985
   int      done;
 
15986
} rs48poop;
 
15987
 
 
15988
void RGB_rescale_48(uint16 *pixmap1, uint16 *pixmap2, int ww1, int hh1, int ww2, int hh2)
 
15989
{
 
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;
 
15995
   float       fx, fy;
 
15996
 
 
15997
   scalex = 1.0 * ww1 / ww2;                                               //  compute x and y scales
 
15998
   scaley = 1.0 * hh1 / hh2;
 
15999
   
 
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);
 
16005
   maxmapy += 1;
 
16006
   
 
16007
   memset(pixmap2, 0, ww2 * hh2 * 6);                                      //  clear output pixmap
 
16008
   
 
16009
   int *py1L = (int *) zmalloc(hh2 * sizeof(int));                         //  maps first (lowest) input pixel
 
16010
   int *px1L = (int *) zmalloc(ww2 * sizeof(int));                         //    per output pixel
 
16011
 
 
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
 
16014
 
 
16015
   for (py2 = 0; py2 < hh2; py2++)                                         //  loop output y-pixels
 
16016
   {
 
16017
      py1a = py2 * scaley;                                                 //  corresponding input y-pixels
 
16018
      py1b = py1a + scaley;
 
16019
      if (py1b >= hh1) py1b = hh1 - 0.001;                                 //  fix precision limitation
 
16020
      pyl = int(py1a);
 
16021
      py1L[py2] = pyl;                                                     //  1st overlapping input pixel
 
16022
 
 
16023
      for (py1 = pyl, pym = 0; py1 < py1b; py1++, pym++)                   //  loop overlapping input pixels
 
16024
      {
 
16025
         if (py1 < py1a) {                                                 //  compute amount of overlap
 
16026
            if (py1+1 < py1b) fy = py1+1 - py1a;                           //    0.0 to 1.0 
 
16027
            else fy = scaley;
 
16028
         }
 
16029
         else if (py1+1 > py1b) fy = py1b - py1;
 
16030
         else fy = 1;
 
16031
 
 
16032
         ii = py2 * maxmapy + pym;                                         //  save it
 
16033
         pymap[ii] = 0.9999 * fy / scaley;
 
16034
      }
 
16035
      ii = py2 * maxmapy + pym;                                            //  set an end marker after
 
16036
      pymap[ii] = -1;                                                      //    last overlapping pixel
 
16037
   }
 
16038
   
 
16039
   for (px2 = 0; px2 < ww2; px2++)                                         //  do same for x-pixels
 
16040
   {
 
16041
      px1a = px2 * scalex;
 
16042
      px1b = px1a + scalex;
 
16043
      if (px1b >= ww1) px1b = ww1 - 0.001;
 
16044
      pxl = int(px1a);
 
16045
      px1L[px2] = pxl;
 
16046
 
 
16047
      for (px1 = pxl, pxm = 0; px1 < px1b; px1++, pxm++)
 
16048
      {
 
16049
         if (px1 < px1a) {
 
16050
            if (px1+1 < px1b) fx = px1+1 - px1a;
 
16051
            else fx = scalex;
 
16052
         }
 
16053
         else if (px1+1 > px1b) fx = px1b - px1;
 
16054
         else fx = 1;
 
16055
 
 
16056
         ii = px2 * maxmapx + pxm;
 
16057
         pxmap[ii] = 0.9999 * fx / scalex;
 
16058
      }
 
16059
      ii = px2 * maxmapx + pxm;
 
16060
      pxmap[ii] = -1;
 
16061
   }
 
16062
   
 
16063
   rs48poop threaddata[maxWthreads];                                       //  data for threads
 
16064
   void  * RGB_rescale_48_thread(void *arg);
 
16065
 
 
16066
   for (ii = 0; ii < NWthreads; ii++)                                      //  start worker threads
 
16067
   {
 
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]);
 
16083
   }
 
16084
   
 
16085
   for (ii = 0; ii < NWthreads; ii++)                                      //  wait for all done
 
16086
      while (threaddata[ii].done == 0) zsleep(0.004);
 
16087
 
 
16088
   zfree(py1L);
 
16089
   zfree(px1L);
 
16090
   zfree(pymap);
 
16091
   zfree(pxmap);
 
16092
   return;
 
16093
}
 
16094
 
 
16095
void * RGB_rescale_48_thread(void *arg)                                    //  worker thread function
 
16096
{
 
16097
   rs48poop * threaddata = (rs48poop *) arg;
 
16098
 
 
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;
 
16111
   
 
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;
 
16117
 
 
16118
   for (py2 = index; py2 < hh2; py2 += NWthreads)                          //  loop output y-pixels
 
16119
   {
 
16120
      pyl = py1L[py2];                                                     //  corresp. 1st input y-pixel
 
16121
 
 
16122
      for (px2 = 0; px2 < ww2; px2++)                                      //  loop output x-pixels
 
16123
      {
 
16124
         pxl = px1L[px2];                                                  //  corresp. 1st input x-pixel
 
16125
 
 
16126
         red = green = blue = 0;                                           //  initz. output pixel
 
16127
 
 
16128
         for (py1 = pyl, pym = 0; ; py1++, pym++)                          //  loop overlapping input y-pixels
 
16129
         {
 
16130
            ii = py2 * maxmapy + pym;                                      //  get y-overlap
 
16131
            fy = pymap[ii];
 
16132
            if (fy < 0) break;                                             //  no more pixels
 
16133
 
 
16134
            for (px1 = pxl, pxm = 0; ; px1++, pxm++)                       //  loop overlapping input x-pixels
 
16135
            {
 
16136
               ii = px2 * maxmapx + pxm;                                   //  get x-overlap
 
16137
               fx = pxmap[ii];
 
16138
               if (fx < 0) break;                                          //  no more pixels
 
16139
 
 
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;
 
16145
            }
 
16146
 
 
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);
 
16151
         }
 
16152
      }
 
16153
   }
 
16154
 
 
16155
   threaddata->done = 1;
 
16156
   pthread_exit(0);
 
16157
}
 
16158
 
 
16159
 
 
16160
/**************************************************************************
 
16161
 
 
16162
      RGB *rgb2 = RGB_rotate_24(RGB *rgb1, double angle)
 
16163
 
 
16164
      Rotate RGB-24 pixmap through an arbitrary angle (degrees).
 
16165
 
 
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.
 
16171
 
 
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.
 
16176
      
 
16177
      Work is divided among NWthreads to gain speed.
 
16178
 
 
16179
      Algorithm:
 
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
 
16189
            continue
 
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)
 
16194
 
 
16195
******/
 
16196
 
 
16197
int      rotrgb24_busy = 0;
 
16198
uint8    *rotrgb24_pixmap1;
 
16199
uint8    *rotrgb24_pixmap2;
 
16200
int      rotrgb24_ww1;
 
16201
int      rotrgb24_hh1;
 
16202
int      rotrgb24_ww2;
 
16203
int      rotrgb24_hh2;
 
16204
double   rotrgb24_angle;
 
16205
 
 
16206
 
 
16207
RGB * RGB_rotate_24(RGB *rgb1, double angle)
 
16208
{
 
16209
   void     *RGB_rotate_24_thread(void *);
 
16210
 
 
16211
   int      ww1, hh1, ww2, hh2, cc, ii;
 
16212
   double   pi = 3.141592654;;
 
16213
   uint8    *pixmap1, *pixmap2;
 
16214
   RGB      *rgb2;
 
16215
   
 
16216
   ww1 = rgb1->ww;
 
16217
   hh1 = rgb1->hh;
 
16218
   pixmap1 = (uint8 *) rgb1->bmp;
 
16219
 
 
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
 
16223
   
 
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);
 
16229
      return rgb2;
 
16230
   }
 
16231
 
 
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)));
 
16234
   
 
16235
   rgb2 = RGB_make(ww2,hh2,24);
 
16236
   pixmap2 = (uint8 *) rgb2->bmp;
 
16237
 
 
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;
 
16245
 
 
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);
 
16249
 
 
16250
   while (rotrgb24_busy) zsleep(0.004);                                    //  wait for completion
 
16251
   return rgb2;
 
16252
}
 
16253
 
 
16254
 
 
16255
void * RGB_rotate_24_thread(void *arg)
 
16256
{
 
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;
 
16264
 
 
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;
 
16272
   
 
16273
   for (py2 = index; py2 < hh2; py2 += NWthreads)                          //  loop through output pixels
 
16274
   for (px2 = 0; px2 < ww2; px2++)                                         //  outer loop y
 
16275
   {
 
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
 
16281
      if (rx2 < 0) {
 
16282
         if (theta < 0) theta = - pi - theta;                              //  adjust for quandrant
 
16283
         else theta = pi - theta;
 
16284
      }
 
16285
 
 
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;
 
16289
 
 
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
 
16294
 
 
16295
      px0 = int(px1);                                                      //  pixel containing (px1,py1)
 
16296
      py0 = int(py1);
 
16297
      
 
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;
 
16301
         continue;
 
16302
      }
 
16303
 
 
16304
      pix0 = ppix1 + (py0 * ww1 + px0) * 3;                                //  4 input pixels based at (px0,py0)
 
16305
      pix1 = pix0 + ww1 * 3;
 
16306
      pix2 = pix0 + 3;
 
16307
      pix3 = pix1 + 3;
 
16308
 
 
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);
 
16313
   
 
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];
 
16317
      
 
16318
      pix2 = ppix2 + (py2 * ww2 + px2) * 3;                                //  output pixel
 
16319
      pix2[0] = int(red);
 
16320
      pix2[1] = int(green);
 
16321
      pix2[2] = int(blue);
 
16322
   }
 
16323
   
 
16324
   zadd_locked(rotrgb24_busy,-1);
 
16325
   pthread_exit(0);
 
16326
}
 
16327
 
 
16328
 
 
16329
/**************************************************************************
 
16330
 
 
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:
 
16334
      uint8 >> uint16
 
16335
      rotrgb24 >> rotrgb48
 
16336
      24 >> 48   
 
16337
      3 >> 6 
 
16338
 
 
16339
**********/
 
16340
 
 
16341
int      rotrgb48_busy = 0;
 
16342
uint16   *rotrgb48_pixmap1;
 
16343
uint16   *rotrgb48_pixmap2;
 
16344
int      rotrgb48_ww1;
 
16345
int      rotrgb48_hh1;
 
16346
int      rotrgb48_ww2;
 
16347
int      rotrgb48_hh2;
 
16348
double   rotrgb48_angle;
 
16349
 
 
16350
 
 
16351
RGB * RGB_rotate_48(RGB *rgb1, double angle)
 
16352
{
 
16353
   void     *RGB_rotate_48_thread(void *);
 
16354
 
 
16355
   int      ww1, hh1, ww2, hh2, cc, ii;
 
16356
   double   pi = 3.141592654;
 
16357
   uint16   *pixmap1, *pixmap2;
 
16358
   RGB      *rgb2;
 
16359
   
 
16360
   ww1 = rgb1->ww;
 
16361
   hh1 = rgb1->hh;
 
16362
   pixmap1 = (uint16 *) rgb1->bmp;
 
16363
 
 
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
 
16367
   
 
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);
 
16373
      return rgb2;
 
16374
   }
 
16375
 
 
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)));
 
16378
   
 
16379
   rgb2 = RGB_make(ww2,hh2,48);
 
16380
   pixmap2 = (uint16 *) rgb2->bmp;
 
16381
 
 
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;
 
16389
 
 
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);
 
16393
 
 
16394
   while (rotrgb48_busy) zsleep(0.004);                                    //  wait for completion
 
16395
   return rgb2;
 
16396
}
 
16397
 
 
16398
 
 
16399
void * RGB_rotate_48_thread(void *arg)
 
16400
{
 
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;
 
16408
 
 
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;
 
16416
   
 
16417
   for (py2 = index; py2 < hh2; py2 += NWthreads)                          //  loop through output pixels
 
16418
   for (px2 = 0; px2 < ww2; px2++)                                         //  outer loop y
 
16419
   {
 
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
 
16425
      if (rx2 < 0) {
 
16426
         if (theta < 0) theta = - pi - theta;                              //  adjust for quandrant
 
16427
         else theta = pi - theta;
 
16428
      }
 
16429
 
 
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;
 
16433
 
 
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
 
16438
 
 
16439
      px0 = int(px1);                                                      //  pixel containing (px1,py1)
 
16440
      py0 = int(py1);
 
16441
      
 
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;
 
16445
         continue;
 
16446
      }
 
16447
 
 
16448
      pix0 = ppix1 + (py0 * ww1 + px0) * 3;                                //  4 input pixels based at (px0,py0)
 
16449
      pix1 = pix0 + ww1 * 3;
 
16450
      pix2 = pix0 + 3;
 
16451
      pix3 = pix1 + 3;
 
16452
 
 
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);
 
16457
   
 
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];
 
16461
      
 
16462
      pix2 = ppix2 + (py2 * ww2 + px2) * 3;                                //  output pixel
 
16463
      pix2[0] = int(red);
 
16464
      pix2[1] = int(green);
 
16465
      pix2[2] = int(blue);
 
16466
   }
 
16467
   
 
16468
   zadd_locked(rotrgb48_busy,-1);
 
16469
   pthread_exit(0);
 
16470
}
 
16471
 
 
16472
 
 
16473