AlbumShaper  1.0a3
sharpen.cpp
Go to the documentation of this file.
00001 //==============================================
00002 //  copyright            : (C) 2003-2005 by Will Stokes
00003 //==============================================
00004 //  This program is free software; you can redistribute it
00005 //  and/or modify it under the terms of the GNU General
00006 //  Public License as published by the Free Software
00007 //  Foundation; either version 2 of the License, or
00008 //  (at your option) any later version.
00009 //==============================================
00010 
00011 //Systemwide includes
00012 #include <qimage.h>
00013 #include <qstring.h>
00014 
00015 //Projectwide includes
00016 #include "sharpen.h"
00017 #include "blur.h"
00018 #include "../tools/imageTools.h"
00019 
00020 //----------------------------------------------
00021 // Inputs:
00022 // -------
00023 // QImage& image - image to blur
00024 // float sigma - how much to blur it
00025 // QPoint offset - offset within edge image we're working on
00026 // QSize fullImageRes - resolution of the full size image
00027 // QImage* edgeImage - an edge image constructing using the full size image
00028 // bool blurEdges    - are we sharpening edges or regions
00029 //
00030 // Outputs:
00031 // --------
00032 // Nothing returned, we'll modify the image passed by refference in place
00033 //
00034 // Description:
00035 // ------------
00036 // The common approach to sharpening images is subtract a 
00037 // blurred version of an image using the following equation:
00038 //
00039 // v' = 2*v - vBlur
00040 //
00041 // ...where v is the original value (luminance) for a given pixel, 
00042 // vBlur is the blurred value, and v' is the end result.
00043 //
00044 // While one could apply this blur-subtraction in the individual color channels
00045 // you will likely encounter strange artifacts at color channel boundaries where new
00046 // colors are introducted. Sharpening in the value/luminance domain helps bright out
00047 // image contrast without introducing color artifacts.
00048 //
00049 // Unfortunately, sharpening using this approach will magnify all image contrast, both
00050 // somewhat strong edges and low level noise. We'd like to be able to aggressively sharpen
00051 // images without magnifying CCD/film grain noise, but how?
00052 //
00053 // A somewhat popular solution to this problem is to use an edge image. Constructing edge images
00054 // can be difficult, but when provided such information can tell us when to to sharpen and when not to, or
00055 // used more wisely, how to blend the sharpend data with the unsharpened original image data seemlessly.
00056 // Grayscale edge images can be used in this way by first blurring slightly, then dividing the 
00057 // value component of a pixel by 255 to get an alpha value. Near edges the value will be closer to 
00058 // 255 and the resulting alpha will be closer to 1. In between regions where we don't want to 
00059 // enhance noise by sharpening alpha values will be close to 0, preventing aggressively 
00060 // sharpened vaues from being used.
00061 //
00062 // Algorithm:
00063 // ----------
00064 // The algorithm works as follows:
00065 // 1.) The input image is blurred using the sigma value. The large the sigma value 
00066 //     the more the input image is blurred and the more pronounced edges will become.
00067 // 2.) We iterate over each image pixel, fetching the color values of the original and blurred forms
00068 //     of the image, as well as the color of the give pixel within the edge image.
00069 // 
00070 //     An alpha value is computed using the edge image pixel color, which in turn is used
00071 //     to compute the blended pixel value after sharpening:
00072 //
00073 //     alpha = edgeColor / 255
00074 //     v' = alpha* min( max( 2*v - vBlur, 0 ), 1) + (1-alpha)*v;
00075 //
00076 //     Finally, we convert the pixel color back to RGB space and write back
00077 //     to the resulting sharpened image.
00078 //
00079 // This algorithm works and was initially tested without the use of an edge image. When
00080 // no edge image is provided alpha is simply set to 1 and full sharpening is applied
00081 // to every image pixel.
00082 //
00083 // Future work:
00084 // ------------
00085 // Further work needs to be done regarding bluring/sharpening edges and region independently.
00086 // The "blurEdges" param allows the algorithm to concentrate sharpening on regions instead of
00087 // object boundaries when an edge image is provided; however, such usage is not well understood
00088 // or used at this time.
00089 //----------------------------------------------
00090 
00091 //==============================================
00092 void sharpenImage( QImage &image, float sigma,
00093                    QPoint offset, QSize fullImageRes,
00094                    QImage* edgeImage, bool blurEdges)
00095 {
00096   //construct blur copy
00097   QImage blurredImage = image.copy();
00098   blurImage( blurredImage, sigma );
00099   
00100   //iterate over each pixel and adjust luminance value
00101   int x, y;
00102   QRgb *origRgb, *blurredRgb, *edgeRgb;
00103   uchar *origScanline;
00104   uchar *blurredScanline;
00105   uchar *edgesScanline = NULL;
00106   
00107   for(y=0; y<image.height(); y++)
00108   {
00109     origScanline = image.scanLine(y);
00110     blurredScanline = blurredImage.scanLine(y);
00111     if( edgeImage != NULL )
00112     {
00113       int edgeY = ((edgeImage->height()-1) * (y+offset.y())) / (fullImageRes.height()-1);
00114       edgesScanline = edgeImage->scanLine(edgeY);
00115     }
00116     
00117     for(x=0; x<image.width(); x++)
00118     {
00119       //get rgb triplets
00120       origRgb = ((QRgb*)origScanline+x);
00121       double r1 = ((double)qRed(*origRgb)   )/255.0;
00122       double g1 = ((double)qGreen(*origRgb) )/255.0;
00123       double b1 = ((double)qBlue(*origRgb)  )/255.0;
00124       
00125       blurredRgb = ((QRgb*)blurredScanline+x);
00126       double r2 = ((double)qRed(*blurredRgb)   )/255.0;
00127       double g2 = ((double)qGreen(*blurredRgb) )/255.0;
00128       double b2 = ((double)qBlue(*blurredRgb)  )/255.0;
00129 
00130       //sharpen the entire thing!
00131       float alpha;
00132       if( edgeImage == NULL)
00133         alpha = 1.0f;
00134       else
00135       {
00136         int edgeX = ((edgeImage->width()-1) * (x+offset.x())) / (fullImageRes.width()-1);
00137         edgeRgb = ((QRgb*)edgesScanline+edgeX);
00138         
00139         alpha = ((float) qRed( *edgeRgb )) / 255.0f;
00140         
00141         //blur regions, not edges
00142         if(!blurEdges)
00143           alpha = 1.0f - alpha;
00144       }
00145       
00146       //convert to hsv
00147       double h1,s1,v1;
00148       RGBtoHSV(r1,g1,b1,&h1,&s1,&v1);
00149 
00150       double h2,s2,v2;
00151       RGBtoHSV(r2,g2,b2,&h2,&s2,&v2);
00152       
00153       //reset v
00154       v1  = (alpha * QMIN( QMAX(2*v1 - v2, 0), 1.0 )) + (1-alpha)*v1;
00155       
00156       //convert adjusted color back to rgb colorspace and clamp
00157       HSVtoRGB( &r1,&g1,&b1, h1,s1,v1);         
00158       int rp = (int) QMIN( QMAX((r1*255), 0), 255 );
00159       int gp = (int) QMIN( QMAX((g1*255), 0), 255 );
00160       int bp = (int) QMIN( QMAX((b1*255), 0), 255 );
00161       
00162       //set adjusted color value
00163       *origRgb = qRgb(rp,gp,bp);
00164     } //x
00165   } //y
00166 
00167 }
00168 //==============================================