From 367d951c5f14469ee909112121e16bd904291647 Mon Sep 17 00:00:00 2001 From: Ingo Date: Wed, 2 Apr 2014 00:02:31 +0200 Subject: [PATCH] Optimization for Focus Mask, Issue 2319 --- rtgui/cropwindow.cc | 353 ++++++++++++++++++++++++++++---------------- 1 file changed, 228 insertions(+), 125 deletions(-) diff --git a/rtgui/cropwindow.cc b/rtgui/cropwindow.cc index db7668f64..598517d41 100755 --- a/rtgui/cropwindow.cc +++ b/rtgui/cropwindow.cc @@ -878,10 +878,6 @@ void CropWindow::updateCursor (int x, int y) { void CropWindow::expose (Cairo::RefPtr cr) { MyMutex::MyLock lock(cropHandler.cimg); - //MyTime t1, t2, t3, t4; - - //t1.set (); - if (decorated) drawDecoration (cr); @@ -930,16 +926,14 @@ void CropWindow::expose (Cairo::RefPtr cr) { imgX = imgAreaX + (imgAreaW-imgW)/2; imgY = imgAreaY + (imgAreaH-imgH)/2; exposeVersion++; -// PERFORMANCE BOTTLENECK STARTS HERE - //t3.set (); + bool showcs = iarea->indClippedPanel->showClippedShadows(); bool showch = iarea->indClippedPanel->showClippedHighlights(); - bool showR = iarea->previewModePanel->showR(); // will show clipping if R channel is clipped - bool showG = iarea->previewModePanel->showG(); // will show clipping if G channel is clipped - bool showB = iarea->previewModePanel->showB(); // will show clipping if B channel is clipped - bool showL = iarea->previewModePanel->showL(); // will show clipping if L value is clipped - bool showFocusMask = iarea->previewModePanel->showFocusMask(); - bool showclippedAny = (!showR && !showG && !showB && !showL); // will show clipping if any of RGB chanels is clipped + const bool showR = iarea->previewModePanel->showR(); // will show clipping if R channel is clipped + const bool showG = iarea->previewModePanel->showG(); // will show clipping if G channel is clipped + const bool showB = iarea->previewModePanel->showB(); // will show clipping if B channel is clipped + const bool showL = iarea->previewModePanel->showL(); // will show clipping if L value is clipped + const bool showFocusMask = iarea->previewModePanel->showFocusMask(); // While the Right-side ALT is pressed, auto-enable highlight and shadow clipping indicators // TODO: Add linux/MacOS specific functions for alternative @@ -954,129 +948,243 @@ void CropWindow::expose (Cairo::RefPtr cr) { guint8* pix = tmp->get_pixels(); guint8* pixWrkSpace = cropHandler.cropPixbuftrue->get_pixels(); - int pixRowStride = tmp->get_rowstride (); - int pixWSRowStride = cropHandler.cropPixbuftrue->get_rowstride (); + const int pixRowStride = tmp->get_rowstride (); + const int pixWSRowStride = cropHandler.cropPixbuftrue->get_rowstride (); - const float ShawdowFac = 64 / (options.shadowThreshold+1); - const float HighlightFac = 64 / (256-options.highlightThreshold); + const int bHeight = tmp->get_height(); + const int bWidth = tmp->get_width(); - #ifdef _OPENMP - #pragma omp parallel for - #endif - for (int i=0; iget_height(); i++) { - guint8* curr = pix + i*pixRowStride; - guint8* currWS = pixWrkSpace + i*pixWSRowStride; + if (showFocusMask) { // modulate preview to display focus mask + const int blur_radius2 = 1; // radius of small kernel. 1 => 3x3 kernel + const int blur_dim2 = 2*blur_radius2+1; // dimension of small kernel + const int blur_radius = (blur_dim2*blur_dim2)/2; // radius of big kernel + const float kernel_size = SQR(2.f*blur_radius+1.f); // count of pixels in the big blur kernel + const float rkernel_size = 1.0f/kernel_size; // reciprocal of kernel_size to avoid divisions + const float kernel_size2 = SQR(2.f*blur_radius2+1.f); // count of pixels in the small blur kernel + const float rkernel_size2 = 1.0f/kernel_size2; // reciprocal of kernel_size to avoid divisions - int delta; bool changedHL; bool changedSH; + // aloocate buffer for precalculated Luminance + float* tmpL = (float*)malloc(bHeight * bWidth * sizeof(float) ); + // aloocate buffers for sums and sums of squares of small kernel + float* tmpLsum = (float*)malloc((bHeight) * (bWidth) * sizeof(float) ); + float* tmpLsumSq = (float*)malloc((bHeight) * (bWidth) * sizeof(float) ); + float* tmpstdDev2 = (float*)malloc((bHeight) * (bWidth) * sizeof(float) ); + float maxstdDev_L2 = 0.f; - for (int j=0; jget_width(); j++) { +#ifdef _OPENMP +#pragma omp parallel +#endif +{ +#ifdef _OPENMP +#pragma omp for +#endif + // precalculate Luminance + for(int i=0;i maxthrstdDev_L2) + maxthrstdDev_L2 = stdDev_L2; + tmpstdDev2[i*bWidth+j] = stdDev_L2; + } + } +#pragma omp critical +{ + if(maxthrstdDev_L2 > maxstdDev_L2) + maxstdDev_L2 = maxthrstdDev_L2; +} +} - //************* - // Copyright (c) 2011 Michael Ezra michael@michaelezra.com - // determine if pixel is in the sharp area of the image using - // standard deviation analysis on two different scales - int blur_radius, blur_radius2; - float curL; - guint8* currIndex; - float avg_L, avg_L2; - float sum_L, sum_L2; - float sumsq_L, sumsq_L2; //sum of deviations squared - float stdDev_L, stdDev_L2; - float focus_thresh; - //float focus_thresh2; - int kernel_size, kernel_size2;// count of pixels in the blur kernel - //float opacity = 0.9;//TODO: implement opacity - //TODO: evaluate effects of altering sampling frequency + const float focus_thresh = 80.f; + maxstdDev_L2 = std::min(maxstdDev_L2,focus_thresh); + const float focus_threshby10 = focus_thresh / 10.f; + #ifdef _OPENMP + #pragma omp parallel for schedule(dynamic,16) + #endif + for (int i=blur_radius+1; iblur_radius && jget_width()-blur_radius - && i>blur_radius && iget_height()-blur_radius){ //stay within image area - // calculate average in +-blur_radius pixels area around the current pixel - // speed up: calculate sum of squares in the same loops + float sum_L = 0.f; + float sumsq_L = 0.f; + // use precalculated values of small kernel to reduce number of iterations + for (int kh=-blur_radius+blur_radius2; kh<=blur_radius-blur_radius2;kh+=blur_dim2){ + float* currLsum = &tmpLsum[(i+kh)*bWidth+j-blur_radius+1]; + float* currLsumSqu = &tmpLsumSq[(i+kh)*bWidth+j-blur_radius+1]; + for (int k=-blur_radius+blur_radius2; k<=blur_radius-blur_radius2;k+=blur_dim2,currLsum+=blur_dim2,currLsumSqu+=blur_dim2){ + sum_L += *currLsum; + sumsq_L += *currLsumSqu; + } + } + float sum_L2 = tmpLsum[i*bWidth+j]; + float sumsq_L2 = tmpLsumSq[i*bWidth+j]; + //************* + // averages + // Optimized formulas to avoid divisions + float stdDev_L = rkernel_size * sqrtf(sumsq_L * kernel_size - sum_L * sum_L); + float stdDev_L2 = tmpstdDev2[i*bWidth+j]; +// float stdDev_L2 = rkernel_size2 * sqrtf(sumsq_L2 * kernel_size2 - sum_L2 * sum_L2); - sum_L = 0; sum_L2=0; - sumsq_L = 0; sumsq_L2 = 0; - for (int kh=-blur_radius; kh<=blur_radius;kh++){ - for (int k=-blur_radius; k<=blur_radius;k++){ - //1 pixel step is equivalent to 3-bytes step - currIndex = currWS+3*k + kh*pixWSRowStride; - curL = 0.299*(currIndex)[0]+0.587*(currIndex)[1]+0.114*(currIndex)[2]; - sum_L += curL; - sumsq_L += SQR(curL); + //TODO: try to normalize by average L of the entire (preview) image - // Band2 @ blur_radius2 - if (kh>=-blur_radius2 && kh<=blur_radius2 && k>=-blur_radius2 && k<=blur_radius2){ - sum_L2 += curL; - sumsq_L2 += SQR(curL); - } - } - } - //************* - // averages - kernel_size= SQR(2*blur_radius+1); // consider -1: Bessel's correction for the sample standard deviation (tried, did not make any visible difference) - kernel_size2= SQR(2*blur_radius2+1); - avg_L = sum_L/kernel_size; - avg_L2 = sum_L2/kernel_size2; + //detection method 1: detect focus in features + //there is no strict condition between stdDev_L and stdDev_L2 themselves + /* if (stdDev_L2>focus_thresh2 + && (stdDev_L = stdDev_L2 //TODO: could vary this to bypass noise better + && stdDev_L2 > stdDev_L //this is the key to select fine detail within lower contrast on larger scale + && stdDev_L > focus_threshby10 //options.highlightThreshold + ){ + // transpareny depends on sdtDev_L2 and maxstdDev_L2 + float transparency = 1.f - std::min(stdDev_L2 / maxstdDev_L2, 1.0f) ; + // first row of circle + guint8* currtmp = &curr[0] + (-3*pixRowStride); + guint8* currtmpWS = &currWs[0] + (-3*pixWSRowStride); + for(int jj=-3;jj<=3;jj+=3) { + guint8* currtmpl=currtmp+jj; + guint8* currtmpWSl=currtmpWS+jj; + //transparent green + currtmpl[0] = transparency * currtmpWSl[0]; + currtmpl[1] = transparency * currtmpWSl[1] + (1.f-transparency)*255.f; + currtmpl[2] = transparency * currtmpWSl[2]; + } + // second row of circle + currtmp = &curr[0] + (-2*pixRowStride); + currtmpWS = &currWs[0] + (-2*pixWSRowStride); + for(int jj=-6;jj<=6;jj+=3) { + guint8* currtmpl=currtmp+jj; + guint8* currtmpWSl=currtmpWS+jj; + //transparent green + currtmpl[0] = transparency * currtmpWSl[0]; + currtmpl[1] = transparency * currtmpWSl[1] + (1.f-transparency)*255.f; + currtmpl[2] = transparency * currtmpWSl[2]; + } + // three middle row of circle + for(int ii=-1;ii<=1;ii++) { + currtmp = &curr[0] + (ii*pixRowStride); + currtmpWS = &currWs[0] + (ii*pixWSRowStride); + for(int jj=-9;jj<=9;jj+=3) { + guint8* currtmpl=currtmp+jj; + guint8* currtmpWSl=currtmpWS+jj; + //transparent green + currtmpl[0] = transparency * currtmpWSl[0]; + currtmpl[1] = transparency * currtmpWSl[1] + (1.f-transparency)*255.f; + currtmpl[2] = transparency * currtmpWSl[2]; + } + } + // second last row of circle + currtmp = &curr[0] + (2*pixRowStride); + currtmpWS = &currWs[0] + (2*pixWSRowStride); + for(int jj=-6;jj<=6;jj+=3) { + guint8* currtmpl=currtmp+jj; + guint8* currtmpWSl=currtmpWS+jj; + //transparent green + currtmpl[0] = transparency * currtmpWSl[0]; + currtmpl[1] = transparency * currtmpWSl[1] + (1.f-transparency)*255.f; + currtmpl[2] = transparency * currtmpWSl[2]; + } + // last row of circle + currtmp = &curr[0] + (3*pixRowStride); + currtmpWS = &currWs[0] + (3*pixWSRowStride); + for(int jj=-3;jj<=3;jj+=3) { + guint8* currtmpl=currtmp+jj; + guint8* currtmpWSl=currtmpWS+jj; + //transparent green + currtmpl[0] = transparency * currtmpWSl[0]; + currtmpl[1] = transparency * currtmpWSl[1] + (1.f-transparency)*255.f; + currtmpl[2] = transparency * currtmpWSl[2]; + } + } + curr+=3; currWs+=3; + } + } + free(tmpL); + free(tmpLsum); + free(tmpLsumSq); + free(tmpstdDev2); + + } else { // !showFocusMask + + const int hlThreshold = options.highlightThreshold; + const int shThreshold = options.shadowThreshold; + const float ShawdowFac = 64 / (options.shadowThreshold+1); + const float HighlightFac = 64 / (256-options.highlightThreshold); + const bool showclippedAny = (!showR && !showG && !showB && !showL); // will show clipping if any of RGB chanels is clipped - //detection method 1: detect focus in features - //there is no strict condition between stdDev_L and stdDev_L2 themselves -/* if (stdDev_L2>focus_thresh2 - && (stdDev_L stdDev_L2 //TODO: could vary this to bypass noise better - && stdDev_L2 > stdDev_L //this is the key to select fine detail within lower contrast on larger scale - && stdDev_L > focus_thresh/10 //options.highlightThreshold - ){ - curr[0]=0; - curr[1]=255; - curr[2]=0; - } - } - } - - else { // !showFocusMask +#ifdef _OPENMP +#pragma omp parallel for schedule(dynamic,16) +#endif + for (int i=0; i=options.highlightThreshold && (showR || showclippedAny)) { delta += 255-currWS[0]; changedHL=true; } - if (currWS[1]>=options.highlightThreshold && (showG || showclippedAny)) { delta += 255-currWS[1]; changedHL=true; } - if (currWS[2]>=options.highlightThreshold && (showB || showclippedAny)) { delta += 255-currWS[2]; changedHL=true; } - if (currWS_L>= options.highlightThreshold && showL) { delta += 255-currWS_L ; changedHL=true; } + if ((showclippedAny || showR) && currWS[0]>=hlThreshold ) { delta += 255-currWS[0]; changedHL=true; } + if ((showclippedAny || showG) && currWS[1]>=hlThreshold ) { delta += 255-currWS[1]; changedHL=true; } + if ((showclippedAny || showB) && currWS[2]>=hlThreshold ) { delta += 255-currWS[2]; changedHL=true; } + if (showL && currWS_L>= hlThreshold ) { delta += 255-currWS_L ; changedHL=true; } if (changedHL) { delta *= HighlightFac; @@ -1085,12 +1193,10 @@ void CropWindow::expose (Cairo::RefPtr cr) { } } if (showcs) { - delta=0; changedSH=false; - - if (currWS[0]<=options.shadowThreshold && (showR || showclippedAny)) { delta += currWS[0]; changedSH=true; } - if (currWS[1]<=options.shadowThreshold && (showG || showclippedAny)) { delta += currWS[1]; changedSH=true; } - if (currWS[2]<=options.shadowThreshold && (showB || showclippedAny)) { delta += currWS[2]; changedSH=true; } - if (currWS_L <=options.shadowThreshold && showL) { delta += currWS_L ; changedSH=true; } + if ((showclippedAny || showR) && currWS[0]<=shThreshold ) { delta += currWS[0]; changedSH=true; } + if ((showclippedAny || showG) && currWS[1]<=shThreshold ) { delta += currWS[1]; changedSH=true; } + if ((showclippedAny || showB) && currWS[2]<=shThreshold ) { delta += currWS[2]; changedSH=true; } + if (showL && currWS_L <=shThreshold ) { delta += currWS_L ; changedSH=true; } if (changedSH) { if (showclippedAny) { @@ -1105,7 +1211,7 @@ void CropWindow::expose (Cairo::RefPtr cr) { } //if (showcs) // modulate the preview of channels & L; - if (!changedHL && !changedSH){ //This condition allows clipping indicators for RGB channels to remain in color + if (!changedHL && !changedSH && !showclippedAny){ //This condition allows clipping indicators for RGB channels to remain in color if (showR) curr[1]=curr[2]=curr[0]; //Red channel in grayscale if (showG) curr[0]=curr[2]=curr[1]; //Green channel in grayscale if (showB) curr[0]=curr[1]=curr[2]; //Blue channel in grayscale @@ -1116,9 +1222,6 @@ void CropWindow::expose (Cairo::RefPtr cr) { curr[0]=curr[1]=curr[2]=L; } } - } // else (!showFocusMask) - - /* if (showch && (currWS[0]>=options.highlightThreshold || currWS[1]>=options.highlightThreshold || currWS[2]>=options.highlightThreshold)) @@ -1131,16 +1234,16 @@ void CropWindow::expose (Cairo::RefPtr cr) { // curr[0] = curr[1] = curr[2] = 255; */ - curr+=3; currWS+=3; - } + curr+=3; currWS+=3; + } + } } //printf("zoomSteps[cropZoom].zoom=%d\n",zoomSteps[cropZoom].zoom); iarea->get_window()->draw_pixbuf (iarea->get_style()->get_base_gc(Gtk::STATE_NORMAL), tmp, 0, 0, x+imgX, y+imgY, -1, -1, Gdk::RGB_DITHER_NONE, 0, 0); } else iarea->get_window()->draw_pixbuf (iarea->get_style()->get_base_gc(Gtk::STATE_NORMAL), cropHandler.cropPixbuf, 0, 0, x+imgX, y+imgY, -1, -1, Gdk::RGB_DITHER_NONE, 0, 0); - //t4.set (); -// END OF BOTTLENECK + if (cropHandler.cropParams.enabled) { int cropX, cropY; cropHandler.getPosition (cropX, cropY);