File indexing completed on 2024-05-19 04:22:56
0001 0002 /* 0003 Copyright (c) 2003-2007 Clarence Dang <dang@kde.org> 0004 Copyright (c) 2006 Mike Gashler <gashlerm@yahoo.com> 0005 All rights reserved. 0006 0007 Redistribution and use in source and binary forms, with or without 0008 modification, are permitted provided that the following conditions 0009 are met: 0010 0011 1. Redistributions of source code must retain the above copyright 0012 notice, this list of conditions and the following disclaimer. 0013 2. Redistributions in binary form must reproduce the above copyright 0014 notice, this list of conditions and the following disclaimer in the 0015 documentation and/or other materials provided with the distribution. 0016 0017 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 0018 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 0019 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 0020 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 0021 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 0022 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 0023 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 0024 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 0025 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 0026 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 0027 */ 0028 0029 0030 // TODO: Clarence's code review 0031 0032 #include "kpEffectToneEnhance.h" 0033 0034 #include <QImage> 0035 0036 #include "kpLogCategories.h" 0037 0038 #include "pixmapfx/kpPixmapFX.h" 0039 0040 0041 #define RED_WEIGHT 77 0042 #define GREEN_WEIGHT 150 0043 #define BLUE_WEIGHT 29 0044 0045 #define MAX_TONE_VALUE ((RED_WEIGHT + GREEN_WEIGHT + BLUE_WEIGHT) * 255) 0046 #define TONE_DROP_BITS 5 0047 #define TONE_MAP_SIZE ((MAX_TONE_VALUE >> TONE_DROP_BITS) + 1) 0048 #define MAX_GRANULARITY 25 0049 #define MIN_IMAGE_DIM 3 0050 0051 //--------------------------------------------------------------------- 0052 0053 inline unsigned int ComputeTone(unsigned int color) 0054 { 0055 return RED_WEIGHT * static_cast<unsigned int> (qRed(color)) + 0056 GREEN_WEIGHT * static_cast<unsigned int> (qGreen(color)) + 0057 BLUE_WEIGHT * static_cast<unsigned int> (qBlue(color)); 0058 } 0059 0060 //--------------------------------------------------------------------- 0061 0062 inline unsigned int AdjustTone(unsigned int color, unsigned int oldTone, unsigned int newTone, double amount) 0063 { 0064 return qRgba( 0065 qMax(0, qMin(255, static_cast<int> (amount * qRed(color) * newTone / oldTone + (1.0 - amount) * qRed(color)))), 0066 qMax(0, qMin(255, static_cast<int> (amount * qGreen(color) * newTone / oldTone + (1.0 - amount) * qGreen(color)))), 0067 qMax(0, qMin(255, static_cast<int> (amount * qBlue(color) * newTone / oldTone + (1.0 - amount) * qBlue(color)))), 0068 qAlpha(color) 0069 ); 0070 } 0071 0072 //--------------------------------------------------------------------- 0073 0074 class kpEffectToneEnhanceApplier 0075 { 0076 public: 0077 kpEffectToneEnhanceApplier (); 0078 ~kpEffectToneEnhanceApplier (); 0079 0080 kpEffectToneEnhanceApplier(const kpEffectToneEnhanceApplier &) = delete; 0081 kpEffectToneEnhanceApplier &operator=(const kpEffectToneEnhanceApplier &) = delete; 0082 0083 void BalanceImageTone(QImage* pImage, double granularity, double amount); 0084 0085 protected: 0086 int m_nToneMapGranularity, m_areaWid, m_areaHgt; 0087 unsigned int m_nComputedWid, m_nComputedHgt; 0088 // LOTODO: Use less error-prone QTL containers instead. 0089 unsigned int* m_pHistogram; 0090 unsigned int** m_pToneMaps; 0091 0092 void DeleteToneMaps(); 0093 unsigned int* MakeToneMap(QImage* pImage, int x, int y, int nGranularity); 0094 void ComputeToneMaps(QImage* pImage, int nGranularity); 0095 unsigned int InterpolateNewTone(QImage* pImage, unsigned int oldTone, int x, int y, int nGranularity); 0096 }; 0097 0098 //--------------------------------------------------------------------- 0099 0100 kpEffectToneEnhanceApplier::kpEffectToneEnhanceApplier () 0101 { 0102 m_nToneMapGranularity = 0; 0103 m_areaWid = 0; 0104 m_areaHgt = 0; 0105 m_nComputedWid = 0; 0106 m_nComputedHgt = 0; 0107 m_pHistogram = new unsigned int[TONE_MAP_SIZE]; 0108 m_pToneMaps = nullptr; 0109 } 0110 0111 //--------------------------------------------------------------------- 0112 0113 kpEffectToneEnhanceApplier::~kpEffectToneEnhanceApplier () 0114 { 0115 DeleteToneMaps(); 0116 delete[] m_pHistogram; 0117 } 0118 0119 //--------------------------------------------------------------------- 0120 0121 // protected 0122 void kpEffectToneEnhanceApplier::DeleteToneMaps() 0123 { 0124 int nToneMaps = m_nToneMapGranularity * m_nToneMapGranularity; 0125 for(int i = 0; i < nToneMaps; i++) { 0126 delete[] m_pToneMaps[i]; 0127 } 0128 delete[] m_pToneMaps; 0129 m_pToneMaps = nullptr; 0130 m_nToneMapGranularity = 0; 0131 } 0132 0133 //--------------------------------------------------------------------- 0134 0135 // protected 0136 unsigned int* kpEffectToneEnhanceApplier::MakeToneMap(QImage* pImage, int u, int v, int nGranularity) 0137 { 0138 // Compute the region to make the tone map for 0139 int xx, yy; 0140 if(nGranularity > 1) 0141 { 0142 xx = u * (pImage->width() - 1) / (nGranularity - 1) - m_areaWid / 2; 0143 if(xx < 0) { 0144 xx = 0; 0145 } 0146 else if(xx + m_areaWid > pImage->width()) { 0147 xx = pImage->width() - m_areaWid; 0148 } 0149 0150 yy = v * (pImage->width() - 1) / (nGranularity - 1) - m_areaHgt / 2; 0151 0152 if(yy < 0) { 0153 yy = 0; 0154 } 0155 else if(yy + m_areaHgt > pImage->height()) { 0156 yy = pImage->height() - m_areaHgt; 0157 } 0158 } 0159 else 0160 { 0161 xx = 0; 0162 yy = 0; 0163 } 0164 0165 // Make a tone histogram for the region 0166 memset(m_pHistogram, '\0', sizeof(unsigned int) * TONE_MAP_SIZE); 0167 int x, y; 0168 unsigned int tone; 0169 for(y = 0; y < m_areaHgt; y++) 0170 { 0171 for(x = 0; x < m_areaWid; x++) 0172 { 0173 tone = ComputeTone(pImage->pixel(xx + x, yy + y)); 0174 m_pHistogram[tone >> TONE_DROP_BITS]++; 0175 } 0176 } 0177 0178 // Forward sum the tone histogram 0179 int i{}; 0180 for(i = 1; i < TONE_MAP_SIZE; i++) { 0181 m_pHistogram[i] += m_pHistogram[i - 1]; 0182 } 0183 0184 // Compute the forward contribution to the tone map 0185 auto total = m_pHistogram[i - 1]; 0186 auto *pToneMap = new unsigned int[TONE_MAP_SIZE]; 0187 for(i = 0; i < TONE_MAP_SIZE; i++) { 0188 pToneMap[i] = static_cast<uint> (static_cast<unsigned long long int> (m_pHistogram[i] * MAX_TONE_VALUE / total)); 0189 } 0190 /* 0191 // Undo the forward sum and reverse sum the tone histogram 0192 m_pHistogram[TONE_MAP_SIZE - 1] -= m_pHistogram[TONE_MAP_SIZE - 2]; 0193 for(i = TONE_MAP_SIZE - 2; i > 0; i--) 0194 { 0195 m_pHistogram[i] -= m_pHistogram[i - 1]; 0196 m_pHistogram[i] += m_pHistogram[i + 1]; 0197 } 0198 m_pHistogram[0] += m_pHistogram[1]; 0199 */ 0200 return pToneMap; 0201 } 0202 0203 //--------------------------------------------------------------------- 0204 0205 // protected 0206 void kpEffectToneEnhanceApplier::ComputeToneMaps(QImage* pImage, int nGranularity) 0207 { 0208 if(nGranularity == m_nToneMapGranularity && pImage->width() == 0209 static_cast<int> (m_nComputedWid) && pImage->height() == static_cast<int> (m_nComputedHgt)) 0210 { 0211 return; // We've already computed tone maps for this granularity 0212 } 0213 DeleteToneMaps(); 0214 m_pToneMaps = new unsigned int*[nGranularity * nGranularity]; 0215 m_nToneMapGranularity = nGranularity; 0216 m_nComputedWid = static_cast<unsigned int> (pImage->width()); 0217 m_nComputedHgt = static_cast<unsigned int> (pImage->height()); 0218 int u, v; 0219 for(v = 0; v < nGranularity; v++) 0220 { 0221 for(u = 0; u < nGranularity; u++) { 0222 m_pToneMaps[nGranularity * v + u] = MakeToneMap(pImage, u, v, nGranularity); 0223 } 0224 } 0225 } 0226 0227 //--------------------------------------------------------------------- 0228 0229 // protected 0230 unsigned int kpEffectToneEnhanceApplier::InterpolateNewTone(QImage* pImage, unsigned int oldTone, int x, int y, int nGranularity) 0231 { 0232 oldTone = (oldTone >> TONE_DROP_BITS); 0233 if(m_nToneMapGranularity <= 1) { 0234 return m_pToneMaps[0][oldTone]; 0235 } 0236 auto u = x * (nGranularity - 1) / pImage->width(); 0237 auto v = y * (nGranularity - 1) / pImage->height(); 0238 auto x1y1 = m_pToneMaps[m_nToneMapGranularity * v + u][oldTone]; 0239 auto x2y1 = m_pToneMaps[m_nToneMapGranularity * v + u + 1][oldTone]; 0240 auto x1y2 = m_pToneMaps[m_nToneMapGranularity * (v + 1) + u][oldTone]; 0241 auto x2y2 = m_pToneMaps[m_nToneMapGranularity * (v + 1) + u + 1][oldTone]; 0242 auto hFac = x - (u * (pImage->width() - 1) / (nGranularity - 1)); 0243 if(hFac > m_areaWid) { 0244 hFac = m_areaWid; 0245 } 0246 unsigned int y1 = (x1y1 * (static_cast<unsigned int> (m_areaWid) - static_cast<unsigned int> (hFac)) 0247 + x2y1 * static_cast<unsigned int> (hFac)) / static_cast<unsigned int> (m_areaWid); 0248 0249 unsigned int y2 = (x1y2 * (static_cast<unsigned int> (m_areaWid) - static_cast<unsigned int> (hFac)) 0250 + x2y2 * static_cast<unsigned int> (hFac)) / static_cast<unsigned int> (m_areaWid); 0251 0252 int vFac = y - (v * (pImage->height() - 1) / (nGranularity - 1)); 0253 if(vFac > m_areaHgt) { 0254 vFac = m_areaHgt; 0255 } 0256 return (y1 * (static_cast<unsigned int> (m_areaHgt) - static_cast<unsigned int> (vFac)) 0257 + y2 * static_cast<unsigned int> (vFac)) / static_cast<unsigned int> (m_areaHgt); 0258 } 0259 0260 //--------------------------------------------------------------------- 0261 0262 // public 0263 void kpEffectToneEnhanceApplier::BalanceImageTone(QImage* pImage, double granularity, double amount) 0264 { 0265 if(pImage->width() < MIN_IMAGE_DIM || pImage->height() < MIN_IMAGE_DIM) { 0266 return; // the image is not big enough to perform this operation 0267 } 0268 int nGranularity = static_cast<int> (granularity * (MAX_GRANULARITY - 2)) + 1; 0269 m_areaWid = pImage->width() / nGranularity; 0270 if(m_areaWid < MIN_IMAGE_DIM) { 0271 m_areaWid = MIN_IMAGE_DIM; 0272 } 0273 m_areaHgt = pImage->height() / nGranularity; 0274 if(m_areaHgt < MIN_IMAGE_DIM) { 0275 m_areaHgt = MIN_IMAGE_DIM; 0276 } 0277 ComputeToneMaps(pImage, nGranularity); 0278 int x, y; 0279 unsigned int oldTone, newTone, col; 0280 for(y = 0; y < pImage->height(); y++) 0281 { 0282 for(x = 0; x < pImage->width(); x++) 0283 { 0284 col = pImage->pixel(x, y); 0285 oldTone = ComputeTone(col); 0286 newTone = InterpolateNewTone(pImage, oldTone, x, y, nGranularity); 0287 pImage->setPixel(x, y, AdjustTone(col, oldTone, newTone, amount)); 0288 } 0289 } 0290 } 0291 0292 //--------------------------------------------------------------------- 0293 0294 // public static 0295 kpImage kpEffectToneEnhance::applyEffect (const kpImage &image, 0296 double granularity, double amount) 0297 { 0298 if (amount == 0.0) { 0299 return image; 0300 } 0301 0302 QImage qimage(image); 0303 0304 // OPT: Cache the calculated values? 0305 kpEffectToneEnhanceApplier applier; 0306 applier.BalanceImageTone (&qimage, granularity, amount); 0307 0308 return qimage; 0309 } 0310 0311 //---------------------------------------------------------------------