зеркало из https://github.com/mozilla/moz-skia.git
Combine native sampling with sampling
In SkSampledCodec, allow the native codec to do its scaling first, then sample on top of that. Since the only codec which can do native scaling is JPEG, and we know what it can do, hard-code for JPEG. Check to see if the sampleSize is something JPEG supports, or a multiple of something it supports. If so, use JPEG directly or combine them. BUG=skia:4320 Review URL: https://codereview.chromium.org/1417583009
This commit is contained in:
Родитель
f7de08a52b
Коммит
501b7344f1
|
@ -978,7 +978,7 @@ public:
|
|||
// All use cases we are aware of only scale by powers of two.
|
||||
// PNG decodes use the indicated sampling strategy regardless of the sample size, so
|
||||
// these tests are sufficient to provide good coverage of our scaling options.
|
||||
const uint32_t sampleSizes[] = { 1, 2, 4, 8, 16 };
|
||||
const uint32_t sampleSizes[] = { 1, 2, 4, 8, 16, 32, 64 };
|
||||
const uint32_t minOutputSize = 512;
|
||||
while (fCurrentBRDImage < fImages.count()) {
|
||||
while (fCurrentBRDStrategy < (int) SK_ARRAY_COUNT(strategies)) {
|
||||
|
|
|
@ -472,7 +472,12 @@ static void push_brd_srcs(Path path) {
|
|||
SkBitmapRegionDecoderInterface::kAndroidCodec_Strategy,
|
||||
};
|
||||
|
||||
const uint32_t sampleSizes[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
|
||||
// Test on a variety of sampleSizes, making sure to include:
|
||||
// - 2, 4, and 8, which are natively supported by jpeg
|
||||
// - multiples of 2 which are not divisible by 4 (analogous for 4)
|
||||
// - larger powers of two, since BRD clients generally use powers of 2
|
||||
// We will only produce output for the larger sizes on large images.
|
||||
const uint32_t sampleSizes[] = { 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 24, 32, 64 };
|
||||
|
||||
// We will only test to one backend (8888), but we will test all of the
|
||||
// color types that we need to decode to on this backend.
|
||||
|
|
|
@ -53,6 +53,11 @@ SkISize SkAndroidCodec::getSampledDimensions(int sampleSize) const {
|
|||
return SkISize::Make(0, 0);
|
||||
}
|
||||
|
||||
// Fast path for when we are not scaling.
|
||||
if (1 == sampleSize) {
|
||||
return fInfo.dimensions();
|
||||
}
|
||||
|
||||
return this->onGetSampledDimensions(sampleSize);
|
||||
}
|
||||
|
||||
|
@ -77,9 +82,9 @@ SkISize SkAndroidCodec::getSampledSubsetDimensions(int sampleSize, const SkIRect
|
|||
return SkISize::Make(0, 0);
|
||||
}
|
||||
|
||||
// If the subset is the entire image, for consistency, use onGetSampledDimensions().
|
||||
// If the subset is the entire image, for consistency, use getSampledDimensions().
|
||||
if (fInfo.dimensions() == subset.size()) {
|
||||
return onGetSampledDimensions(sampleSize);
|
||||
return this->getSampledDimensions(sampleSize);
|
||||
}
|
||||
|
||||
// This should perhaps call a virtual function, but currently both of our subclasses
|
||||
|
@ -104,6 +109,15 @@ SkCodec::Result SkAndroidCodec::getAndroidPixels(const SkImageInfo& info, void*
|
|||
if (!is_valid_subset(*options->fSubset, fInfo.dimensions())) {
|
||||
return SkCodec::kInvalidParameters;
|
||||
}
|
||||
|
||||
if (SkIRect::MakeSize(fInfo.dimensions()) == *options->fSubset) {
|
||||
// The caller wants the whole thing, rather than a subset. Modify
|
||||
// the AndroidOptions passed to onGetAndroidPixels to not specify
|
||||
// a subset.
|
||||
defaultOptions = *options;
|
||||
defaultOptions.fSubset = nullptr;
|
||||
options = &defaultOptions;
|
||||
}
|
||||
}
|
||||
|
||||
return this->onGetAndroidPixels(info, pixels, rowBytes, *options);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "SkCodec.h"
|
||||
#include "SkCodecPriv.h"
|
||||
#include "SkMath.h"
|
||||
#include "SkSampledCodec.h"
|
||||
|
||||
SkSampledCodec::SkSampledCodec(SkCodec* codec)
|
||||
|
@ -14,30 +15,58 @@ SkSampledCodec::SkSampledCodec(SkCodec* codec)
|
|||
, fCodec(codec)
|
||||
{}
|
||||
|
||||
SkISize SkSampledCodec::accountForNativeScaling(int* sampleSizePtr, int* nativeSampleSize) const {
|
||||
SkISize preSampledSize = fCodec->getInfo().dimensions();
|
||||
int sampleSize = *sampleSizePtr;
|
||||
SkASSERT(sampleSize > 1);
|
||||
|
||||
if (nativeSampleSize) {
|
||||
*nativeSampleSize = 1;
|
||||
}
|
||||
|
||||
// Only JPEG supports native downsampling.
|
||||
if (fCodec->getEncodedFormat() == kJPEG_SkEncodedFormat) {
|
||||
// See if libjpeg supports this scale directly
|
||||
switch (sampleSize) {
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
// This class does not need to do any sampling.
|
||||
*sampleSizePtr = 1;
|
||||
return fCodec->getScaledDimensions(get_scale_from_sample_size(sampleSize));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if sampleSize is a multiple of something libjpeg can support.
|
||||
int remainder;
|
||||
const int sampleSizes[] = { 8, 4, 2 };
|
||||
for (int supportedSampleSize : sampleSizes) {
|
||||
int actualSampleSize;
|
||||
SkTDivMod(sampleSize, supportedSampleSize, &actualSampleSize, &remainder);
|
||||
if (0 == remainder) {
|
||||
float scale = get_scale_from_sample_size(supportedSampleSize);
|
||||
|
||||
// fCodec will scale to this size.
|
||||
preSampledSize = fCodec->getScaledDimensions(scale);
|
||||
|
||||
// And then this class will sample it.
|
||||
*sampleSizePtr = actualSampleSize;
|
||||
if (nativeSampleSize) {
|
||||
*nativeSampleSize = supportedSampleSize;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return preSampledSize;
|
||||
}
|
||||
|
||||
SkISize SkSampledCodec::onGetSampledDimensions(int sampleSize) const {
|
||||
// Fast path for when we are not scaling.
|
||||
if (1 == sampleSize) {
|
||||
return fCodec->getInfo().dimensions();
|
||||
}
|
||||
|
||||
const int width = fCodec->getInfo().width();
|
||||
const int height = fCodec->getInfo().height();
|
||||
|
||||
// Check if the codec can provide the scaling natively.
|
||||
float scale = get_scale_from_sample_size(sampleSize);
|
||||
SkSize idealSize = SkSize::Make(scale * (float) width, scale * (float) height);
|
||||
SkISize nativeSize = fCodec->getScaledDimensions(scale);
|
||||
float widthDiff = SkTAbs(((float) nativeSize.width()) - idealSize.width());
|
||||
float heightDiff = SkTAbs(((float) nativeSize.height()) - idealSize.height());
|
||||
// Native scaling is preferred to sampling. If we can scale natively to
|
||||
// within one of the ideal value, we should choose to scale natively.
|
||||
if (widthDiff < 1.0f && heightDiff < 1.0f) {
|
||||
return nativeSize;
|
||||
}
|
||||
|
||||
// Provide the scaling by sampling.
|
||||
return SkISize::Make(get_scaled_dimension(width, sampleSize),
|
||||
get_scaled_dimension(height, sampleSize));
|
||||
const SkISize size = this->accountForNativeScaling(&sampleSize);
|
||||
return SkISize::Make(get_scaled_dimension(size.width(), sampleSize),
|
||||
get_scaled_dimension(size.height(), sampleSize));
|
||||
}
|
||||
|
||||
SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void* pixels,
|
||||
|
@ -59,7 +88,7 @@ SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void
|
|||
|
||||
// We are performing a subset decode.
|
||||
int sampleSize = options.fSampleSize;
|
||||
SkISize scaledSize = this->onGetSampledDimensions(sampleSize);
|
||||
SkISize scaledSize = this->getSampledDimensions(sampleSize);
|
||||
if (!fCodec->dimensionsSupported(scaledSize)) {
|
||||
// If the native codec does not support the requested scale, scale by sampling.
|
||||
return this->sampledDecode(info, pixels, rowBytes, options);
|
||||
|
@ -107,31 +136,46 @@ SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void
|
|||
|
||||
SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pixels,
|
||||
size_t rowBytes, AndroidOptions& options) {
|
||||
// We should only call this function when sampling.
|
||||
SkASSERT(options.fSampleSize > 1);
|
||||
|
||||
// Create options struct for the codec.
|
||||
SkCodec::Options sampledOptions;
|
||||
sampledOptions.fZeroInitialized = options.fZeroInitialized;
|
||||
|
||||
// FIXME: This was already called by onGetAndroidPixels. Can we reduce that?
|
||||
int sampleSize = options.fSampleSize;
|
||||
int nativeSampleSize;
|
||||
SkISize nativeSize = this->accountForNativeScaling(&sampleSize, &nativeSampleSize);
|
||||
|
||||
// Check if there is a subset.
|
||||
SkIRect subset;
|
||||
int subsetY = 0;
|
||||
int subsetWidth = fCodec->getInfo().width();
|
||||
int subsetHeight = fCodec->getInfo().height();
|
||||
int subsetWidth = nativeSize.width();
|
||||
int subsetHeight = nativeSize.height();
|
||||
if (options.fSubset) {
|
||||
// We will need to know about subsetting in the y-dimension in order to use the
|
||||
// scanline decoder.
|
||||
// Update the subset to account for scaling done by fCodec.
|
||||
SkIRect* subsetPtr = options.fSubset;
|
||||
subsetY = subsetPtr->y();
|
||||
subsetWidth = subsetPtr->width();
|
||||
subsetHeight = subsetPtr->height();
|
||||
|
||||
// Do the divide ourselves, instead of calling get_scaled_dimension. If
|
||||
// X and Y are 0, they should remain 0, rather than being upgraded to 1
|
||||
// due to being smaller than the sampleSize.
|
||||
const int subsetX = subsetPtr->x() / nativeSampleSize;
|
||||
subsetY = subsetPtr->y() / nativeSampleSize;
|
||||
|
||||
subsetWidth = get_scaled_dimension(subsetPtr->width(), nativeSampleSize);
|
||||
subsetHeight = get_scaled_dimension(subsetPtr->height(), nativeSampleSize);
|
||||
|
||||
// The scanline decoder only needs to be aware of subsetting in the x-dimension.
|
||||
subset.setXYWH(subsetPtr->x(), 0, subsetWidth, fCodec->getInfo().height());
|
||||
subset.setXYWH(subsetX, 0, subsetWidth, nativeSize.height());
|
||||
sampledOptions.fSubset = ⊂
|
||||
}
|
||||
|
||||
// Start the scanline decode.
|
||||
SkCodec::Result result = fCodec->startScanlineDecode(
|
||||
info.makeWH(fCodec->getInfo().width(), fCodec->getInfo().height()), &sampledOptions,
|
||||
info.makeWH(nativeSize.width(), nativeSize.height()), &sampledOptions,
|
||||
options.fColorPtr, options.fColorCount);
|
||||
if (SkCodec::kSuccess != result) {
|
||||
return result;
|
||||
|
|
|
@ -33,6 +33,19 @@ protected:
|
|||
AndroidOptions& options) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Find the best way to account for native scaling.
|
||||
*
|
||||
* Return a size that fCodec can scale to, and adjust sampleSize to finish scaling.
|
||||
*
|
||||
* @param sampleSize As an input, the requested sample size.
|
||||
* As an output, sampling needed after letting fCodec
|
||||
* scale to the returned dimensions.
|
||||
* @param nativeSampleSize Optional output parameter. Will be set to the
|
||||
* effective sample size done by fCodec.
|
||||
* @return SkISize The size that fCodec should scale to.
|
||||
*/
|
||||
SkISize accountForNativeScaling(int* sampleSize, int* nativeSampleSize = nullptr) const;
|
||||
|
||||
/**
|
||||
* This fulfills the same contract as onGetAndroidPixels().
|
||||
|
|
|
@ -581,7 +581,7 @@ static void test_dimensions(skiatest::Reporter* r, const char path[]) {
|
|||
}
|
||||
|
||||
// Check that the decode is successful for a variety of scales
|
||||
for (int sampleSize = 1; sampleSize < 10; sampleSize++) {
|
||||
for (int sampleSize = 1; sampleSize < 32; sampleSize++) {
|
||||
// Scale the output dimensions
|
||||
SkISize scaledDims = codec->getSampledDimensions(sampleSize);
|
||||
SkImageInfo scaledInfo = codec->getInfo()
|
||||
|
|
Загрузка…
Ссылка в новой задаче