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 //---------------------------------------------------------------------