Make perlin noise do CTM-correct scaling.

When drawing perlin noise, generate noise at the resolution determined
by CTM, not by the resolution at construction time. This required moving
the generation of PaintingData to getContext() and asNewEffect() for the
raster and GPU paths, respectively.

It also required adjusting the matrices used during rendering
to be translate-only.

R=sugoi@chromium.org, bsalomon@google.com

Author: senorblanco@chromium.org

Review URL: https://codereview.chromium.org/358903002
This commit is contained in:
senorblanco 2014-06-27 13:35:52 -07:00 коммит произвёл Commit bot
Родитель d3e474e20c
Коммит ca6a7c2945
3 изменённых файлов: 81 добавлений и 82 удалений

Просмотреть файл

@ -45,3 +45,9 @@ bleed
blurquickreject
blurrects
bigblurs
# senorblanco:
# Needs rebaselining after improved perlin noise scaling
perlinnoise
perlinnoise_localmatrix
imagefiltersscaled

Просмотреть файл

@ -23,9 +23,9 @@
http://www.w3.org/TR/SVG/filters.html#feTurbulenceElement
*/
class SK_API SkPerlinNoiseShader : public SkShader {
struct PaintingData;
public:
struct StitchData;
struct PaintingData;
/**
* About the noise types : the difference between the 2 is just minor tweaks to the algorithm,
@ -77,7 +77,7 @@ public:
class PerlinNoiseShaderContext : public SkShader::Context {
public:
PerlinNoiseShaderContext(const SkPerlinNoiseShader& shader, const ContextRec&);
virtual ~PerlinNoiseShaderContext() {}
virtual ~PerlinNoiseShaderContext();
virtual void shadeSpan(int x, int y, SkPMColor[], int count) SK_OVERRIDE;
virtual void shadeSpan16(int x, int y, uint16_t[], int count) SK_OVERRIDE;
@ -85,12 +85,13 @@ public:
private:
SkPMColor shade(const SkPoint& point, StitchData& stitchData) const;
SkScalar calculateTurbulenceValueForPoint(
int channel, const PaintingData& paintingData,
int channel,
StitchData& stitchData, const SkPoint& point) const;
SkScalar noise2D(int channel, const PaintingData& paintingData,
SkScalar noise2D(int channel,
const StitchData& stitchData, const SkPoint& noiseVector) const;
SkMatrix fMatrix;
PaintingData* fPaintingData;
typedef SkShader::Context INHERITED;
};
@ -123,8 +124,6 @@ private:
/*const*/ SkISize fTileSize;
/*const*/ bool fStitchTiles;
PaintingData* fPaintingData;
typedef SkShader INHERITED;
};

Просмотреть файл

