gecko-dev/gfx/2d/ConvolutionFilter.cpp

176 строки
6.3 KiB
C++

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "2D.h"
#include "ConvolutionFilter.h"
#include "skia/src/core/SkBitmapFilter.h"
#include "skia/src/core/SkConvolver.h"
#include "skia/src/core/SkOpts.h"
#include <algorithm>
#include <cmath>
#include "mozilla/Vector.h"
namespace mozilla {
namespace gfx {
ConvolutionFilter::ConvolutionFilter()
: mFilter(MakeUnique<SkConvolutionFilter1D>())
{
}
ConvolutionFilter::~ConvolutionFilter()
{
}
int32_t
ConvolutionFilter::MaxFilter() const
{
return mFilter->maxFilter();
}
int32_t
ConvolutionFilter::NumValues() const
{
return mFilter->numValues();
}
bool
ConvolutionFilter::GetFilterOffsetAndLength(int32_t aRowIndex, int32_t* aResultOffset, int32_t* aResultLength)
{
if (aRowIndex >= mFilter->numValues()) {
return false;
}
mFilter->FilterForValue(aRowIndex, aResultOffset, aResultLength);
return true;
}
void
ConvolutionFilter::ConvolveHorizontally(const uint8_t* aSrc, uint8_t* aDst, bool aHasAlpha)
{
SkOpts::convolve_horizontally(aSrc, *mFilter, aDst, aHasAlpha);
}
void
ConvolutionFilter::ConvolveVertically(uint8_t* const* aSrc, uint8_t* aDst, int32_t aRowIndex, int32_t aRowSize, bool aHasAlpha)
{
MOZ_ASSERT(aRowIndex < mFilter->numValues());
int32_t filterOffset;
int32_t filterLength;
auto filterValues = mFilter->FilterForValue(aRowIndex, &filterOffset, &filterLength);
SkOpts::convolve_vertically(filterValues, filterLength, aSrc, aRowSize, aDst, aHasAlpha);
}
/* ConvolutionFilter::ComputeResizeFactor is derived from Skia's SkBitmapScaler/SkResizeFilter::computeFactors.
* It is governed by Skia's BSD-style license (see gfx/skia/LICENSE) and the following copyright:
* Copyright (c) 2015 Google Inc.
*/
bool
ConvolutionFilter::ComputeResizeFilter(ResizeMethod aResizeMethod, int32_t aSrcSize, int32_t aDstSize)
{
typedef SkConvolutionFilter1D::ConvolutionFixed Fixed;
UniquePtr<SkBitmapFilter> bitmapFilter;
switch (aResizeMethod) {
case ResizeMethod::BOX:
bitmapFilter = MakeUnique<SkBoxFilter>();
break;
case ResizeMethod::TRIANGLE:
bitmapFilter = MakeUnique<SkTriangleFilter>();
break;
case ResizeMethod::LANCZOS3:
bitmapFilter = MakeUnique<SkLanczosFilter>();
break;
case ResizeMethod::HAMMING:
bitmapFilter = MakeUnique<SkHammingFilter>();
break;
case ResizeMethod::MITCHELL:
bitmapFilter = MakeUnique<SkMitchellFilter>();
break;
default:
return false;
}
// 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 scale = float(aDstSize) / float(aSrcSize);
float clampedScale = std::min(1.0f, scale);
// This is how many source pixels from the center we need to count
// to support the filtering function.
float srcSupport = bitmapFilter->width() / clampedScale;
float invScale = 1.0f / scale;
Vector<float, 64> filterValues;
Vector<Fixed, 64> fixedFilterValues;
// 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).
float srcPixel = 0.5f * invScale;
mFilter->reserveAdditional(aDstSize, int32_t(ceil(aDstSize * srcSupport * 2)));
for (int32_t destI = 0; destI < aDstSize; destI++) {
// Compute the (inclusive) range of source pixels the filter covers.
float srcBegin = std::max(0.0f, floorf(srcPixel - srcSupport));
float srcEnd = std::min(aSrcSize - 1.0f, ceilf(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;
int32_t filterCount = int32_t(srcEnd - srcBegin) + 1;
if (filterCount <= 0 ||
!filterValues.resize(filterCount) ||
!fixedFilterValues.resize(filterCount)) {
return false;
}
float filterSum = bitmapFilter->evaluate_n(destFilterDist, clampedScale, filterCount,
filterValues.begin());
// The filter must be normalized so that we don't affect the brightness of
// the image. Convert to normalized fixed point.
Fixed fixedSum = 0;
float invFilterSum = 1.0f / filterSum;
for (int32_t fixedI = 0; fixedI < filterCount; fixedI++) {
Fixed curFixed = SkConvolutionFilter1D::FloatToFixed(filterValues[fixedI] * invFilterSum);
fixedSum += curFixed;
fixedFilterValues[fixedI] = curFixed;
}
// 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).
Fixed leftovers = SkConvolutionFilter1D::FloatToFixed(1) - fixedSum;
fixedFilterValues[filterCount / 2] += leftovers;
mFilter->AddFilter(int32_t(srcBegin), fixedFilterValues.begin(), filterCount);
srcPixel += invScale;
}
return mFilter->maxFilter() > 0 && mFilter->numValues() == aDstSize;
}
} // namespace gfx
} // namespace mozilla