| /* |
| * Copyright 2015 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "SkBitmapScaler.h" |
| #include "SkBitmapFilter.h" |
| #include "SkConvolver.h" |
| #include "SkImageInfo.h" |
| #include "SkPixmap.h" |
| #include "SkRect.h" |
| #include "SkTArray.h" |
| |
| // SkResizeFilter ---------------------------------------------------------------- |
| |
| // Encapsulates computation and storage of the filters required for one complete |
| // resize operation. |
| class SkResizeFilter { |
| public: |
| SkResizeFilter(SkBitmapScaler::ResizeMethod method, |
| int srcFullWidth, int srcFullHeight, |
| float destWidth, float destHeight, |
| const SkRect& destSubset); |
| ~SkResizeFilter() { delete fBitmapFilter; } |
| |
| // Returns the filled filter values. |
| const SkConvolutionFilter1D& xFilter() { return fXFilter; } |
| const SkConvolutionFilter1D& yFilter() { return fYFilter; } |
| |
| private: |
| |
| SkBitmapFilter* fBitmapFilter; |
| |
| // Computes one set of filters either horizontally or vertically. The caller |
| // will specify the "min" and "max" rather than the bottom/top and |
| // right/bottom so that the same code can be re-used in each dimension. |
| // |
| // |srcDependLo| and |srcDependSize| gives the range for the source |
| // depend rectangle (horizontally or vertically at the caller's discretion |
| // -- see above for what this means). |
| // |
| // Likewise, the range of destination values to compute and the scale factor |
| // for the transform is also specified. |
| |
| void computeFilters(int srcSize, |
| float destSubsetLo, float destSubsetSize, |
| float scale, |
| SkConvolutionFilter1D* output); |
| |
| SkConvolutionFilter1D fXFilter; |
| SkConvolutionFilter1D fYFilter; |
| }; |
| |
| SkResizeFilter::SkResizeFilter(SkBitmapScaler::ResizeMethod method, |
| int srcFullWidth, int srcFullHeight, |
| float destWidth, float destHeight, |
| const SkRect& destSubset) { |
| |
| SkASSERT(method >= SkBitmapScaler::RESIZE_FirstMethod && |
| method <= SkBitmapScaler::RESIZE_LastMethod); |
| |
| fBitmapFilter = nullptr; |
| switch(method) { |
| case SkBitmapScaler::RESIZE_BOX: |
| fBitmapFilter = new SkBoxFilter; |
| break; |
| case SkBitmapScaler::RESIZE_TRIANGLE: |
| fBitmapFilter = new SkTriangleFilter; |
| break; |
| case SkBitmapScaler::RESIZE_MITCHELL: |
| fBitmapFilter = new SkMitchellFilter; |
| break; |
| case SkBitmapScaler::RESIZE_HAMMING: |
| fBitmapFilter = new SkHammingFilter; |
| break; |
| case SkBitmapScaler::RESIZE_LANCZOS3: |
| fBitmapFilter = new SkLanczosFilter; |
| break; |
| } |
| |
| |
| float scaleX = destWidth / srcFullWidth; |
| float scaleY = destHeight / srcFullHeight; |
| |
| this->computeFilters(srcFullWidth, destSubset.fLeft, destSubset.width(), |
| scaleX, &fXFilter); |
| if (srcFullWidth == srcFullHeight && |
| destSubset.fLeft == destSubset.fTop && |
| destSubset.width() == destSubset.height()&& |
| scaleX == scaleY) { |
| fYFilter = fXFilter; |
| } else { |
| this->computeFilters(srcFullHeight, destSubset.fTop, destSubset.height(), |
| scaleY, &fYFilter); |
| } |
| } |
| |
| // TODO(egouriou): Take advantage of periods in the convolution. |
| // Practical resizing filters are periodic outside of the border area. |
| // For Lanczos, a scaling by a (reduced) factor of p/q (q pixels in the |
| // source become p pixels in the destination) will have a period of p. |
| // A nice consequence is a period of 1 when downscaling by an integral |
| // factor. Downscaling from typical display resolutions is also bound |
| // to produce interesting periods as those are chosen to have multiple |
| // small factors. |
| // Small periods reduce computational load and improve cache usage if |
| // the coefficients can be shared. For periods of 1 we can consider |
| // loading the factors only once outside the borders. |
| void SkResizeFilter::computeFilters(int srcSize, |
| float destSubsetLo, float destSubsetSize, |
| float scale, |
| SkConvolutionFilter1D* output) { |
| float destSubsetHi = destSubsetLo + destSubsetSize; // [lo, hi) |
| |
| // When we're doing a magnification, the scale will be larger than one. This |
| // means the destination pixels are much smaller than the source pixels, and |
| // that the range covered by the filter won't necessarily cover any source |
| // pixel boundaries. Therefore, we use these clamped values (max of 1) for |
| // some computations. |
| float clampedScale = SkTMin(1.0f, scale); |
| |
| // This is how many source pixels from the center we need to count |
| // to support the filtering function. |
| float srcSupport = fBitmapFilter->width() / clampedScale; |
| |
| float invScale = 1.0f / scale; |
| |
| SkSTArray<64, float, true> filterValuesArray; |
| SkSTArray<64, SkConvolutionFilter1D::ConvolutionFixed, true> fixedFilterValuesArray; |
| |
| // Loop over all pixels in the output range. We will generate one set of |
| // filter values for each one. Those values will tell us how to blend the |
| // source pixels to compute the destination pixel. |
| |
| // This is the pixel in the source directly under the pixel in the dest. |
| // Note that we base computations on the "center" of the pixels. To see |
| // why, observe that the destination pixel at coordinates (0, 0) in a 5.0x |
| // downscale should "cover" the pixels around the pixel with *its center* |
| // at coordinates (2.5, 2.5) in the source, not those around (0, 0). |
| // Hence we need to scale coordinates (0.5, 0.5), not (0, 0). |
| destSubsetLo = SkScalarFloorToScalar(destSubsetLo); |
| destSubsetHi = SkScalarCeilToScalar(destSubsetHi); |
| float srcPixel = (destSubsetLo + 0.5f) * invScale; |
| int destLimit = SkScalarTruncToInt(destSubsetHi - destSubsetLo); |
| output->reserveAdditional(destLimit, SkScalarCeilToInt(destLimit * srcSupport * 2)); |
| for (int destI = 0; destI < destLimit; srcPixel += invScale, destI++) { |
| // Compute the (inclusive) range of source pixels the filter covers. |
| float srcBegin = SkTMax(0.f, SkScalarFloorToScalar(srcPixel - srcSupport)); |
| float srcEnd = SkTMin(srcSize - 1.f, SkScalarCeilToScalar(srcPixel + srcSupport)); |
| |
| // Compute the unnormalized filter value at each location of the source |
| // it covers. |
| |
| // Sum of the filter values for normalizing. |
| // Distance from the center of the filter, this is the filter coordinate |
| // in source space. We also need to consider the center of the pixel |
| // when comparing distance against 'srcPixel'. In the 5x downscale |
| // example used above the distance from the center of the filter to |
| // the pixel with coordinates (2, 2) should be 0, because its center |
| // is at (2.5, 2.5). |
| float destFilterDist = (srcBegin + 0.5f - srcPixel) * clampedScale; |
| int filterCount = SkScalarTruncToInt(srcEnd - srcBegin) + 1; |
| if (filterCount <= 0) { |
| // true when srcSize is equal to srcPixel - srcSupport; this may be a bug |
| return; |
| } |
| filterValuesArray.reset(filterCount); |
| float filterSum = fBitmapFilter->evaluate_n(destFilterDist, clampedScale, filterCount, |
| filterValuesArray.begin()); |
| |
| // The filter must be normalized so that we don't affect the brightness of |
| // the image. Convert to normalized fixed point. |
| int fixedSum = 0; |
| fixedFilterValuesArray.reset(filterCount); |
| const float* filterValues = filterValuesArray.begin(); |
| SkConvolutionFilter1D::ConvolutionFixed* fixedFilterValues = fixedFilterValuesArray.begin(); |
| float invFilterSum = 1 / filterSum; |
| for (int fixedI = 0; fixedI < filterCount; fixedI++) { |
| int curFixed = SkConvolutionFilter1D::FloatToFixed(filterValues[fixedI] * invFilterSum); |
| fixedSum += curFixed; |
| fixedFilterValues[fixedI] = SkToS16(curFixed); |
| } |
| SkASSERT(fixedSum <= 0x7FFF); |
| |
| // The conversion to fixed point will leave some rounding errors, which |
| // we add back in to avoid affecting the brightness of the image. We |
| // arbitrarily add this to the center of the filter array (this won't always |
| // be the center of the filter function since it could get clipped on the |
| // edges, but it doesn't matter enough to worry about that case). |
| int leftovers = SkConvolutionFilter1D::FloatToFixed(1) - fixedSum; |
| fixedFilterValues[filterCount / 2] += leftovers; |
| |
| // Now it's ready to go. |
| output->AddFilter(SkScalarFloorToInt(srcBegin), fixedFilterValues, filterCount); |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| static bool valid_for_resize(const SkPixmap& source, int dstW, int dstH) { |
| // TODO: Seems like we shouldn't care about the swizzle of source, just that it's 8888 |
| return source.addr() && source.colorType() == kN32_SkColorType && |
| source.width() >= 1 && source.height() >= 1 && dstW >= 1 && dstH >= 1; |
| } |
| |
| bool SkBitmapScaler::Resize(const SkPixmap& result, const SkPixmap& source, ResizeMethod method) { |
| if (!valid_for_resize(source, result.width(), result.height())) { |
| return false; |
| } |
| if (!result.addr() || result.colorType() != source.colorType()) { |
| return false; |
| } |
| |
| SkRect destSubset = SkRect::MakeIWH(result.width(), result.height()); |
| |
| SkResizeFilter filter(method, source.width(), source.height(), |
| result.width(), result.height(), destSubset); |
| |
| // Get a subset encompassing this touched area. We construct the |
| // offsets and row strides such that it looks like a new bitmap, while |
| // referring to the old data. |
| const uint8_t* sourceSubset = reinterpret_cast<const uint8_t*>(source.addr()); |
| |
| return BGRAConvolve2D(sourceSubset, static_cast<int>(source.rowBytes()), |
| !source.isOpaque(), filter.xFilter(), filter.yFilter(), |
| static_cast<int>(result.rowBytes()), |
| static_cast<unsigned char*>(result.writable_addr())); |
| } |
| |
| bool SkBitmapScaler::Resize(SkBitmap* resultPtr, const SkPixmap& source, ResizeMethod method, |
| int destWidth, int destHeight, SkBitmap::Allocator* allocator) { |
| // Preflight some of the checks, to avoid allocating the result if we don't need it. |
| if (!valid_for_resize(source, destWidth, destHeight)) { |
| return false; |
| } |
| |
| SkBitmap result; |
| // Note: pass along the profile information even thought this is no the right answer because |
| // this could be scaling in sRGB. |
| result.setInfo(SkImageInfo::MakeN32(destWidth, destHeight, source.alphaType(), |
| sk_ref_sp(source.info().colorSpace()))); |
| result.allocPixels(allocator, nullptr); |
| |
| SkPixmap resultPM; |
| if (!result.peekPixels(&resultPM) || !Resize(resultPM, source, method)) { |
| return false; |
| } |
| |
| *resultPtr = result; |
| SkASSERT(resultPtr->getPixels()); |
| return true; |
| } |