1
/**************************************************************************
3
Fotoxx edit photos and manage collections
5
Copyright 2007 2008 2009 2010 2011 Michael Cornelison
6
Source URL: http://kornelix.squarespace.com/fotoxx
7
Contact: kornelix2@googlemail.com
9
This program is free software: you can redistribute it and/or modify
10
it under the terms of the GNU General Public License as published by
11
the Free Software Foundation, either version 3 of the License, or
12
(at your option) any later version.
14
This program is distributed in the hope that it will be useful,
15
but WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
GNU General Public License for more details.
19
You should have received a copy of the GNU General Public License
20
along with this program. If not, see http://www.gnu.org/licenses/.
22
***************************************************************************/
24
#define EX extern // enable extern declarations
28
/**************************************************************************
30
Fotoxx image edit functions - composite functions
32
***************************************************************************/
35
// File scope variables and functions for composite images
36
// used by HDR, HDF, STP, STN, Panorama.
39
int cimNF; // image count, <= 10
40
char *cimFile[10]; // image files
41
PXM *cimPXMf[10]; // original images
42
PXM *cimPXMs[10]; // alignment images, scaled and curved (pano)
43
PXM *cimPXMw[10]; // alignment images, warped
45
struct cimoffs { // image alignment offsets
46
double xf, yf, tf; // x, y, theta offsets
47
double wx[4], wy[4]; // x/y corner warps, 0=NW, 1=NE, 2=SE, 3=SW
49
cimoffs cimOffs[10]; // image alignment data in E3 output image
51
double cimScale; // alignment image size relative to full image
52
double cimBlend; // image blend width at overlap, pixels (pano)
53
double cimSearchRange; // alignment search range, pixels
54
double cimSearchStep; // alignment search step, vpixels
55
double cimWarpRange; // alignment corner warp range, pixels
56
double cimWarpStep; // alignment corner warp step, vpixels
57
double cimSampSize; // pixel sample size
58
int cimOv1xlo, cimOv1xhi, cimOv1ylo, cimOv1yhi; // rectangle enclosing overlap area,
59
int cimOv2xlo, cimOv2xhi, cimOv2ylo, cimOv2yhi; // image 1 and image2 coordinates
60
double cimRGBmf1[3][65536]; // RGB matching factors for pixel comparisons:
61
double cimRGBmf2[3][65536]; // cimRGBmf1[*][pix1[*]] == cimRGBmf2[*][pix2[*]]
62
char *cimRedpix = 0; // maps high-contrast pixels for alignment
63
int cimRedImage; // which image has red pixels
64
int cimNsearch; // alignment search counter
65
int cimShowIm1, cimShowIm2; // two images for cim_show_images()
66
int cimShowAll; // if > 0, show all images
67
int cimShrink; // image shrinkage from pano image curving
68
int cimPano; // pano mode flag for cim_align_image()
69
int cimPanoV; // vertical pano flag
71
int cim_load_files(); // load and check selected files
72
void cim_scale_image(int im, PXM **); // scale image, 1.0 to cimScale (normally < 1)
73
double cim_get_overlap(int im1, int im2, PXM **); // get overlap area for images (horiz or vert)
74
void cim_match_colors(int im1, int im2, PXM **); // match image RGB levels >> match data
75
void cim_adjust_colors(PXM *pxm, int fwhich); // adjust RGB levels from match data
76
void cim_get_redpix(int im1); // find high-contrast pixels in overlap area
77
void cim_curve_image(int im); // curve cimPXMs[im] using lens parameters
78
void cim_curve_Vimage(int im); // vertical pano version
79
void cim_warp_image(int im); // warp image corners: cimPXMs[im] >> cimPXMw[im]
80
void cim_warp_image_pano(int im, int fblend); // pano version, all / left side / blend stripe
81
void cim_warp_image_Vpano(int im, int fblend); // vertical pans version: bottom side corners
82
void cim_align_image(int im1, int im2); // align image im2 to im1, modify im2 offsets
83
double cim_match_images(int im1, int im2); // compute match for overlapped images
84
void cim_show_images(int fnew, int fblend); // combine images >> E3pxm16 >> main window
85
void cim_show_Vimages(int fnew, int fblend); // vertical pano version
86
void cim_trim(); // cut-off edges where all images do not overlap
87
void cim_dump_offsets(cchar *text); // diagnostic tool
90
// load image file into pixmaps cimPXMf[*] and check for errors
93
int cim_load_files() // v.10.7
97
for (int imx = 0; imx < cimNF; imx++)
99
PXM_free(cimPXMf[imx]);
100
pxm = f_load(cimFile[imx],16); // will diagnose errors
104
PXM_fixblue(pxm); // blue=0 >> blue=2 for vpixel() v.11.07
111
// scale image from full size) to cimScale (normally < 1.0)
113
void cim_scale_image(int im, PXM** pxmout) // v.10.7
117
ww = cimScale * cimPXMf[im]->ww;
118
hh = cimScale * cimPXMf[im]->hh;
120
PXM_free(pxmout[im]);
121
pxmout[im] = PXM_rescale(cimPXMf[im],ww,hh);
123
PXM_fixblue(pxmout[im]); // blue=0 >> blue=2 for vpixel() v.11.07
129
// get overlap area for a pair of images im1 and im2
130
// outputs are coordinates of overlap area in im1 and in im2
131
// returns overlap width as fraction of image width <= 1.0
133
double cim_get_overlap(int im1, int im2, PXM **pxmx) // v.11.04
135
double x1, y1, t1, x2, y2, t2;
136
double xoff, yoff, toff, costf, sintf;
137
int ww1, ww2, hh1, hh2, pxM;
140
x1 = cimOffs[im1].xf; // im1, im2 absolute offsets
141
y1 = cimOffs[im1].yf;
142
t1 = cimOffs[im1].tf;
143
x2 = cimOffs[im2].xf;
144
y2 = cimOffs[im2].yf;
145
t2 = cimOffs[im2].tf;
147
xoff = (x2 - x1) * cos(t1) + (y2 - y1) * sin(t1); // offset of im2 relative to im1
148
yoff = (y2 - y1) * cos(t1) - (x2 - x1) * sin(t1);
162
cimOv1xlo = 0; // lowest x overlap
163
if (xoff > 0) cimOv1xlo = xoff;
165
cimOv1xhi = ww1-1; // highest x overlap
166
if (cimOv1xhi > xoff + ww2-1) cimOv1xhi = xoff + ww2-1;
168
cimOv1ylo = 0; // lowest y overlap
169
if (yoff > 0) cimOv1ylo = yoff;
171
cimOv1yhi = hh1-1; // highest y overlap
172
if (cimOv1yhi > yoff + hh2-1) cimOv1yhi = yoff + hh2-1;
174
if (toff < 0) cimOv1xlo -= toff * (cimOv1yhi - cimOv1ylo); // reduce for theta offset
175
if (toff < 0) cimOv1yhi += toff * (cimOv1xhi - cimOv1xlo);
176
if (toff > 0) cimOv1xhi -= toff * (cimOv1yhi - cimOv1ylo);
177
if (toff > 0) cimOv1ylo += toff * (cimOv1xhi - cimOv1xlo);
179
cimOv1xlo += cimShrink + 3; // account for void areas from
180
cimOv1xhi += - cimShrink - 3; // image shrinkage from
181
cimOv1ylo += cimShrink + 3; // cim_curve_image()
182
cimOv1yhi += - cimShrink - 3; // v.11.04
185
if (cimBlend && cimBlend < (cimOv1yhi - cimOv1ylo)) { // reduce y range to cimBlend v.11.04
186
pxM = (cimOv1yhi + cimOv1ylo) / 2;
187
cimOv1ylo = pxM - cimBlend / 2;
188
cimOv1yhi = pxM + cimBlend / 2;
192
if (cimBlend && cimBlend < (cimOv1xhi - cimOv1xlo)) { // reduce x range to cimBlend
193
pxM = (cimOv1xhi + cimOv1xlo) / 2;
194
cimOv1xlo = pxM - cimBlend / 2;
195
cimOv1xhi = pxM + cimBlend / 2;
199
cimOv2xlo = costf * (cimOv1xlo - xoff) + sintf * (cimOv1ylo - yoff); // overlap area in im2 coordinates
200
cimOv2xhi = costf * (cimOv1xhi - xoff) + sintf * (cimOv1yhi - yoff);
201
cimOv2ylo = costf * (cimOv1ylo - yoff) + sintf * (cimOv1xlo - xoff);
202
cimOv2yhi = costf * (cimOv1yhi - yoff) + sintf * (cimOv1xhi - xoff);
204
if (cimOv1xlo < 0) cimOv1xlo = 0; // take care of limits
205
if (cimOv1ylo < 0) cimOv1ylo = 0;
206
if (cimOv2xlo < 0) cimOv2xlo = 0;
207
if (cimOv2ylo < 0) cimOv2ylo = 0;
208
if (cimOv1xhi > ww1-1) cimOv1xhi = ww1-1;
209
if (cimOv1yhi > hh1-1) cimOv1yhi = hh1-1;
210
if (cimOv2xhi > ww2-1) cimOv2xhi = ww2-1;
211
if (cimOv2yhi > hh2-1) cimOv2yhi = hh2-1;
213
if (cimPanoV) return 1.0 * (cimOv1yhi - cimOv1ylo) / hh1; // return overlap height <= 1.0 v.11.04
214
else return 1.0 * (cimOv1xhi - cimOv1xlo) / ww1; // return overlap width <= 1.0 v.11.03
218
// Get the RGB brightness distribution in the overlap area for each image.
219
// Compute matching factors to compare pixels within the overlap area.
220
// compare cimRGBmf1[rgb][pix1[rgb]] to cimRGBmf2[rgb][pix2[rgb]]
222
void cim_match_colors(int im1, int im2, PXM **pxmx) // v.10.7
224
double Bratios1[3][256]; // image2/image1 brightness ratio per color per level
225
double Bratios2[3][256]; // image1/image2 brightness ratio per color per level
227
uint16 *pix1, vpix2[3];
228
int vstat2, px1, py1;
230
int npix, npix1, npix2, npix3;
231
int brdist1[3][256], brdist2[3][256];
232
double x1, y1, t1, x2, y2, t2;
233
double xoff, yoff, toff, costf, sintf;
235
double brlev1[3][256], brlev2[3][256];
236
double a1, a2, b1, b2, bratio = 1;
237
double r256 = 1.0 / 256.0;
243
x1 = cimOffs[im1].xf; // im1, im2 absolute offsets
244
y1 = cimOffs[im1].yf;
245
t1 = cimOffs[im1].tf;
246
x2 = cimOffs[im2].xf;
247
y2 = cimOffs[im2].yf;
248
t2 = cimOffs[im2].tf;
250
xoff = (x2 - x1) * cos(t1) + (y2 - y1) * sin(t1); // offset of im2 relative to im1
251
yoff = (y2 - y1) * cos(t1) - (x2 - x1) * sin(t1);
257
for (rgb = 0; rgb < 3; rgb++) // clear distributions
258
for (ii = 0; ii < 256; ii++)
259
brdist1[rgb][ii] = brdist2[rgb][ii] = 0;
263
for (py1 = cimOv1ylo; py1 < cimOv1yhi; py1++) // loop overlapped rows
264
for (px1 = cimOv1xlo; px1 < cimOv1xhi; px1++) // loop overlapped columns
266
pix1 = PXMpix(pxm1,px1,py1); // image1 pixel
267
if (! pix1[2]) continue; // ignore void pixels
269
px2 = costf * (px1 - xoff) + sintf * (py1 - yoff); // corresponding image2 pixel
270
py2 = costf * (py1 - yoff) - sintf * (px1 - xoff);
271
vstat2 = vpixel(pxm2,px2,py2,vpix2);
272
if (! vstat2) continue; // does not exist
274
++npix; // count overlapping pixels
276
for (rgb = 0; rgb < 3; rgb++) // accumulate distributions
277
{ // by color in 256 bins
278
++brdist1[rgb][int(r256*pix1[rgb])];
279
++brdist2[rgb][int(r256*vpix2[rgb])];
283
npix1 = npix / 256; // 1/256th of total pixels
285
for (rgb = 0; rgb < 3; rgb++) // get brlev1[rgb][N] = mean bright
286
for (ii = jj = 0; jj < 256; jj++) // for Nth group of image1 pixels
289
npix2 = npix1; // 1/256th of total pixels
291
while (npix2 > 0 && ii < 256) // next 1/256th group from distr,
293
npix3 = brdist1[rgb][ii];
294
if (npix3 == 0) { ++ii; continue; }
295
if (npix3 > npix2) npix3 = npix2;
296
brlev1[rgb][jj] += ii * npix3; // brightness * (pixels with)
297
brdist1[rgb][ii] -= npix3;
301
brlev1[rgb][jj] = brlev1[rgb][jj] / npix1; // mean brightness for group, 0-255
304
for (rgb = 0; rgb < 3; rgb++) // do same for image2
305
for (ii = jj = 0; jj < 256; jj++)
310
while (npix2 > 0 && ii < 256)
312
npix3 = brdist2[rgb][ii];
313
if (npix3 == 0) { ++ii; continue; }
314
if (npix3 > npix2) npix3 = npix2;
315
brlev2[rgb][jj] += ii * npix3;
316
brdist2[rgb][ii] -= npix3;
320
brlev2[rgb][jj] = brlev2[rgb][jj] / npix1;
323
for (rgb = 0; rgb < 3; rgb++) // color
324
for (ii = jj = 0; ii < 256; ii++) // brlev1 brightness, 0 to 255
326
if (ii == 0) bratio = 1;
327
while (ii > brlev2[rgb][jj] && jj < 256) ++jj; // find matching brlev2 brightness
328
a2 = brlev2[rgb][jj]; // next higher value
329
b2 = brlev1[rgb][jj];
330
if (a2 > 0 && b2 > 0) {
332
a1 = brlev2[rgb][jj-1]; // next lower value
333
b1 = brlev1[rgb][jj-1];
336
if (ii == 0) bratio = b2 / a2;
337
else bratio = (b1 + (ii-a1)/(a2-a1) * (b2-b1)) / ii; // interpolate
340
if (bratio < 0.2) bratio = 0.2; // contain outliers
341
if (bratio > 5) bratio = 5;
342
Bratios2[rgb][ii] = bratio;
345
for (rgb = 0; rgb < 3; rgb++) // color
346
for (ii = jj = 0; ii < 256; ii++) // brlev2 brightness, 0 to 255
348
if (ii == 0) bratio = 1;
349
while (ii > brlev1[rgb][jj] && jj < 256) ++jj; // find matching brlev1 brightness
350
a2 = brlev1[rgb][jj]; // next higher value
351
b2 = brlev2[rgb][jj];
352
if (a2 > 0 && b2 > 0) {
354
a1 = brlev1[rgb][jj-1]; // next lower value
355
b1 = brlev2[rgb][jj-1];
358
if (ii == 0) bratio = b2 / a2;
359
else bratio = (b1 + (ii-a1)/(a2-a1) * (b2-b1)) / ii; // interpolate
362
if (bratio < 0.2) bratio = 0.2; // contain outliers
363
if (bratio > 5) bratio = 5;
364
Bratios1[rgb][ii] = bratio;
367
for (ii = 0; ii < 65536; ii++) // convert brightness ratios into
368
{ // conversion factors
371
for (rgb = 0; rgb < 3; rgb++)
373
cimRGBmf1[rgb][ii] = sqrt(Bratios1[rgb][jj]) * ii; // use sqrt(ratio) so that adjustment
374
cimRGBmf2[rgb][ii] = sqrt(Bratios2[rgb][jj]) * ii; // can be applied to both images
382
// Use color match data from cim_match_colors() to
383
// modify images so the colors match.
385
void cim_adjust_colors(PXM *pxm, int fwhich) // v.10.7
388
int red, green, blue, max;
395
for (py = 0; py < hh; py++)
396
for (px = 0; px < ww; px++)
398
pix = PXMpix(pxm,px,py);
402
if (! blue) continue;
405
red = cimRGBmf1[0][red];
406
green = cimRGBmf1[1][green];
407
blue = cimRGBmf1[2][blue];
411
red = cimRGBmf2[0][red];
412
green = cimRGBmf2[1][green];
413
blue = cimRGBmf2[2][blue];
416
if (red > 65535 || green > 65535 || blue > 65535) {
418
if (green > max) max = green;
419
if (blue > max) max = blue;
426
if (! blue) blue = 1; // avoid 0 v.10.7
437
// find pixels of greatest contrast within overlap area
438
// flag high-contrast pixels to use in each image compare region
440
void cim_get_redpix(int im1) // v.10.7
442
int ww, hh, samp, xzone, yzone;
443
int pxL, pxH, pyL, pyH;
444
int px, py, ii, jj, npix;
445
int red1, green1, blue1, red2, green2, blue2, tcon;
446
int ov1xlo, ov1xhi, ov1ylo, ov1yhi;
447
int Hdist[256], Vdist[256], Hmin, Vmin;
448
double s8 = 1.0 / 770.0;
449
double zsamp[16] = { 4,6,6,4,6,9,9,6,6,9,9,6,4,6,6,4 }; // % sample per zone, sum = 100
454
pxm = cimPXMs[im1]; // v.11.04
458
if (cimRedpix) zfree(cimRedpix); // clear prior
459
cimRedpix = zmalloc(ww*hh,"cimRedpix");
460
memset(cimRedpix,0,ww*hh);
462
cimRedImage = im1; // image with red pixels
464
ov1xlo = cimOv1xlo + cimSearchRange; // stay within x/y search range
465
ov1xhi = cimOv1xhi - cimSearchRange; // so that red pixels persist
466
ov1ylo = cimOv1ylo + cimSearchRange; // over offset changes
467
ov1yhi = cimOv1yhi - cimSearchRange;
469
for (yzone = 0; yzone < 4; yzone++) // loop 16 zones v.10.8
470
for (xzone = 0; xzone < 4; xzone++)
472
pxL = ov1xlo + 0.25 * xzone * (ov1xhi - ov1xlo); // px and py zone limits
473
pxH = ov1xlo + 0.25 * (xzone+1) * (ov1xhi - ov1xlo);
474
pyL = ov1ylo + 0.25 * yzone * (ov1yhi - ov1ylo);
475
pyH = ov1ylo + 0.25 * (yzone+1) * (ov1yhi - ov1ylo);
477
npix = (pxH - pxL) * (pyH - pyL); // zone pixels
478
Hcon = (uchar *) zmalloc(npix,"cimRedpix"); // horizontal pixel contrast 0-255
479
Vcon = (uchar *) zmalloc(npix,"cimRedpix"); // vertical pixel contrast 0-255
481
ii = 4 * yzone + xzone;
482
samp = cimSampSize * 0.01 * zsamp[ii]; // sample size for zone
483
if (samp > 0.1 * npix) samp = 0.1 * npix; // limit to 10% of zone pixels
485
for (py = pyL; py < pyH; py++) // scan image pixels in zone
486
for (px = pxL; px < pxH; px++)
488
ii = (py-pyL) * (pxH-pxL) + (px-pxL);
489
Hcon[ii] = Vcon[ii] = 0; // horiz. = vert. contrast = 0
491
if (py < 8 || py > hh-9) continue; // keep away from image edges
492
if (px < 8 || px > ww-9) continue;
494
pix1 = PXMpix(pxm,px,py-6); // verify not near void areas
495
if (! pix1[2]) continue;
496
pix1 = PXMpix(pxm,px+6,py);
497
if (! pix1[2]) continue;
498
pix1 = PXMpix(pxm,px,py+6);
499
if (! pix1[2]) continue;
500
pix1 = PXMpix(pxm,px-6,py);
501
if (! pix1[2]) continue;
503
pix1 = PXMpix(pxm,px,py); // candidate red pixel
508
pix2 = PXMpix(pxm,px+2,py); // 2 pixels to right
513
tcon = abs(red1-red2) + abs(green1-green2) + abs(blue1-blue2); // horizontal contrast
514
Hcon[ii] = int(tcon * s8); // scale 0 - 255
516
pix2 = PXMpix(pxm,px,py+2); // 2 pixels below
521
tcon = abs(red1-red2) + abs(green1-green2) + abs(blue1-blue2); // vertical contrast
522
Vcon[ii] = int(tcon * s8);
525
for (ii = 0; ii < 256; ii++) Hdist[ii] = Vdist[ii] = 0; // clear contrast distributions
527
for (py = pyL; py < pyH; py++) // scan image pixels
528
for (px = pxL; px < pxH; px++)
529
{ // build contrast distributions
530
ii = (py-pyL) * (pxH-pxL) + (px-pxL);
535
for (npix = 0, ii = 255; ii > 0; ii--) // find minimum contrast needed to get
536
{ // enough pixels for sample size
537
npix += Hdist[ii]; // (horizontal contrast pixels)
538
if (npix > samp) break;
542
for (npix = 0, ii = 255; ii > 0; ii--) // (verticle contrast pixels)
545
if (npix > samp) break;
549
for (py = pyL; py < pyH; py++) // scan zone pixels
550
for (px = pxL; px < pxH; px++)
552
ii = (py-pyL) * (pxH-pxL) + (px-pxL);
554
if (Hcon[ii] > Hmin) cimRedpix[jj] = 1; // flag pixels above min. contrast
555
if (Vcon[ii] > Vmin) cimRedpix[jj] = 1;
561
for (py = pyL; py < pyH; py++) // scan zone pixels
562
for (px = pxL; px < pxH; px++)
564
ii = (py-pyL) * (pxH-pxL) + (px-pxL);
566
if (! cimRedpix[jj]) continue;
567
npix = cimRedpix[jj-1] + cimRedpix[jj+1]; // eliminate flagged pixels with no
568
npix += cimRedpix[jj-ww] + cimRedpix[jj+ww]; // neighboring flagged pixels
569
npix += cimRedpix[jj-ww-1] + cimRedpix[jj+ww-1]; // v.11.03
570
npix += cimRedpix[jj-ww+1] + cimRedpix[jj+ww+1];
571
if (npix < 2) cimRedpix[jj] = 0;
574
for (py = pyL; py < pyH; py++) // scan zone pixels
575
for (px = pxL; px < pxH; px++)
577
ii = (py-pyL) * (pxH-pxL) + (px-pxL);
580
if (cimRedpix[jj] == 1) { // flag horizontal group of 3
583
cimRedpix[jj+ww] = 2; // and vertical group of 3
584
cimRedpix[jj+2*ww] = 2;
593
// curve image based on lens parameters (pano)
594
// replaces cimPXMs[im] with curved version
596
void cim_curve_image(int im) // overhauled v.11.03
598
int px, py, ww, hh, vstat;
601
double F = lens_mm; // lens focal length, 35mm equivalent
602
double S = 35.0; // corresponding image width
603
double R1, R2, G, T, bow;
605
uint16 vpix[3], *pix;
607
pxmin = cimPXMs[im]; // input and output image
608
ww = pxmin->ww; // 200
610
ww2 = 0.5 * ww; // 100
613
if (hh > ww) S = S * ww / hh; // vertical format
614
F = F / S; // 28 / 35 // scale to image dimensions
617
R1 = F; // cylinder tangent to image plane
619
bow = -lens_bow * 0.01 / hh2 / hh2; // lens bow % to fraction
621
bow = -lens_bow * 0.01 / ww2 / ww2;
623
pxmout = PXM_make(ww,hh,16); // temp. output PXM
625
for (py = 0; py < hh; py++) // cylindrical projection v.11.03
626
for (px = 0; px < ww; px++)
632
R2 = sqrt(dx * dx + F * F);
634
dy = (dy * R2) / (R2 + G);
635
dx += bow * dx * dy * dy; // barrel distortion
638
vstat = vpixel(pxmin,dx,dy,vpix); // input virtual pixel
639
pix = PXMpix(pxmout,px,py); // output real pixel
645
else pix[0] = pix[1] = pix[2] = 0; // voided pixels are (0,0,0)
648
for (px = 1; px < ww2; px++) { // compute image shrinkage
649
pix = PXMpix(pxmout,px,hh/2);
652
cimShrink = px-1; // = 0 if no curvature
654
PXM_free(pxmin); // replace input with output PXM
655
cimPXMs[im] = pxmout;
661
// version for vertical panorama
663
void cim_curve_Vimage(int im) // v.11.04
665
int px, py, ww, hh, vstat;
668
double F = lens_mm; // lens focal length, 35mm equivalent
669
double S = 35.0; // corresponding image width
670
double R1, R2, G, T, bow;
672
uint16 vpix[3], *pix;
674
pxmin = cimPXMs[im]; // input and output image
675
ww = pxmin->ww; // 200
677
ww2 = 0.5 * ww; // 100
680
if (hh > ww) S = S * ww / hh; // vertical format
681
F = F / S; // 28 / 35 // scale to image dimensions
684
R1 = F; // cylinder tangent to image plane
686
bow = -lens_bow * 0.01 / hh2 / hh2; // lens bow % to fraction
688
bow = -lens_bow * 0.01 / ww2 / ww2;
690
pxmout = PXM_make(ww,hh,16); // temp. output PXM
692
for (py = 0; py < hh; py++) // cylindrical projection v.11.03
693
for (px = 0; px < ww; px++)
699
R2 = sqrt(dy * dy + F * F);
701
dx = (dx * R2) / (R2 + G);
702
dy += bow * dy * dx * dx; // barrel distortion
705
vstat = vpixel(pxmin,dx,dy,vpix); // input virtual pixel
706
pix = PXMpix(pxmout,px,py); // output real pixel
712
else pix[0] = pix[1] = pix[2] = 0; // voided pixels are (0,0,0)
715
for (py = 1; py < hh2; py++) { // compute image shrinkage
716
pix = PXMpix(pxmout,ww/2,py);
719
cimShrink = py-1; // = 0 if no curvature
721
PXM_free(pxmin); // replace input with output PXM
722
cimPXMs[im] = pxmout;
728
// Warp 4 image corners according to cimOffs[im].wx[ii] and .wy[ii]
729
// corner = 0 = NW, 1 = NE, 2 = SE, 3 = SW
730
// 4 corners move by these pixel amounts and center does not move.
731
// input: cimPXMs[im] (flat or curved) output: cimPXMw[im]
733
namespace cim_warp_image_names {
735
double ww, hh, wwi, hhi;
736
double wx0, wy0, wx1, wy1, wx2, wy2, wx3, wy3;
739
void cim_warp_image(int im) // caller function v.10.8
741
using namespace cim_warp_image_names;
743
void * cim_warp_image_wthread(void *arg);
745
pxmin = cimPXMs[im]; // input and output pixmaps
746
pxmout = cimPXMw[im];
748
PXM_free(pxmout); // v.11.04
749
pxmout = PXM_copy(pxmin);
750
cimPXMw[im] = pxmout;
757
wx0 = cimOffs[im].wx[0]; // corner warps
758
wy0 = cimOffs[im].wy[0];
759
wx1 = cimOffs[im].wx[1];
760
wy1 = cimOffs[im].wy[1];
761
wx2 = cimOffs[im].wx[2];
762
wy2 = cimOffs[im].wy[2];
763
wx3 = cimOffs[im].wx[3];
764
wy3 = cimOffs[im].wy[3];
766
for (int ii = 0; ii < Nwt; ii++) // start worker threads
767
start_wthread(cim_warp_image_wthread,&wtnx[ii]);
768
wait_wthreads(); // wait for completion
774
void * cim_warp_image_wthread(void *arg) // worker thread function v.10.8
776
using namespace cim_warp_image_names;
778
int index = *((int *) arg);
780
uint16 vpix[3], *pixm;
781
double px, py, dx, dy, coeff;
783
for (pym = index; pym < hh; pym += Nwt) // loop all pixels for this thread
784
for (pxm = 0; pxm < ww; pxm++)
788
coeff = (1.0 - pym * hhi - pxm * wwi); // corner 0 NW
793
coeff = (1.0 - pym * hhi - (ww - pxm) * wwi); // corner 1 NE
798
coeff = (1.0 - (hh - pym) * hhi - (ww - pxm) * wwi); // corner 2 SE
803
coeff = (1.0 - (hh - pym) * hhi - pxm * wwi); // corner 3 SW
809
px = pxm + dx; // source pixel location
812
vstat = vpixel(pxmin,px,py,vpix); // input virtual pixel
813
pixm = PXMpix(pxmout,pxm,pym); // output real pixel
820
else pixm[0] = pixm[1] = pixm[2] = 0;
824
return 0; // not executed, avoid gcc warning
828
// warp image for pano, left side corners only, reduced warp range
829
// input: cimPXMs[im] (curved)
830
// output: cimPXMw[im] (warped)
831
// fblend: 0 = process entire image
832
// 1 = process left half only
833
// 2 = process blend stripe only
835
void cim_warp_image_pano(int im, int fblend) // v.10.8
837
int ww, hh, ww2, hh2, pxL, pxH;
839
uint16 vpix[3], *pixm;
840
double ww2i, hh2i, pxs, pys, xdisp, ydisp;
841
double wx0, wy0, wx3, wy3;
844
pxmin = cimPXMs[im]; // input and output pixmaps
845
pxmout = cimPXMw[im];
847
PXM_free(pxmout); // v.11.04
848
pxmout = PXM_copy(pxmin);
849
cimPXMw[im] = pxmout;
857
ww2i = 1.0 / ww2; // v.10.8
860
wx0 = cimOffs[im].wx[0]; // NW corner warp
861
wy0 = cimOffs[im].wy[0];
863
wx3 = cimOffs[im].wx[3]; // SW corner warp
864
wy3 = cimOffs[im].wy[3];
866
pxL = 0; // entire image v.10.8
869
if (fblend == 1) // left half
873
pxL = cimOv2xlo; // limit to overlap/blend width
877
for (pym = 0; pym < hh; pym++) // loop all output pixels
878
for (pxm = pxL; pxm < pxH; pxm++)
880
pixm = PXMpix(pxmout,pxm,pym); // output pixel
882
xdisp = (pxm - ww2) * ww2i; // -1 ... 0 ... +1
883
ydisp = (pym - hh2) * hh2i; // v.11.04
885
if (xdisp > 0) { // right half, no warp
889
else if (ydisp < 0) { // use NW corner warp
890
pxs = pxm + wx0 * xdisp * ydisp;
891
pys = pym + wy0 * xdisp * ydisp;
893
else { // use SW corner warp
894
pxs = pxm + wx3 * xdisp * ydisp;
895
pys = pym + wy3 * xdisp * ydisp;
898
vstat = vpixel(pxmin,pxs,pys,vpix); // input virtual pixel
905
else pixm[0] = pixm[1] = pixm[2] = 0;
908
for (pxm = 1; pxm < ww2; pxm++) { // compute image shrinkage
909
pixm = PXMpix(pxmout,pxm,hh2); // used by cim_get_overlap()
918
// vertical pano version - warp top side corners (NW, NE)
920
void cim_warp_image_Vpano(int im, int fblend) // v.11.04
922
int ww, hh, ww2, hh2, pyL, pyH;
924
uint16 vpix[3], *pixm;
925
double ww2i, hh2i, pxs, pys, xdisp, ydisp;
926
double wx0, wy0, wx1, wy1;
929
pxmin = cimPXMs[im]; // input and output pixmaps
930
pxmout = cimPXMw[im];
932
PXM_free(pxmout); // v.11.04
933
pxmout = PXM_copy(pxmin);
934
cimPXMw[im] = pxmout;
942
ww2i = 1.0 / ww2; // v.10.8
945
wx0 = cimOffs[im].wx[0]; // NW corner warp
946
wy0 = cimOffs[im].wy[0];
948
wx1 = cimOffs[im].wx[1]; // NE corner warp
949
wy1 = cimOffs[im].wy[1];
951
pyL = 0; // entire image v.10.8
954
if (fblend == 1) // top half
958
pyL = cimOv2ylo; // limit to overlap/blend width
962
for (pym = pyL; pym < pyH; pym++) // loop all output pixels
963
for (pxm = 0; pxm < ww; pxm++)
965
pixm = PXMpix(pxmout,pxm,pym); // output pixel
967
xdisp = (pxm - ww2) * ww2i; // -1 ... 0 ... +1
968
ydisp = (pym - hh2) * hh2i;
970
if (ydisp > 0) { // bottom half, no warp
974
else if (xdisp < 0) { // use NW corner warp
975
pxs = pxm + wx0 * xdisp * ydisp;
976
pys = pym + wy0 * xdisp * ydisp;
978
else { // use NE corner warp
979
pxs = pxm + wx1 * xdisp * ydisp;
980
pys = pym + wy1 * xdisp * ydisp;
983
vstat = vpixel(pxmin,pxs,pys,vpix); // input virtual pixel
990
else pixm[0] = pixm[1] = pixm[2] = 0;
993
for (pym = 1; pym < hh2; pym++) { // compute image shrinkage
994
pixm = PXMpix(pxmout,ww2,pym); // used by cim_get_overlap()
1003
// fine-align a pair of images im1 and im2
1004
// cimPXMs[im2] is aligned with cimPXMs[im1]
1005
// inputs are cimOffs[im1] and cimOffs[im2] (x/y/t and corner offsets)
1006
// output is adjusted offsets and corner warp values for im2 only
1007
// (im1 is used as-is without corner warps)
1009
void cim_align_image(int im1, int im2) // speedup v.11.03
1011
int ii, corner1, cornerstep, cornerN, pass;
1012
double xyrange, xystep, trange, tstep, wrange, wstep;
1013
double xfL, xfH, yfL, yfH, tfL, tfH;
1014
double wxL, wxH, wyL, wyH;
1015
double match, matchB;
1016
cimoffs offsets0, offsetsB;
1018
offsets0 = cimOffs[im2]; // initial offsets
1019
offsetsB = offsets0; // = best offsets so far
1020
matchB = cim_match_images(im1,im2); // = best image match level
1022
if (cimPanoV) cim_show_Vimages(0,0); // v.11.04
1023
else cim_show_images(0,0); // show with 50/50 blend
1025
for (pass = 1; pass <=2; pass++) // main pass and 2nd pass v.10.8
1027
xyrange = cimSearchRange; // x/y search range and step
1028
xystep = cimSearchStep;
1030
trange = xyrange / (cimOv1yhi - cimOv1ylo); // angle range, radians
1031
tstep = trange * xystep / xyrange;
1034
xyrange = 0.5 * xyrange; // 2nd pass, reduce range and step
1035
xystep = 0.5 * xystep; // v.11.04
1036
trange = 0.5 * trange;
1037
tstep = 0.5 * tstep;
1040
// search x/y/t range for best match
1042
xfL = cimOffs[im2].xf - xyrange;
1043
xfH = cimOffs[im2].xf + xyrange + 0.5 * xystep;
1044
yfL = cimOffs[im2].yf - xyrange;
1045
yfH = cimOffs[im2].yf + xyrange + 0.5 * xystep;
1046
tfL = cimOffs[im2].tf - trange;
1047
tfH = cimOffs[im2].tf + trange + 0.5 * tstep;
1049
for (cimOffs[im2].xf = xfL; cimOffs[im2].xf < xfH; cimOffs[im2].xf += xystep)
1050
for (cimOffs[im2].yf = yfL; cimOffs[im2].yf < yfH; cimOffs[im2].yf += xystep)
1051
for (cimOffs[im2].tf = tfL; cimOffs[im2].tf < tfH; cimOffs[im2].tf += tstep)
1053
match = cim_match_images(im1,im2); // get match level
1055
if (sigdiff(match,matchB,0.00001) > 0) {
1056
matchB = match; // save best match
1057
offsetsB = cimOffs[im2];
1060
sprintf(SB_text,"align: %d match: %.5f",cimNsearch++,matchB); // update status bar
1061
zmainloop(); // v.11.11.1
1064
cimOffs[im2] = offsetsB; // restore best match
1066
if (cimPanoV) cim_show_Vimages(0,0); // v.11.04
1067
else cim_show_images(0,0);
1069
// warp corners and search for best match
1071
wrange = cimWarpRange; // corner warp range and step
1072
wstep = cimWarpStep;
1073
if (! wrange) continue;
1075
if (pass == 2) { // 2nd pass, 1/4 range and 1/2 step
1076
wrange = wrange / 4;
1080
corner1 = 0; // process all 4 corners
1085
corner1 = 0; // left side corners 0, 3
1091
corner1 = 0; // top side corners 0, 1 v.11.04
1096
matchB = cim_match_images(im1,im2); // initial image match level
1098
for (ii = corner1; ii <= cornerN; ii += cornerstep) // modify one corner at a time
1100
wxL = cimOffs[im2].wx[ii] - wrange;
1101
wxH = cimOffs[im2].wx[ii] + wrange + 0.5 * wstep;
1102
wyL = cimOffs[im2].wy[ii] - wrange;
1103
wyH = cimOffs[im2].wy[ii] + wrange + 0.5 * wstep;
1105
for (cimOffs[im2].wx[ii] = wxL; cimOffs[im2].wx[ii] < wxH; cimOffs[im2].wx[ii] += wstep)
1106
for (cimOffs[im2].wy[ii] = wyL; cimOffs[im2].wy[ii] < wyH; cimOffs[im2].wy[ii] += wstep)
1108
match = cim_match_images(im1,im2); // get match level
1110
if (sigdiff(match,matchB,0.00001) > 0) {
1111
matchB = match; // save best match
1112
offsetsB = cimOffs[im2];
1115
sprintf(SB_text,"warp: %d match: %.5f",cimNsearch++,matchB);
1116
zmainloop(); // v.11.11.1
1119
cimOffs[im2] = offsetsB; // restore best match
1122
if (cimPano) cim_warp_image_pano(im2,1); // apply corner warps v.11.04
1123
else if (cimPanoV) cim_warp_image_Vpano(im2,1);
1124
else cim_warp_image(im2);
1126
if (cimPanoV) cim_show_Vimages(0,0); // v.11.04
1127
else cim_show_images(0,0);
1134
// Compare 2 pixels using precalculated brightness ratios
1135
// 1.0 = perfect match 0 = total mismatch (black/white)
1137
inline double cim_match_pixels(uint16 *pix1, uint16 *pix2) // v.10.7
1139
double red1, green1, blue1, red2, green2, blue2;
1140
double reddiff, greendiff, bluediff, match;
1141
double ff = 1.0 / 65536.0;
1151
reddiff = ff * fabs(red1-red2); // 0 = perfect match
1152
greendiff = ff * fabs(green1-green2); // 1 = total mismatch
1153
bluediff = ff * fabs(blue1-blue2);
1155
match = (1.0 - reddiff) * (1.0 - greendiff) * (1.0 - bluediff); // 1 = perfect match
1160
// Compare two images in overlapping areas.
1161
// Use the high-contrast pixels from cim_get_redpix()
1162
// return: 1 = perfect match, 0 = total mismatch (black/white)
1163
// cimPXMs[im1] is matched to cimPXMs[im2] + virtual warps
1165
double cim_match_images(int im1, int im2) // v.11.03
1167
uint16 *pix1, vpix2[3];
1168
int ww, hh, ww2, hh2;
1169
int px1, py1, ii, vstat;
1170
double wwi, hhi, ww2i, hh2i, xdisp, ydisp;
1171
double wx0, wy0, wx1, wy1, wx2, wy2, wx3, wy3;
1172
double dx, dy, px2, py2;
1173
double x1, y1, t1, x2, y2, t2;
1174
double xoff, yoff, toff, costf, sintf, coeff;
1175
double match, cmatch, maxcmatch;
1178
x1 = cimOffs[im1].xf; // im1, im2 absolute offsets
1179
y1 = cimOffs[im1].yf;
1180
t1 = cimOffs[im1].tf;
1181
x2 = cimOffs[im2].xf;
1182
y2 = cimOffs[im2].yf;
1183
t2 = cimOffs[im2].tf;
1185
xoff = (x2 - x1) * cos(t1) + (y2 - y1) * sin(t1); // offset of im2 relative to im1
1186
yoff = (y2 - y1) * cos(t1) - (x2 - x1) * sin(t1);
1192
wx0 = cimOffs[im2].wx[0]; // im2 corner warps
1193
wy0 = cimOffs[im2].wy[0];
1194
wx1 = cimOffs[im2].wx[1];
1195
wy1 = cimOffs[im2].wy[1];
1196
wx2 = cimOffs[im2].wx[2];
1197
wy2 = cimOffs[im2].wy[2];
1198
wx3 = cimOffs[im2].wx[3];
1199
wy3 = cimOffs[im2].wy[3];
1201
pxm1 = cimPXMs[im1]; // base image
1202
pxm2 = cimPXMs[im2]; // comparison image (virtual warps)
1219
for (py1 = cimOv1ylo; py1 < cimOv1yhi; py1++) // loop overlapping pixels
1220
for (px1 = cimOv1xlo; px1 < cimOv1xhi; px1++)
1222
ii = py1 * ww + px1; // skip low-contrast pixels
1223
if (! cimRedpix[ii]) continue;
1225
pix1 = PXMpix(pxm1,px1,py1); // image1 pixel
1226
if (! pix1[2]) continue; // ignore void pixels
1228
px2 = costf * (px1 - xoff) + sintf * (py1 - yoff); // corresponding image2 pixel
1229
py2 = costf * (py1 - yoff) - sintf * (px1 - xoff);
1231
dx = dy = 0.0; // corner warp
1233
xdisp = (px2 - ww2) * ww2i; // -1 ... 0 ... +1
1234
ydisp = (py2 - hh2) * hh2i; // v.11.04
1236
if (xdisp > 0) // right half, no warp
1239
else if (ydisp < 0) { // use NW corner warp
1240
dx = wx0 * xdisp * ydisp;
1241
dy = wy0 * xdisp * ydisp;
1244
else { // use SW corner warp
1245
dx = wx3 * xdisp * ydisp;
1246
dy = wy3 * xdisp * ydisp;
1249
px2 += dx; // source pixel location
1250
py2 += dy; // after corner warps
1252
vstat = vpixel(pxm2,px2,py2,vpix2);
1253
if (! vstat) continue;
1255
match = cim_match_pixels(pix1,vpix2); // compare brightness adjusted
1256
cmatch += match; // accumulate total match
1263
for (py1 = cimOv1ylo; py1 < cimOv1yhi; py1++) // loop overlapping pixels
1264
for (px1 = cimOv1xlo; px1 < cimOv1xhi; px1++)
1266
ii = py1 * ww + px1; // skip low-contrast pixels
1267
if (! cimRedpix[ii]) continue;
1269
pix1 = PXMpix(pxm1,px1,py1); // image1 pixel
1270
if (! pix1[2]) continue; // ignore void pixels
1272
px2 = costf * (px1 - xoff) + sintf * (py1 - yoff); // corresponding image2 pixel
1273
py2 = costf * (py1 - yoff) - sintf * (px1 - xoff);
1275
dx = dy = 0.0; // corner warp
1277
xdisp = (px2 - ww2) * ww2i; // -1 ... 0 ... +1
1278
ydisp = (py2 - hh2) * hh2i;
1280
if (ydisp > 0) // bottom half, no warp
1283
else if (xdisp < 0) { // use NW corner warp
1284
dx = wx0 * xdisp * ydisp;
1285
dy = wy0 * xdisp * ydisp;
1288
else { // use NE corner warp
1289
dx = wx1 * xdisp * ydisp;
1290
dy = wy1 * xdisp * ydisp;
1293
px2 += dx; // source pixel location
1294
py2 += dy; // after corner warps
1296
vstat = vpixel(pxm2,px2,py2,vpix2);
1297
if (! vstat) continue;
1299
match = cim_match_pixels(pix1,vpix2); // compare brightness adjusted
1300
cmatch += match; // accumulate total match
1307
for (py1 = cimOv1ylo; py1 < cimOv1yhi; py1++) // loop overlapping pixels
1308
for (px1 = cimOv1xlo; px1 < cimOv1xhi; px1++)
1310
ii = py1 * ww + px1; // skip low-contrast pixels
1311
if (! cimRedpix[ii]) continue;
1313
pix1 = PXMpix(pxm1,px1,py1); // image1 pixel
1314
if (! pix1[2]) continue; // ignore void pixels
1316
px2 = costf * (px1 - xoff) + sintf * (py1 - yoff); // corresponding image2 pixel
1317
py2 = costf * (py1 - yoff) - sintf * (px1 - xoff);
1319
dx = dy = 0.0; // corner warp
1321
coeff = (1.0 - py2 * hhi - px2 * wwi); // corner 0 NW
1326
coeff = (1.0 - py2 * hhi - (ww - px2) * wwi); // corner 1 NE
1331
coeff = (1.0 - (hh - py2) * hhi - (ww - px2) * wwi); // corner 2 SE
1336
coeff = (1.0 - (hh - py2) * hhi - px2 * wwi); // corner 3 SW
1342
px2 += dx; // source pixel location
1343
py2 += dy; // after corner warps
1345
vstat = vpixel(pxm2,px2,py2,vpix2);
1346
if (! vstat) continue;
1348
match = cim_match_pixels(pix1,vpix2); // compare brightness adjusted
1349
cmatch += match; // accumulate total match
1354
return cmatch / maxcmatch;
1358
// combine and show all images
1359
// fnew >> make new E3 output image and adjust x and y offsets
1360
// cimPXMw[*] >> E3pxm16 >> main window
1361
// fblend: 0 > 50/50 blend, 1 > gradual blend
1363
namespace cim_show_images_names {
1364
int im1, im2, iminc, fblendd;
1365
int wwlo[10], wwhi[10];
1366
int hhlo[10], hhhi[10];
1367
double costf[10], sintf[10];
1370
void cim_show_images(int fnew, int fblend) // v.10.7
1372
using namespace cim_show_images_names;
1374
void * cim_show_images_wthread(void *arg);
1376
int imx, pxr, pyr, ii, px3, py3;
1377
int ww, hh, wwmin, wwmax, hhmin, hhmax, bmid;
1381
mutex_lock(&Fpixmap_lock); // stop window updates
1383
fblendd = fblend; // blend 50/50 or gradual ramp
1385
im1 = cimShowIm1; // two images to show
1387
iminc = im2 - im1; // v.10.9
1389
if (cimShowAll) { // show all images v.10.9
1395
for (imx = 0; imx < cimNF; imx++) { // pre-calculate
1396
costf[imx] = cos(cimOffs[imx].tf);
1397
sintf[imx] = sin(cimOffs[imx].tf);
1400
if (fnew) PXM_free(E3pxm16); // force new output pixmap
1402
if (! E3pxm16) // allocate output pixmap
1404
wwmin = hhmin = 9999; // initial values
1405
wwmax = cimPXMw[im2]->ww;
1406
hhmax = cimPXMw[im2]->hh;
1408
for (imx = im1; imx <= im2; imx += iminc) // find min and max ww and hh extents
1410
xf = cimOffs[imx].xf;
1411
yf = cimOffs[imx].yf;
1412
tf = cimOffs[imx].tf;
1413
ww = cimPXMw[imx]->ww;
1414
hh = cimPXMw[imx]->hh;
1415
if (xf < wwmin) wwmin = xf;
1416
if (xf - tf * hh < wwmin) wwmin = xf + tf * hh;
1417
if (xf + ww > wwmax) wwmax = xf + ww;
1418
if (xf + ww - tf * hh > wwmax) wwmax = xf + ww - tf * hh;
1419
if (yf < hhmin) hhmin = yf;
1420
if (yf + tf * ww < hhmin) hhmin = yf + tf * ww;
1421
if (yf + hh > hhmax) hhmax = yf + hh;
1422
if (yf + hh + tf * ww > hhmax) hhmax = yf + hh + tf * ww;
1425
for (imx = im1; imx <= im2; imx += iminc) { // align to top and left edges
1426
cimOffs[imx].xf -= wwmin;
1427
cimOffs[imx].yf -= hhmin;
1429
wwmax = wwmax - wwmin;
1430
hhmax = hhmax - hhmin;
1434
for (imx = im1; imx <= im2; imx += iminc) // deliberate margins v.11.03
1435
cimOffs[imx].yf += 10;
1440
for (imx = im1; imx <= im2; imx += iminc) // deliberate margins v.11.04
1441
cimOffs[imx].xf += 10;
1445
E3pxm16 = PXM_make(wwmax,hhmax,16); // allocate output image
1450
for (imx = im1; imx <= im2; imx += iminc) // get ww range of each image
1452
ww = cimPXMw[imx]->ww;
1453
hh = cimPXMw[imx]->hh;
1454
tf = cimOffs[imx].tf;
1455
wwlo[imx] = cimOffs[imx].xf;
1456
wwhi[imx] = wwlo[imx] + ww;
1457
wwlo[imx] -= 0.5 * tf * hh; // use midpoint of sloping edges
1458
wwhi[imx] -= 0.5 * tf * hh;
1461
if (cimBlend) { // blend width active
1462
for (imx = im1; imx <= im2-1; imx += iminc) // reduce for blend width
1464
if (wwhi[imx] - wwlo[imx+1] > cimBlend) {
1465
bmid = (wwhi[imx] + wwlo[imx+1]) / 2;
1466
wwlo[imx+1] = bmid - cimBlend / 2;
1467
wwhi[imx] = bmid + cimBlend / 2;
1472
for (ii = 0; ii < Nwt; ii++) // start worker threads v.10.7
1473
start_wthread(cim_show_images_wthread,&wtnx[ii]);
1474
wait_wthreads(); // wait for completion
1478
imx = cimRedImage; // paint red pixels for current image
1479
ww = cimPXMw[imx]->ww; // being aligned
1480
hh = cimPXMw[imx]->hh;
1482
for (ii = 0; ii < ww * hh; ii++)
1484
if (cimRedpix[ii]) {
1485
pyr = ii / ww; // red pixel
1486
pxr = ii - pyr * ww;
1487
px3 = cimOffs[imx].xf + pxr * costf[imx] - pyr * sintf[imx] + 0.5;
1488
py3 = cimOffs[imx].yf + pyr * costf[imx] + pxr * sintf[imx] + 0.5;
1489
pix3 = PXMpix(E3pxm16,px3,py3);
1490
pix3[0] = 65535; pix3[1] = pix3[2] = 1;
1495
mutex_unlock(&Fpixmap_lock);
1496
mwpaint2(); // update window
1497
zmainloop(); // v.11.11.1
1503
void * cim_show_images_wthread(void *arg) // working thread v.10.7
1505
using namespace cim_show_images_names;
1507
int index = *((int *) (arg));
1510
int vstat, vstat1, vstat2;
1511
int red1, green1, blue1;
1512
int red2, green2, blue2;
1513
int red3, green3, blue3;
1514
double f1, f2, px, py;
1515
uint16 vpix[3], *pix3;
1517
red1 = green1 = blue1 = 0;
1519
f1 = f2 = 0.5; // to use if no fblend flag
1521
for (py3 = index; py3 < E3hh; py3 += Nwt) // loop E3 rows
1522
for (px3 = 0; px3 < E3ww; px3++) // loop E3 columns
1524
vstat1 = vstat2 = 0;
1526
for (imx = imy = im1; imx <= im2; imx += iminc) // find which images overlap this pixel
1528
if (px3 < wwlo[imx] || px3 > wwhi[imx]) continue;
1529
px = costf[imx] * (px3 - cimOffs[imx].xf) + sintf[imx] * (py3 - cimOffs[imx].yf);
1530
py = costf[imx] * (py3 - cimOffs[imx].yf) - sintf[imx] * (px3 - cimOffs[imx].xf);
1531
vstat = vpixel(cimPXMw[imx],px,py,vpix);
1532
if (! vstat) continue;
1534
if (! vstat1) { // first overlapping image
1541
else { // second image
1550
imx = imy; // first of 1 or 2 overlapping images
1554
red3 = red1; // use image1 pixel
1558
else { // use blended image1 + image2 pixels
1560
f1 = wwhi[imx] - px3; // gradual blend
1561
f2 = px3 - wwlo[imx+1];
1562
f1 = f1 / (f1 + f2);
1565
red3 = f1 * red1 + f2 * red2 + 0.5;
1566
green3 = f1 * green1 + f2 * green2 + 0.5;
1567
blue3 = f1 * blue1 + f2 * blue2 + 0.5;
1571
else red3 = green3 = blue3 = 0; // no overlapping image, use black pixel
1573
pix3 = PXMpix(E3pxm16,px3,py3); // output pixel
1580
return 0; // not executed, avoid gcc warning
1584
// version for vertical panorama
1586
void cim_show_Vimages(int fnew, int fblend) // v.11.04
1588
using namespace cim_show_images_names;
1590
void * cim_show_Vimages_wthread(void *arg);
1592
int imx, pxr, pyr, ii, px3, py3;
1593
int ww, hh, wwmin, wwmax, hhmin, hhmax, bmid;
1597
mutex_lock(&Fpixmap_lock); // stop window updates
1599
fblendd = fblend; // blend 50/50 or gradual ramp
1601
im1 = 0; // show all images (pano)
1604
for (imx = 0; imx < cimNF; imx++) { // pre-calculate
1605
costf[imx] = cos(cimOffs[imx].tf);
1606
sintf[imx] = sin(cimOffs[imx].tf);
1609
if (fnew) PXM_free(E3pxm16); // force new output pixmap
1611
if (! E3pxm16) // allocate output pixmap
1613
wwmin = hhmin = 9999;
1616
for (imx = im1; imx <= im2; imx++) // find min and max ww and hh extents
1618
xf = cimOffs[imx].xf;
1619
yf = cimOffs[imx].yf;
1620
tf = cimOffs[imx].tf;
1621
ww = cimPXMw[imx]->ww;
1622
hh = cimPXMw[imx]->hh;
1623
if (xf < wwmin) wwmin = xf;
1624
if (xf - tf * hh < wwmin) wwmin = xf + tf * hh;
1625
if (xf + ww > wwmax) wwmax = xf + ww;
1626
if (xf + ww - tf * hh > wwmax) wwmax = xf + ww - tf * hh;
1627
if (yf < hhmin) hhmin = yf;
1628
if (yf + tf * ww < hhmin) hhmin = yf + tf * ww;
1629
if (yf + hh > hhmax) hhmax = yf + hh;
1630
if (yf + hh + tf * ww > hhmax) hhmax = yf + hh + tf * ww;
1633
for (imx = im1; imx <= im2; imx++) { // align to top and left edges
1634
cimOffs[imx].xf -= wwmin;
1635
cimOffs[imx].yf -= hhmin;
1637
wwmax = wwmax - wwmin;
1638
hhmax = hhmax - hhmin;
1641
for (imx = im1; imx <= im2; imx++) // deliberate margins
1642
cimOffs[imx].xf += 10;
1645
E3pxm16 = PXM_make(wwmax,hhmax,16); // allocate output image
1650
for (imx = im1; imx <= im2; imx++) // get hh range of each image
1652
ww = cimPXMw[imx]->ww;
1653
hh = cimPXMw[imx]->hh;
1654
tf = cimOffs[imx].tf;
1655
hhlo[imx] = cimOffs[imx].yf;
1656
hhhi[imx] = hhlo[imx] + hh;
1657
hhlo[imx] += 0.5 * tf * ww; // use midpoint of sloping edges
1658
hhhi[imx] += 0.5 * tf * ww;
1661
if (cimBlend) { // blend width active
1662
for (imx = im1; imx <= im2-1; imx++) // reduce for blend width
1664
if (hhhi[imx] - hhlo[imx+1] > cimBlend) {
1665
bmid = (hhhi[imx] + hhlo[imx+1]) / 2;
1666
hhlo[imx+1] = bmid - cimBlend / 2;
1667
hhhi[imx] = bmid + cimBlend / 2;
1672
for (ii = 0; ii < Nwt; ii++) // start worker threads v.10.7
1673
start_wthread(cim_show_Vimages_wthread,&wtnx[ii]);
1674
wait_wthreads(); // wait for completion
1678
imx = cimRedImage; // paint red pixels for current image
1679
ww = cimPXMw[imx]->ww; // being aligned
1680
hh = cimPXMw[imx]->hh;
1682
for (ii = 0; ii < ww * hh; ii++)
1684
if (cimRedpix[ii]) {
1685
pyr = ii / ww; // red pixel
1686
pxr = ii - pyr * ww;
1687
px3 = cimOffs[imx].xf + pxr * costf[imx] - pyr * sintf[imx] + 0.5;
1688
py3 = cimOffs[imx].yf + pyr * costf[imx] + pxr * sintf[imx] + 0.5;
1689
pix3 = PXMpix(E3pxm16,px3,py3);
1690
pix3[0] = 65535; pix3[1] = pix3[2] = 1;
1695
mutex_unlock(&Fpixmap_lock);
1696
mwpaint2(); // update window
1697
zmainloop(); // v.11.11.1
1702
void * cim_show_Vimages_wthread(void *arg) // working thread v.11.04
1704
using namespace cim_show_images_names;
1706
int index = *((int *) (arg));
1709
int vstat, vstat1, vstat2;
1710
int red1, green1, blue1;
1711
int red2, green2, blue2;
1712
int red3, green3, blue3;
1713
double f1, f2, px, py;
1714
uint16 vpix[3], *pix3;
1716
red1 = green1 = blue1 = 0;
1718
f1 = f2 = 0.5; // to use if no fblend flag
1720
for (py3 = index; py3 < E3hh; py3 += Nwt) // loop E3 rows
1721
for (px3 = 0; px3 < E3ww; px3++) // loop E3 columns
1723
vstat1 = vstat2 = 0;
1725
for (imx = imy = im1; imx <= im2; imx++) // find which images overlap this pixel
1727
if (py3 < hhlo[imx] || py3 > hhhi[imx]) continue;
1728
px = costf[imx] * (px3 - cimOffs[imx].xf) + sintf[imx] * (py3 - cimOffs[imx].yf);
1729
py = costf[imx] * (py3 - cimOffs[imx].yf) - sintf[imx] * (px3 - cimOffs[imx].xf);
1730
vstat = vpixel(cimPXMw[imx],px,py,vpix);
1731
if (! vstat) continue;
1733
if (! vstat1) { // first overlapping image
1740
else { // second image
1749
imx = imy; // first of 1 or 2 overlapping images
1753
red3 = red1; // use image1 pixel
1757
else { // use blended image1 + image2 pixels
1759
f1 = hhhi[imx] - py3; // gradual blend
1760
f2 = py3 - hhlo[imx+1];
1761
f1 = f1 / (f1 + f2);
1764
red3 = f1 * red1 + f2 * red2 + 0.5;
1765
green3 = f1 * green1 + f2 * green2 + 0.5;
1766
blue3 = f1 * blue1 + f2 * blue2 + 0.5;
1770
else red3 = green3 = blue3 = 0; // no overlapping image, use black pixel
1772
pix3 = PXMpix(E3pxm16,px3,py3); // output pixel
1779
return 0; // not executed, avoid gcc warning
1783
// cut-off edges of output image where all input images do not overlap
1786
void cim_trim() // v.10.9
1788
int edgex[8] = { 0, 1, 2, 2, 2, 1, 0, 0 }; // 4 corners and 4 midpoints of rectangle
1789
int edgey[8] = { 0, 0, 0, 1, 2, 2, 2, 1 }; // 0 and 2 mark corners, 1 marks midpoints
1790
int edgewx[4] = { +1, -1, -1, +1 };
1791
int edgewy[4] = { +1, +1, -1, -1 };
1793
int imx, ii, jj, ww, hh, px3, py3, px9, py9;
1794
int wwmin, wwmax, hhmin, hhmax;
1795
double xf, yf, tf, sintf, costf, px, py, wx, wy;
1796
uint16 *pix3, *pix9;
1802
for (imx = 0; imx < cimNF; imx++) // loop all images
1804
ww = cimPXMw[imx]->ww; // image size
1805
hh = cimPXMw[imx]->hh;
1806
xf = cimOffs[imx].xf; // alignment offsets
1807
yf = cimOffs[imx].yf;
1808
tf = cimOffs[imx].tf;
1812
for (ii = 0; ii < 8; ii++) // 8 points around image rectangle
1814
px = ww * edgex[ii] / 2; // coordinates before warping
1815
py = hh * edgey[ii] / 2;
1817
if (edgex[ii] != 1 && edgey[ii] != 1) { // if a corner
1819
wx = cimOffs[imx].wx[jj]; // corner warp
1820
wy = cimOffs[imx].wy[jj];
1821
if (edgewx[jj] > 0 && wx < 0) px -= wx; // if warp direction inwards,
1822
if (edgewx[jj] < 0 && wx > 0) px -= wx; // reduce px/py by warp
1823
if (edgewy[jj] > 0 && wy < 0) py -= wy;
1824
if (edgewy[jj] < 0 && wy > 0) py -= wy;
1827
px3 = xf + px * costf - py * sintf; // map px/py to output image px3/py3
1828
py3 = yf + py * costf + px * sintf;
1830
if (edgex[ii] != 1) {
1831
if (px3 < ww/2 && px3 > wwmin) wwmin = px3; // remember px3/py3 extremes
1832
if (px3 > ww/2 && px3 < wwmax) wwmax = px3;
1835
if (edgey[ii] != 1) {
1836
if (py3 < hh/2 && py3 > hhmin) hhmin = py3;
1837
if (py3 > hh/2 && py3 < hhmax) hhmax = py3;
1842
wwmin += 2; // compensate rounding
1847
ww = wwmax - wwmin; // new image size
1850
if (ww < 0.7 * E3ww) return; // sanity check
1851
if (hh < 0.7 * E3hh) return;
1853
E9pxm16 = PXM_make(ww,hh,16);
1855
for (py3 = hhmin; py3 < hhmax; py3++) // E9 = trimmed E3
1856
for (px3 = wwmin; px3 < wwmax; px3++)
1860
pix3 = PXMpix(E3pxm16,px3,py3);
1861
pix9 = PXMpix(E9pxm16,px9,py9);
1867
PXM_free(E3pxm16); // E3 = E9
1877
// dump offsets to stdout - diagnostic tool
1879
void cim_dump_offsets(cchar *text)
1881
printf("\n offsets: %s \n",text);
1883
for (int imx = 0; imx < cimNF; imx++)
1885
printf(" imx %d x/y/t: %.1f %.1f %.4f w0: %.1f %.1f w1: %.1f %.1f w2: %.1f %.1f w3: %.1f %.1f \n",
1886
imx, cimOffs[imx].xf, cimOffs[imx].yf, cimOffs[imx].tf,
1887
cimOffs[imx].wx[0], cimOffs[imx].wy[0], cimOffs[imx].wx[1], cimOffs[imx].wy[1],
1888
cimOffs[imx].wx[2], cimOffs[imx].wy[2], cimOffs[imx].wx[3], cimOffs[imx].wy[3]);
1895
/**************************************************************************
1897
Make an HDR (high dynamic range) image from several images of the same
1898
subject with different exposure levels. The composite image has better
1899
visibility of detail in both the brightest and darkest areas.
1901
***************************************************************************/
1903
int HDRstat; // 1 = OK, 0 = failed or canceled
1904
double HDRinitAlignSize = 160; // initial align image size
1905
double HDRimageIncrease = 1.6; // image size increase per align cycle
1906
double HDRsampSize = 6000; // pixel sample size 11.03
1908
double HDRinitSearchRange = 8.0; // initial search range, +/- pixels
1909
double HDRinitSearchStep = 1.0; // initial search step, pixels
1910
double HDRinitWarpRange = 3.0; // initial corner warp range, +/- pixels
1911
double HDRinitWarpStep = 0.67; // initial corner warp step, pixels
1912
double HDRsearchRange = 2.0; // normal search range, +/- pixels
1913
double HDRsearchStep = 0.67; // normal search step, pixels
1914
double HDRwarpRange = 2.0; // normal corner warp range, +/- pixels
1915
double HDRwarpStep = 0.67; // normal corner warp step, pixels
1917
float *HDRbright = 0; // maps brightness per pixel
1918
zdialog *HDRzd = 0; // tweak dialog
1919
double HDR_respfac[10][1000]; // contribution / image / pixel brightness
1921
void * HDR_align_thread(void *); // align 2 images
1922
void HDR_brightness(); // compute pixel brightness levels
1923
void HDR_tweak(); // adjust image contribution curves
1924
void * HDR_combine_thread(void *); // combine images per contribution curves
1926
editfunc EFhdr; // edit function data
1931
void m_HDR(GtkWidget *, cchar *) // v.10.7
1933
char **flist, *ftemp;
1934
int imx, jj, err, px, py, ww, hh;
1935
double diffw, diffh;
1936
double fbright[10], btemp;
1937
double pixsum, fnorm = 3.0 / 65536.0;
1941
zfuncs::F1_help_topic = "HDR"; // help topic
1943
if (mod_keep()) return; // warn unsaved changes
1944
if (! menulock(1)) return; // test menu lock v.11.07
1947
for (imx = 0; imx < 10; imx++)
1948
{ // clear all file and PXM data
1950
cimPXMf[imx] = cimPXMs[imx] = cimPXMw[imx] = 0;
1956
flist = zgetfileN(ZTX("Select 2 to 9 files"),"openN",curr_file); // select images to combine
1957
if (! flist) return;
1959
for (imx = 0; flist[imx]; imx++); // count selected files
1960
if (imx < 2 || imx > 9) {
1961
zmessageACK(mWin,ZTX("Select 2 to 9 files"));
1965
cimNF = imx; // file count
1966
for (imx = 0; imx < cimNF; imx++)
1967
cimFile[imx] = strdupz(flist[imx],0,"HDR"); // set up file list
1969
if (! cim_load_files()) goto cleanup; // load and check all files
1971
ww = cimPXMf[0]->ww;
1972
hh = cimPXMf[0]->hh;
1974
for (imx = 1; imx < cimNF; imx++) // check image compatibility
1976
diffw = abs(ww - cimPXMf[imx]->ww);
1978
diffh = abs(hh - cimPXMf[imx]->hh);
1981
if (diffw > 0.02 || diffh > 0.02) {
1982
zmessageACK(mWin,ZTX("Images are not all the same size"));
1987
free_resources(); // ready to commit
1989
err = f_open(cimFile[0],0); // curr_file = 1st file in list
1990
if (err) goto cleanup;
1992
EFhdr.funcname = "HDR";
1993
if (! edit_setup(EFhdr)) goto cleanup; // setup edit (will lock)
1995
for (imx = 0; imx < cimNF; imx++) // compute image brightness levels
1998
for (py = 0; py < Fhh; py++)
1999
for (px = 0; px < Fww; px++)
2001
pixel = PXMpix(cimPXMf[imx],px,py);
2002
pixsum += fnorm * (pixel[0] + pixel[1] + pixel[2]);
2004
fbright[imx] = pixsum / (Fww * Fhh);
2007
for (imx = 0; imx < cimNF; imx++) // sort file and pixmap lists
2008
for (jj = imx+1; jj < cimNF; jj++) // by decreasing brightness
2010
if (fbright[jj] > fbright[imx]) { // bubble sort
2011
btemp = fbright[jj];
2012
fbright[jj] = fbright[imx];
2013
fbright[imx] = btemp;
2014
ftemp = cimFile[jj];
2015
cimFile[jj] = cimFile[imx];
2016
cimFile[imx] = ftemp;
2017
pxmtemp = cimPXMf[jj];
2018
cimPXMf[jj] = cimPXMf[imx];
2019
cimPXMf[imx] = pxmtemp;
2023
start_thread(HDR_align_thread,0); // align each pair of images
2024
wrapup_thread(0); // wait for completion
2025
if (HDRstat != 1) goto cancel;
2027
HDR_brightness(); // compute pixel brightness levels
2028
if (HDRstat != 1) goto cancel;
2030
HDR_tweak(); // combine images based on user inputs
2031
if (HDRstat != 1) goto cancel;
2033
CEF->Fmod = 1; // done
2043
for (imx = 0; flist[imx]; imx++) // free file list
2048
for (imx = 0; imx < cimNF; imx++) { // free cim file and PXM data
2049
if (cimFile[imx]) zfree(cimFile[imx]);
2050
if (cimPXMf[imx]) PXM_free(cimPXMf[imx]);
2051
if (cimPXMs[imx]) PXM_free(cimPXMs[imx]);
2052
if (cimPXMw[imx]) PXM_free(cimPXMw[imx]);
2055
if (HDRbright) zfree(HDRbright);
2062
// HDR align each pair of input images, output combined image to E3pxm16
2063
// cimPXMf[*] original image
2064
// cimPXMs[*] scaled and color adjusted for pixel comparisons
2065
// cimPXMw[*] warped for display
2067
void * HDR_align_thread(void *) // v.10.7
2069
int imx, im1, im2, ww, hh, ii, nn;
2070
double R, maxtf, mintf, midtf;
2071
double xoff, yoff, toff, dxoff, dyoff;
2072
cimoffs offsets[10]; // x/y/t offsets after alignment
2074
Fzoom = 0; // fit to window if big
2075
Fblowup = 1; // scale up to window if small
2076
Ffuncbusy++; // v.11.01
2077
cimShrink = 0; // no warp shrinkage (pano)
2078
cimPano = cimPanoV = 0; // no pano mode
2080
for (imx = 0; imx < cimNF; imx++) // bugfix v.10.8
2081
memset(&offsets[imx],0,sizeof(cimoffs));
2083
for (im1 = 0; im1 < cimNF-1; im1++) // loop each pair of images
2087
memset(&cimOffs[im1],0,sizeof(cimoffs)); // initial image offsets = 0
2088
memset(&cimOffs[im2],0,sizeof(cimoffs));
2090
ww = cimPXMf[im1]->ww; // image dimensions
2091
hh = cimPXMf[im1]->hh;
2093
nn = ww; // use larger of ww, hh
2094
if (hh > ww) nn = hh;
2095
cimScale = HDRinitAlignSize / nn; // initial align image size
2096
if (cimScale > 1.0) cimScale = 1.0;
2098
cimBlend = 0; // no blend width (use all)
2099
cim_get_overlap(im1,im2,cimPXMf); // get overlap area
2100
cim_match_colors(im1,im2,cimPXMf); // get color matching factors
2102
cimSearchRange = HDRinitSearchRange; // initial align search range
2103
cimSearchStep = HDRinitSearchStep; // initial align search step
2104
cimWarpRange = HDRinitWarpRange; // initial align corner warp range
2105
cimWarpStep = HDRinitWarpStep; // initial align corner warp step
2106
cimSampSize = HDRsampSize; // pixel sample size for align/compare
2107
cimNsearch = 0; // reset align search counter
2109
while (true) // loop, increasing image size
2111
cim_scale_image(im1,cimPXMs); // scale images to cimScale
2112
cim_scale_image(im2,cimPXMs);
2114
cim_adjust_colors(cimPXMs[im1],1); // apply color adjustments
2115
cim_adjust_colors(cimPXMs[im2],2);
2117
cim_warp_image(im1); // make warped images to show
2118
cim_warp_image(im2);
2120
cimShowIm1 = im1; // show two images with 50/50 blend
2123
cim_show_images(1,0); // (x/y offsets can change)
2125
cim_get_overlap(im1,im2,cimPXMs); // get overlap area v.11.04
2126
cim_get_redpix(im1); // get high-contrast pixels
2128
cim_align_image(im1,im2); // align im2 to im1
2130
zfree(cimRedpix); // clear red pixels
2133
if (cimScale == 1.0) break; // done
2135
R = HDRimageIncrease; // next larger image size
2136
cimScale = cimScale * R;
2137
if (cimScale > 0.85) { // if close to end, jump to end
2142
cimOffs[im1].xf *= R; // scale offsets for larger image
2143
cimOffs[im1].yf *= R;
2144
cimOffs[im2].xf *= R;
2145
cimOffs[im2].yf *= R;
2147
for (ii = 0; ii < 4; ii++) {
2148
cimOffs[im1].wx[ii] *= R;
2149
cimOffs[im1].wy[ii] *= R;
2150
cimOffs[im2].wx[ii] *= R;
2151
cimOffs[im2].wy[ii] *= R;
2154
cimSearchRange = HDRsearchRange; // align search range
2155
cimSearchStep = HDRsearchStep; // align search step size
2156
cimWarpRange = HDRwarpRange; // align corner warp range
2157
cimWarpStep = HDRwarpStep; // align corner warp step size
2160
offsets[im2].xf = cimOffs[im2].xf - cimOffs[im1].xf; // save im2 offsets from im1
2161
offsets[im2].yf = cimOffs[im2].yf - cimOffs[im1].yf;
2162
offsets[im2].tf = cimOffs[im2].tf - cimOffs[im1].tf;
2164
for (ii = 0; ii < 4; ii++) {
2165
offsets[im2].wx[ii] = cimOffs[im2].wx[ii] - cimOffs[im1].wx[ii];
2166
offsets[im2].wy[ii] = cimOffs[im2].wy[ii] - cimOffs[im1].wy[ii];
2170
for (imx = 0; imx < cimNF; imx++) // offsets[*] >> cimOffs[*]
2171
cimOffs[imx] = offsets[imx];
2173
cimOffs[0].xf = cimOffs[0].yf = cimOffs[0].tf = 0; // image 0 at (0,0,0)
2175
for (im1 = 0; im1 < cimNF-1; im1++) // absolute offsets for image 1 to last
2178
cimOffs[im2].xf += cimOffs[im1].xf; // x/y/t offsets are additive
2179
cimOffs[im2].yf += cimOffs[im1].yf;
2180
cimOffs[im2].tf += cimOffs[im1].tf;
2182
for (ii = 0; ii < 4; ii++) { // corner warps are additive
2183
cimOffs[im2].wx[ii] += cimOffs[im1].wx[ii];
2184
cimOffs[im2].wy[ii] += cimOffs[im1].wy[ii];
2188
for (imx = 1; imx < cimNF; imx++) // re-warp to absolute v.10.8
2189
cim_warp_image(imx);
2191
toff = cimOffs[0].tf; // balance +/- thetas
2192
maxtf = mintf = toff;
2193
for (imx = 1; imx < cimNF; imx++) {
2194
toff = cimOffs[imx].tf;
2195
if (toff > maxtf) maxtf = toff;
2196
if (toff < mintf) mintf = toff;
2198
midtf = 0.5 * (maxtf + mintf);
2200
for (imx = 0; imx < cimNF; imx++)
2201
cimOffs[imx].tf -= midtf;
2203
for (im1 = 0; im1 < cimNF-1; im1++) // adjust x/y offsets for images after im1
2204
for (im2 = im1+1; im2 < cimNF; im2++) // due to im1 theta offset
2206
toff = cimOffs[im1].tf;
2207
xoff = cimOffs[im2].xf - cimOffs[im1].xf;
2208
yoff = cimOffs[im2].yf - cimOffs[im1].yf;
2209
dxoff = yoff * sin(toff);
2210
dyoff = xoff * sin(toff);
2211
cimOffs[im2].xf -= dxoff;
2212
cimOffs[im2].yf += dyoff;
2215
Fzoom = Fblowup = 0;
2219
return 0; // not executed
2223
// Compute mean image pixel brightness levels.
2224
// (basis for setting image contributions per brightness level)
2226
void HDR_brightness() // v.10.7
2228
int px3, py3, ww, hh, imx, kk, vstat;
2229
double px, py, red, green, blue;
2230
double bright, maxbright, minbright;
2231
double xoff, yoff, sintf[10], costf[10];
2232
double norm, fnorm = 1.0 / 65536.0;
2233
uint16 vpix[3], *pix3;
2237
for (imx = 0; imx < cimNF; imx++) // replace alignment images
2238
{ // (color adjusted for pixel matching)
2239
PXM_free(cimPXMs[imx]); // with the original images
2240
cimPXMs[imx] = PXM_copy(cimPXMf[imx]);
2241
cim_warp_image(imx); // re-apply warps
2244
for (imx = 0; imx < cimNF; imx++) // pre-calculate trig functions
2246
sintf[imx] = sin(cimOffs[imx].tf);
2247
costf[imx] = cos(cimOffs[imx].tf);
2253
HDRbright = (float *) zmalloc(ww*hh*sizeof(int),"HDR"); // get memory for brightness array
2258
for (py3 = 0; py3 < hh; py3++) // step through all output pixels
2259
for (px3 = 0; px3 < ww; px3++)
2261
red = green = blue = 0;
2264
for (imx = 0; imx < cimNF; imx++) // step through all input images
2266
xoff = cimOffs[imx].xf;
2267
yoff = cimOffs[imx].yf;
2269
px = costf[imx] * (px3 - xoff) + sintf[imx] * (py3 - yoff); // image N pixel, after offsets
2270
py = costf[imx] * (py3 - yoff) - sintf[imx] * (px3 - xoff);
2271
vstat = vpixel(cimPXMw[imx],px,py,vpix);
2274
red += fnorm * vpix[0]; // sum input pixels
2275
green += fnorm * vpix[1];
2276
blue += fnorm * vpix[2];
2279
if (! vstat) { // pixel outside some image
2280
pix3 = PXMpix(E3pxm16,px3,py3); // output pixel = black
2281
pix3[0] = pix3[1] = pix3[2] = 0;
2282
kk = py3 * ww + px3;
2287
bright = (red + green + blue) / (3 * cimNF); // mean pixel brightness, 0.0 to 1.0
2288
kk = py3 * ww + px3;
2289
HDRbright[kk] = bright;
2291
if (bright > maxbright) maxbright = bright;
2292
if (bright < minbright) minbright = bright;
2294
pix3 = PXMpix(E3pxm16,px3,py3); // output pixel
2295
pix3[0] = red * 65535.0 / cimNF;
2296
pix3[1] = green * 65535.0 / cimNF;
2297
pix3[2] = blue * 65535.0 / cimNF;
2300
norm = 0.999 / (maxbright - minbright); // normalize to range 0.0 to 0.999
2302
for (int ii = 0; ii < ww * hh; ii++)
2303
HDRbright[ii] = (HDRbright[ii] - minbright) * norm;
2305
mwpaint2(); // update window
2310
// Dialog for user to control the contributions of each input image
2311
// while watching the output image which is updated in real time.
2313
void HDR_tweak() // v.10.7
2315
int HDR_tweak_event(zdialog *zd, cchar *event);
2316
void HDR_curvedit(int);
2319
double cww = 1.0 / (cimNF-1);
2321
HDRzd = zdialog_new(ZTX("Adjust Image Contributions"),mWin,Bdone,Bcancel,null);
2322
zdialog_add_widget(HDRzd,"frame","brframe","dialog",0,"expand|space=2");
2323
zdialog_add_widget(HDRzd,"hbox","hb1","dialog",0);
2324
zdialog_add_widget(HDRzd,"label","lab11","hb1",ZTX("dark pixels"),"space=3");
2325
zdialog_add_widget(HDRzd,"label","lab12","hb1",0,"expand");
2326
zdialog_add_widget(HDRzd,"label","lab13","hb1",ZTX("light pixels"),"space=3");
2327
zdialog_add_widget(HDRzd,"hbox","hb2","dialog",0,"space=3");
2328
zdialog_add_widget(HDRzd,"label","labf1","hb2",ZTX("file:"),"space=3");
2329
zdialog_add_widget(HDRzd,"label","labf2","hb2","*");
2331
zdialog_add_widget(HDRzd,"hbox","hbcf","dialog",0,"space=5");
2332
zdialog_add_widget(HDRzd,"label","labcf","hbcf",Bcurvefile,"space=5");
2333
zdialog_add_widget(HDRzd,"button","load","hbcf",Bopen,"space=5");
2334
zdialog_add_widget(HDRzd,"button","save","hbcf",Bsave,"space=5");
2336
GtkWidget *brframe = zdialog_widget(HDRzd,"brframe"); // set up curve edit
2337
spldat *sd = splcurve_init(brframe,HDR_curvedit); // v.11.01
2340
sd->Nspc = cimNF; // no. curves = no. files
2342
for (imx = 0; imx < cimNF; imx++) // set up initial response curve
2346
sd->apx[imx][0] = 0.01; // flatter curves, v.9.3
2347
sd->apx[imx][1] = 0.99;
2348
sd->apy[imx][0] = 0.9 - imx * 0.8 * cww;
2349
sd->apy[imx][1] = 0.1 + imx * 0.8 * cww;
2350
splcurve_generate(sd,imx);
2353
start_thread(HDR_combine_thread,0); // start working thread
2356
zdialog_resize(HDRzd,400,360);
2357
zdialog_run(HDRzd,HDR_tweak_event,"-10/20"); // run dialog v.11.07
2358
zdialog_wait(HDRzd); // wait for completion
2364
// dialog event and completion callback function
2366
int HDR_tweak_event(zdialog *zd, cchar *event)
2368
spldat *sd = EFhdr.curves;
2370
if (strEqu(event,"load")) { // load saved curve v.11.02
2372
zdialog_stuff(HDRzd,"labf2","*");
2377
if (strEqu(event,"save")) { // save curve to file v.11.02
2382
if (zd->zstat) // dialog complete
2385
if (zd->zstat == 1) HDRstat = 1;
2387
zdialog_free(HDRzd);
2388
if (HDRstat == 1) cim_trim(); // cut-off edges v.10.9
2395
// this function is called when a curve is edited
2397
void HDR_curvedit(int spc)
2401
pp = strrchr(cimFile[spc],'/');
2402
zdialog_stuff(HDRzd,"labf2",pp+1);
2408
// Combine all input images >> E3pxm16 based on image response curves.
2410
void * HDR_combine_thread(void *)
2412
void * HDR_combine_wthread(void *arg);
2415
double xlo, xhi, xval, yval, sumrf;
2416
spldat *sd = EFhdr.curves;
2420
thread_idle_loop(); // wait for work or exit request
2422
for (imx = 0; imx < cimNF; imx++) // loop input images
2424
ii = sd->nap[imx]; // get low and high anchor points
2425
xlo = sd->apx[imx][0]; // for image response curve
2426
xhi = sd->apx[imx][ii-1];
2427
if (xlo < 0.02) xlo = 0; // snap-to scale end points
2428
if (xhi > 0.98) xhi = 1;
2430
for (ii = 0; ii < 1000; ii++) // loop all brightness levels
2432
HDR_respfac[imx][ii] = 0;
2434
if (xval < xlo || xval > xhi) continue; // no influence for brightness level
2435
kk = 1000 * xval; // speedup v.11.06
2436
yval = sd->yval[imx][kk];
2437
HDR_respfac[imx][ii] = yval; // = contribution of this input image
2441
for (ii = 0; ii < 1000; ii++) // normalize the factors so that
2442
{ // they sum to 1.0
2444
for (imx = 0; imx < cimNF; imx++)
2445
sumrf += HDR_respfac[imx][ii];
2446
if (! sumrf) continue;
2447
for (imx = 0; imx < cimNF; imx++)
2448
HDR_respfac[imx][ii] = HDR_respfac[imx][ii] / sumrf;
2451
mutex_lock(&Fpixmap_lock); // stop window updates
2453
for (ii = 0; ii < Nwt; ii++) // start worker threads v.10.7
2454
start_wthread(HDR_combine_wthread,&wtnx[ii]);
2455
wait_wthreads(); // wait for completion
2457
mutex_unlock(&Fpixmap_lock);
2458
mwpaint2(); // update window
2461
return 0; // not executed
2465
void * HDR_combine_wthread(void *arg) // working thread
2467
int index = *((int *) (arg));
2468
int imx, ww, hh, ii, px3, py3, vstat;
2469
double sintf[10], costf[10], xoff, yoff;
2470
double px, py, red, green, blue, bright, factor;
2471
uint16 vpix[3], *pix3;
2473
for (imx = 0; imx < cimNF; imx++) // pre-calculate trig functions
2475
sintf[imx] = sin(cimOffs[imx].tf);
2476
costf[imx] = cos(cimOffs[imx].tf);
2482
for (py3 = index; py3 < hh; py3 += Nwt) // step through all output pixels
2483
for (px3 = 0; px3 < ww; px3++)
2485
ii = py3 * ww + px3;
2486
bright = HDRbright[ii]; // mean brightness, 0.0 to 1.0
2489
red = green = blue = 0;
2491
for (imx = 0; imx < cimNF; imx++) // loop input images
2493
factor = HDR_respfac[imx][ii]; // image contribution to this pixel
2494
if (! factor) continue; // none
2496
xoff = cimOffs[imx].xf;
2497
yoff = cimOffs[imx].yf;
2499
px = costf[imx] * (px3 - xoff) + sintf[imx] * (py3 - yoff); // input virtual pixel mapping to
2500
py = costf[imx] * (py3 - yoff) - sintf[imx] * (px3 - xoff); // this output pixel
2502
vstat = vpixel(cimPXMw[imx],px,py,vpix); // get input pixel
2503
if (! vstat) continue;
2505
red += factor * vpix[0]; // accumulate brightness contribution
2506
green += factor * vpix[1];
2507
blue += factor * vpix[2];
2510
pix3 = PXMpix(E3pxm16,px3,py3); // output pixel
2512
pix3[0] = red; // = sum of input pixel contributions
2518
return 0; // not executed
2522
/**************************************************************************
2524
Make an HDF (high depth of field) image from several images of the same
2525
subject with different focus settings. Combine the images and allow the
2526
user to "paint" the output composite image using the mouse and choosing
2527
the sharpest input image for each area of the output image. The result
2528
is an image with a depth of field that exceeds the camera capability.
2530
The images are aligned at the center, but small differences in camera
2531
position (hand-held photos) will cause parallax errors that prevent
2532
perfect alignment of the images. Also, the images with nearer focus
2533
will be slightly larger than those with farther focus. These problems
2534
can be compensated by dragging and warping the images using the mouse. v.10.7
2536
**************************************************************************/
2538
int HDFstat; // 1 = OK, 0 = failed or canceled
2539
double HDFinitAlignSize = 160; // initial align image size
2540
double HDFimageIncrease = 1.6; // image size increase per align cycle
2541
double HDFsampSize = 6000; // pixel sample size
2543
double HDFinitSearchRange = 8.0; // initial search range, +/- pixels
2544
double HDFinitSearchStep = 1.0; // initial search step, pixels
2545
double HDFinitWarpRange = 4.0; // initial corner warp range
2546
double HDFinitWarpStep = 1.0; // initial corner warp step
2547
double HDFsearchRange = 2.0; // normal search range
2548
double HDFsearchStep = 1.0; // normal search step
2549
double HDFwarpRange = 1.0; // normal corner warp range v.11.03
2550
double HDFwarpStep = 0.67; // normal corner warp step
2552
void * HDF_align_thread(void *);
2554
void HDF_mousefunc();
2555
void * HDF_combine_thread(void *);
2557
editfunc EFhdf; // edit function data
2562
void m_HDF(GtkWidget *, cchar *) // v.10.7
2565
int imx, err, ww, hh;
2566
double diffw, diffh;
2568
zfuncs::F1_help_topic = "HDF"; // help topic
2570
if (mod_keep()) return; // warn unsaved changes
2571
if (! menulock(1)) return; // test menu lock v.11.07
2574
for (imx = 0; imx < 10; imx++)
2575
{ // clear all file and PXM data
2577
cimPXMf[imx] = cimPXMs[imx] = cimPXMw[imx] = 0;
2582
flist = zgetfileN(ZTX("Select 2 to 9 files"),"openN",curr_file); // select images to combine
2583
if (! flist) return;
2585
for (imx = 0; flist[imx]; imx++); // count selected files
2586
if (imx < 2 || imx > 9) {
2587
zmessageACK(mWin,ZTX("Select 2 to 9 files"));
2591
cimNF = imx; // file count
2592
for (imx = 0; imx < cimNF; imx++)
2593
cimFile[imx] = strdupz(flist[imx],0,"HDF"); // set up file list
2595
if (! cim_load_files()) goto cleanup; // load and check all files
2597
ww = cimPXMf[0]->ww;
2598
hh = cimPXMf[0]->hh;
2600
for (imx = 1; imx < cimNF; imx++) // check image compatibility
2602
diffw = abs(ww - cimPXMf[imx]->ww);
2604
diffh = abs(hh - cimPXMf[imx]->hh);
2607
if (diffw > 0.02 || diffh > 0.02) {
2608
zmessageACK(mWin,ZTX("Images are not all the same size"));
2613
free_resources(); // ready to commit
2615
err = f_open(cimFile[0],0); // curr_file = 1st file in list
2616
if (err) goto cleanup;
2618
EFhdf.funcname = "HDF";
2619
if (! edit_setup(EFhdf)) goto cleanup; // setup edit (will lock)
2621
start_thread(HDF_align_thread,0); // align each pair of images
2622
wrapup_thread(0); // wait for completion
2623
if (HDFstat != 1) goto cancel;
2625
HDF_tweak(); // combine images based on user inputs
2626
if (HDFstat != 1) goto cancel;
2628
CEF->Fmod = 1; // done
2638
for (imx = 0; flist[imx]; imx++) // free file list
2643
for (imx = 0; imx < cimNF; imx++) { // free cim file and PXM data
2644
if (cimFile[imx]) zfree(cimFile[imx]);
2645
if (cimPXMf[imx]) PXM_free(cimPXMf[imx]);
2646
if (cimPXMs[imx]) PXM_free(cimPXMs[imx]);
2647
if (cimPXMw[imx]) PXM_free(cimPXMw[imx]);
2655
// HDF align each pair of input images, output combined image to E3pxm16
2656
// cimPXMf[*] original image
2657
// cimPXMs[*] scaled and color adjusted for pixel comparisons
2658
// cimPXMw[*] warped for display
2660
void * HDF_align_thread(void *) // v.10.7
2662
int imx, im1, im2, ww, hh, ii, nn;
2663
double R, maxtf, mintf, midtf;
2664
double xoff, yoff, toff, dxoff, dyoff;
2665
cimoffs offsets[10]; // x/y/t offsets after alignment
2667
Fzoom = 0; // fit to window if big
2668
Fblowup = 1; // scale up to window if small
2669
Ffuncbusy++; // v.11.01
2670
cimShrink = 0; // no warp shrinkage (pano)
2671
cimPano = cimPanoV = 0; // no pano mode
2673
for (imx = 0; imx < cimNF; imx++) // bugfix v.10.8
2674
memset(&offsets[imx],0,sizeof(cimoffs));
2676
for (im1 = 0; im1 < cimNF-1; im1++) // loop each pair of images
2680
memset(&cimOffs[im1],0,sizeof(cimoffs)); // initial image offsets = 0
2681
memset(&cimOffs[im2],0,sizeof(cimoffs));
2683
ww = cimPXMf[im1]->ww; // image dimensions
2684
hh = cimPXMf[im1]->hh;
2686
nn = ww; // use larger of ww, hh
2687
if (hh > ww) nn = hh;
2688
cimScale = HDFinitAlignSize / nn; // initial align image size
2689
if (cimScale > 1.0) cimScale = 1.0;
2691
cimBlend = 0; // no blend width (use all)
2692
cim_get_overlap(im1,im2,cimPXMf); // get overlap area
2693
cim_match_colors(im1,im2,cimPXMf); // get color matching factors
2695
cimSearchRange = HDFinitSearchRange; // initial align search range
2696
cimSearchStep = HDFinitSearchStep; // initial align search step
2697
cimWarpRange = HDFinitWarpRange; // initial align corner warp range
2698
cimWarpStep = HDFinitWarpStep; // initial align corner warp step
2699
cimSampSize = HDFsampSize; // pixel sample size for align/compare
2700
cimNsearch = 0; // reset align search counter
2702
while (true) // loop, increasing image size
2704
cim_scale_image(im1,cimPXMs); // scale images to cimScale
2705
cim_scale_image(im2,cimPXMs);
2707
cim_adjust_colors(cimPXMs[im1],1); // apply color adjustments
2708
cim_adjust_colors(cimPXMs[im2],2);
2710
cim_warp_image(im1); // warp images for show
2711
cim_warp_image(im2);
2713
cimShowIm1 = im1; // show these two images
2714
cimShowIm2 = im2; // with 50/50 blend
2716
cim_show_images(1,0); // (y offset can change)
2718
cim_get_overlap(im1,im2,cimPXMs); // get overlap area v.11.04
2719
cim_get_redpix(im1); // get high-contrast pixels
2721
cim_align_image(im1,im2); // align im2 to im1
2723
zfree(cimRedpix); // clear red pixels
2726
if (cimScale == 1.0) break; // done
2728
R = HDFimageIncrease; // next larger image size
2729
cimScale = cimScale * R;
2730
if (cimScale > 0.85) { // if close to end, jump to end
2735
cimOffs[im1].xf *= R; // scale offsets for larger image
2736
cimOffs[im1].yf *= R;
2737
cimOffs[im2].xf *= R;
2738
cimOffs[im2].yf *= R;
2740
for (ii = 0; ii < 4; ii++) {
2741
cimOffs[im1].wx[ii] *= R;
2742
cimOffs[im1].wy[ii] *= R;
2743
cimOffs[im2].wx[ii] *= R;
2744
cimOffs[im2].wy[ii] *= R;
2747
cimSearchRange = HDFsearchRange; // align search range
2748
cimSearchStep = HDFsearchStep; // align search step size
2749
cimWarpRange = HDFwarpRange; // align corner warp range
2750
cimWarpStep = HDFwarpStep; // align corner warp step size
2753
offsets[im2].xf = cimOffs[im2].xf - cimOffs[im1].xf; // save im2 offsets from im1
2754
offsets[im2].yf = cimOffs[im2].yf - cimOffs[im1].yf;
2755
offsets[im2].tf = cimOffs[im2].tf - cimOffs[im1].tf;
2757
for (ii = 0; ii < 4; ii++) {
2758
offsets[im2].wx[ii] = cimOffs[im2].wx[ii] - cimOffs[im1].wx[ii];
2759
offsets[im2].wy[ii] = cimOffs[im2].wy[ii] - cimOffs[im1].wy[ii];
2763
for (imx = 0; imx < cimNF; imx++) // offsets[*] >> cimOffs[*]
2764
cimOffs[imx] = offsets[imx];
2766
cimOffs[0].xf = cimOffs[0].yf = cimOffs[0].tf = 0; // image 0 at (0,0,0)
2768
for (im1 = 0; im1 < cimNF-1; im1++) // absolute offsets for image 1 to last
2771
cimOffs[im2].xf += cimOffs[im1].xf; // x/y/t offsets are additive
2772
cimOffs[im2].yf += cimOffs[im1].yf;
2773
cimOffs[im2].tf += cimOffs[im1].tf;
2775
for (ii = 0; ii < 4; ii++) { // corner warps are additive
2776
cimOffs[im2].wx[ii] += cimOffs[im1].wx[ii];
2777
cimOffs[im2].wy[ii] += cimOffs[im1].wy[ii];
2781
for (imx = 1; imx < cimNF; imx++) // re-warp to absolute v.10.8
2782
cim_warp_image(imx);
2784
toff = cimOffs[0].tf; // balance +/- thetas
2785
maxtf = mintf = toff;
2786
for (imx = 1; imx < cimNF; imx++) {
2787
toff = cimOffs[imx].tf;
2788
if (toff > maxtf) maxtf = toff;
2789
if (toff < mintf) mintf = toff;
2791
midtf = 0.5 * (maxtf + mintf);
2793
for (imx = 0; imx < cimNF; imx++)
2794
cimOffs[imx].tf -= midtf;
2796
for (im1 = 0; im1 < cimNF-1; im1++) // adjust x/y offsets for images after im1
2797
for (im2 = im1+1; im2 < cimNF; im2++) // due to im1 theta offset
2799
toff = cimOffs[im1].tf;
2800
xoff = cimOffs[im2].xf - cimOffs[im1].xf;
2801
yoff = cimOffs[im2].yf - cimOffs[im1].yf;
2802
dxoff = yoff * sin(toff);
2803
dyoff = xoff * sin(toff);
2804
cimOffs[im2].xf -= dxoff;
2805
cimOffs[im2].yf += dyoff;
2808
for (imx = 0; imx < cimNF; imx++) // use final warped images as basis
2809
{ // for manual align adjustments
2810
PXM_free(cimPXMs[imx]); // bugfix v.11.04
2811
cimPXMs[imx] = PXM_copy(cimPXMw[imx]);
2814
Fzoom = Fblowup = 0;
2818
return 0; // not executed
2822
// paint and warp output image
2824
zdialog *HDFzd = 0; // paint dialog
2825
int HDFmode; // mode: paint or warp
2826
int HDFimage; // current image (0 based)
2827
int HDFradius; // paint mode radius
2828
char *HDFpixmap = 0; // map input image per output pixel
2829
float *HDFwarpx[10], *HDFwarpy[10]; // warp memory, pixel displacements
2832
void HDF_tweak() // v.10.7
2834
char imageN[8] = "imageN", labN[4] = "0";
2835
int cc, imx, ww, hh;
2837
int HDF_tweak_dialog_event(zdialog *zd, cchar *event);
2839
// image (o) 1 (o) 2 (o) 3 ...
2840
// (o) paint radius [___]
2844
HDFzd = zdialog_new(ZTX("Paint and Warp Image"),mWin,Bdone,Bcancel,null);
2846
zdialog_add_widget(HDFzd,"hbox","hbim","dialog",0,"space=3");
2847
zdialog_add_widget(HDFzd,"label","labim","hbim",ZTX("image"),"space=5");
2848
zdialog_add_widget(HDFzd,"hbox","hbpw","dialog",0,"space=3");
2849
zdialog_add_widget(HDFzd,"vbox","vbpw1","hbpw",0,"homog|space=5");
2850
zdialog_add_widget(HDFzd,"vbox","vbpw2","hbpw",0,"homog|space=5");
2851
zdialog_add_widget(HDFzd,"radio","paint","vbpw1",ZTX("paint"));
2852
zdialog_add_widget(HDFzd,"radio","warp","vbpw1",ZTX("warp"));
2853
zdialog_add_widget(HDFzd,"hbox","hbp","vbpw2");
2854
zdialog_add_widget(HDFzd,"label","labpr","hbp",Bradius,"space=5");
2855
zdialog_add_widget(HDFzd,"spin","radius","hbp","1|400|1|100");
2856
zdialog_add_widget(HDFzd,"label","space","vbpw2");
2857
zdialog_add_widget(HDFzd,"hbox","hbsr","dialog");
2858
zdialog_add_widget(HDFzd,"check","mymouse","hbsr",BmyMouse,"space=5");
2860
for (imx = 0; imx < cimNF; imx++) { // add radio button for each image
2861
imageN[5] = '1' + imx;
2862
labN[0] = '1' + imx;
2863
zdialog_add_widget(HDFzd,"radio",imageN,"hbim",labN);
2866
zdialog_stuff(HDFzd,"paint",1); // paint button on
2867
zdialog_stuff(HDFzd,"warp",0); // warp button off
2868
zdialog_stuff(HDFzd,"image1",1); // initial image = 1st
2870
HDFmode = 0; // start in paint mode
2871
HDFimage = 0; // initial image
2872
HDFradius = 100; // paint radius
2874
takeMouse(HDFzd,HDF_mousefunc,0); // connect mouse function v.10.12
2876
cc = E3ww * E3hh; // allocate pixel map
2877
HDFpixmap = zmalloc(cc,"HDF");
2878
memset(HDFpixmap,cimNF,cc); // initial state, blend all images
2880
for (imx = 0; imx < cimNF; imx++) { // allocate warp memory
2881
ww = cimPXMw[imx]->ww;
2882
hh = cimPXMw[imx]->hh;
2883
HDFwarpx[imx] = (float *) zmalloc(ww * hh * sizeof(float),"HDF");
2884
HDFwarpy[imx] = (float *) zmalloc(ww * hh * sizeof(float),"HDF");
2887
start_thread(HDF_combine_thread,0); // start working thread
2890
zdialog_resize(HDFzd,250,0); // stretch a bit v.11.07
2891
zdialog_run(HDFzd,HDF_tweak_dialog_event,"-10/20"); // run dialog, parallel v.11.07
2892
zdialog_wait(HDFzd); // wait for completion
2898
// dialog event and completion callback function
2900
int HDF_tweak_dialog_event(zdialog *zd, cchar *event) // v.10.7
2902
int imx, nn, mymouse;
2904
if (zd->zstat) // dialog finish
2906
freeMouse(); // disconnect mouse function v.10.12
2909
if (zd->zstat == 1) HDFstat = 1;
2911
if (HDFstat == 1) cim_trim(); // cut-off edges v.10.9
2912
zdialog_free(HDFzd);
2914
zfree(HDFpixmap); // free pixel map
2915
for (imx = 0; imx < cimNF; imx++) {
2916
zfree(HDFwarpx[imx]); // free warp memory
2917
zfree(HDFwarpy[imx]);
2921
if (strEqu(event,"paint")) { // set paint mode
2922
zdialog_fetch(zd,"paint",nn);
2925
gdk_window_set_cursor(drWin->window,0); // no drag cursor v.11.03
2928
if (strEqu(event,"warp")) { // set warp mode
2929
zdialog_fetch(zd,"warp",nn);
2932
paint_toparc(2); // stop brush outline
2933
zdialog_fetch(zd,"mymouse",mymouse);
2935
gdk_window_set_cursor(drWin->window,dragcursor); // set drag cursor v.11.03
2938
if (strnEqu(event,"image",5)) { // image radio button
2939
nn = event[5] - '0'; // 1 to cimNF
2940
if (nn > 0 && nn <= cimNF)
2941
HDFimage = nn - 1; // 0 to cimNF-1
2945
if (strEqu(event,"radius")) // change paint radius
2946
zdialog_fetch(zd,"radius",HDFradius);
2948
if (strEqu(event,"mymouse")) { // toggle mouse capture v.10.12
2949
zdialog_fetch(zd,"mymouse",mymouse);
2951
takeMouse(zd,HDF_mousefunc,0); // connect mouse function
2953
gdk_window_set_cursor(drWin->window,dragcursor); // warp mode, drag cursor v.11.03
2956
else freeMouse(); // disconnect mouse
2963
// HDF dialog mouse function
2964
// paint: during drag, selected image >> HDFpixmap (within paint radius) >> E3
2965
// warp: for selected image, cimPXMs >> warp >> cimPXMw >> E3
2967
void HDF_mousefunc() // v.10.7
2969
uint16 vpix1[3], *pix2, *pix3;
2970
int imx, radius, radius2, vstat1;
2971
int mx, my, dx, dy, px3, py3;
2972
char imageN[8] = "imageN";
2974
double xoff, yoff, sintf[10], costf[10];
2975
int ii, px, py, ww, hh;
2976
double mag, dispx, dispy, d1, d2;
2979
if (HDFmode == 0) goto paint;
2980
if (HDFmode == 1) goto warp;
2985
radius = HDFradius; // paintbrush radius
2986
radius2 = radius * radius;
2988
toparcx = Mxposn - radius; // paintbrush outline circle
2989
toparcy = Myposn - radius;
2990
toparcw = toparch = 2 * radius;
2994
if (LMclick || RMclick) { // mouse click
2995
LMclick = RMclick = 0;
2996
return; // ignore v.10.8
2999
else if (Mxdrag || Mydrag) { // drag in progress
3006
if (mx < 0 || mx > E3ww-1 || my < 0 || my > E3hh-1) // mouse outside image area
3009
for (imx = 0; imx < cimNF; imx++) // pre-calculate trig funcs
3011
sintf[imx] = sin(cimOffs[imx].tf);
3012
costf[imx] = cos(cimOffs[imx].tf);
3015
for (dy = -radius; dy <= radius; dy++) // loop pixels around mouse
3016
for (dx = -radius; dx <= radius; dx++)
3018
if (dx*dx + dy*dy > radius2) continue; // outside radius
3020
px3 = mx + dx; // output pixel
3022
if (px3 < 0 || px3 > E3ww-1) continue; // outside image
3023
if (py3 < 0 || py3 > E3hh-1) continue;
3025
pix3 = PXMpix(E3pxm16,px3,py3); // output pixel
3027
imx = py3 * E3ww + px3; // update pixmap to selected image
3028
HDFpixmap[imx] = HDFimage;
3031
xoff = cimOffs[imx].xf;
3032
yoff = cimOffs[imx].yf;
3033
px1 = costf[imx] * (px3 - xoff) + sintf[imx] * (py3 - yoff); // input virtual pixel
3034
py1 = costf[imx] * (py3 - yoff) - sintf[imx] * (px3 - xoff);
3035
vstat1 = vpixel(cimPXMw[imx],px1,py1,vpix1);
3041
else pix3[0] = pix3[1] = pix3[2] = 0;
3044
mx = mx - radius - 1; // update window v.10.12
3045
my = my - radius - 1;
3046
ww = 2 * radius + 3;
3048
mwpaint3(mx,my,ww,ww);
3056
if (LMclick || RMclick) { // mouse click
3057
LMclick = RMclick = 0; // ignore v.10.8
3061
else if (Mxdrag || Mydrag) { // drag in progress
3068
if (mx < 0 || mx > E3ww-1 || my < 0 || my > E3hh-1) // mouse outside image area
3071
imx = my * E3ww + mx; // if pixel has been painted,
3072
imx = HDFpixmap[imx]; // select corresp. image to warp
3073
if (imx == cimNF) return; // else no action v.10.8
3075
if (imx != HDFimage) {
3076
HDFimage = imx; // update selected image and
3077
imageN[5] = '1' + imx; // dialog radio button
3078
zdialog_stuff(HDFzd,imageN,1);
3081
pxm1 = cimPXMs[imx]; // input image
3082
pxm2 = cimPXMw[imx]; // output image
3086
mx = Mxdown; // drag origin, image coordinates
3088
dx = Mxdrag - Mxdown; // drag increment
3089
dy = Mydrag - Mydown;
3090
Mxdown = Mxdrag; // next drag origin
3093
d1 = ww * ww + hh * hh;
3095
for (py = 0; py < hh; py++) // process all output pixels
3096
for (px = 0; px < ww; px++)
3098
d2 = (px-mx)*(px-mx) + (py-my)*(py-my);
3099
mag = (1.0 - d2 / d1);
3100
mag = mag * mag * mag * mag;
3101
mag = mag * mag * mag * mag;
3102
mag = mag * mag * mag * mag;
3104
dispx = -dx * mag; // displacement = drag * mag
3108
HDFwarpx[imx][ii] += dispx; // add this drag to prior sum
3109
HDFwarpy[imx][ii] += dispy;
3111
dispx = HDFwarpx[imx][ii];
3112
dispy = HDFwarpy[imx][ii];
3114
vstat1 = vpixel(pxm1,px+dispx,py+dispy,vpix1); // input virtual pixel
3115
pix2 = PXMpix(pxm2,px,py); // output pixel
3121
else pix2[0] = pix2[1] = pix2[2] = 0;
3124
signal_thread(); // combine images >> E3 >> main window
3129
// Combine images in E3pxm16 (not reallocated). Update main window.
3131
void * HDF_combine_thread(void *) // v.10.7
3133
void * HDF_combine_wthread(void *);
3137
thread_idle_loop(); // wait for work or exit request
3139
mutex_lock(&Fpixmap_lock); // stop window updates
3141
for (int ii = 0; ii < Nwt; ii++) // start worker threads v.10.7
3142
start_wthread(HDF_combine_wthread,&wtnx[ii]);
3143
wait_wthreads(); // wait for completion
3145
mutex_unlock(&Fpixmap_lock); // update window
3149
return 0; // not executed
3153
void * HDF_combine_wthread(void *arg) // worker thread
3155
int index = *((int *) (arg)); // no more paint and warp modes v.10.8
3156
int px3, py3, vstat1;
3157
int imx, red, green, blue;
3159
double xoff, yoff, sintf[10], costf[10];
3160
uint16 vpix1[3], *pix3;
3162
for (imx = 0; imx < cimNF; imx++) // pre-calculate trig funcs
3164
sintf[imx] = sin(cimOffs[imx].tf);
3165
costf[imx] = cos(cimOffs[imx].tf);
3168
for (py3 = index+1; py3 < E3hh-1; py3 += Nwt) // step through output pixels
3169
for (px3 = 1; px3 < E3ww-1; px3++)
3171
pix3 = PXMpix(E3pxm16,px3,py3);
3173
imx = py3 * E3ww + px3;
3174
imx = HDFpixmap[imx];
3176
if (imx < cimNF) // specific image maps to pixel
3178
xoff = cimOffs[imx].xf;
3179
yoff = cimOffs[imx].yf;
3180
px = costf[imx] * (px3 - xoff) + sintf[imx] * (py3 - yoff);
3181
py = costf[imx] * (py3 - yoff) - sintf[imx] * (px3 - xoff);
3182
vstat1 = vpixel(cimPXMw[imx],px,py,vpix1); // corresp. input vpixel
3188
else pix3[0] = pix3[1] = pix3[2] = 0;
3191
else // use blend of all images
3193
red = green = blue = 0;
3195
for (imx = 0; imx < cimNF; imx++)
3197
xoff = cimOffs[imx].xf;
3198
yoff = cimOffs[imx].yf;
3199
px = costf[imx] * (px3 - xoff) + sintf[imx] * (py3 - yoff);
3200
py = costf[imx] * (py3 - yoff) - sintf[imx] * (px3 - xoff);
3201
vstat1 = vpixel(cimPXMw[imx],px,py,vpix1);
3209
pix3[0] = red / cimNF;
3210
pix3[1] = green / cimNF;
3211
pix3[2] = blue / cimNF;
3216
return 0; // not executed, avoid gcc warning
3220
/**************************************************************************
3222
Stack/Paint function
3223
Combine multiple images of one subject taken at different times from
3224
(almost) the same camera position. Align the images and allow the user
3225
to choose which input image to use for each area of the output image,
3226
by "painting" with the mouse. Use this to remove tourists and cars that
3227
move in and out of a scene being photographed.
3229
**************************************************************************/
3231
int STPstat; // 1 = OK, 0 = failed or canceled
3232
double STPinitAlignSize = 160; // initial align image size
3233
double STPimageIncrease = 1.6; // image size increase per align cycle
3234
double STPsampSize = 10000; // pixel sample size v.11.03
3236
double STPinitSearchRange = 5.0; // initial search range, +/- pixels
3237
double STPinitSearchStep = 1.0; // initial search step, pixels
3238
double STPinitWarpRange = 2.0; // initial corner warp range
3239
double STPinitWarpStep = 1.0; // initial corner warp step
3240
double STPsearchRange = 2.0; // normal search range
3241
double STPsearchStep = 1.0; // normal search step
3242
double STPwarpRange = 1.0; // normal corner warp range
3243
double STPwarpStep = 0.67; // normal corner warp step
3245
void * STP_align_thread(void *);
3247
void STP_mousefunc();
3248
void * STP_combine_thread(void *);
3250
editfunc EFstp; // edit function data
3255
void m_STP(GtkWidget *, cchar *) // v.11.02
3258
int imx, err, ww, hh;
3259
double diffw, diffh;
3261
zfuncs::F1_help_topic = "stack_paint"; // help topic
3263
if (mod_keep()) return; // warn unsaved changes
3264
if (! menulock(1)) return; // test menu lock v.11.07
3267
for (imx = 0; imx < 10; imx++)
3268
{ // clear all file and PXM data
3270
cimPXMf[imx] = cimPXMs[imx] = cimPXMw[imx] = 0;
3275
flist = zgetfileN(ZTX("Select 2 to 9 files"),"openN",curr_file); // select images to combine
3276
if (! flist) return;
3278
for (imx = 0; flist[imx]; imx++); // count selected files
3279
if (imx < 2 || imx > 9) {
3280
zmessageACK(mWin,ZTX("Select 2 to 9 files"));
3284
cimNF = imx; // file count
3285
for (imx = 0; imx < cimNF; imx++)
3286
cimFile[imx] = strdupz(flist[imx],0,"STP"); // set up file list
3288
if (! cim_load_files()) goto cleanup; // load and check all files
3290
ww = cimPXMf[0]->ww;
3291
hh = cimPXMf[0]->hh;
3293
for (imx = 1; imx < cimNF; imx++) // check image compatibility
3295
diffw = abs(ww - cimPXMf[imx]->ww);
3297
diffh = abs(hh - cimPXMf[imx]->hh);
3300
if (diffw > 0.02 || diffh > 0.02) {
3301
zmessageACK(mWin,ZTX("Images are not all the same size"));
3306
free_resources(); // ready to commit
3308
err = f_open(cimFile[0],0); // curr_file = 1st file in list
3309
if (err) goto cleanup;
3311
EFstp.funcname = "stack-paint";
3312
if (! edit_setup(EFstp)) goto cleanup; // setup edit (will lock)
3314
start_thread(STP_align_thread,0); // align each pair of images
3315
wrapup_thread(0); // wait for completion
3316
if (STPstat != 1) goto cancel;
3318
STP_tweak(); // combine images based on user inputs
3319
if (STPstat != 1) goto cancel;
3321
CEF->Fmod = 1; // done
3331
for (imx = 0; flist[imx]; imx++) // free file list
3336
for (imx = 0; imx < cimNF; imx++) { // free cim file and PXM data
3337
if (cimFile[imx]) zfree(cimFile[imx]);
3338
if (cimPXMf[imx]) PXM_free(cimPXMf[imx]);
3339
if (cimPXMs[imx]) PXM_free(cimPXMs[imx]);
3340
if (cimPXMw[imx]) PXM_free(cimPXMw[imx]);
3348
// align each pair of input images, output combined image to E3pxm16
3349
// cimPXMf[*] original image
3350
// cimPXMs[*] scaled and color adjusted for pixel comparisons
3351
// cimPXMw[*] warped for display
3353
void * STP_align_thread(void *) // v.11.02
3355
int imx, im1, im2, ww, hh, ii, nn;
3356
double R, maxtf, mintf, midtf;
3357
double xoff, yoff, toff, dxoff, dyoff;
3358
cimoffs offsets[10]; // x/y/t offsets after alignment
3360
Fzoom = 0; // fit to window if big
3361
Fblowup = 1; // scale up to window if small
3363
cimShrink = 0; // no warp shrinkage (pano)
3364
cimPano = cimPanoV = 0; // no pano mode
3366
for (imx = 0; imx < cimNF; imx++)
3367
memset(&offsets[imx],0,sizeof(cimoffs));
3369
for (im1 = 0; im1 < cimNF-1; im1++) // loop each pair of images
3373
memset(&cimOffs[im1],0,sizeof(cimoffs)); // initial image offsets = 0
3374
memset(&cimOffs[im2],0,sizeof(cimoffs));
3376
ww = cimPXMf[im1]->ww; // image dimensions
3377
hh = cimPXMf[im1]->hh;
3379
nn = ww; // use larger of ww, hh
3380
if (hh > ww) nn = hh;
3381
cimScale = STPinitAlignSize / nn; // initial align image size
3382
if (cimScale > 1.0) cimScale = 1.0;
3384
cimBlend = 0; // no blend width (use all)
3385
cim_get_overlap(im1,im2,cimPXMf); // get overlap area
3386
cim_match_colors(im1,im2,cimPXMf); // get color matching factors
3388
cimSearchRange = STPinitSearchRange; // initial align search range
3389
cimSearchStep = STPinitSearchStep; // initial align search step
3390
cimWarpRange = STPinitWarpRange; // initial align corner warp range
3391
cimWarpStep = STPinitWarpStep; // initial align corner warp step
3392
cimSampSize = STPsampSize; // pixel sample size for align/compare
3393
cimNsearch = 0; // reset align search counter
3395
while (true) // loop, increasing image size
3397
cim_scale_image(im1,cimPXMs); // scale images to cimScale
3398
cim_scale_image(im2,cimPXMs);
3400
cim_adjust_colors(cimPXMs[im1],1); // apply color adjustments
3401
cim_adjust_colors(cimPXMs[im2],2);
3403
cim_warp_image(im1); // warp images for show
3404
cim_warp_image(im2);
3406
cimShowIm1 = im1; // show these two images
3407
cimShowIm2 = im2; // with 50/50 blend
3409
cim_show_images(1,0); // (y offset can change)
3411
cim_get_overlap(im1,im2,cimPXMs); // get overlap area v.11.04
3412
cim_get_redpix(im1); // get high-contrast pixels
3414
cim_align_image(im1,im2); // align im2 to im1
3416
zfree(cimRedpix); // clear red pixels
3419
if (cimScale == 1.0) break; // done
3421
R = STPimageIncrease; // next larger image size
3422
cimScale = cimScale * R;
3423
if (cimScale > 0.85) { // if close to end, jump to end
3428
cimOffs[im1].xf *= R; // scale offsets for larger image
3429
cimOffs[im1].yf *= R;
3430
cimOffs[im2].xf *= R;
3431
cimOffs[im2].yf *= R;
3433
for (ii = 0; ii < 4; ii++) {
3434
cimOffs[im1].wx[ii] *= R;
3435
cimOffs[im1].wy[ii] *= R;
3436
cimOffs[im2].wx[ii] *= R;
3437
cimOffs[im2].wy[ii] *= R;
3440
cimSearchRange = STPsearchRange; // align search range
3441
cimSearchStep = STPsearchStep; // align search step size
3442
cimWarpRange = STPwarpRange; // align corner warp range
3443
cimWarpStep = STPwarpStep; // align corner warp step size
3446
offsets[im2].xf = cimOffs[im2].xf - cimOffs[im1].xf; // save im2 offsets from im1
3447
offsets[im2].yf = cimOffs[im2].yf - cimOffs[im1].yf;
3448
offsets[im2].tf = cimOffs[im2].tf - cimOffs[im1].tf;
3450
for (ii = 0; ii < 4; ii++) {
3451
offsets[im2].wx[ii] = cimOffs[im2].wx[ii] - cimOffs[im1].wx[ii];
3452
offsets[im2].wy[ii] = cimOffs[im2].wy[ii] - cimOffs[im1].wy[ii];
3456
for (imx = 0; imx < cimNF; imx++) // offsets[*] >> cimOffs[*]
3457
cimOffs[imx] = offsets[imx];
3459
cimOffs[0].xf = cimOffs[0].yf = cimOffs[0].tf = 0; // image 0 at (0,0,0)
3461
for (im1 = 0; im1 < cimNF-1; im1++) // absolute offsets for image 1 to last
3464
cimOffs[im2].xf += cimOffs[im1].xf; // x/y/t offsets are additive
3465
cimOffs[im2].yf += cimOffs[im1].yf;
3466
cimOffs[im2].tf += cimOffs[im1].tf;
3468
for (ii = 0; ii < 4; ii++) { // corner warps are additive
3469
cimOffs[im2].wx[ii] += cimOffs[im1].wx[ii];
3470
cimOffs[im2].wy[ii] += cimOffs[im1].wy[ii];
3474
for (imx = 1; imx < cimNF; imx++) // re-warp to absolute
3475
cim_warp_image(imx);
3477
toff = cimOffs[0].tf; // balance +/- thetas
3478
maxtf = mintf = toff;
3479
for (imx = 1; imx < cimNF; imx++) {
3480
toff = cimOffs[imx].tf;
3481
if (toff > maxtf) maxtf = toff;
3482
if (toff < mintf) mintf = toff;
3484
midtf = 0.5 * (maxtf + mintf);
3486
for (imx = 0; imx < cimNF; imx++)
3487
cimOffs[imx].tf -= midtf;
3489
for (im1 = 0; im1 < cimNF-1; im1++) // adjust x/y offsets for images after im1
3490
for (im2 = im1+1; im2 < cimNF; im2++) // due to im1 theta offset
3492
toff = cimOffs[im1].tf;
3493
xoff = cimOffs[im2].xf - cimOffs[im1].xf;
3494
yoff = cimOffs[im2].yf - cimOffs[im1].yf;
3495
dxoff = yoff * sin(toff);
3496
dyoff = xoff * sin(toff);
3497
cimOffs[im2].xf -= dxoff;
3498
cimOffs[im2].yf += dyoff;
3501
Fzoom = Fblowup = 0;
3505
return 0; // not executed
3509
// paint output image
3511
zdialog *STPzd = 0; // paint dialog
3512
int STPimage; // current image (0 based)
3513
int STPradius; // paint mode radius
3514
char *STPpixmap = 0; // map input image per output pixel
3517
void STP_tweak() // v.11.02
3519
char imageN[8] = "imageN", labN[4] = "0";
3522
int STP_tweak_dialog_event(zdialog *zd, cchar *event);
3524
// image (o) 1 (o) 2 (o) 3 ...
3525
// [x] my mouse radius [___]
3527
STPzd = zdialog_new(ZTX("Select and Paint Image"),mWin,Bdone,Bcancel,null);
3528
zdialog_add_widget(STPzd,"hbox","hbim","dialog",0,"space=3");
3529
zdialog_add_widget(STPzd,"label","labim","hbim",ZTX("image"),"space=5");
3530
zdialog_add_widget(STPzd,"hbox","hbmr","dialog",0,"space=3");
3531
zdialog_add_widget(STPzd,"check","mymouse","hbmr",BmyMouse,"space=5");
3532
zdialog_add_widget(STPzd,"label","labr","hbmr",Bradius,"space=5");
3533
zdialog_add_widget(STPzd,"spin","radius","hbmr","1|400|1|100");
3535
for (imx = 0; imx < cimNF; imx++) { // add radio button for each image
3536
imageN[5] = '1' + imx;
3537
labN[0] = '1' + imx;
3538
zdialog_add_widget(STPzd,"radio",imageN,"hbim",labN);
3541
zdialog_stuff(STPzd,"image1",1); // initial image = 1st
3543
STPimage = 0; // initial image
3544
STPradius = 100; // paint radius
3546
takeMouse(STPzd,STP_mousefunc,0); // connect mouse function
3548
cc = E3ww * E3hh; // allocate pixel map
3549
STPpixmap = zmalloc(cc,"STP");
3550
memset(STPpixmap,cimNF,cc); // initial state, blend all images
3552
start_thread(STP_combine_thread,0); // start working thread
3555
zdialog_run(STPzd,STP_tweak_dialog_event,"-10/20"); // run dialog, parallel v.11.07
3556
zdialog_wait(STPzd); // wait for completion
3562
// dialog event and completion callback function
3564
int STP_tweak_dialog_event(zdialog *zd, cchar *event) // v.11.02
3568
if (zd->zstat) // dialog finish
3570
freeMouse(); // disconnect mouse function
3573
if (zd->zstat == 1) STPstat = 1;
3575
if (STPstat == 1) cim_trim(); // cut-off edges
3576
zdialog_free(STPzd);
3577
zfree(STPpixmap); // free pixel map
3580
if (strnEqu(event,"image",5)) { // image radio button
3581
nn = event[5] - '0'; // 1 to cimNF
3582
if (nn > 0 && nn <= cimNF)
3583
STPimage = nn - 1; // 0 to cimNF-1
3587
if (strEqu(event,"radius")) // change paint radius
3588
zdialog_fetch(zd,"radius",STPradius);
3590
if (strEqu(event,"mymouse")) { // toggle mouse capture
3591
zdialog_fetch(zd,"mymouse",mymouse);
3593
takeMouse(zd,STP_mousefunc,0); // connect mouse function
3596
else freeMouse(); // disconnect mouse
3603
// STP dialog mouse function
3604
// paint: during drag, selected image >> STPpixmap (within paint radius) >> E3
3605
// warp: for selected image, cimPXMs >> warp >> cimPXMw >> E3
3607
void STP_mousefunc() // v.11.02
3609
uint16 vpix1[3], *pix3;
3610
int imx, radius, radius2, vstat1;
3611
int mx, my, dx, dy, px3, py3, ww;
3613
double xoff, yoff, sintf[10], costf[10];
3615
radius = STPradius; // paintbrush radius
3616
radius2 = radius * radius;
3618
toparcx = Mxposn - radius; // paintbrush outline circle
3619
toparcy = Myposn - radius;
3620
toparcw = toparch = 2 * radius;
3624
if (LMclick || RMclick) { // mouse click
3625
LMclick = RMclick = 0;
3629
else if (Mxdrag || Mydrag) { // drag in progress
3636
if (mx < 0 || mx > E3ww-1 || my < 0 || my > E3hh-1) // mouse outside image area
3639
for (imx = 0; imx < cimNF; imx++) // pre-calculate trig funcs
3641
sintf[imx] = sin(cimOffs[imx].tf);
3642
costf[imx] = cos(cimOffs[imx].tf);
3645
for (dy = -radius; dy <= radius; dy++) // loop pixels around mouse
3646
for (dx = -radius; dx <= radius; dx++)
3648
if (dx*dx + dy*dy > radius2) continue; // outside radius
3650
px3 = mx + dx; // output pixel
3652
if (px3 < 0 || px3 > E3ww-1) continue; // outside image
3653
if (py3 < 0 || py3 > E3hh-1) continue;
3655
pix3 = PXMpix(E3pxm16,px3,py3); // output pixel
3657
imx = py3 * E3ww + px3; // update pixmap to selected image
3658
STPpixmap[imx] = STPimage;
3661
xoff = cimOffs[imx].xf;
3662
yoff = cimOffs[imx].yf;
3663
px1 = costf[imx] * (px3 - xoff) + sintf[imx] * (py3 - yoff); // input virtual pixel
3664
py1 = costf[imx] * (py3 - yoff) - sintf[imx] * (px3 - xoff);
3665
vstat1 = vpixel(cimPXMw[imx],px1,py1,vpix1);
3671
else pix3[0] = pix3[1] = pix3[2] = 0;
3674
mx = mx - radius - 1; // update window
3675
my = my - radius - 1;
3676
ww = 2 * radius + 3;
3678
mwpaint3(mx,my,ww,ww);
3685
// Combine images in E3pxm16 (not reallocated). Update main window.
3687
void * STP_combine_thread(void *) // v.11.02
3689
void * STP_combine_wthread(void *);
3693
thread_idle_loop(); // wait for work or exit request
3695
mutex_lock(&Fpixmap_lock); // stop window updates
3697
for (int ii = 0; ii < Nwt; ii++) // start worker threads
3698
start_wthread(STP_combine_wthread,&wtnx[ii]);
3699
wait_wthreads(); // wait for completion
3701
mutex_unlock(&Fpixmap_lock); // update window
3705
return 0; // not executed
3709
void * STP_combine_wthread(void *arg) // worker thread
3711
int index = *((int *) (arg));
3712
int px3, py3, vstat1;
3713
int imx, red, green, blue;
3715
double xoff, yoff, sintf[10], costf[10];
3716
uint16 vpix1[3], *pix3;
3718
for (imx = 0; imx < cimNF; imx++) // pre-calculate trig funcs
3720
sintf[imx] = sin(cimOffs[imx].tf);
3721
costf[imx] = cos(cimOffs[imx].tf);
3724
for (py3 = index+1; py3 < E3hh-1; py3 += Nwt) // step through output pixels
3725
for (px3 = 1; px3 < E3ww-1; px3++)
3727
pix3 = PXMpix(E3pxm16,px3,py3);
3729
imx = py3 * E3ww + px3;
3730
imx = STPpixmap[imx];
3732
if (imx < cimNF) // specific image maps to pixel
3734
xoff = cimOffs[imx].xf;
3735
yoff = cimOffs[imx].yf;
3736
px = costf[imx] * (px3 - xoff) + sintf[imx] * (py3 - yoff);
3737
py = costf[imx] * (py3 - yoff) - sintf[imx] * (px3 - xoff);
3738
vstat1 = vpixel(cimPXMw[imx],px,py,vpix1); // corresp. input vpixel
3744
else pix3[0] = pix3[1] = pix3[2] = 0;
3747
else // use blend of all images
3749
red = green = blue = 0;
3751
for (imx = 0; imx < cimNF; imx++)
3753
xoff = cimOffs[imx].xf;
3754
yoff = cimOffs[imx].yf;
3755
px = costf[imx] * (px3 - xoff) + sintf[imx] * (py3 - yoff);
3756
py = costf[imx] * (py3 - yoff) - sintf[imx] * (px3 - xoff);
3757
vstat1 = vpixel(cimPXMw[imx],px,py,vpix1);
3765
pix3[0] = red / cimNF;
3766
pix3[1] = green / cimNF;
3767
pix3[2] = blue / cimNF;
3772
return 0; // not executed, avoid gcc warning
3776
/**************************************************************************
3778
Stack/Noise function
3779
Combine multiple photos of the same subject and average the
3780
pixels for noise reduction.
3782
**************************************************************************/
3784
double STN_initAlignSize = 160; // initial align image size
3785
double STN_imageIncrease = 1.6; // image size increase per align cycle
3786
double STN_sampSize = 6000; // pixel sample size
3788
double STN_initSearchRange = 5.0; // initial search range, +/- pixels
3789
double STN_initSearchStep = 1.0; // initial search step, pixels
3790
double STN_initWarpRange = 2.0; // initial corner warp range
3791
double STN_initWarpStep = 1.0; // initial corner warp step
3792
double STN_searchRange = 2.0; // normal search range
3793
double STN_searchStep = 1.0; // normal search step
3794
double STN_warpRange = 1.0; // normal corner warp range
3795
double STN_warpStep = 0.67; // normal corner warp step
3797
int STN_stat; // 1 = OK, 0 = failed or canceled
3798
int STN_average = 1, STN_median = 0; // use average/median of input pixels
3799
int STN_exlow = 0, STN_exhigh = 0; // exclude low/high pixel
3801
void * STN_align_thread(void *);
3803
void * STN_combine_thread(void *);
3805
editfunc EFstn; // edit function data
3810
void m_STN(GtkWidget *, cchar *) // new v.10.9
3813
int imx, err, ww, hh;
3814
double diffw, diffh;
3816
zfuncs::F1_help_topic = "stack_noise"; // help topic
3818
if (mod_keep()) return; // warn unsaved changes
3819
if (! menulock(1)) return; // test menu lock v.11.07
3822
for (imx = 0; imx < 10; imx++)
3823
{ // clear all file and PXM data
3825
cimPXMf[imx] = cimPXMs[imx] = cimPXMw[imx] = 0;
3830
flist = zgetfileN(ZTX("Select 2 to 9 files"),"openN",curr_file); // select images to combine
3831
if (! flist) return;
3833
for (imx = 0; flist[imx]; imx++); // count selected files
3834
if (imx < 2 || imx > 9) {
3835
zmessageACK(mWin,ZTX("Select 2 to 9 files"));
3839
cimNF = imx; // file count
3840
for (imx = 0; imx < cimNF; imx++)
3841
cimFile[imx] = strdupz(flist[imx],0,"STN"); // set up file list
3843
if (! cim_load_files()) goto cleanup; // load and check all files
3845
ww = cimPXMf[0]->ww;
3846
hh = cimPXMf[0]->hh;
3848
for (imx = 1; imx < cimNF; imx++) // check image compatibility
3850
diffw = abs(ww - cimPXMf[imx]->ww);
3852
diffh = abs(hh - cimPXMf[imx]->hh);
3855
if (diffw > 0.02 || diffh > 0.02) {
3856
zmessageACK(mWin,ZTX("Images are not all the same size"));
3861
free_resources(); // ready to commit
3863
err = f_open(cimFile[0],0); // curr_file = 1st file in list
3864
if (err) goto cleanup;
3866
EFstn.funcname = "stack-noise";
3867
if (! edit_setup(EFstn)) goto cleanup; // setup edit (will lock)
3869
start_thread(STN_align_thread,0); // align each pair of images
3870
wrapup_thread(0); // wait for completion
3871
if (STN_stat != 1) goto cancel;
3873
STN_tweak(); // combine images based on user inputs
3874
if (STN_stat != 1) goto cancel;
3876
CEF->Fmod = 1; // done
3886
for (imx = 0; flist[imx]; imx++) // free file list
3891
for (imx = 0; imx < cimNF; imx++) { // free cim file and PXM data
3892
if (cimFile[imx]) zfree(cimFile[imx]);
3893
if (cimPXMf[imx]) PXM_free(cimPXMf[imx]);
3894
if (cimPXMs[imx]) PXM_free(cimPXMs[imx]);
3895
if (cimPXMw[imx]) PXM_free(cimPXMw[imx]);
3903
// align each image 2nd-last to 1st image
3904
// cimPXMf[*] original image
3905
// cimPXMs[*] scaled and color adjusted for pixel comparisons
3906
// cimPXMw[*] warped for display
3908
void * STN_align_thread(void *) // v.10.9
3910
int imx, im1, im2, ww, hh, ii, nn;
3911
double R, maxtf, mintf, midtf;
3912
double xoff, yoff, toff, dxoff, dyoff;
3914
Fzoom = 0; // fit to window if big
3915
Fblowup = 1; // scale up to window if small
3916
Ffuncbusy++; // v.11.01
3917
cimShrink = 0; // no warp shrinkage (pano)
3918
cimPano = cimPanoV = 0; // no pano mode
3920
for (imx = 1; imx < cimNF; imx++) // loop 2nd to last image
3922
im1 = 0; // images to align
3925
memset(&cimOffs[im1],0,sizeof(cimoffs)); // initial image offsets = 0
3926
memset(&cimOffs[im2],0,sizeof(cimoffs));
3928
ww = cimPXMf[im1]->ww; // image dimensions
3929
hh = cimPXMf[im1]->hh;
3931
nn = ww; // use larger of ww, hh
3932
if (hh > ww) nn = hh;
3933
cimScale = STN_initAlignSize / nn; // initial align image size
3934
if (cimScale > 1.0) cimScale = 1.0;
3936
cimBlend = 0; // no blend width (use all)
3937
cim_get_overlap(im1,im2,cimPXMf); // get overlap area
3938
cim_match_colors(im1,im2,cimPXMf); // get color matching factors
3940
cimSearchRange = STN_initSearchRange; // initial align search range
3941
cimSearchStep = STN_initSearchStep; // initial align search step
3942
cimWarpRange = STN_initWarpRange; // initial align corner warp range
3943
cimWarpStep = STN_initWarpStep; // initial align corner warp step
3944
cimSampSize = STN_sampSize; // pixel sample size for align/compare
3945
cimNsearch = 0; // reset align search counter
3947
while (true) // loop, increasing image size
3949
cim_scale_image(im1,cimPXMs); // scale images to cimScale
3950
cim_scale_image(im2,cimPXMs);
3952
cim_adjust_colors(cimPXMs[im1],1); // apply color adjustments
3953
cim_adjust_colors(cimPXMs[im2],2);
3955
cim_warp_image(im1); // warp images for show
3956
cim_warp_image(im2);
3958
cimShowIm1 = im1; // show these two images
3959
cimShowIm2 = im2; // with 50/50 blend
3961
cim_show_images(1,0); // (y offset can change)
3963
cim_get_overlap(im1,im2,cimPXMs); // get overlap area v.11.04
3964
cim_get_redpix(im1); // get high-contrast pixels
3966
cim_align_image(im1,im2); // align im2 to im1
3968
zfree(cimRedpix); // clear red pixels
3971
if (cimScale == 1.0) break; // done
3973
R = STN_imageIncrease; // next larger image size
3974
cimScale = cimScale * R;
3975
if (cimScale > 0.85) { // if close to end, jump to end
3980
cimOffs[im1].xf *= R; // scale offsets for larger image
3981
cimOffs[im1].yf *= R;
3982
cimOffs[im2].xf *= R;
3983
cimOffs[im2].yf *= R;
3985
for (ii = 0; ii < 4; ii++) {
3986
cimOffs[im1].wx[ii] *= R;
3987
cimOffs[im1].wy[ii] *= R;
3988
cimOffs[im2].wx[ii] *= R;
3989
cimOffs[im2].wy[ii] *= R;
3992
cimSearchRange = STN_searchRange; // align search range
3993
cimSearchStep = STN_searchStep; // align search step size
3994
cimWarpRange = STN_warpRange; // align corner warp range
3995
cimWarpStep = STN_warpStep; // align corner warp step size
3999
toff = cimOffs[0].tf; // balance +/- thetas
4000
maxtf = mintf = toff;
4001
for (imx = 1; imx < cimNF; imx++) {
4002
toff = cimOffs[imx].tf;
4003
if (toff > maxtf) maxtf = toff;
4004
if (toff < mintf) mintf = toff;
4006
midtf = 0.5 * (maxtf + mintf);
4008
for (imx = 0; imx < cimNF; imx++)
4009
cimOffs[imx].tf -= midtf;
4011
for (im1 = 0; im1 < cimNF-1; im1++) // adjust x/y offsets for images after im1
4012
for (im2 = im1+1; im2 < cimNF; im2++) // due to im1 theta offset
4014
toff = cimOffs[im1].tf;
4015
xoff = cimOffs[im2].xf - cimOffs[im1].xf;
4016
yoff = cimOffs[im2].yf - cimOffs[im1].yf;
4017
dxoff = yoff * sin(toff);
4018
dyoff = xoff * sin(toff);
4019
cimOffs[im2].xf -= dxoff;
4020
cimOffs[im2].yf += dyoff;
4023
Fzoom = Fblowup = 0;
4027
return 0; // not executed
4031
// change pixel combination according to user input
4033
void STN_tweak() // v.10.9
4037
int STN_tweak_dialog_event(zdialog *zd, cchar *event);
4039
// Adjust Pixel Composition
4041
// (o) use average (o) use median
4042
// [x] omit lowest value
4043
// [x] omit highest value
4045
zd = zdialog_new(ZTX("Adjust Pixel Composition"),mWin,Bdone,Bcancel,null);
4046
zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=3");
4047
zdialog_add_widget(zd,"radio","average","hb1","use average","space=3");
4048
zdialog_add_widget(zd,"radio","median","hb1","use median","space=3");
4049
zdialog_add_widget(zd,"hbox","hb2","dialog",0,"space=3");
4050
zdialog_add_widget(zd,"check","exlow","hb2","omit low pixel","space=3");
4051
zdialog_add_widget(zd,"check","exhigh","hb2","omit high pixel","space=3");
4053
zdialog_stuff(zd,"average",1); // default = average
4054
zdialog_stuff(zd,"median",0);
4055
zdialog_stuff(zd,"exlow",0);
4056
zdialog_stuff(zd,"exhigh",0);
4063
start_thread(STN_combine_thread,0); // start working thread
4066
zdialog_resize(zd,250,0);
4067
zdialog_run(zd,STN_tweak_dialog_event,"-10/20"); // run dialog, parallel v.11.07
4068
zdialog_wait(zd); // wait for completion
4074
// dialog event and completion callback function
4076
int STN_tweak_dialog_event(zdialog *zd, cchar *event) // v.10.9
4078
if (zd->zstat) { // dialog finish
4079
if (zd->zstat == 1) STN_stat = 1;
4083
if (STN_stat == 1) cim_trim(); // trim edges v.10.9
4086
if (strEqu(event,"average")) {
4087
zdialog_fetch(zd,"average",STN_average);
4091
if (strEqu(event,"median")) {
4092
zdialog_fetch(zd,"median",STN_median);
4096
if (strEqu(event,"exlow")) {
4097
zdialog_fetch(zd,"exlow",STN_exlow);
4101
if (strEqu(event,"exhigh")) {
4102
zdialog_fetch(zd,"exhigh",STN_exhigh);
4110
// compute mean/median mix for each output pixel and update E3 image
4112
void * STN_combine_thread(void *) // v.10.9
4114
void * STN_combine_wthread(void *arg); // worker thread
4118
thread_idle_loop(); // wait for work or exit request
4120
for (int ii = 0; ii < Nwt; ii++) // start worker threads
4121
start_wthread(STN_combine_wthread,&wtnx[ii]);
4122
wait_wthreads(); // wait for completion
4125
mwpaint2(); // update window
4128
return 0; // not executed
4134
void * STN_combine_wthread(void *arg) // v.10.9
4136
int index = *((int *) arg);
4137
int imx, vstat, px3, py3;
4138
int red, green, blue;
4139
int ii, ns, ns1, ns2;
4140
int Rlist[10], Glist[10], Blist[10];
4142
double xoff, yoff, sintf[10], costf[10];
4143
uint16 *pix3, vpix[3];
4145
// input layers 0 1 2 3 4 5 6 7 8 9 10
4146
int nsx[11][2] = { {0,0}, {0,0}, {0,1}, {1,1}, {1,2}, {2,2}, {2,3}, {2,4}, {2,5}, {3,5}, {3,6} };
4148
for (imx = 0; imx < cimNF; imx++) // pre-calculate trig funcs
4150
sintf[imx] = sin(cimOffs[imx].tf);
4151
costf[imx] = cos(cimOffs[imx].tf);
4154
for (py3 = index+1; py3 < E3hh-1; py3 += Nwt) // step through output pixels
4155
for (px3 = 1; px3 < E3ww-1; px3++)
4157
for (imx = ns = 0; imx < cimNF; imx++) // get aligned input pixels
4159
xoff = cimOffs[imx].xf;
4160
yoff = cimOffs[imx].yf;
4161
px = costf[imx] * (px3 - xoff) + sintf[imx] * (py3 - yoff);
4162
py = costf[imx] * (py3 - yoff) - sintf[imx] * (px3 - xoff);
4164
vstat = vpixel(cimPXMw[imx],px,py,vpix);
4166
Rlist[ns] = vpix[0]; // add pixel RGB values to list
4167
Glist[ns] = vpix[1];
4168
Blist[ns] = vpix[2];
4175
if (STN_exlow || STN_exhigh || STN_median) { // RGB values must be sorted
4181
red = green = blue = 0;
4183
if (STN_average) // average the input pixels
4185
ns1 = 0; // low and high RGB values
4188
if (STN_exlow) { // exclude low
4190
if (ns1 > ns2) ns1--;
4193
if (STN_exhigh) { // exclude high
4195
if (ns1 > ns2) ns2++;
4198
for (ii = ns1; ii <= ns2; ii++) // sum remaining RGB levels
4205
ns = ns2 - ns1 + 1; // sample count
4207
red = red / ns; // output RGB = average
4212
if (STN_median) // use median input pixels
4214
ns1 = nsx[ns][0]; // middle group of pixels
4217
for (ii = ns1; ii <= ns2; ii++)
4224
ns = ns2 - ns1 + 1; // sample count
4226
red = red / ns; // output RGB = average
4231
pix3 = PXMpix(E3pxm16,px3,py3); // output pixel
4238
return 0; // not executed
4242
/**************************************************************************
4244
Panorama function: join 2, 3, or 4 images.
4246
***************************************************************************/
4248
int panStat; // 1 = OK
4249
zdialog *panozd = 0; // pre-align dialog
4251
double panPreAlignSize = 1000; // pre-align image size (ww)
4252
double panInitAlignSize = 200; // initial align image size
4253
double panImageIncrease = 1.6; // image size increase per align cycle
4254
double panSampSize = 10000; // pixel sample size
4256
double panPreAlignBlend = 0.30; // pre-align blend width * ww
4257
double panInitBlend = 0.20; // initial blend width during auto-align
4258
double panFinalBlend = 0.08; // final blend width * ww
4259
double panBlendDecrease = 0.8; // blend width reduction per align cycle
4261
double panInitSearchRange = 5.0; // initial search range, +/- pixels
4262
double panInitSearchStep = 0.7; // initial search step, pixels
4263
double panInitWarpRange = 4.0; // initial corner warp range, +/- pixels
4264
double panInitWarpStep = 1.0; // initial corner warp step, pixels
4265
double panSearchRange = 3.0; // normal search range, +/- pixels
4266
double panSearchStep = 1.0; // normal search step, pixels
4267
double panWarpRange = 2.0; // normal corner warp range, +/- pixels
4268
double panWarpStep = 1.0; // normal corner warp step, pixels
4270
void pano_prealign(); // manual pre-align
4271
void pano_align(); // auto fine-align
4272
void pano_tweak(); // user color tweak
4274
editfunc EFpano; // edit function data
4279
void m_pano(GtkWidget *, cchar *) // v.10.7
4284
zfuncs::F1_help_topic = "panorama"; // help topic
4286
if (mod_keep()) return; // warn unsaved changes
4287
if (! menulock(1)) return; // test menu lock v.11.07
4290
for (imx = 0; imx < 10; imx++)
4291
{ // clear all file and PXM data
4293
cimPXMf[imx] = cimPXMs[imx] = cimPXMw[imx] = 0;
4297
flist = zgetfileN(ZTX("Select 2 to 4 files"),"openN",curr_file); // select images to combine
4298
if (! flist) return;
4300
for (imx = 0; flist[imx]; imx++); // count selected files
4301
if (imx < 2 || imx > 4) {
4302
zmessageACK(mWin,ZTX("Select 2 to 4 files"));
4306
cimNF = imx; // file count
4307
for (imx = 0; imx < cimNF; imx++)
4308
cimFile[imx] = strdupz(flist[imx],0,"pano"); // set up file list
4310
if (! cim_load_files()) goto cleanup; // load and check all files
4312
free_resources(); // ready to commit
4314
err = f_open(cimFile[0],0); // curr_file = 1st file in list
4315
if (err) goto cleanup;
4317
EFpano.funcname = "pano";
4318
if (! edit_setup(EFpano)) goto cleanup; // setup edit (will lock)
4320
cimShowAll = 1; // for cim_show_images(), show all v.10.9
4321
cimShrink = 0; // no warp shrinkage v.11.04
4322
cimPano = 1; // horizontal pano mode v.11.04
4325
pano_prealign(); // manual pre-alignment
4326
if (panStat != 1) goto cancel;
4328
pano_align(); // auto full alignment
4329
if (panStat != 1) goto cancel;
4331
pano_tweak(); // manual color adjustment
4332
if (panStat != 1) goto cancel;
4334
CEF->Fmod = 1; // done
4338
cancel: // failed or canceled
4339
edit_cancel(EFpano);
4344
for (imx = 0; flist[imx]; imx++) // free file list
4349
for (imx = 0; imx < cimNF; imx++) { // free cim file and PXM data
4350
if (cimFile[imx]) zfree(cimFile[imx]);
4351
if (cimPXMf[imx]) PXM_free(cimPXMf[imx]);
4352
if (cimPXMs[imx]) PXM_free(cimPXMs[imx]);
4353
if (cimPXMw[imx]) PXM_free(cimPXMw[imx]);
4361
// perform manual pre-align of all images
4362
// returns alignment data in cimOffs[*]
4363
// lens_mm and lens_bow may also be altered
4365
void pano_prealign() // v.10.7
4367
int pano_prealign_event(zdialog *zd, cchar *event); // dialog event function
4368
void * pano_prealign_thread(void *); // working thread
4370
int imx, ww, err = 0;
4371
cchar *exifkey = { exif_focal_length_key };
4372
char lensname[40], **pp = 0;
4374
cchar *align_mess = ZTX("Drag images into rough alignment.\n"
4375
"To rotate, drag from lower edge.");
4376
cchar *search_mess = ZTX("Search for lens mm and bow");
4378
pp = info_get(curr_file,&exifkey,1); // get lens mm from EXIF if available
4380
err = convSD(*pp, lens_mm, 20, 1000); // leave lens_bow unchanged (no source)
4381
strcpy(lensname,"(EXIF)"); // lens name = EXIF
4384
if (! pp || ! *pp || err) { // not available
4385
lens_mm = lens4_mm[curr_lens]; // get curr. lens mm, bow, name
4386
lens_bow = lens4_bow[curr_lens];
4388
strncatv(lensname,40,"(",lens4_name[curr_lens],")",null);
4391
for (imx = 0; imx < 10; imx++) // set all alignment offsets = 0
4392
memset(&cimOffs[imx],0,sizeof(cimoffs));
4394
for (imx = ww = 0; imx < cimNF; imx++) // sum image widths
4395
ww += cimPXMf[imx]->ww;
4397
cimScale = 1.4 * panPreAlignSize / ww; // set alignment image scale
4398
if (cimScale > 1.0) cimScale = 1.0; // (* 0.7 after overlaps)
4400
for (imx = 0; imx < cimNF; imx++) // scale images > cimPXMs[*]
4401
cim_scale_image(imx,cimPXMs);
4403
for (imx = 0; imx < cimNF; imx++) { // curve images, cimPXMs[*] replaced
4404
cim_curve_image(imx);
4405
cimPXMw[imx] = PXM_copy(cimPXMs[imx]); // copy to cimPXMw[*] for display
4408
cimOffs[0].xf = cimOffs[0].yf = 0; // first image at (0,0)
4410
for (imx = 1; imx < cimNF; imx++) // position images with 30% overlap
4411
{ // in horizontal row
4412
cimOffs[imx].xf = cimOffs[imx-1].xf + 0.7 * cimPXMw[imx-1]->ww;
4413
cimOffs[imx].yf = cimOffs[imx-1].yf;
4416
Fzoom = 0; // scale image to fit window
4417
Fblowup = 1; // magnify small image to window size
4419
cimBlend = panPreAlignBlend * cimPXMw[1]->ww; // overlap in align window
4420
cim_show_images(1,0); // combine and show images in main window
4422
panozd = zdialog_new(ZTX("Pre-align Images"),mWin,Bproceed,Bcancel,null); // start pre-align dialog
4423
zdialog_add_widget(panozd,"label","lab1","dialog",align_mess,"space=5");
4424
zdialog_add_widget(panozd,"hbox","hb1","dialog",0,"space=2");
4425
zdialog_add_widget(panozd,"spin","spmm","hb1","22|200|0.1|35","space=5"); // [ 35 ] lens mm (source)
4426
zdialog_add_widget(panozd,"label","labmm","hb1",ZTX("lens mm")); // [ 0.3 ] lens bow
4427
zdialog_add_widget(panozd,"label","lablens","hb1","","space=5"); // [resize] resize window
4428
zdialog_add_widget(panozd,"hbox","hb2","dialog",0,"space=2"); // [search] search lens mm and bow
4429
zdialog_add_widget(panozd,"spin","spbow","hb2","-9|9|0.01|0","space=5");
4430
zdialog_add_widget(panozd,"label","labbow","hb2",ZTX("lens bow"));
4431
zdialog_add_widget(panozd,"hbox","hb3","dialog",0,"space=2");
4432
zdialog_add_widget(panozd,"button","resize","hb3",ZTX("Resize"),"space=5");
4433
zdialog_add_widget(panozd,"label","labsiz","hb3",ZTX("resize window"),"space=5");
4434
zdialog_add_widget(panozd,"hbox","hb4","dialog",0,"space=2");
4435
zdialog_add_widget(panozd,"button","search","hb4",Bsearch,"space=5");
4436
zdialog_add_widget(panozd,"label","labsearch","hb4",search_mess,"space=5");
4438
zdialog_stuff(panozd,"spmm",lens_mm); // stuff lens data
4439
zdialog_stuff(panozd,"spbow",lens_bow);
4440
zdialog_stuff(panozd,"lablens",lensname); // show source of lens data
4442
panStat = -1; // busy status
4443
gdk_window_set_cursor(drWin->window,dragcursor); // set drag cursor v.11.03
4444
zdialog_run(panozd,pano_prealign_event,"-10/20"); // start dialog v.11.07
4445
start_thread(pano_prealign_thread,0); // start working thread
4446
zdialog_wait(panozd); // wait for dialog completion
4447
gdk_window_set_cursor(drWin->window,0); // restore normal cursor v.11.03
4448
Fzoom = Fblowup = 0;
4453
// pre-align dialog event function
4455
int pano_prealign_event(zdialog *zd, cchar *event) // v.10.7
4460
if (strstr("spmm spbow",event)) {
4461
zdialog_fetch(zd,"spmm",lens_mm); // get revised lens data
4462
zdialog_fetch(zd,"spbow",lens_bow);
4465
if (strEqu(event,"resize")) // allocate new E3 image
4466
cim_show_images(1,0);
4468
if (strEqu(event,"search")) { // search for optimal lens parms
4470
zmessageACK(mWin,ZTX("use two images only"));
4471
else panStat = 2; // tell thread to search
4475
if (zd->zstat) // dialog complete
4477
if (zd->zstat == 1) // proceed
4479
else // cancel or other
4482
zdialog_free(panozd); // kill dialog
4483
wrapup_thread(0); // wait for thread
4485
if (! panStat) return 0; // canceled
4487
for (imx = 0; imx < cimNF-1; imx++) // check for enough overlap
4489
overlap = cim_get_overlap(imx,imx+1,cimPXMs); // v.11.04
4490
if (overlap < panFinalBlend) { // v.11.09
4491
zmessageACK(mWin,ZTX("Too little overlap, cannot align"));
4502
// pre-align working thread
4503
// convert mouse and KB events into image movements // overhauled v.11.02
4505
void * pano_prealign_thread(void *)
4507
void pano_autolens();
4512
int im1, im2, imm, imx;
4513
int mx0, my0, mx, my; // mouse drag origin, position
4514
int xoff, yoff, lox, hix;
4516
int ww, hh, rotate, midx;
4517
double lens_mm0, lens_bow0;
4518
double dx, dy, t1, t2, dt;
4520
imm = ww = hh = rotate = xoff = yoff = 0; // stop compiler warnings
4522
lens_mm0 = lens_mm; // to detect changes
4523
lens_bow0 = lens_bow;
4525
mx0 = my0 = 0; // no drag in progress
4526
Mcapture = KBcapture = 1; // capture mouse drag and KB keys
4528
cimBlend = 0; // full blend during pre-align
4530
while (true) // loop and align until done
4532
zsleep(0.05); // logic simplified
4534
if (panStat == 2) { // dialog search button
4535
panStat = -1; // back to busy status
4539
if (panStat != -1) break; // quit signal from dialog
4541
if (lens_mm != lens_mm0 || lens_bow != lens_bow0) { // change in lens parameters
4543
lens_bow0 = lens_bow;
4545
for (imx = 0; imx < cimNF; imx++) { // re-curve images
4546
cim_scale_image(imx,cimPXMs);
4547
cim_curve_image(imx);
4548
PXM_free(cimPXMw[imx]);
4549
cimPXMw[imx] = PXM_copy(cimPXMs[imx]);
4552
cim_show_images(1,0); // combine and show images
4556
if (KBkey) { // KB input
4557
if (KBkey == GDK_Left) cimOffs[imm].xf -= 0.5; // tweak alignment offsets
4558
if (KBkey == GDK_Right) cimOffs[imm].xf += 0.5;
4559
if (KBkey == GDK_Up) cimOffs[imm].yf -= 0.5;
4560
if (KBkey == GDK_Down) cimOffs[imm].yf += 0.5;
4561
if (KBkey == GDK_r) cimOffs[imm].tf += 0.0005;
4562
if (KBkey == GDK_l) cimOffs[imm].tf -= 0.0005;
4565
cim_show_images(0,0); // combine and show images
4569
if (! Mxdrag && ! Mydrag) // no drag underway
4570
mx0 = my0 = 0; // reset drag origin
4572
if (Mxdrag || Mydrag) // mouse drag underway
4574
mx = Mxdrag; // mouse position in image
4577
if (! mx0 && ! my0) // new drag
4579
mx0 = mx; // set drag origin
4583
for (imx = 0; imx < cimNF; imx++) // find image with midpoint
4584
{ // closest to mouse x
4585
lox = cimOffs[imx].xf;
4586
hix = lox + cimPXMw[imx]->ww;
4587
midx = (lox + hix) / 2;
4588
sepx = abs(midx - mx0);
4589
if (sepx < minsep) {
4591
imm = imx; // image to drag or rotate
4595
xoff = cimOffs[imm].xf;
4596
yoff = cimOffs[imm].yf;
4597
ww = cimPXMw[imm]->ww;
4598
hh = cimPXMw[imm]->hh;
4600
rotate = 0; // if drag at bottom edge,
4601
if (my0 > yoff + 0.85 * hh) rotate = 1; // set rotate flag v.11.04
4604
if (mx != mx0 || my != my0) // drag is progressing
4606
dx = mx - mx0; // mouse movement
4609
if (rotate && my0 > yoff && my > yoff) // rotation
4612
lox = cimOffs[imm].xf; // if there is an image to the left,
4613
hix = cimOffs[imm-1].xf + cimPXMw[imm-1]->ww; // midx = midpoint of overlap
4614
midx = (lox + hix) / 2;
4616
else midx = 0; // this is the leftmost image
4618
t1 = atan(1.0 * (mx0-xoff) / (my0-yoff));
4619
t2 = atan(1.0 * (mx-xoff) / (my-yoff));
4620
dt = t1 - t2; // angle change
4621
dx = dt * (hh/2 + yoff); // pivot = middle of overlap on left
4622
dy = -dt * (midx-xoff);
4625
else dt = 0; // x/y drag
4627
cimOffs[imm].xf += dx; // update image
4628
cimOffs[imm].yf += dy;
4629
cimOffs[imm].tf += dt;
4630
xoff = cimOffs[imm].xf; // v.11.04
4631
yoff = cimOffs[imm].yf;
4633
cim_show_images(0,0); // show combined images
4635
mx0 = mx; // next drag origin = current mouse
4640
for (im1 = 0; im1 < cimNF-1; im1++) // track image order changes
4643
if (cimOffs[im2].xf < cimOffs[im1].xf)
4645
ftemp = cimFile[im2]; // switch filespecs
4646
cimFile[im2] = cimFile[im1];
4647
cimFile[im1] = ftemp;
4648
pxmtemp = cimPXMf[im2]; // switch images
4649
cimPXMf[im2] = cimPXMf[im1];
4650
cimPXMf[im1] = pxmtemp;
4651
pxmtemp = cimPXMs[im2]; // scaled images
4652
cimPXMs[im2] = cimPXMs[im1];
4653
cimPXMs[im1] = pxmtemp;
4654
pxmtemp = cimPXMw[im2]; // warped images
4655
cimPXMw[im2] = cimPXMw[im1];
4656
cimPXMw[im1] = pxmtemp;
4657
offstemp = cimOffs[im2]; // offsets
4658
cimOffs[im2] = cimOffs[im1];
4659
cimOffs[im1] = offstemp;
4660
if (imm == im1) imm = im2; // current drag image
4661
else if (imm == im2) imm = im1;
4667
KBcapture = Mcapture = 0;
4669
return 0; // not executed, stop g++ warning
4673
// optimize lens parameters
4674
// inputs and outputs:
4675
// pre-aligned images cimPXMw[0] and [1]
4676
// offsets in cimOffs[0] and [1]
4677
// lens_mm, lens_bow
4679
void pano_autolens() // v.10.7
4681
double mm_range, bow_range, xf_range, yf_range, tf_range;
4682
double squeeze, xf_rfinal, rnum, matchB, matchlev;
4683
double overlap, lens_mmB, lens_bowB;
4684
int imx, randcount = 0;
4687
overlap = cim_get_overlap(0,1,cimPXMs); // v.11.04
4688
if (overlap < 0.1) {
4689
threadmessage = ZTX("Too little overlap, cannot align");
4693
Ffuncbusy++; // v.11.01
4695
cimSampSize = 2000; // v.11.03
4698
mm_range = 0.1 * lens_mm; // set initial search ranges v.11.03
4699
bow_range = 0.3 * lens_bow;
4700
if (bow_range < 0.5) bow_range = 0.5;
4704
xf_rfinal = 0.3; // final xf range - when to quit
4706
cim_match_colors(0,1,cimPXMw); // adjust colors for image matching
4707
cim_adjust_colors(cimPXMs[0],1);
4708
cim_adjust_colors(cimPXMw[0],1);
4709
cim_adjust_colors(cimPXMs[1],2);
4710
cim_adjust_colors(cimPXMw[1],2);
4712
lens_mmB = lens_mm; // starting point
4713
lens_bowB = lens_bow;
4714
offsetsB = cimOffs[1];
4721
srand48(time(0) + randcount++);
4722
lens_mm = lens_mmB + mm_range * (drand48() - 0.5); // new random lens factors
4723
lens_bow = lens_bowB + bow_range * (drand48() - 0.5); // within search range
4725
for (imx = 0; imx <= 1; imx++) { // re-curve images
4726
cim_scale_image(imx,cimPXMs);
4727
cim_curve_image(imx);
4728
PXM_free(cimPXMw[imx]);
4729
cimPXMw[imx] = PXM_copy(cimPXMs[imx]);
4732
cim_get_redpix(0); // get high-contrast pixels v.11.03
4733
cim_show_images(0,0); // combine and show images
4735
squeeze = 0.97; // search range reduction v.10.7
4737
for (int ii = 0; ii < 1000; ii++) // loop random x/y/t alignments
4740
if (rnum < 0.33) // random change some alignment offset
4741
cimOffs[1].xf = offsetsB.xf + xf_range * (drand48() - 0.5);
4742
else if (rnum < 0.67)
4743
cimOffs[1].yf = offsetsB.yf + yf_range * (drand48() - 0.5);
4745
cimOffs[1].tf = offsetsB.tf + tf_range * (drand48() - 0.5);
4747
matchlev = cim_match_images(0,1); // test quality of image alignment
4749
sprintf(SB_text,"align: %d match: %.5f lens: %.1f %.2f", // update status bar
4750
++cimNsearch, matchB, lens_mmB, lens_bowB);
4751
zmainloop(); // v.11.11.1
4753
if (sigdiff(matchlev,matchB,0.00001) > 0) {
4754
matchB = matchlev; // save new best fit
4755
lens_mmB = lens_mm; // alignment is better
4756
lens_bowB = lens_bow;
4757
offsetsB = cimOffs[1];
4758
cim_show_images(0,0);
4759
squeeze = 1; // keep same search range as long
4760
break; // as improvements are found
4763
if (panStat != -1) goto done; // user kill
4766
if (xf_range < xf_rfinal) goto done; // finished
4768
sprintf(SB_text,"align: %d match: %.5f lens: %.1f %.2f", // update status bar
4769
cimNsearch, matchB, lens_mmB, lens_bowB);
4770
zmainloop(); // v.11.11.1
4772
mm_range = squeeze * mm_range; // reduce search range if no
4773
if (mm_range < 0.02 * lens_mmB) mm_range = 0.02 * lens_mmB; // improvements were found
4774
bow_range = squeeze * bow_range;
4775
if (bow_range < 0.1 * lens_bowB) bow_range = 0.1 * lens_bowB;
4776
if (bow_range < 0.2) bow_range = 0.2;
4777
xf_range = squeeze * xf_range;
4778
yf_range = squeeze * yf_range;
4779
tf_range = squeeze * tf_range;
4786
lens_mm = lens_mmB; // save best lens params found
4787
lens_bow = lens_bowB;
4788
if (panStat == -1 && panozd) { // unless killed
4789
zdialog_stuff(panozd,"spmm",lens_mm);
4790
zdialog_stuff(panozd,"spbow",lens_bow);
4793
cimSampSize = panSampSize; // restore
4795
cim_show_images(1,0); // images are left color-matched
4801
// start with very small image size
4802
// search around offset values for best match
4803
// increase image size and loop until full-size
4805
void pano_align() // v.10.7
4807
int imx, im1, im2, ww;
4808
double R, dx, dy, dt;
4812
Fzoom = 0; // scale E3 to fit window
4813
Fblowup = 1; // magnify small image to window size
4814
Ffuncbusy++; // v.11.01
4816
for (imx = 0; imx < cimNF; imx++) {
4817
cimOffs[imx].xf = cimOffs[imx].xf / cimScale; // scale x/y offsets for full-size images
4818
cimOffs[imx].yf = cimOffs[imx].yf / cimScale;
4821
cimScale = 1.0; // full-size
4823
for (imx = 0; imx < cimNF; imx++) {
4824
PXM_free(cimPXMs[imx]);
4825
cimPXMs[imx] = PXM_copy(cimPXMf[imx]); // copy full-size images
4826
cim_curve_image(imx); // curve them
4829
cimBlend = 0.3 * cimPXMs[0]->ww;
4830
cim_get_overlap(0,1,cimPXMs); // match images 0 & 1 in overlap area
4831
cim_match_colors(0,1,cimPXMs);
4832
cim_adjust_colors(cimPXMf[0],1); // image 0 << profile 1
4833
cim_adjust_colors(cimPXMf[1],2); // image 1 << profile 2
4836
cimBlend = 0.3 * cimPXMs[1]->ww;
4837
cim_get_overlap(1,2,cimPXMs);
4838
cim_match_colors(1,2,cimPXMs);
4839
cim_adjust_colors(cimPXMf[0],1);
4840
cim_adjust_colors(cimPXMf[1],1);
4841
cim_adjust_colors(cimPXMf[2],2);
4845
cimBlend = 0.3 * cimPXMs[2]->ww;
4846
cim_get_overlap(2,3,cimPXMs);
4847
cim_match_colors(2,3,cimPXMs);
4848
cim_adjust_colors(cimPXMf[0],1);
4849
cim_adjust_colors(cimPXMf[1],1);
4850
cim_adjust_colors(cimPXMf[2],1);
4851
cim_adjust_colors(cimPXMf[3],2);
4854
cimScale = panInitAlignSize / cimPXMf[1]->hh; // initial align image scale
4855
if (cimScale > 1.0) cimScale = 1.0;
4857
for (imx = 0; imx < cimNF; imx++) { // scale offsets for image scale
4858
cimOffs[imx].xf = cimOffs[imx].xf * cimScale;
4859
cimOffs[imx].yf = cimOffs[imx].yf * cimScale;
4862
cimSearchRange = panInitSearchRange; // initial align search range
4863
cimSearchStep = panInitSearchStep; // initial align search step
4864
cimWarpRange = panInitWarpRange; // initial align corner warp range
4865
cimWarpStep = panInitWarpStep; // initial align corner warp step
4866
ww = cimPXMf[0]->ww * cimScale; // initial align image width
4867
cimBlend = ww * panInitBlend; // initial align blend width
4868
cimSampSize = panSampSize; // pixel sample size for align/compare
4869
cimNsearch = 0; // reset align search counter
4871
while (true) // loop, increasing image size
4873
for (imx = 0; imx < cimNF; imx++) { // prepare images
4874
cim_scale_image(imx,cimPXMs); // scale to new size
4875
cim_curve_image(imx); // curve based on lens params
4876
cim_warp_image_pano(imx,1); // apply corner warps
4879
cim_show_images(1,0); // show with 50/50 blend in overlaps
4881
for (im1 = 0; im1 < cimNF-1; im1++) // fine-align each image with left neighbor
4885
offsets0 = cimOffs[im2]; // save initial alignment offsets
4886
overlap = cim_get_overlap(im1,im2,cimPXMs); // get overlap area v.11.04
4887
if (overlap < panFinalBlend-2) {
4888
zmessageACK(mWin,ZTX("Too little overlap, cannot align")); // v.11.03
4891
cim_get_redpix(im1); // get high-contrast pixels
4893
cim_align_image(im1,im2); // search for best offsets and warps
4895
zfree(cimRedpix); // clear red pixels
4898
dx = cimOffs[im2].xf - offsets0.xf; // changes from initial offsets
4899
dy = cimOffs[im2].yf - offsets0.yf;
4900
dt = cimOffs[im2].tf - offsets0.tf;
4902
for (imx = im2+1; imx < cimNF; imx++) // propagate to following images
4904
cimOffs[imx].xf += dx;
4905
cimOffs[imx].yf += dy;
4906
cimOffs[imx].tf += dt;
4907
ww = cimOffs[imx].xf - cimOffs[im2].xf;
4908
cimOffs[imx].yf += ww * dt;
4912
if (cimScale == 1.0) goto success; // done
4914
R = panImageIncrease; // next larger image size
4915
cimScale = cimScale * R;
4916
if (cimScale > 0.85) { // if close to end, jump to end
4921
for (imx = 0; imx < cimNF; imx++) // scale offsets for new size
4923
cimOffs[imx].xf *= R;
4924
cimOffs[imx].yf *= R;
4926
for (int ii = 0; ii < 4; ii++) {
4927
cimOffs[imx].wx[ii] *= R;
4928
cimOffs[imx].wy[ii] *= R;
4932
cimSearchRange = panSearchRange; // align search range
4933
cimSearchStep = panSearchStep; // align search step size
4934
cimWarpRange = panWarpRange; // align corner warp range
4935
cimWarpStep = panWarpStep; // align corner warp step size
4937
cimBlend = cimBlend * panBlendDecrease * R; // blend width, reduced
4938
ww = cimPXMf[0]->ww * cimScale;
4939
if (cimBlend < panFinalBlend * ww)
4940
cimBlend = panFinalBlend * ww; // stay above minimum
4949
cimBlend = 1; // tiny blend (increase in tweak if wanted)
4950
Fzoom = Fblowup = 0;
4952
cim_show_images(0,0);
4957
// get user inputs for RGB changes and blend width, update cimPXMw[*]
4959
void pano_tweak() // v.10.7
4961
int pano_tweak_event(zdialog *zd, cchar *event); // dialog event function
4963
cchar *tweaktitle = ZTX("Match Brightness and Color");
4964
char imageN[8] = "imageN";
4967
cimBlend = 1; // init. blend width
4969
panozd = zdialog_new(tweaktitle,mWin,Bdone,Bcancel,null);
4971
zdialog_add_widget(panozd,"hbox","hbim","dialog",0,"space=5");
4972
zdialog_add_widget(panozd,"label","labim","hbim",ZTX("image"),"space=5"); // image (o) (o) (o) (o)
4973
zdialog_add_widget(panozd,"hbox","hbc1","dialog",0,"homog"); //
4974
zdialog_add_widget(panozd,"label","labred","hbc1",Bred); // red green blue
4975
zdialog_add_widget(panozd,"label","labgreen","hbc1",Bgreen); // [_____] [_____] [_____]
4976
zdialog_add_widget(panozd,"label","labblue","hbc1",Bblue); //
4977
zdialog_add_widget(panozd,"hbox","hbc2","dialog",0,"homog"); // brightness [___] [apply]
4978
zdialog_add_widget(panozd,"spin","red","hbc2","50|200|0.1|100","space=5"); //
4979
zdialog_add_widget(panozd,"spin","green","hbc2","50|200|0.1|100","space=5"); // --------------------------
4980
zdialog_add_widget(panozd,"spin","blue","hbc2","50|200|0.1|100","space=5"); //
4981
zdialog_add_widget(panozd,"hbox","hbbri","dialog",0,"space=5"); // [auto color] [file color]
4982
zdialog_add_widget(panozd,"label","labbr","hbbri",Bbrightness,"space=5"); //
4983
zdialog_add_widget(panozd,"spin","bright","hbbri","50|200|0.1|100"); // --------------------------
4984
zdialog_add_widget(panozd,"button","brapp","hbbri",Bapply,"space=10"); //
4985
zdialog_add_widget(panozd,"hsep","hsep","dialog",0,"space=5"); // blend width [___] [apply]
4986
zdialog_add_widget(panozd,"hbox","hbc3","dialog",0,"space=5"); //
4987
zdialog_add_widget(panozd,"button","auto","hbc3",ZTX("auto color"),"space=5"); // [done] [cancel]
4988
zdialog_add_widget(panozd,"button","file","hbc3",ZTX("file color"),"space=5");
4989
zdialog_add_widget(panozd,"hsep","hsep","dialog",0,"space=5");
4990
zdialog_add_widget(panozd,"hbox","hbblen","dialog",0);
4991
zdialog_add_widget(panozd,"label","labbl","hbblen",Bblendwidth,"space=5");
4992
zdialog_add_widget(panozd,"spin","blend","hbblen","1|300|1|1");
4993
zdialog_add_widget(panozd,"button","blapp","hbblen",Bapply,"space=15");
4995
for (imx = 0; imx < cimNF; imx++) { // add radio button per image
4996
imageN[5] = '0' + imx;
4997
zdialog_add_widget(panozd,"radio",imageN,"hbim",0,"space=5");
5000
zdialog_stuff(panozd,"image0",1); // pre-select 1st image
5001
zdialog_resize(panozd,300,0);
5003
panStat = -1; // busy status
5004
zdialog_run(panozd,pano_tweak_event,"-10/20"); // run dialog, parallel v.11.07
5005
zdialog_wait(panozd); // wait for dialog completion
5010
// dialog event function
5012
int pano_tweak_event(zdialog *zd, cchar *event) // v.10.7
5014
char imageN[8] = "imageN";
5015
double red, green, blue, bright, bright2;
5016
double red1, green1, blue1;
5017
int nn, im0, imx, im1, im2, ww, hh, px, py;
5020
if (zd->zstat) // dialog complete
5022
if (zd->zstat == 1) panStat = 1; // done
5023
if (zd->zstat == 2) panStat = 0; // cancel
5024
zdialog_free(panozd); // kill dialog
5028
for (im0 = 0; im0 < cimNF; im0++) { // get which image is selected
5029
imageN[5] = '0' + im0; // by the radio buttons
5030
zdialog_fetch(zd,imageN,nn);
5033
if (im0 == cimNF) return 1;
5035
zdialog_fetch(zd,"red",red); // get color adjustments
5036
zdialog_fetch(zd,"green",green);
5037
zdialog_fetch(zd,"blue",blue);
5038
zdialog_fetch(zd,"bright",bright); // brightness adjustment
5040
bright2 = (red + green + blue) / 3; // RGB brightness
5041
bright = bright / bright2; // bright setpoint / RGB brightness
5042
red = red * bright; // adjust RGB brightness
5043
green = green * bright;
5044
blue = blue * bright;
5046
bright = (red + green + blue) / 3;
5047
zdialog_stuff(zd,"red",red); // force back into consistency
5048
zdialog_stuff(zd,"green",green);
5049
zdialog_stuff(zd,"blue",blue);
5050
zdialog_stuff(zd,"bright",bright);
5052
if (strEqu(event,"brapp")) // apply color & brightness changes
5054
red = red / 100; // normalize 0.5 ... 2.0
5055
green = green / 100;
5058
cim_warp_image_pano(im0,0); // refresh cimPXMw from cimPXMs
5060
ww = cimPXMw[im0]->ww;
5061
hh = cimPXMw[im0]->hh;
5063
for (py = 0; py < hh; py++) // loop all image pixels
5064
for (px = 0; px < ww; px++)
5066
pixel = PXMpix(cimPXMw[im0],px,py);
5067
red1 = red * pixel[0]; // apply color factors
5068
green1 = green * pixel[1];
5069
blue1 = blue * pixel[2];
5070
if (! blue1) continue;
5072
if (red1 > 65535 || green1 > 65535 || blue1 > 65535) {
5073
bright = red1; // avoid overflow
5074
if (green1 > bright) bright = green1;
5075
if (blue1 > bright) bright = blue1;
5076
bright = 65535.0 / bright;
5077
red1 = red1 * bright;
5078
green1 = green1 * bright;
5079
blue1 = blue1 * bright;
5082
if (blue1 < 1) blue1 = 1; // avoid 0 v.10.7
5090
zdialog_stuff(zd,"blend",cimBlend); // v.11.04
5091
cim_show_images(0,0); // combine and show with 50/50 blend
5094
if (strEqu(event,"auto")) // auto match color of selected image
5096
for (im1 = im0; im1 < cimNF-1; im1++) // from selected image to last image
5099
cimBlend = 0.3 * cimPXMw[im2]->ww;
5100
cim_get_overlap(im1,im2,cimPXMw); // match images in overlap area
5101
cim_match_colors(im1,im2,cimPXMw);
5102
cim_adjust_colors(cimPXMw[im1],1); // image im1 << profile 1
5103
cim_adjust_colors(cimPXMw[im2],2); // image im2 << profile 2
5104
for (imx = im1-1; imx >= im0; imx--)
5105
cim_adjust_colors(cimPXMw[imx],1);
5107
zdialog_stuff(zd,"blend",cimBlend); // v.11.04
5108
cim_show_images(0,0);
5111
for (im1 = im0-1; im1 >= 0; im1--) // from selected image to 1st image
5114
cimBlend = 0.3 * cimPXMw[im2]->ww;
5115
cim_get_overlap(im1,im2,cimPXMw); // match images in overlap area
5116
cim_match_colors(im1,im2,cimPXMw);
5117
cim_adjust_colors(cimPXMw[im1],1); // image im1 << profile 1
5118
cim_adjust_colors(cimPXMw[im2],2); // image im2 << profile 2
5119
for (imx = im2+1; imx < cimNF; imx++)
5120
cim_adjust_colors(cimPXMw[imx],2);
5122
zdialog_stuff(zd,"blend",cimBlend); // v.11.04
5123
cim_show_images(0,0);
5127
if (strEqu(event,"file")) // use original file colors
5129
if (! cim_load_files()) return 1;
5131
for (imx = 0; imx < cimNF; imx++) {
5132
PXM_free(cimPXMs[imx]);
5133
cimPXMs[imx] = PXM_copy(cimPXMf[imx]);
5134
cim_curve_image(imx); // curve and warp
5135
cim_warp_image_pano(imx,0);
5139
zdialog_stuff(zd,"blend",cimBlend); // v.11.04
5140
cim_show_images(0,0);
5143
if (strEqu(event,"blapp")) // apply new blend width
5145
zdialog_fetch(zd,"blend",cimBlend); // can be zero
5146
cim_show_images(0,1); // show with gradual blend
5153
/**************************************************************************
5155
Vertical Panorama function: join 2, 3, or 4 images.
5157
***************************************************************************/
5159
void vpano_prealign(); // manual pre-align
5160
void vpano_align(); // auto fine-align
5161
void vpano_tweak(); // user color tweak
5163
editfunc EFvpano; // edit function data
5168
void m_vpano(GtkWidget *, cchar *) // v.11.04
5173
zfuncs::F1_help_topic = "panorama"; // help topic
5175
if (mod_keep()) return; // warn unsaved changes
5176
if (! menulock(1)) return; // test menu lock v.11.07
5179
for (imx = 0; imx < 10; imx++)
5180
{ // clear all file and PXM data
5182
cimPXMf[imx] = cimPXMs[imx] = cimPXMw[imx] = 0;
5186
flist = zgetfileN(ZTX("Select 2 to 4 files"),"openN",curr_file); // select images to combine
5187
if (! flist) return;
5189
for (imx = 0; flist[imx]; imx++); // count selected files
5190
if (imx < 2 || imx > 4) {
5191
zmessageACK(mWin,ZTX("Select 2 to 4 files"));
5195
cimNF = imx; // file count
5196
for (imx = 0; imx < cimNF; imx++)
5197
cimFile[imx] = strdupz(flist[imx],0,"pano"); // set up file list
5199
if (! cim_load_files()) goto cleanup; // load and check all files
5201
free_resources(); // ready to commit
5203
err = f_open(cimFile[0],0); // curr_file = 1st file in list
5204
if (err) goto cleanup;
5206
EFvpano.funcname = "vpano";
5207
if (! edit_setup(EFvpano)) goto cleanup; // setup edit (will lock)
5209
cimShowAll = 1; // for cim_show_images(), show all
5210
cimShrink = 0; // no warp shrinkage v.11.04
5211
cimPano = 0; // vertical pano mode v.11.04
5214
vpano_prealign(); // manual pre-alignment
5215
if (panStat != 1) goto cancel;
5217
vpano_align(); // auto full alignment
5218
if (panStat != 1) goto cancel;
5220
vpano_tweak(); // manual color adjustment
5221
if (panStat != 1) goto cancel;
5223
CEF->Fmod = 1; // done
5227
cancel: // failed or canceled
5228
edit_cancel(EFvpano);
5233
for (imx = 0; flist[imx]; imx++) // free file list
5238
for (imx = 0; imx < cimNF; imx++) { // free cim file and PXM data
5239
if (cimFile[imx]) zfree(cimFile[imx]);
5240
if (cimPXMf[imx]) PXM_free(cimPXMf[imx]);
5241
if (cimPXMs[imx]) PXM_free(cimPXMs[imx]);
5242
if (cimPXMw[imx]) PXM_free(cimPXMw[imx]);
5250
// perform manual pre-align of all images
5251
// returns alignment data in cimOffs[*]
5252
// lens_mm and lens_bow may also be altered
5254
void vpano_prealign()
5256
int vpano_prealign_event(zdialog *zd, cchar *event); // dialog event function
5257
void * vpano_prealign_thread(void *); // working thread
5259
int imx, hh, err = 0;
5260
cchar *exifkey = { exif_focal_length_key };
5261
char lensname[40], **pp = 0;
5263
cchar *align_mess = ZTX("Drag images into rough alignment.\n"
5264
"To rotate, drag from right edge.");
5266
pp = info_get(curr_file,&exifkey,1); // get lens mm from EXIF if available
5268
err = convSD(*pp, lens_mm, 20, 1000); // leave lens_bow unchanged (no source)
5269
strcpy(lensname,"(EXIF)"); // lens name = EXIF
5272
if (! pp || ! *pp || err) { // not available
5273
lens_mm = lens4_mm[curr_lens]; // get curr. lens mm, bow, name
5274
lens_bow = lens4_bow[curr_lens];
5276
strncatv(lensname,40,"(",lens4_name[curr_lens],")",null);
5279
for (imx = 0; imx < 10; imx++) // set all alignment offsets = 0
5280
memset(&cimOffs[imx],0,sizeof(cimoffs));
5282
for (imx = hh = 0; imx < cimNF; imx++) // sum image heights
5283
hh += cimPXMf[imx]->hh;
5285
cimScale = 1.4 * panPreAlignSize / hh; // set alignment image scale
5286
if (cimScale > 1.0) cimScale = 1.0; // (* 0.7 after overlaps)
5288
for (imx = 0; imx < cimNF; imx++) // scale images > cimPXMs[*]
5289
cim_scale_image(imx,cimPXMs);
5291
for (imx = 0; imx < cimNF; imx++) { // curve images, cimPXMs[*] replaced
5292
cim_curve_Vimage(imx);
5293
cimPXMw[imx] = PXM_copy(cimPXMs[imx]); // copy to cimPXMw[*] for display
5296
cimOffs[0].xf = cimOffs[0].yf = 0; // first image at (0,0)
5298
for (imx = 1; imx < cimNF; imx++) // position images with 30% overlap
5299
{ // in vertical row
5300
cimOffs[imx].yf = cimOffs[imx-1].yf + 0.7 * cimPXMw[imx-1]->hh;
5301
cimOffs[imx].xf = cimOffs[imx-1].xf;
5304
Fzoom = 0; // scale image to fit window
5305
Fblowup = 1; // magnify small image to window size
5307
cimBlend = panPreAlignBlend * cimPXMw[1]->hh; // overlap in align window
5308
cim_show_Vimages(1,0); // combine and show images in main window
5310
panozd = zdialog_new(ZTX("Pre-align Images"),mWin,Bproceed,Bcancel,null); // start pre-align dialog
5311
zdialog_add_widget(panozd,"label","lab1","dialog",align_mess,"space=5");
5312
zdialog_add_widget(panozd,"hbox","hb1","dialog",0,"space=2");
5313
zdialog_add_widget(panozd,"spin","spmm","hb1","22|200|0.1|35","space=5"); // [ 35 ] lens mm (source)
5314
zdialog_add_widget(panozd,"label","labmm","hb1",ZTX("lens mm")); // [ 0.3 ] lens bow
5315
zdialog_add_widget(panozd,"label","lablens","hb1","","space=5"); // [resize] resize window
5316
zdialog_add_widget(panozd,"hbox","hb2","dialog",0,"space=2");
5317
zdialog_add_widget(panozd,"spin","spbow","hb2","-9|9|0.01|0","space=5");
5318
zdialog_add_widget(panozd,"label","labbow","hb2",ZTX("lens bow"));
5319
zdialog_add_widget(panozd,"hbox","hb3","dialog",0,"space=2");
5320
zdialog_add_widget(panozd,"button","resize","hb3",ZTX("Resize"),"space=5");
5321
zdialog_add_widget(panozd,"label","labsiz","hb3",ZTX("resize window"),"space=5");
5323
zdialog_stuff(panozd,"spmm",lens_mm); // stuff lens data
5324
zdialog_stuff(panozd,"spbow",lens_bow);
5325
zdialog_stuff(panozd,"lablens",lensname); // show source of lens data
5327
panStat = -1; // busy status
5328
gdk_window_set_cursor(drWin->window,dragcursor); // set drag cursor
5329
zdialog_run(panozd,vpano_prealign_event,"-10/20"); // start dialog v.11.07
5330
start_thread(vpano_prealign_thread,0); // start working thread
5331
zdialog_wait(panozd); // wait for dialog completion
5332
gdk_window_set_cursor(drWin->window,0); // restore normal cursor
5333
Fzoom = Fblowup = 0;
5338
// pre-align dialog event function
5340
int vpano_prealign_event(zdialog *zd, cchar *event)
5345
if (strstr("spmm spbow",event)) {
5346
zdialog_fetch(zd,"spmm",lens_mm); // get revised lens data
5347
zdialog_fetch(zd,"spbow",lens_bow);
5350
if (strEqu(event,"resize")) // allocate new E3 image
5351
cim_show_Vimages(1,0);
5353
if (zd->zstat) // dialog complete
5355
if (zd->zstat == 1) // proceed
5357
else // cancel or other
5360
zdialog_free(panozd); // kill dialog
5361
wrapup_thread(0); // wait for thread
5363
if (! panStat) return 0; // canceled
5365
for (imx = 0; imx < cimNF-1; imx++) // check for enough overlap
5367
overlap = cim_get_overlap(imx,imx+1,cimPXMs); // v.11.04
5368
if (overlap < panFinalBlend) { // v.11.09
5369
zmessageACK(mWin,ZTX("Too little overlap, cannot align"));
5380
// pre-align working thread
5381
// convert mouse and KB events into image movements // overhauled
5383
void * vpano_prealign_thread(void *)
5388
int im1, im2, imm, imx;
5389
int mx0, my0, mx, my; // mouse drag origin, position
5390
int xoff, yoff, loy, hiy;
5392
int ww, hh, rotate, midy;
5393
double lens_mm0, lens_bow0;
5394
double dx, dy, t1, t2, dt;
5396
imm = ww = hh = rotate = xoff = yoff = 0; // stop compiler warnings
5398
lens_mm0 = lens_mm; // to detect changes
5399
lens_bow0 = lens_bow;
5401
mx0 = my0 = 0; // no drag in progress
5402
Mcapture = KBcapture = 1; // capture mouse drag and KB keys
5404
cimBlend = 0; // full blend during pre-align
5406
while (true) // loop and align until done
5408
zsleep(0.05); // logic simplified
5410
if (panStat != -1) break; // quit signal from dialog
5412
if (lens_mm != lens_mm0 || lens_bow != lens_bow0) { // change in lens parameters
5414
lens_bow0 = lens_bow;
5416
for (imx = 0; imx < cimNF; imx++) { // re-curve images
5417
cim_scale_image(imx,cimPXMs);
5418
cim_curve_Vimage(imx);
5419
PXM_free(cimPXMw[imx]);
5420
cimPXMw[imx] = PXM_copy(cimPXMs[imx]);
5423
cim_show_Vimages(1,0); // combine and show images
5427
if (KBkey) { // KB input
5428
if (KBkey == GDK_Left) cimOffs[imm].xf -= 0.5; // tweak alignment offsets
5429
if (KBkey == GDK_Right) cimOffs[imm].xf += 0.5;
5430
if (KBkey == GDK_Up) cimOffs[imm].yf -= 0.5;
5431
if (KBkey == GDK_Down) cimOffs[imm].yf += 0.5;
5432
if (KBkey == GDK_r) cimOffs[imm].tf += 0.0005;
5433
if (KBkey == GDK_l) cimOffs[imm].tf -= 0.0005;
5436
cim_show_Vimages(0,0); // combine and show images
5440
if (! Mxdrag && ! Mydrag) // no drag underway
5441
mx0 = my0 = 0; // reset drag origin
5443
if (Mxdrag || Mydrag) // mouse drag underway
5445
mx = Mxdrag; // mouse position in image
5448
if (! mx0 && ! my0) // new drag
5450
mx0 = mx; // set drag origin
5454
for (imx = 0; imx < cimNF; imx++) // find image with midpoint
5455
{ // closest to mouse y
5456
loy = cimOffs[imx].yf;
5457
hiy = loy + cimPXMw[imx]->hh;
5458
midy = (loy + hiy) / 2;
5459
sepy = abs(midy - my0);
5460
if (sepy < minsep) {
5462
imm = imx; // image to drag or rotate
5466
xoff = cimOffs[imm].xf;
5467
yoff = cimOffs[imm].yf;
5468
ww = cimPXMw[imm]->ww;
5469
hh = cimPXMw[imm]->hh;
5471
rotate = 0; // if drag at right edge,
5472
if (mx0 > xoff + 0.85 * ww) rotate = 1; // set rotate flag
5475
if (mx != mx0 || my != my0) // drag is progressing
5477
dx = mx - mx0; // mouse movement
5480
if (rotate && my0 > yoff && my > yoff) // rotation
5483
loy = cimOffs[imm].yf; // if there is an image above,
5484
hiy = cimOffs[imm-1].yf + cimPXMw[imm-1]->hh; // midy = midpoint of overlap
5485
midy = (loy + hiy) / 2;
5487
else midy = 0; // this is the topmist image
5489
t1 = atan(1.0 * (my0-yoff) / (mx0-xoff));
5490
t2 = atan(1.0 * (my-yoff) / (mx-xoff));
5491
dt = t2 - t1; // angle change
5492
dy = - dt * ww / 2; // pivot = middle of overlap above
5493
dx = dt * (midy-yoff);
5496
else dt = 0; // x/y drag
5498
cimOffs[imm].xf += dx; // update image
5499
cimOffs[imm].yf += dy;
5500
cimOffs[imm].tf += dt;
5501
xoff = cimOffs[imm].xf; // v.11.04
5502
yoff = cimOffs[imm].yf;
5504
cim_show_Vimages(0,0); // show combined images
5506
mx0 = mx; // next drag origin = current mouse
5511
for (im1 = 0; im1 < cimNF-1; im1++) // track image order changes
5514
if (cimOffs[im2].yf < cimOffs[im1].yf)
5516
ftemp = cimFile[im2]; // switch filespecs
5517
cimFile[im2] = cimFile[im1];
5518
cimFile[im1] = ftemp;
5519
pxmtemp = cimPXMf[im2]; // switch images
5520
cimPXMf[im2] = cimPXMf[im1];
5521
cimPXMf[im1] = pxmtemp;
5522
pxmtemp = cimPXMs[im2]; // scaled images
5523
cimPXMs[im2] = cimPXMs[im1];
5524
cimPXMs[im1] = pxmtemp;
5525
pxmtemp = cimPXMw[im2]; // warped images
5526
cimPXMw[im2] = cimPXMw[im1];
5527
cimPXMw[im1] = pxmtemp;
5528
offstemp = cimOffs[im2]; // offsets
5529
cimOffs[im2] = cimOffs[im1];
5530
cimOffs[im1] = offstemp;
5531
if (imm == im1) imm = im2; // current drag image
5532
else if (imm == im2) imm = im1;
5538
KBcapture = Mcapture = 0;
5540
return 0; // not executed, stop g++ warning
5545
// start with very small image size
5546
// search around offset values for best match
5547
// increase image size and loop until full-size
5551
int imx, im1, im2, ww, hh;
5552
double R, dx, dy, dt;
5556
Fzoom = 0; // scale E3 to fit window
5557
Fblowup = 1; // magnify small image to window size
5560
for (imx = 0; imx < cimNF; imx++) {
5561
cimOffs[imx].xf = cimOffs[imx].xf / cimScale; // scale x/y offsets for full-size images
5562
cimOffs[imx].yf = cimOffs[imx].yf / cimScale;
5565
cimScale = 1.0; // full-size
5567
for (imx = 0; imx < cimNF; imx++) {
5568
PXM_free(cimPXMs[imx]);
5569
cimPXMs[imx] = PXM_copy(cimPXMf[imx]); // copy full-size images
5570
cim_curve_Vimage(imx); // curve them
5573
cimBlend = 0.3 * cimPXMs[0]->hh;
5574
cim_get_overlap(0,1,cimPXMs); // match images 0 & 1 in overlap area
5575
cim_match_colors(0,1,cimPXMs);
5576
cim_adjust_colors(cimPXMf[0],1); // image 0 << profile 1
5577
cim_adjust_colors(cimPXMf[1],2); // image 1 << profile 2
5580
cimBlend = 0.3 * cimPXMs[1]->hh;
5581
cim_get_overlap(1,2,cimPXMs);
5582
cim_match_colors(1,2,cimPXMs);
5583
cim_adjust_colors(cimPXMf[0],1);
5584
cim_adjust_colors(cimPXMf[1],1);
5585
cim_adjust_colors(cimPXMf[2],2);
5589
cimBlend = 0.3 * cimPXMs[2]->hh;
5590
cim_get_overlap(2,3,cimPXMs);
5591
cim_match_colors(2,3,cimPXMs);
5592
cim_adjust_colors(cimPXMf[0],1);
5593
cim_adjust_colors(cimPXMf[1],1);
5594
cim_adjust_colors(cimPXMf[2],1);
5595
cim_adjust_colors(cimPXMf[3],2);
5598
cimScale = panInitAlignSize / cimPXMf[1]->hh; // initial align image scale
5599
if (cimScale > 1.0) cimScale = 1.0;
5601
for (imx = 0; imx < cimNF; imx++) { // scale offsets for image scale
5602
cimOffs[imx].xf = cimOffs[imx].xf * cimScale;
5603
cimOffs[imx].yf = cimOffs[imx].yf * cimScale;
5606
cimSearchRange = panInitSearchRange; // initial align search range
5607
cimSearchStep = panInitSearchStep; // initial align search step
5608
cimWarpRange = panInitWarpRange; // initial align corner warp range
5609
cimWarpStep = panInitWarpStep; // initial align corner warp step
5610
hh = cimPXMf[0]->hh * cimScale; // initial align image width
5611
cimBlend = hh * panInitBlend; // initial align blend width
5612
cimSampSize = panSampSize; // pixel sample size for align/compare
5613
cimNsearch = 0; // reset align search counter
5615
while (true) // loop, increasing image size
5617
for (imx = 0; imx < cimNF; imx++) { // prepare images
5618
cim_scale_image(imx,cimPXMs); // scale to new size
5619
cim_curve_Vimage(imx); // curve based on lens params
5620
cim_warp_image_Vpano(imx,1); // apply corner warps
5623
cim_show_Vimages(1,0); // show with 50/50 blend in overlaps
5625
for (im1 = 0; im1 < cimNF-1; im1++) // fine-align each image with top neighbor
5629
offsets0 = cimOffs[im2]; // save initial alignment offsets
5630
overlap = cim_get_overlap(im1,im2,cimPXMs); // get overlap area v.11.04
5631
if (overlap < panFinalBlend-2) {
5632
zmessageACK(mWin,ZTX("Too little overlap, cannot align"));
5636
cim_get_redpix(im1); // get high-contrast pixels
5638
cim_align_image(im1,im2); // search for best offsets and warps
5640
zfree(cimRedpix); // clear red pixels
5643
dx = cimOffs[im2].xf - offsets0.xf; // changes from initial offsets
5644
dy = cimOffs[im2].yf - offsets0.yf;
5645
dt = cimOffs[im2].tf - offsets0.tf;
5647
for (imx = im2+1; imx < cimNF; imx++) // propagate to following images
5649
cimOffs[imx].xf += dx;
5650
cimOffs[imx].yf += dy;
5651
cimOffs[imx].tf += dt;
5652
ww = cimOffs[imx].xf - cimOffs[im2].xf;
5653
cimOffs[imx].yf += ww * dt;
5657
if (cimScale == 1.0) goto success; // done
5659
R = panImageIncrease; // next larger image size
5660
cimScale = cimScale * R;
5661
if (cimScale > 0.85) { // if close to end, jump to end
5666
for (imx = 0; imx < cimNF; imx++) // scale offsets for new size
5668
cimOffs[imx].xf *= R;
5669
cimOffs[imx].yf *= R;
5671
for (int ii = 0; ii < 4; ii++) {
5672
cimOffs[imx].wx[ii] *= R;
5673
cimOffs[imx].wy[ii] *= R;
5677
cimSearchRange = panSearchRange; // align search range
5678
cimSearchStep = panSearchStep; // align search step size
5679
cimWarpRange = panWarpRange; // align corner warp range
5680
cimWarpStep = panWarpStep; // align corner warp step size
5682
cimBlend = cimBlend * panBlendDecrease * R; // blend width, reduced
5683
hh = cimPXMf[0]->hh * cimScale;
5684
if (cimBlend < panFinalBlend * hh)
5685
cimBlend = panFinalBlend * hh; // stay above minimum
5694
Fzoom = Fblowup = 0;
5696
cimBlend = 1; // tiny blend (increase in tweak if wanted)
5697
cim_show_Vimages(0,0);
5702
// get user inputs for RGB changes and blend width, update cimPXMw[*]
5706
int vpano_tweak_event(zdialog *zd, cchar *event); // dialog event function
5708
cchar *tweaktitle = ZTX("Match Brightness and Color");
5709
char imageN[8] = "imageN";
5712
cimBlend = 1; // init. blend width
5714
panozd = zdialog_new(tweaktitle,mWin,Bdone,Bcancel,null);
5716
zdialog_add_widget(panozd,"hbox","hbim","dialog",0,"space=5");
5717
zdialog_add_widget(panozd,"label","labim","hbim",ZTX("image"),"space=5"); // image (o) (o) (o) (o)
5718
zdialog_add_widget(panozd,"hbox","hbc1","dialog",0,"homog"); //
5719
zdialog_add_widget(panozd,"label","labred","hbc1",Bred); // red green blue
5720
zdialog_add_widget(panozd,"label","labgreen","hbc1",Bgreen); // [_____] [_____] [_____]
5721
zdialog_add_widget(panozd,"label","labblue","hbc1",Bblue); //
5722
zdialog_add_widget(panozd,"hbox","hbc2","dialog",0,"homog"); // brightness [___] [apply]
5723
zdialog_add_widget(panozd,"spin","red","hbc2","50|200|0.1|100","space=5"); //
5724
zdialog_add_widget(panozd,"spin","green","hbc2","50|200|0.1|100","space=5"); // --------------------------
5725
zdialog_add_widget(panozd,"spin","blue","hbc2","50|200|0.1|100","space=5"); //
5726
zdialog_add_widget(panozd,"hbox","hbbri","dialog",0,"space=5"); // [auto color] [file color]
5727
zdialog_add_widget(panozd,"label","labbr","hbbri",Bbrightness,"space=5"); //
5728
zdialog_add_widget(panozd,"spin","bright","hbbri","50|200|0.1|100"); // --------------------------
5729
zdialog_add_widget(panozd,"button","brapp","hbbri",Bapply,"space=10"); //
5730
zdialog_add_widget(panozd,"hsep","hsep","dialog",0,"space=5"); // blend width [___] [apply]
5731
zdialog_add_widget(panozd,"hbox","hbc3","dialog",0,"space=5"); //
5732
zdialog_add_widget(panozd,"button","auto","hbc3",ZTX("auto color"),"space=5"); // [done] [cancel]
5733
zdialog_add_widget(panozd,"button","file","hbc3",ZTX("file color"),"space=5");
5734
zdialog_add_widget(panozd,"hsep","hsep","dialog",0,"space=5");
5735
zdialog_add_widget(panozd,"hbox","hbblen","dialog",0);
5736
zdialog_add_widget(panozd,"label","labbl","hbblen",Bblendwidth,"space=5");
5737
zdialog_add_widget(panozd,"spin","blend","hbblen","1|300|1|1");
5738
zdialog_add_widget(panozd,"button","blapp","hbblen",Bapply,"space=15");
5740
for (imx = 0; imx < cimNF; imx++) { // add radio button per image
5741
imageN[5] = '0' + imx;
5742
zdialog_add_widget(panozd,"radio",imageN,"hbim",0,"space=5");
5745
zdialog_stuff(panozd,"image0",1); // pre-select 1st image
5746
zdialog_resize(panozd,300,0);
5748
panStat = -1; // busy status
5749
zdialog_run(panozd,vpano_tweak_event,"-10/20"); // run dialog, parallel v.11.07
5750
zdialog_wait(panozd); // wait for dialog completion
5755
// dialog event function
5757
int vpano_tweak_event(zdialog *zd, cchar *event)
5759
char imageN[8] = "imageN";
5760
double red, green, blue, bright, bright2;
5761
double red1, green1, blue1;
5762
int nn, im0, imx, im1, im2, ww, hh, px, py;
5765
if (zd->zstat) // dialog complete
5767
if (zd->zstat == 1) panStat = 1; // done
5768
if (zd->zstat == 2) panStat = 0; // cancel
5769
zdialog_free(panozd); // kill dialog
5773
for (im0 = 0; im0 < cimNF; im0++) { // get which image is selected
5774
imageN[5] = '0' + im0; // by the radio buttons
5775
zdialog_fetch(zd,imageN,nn);
5778
if (im0 == cimNF) return 1;
5780
zdialog_fetch(zd,"red",red); // get color adjustments
5781
zdialog_fetch(zd,"green",green);
5782
zdialog_fetch(zd,"blue",blue);
5783
zdialog_fetch(zd,"bright",bright); // brightness adjustment
5785
bright2 = (red + green + blue) / 3; // RGB brightness
5786
bright = bright / bright2; // bright setpoint / RGB brightness
5787
red = red * bright; // adjust RGB brightness
5788
green = green * bright;
5789
blue = blue * bright;
5791
bright = (red + green + blue) / 3;
5792
zdialog_stuff(zd,"red",red); // force back into consistency
5793
zdialog_stuff(zd,"green",green);
5794
zdialog_stuff(zd,"blue",blue);
5795
zdialog_stuff(zd,"bright",bright);
5797
if (strEqu(event,"brapp")) // apply color & brightness changes
5799
red = red / 100; // normalize 0.5 ... 2.0
5800
green = green / 100;
5803
cim_warp_image_Vpano(im0,0); // refresh cimPXMw from cimPXMs
5805
ww = cimPXMw[im0]->ww;
5806
hh = cimPXMw[im0]->hh;
5808
for (py = 0; py < hh; py++) // loop all image pixels
5809
for (px = 0; px < ww; px++)
5811
pixel = PXMpix(cimPXMw[im0],px,py);
5812
red1 = red * pixel[0]; // apply color factors
5813
green1 = green * pixel[1];
5814
blue1 = blue * pixel[2];
5815
if (! blue1) continue;
5817
if (red1 > 65535 || green1 > 65535 || blue1 > 65535) {
5818
bright = red1; // avoid overflow
5819
if (green1 > bright) bright = green1;
5820
if (blue1 > bright) bright = blue1;
5821
bright = 65535.0 / bright;
5822
red1 = red1 * bright;
5823
green1 = green1 * bright;
5824
blue1 = blue1 * bright;
5827
if (blue1 < 1) blue1 = 1; // avoid 0
5835
zdialog_stuff(zd,"blend",cimBlend);
5836
cim_show_Vimages(0,0); // combine and show with 50/50 blend
5839
if (strEqu(event,"auto")) // auto match color of selected image
5841
for (im1 = im0; im1 < cimNF-1; im1++) // from selected image to last image
5844
cimBlend = 0.3 * cimPXMw[im2]->hh;
5845
cim_get_overlap(im1,im2,cimPXMw); // match images in overlap area
5846
cim_match_colors(im1,im2,cimPXMw);
5847
cim_adjust_colors(cimPXMw[im1],1); // image im1 << profile 1
5848
cim_adjust_colors(cimPXMw[im2],2); // image im2 << profile 2
5849
for (imx = im1-1; imx >= im0; imx--)
5850
cim_adjust_colors(cimPXMw[imx],1);
5852
zdialog_stuff(zd,"blend",cimBlend);
5853
cim_show_Vimages(0,0);
5856
for (im1 = im0-1; im1 >= 0; im1--) // from selected image to 1st image
5859
cimBlend = 0.3 * cimPXMw[im2]->hh;
5860
cim_get_overlap(im1,im2,cimPXMw); // match images in overlap area
5861
cim_match_colors(im1,im2,cimPXMw);
5862
cim_adjust_colors(cimPXMw[im1],1); // image im1 << profile 1
5863
cim_adjust_colors(cimPXMw[im2],2); // image im2 << profile 2
5864
for (imx = im2+1; imx < cimNF; imx++)
5865
cim_adjust_colors(cimPXMw[imx],2);
5867
zdialog_stuff(zd,"blend",cimBlend);
5868
cim_show_Vimages(0,0);
5872
if (strEqu(event,"file")) // use original file colors
5874
if (! cim_load_files()) return 1;
5876
for (imx = 0; imx < cimNF; imx++) {
5877
PXM_free(cimPXMs[imx]);
5878
cimPXMs[imx] = PXM_copy(cimPXMf[imx]);
5879
cim_curve_Vimage(imx); // curve and warp
5880
cim_warp_image_Vpano(imx,0);
5884
zdialog_stuff(zd,"blend",cimBlend);
5885
cim_show_Vimages(0,0);
5888
if (strEqu(event,"blapp")) // apply new blend width
5890
zdialog_fetch(zd,"blend",cimBlend); // can be zero
5891
cim_show_Vimages(0,1); // show with gradual blend