@ -79,10 +79,19 @@ struct SkPerlinNoiseShader::StitchData {
struct SkPerlinNoiseShader::PaintingData {
PaintingData(const SkISize& tileSize, SkScalar seed,
SkScalar baseFrequencyX, SkScalar baseFrequencyY)
: fTileSize(tileSize)
, fBaseFrequency(SkPoint::Make(baseFrequencyX, baseFrequencyY))
SkScalar baseFrequencyX, SkScalar baseFrequencyY,
const SkMatrix& matrix)
{
SkVector wavelength = SkVector::Make(SkScalarInvert(baseFrequencyX),
SkScalarInvert(baseFrequencyY));
matrix.mapVectors(&wavelength, 1);
fBaseFrequency.fX = SkScalarInvert(wavelength.fX);
fBaseFrequency.fY = SkScalarInvert(wavelength.fY);
SkVector sizeVec = SkVector::Make(SkIntToScalar(tileSize.fWidth),
SkIntToScalar(tileSize.fHeight));
matrix.mapVectors(&sizeVec, 1);
fTileSize.fWidth = SkScalarRoundToInt(sizeVec.fX);
fTileSize.fHeight = SkScalarRoundToInt(sizeVec.fY);
this->init(seed);
if (!fTileSize.isEmpty()) {
this->stitch();
@ -275,7 +284,6 @@ SkPerlinNoiseShader::SkPerlinNoiseShader(SkPerlinNoiseShader::Type type,
, fStitchTiles(!fTileSize.isEmpty())
{
SkASSERT(numOctaves >= 0 && numOctaves < 256);
fPaintingData = SkNEW_ARGS(PaintingData, (fTileSize, fSeed, fBaseFrequencyX, fBaseFrequencyY));
}
SkPerlinNoiseShader::SkPerlinNoiseShader(SkReadBuffer& buffer)
@ -289,15 +297,12 @@ SkPerlinNoiseShader::SkPerlinNoiseShader(SkReadBuffer& buffer)
fStitchTiles = buffer.readBool();
fTileSize.fWidth = buffer.readInt();
fTileSize.fHeight = buffer.readInt();
fPaintingData = SkNEW_ARGS(PaintingData, (fTileSize, fSeed, fBaseFrequencyX, fBaseFrequencyY));
buffer.validate(perlin_noise_type_is_valid(fType) &&
(fNumOctaves >= 0) && (fNumOctaves <= 255) &&
(fStitchTiles != fTileSize.isEmpty()));
}
SkPerlinNoiseShader::~SkPerlinNoiseShader() {
// Safety, should have been done in endContext()
SkDELETE(fPaintingData);
}
void SkPerlinNoiseShader::flatten(SkWriteBuffer& buffer) const {
@ -313,8 +318,7 @@ void SkPerlinNoiseShader::flatten(SkWriteBuffer& buffer) const {
}
SkScalar SkPerlinNoiseShader::PerlinNoiseShaderContext::noise2D(
int channel, const PaintingData& paintingData,
const StitchData& stitchData, const SkPoint& noiseVector) const {
int channel, const StitchData& stitchData, const SkPoint& noiseVector) const {
struct Noise {
int noisePositionIntegerValue;
int nextNoisePositionIntegerValue;
@ -347,9 +351,9 @@ SkScalar SkPerlinNoiseShader::PerlinNoiseShaderContext::noise2D(
noiseX.nextNoisePositionIntegerValue &= kBlockMask;
noiseY.nextNoisePositionIntegerValue &= kBlockMask;
int i =
paintingData.fLatticeSelector[noiseX.noisePositionIntegerValue];
fPaintingData->fLatticeSelector[noiseX.noisePositionIntegerValue];
int j =
paintingData.fLatticeSelector[noiseX.nextNoisePositionIntegerValue];
fPaintingData->fLatticeSelector[noiseX.nextNoisePositionIntegerValue];
int b00 = (i + noiseY.noisePositionIntegerValue) & kBlockMask;
int b10 = (j + noiseY.noisePositionIntegerValue) & kBlockMask;
int b01 = (i + noiseY.nextNoisePositionIntegerValue) & kBlockMask;
@ -359,32 +363,31 @@ SkScalar SkPerlinNoiseShader::PerlinNoiseShaderContext::noise2D(
// This is taken 1:1 from SVG spec: http://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement
SkPoint fractionValue = SkPoint::Make(noiseX.noisePositionFractionValue,
noiseY.noisePositionFractionValue); // Offset (0,0)
u = paintingData.fGradient[channel][b00].dot(fractionValue);
u = fPaintingData->fGradient[channel][b00].dot(fractionValue);
fractionValue.fX -= SK_Scalar1; // Offset (-1,0)
v = paintingData.fGradient[channel][b10].dot(fractionValue);
v = fPaintingData->fGradient[channel][b10].dot(fractionValue);
SkScalar a = SkScalarInterp(u, v, sx);
fractionValue.fY -= SK_Scalar1; // Offset (-1,-1)
v = paintingData.fGradient[channel][b11].dot(fractionValue);
v = fPaintingData->fGradient[channel][b11].dot(fractionValue);
fractionValue.fX = noiseX.noisePositionFractionValue; // Offset (0,-1)
u = paintingData.fGradient[channel][b01].dot(fractionValue);
u = fPaintingData->fGradient[channel][b01].dot(fractionValue);
SkScalar b = SkScalarInterp(u, v, sx);
return SkScalarInterp(a, b, sy);
}
SkScalar SkPerlinNoiseShader::PerlinNoiseShaderContext::calculateTurbulenceValueForPoint(
int channel, const PaintingData& paintingData,
StitchData& stitchData, const SkPoint& point) const {
int channel, StitchData& stitchData, const SkPoint& point) const {
const SkPerlinNoiseShader& perlinNoiseShader = static_cast<const SkPerlinNoiseShader&>(fShader);
if (perlinNoiseShader.fStitchTiles) {
// Set up TurbulenceInitial stitch values.
stitchData = paintingData.fStitchDataInit;
stitchData = fPaintingData->fStitchDataInit;
}
SkScalar turbulenceFunctionResult = 0;
SkPoint noiseVector(SkPoint::Make(SkScalarMul(point.x(), paintingData.fBaseFrequency.fX),
SkScalarMul(point.y(), paintingData.fBaseFrequency.fY)));
SkPoint noiseVector(SkPoint::Make(SkScalarMul(point.x(), fPaintingData->fBaseFrequency.fX),
SkScalarMul(point.y(), fPaintingData->fBaseFrequency.fY)));
SkScalar ratio = SK_Scalar1;
for (int octave = 0; octave < perlinNoiseShader.fNumOctaves; ++octave) {
SkScalar noise = noise2D(channel, paintingData, stitchData, noiseVector);
SkScalar noise = noise2D(channel, stitchData, noiseVector);
turbulenceFunctionResult += SkScalarDiv(
(perlinNoiseShader.fType == kFractalNoise_Type) ? noise : SkScalarAbs(noise), ratio);
noiseVector.fX *= 2;
@ -417,7 +420,6 @@ SkScalar SkPerlinNoiseShader::PerlinNoiseShaderContext::calculateTurbulenceValue
SkPMColor SkPerlinNoiseShader::PerlinNoiseShaderContext::shade(
const SkPoint& point, StitchData& stitchData) const {
const SkPerlinNoiseShader& perlinNoiseShader = static_cast<const SkPerlinNoiseShader&>(fShader);
SkPoint newPoint;
fMatrix.mapPoints(&newPoint, &point, 1);
newPoint.fX = SkScalarRoundToScalar(newPoint.fX);
@ -426,8 +428,7 @@ SkPMColor SkPerlinNoiseShader::PerlinNoiseShaderContext::shade(
U8CPU rgba[4];
for (int channel = 3; channel >= 0; --channel) {
rgba[channel] = SkScalarFloorToInt(255 *
calculateTurbulenceValueForPoint(channel, *perlinNoiseShader.fPaintingData,
stitchData, newPoint));
calculateTurbulenceValueForPoint(channel, stitchData, newPoint));
}
return SkPreMultiplyARGB(rgba[3], rgba[0], rgba[1], rgba[2]);
}
@ -450,16 +451,14 @@ SkPerlinNoiseShader::PerlinNoiseShaderContext::PerlinNoiseShaderContext(
if (rec.fLocalMatrix) {
newMatrix.preConcat(*rec.fLocalMatrix);
}
SkMatrix invMatrix;
if (!newMatrix.invert(&invMatrix)) {
invMatrix.reset();
}
// This (1,1) translation is due to WebKit's 1 based coordinates for the noise
// (as opposed to 0 based, usually). The same adjustment is in the setData() function.
newMatrix.postTranslate(SK_Scalar1, SK_Scalar1);
newMatrix.postConcat(invMatrix);
newMatrix.postConcat(invMatrix);
fMatrix = newMatrix;
fMatrix.setTranslate(-newMatrix.getTranslateX() + SK_Scalar1, -newMatrix.getTranslateY() + SK_Scalar1);
fPaintingData = SkNEW_ARGS(PaintingData, (shader.fTileSize, shader.fSeed, shader.fBaseFrequencyX, shader.fBaseFrequencyY, newMatrix));
}
SkPerlinNoiseShader::PerlinNoiseShaderContext::~PerlinNoiseShaderContext() {
SkDELETE(fPaintingData);
}
void SkPerlinNoiseShader::PerlinNoiseShaderContext::shadeSpan(
@ -517,7 +516,6 @@ private:
int fNumOctaves;
GrGLUniformManager::UniformHandle fBaseFrequencyUni;
GrGLUniformManager::UniformHandle fAlphaUni;
GrGLUniformManager::UniformHandle fInvMatrixUni;
private:
typedef GrGLEffect INHERITED;
@ -527,27 +525,29 @@ private:
class GrPerlinNoiseEffect : public GrEffect {
public:
static GrEffectRef* Create(SkPerlinNoiseShader::Type type, const SkVector& baseFrequency,
static GrEffectRef* Create(SkPerlinNoiseShader::Type type,
int numOctaves, bool stitchTiles,
const SkPerlinNoiseShader::StitchData& stitchData,
SkPerlinNoiseShader::PaintingData* paintingData,
GrTexture* permutationsTexture, GrTexture* noiseTexture,
const SkMatrix& matrix, uint8_t alpha) {
AutoEffectUnref effect(SkNEW_ARGS(GrPerlinNoiseEffect, (type, baseFrequency, numOctaves,
stitchTiles, stitchData, permutationsTexture, noiseTexture, matrix, alpha)));
AutoEffectUnref effect(SkNEW_ARGS(GrPerlinNoiseEffect, (type, numOctaves,
stitchTiles, paintingData, permutationsTexture, noiseTexture, matrix, alpha)));
return CreateEffectRef(effect);
}
virtual ~GrPerlinNoiseEffect() { }
virtual ~GrPerlinNoiseEffect() {
SkDELETE(fPaintingData);
}
static const char* Name() { return "PerlinNoise"; }
virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
return GrTBackendEffectFactory<GrPerlinNoiseEffect>::getInstance();
}
const SkPerlinNoiseShader::StitchData& stitchData() const { return fStitchData; }
const SkPerlinNoiseShader::StitchData& stitchData() const { return fPaintingData->fStitchDataInit; }
SkPerlinNoiseShader::Type type() const { return fType; }
bool stitchTiles() const { return fStitchTiles; }
const SkVector& baseFrequency() const { return fBaseFrequency; }
const SkVector& baseFrequency() const { return fPaintingData->fBaseFrequency; }
int numOctaves() const { return fNumOctaves; }
const SkMatrix& matrix() const { return fCoordTransform.getMatrix(); }
uint8_t alpha() const { return fAlpha; }
@ -558,34 +558,31 @@ private:
virtual bool onIsEqual(const GrEffect& sBase) const SK_OVERRIDE {
const GrPerlinNoiseEffect& s = CastEffect<GrPerlinNoiseEffect>(sBase);
return fType == s.fType &&
fBaseFrequency == s.fBaseFrequency &&
fPaintingData->fBaseFrequency == s.fPaintingData->fBaseFrequency &&
fNumOctaves == s.fNumOctaves &&
fStitchTiles == s.fStitchTiles &&
fCoordTransform.getMatrix() == s.fCoordTransform.getMatrix() &&
fAlpha == s.fAlpha &&
fPermutationsAccess.getTexture() == s.fPermutationsAccess.getTexture() &&
fNoiseAccess.getTexture() == s.fNoiseAccess.getTexture() &&
fStitchData == s.fStitchData;
fPaintingData->fStitchDataInit == s.fPaintingData->fStitchDataInit;
}
GrPerlinNoiseEffect(SkPerlinNoiseShader::Type type, const SkVector& baseFrequency,
GrPerlinNoiseEffect(SkPerlinNoiseShader::Type type,
int numOctaves, bool stitchTiles,
const SkPerlinNoiseShader::StitchData& stitchData,
SkPerlinNoiseShader::PaintingData* paintingData,
GrTexture* permutationsTexture, GrTexture* noiseTexture,
const SkMatrix& matrix, uint8_t alpha)
: fType(type)
, fBaseFrequency(baseFrequency)
, fNumOctaves(numOctaves)
, fStitchTiles(stitchTiles)
, fAlpha(alpha)
, fPermutationsAccess(permutationsTexture)
, fNoiseAccess(noiseTexture)
, fStitchData(stitchData) {
, fPaintingData(paintingData) {
this->addTextureAccess(&fPermutationsAccess);
this->addTextureAccess(&fNoiseAccess);
SkMatrix m = matrix;
m.postTranslate(SK_Scalar1, SK_Scalar1);
fCoordTransform.reset(kLocal_GrCoordSet, m);
fCoordTransform.reset(kLocal_GrCoordSet, matrix);
this->addCoordTransform(&fCoordTransform);
this->setWillNotUseInputColor();
}
@ -594,13 +591,12 @@ private:
SkPerlinNoiseShader::Type fType;
GrCoordTransform fCoordTransform;
SkVector fBaseFrequency;
int fNumOctaves;
bool fStitchTiles;
uint8_t fAlpha;
GrTextureAccess fPermutationsAccess;
GrTextureAccess fNoiseAccess;
SkPerlinNoiseShader::StitchData fStitchData;
SkPerlinNoiseShader::PaintingData *fPaintingData;
void getConstantColorComponents(GrColor*, uint32_t* validFlags) const SK_OVERRIDE {
*validFlags = 0; // This is noise. Nothing is constant.
@ -660,9 +656,6 @@ void GrGLPerlinNoise::emitCode(GrGLShaderBuilder* builder,
SkString vCoords = builder->ensureFSCoords2D(coords, 0);
fInvMatrixUni = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
kMat33f_GrSLType, "invMatrix");
const char* invMatrixUni = builder->getUniformCStr(fInvMatrixUni);
fBaseFrequencyUni = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
kVec2f_GrSLType, "baseFrequency");
const char* baseFrequencyUni = builder->getUniformCStr(fBaseFrequencyUni);
@ -841,8 +834,8 @@ void GrGLPerlinNoise::emitCode(GrGLShaderBuilder* builder,
}
// There are rounding errors if the floor operation is not performed here
builder->fsCodeAppendf("\n\t\tvec2 %s = floor((%s * vec3(%s, 1.0)).xy) * %s;",
noiseVec, invMatrixUni, vCoords.c_str(), baseFrequencyUni);
builder->fsCodeAppendf("\n\t\tvec2 %s = floor(%s.xy) * %s;",
noiseVec, vCoords.c_str(), baseFrequencyUni);
// Clear the color accumulator
builder->fsCodeAppendf("\n\t\t%s = vec4(0.0);", outputColor);
@ -942,16 +935,6 @@ void GrGLPerlinNoise::setData(const GrGLUniformManager& uman, const GrDrawEffect
uman.set2f(fBaseFrequencyUni, baseFrequency.fX, baseFrequency.fY);
uman.set1f(fAlphaUni, SkScalarDiv(SkIntToScalar(turbulence.alpha()), SkIntToScalar(255)));
SkMatrix m = turbulence.matrix();
m.postTranslate(-SK_Scalar1, -SK_Scalar1);
SkMatrix invM;
if (!m.invert(&invM)) {
invM.reset();
} else {
invM.postConcat(invM); // Square the matrix
}
uman.setSkMatrix(fInvMatrixUni, invM);
if (turbulence.stitchTiles()) {
const SkPerlinNoiseShader::StitchData& stitchData = turbulence.stitchData();
uman.set2f(fStitchDataUni, SkIntToScalar(stitchData.fWidth),
@ -973,6 +956,9 @@ bool SkPerlinNoiseShader::asNewEffect(GrContext* context, const SkPaint& paint,
localMatrix.preConcat(*externalLocalMatrix);
}
SkMatrix matrix = context->getMatrix();
matrix.preConcat(localMatrix);
if (0 == fNumOctaves) {
SkColor clearColor = 0;
if (kFractalNoise_Type == fType) {
@ -987,18 +973,26 @@ bool SkPerlinNoiseShader::asNewEffect(GrContext* context, const SkPaint& paint,
// Either we don't stitch tiles, either we have a valid tile size
SkASSERT(!fStitchTiles || !fTileSize.isEmpty());
SkPerlinNoiseShader::PaintingData* paintingData = SkNEW_ARGS(PaintingData, (fTileSize, fSeed, fBaseFrequencyX, fBaseFrequencyY, matrix));
GrTexture* permutationsTexture = GrLockAndRefCachedBitmapTexture(
context, fPaintingData->getPermutationsBitmap(), NULL);
context, paintingData->getPermutationsBitmap(), NULL);
GrTexture* noiseTexture = GrLockAndRefCachedBitmapTexture(
context, fPaintingData->getNoiseBitmap(), NULL);
context, paintingData->getNoiseBitmap(), NULL);
*grEffect = (NULL != permutationsTexture) && (NULL != noiseTexture) ?
GrPerlinNoiseEffect::Create(fType, fPaintingData->fBaseFrequency,
fNumOctaves, fStitchTiles,
fPaintingData->fStitchDataInit,
permutationsTexture, noiseTexture,
localMatrix, paint.getAlpha()) :
NULL;
SkMatrix m = context->getMatrix();
m.setTranslateX(-localMatrix.getTranslateX() + SK_Scalar1);
m.setTranslateY(-localMatrix.getTranslateY() + SK_Scalar1);
if ((NULL != permutationsTexture) && (NULL != noiseTexture)) {
*grEffect = GrPerlinNoiseEffect::Create(fType,
fNumOctaves,
fStitchTiles,
paintingData,
permutationsTexture, noiseTexture,
m, paint.getAlpha());
} else {
SkDELETE(paintingData);
*grEffect = NULL;
}
// Unlock immediately, this is not great, but we don't have a way of
// knowing when else to unlock it currently. TODO: Remove this when