AlbumShaper 1.0a3
emboss.cpp
Go to the documentation of this file.
1//==============================================
2// copyright : (C) 2003-2005 by Will Stokes
3//==============================================
4// This program is free software; you can redistribute it
5// and/or modify it under the terms of the GNU General
6// Public License as published by the Free Software
7// Foundation; either version 2 of the License, or
8// (at your option) any later version.
9//==============================================
10
11//Systemwide includes
12#include <qimage.h>
13#include <qstring.h>
14#include <qapplication.h>
15#include <math.h>
16
17#define MIN(x,y) ((x) < (y) ? (x) : (y))
18#define MAX(x,y) ((x) < (y) ? (x) : (y))
19
20//Projectwide includes
21#include "emboss.h"
22#include "manipulationOptions.h"
23#include "../tools/imageTools.h"
25
26//----------------------------------------------
27// Inputs:
28// -------
29// QString filename - location of original image on disk
30// StatusWidget* status - widget for making progress visible to user
31//
32// Outputs:
33// --------
34// QImage* returned - constructed image
35//
36// Description:
37// ------------
38// This method constructs an embossed version of
39// the image by modifying the luminance of each pixel
40// by taking a weighted average of pixel luminance within
41// a local neighborhood. Most embossing algorithms convert an
42// image to grayscale and then combine a pixel's gray value with
43// its neighbors using the following convolution matrix:
44//
45// [ -1 -1 0
46// -1 1 1
47// 0 1 1 ]
48//
49// This apporach has two problems. First, all color information
50// is lost. Second, the convolution filter is not a function of image
51// size, and as a result embossing results on high resolution images are
52// barely when viewed on a high resolution screen or printed at high DPI.
53//
54// My solution to this problem is to:
55//
56// 1.) Apply the emboss effect on pixel luminance and leave pixel
57// color untouched. The varying lightness that results produces
58// the same effect, and if a grayscale image is really desired the
59// image can be converted to grayscale either before or after the
60// emboss effect takes place.
61//
62// 2.) using neighboring pixels that are a distance D from the
63// pixel being modified in the X or Y dimensions. This simply
64// modification allows the embossing procedure to run at the
65// same speed (larger convolution matrices would slow the algorithm
66// to at best O(N^2 * M^2) where there are N^2 pixels in the image
67// and the convolution matrix is M^2 in size) while providing the
68// same effective emboss effect at any image resolution. This is
69// done by computing distance D using the minimum image dimension:
70//
71// D = MIN( width, height )
72//
73// At each pixel, we compute an average luminance of the 6
74// neighbors and combine these values with a 50% luminance value for
75// the pixel in question. This luminance value is used to replace
76// the current pixel luminance before converting it back to RGB
77// space and storing in the edited image object.
78//
79// Neighbor pixels that are clamped to be on the image plane to
80// prevent lookups of neighbor pixels with negative coordinates or
81// coordinates beyond the width/height of the image in question.
82//----------------------------------------------
83
84//==============================================
85QImage* embossEffect( QString filename, ManipulationOptions* options )
86{
87 //load original image
88 QImage originalImage( filename );
89
90 //convert to 32-bit depth if necessary
91 if( originalImage.depth() < 32 ) { originalImage = originalImage.convertDepth( 32, Qt::AutoColor ); }
92
93 //create edited image
94 QImage* editedImage = new QImage( filename );
95
96 //convert to 32-bit depth if necessary
97 if( editedImage->depth() < 32 )
98 {
99 QImage* tmp = editedImage;
100 editedImage = new QImage( tmp->convertDepth( 32, Qt::AutoColor ) );
101 delete tmp; tmp=NULL;
102 }
103
104 //determine if busy indicators will be used
105 bool useBusyIndicators = false;
106 StatusWidget* status = NULL;
107 if( options != NULL && options->getStatus() != NULL )
108 {
109 useBusyIndicators = true;
110 status = options->getStatus();
111 }
112
113 //setup progress bar
114 if(useBusyIndicators)
115 {
116 QString statusMessage = qApp->translate( "embossEffect", "Applying Emboss Effect:" );
117 status->showProgressBar( statusMessage, 100 );
118 qApp->processEvents();
119 }
120
121 //update progress bar for every 1% of completion
122 const int updateIncrement = (int) ( 0.01 * originalImage.width() * originalImage.height() );
123 int newProgress = 0;
124
125 //iterate over each selected scanline
126 int x, y;
127 QRgb* rgb;
128 uchar* scanLine;
129
130 int yPrev, yNext, xPrev, xNext;
131
132 //compute the radius using image resolution
133 double minDimen = (double) MIN( editedImage->width(), editedImage->height() );
134 const int embossRadius = (int) MAX( 1, (sqrt(minDimen)/8) );
135
136 for( y=0; y<editedImage->height(); y++)
137 {
138 scanLine = originalImage.scanLine(y);
139
140 //compute previous and next y pixel coordinates
141 yPrev = MAX( y-embossRadius, 0 );
142 yNext = MIN( y+embossRadius, editedImage->height() - 1 );
143
144 //iterate over each selected pixel in scanline
145 for( x=0; x<editedImage->width(); x++)
146 {
147 //compute previous and next x pixel coordinates
148 xPrev = MAX( x-embossRadius, 0 );
149 xNext = MIN( x+embossRadius, editedImage->width() - 1 );
150
151 //start with a default luminance of 128 (50% luminance)
152 int sum = 128;
153
154 //sum weighted gray values of neighbors
155 scanLine = originalImage.scanLine( yPrev );
156 sum-= qGray( *((QRgb*)scanLine + xPrev ) );
157 sum-= qGray( *((QRgb*)scanLine + x ) );
158
159 scanLine = originalImage.scanLine( y );
160 sum-= qGray( *((QRgb*)scanLine + xPrev ) );
161 sum+= qGray( *((QRgb*)scanLine + xNext ) );
162
163 scanLine = originalImage.scanLine( yNext );
164 sum+= qGray( *((QRgb*)scanLine + x ) );
165 sum+= qGray( *((QRgb*)scanLine + xNext ) );
166
167 //clamp sum to within 0-255 range
168 sum = MAX( MIN( sum, 255), 0 );
169
170 //get original pixel color in HSV space
171 scanLine = editedImage->scanLine(y);
172 rgb = ((QRgb*)scanLine+x);
173 double r = ((double)qRed(*rgb) )/255.0;
174 double g = ((double)qGreen(*rgb) )/255.0;
175 double b = ((double)qBlue(*rgb) )/255.0;
176
177 //convert to hsv
178 double h,s,v;
179 RGBtoHSV(r,g,b,&h,&s,&v);
180
181 //reset v
182 v = ((double)sum)/255;
183
184 //convert adjusted color back to rgb colorspace and clamp
185 HSVtoRGB( &r,&g,&b, h,s,v);
186 int rp = (int) MIN( MAX((r*255), 0), 255 );
187 int gp = (int) MIN( MAX((g*255), 0), 255 );
188 int bp = (int) MIN( MAX((b*255), 0), 255 );
189
190 //set adjusted color value
191 *rgb = qRgb(rp,gp,bp);
192
193 //update status bar if significant progress has been made since last update
194 if(useBusyIndicators)
195 {
196 newProgress++;
198 {
199 newProgress = 0;
201 qApp->processEvents();
202 }
203 }
204
205 }
206 }
207
208 //return pointer to edited image
209 return editedImage;
210}
211//==============================================
StatusWidget * getStatus()
void showProgressBar(QString message, int numSteps)
Initializes the progress bar.
void incrementProgress()
Updates the progress bar by one step.
#define MIN(x, y)
Definition emboss.cpp:17
QImage * embossEffect(QString filename, ManipulationOptions *options)
Definition emboss.cpp:85
#define MAX(x, y)
Definition emboss.cpp:18
void RGBtoHSV(double r, double g, double b, double *h, double *s, double *v)
Convert a RGB color triplet to HSV.
void HSVtoRGB(double *r, double *g, double *b, double h, double s, double v)
Convert a HSV color triplet to RGB.
long b
int updateIncrement
QImage * editedImage
StatusWidget * status
int newProgress