/* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2006 Mike Gashler All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // TODO: Clarence's code review #include "kpEffectToneEnhance.h" #include #include "kpLogCategories.h" #include "pixmapfx/kpPixmapFX.h" #define RED_WEIGHT 77 #define GREEN_WEIGHT 150 #define BLUE_WEIGHT 29 #define MAX_TONE_VALUE ((RED_WEIGHT + GREEN_WEIGHT + BLUE_WEIGHT) * 255) #define TONE_DROP_BITS 5 #define TONE_MAP_SIZE ((MAX_TONE_VALUE >> TONE_DROP_BITS) + 1) #define MAX_GRANULARITY 25 #define MIN_IMAGE_DIM 3 //--------------------------------------------------------------------- inline unsigned int ComputeTone(unsigned int color) { return RED_WEIGHT * static_cast (qRed(color)) + GREEN_WEIGHT * static_cast (qGreen(color)) + BLUE_WEIGHT * static_cast (qBlue(color)); } //--------------------------------------------------------------------- inline unsigned int AdjustTone(unsigned int color, unsigned int oldTone, unsigned int newTone, double amount) { return qRgba( qMax(0, qMin(255, static_cast (amount * qRed(color) * newTone / oldTone + (1.0 - amount) * qRed(color)))), qMax(0, qMin(255, static_cast (amount * qGreen(color) * newTone / oldTone + (1.0 - amount) * qGreen(color)))), qMax(0, qMin(255, static_cast (amount * qBlue(color) * newTone / oldTone + (1.0 - amount) * qBlue(color)))), qAlpha(color) ); } //--------------------------------------------------------------------- class kpEffectToneEnhanceApplier { public: kpEffectToneEnhanceApplier (); ~kpEffectToneEnhanceApplier (); void BalanceImageTone(QImage* pImage, double granularity, double amount); protected: int m_nToneMapGranularity, m_areaWid, m_areaHgt; unsigned int m_nComputedWid, m_nComputedHgt; // LOTODO: Use less error-prone QTL containers instead. unsigned int* m_pHistogram; unsigned int** m_pToneMaps; void DeleteToneMaps(); unsigned int* MakeToneMap(QImage* pImage, int x, int y, int nGranularity); void ComputeToneMaps(QImage* pImage, int nGranularity); unsigned int InterpolateNewTone(QImage* pImage, unsigned int oldTone, int x, int y, int nGranularity); }; //--------------------------------------------------------------------- kpEffectToneEnhanceApplier::kpEffectToneEnhanceApplier () { m_nToneMapGranularity = 0; m_areaWid = 0; m_areaHgt = 0; m_nComputedWid = 0; m_nComputedHgt = 0; m_pHistogram = new unsigned int[TONE_MAP_SIZE]; m_pToneMaps = nullptr; } //--------------------------------------------------------------------- kpEffectToneEnhanceApplier::~kpEffectToneEnhanceApplier () { DeleteToneMaps(); delete[] m_pHistogram; } //--------------------------------------------------------------------- // protected void kpEffectToneEnhanceApplier::DeleteToneMaps() { int nToneMaps = m_nToneMapGranularity * m_nToneMapGranularity; for(int i = 0; i < nToneMaps; i++) { delete[] m_pToneMaps[i]; } delete[] m_pToneMaps; m_pToneMaps = nullptr; m_nToneMapGranularity = 0; } //--------------------------------------------------------------------- // protected unsigned int* kpEffectToneEnhanceApplier::MakeToneMap(QImage* pImage, int u, int v, int nGranularity) { // Compute the region to make the tone map for int xx, yy; if(nGranularity > 1) { xx = u * (pImage->width() - 1) / (nGranularity - 1) - m_areaWid / 2; if(xx < 0) { xx = 0; } else if(xx + m_areaWid > pImage->width()) { xx = pImage->width() - m_areaWid; } yy = v * (pImage->width() - 1) / (nGranularity - 1) - m_areaHgt / 2; if(yy < 0) { yy = 0; } else if(yy + m_areaHgt > pImage->height()) { yy = pImage->height() - m_areaHgt; } } else { xx = 0; yy = 0; } // Make a tone histogram for the region memset(m_pHistogram, '\0', sizeof(unsigned int) * TONE_MAP_SIZE); int x, y; unsigned int tone; for(y = 0; y < m_areaHgt; y++) { for(x = 0; x < m_areaWid; x++) { tone = ComputeTone(pImage->pixel(xx + x, yy + y)); m_pHistogram[tone >> TONE_DROP_BITS]++; } } // Forward sum the tone histogram int i{}; for(i = 1; i < TONE_MAP_SIZE; i++) { m_pHistogram[i] += m_pHistogram[i - 1]; } // Compute the forward contribution to the tone map auto total = m_pHistogram[i - 1]; auto *pToneMap = new unsigned int[TONE_MAP_SIZE]; for(i = 0; i < TONE_MAP_SIZE; i++) { pToneMap[i] = static_cast (static_cast (m_pHistogram[i] * MAX_TONE_VALUE / total)); } /* // Undo the forward sum and reverse sum the tone histogram m_pHistogram[TONE_MAP_SIZE - 1] -= m_pHistogram[TONE_MAP_SIZE - 2]; for(i = TONE_MAP_SIZE - 2; i > 0; i--) { m_pHistogram[i] -= m_pHistogram[i - 1]; m_pHistogram[i] += m_pHistogram[i + 1]; } m_pHistogram[0] += m_pHistogram[1]; */ return pToneMap; } //--------------------------------------------------------------------- // protected void kpEffectToneEnhanceApplier::ComputeToneMaps(QImage* pImage, int nGranularity) { if(nGranularity == m_nToneMapGranularity && pImage->width() == static_cast (m_nComputedWid) && pImage->height() == static_cast (m_nComputedHgt)) { return; // We've already computed tone maps for this granularity } DeleteToneMaps(); m_pToneMaps = new unsigned int*[nGranularity * nGranularity]; m_nToneMapGranularity = nGranularity; m_nComputedWid = static_cast (pImage->width()); m_nComputedHgt = static_cast (pImage->height()); int u, v; for(v = 0; v < nGranularity; v++) { for(u = 0; u < nGranularity; u++) { m_pToneMaps[nGranularity * v + u] = MakeToneMap(pImage, u, v, nGranularity); } } } //--------------------------------------------------------------------- // protected unsigned int kpEffectToneEnhanceApplier::InterpolateNewTone(QImage* pImage, unsigned int oldTone, int x, int y, int nGranularity) { oldTone = (oldTone >> TONE_DROP_BITS); if(m_nToneMapGranularity <= 1) { return m_pToneMaps[0][oldTone]; } auto u = x * (nGranularity - 1) / pImage->width(); auto v = y * (nGranularity - 1) / pImage->height(); auto x1y1 = m_pToneMaps[m_nToneMapGranularity * v + u][oldTone]; auto x2y1 = m_pToneMaps[m_nToneMapGranularity * v + u + 1][oldTone]; auto x1y2 = m_pToneMaps[m_nToneMapGranularity * (v + 1) + u][oldTone]; auto x2y2 = m_pToneMaps[m_nToneMapGranularity * (v + 1) + u + 1][oldTone]; auto hFac = x - (u * (pImage->width() - 1) / (nGranularity - 1)); if(hFac > m_areaWid) { hFac = m_areaWid; } unsigned int y1 = (x1y1 * (static_cast (m_areaWid) - static_cast (hFac)) + x2y1 * static_cast (hFac)) / static_cast (m_areaWid); unsigned int y2 = (x1y2 * (static_cast (m_areaWid) - static_cast (hFac)) + x2y2 * static_cast (hFac)) / static_cast (m_areaWid); int vFac = y - (v * (pImage->height() - 1) / (nGranularity - 1)); if(vFac > m_areaHgt) { vFac = m_areaHgt; } return (y1 * (static_cast (m_areaHgt) - static_cast (vFac)) + y2 * static_cast (vFac)) / static_cast (m_areaHgt); } //--------------------------------------------------------------------- // public void kpEffectToneEnhanceApplier::BalanceImageTone(QImage* pImage, double granularity, double amount) { if(pImage->width() < MIN_IMAGE_DIM || pImage->height() < MIN_IMAGE_DIM) { return; // the image is not big enough to perform this operation } int nGranularity = static_cast (granularity * (MAX_GRANULARITY - 2)) + 1; m_areaWid = pImage->width() / nGranularity; if(m_areaWid < MIN_IMAGE_DIM) { m_areaWid = MIN_IMAGE_DIM; } m_areaHgt = pImage->height() / nGranularity; if(m_areaHgt < MIN_IMAGE_DIM) { m_areaHgt = MIN_IMAGE_DIM; } ComputeToneMaps(pImage, nGranularity); int x, y; unsigned int oldTone, newTone, col; for(y = 0; y < pImage->height(); y++) { for(x = 0; x < pImage->width(); x++) { col = pImage->pixel(x, y); oldTone = ComputeTone(col); newTone = InterpolateNewTone(pImage, oldTone, x, y, nGranularity); pImage->setPixel(x, y, AdjustTone(col, oldTone, newTone, amount)); } } } //--------------------------------------------------------------------- // public static kpImage kpEffectToneEnhance::applyEffect (const kpImage &image, double granularity, double amount) { if (amount == 0.0) { return image; } QImage qimage(image); // OPT: Cache the calculated values? kpEffectToneEnhanceApplier applier; applier.BalanceImageTone (&qimage, granularity, amount); return qimage; } //---------------------------------------------------------------------