/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef QDENGINE_QDCORE_UTIL_2_PASS_SCALE_H #define QDENGINE_QDCORE_UTIL_2_PASS_SCALE_H #include "qdengine/qdcore/util/Filters.h" #define TRACE(a) #define ASSERT(a) namespace QDEngine { namespace scl { typedef struct { double *weights; // Normalized weights of neighboring pixels int left, right; // Bounds of source pixels window } ContributionType; // Contirbution information for a single pixel typedef struct { ContributionType *contribRow; // Row (or column) of contribution weights uint32 windowSize, // Filter window size (of affecting source pixels) lineLength; // Length of line (no. or rows / cols) } LineContribType; // Contribution information for an entire line (row or column) template class C2PassScale { public: C2PassScale() : _temp_buffer(65536, 0), _weights_buffer(16384, 0.0), _contribution_buffer(500) { } virtual ~C2PassScale() { } uint32 *scale(uint32 *pOrigImage, uint32 uOrigWidth, uint32 uOrigHeight, uint32 *pDstImage, uint32 uNewWidth, uint32 uNewHeight); private: Std::vector _temp_buffer; Std::vector _weights_buffer; Std::vector _contribution_buffer; LineContribType *allocContributions(uint32 uLineLength, uint32 uWindowSize); LineContribType *calcContributions(uint32 uLineSize, uint32 uSrcSize, double dScale); void scaleRow(uint32 *pSrc, uint32 uSrcWidth, uint32 *pRes, uint32 uResWidth, uint32 uRow, LineContribType *Contrib); void horizScale(uint32 *pSrc, uint32 uSrcWidth, uint32 uSrcHeight, uint32 *pDst, uint32 uResWidth, uint32 uResHeight); void scaleCol(uint32 *pSrc, uint32 uSrcWidth, uint32 *pRes, uint32 uResWidth, uint32 uResHeight, uint32 uCol, LineContribType *Contrib); void vertScale(uint32 *pSrc, uint32 uSrcWidth, uint32 uSrcHeight, uint32 *pDst, uint32 uResWidth, uint32 uResHeight); static inline byte make_r(uint32 col) { return reinterpret_cast(&col)[2]; } static inline byte make_g(uint32 col) { return reinterpret_cast(&col)[1]; } static inline byte make_b(uint32 col) { return reinterpret_cast(&col)[0]; } static inline byte make_a(uint32 col) { return reinterpret_cast(&col)[3]; } static inline uint32 make_rgba(byte r, byte g, byte b, byte a) { return (r << 16) | (g << 8) | (b << 0) | (a << 24); } }; template LineContribType *C2PassScale::allocContributions(uint32 uLineLength, uint32 uWindowSize) { static LineContribType line_ct; line_ct.windowSize = uWindowSize; line_ct.lineLength = uLineLength; if (_contribution_buffer.size() < uLineLength) _contribution_buffer.resize(uLineLength); line_ct.contribRow = &*_contribution_buffer.begin(); if (_weights_buffer.size() < uLineLength * uWindowSize) _weights_buffer.resize(uLineLength * uWindowSize); double *p = &*_weights_buffer.begin(); for (uint32 u = 0; u < uLineLength; u++) { line_ct.contribRow[u].weights = p; p += uWindowSize; } return &line_ct; } template LineContribType *C2PassScale::calcContributions(uint32 uLineSize, uint32 uSrcSize, double dScale) { FilterClass curFilter; double dWidth; double dFScale = 1.0; double dFilterWidth = curFilter.getWidth(); if (dScale < 1.0) { // Minification dWidth = dFilterWidth / dScale; dFScale = dScale; } else { // Magnification dWidth = dFilterWidth; } // Window size is the number of sampled pixels int iWindowSize = 2 * (int)ceil(dWidth) + 1; // Allocate a new line contributions strucutre LineContribType *res = allocContributions(uLineSize, iWindowSize); for (uint32 u = 0; u < uLineSize; u++) { // Scan through line of contributions double dCenter = (double)u / dScale; // Reverse mapping // Find the significant edge points that affect the pixel int iLeft = MAX(0, (int)floor(dCenter - dWidth)); int iRight = MIN((int)ceil(dCenter + dWidth), int(uSrcSize) - 1); // Cut edge points to fit in filter window in case of spill-off if (iRight - iLeft + 1 > iWindowSize) { if (iLeft < (int(uSrcSize) - 1 / 2)) { iLeft++; } else { iRight--; } } res->contribRow[u].left = iLeft; res->contribRow[u].right = iRight; double dTotalWeight = 0.0; // Zero sum of weights for (int iSrc = iLeft; iSrc <= iRight; iSrc++) { // Calculate weights dTotalWeight += (res->contribRow[u].weights[iSrc - iLeft] = dFScale * curFilter.filter(dFScale * (dCenter - (double)iSrc))); } ASSERT(dTotalWeight >= 0.0); // An error in the filter function can cause this if (dTotalWeight > 0.0) { // Normalize weight of neighbouring points for (int iSrc = iLeft; iSrc <= iRight; iSrc++) { // Normalize point res->contribRow[u].weights[iSrc - iLeft] /= dTotalWeight; } } } return res; } template void C2PassScale::scaleRow(uint32 *pSrc, uint32 uSrcWidth, uint32 *pRes, uint32 uResWidth, uint32 uRow, LineContribType *contrib) { uint32 *pSrcRow = &(pSrc[uRow * uSrcWidth]); uint32 *pDstRow = &(pRes[uRow * uResWidth]); for (uint32 x = 0; x < uResWidth; x++) { // Loop through row double dr = 0.0; double dg = 0.0; double db = 0.0; double da = 0.0; int iLeft = contrib->contribRow[x].left; // Retrieve left boundries int iRight = contrib->contribRow[x].right; // Retrieve right boundries for (int i = iLeft; i <= iRight; i++) { // Scan between boundries // Accumulate weighted effect of each neighboring pixel dr += contrib->contribRow[x].weights[i - iLeft] * (double)(make_r(pSrcRow[i])); dg += contrib->contribRow[x].weights[i - iLeft] * (double)(make_g(pSrcRow[i])); db += contrib->contribRow[x].weights[i - iLeft] * (double)(make_b(pSrcRow[i])); da += contrib->contribRow[x].weights[i - iLeft] * (double)(make_a(pSrcRow[i])); } uint32 r = round(dr); uint32 g = round(dg); uint32 b = round(db); uint32 a = round(da); pDstRow[x] = make_rgba(r, g, b, a); // Place result in destination pixel } } template void C2PassScale::horizScale(uint32 *pSrc, uint32 uSrcWidth, uint32 uSrcHeight, uint32 *pDst, uint32 uResWidth, uint32 uResHeight) { TRACE("Performing horizontal scaling...\n"); if (uResWidth == uSrcWidth) { // No scaling required, just copy memcpy(pDst, pSrc, sizeof(uint32) * uSrcHeight * uSrcWidth); return; } // Allocate and calculate the contributions LineContribType *contrib = calcContributions(uResWidth, uSrcWidth, double(uResWidth) / double(uSrcWidth)); for (uint32 u = 0; u < uResHeight; u++) scaleRow(pSrc, uSrcWidth, pDst, uResWidth, u, contrib); // Scale each row } template void C2PassScale::scaleCol(uint32 *pSrc, uint32 uSrcWidth, uint32 *pRes, uint32 uResWidth, uint32 uResHeight, uint32 uCol, LineContribType *contrib) { for (uint32 y = 0; y < uResHeight; y++) { // Loop through column double dr = 0.0; double dg = 0.0; double db = 0.0; double da = 0.0; int iLeft = contrib->contribRow[y].left; // Retrieve left boundries int iRight = contrib->contribRow[y].right; // Retrieve right boundries for (int i = iLeft; i <= iRight; i++) { // Scan between boundries // Accumulate weighted effect of each neighboring pixel uint32 pCurSrc = pSrc[i * uSrcWidth + uCol]; dr += contrib->contribRow[y].weights[i - iLeft] * (double)(make_r(pCurSrc)); dg += contrib->contribRow[y].weights[i - iLeft] * (double)(make_g(pCurSrc)); db += contrib->contribRow[y].weights[i - iLeft] * (double)(make_b(pCurSrc)); da += contrib->contribRow[y].weights[i - iLeft] * (double)(make_a(pCurSrc)); } uint32 r = round(dr); uint32 g = round(dg); uint32 b = round(db); uint32 a = round(da); pRes[y * uResWidth + uCol] = make_rgba(r, g, b, a); // Place result in destination pixel } } template void C2PassScale::vertScale(uint32 *pSrc, uint32 uSrcWidth, uint32 uSrcHeight, uint32 *pDst, uint32 uResWidth, uint32 uResHeight) { TRACE("Performing vertical scaling..."); if (uSrcHeight == uResHeight) { // No scaling required, just copy memcpy(pDst, pSrc, sizeof(uint32) * uSrcHeight * uSrcWidth); return; } // Allocate and calculate the contributions LineContribType *Contrib = calcContributions(uResHeight, uSrcHeight, double(uResHeight) / double(uSrcHeight)); for (uint32 u = 0; u < uResWidth; u++) scaleCol(pSrc, uSrcWidth, pDst, uResWidth, uResHeight, u, Contrib); // Scale each column } template uint32 *C2PassScale::scale(uint32 *pOrigImage, uint32 uOrigWidth, uint32 uOrigHeight, uint32 *pDstImage, uint32 uNewWidth, uint32 uNewHeight) { if (_temp_buffer.size() < uNewWidth * uOrigHeight) _temp_buffer.resize(uNewWidth * uOrigHeight); uint32 *pTemp = reinterpret_cast(&*_temp_buffer.begin()); horizScale(pOrigImage, uOrigWidth, uOrigHeight, pTemp, uNewWidth, uOrigHeight); // Scale temporary image vertically into result image vertScale(pTemp, uNewWidth, uOrigHeight, pDstImage, uNewWidth, uNewHeight); return pDstImage; } } // namespace scl } // namespace QDEngine #endif // QDENGINE_QDCORE_UTIL_2_PASS_SCALE_H