2017-10-28 02:10:06 +03:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
2017-06-22 20:57:17 +03:00
|
|
|
/* 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"
|
|
|
|
|
2020-01-18 16:48:34 +03:00
|
|
|
namespace mozilla::gfx {
|
2017-06-22 20:57:17 +03:00
|
|
|
|
|
|
|
ConvolutionFilter::ConvolutionFilter()
|
|
|
|
: mFilter(MakeUnique<SkConvolutionFilter1D>()) {}
|
|
|
|
|
2020-03-04 18:39:20 +03:00
|
|
|
ConvolutionFilter::~ConvolutionFilter() = default;
|
2017-06-22 20:57:17 +03:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
mFilter->reserveAdditional(aDstSize,
|
|
|
|
int32_t(ceil(aDstSize * srcSupport * 2)));
|
|
|
|
for (int32_t destI = 0; destI < aDstSize; destI++) {
|
2017-11-29 11:22:53 +03:00
|
|
|
// 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 = (static_cast<float>(destI) + 0.5f) * invScale;
|
|
|
|
|
2017-06-22 20:57:17 +03:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
return mFilter->maxFilter() > 0 && mFilter->numValues() == aDstSize;
|
|
|
|
}
|
|
|
|
|
2020-01-18 16:48:34 +03:00
|
|
|
} // namespace mozilla::gfx
|