yet another approach to unpremul scale pixels

This keeps punning things to premul (in anticipation of pulling
back on any unpremul support outside read/write/scalePixels),
and yet still clamps correctly for scalePixels() + unpremul + HQ
and/or gamut transform.

Change-Id: I75977cfdb94ffbe62c538ddee39f1abd2cc01935
Reviewed-on: https://skia-review.googlesource.com/106265
Reviewed-by: Brian Osman <brianosman@google.com>
This commit is contained in:
Mike Klein 2018-01-03 10:30:21 -05:00
parent 528ceb9c0d
commit 1f31309d7a
4 changed files with 85 additions and 53 deletions

View File

@ -144,6 +144,9 @@ config("skia_public") {
if (skia_enable_atlas_text) {
defines += [ "SK_SUPPORT_ATLAS_TEXT=1" ]
}
# Temporary.
defines += [ "SK_LEGACY_HIGH_QUALITY_SCALING_CLAMP" ]
}
# Skia internal APIs, used by Skia itself and a few test tools.

View File

@ -11,6 +11,7 @@
#include "SkConvertPixels.h"
#include "SkData.h"
#include "SkImageInfoPriv.h"
#include "SkImageShader.h"
#include "SkHalf.h"
#include "SkMask.h"
#include "SkNx.h"
@ -229,54 +230,65 @@ bool SkPixmap::erase(const SkColor4f& origColor, const SkIRect* subset) const {
return true;
}
static void set_alphatype(SkPixmap* dst, const SkPixmap& src, SkAlphaType at) {
dst->reset(src.info().makeAlphaType(at), src.addr(), src.rowBytes());
}
bool SkPixmap::scalePixels(const SkPixmap& actualDst, SkFilterQuality quality) const {
// We may need to tweak how we interpret these just a little below, so we make copies.
SkPixmap src = *this,
dst = actualDst;
bool SkPixmap::scalePixels(const SkPixmap& dst, SkFilterQuality quality) const {
// Can't do anthing with empty src or dst
if (this->width() <= 0 || this->height() <= 0 || dst.width() <= 0 || dst.height() <= 0) {
if (src.width() <= 0 || src.height() <= 0 ||
dst.width() <= 0 || dst.height() <= 0) {
return false;
}
// no scaling involved?
if (dst.width() == this->width() && dst.height() == this->height()) {
return this->readPixels(dst);
if (src.width() == dst.width() && src.height() == dst.height()) {
return src.readPixels(dst);
}
// Temp storage in case we need to edit the requested alphatypes
SkPixmap storage_src, storage_dst;
const SkPixmap* srcPtr = this;
const SkPixmap* dstPtr = &dst;
// If src and dst are both unpremul, we'll fake them out to appear as if premul.
bool clampAsIfUnpremul = false;
if (src.alphaType() == kUnpremul_SkAlphaType &&
dst.alphaType() == kUnpremul_SkAlphaType) {
src.reset(src.info().makeAlphaType(kPremul_SkAlphaType), src.addr(), src.rowBytes());
dst.reset(dst.info().makeAlphaType(kPremul_SkAlphaType), dst.addr(), dst.rowBytes());
// Trick: if src and dst are both unpremul, we can give the correct result if we change both
// to premul (or opaque), since the draw will not try to blend or otherwise interpret
// the pixels' alpha.
if (srcPtr->alphaType() == kUnpremul_SkAlphaType &&
dstPtr->alphaType() == kUnpremul_SkAlphaType)
{
set_alphatype(&storage_src, *this, kPremul_SkAlphaType);
set_alphatype(&storage_dst, dst, kPremul_SkAlphaType);
srcPtr = &storage_src;
dstPtr = &storage_dst;
// In turn, we'll need to tell the image shader to clamp to [0,1] instead
// of the usual [0,a] when using a bicubic scaling (kHigh_SkFilterQuality)
// or a gamut transformation.
clampAsIfUnpremul = true;
}
SkBitmap bitmap;
if (!bitmap.installPixels(*srcPtr)) {
if (!bitmap.installPixels(src)) {
return false;
}
bitmap.setIsVolatile(true); // so we don't try to cache it
bitmap.setImmutable(); // Don't copy when we create an image.
bitmap.setIsVolatile(true); // Disable any caching.
auto surface(SkSurface::MakeRasterDirect(dstPtr->info(), dstPtr->writable_addr(), dstPtr->rowBytes()));
if (!surface) {
SkMatrix scale = SkMatrix::MakeRectToRect(SkRect::Make(src.bounds()),
SkRect::Make(dst.bounds()),
SkMatrix::kFill_ScaleToFit);
// We'll create a shader to do this draw so we have control over the bicubic clamp.
sk_sp<SkShader> shader = SkImageShader::Make(SkImage::MakeFromBitmap(bitmap),
SkShader::kClamp_TileMode,
SkShader::kClamp_TileMode,
&scale,
clampAsIfUnpremul);
sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect(dst.info(),
dst.writable_addr(),
dst.rowBytes());
if (!shader || !surface) {
return false;
}
SkPaint paint;
paint.setFilterQuality(quality);
paint.setBlendMode(SkBlendMode::kSrc);
surface->getCanvas()->drawBitmapRect(bitmap, SkRect::MakeIWH(dst.width(), dst.height()),
&paint);
paint.setFilterQuality(quality);
paint.setShader(std::move(shader));
surface->getCanvas()->drawPaint(paint);
return true;
}

View File

@ -31,23 +31,30 @@ static SkShader::TileMode optimize(SkShader::TileMode tm, int dimension) {
#endif
}
SkImageShader::SkImageShader(sk_sp<SkImage> img, TileMode tmx, TileMode tmy, const SkMatrix* matrix)
: INHERITED(matrix)
SkImageShader::SkImageShader(sk_sp<SkImage> img,
TileMode tmx, TileMode tmy,
const SkMatrix* localMatrix,
bool clampAsIfUnpremul)
: INHERITED(localMatrix)
, fImage(std::move(img))
, fTileModeX(optimize(tmx, fImage->width()))
, fTileModeY(optimize(tmy, fImage->height()))
, fClampAsIfUnpremul(clampAsIfUnpremul)
{}
// fClampAsIfUnpremul is always false when constructed through public APIs,
// so there's no need to read or write it here.
sk_sp<SkFlattenable> SkImageShader::CreateProc(SkReadBuffer& buffer) {
const TileMode tx = (TileMode)buffer.readUInt();
const TileMode ty = (TileMode)buffer.readUInt();
SkMatrix matrix;
buffer.readMatrix(&matrix);
SkMatrix localMatrix;
buffer.readMatrix(&localMatrix);
sk_sp<SkImage> img = buffer.readImage();
if (!img) {
return nullptr;
}
return SkImageShader::Make(std::move(img), tx, ty, &matrix);
return SkImageShader::Make(std::move(img), tx, ty, &localMatrix);
}
void SkImageShader::flatten(SkWriteBuffer& buffer) const {
@ -55,6 +62,7 @@ void SkImageShader::flatten(SkWriteBuffer& buffer) const {
buffer.writeUInt(fTileModeY);
buffer.writeMatrix(this->getLocalMatrix());
buffer.writeImage(fImage.get());
SkASSERT(fClampAsIfUnpremul == false);
}
bool SkImageShader::isOpaque() const {
@ -164,13 +172,14 @@ static bool bitmap_is_too_big(int w, int h) {
return w > kMaxSize || h > kMaxSize;
}
sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image, TileMode tx, TileMode ty,
const SkMatrix* localMatrix) {
sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image,
TileMode tx, TileMode ty,
const SkMatrix* localMatrix,
bool clampAsIfUnpremul) {
if (!image || bitmap_is_too_big(image->width(), image->height())) {
return sk_make_sp<SkEmptyShader>();
} else {
return sk_make_sp<SkImageShader>(image, tx, ty, localMatrix);
}
return sk_sp<SkShader>{ new SkImageShader(image, tx,ty, localMatrix, clampAsIfUnpremul) };
}
#ifndef SK_IGNORE_TO_STRING
@ -390,14 +399,16 @@ bool SkImageShader::onAppendStages(const StageRec& rec) const {
info.alphaType() == kUnpremul_SkAlphaType) {
p->append(SkRasterPipeline::premul);
}
#if defined(SK_LEGACY_HIGH_QUALITY_SCALING_CLAMP)
#if defined(SK_LEGACY_HIGH_QUALITY_SCALING_CLAMP)
if (quality > kLow_SkFilterQuality) {
// Bicubic filtering naturally produces out of range values on both sides.
p->append(SkRasterPipeline::clamp_0);
p->append(SkRasterPipeline::clamp_a);
p->append(fClampAsIfUnpremul ? SkRasterPipeline::clamp_1
: SkRasterPipeline::clamp_a);
}
#endif
append_gamut_transform(p, alloc, info.colorSpace(), rec.fDstCS, kPremul_SkAlphaType);
#endif
append_gamut_transform(p, alloc, info.colorSpace(), rec.fDstCS,
fClampAsIfUnpremul ? kUnpremul_SkAlphaType : kPremul_SkAlphaType);
return true;
};

View File

@ -15,8 +15,11 @@
class SkImageShader : public SkShaderBase {
public:
static sk_sp<SkShader> Make(sk_sp<SkImage>, TileMode tx, TileMode ty,
const SkMatrix* localMatrix);
static sk_sp<SkShader> Make(sk_sp<SkImage>,
SkShader::TileMode tx,
SkShader::TileMode ty,
const SkMatrix* localMatrix,
bool clampAsIfUnpremul = false);
bool isOpaque() const override;
@ -27,19 +30,23 @@ public:
std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&) const override;
#endif
SkImageShader(sk_sp<SkImage>, TileMode tx, TileMode ty, const SkMatrix* localMatrix);
static bool IsRasterPipelineOnly(const SkMatrix& ctm, SkColorType, SkAlphaType,
SkShader::TileMode tx, SkShader::TileMode ty,
const SkMatrix& localM);
protected:
private:
SkImageShader(sk_sp<SkImage>,
SkShader::TileMode tx,
SkShader::TileMode ty,
const SkMatrix* localMatrix,
bool clampAsIfUnpremul);
void flatten(SkWriteBuffer&) const override;
Context* onMakeContext(const ContextRec&, SkArenaAlloc* storage) const override;
#ifdef SK_SUPPORT_LEGACY_SHADER_ISABITMAP
bool onIsABitmap(SkBitmap*, SkMatrix*, TileMode*) const override;
bool onIsABitmap(SkBitmap*, SkMatrix*, SkShader::TileMode*) const override;
#endif
SkImage* onIsAImage(SkMatrix*, TileMode*) const override;
SkImage* onIsAImage(SkMatrix*, SkShader::TileMode*) const override;
bool onIsRasterPipelineOnly(const SkMatrix& ctm) const override;
@ -50,13 +57,12 @@ protected:
&this->getLocalMatrix());
}
sk_sp<SkImage> fImage;
const TileMode fTileModeX;
const TileMode fTileModeY;
sk_sp<SkImage> fImage;
const SkShader::TileMode fTileModeX;
const SkShader::TileMode fTileModeY;
const bool fClampAsIfUnpremul;
private:
friend class SkShaderBase;
typedef SkShaderBase INHERITED;
};