1 /* 2 * Copyright 2013 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "SkDither.h" 9 #include "SkPerlinNoiseShader.h" 10 #include "SkColorFilter.h" 11 #include "SkReadBuffer.h" 12 #include "SkWriteBuffer.h" 13 #include "SkShader.h" 14 #include "SkUnPreMultiply.h" 15 #include "SkString.h" 16 17 #if SK_SUPPORT_GPU 18 #include "GrContext.h" 19 #include "GrCoordTransform.h" 20 #include "GrInvariantOutput.h" 21 #include "SkGr.h" 22 #include "gl/GrGLProcessor.h" 23 #include "gl/builders/GrGLProgramBuilder.h" 24 #endif 25 26 static const int kBlockSize = 256; 27 static const int kBlockMask = kBlockSize - 1; 28 static const int kPerlinNoise = 4096; 29 static const int kRandMaximum = SK_MaxS32; // 2**31 - 1 30 31 namespace { 32 33 // noiseValue is the color component's value (or color) 34 // limitValue is the maximum perlin noise array index value allowed 35 // newValue is the current noise dimension (either width or height) 36 inline int checkNoise(int noiseValue, int limitValue, int newValue) { 37 // If the noise value would bring us out of bounds of the current noise array while we are 38 // stiching noise tiles together, wrap the noise around the current dimension of the noise to 39 // stay within the array bounds in a continuous fashion (so that tiling lines are not visible) 40 if (noiseValue >= limitValue) { 41 noiseValue -= newValue; 42 } 43 return noiseValue; 44 } 45 46 inline SkScalar smoothCurve(SkScalar t) { 47 static const SkScalar SK_Scalar3 = 3.0f; 48 49 // returns t * t * (3 - 2 * t) 50 return SkScalarMul(SkScalarSquare(t), SK_Scalar3 - 2 * t); 51 } 52 53 } // end namespace 54 55 struct SkPerlinNoiseShader::StitchData { 56 StitchData() 57 : fWidth(0) 58 , fWrapX(0) 59 , fHeight(0) 60 , fWrapY(0) 61 {} 62 63 bool operator==(const StitchData& other) const { 64 return fWidth == other.fWidth && 65 fWrapX == other.fWrapX && 66 fHeight == other.fHeight && 67 fWrapY == other.fWrapY; 68 } 69 70 int fWidth; // How much to subtract to wrap for stitching. 71 int fWrapX; // Minimum value to wrap. 72 int fHeight; 73 int fWrapY; 74 }; 75 76 struct SkPerlinNoiseShader::PaintingData { 77 PaintingData(const SkISize& tileSize, SkScalar seed, 78 SkScalar baseFrequencyX, SkScalar baseFrequencyY, 79 const SkMatrix& matrix) 80 { 81 SkVector vec[2] = { 82 { SkScalarInvert(baseFrequencyX), SkScalarInvert(baseFrequencyY) }, 83 { SkIntToScalar(tileSize.fWidth), SkIntToScalar(tileSize.fHeight) }, 84 }; 85 matrix.mapVectors(vec, 2); 86 87 fBaseFrequency.set(SkScalarInvert(vec[0].fX), SkScalarInvert(vec[0].fY)); 88 fTileSize.set(SkScalarRoundToInt(vec[1].fX), SkScalarRoundToInt(vec[1].fY)); 89 this->init(seed); 90 if (!fTileSize.isEmpty()) { 91 this->stitch(); 92 } 93 94 #if SK_SUPPORT_GPU 95 fPermutationsBitmap.setInfo(SkImageInfo::MakeA8(kBlockSize, 1)); 96 fPermutationsBitmap.setPixels(fLatticeSelector); 97 98 fNoiseBitmap.setInfo(SkImageInfo::MakeN32Premul(kBlockSize, 4)); 99 fNoiseBitmap.setPixels(fNoise[0][0]); 100 #endif 101 } 102 103 int fSeed; 104 uint8_t fLatticeSelector[kBlockSize]; 105 uint16_t fNoise[4][kBlockSize][2]; 106 SkPoint fGradient[4][kBlockSize]; 107 SkISize fTileSize; 108 SkVector fBaseFrequency; 109 StitchData fStitchDataInit; 110 111 private: 112 113 #if SK_SUPPORT_GPU 114 SkBitmap fPermutationsBitmap; 115 SkBitmap fNoiseBitmap; 116 #endif 117 118 inline int random() { 119 static const int gRandAmplitude = 16807; // 7**5; primitive root of m 120 static const int gRandQ = 127773; // m / a 121 static const int gRandR = 2836; // m % a 122 123 int result = gRandAmplitude * (fSeed % gRandQ) - gRandR * (fSeed / gRandQ); 124 if (result <= 0) 125 result += kRandMaximum; 126 fSeed = result; 127 return result; 128 } 129 130 // Only called once. Could be part of the constructor. 131 void init(SkScalar seed) 132 { 133 static const SkScalar gInvBlockSizef = SkScalarInvert(SkIntToScalar(kBlockSize)); 134 135 // According to the SVG spec, we must truncate (not round) the seed value. 136 fSeed = SkScalarTruncToInt(seed); 137 // The seed value clamp to the range [1, kRandMaximum - 1]. 138 if (fSeed <= 0) { 139 fSeed = -(fSeed % (kRandMaximum - 1)) + 1; 140 } 141 if (fSeed > kRandMaximum - 1) { 142 fSeed = kRandMaximum - 1; 143 } 144 for (int channel = 0; channel < 4; ++channel) { 145 for (int i = 0; i < kBlockSize; ++i) { 146 fLatticeSelector[i] = i; 147 fNoise[channel][i][0] = (random() % (2 * kBlockSize)); 148 fNoise[channel][i][1] = (random() % (2 * kBlockSize)); 149 } 150 } 151 for (int i = kBlockSize - 1; i > 0; --i) { 152 int k = fLatticeSelector[i]; 153 int j = random() % kBlockSize; 154 SkASSERT(j >= 0); 155 SkASSERT(j < kBlockSize); 156 fLatticeSelector[i] = fLatticeSelector[j]; 157 fLatticeSelector[j] = k; 158 } 159 160 // Perform the permutations now 161 { 162 // Copy noise data 163 uint16_t noise[4][kBlockSize][2]; 164 for (int i = 0; i < kBlockSize; ++i) { 165 for (int channel = 0; channel < 4; ++channel) { 166 for (int j = 0; j < 2; ++j) { 167 noise[channel][i][j] = fNoise[channel][i][j]; 168 } 169 } 170 } 171 // Do permutations on noise data 172 for (int i = 0; i < kBlockSize; ++i) { 173 for (int channel = 0; channel < 4; ++channel) { 174 for (int j = 0; j < 2; ++j) { 175 fNoise[channel][i][j] = noise[channel][fLatticeSelector[i]][j]; 176 } 177 } 178 } 179 } 180 181 // Half of the largest possible value for 16 bit unsigned int 182 static const SkScalar gHalfMax16bits = 32767.5f; 183 184 // Compute gradients from permutated noise data 185 for (int channel = 0; channel < 4; ++channel) { 186 for (int i = 0; i < kBlockSize; ++i) { 187 fGradient[channel][i] = SkPoint::Make( 188 SkScalarMul(SkIntToScalar(fNoise[channel][i][0] - kBlockSize), 189 gInvBlockSizef), 190 SkScalarMul(SkIntToScalar(fNoise[channel][i][1] - kBlockSize), 191 gInvBlockSizef)); 192 fGradient[channel][i].normalize(); 193 // Put the normalized gradient back into the noise data 194 fNoise[channel][i][0] = SkScalarRoundToInt(SkScalarMul( 195 fGradient[channel][i].fX + SK_Scalar1, gHalfMax16bits)); 196 fNoise[channel][i][1] = SkScalarRoundToInt(SkScalarMul( 197 fGradient[channel][i].fY + SK_Scalar1, gHalfMax16bits)); 198 } 199 } 200 } 201 202 // Only called once. Could be part of the constructor. 203 void stitch() { 204 SkScalar tileWidth = SkIntToScalar(fTileSize.width()); 205 SkScalar tileHeight = SkIntToScalar(fTileSize.height()); 206 SkASSERT(tileWidth > 0 && tileHeight > 0); 207 // When stitching tiled turbulence, the frequencies must be adjusted 208 // so that the tile borders will be continuous. 209 if (fBaseFrequency.fX) { 210 SkScalar lowFrequencx = 211 SkScalarFloorToScalar(tileWidth * fBaseFrequency.fX) / tileWidth; 212 SkScalar highFrequencx = 213 SkScalarCeilToScalar(tileWidth * fBaseFrequency.fX) / tileWidth; 214 // BaseFrequency should be non-negative according to the standard. 215 if (fBaseFrequency.fX / lowFrequencx < highFrequencx / fBaseFrequency.fX) { 216 fBaseFrequency.fX = lowFrequencx; 217 } else { 218 fBaseFrequency.fX = highFrequencx; 219 } 220 } 221 if (fBaseFrequency.fY) { 222 SkScalar lowFrequency = 223 SkScalarFloorToScalar(tileHeight * fBaseFrequency.fY) / tileHeight; 224 SkScalar highFrequency = 225 SkScalarCeilToScalar(tileHeight * fBaseFrequency.fY) / tileHeight; 226 if (fBaseFrequency.fY / lowFrequency < highFrequency / fBaseFrequency.fY) { 227 fBaseFrequency.fY = lowFrequency; 228 } else { 229 fBaseFrequency.fY = highFrequency; 230 } 231 } 232 // Set up TurbulenceInitial stitch values. 233 fStitchDataInit.fWidth = 234 SkScalarRoundToInt(tileWidth * fBaseFrequency.fX); 235 fStitchDataInit.fWrapX = kPerlinNoise + fStitchDataInit.fWidth; 236 fStitchDataInit.fHeight = 237 SkScalarRoundToInt(tileHeight * fBaseFrequency.fY); 238 fStitchDataInit.fWrapY = kPerlinNoise + fStitchDataInit.fHeight; 239 } 240 241 public: 242 243 #if SK_SUPPORT_GPU 244 const SkBitmap& getPermutationsBitmap() const { return fPermutationsBitmap; } 245 246 const SkBitmap& getNoiseBitmap() const { return fNoiseBitmap; } 247 #endif 248 }; 249 250 SkShader* SkPerlinNoiseShader::CreateFractalNoise(SkScalar baseFrequencyX, SkScalar baseFrequencyY, 251 int numOctaves, SkScalar seed, 252 const SkISize* tileSize) { 253 return SkNEW_ARGS(SkPerlinNoiseShader, (kFractalNoise_Type, baseFrequencyX, baseFrequencyY, 254 numOctaves, seed, tileSize)); 255 } 256 257 SkShader* SkPerlinNoiseShader::CreateTurbulence(SkScalar baseFrequencyX, SkScalar baseFrequencyY, 258 int numOctaves, SkScalar seed, 259 const SkISize* tileSize) { 260 return SkNEW_ARGS(SkPerlinNoiseShader, (kTurbulence_Type, baseFrequencyX, baseFrequencyY, 261 numOctaves, seed, tileSize)); 262 } 263 264 SkPerlinNoiseShader::SkPerlinNoiseShader(SkPerlinNoiseShader::Type type, 265 SkScalar baseFrequencyX, 266 SkScalar baseFrequencyY, 267 int numOctaves, 268 SkScalar seed, 269 const SkISize* tileSize) 270 : fType(type) 271 , fBaseFrequencyX(baseFrequencyX) 272 , fBaseFrequencyY(baseFrequencyY) 273 , fNumOctaves(numOctaves > 255 ? 255 : numOctaves/*[0,255] octaves allowed*/) 274 , fSeed(seed) 275 , fTileSize(NULL == tileSize ? SkISize::Make(0, 0) : *tileSize) 276 , fStitchTiles(!fTileSize.isEmpty()) 277 { 278 SkASSERT(numOctaves >= 0 && numOctaves < 256); 279 } 280 281 SkPerlinNoiseShader::~SkPerlinNoiseShader() { 282 } 283 284 SkFlattenable* SkPerlinNoiseShader::CreateProc(SkReadBuffer& buffer) { 285 Type type = (Type)buffer.readInt(); 286 SkScalar freqX = buffer.readScalar(); 287 SkScalar freqY = buffer.readScalar(); 288 int octaves = buffer.readInt(); 289 SkScalar seed = buffer.readScalar(); 290 SkISize tileSize; 291 tileSize.fWidth = buffer.readInt(); 292 tileSize.fHeight = buffer.readInt(); 293 294 switch (type) { 295 case kFractalNoise_Type: 296 return SkPerlinNoiseShader::CreateFractalNoise(freqX, freqY, octaves, seed, &tileSize); 297 case kTurbulence_Type: 298 return SkPerlinNoiseShader::CreateTubulence(freqX, freqY, octaves, seed, &tileSize); 299 default: 300 return NULL; 301 } 302 } 303 304 void SkPerlinNoiseShader::flatten(SkWriteBuffer& buffer) const { 305 buffer.writeInt((int) fType); 306 buffer.writeScalar(fBaseFrequencyX); 307 buffer.writeScalar(fBaseFrequencyY); 308 buffer.writeInt(fNumOctaves); 309 buffer.writeScalar(fSeed); 310 buffer.writeInt(fTileSize.fWidth); 311 buffer.writeInt(fTileSize.fHeight); 312 } 313 314 SkScalar SkPerlinNoiseShader::PerlinNoiseShaderContext::noise2D( 315 int channel, const StitchData& stitchData, const SkPoint& noiseVector) const { 316 struct Noise { 317 int noisePositionIntegerValue; 318 int nextNoisePositionIntegerValue; 319 SkScalar noisePositionFractionValue; 320 Noise(SkScalar component) 321 { 322 SkScalar position = component + kPerlinNoise; 323 noisePositionIntegerValue = SkScalarFloorToInt(position); 324 noisePositionFractionValue = position - SkIntToScalar(noisePositionIntegerValue); 325 nextNoisePositionIntegerValue = noisePositionIntegerValue + 1; 326 } 327 }; 328 Noise noiseX(noiseVector.x()); 329 Noise noiseY(noiseVector.y()); 330 SkScalar u, v; 331 const SkPerlinNoiseShader& perlinNoiseShader = static_cast<const SkPerlinNoiseShader&>(fShader); 332 // If stitching, adjust lattice points accordingly. 333 if (perlinNoiseShader.fStitchTiles) { 334 noiseX.noisePositionIntegerValue = 335 checkNoise(noiseX.noisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth); 336 noiseY.noisePositionIntegerValue = 337 checkNoise(noiseY.noisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight); 338 noiseX.nextNoisePositionIntegerValue = 339 checkNoise(noiseX.nextNoisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth); 340 noiseY.nextNoisePositionIntegerValue = 341 checkNoise(noiseY.nextNoisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight); 342 } 343 noiseX.noisePositionIntegerValue &= kBlockMask; 344 noiseY.noisePositionIntegerValue &= kBlockMask; 345 noiseX.nextNoisePositionIntegerValue &= kBlockMask; 346 noiseY.nextNoisePositionIntegerValue &= kBlockMask; 347 int i = 348 fPaintingData->fLatticeSelector[noiseX.noisePositionIntegerValue]; 349 int j = 350 fPaintingData->fLatticeSelector[noiseX.nextNoisePositionIntegerValue]; 351 int b00 = (i + noiseY.noisePositionIntegerValue) & kBlockMask; 352 int b10 = (j + noiseY.noisePositionIntegerValue) & kBlockMask; 353 int b01 = (i + noiseY.nextNoisePositionIntegerValue) & kBlockMask; 354 int b11 = (j + noiseY.nextNoisePositionIntegerValue) & kBlockMask; 355 SkScalar sx = smoothCurve(noiseX.noisePositionFractionValue); 356 SkScalar sy = smoothCurve(noiseY.noisePositionFractionValue); 357 // This is taken 1:1 from SVG spec: http://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement 358 SkPoint fractionValue = SkPoint::Make(noiseX.noisePositionFractionValue, 359 noiseY.noisePositionFractionValue); // Offset (0,0) 360 u = fPaintingData->fGradient[channel][b00].dot(fractionValue); 361 fractionValue.fX -= SK_Scalar1; // Offset (-1,0) 362 v = fPaintingData->fGradient[channel][b10].dot(fractionValue); 363 SkScalar a = SkScalarInterp(u, v, sx); 364 fractionValue.fY -= SK_Scalar1; // Offset (-1,-1) 365 v = fPaintingData->fGradient[channel][b11].dot(fractionValue); 366 fractionValue.fX = noiseX.noisePositionFractionValue; // Offset (0,-1) 367 u = fPaintingData->fGradient[channel][b01].dot(fractionValue); 368 SkScalar b = SkScalarInterp(u, v, sx); 369 return SkScalarInterp(a, b, sy); 370 } 371 372 SkScalar SkPerlinNoiseShader::PerlinNoiseShaderContext::calculateTurbulenceValueForPoint( 373 int channel, StitchData& stitchData, const SkPoint& point) const { 374 const SkPerlinNoiseShader& perlinNoiseShader = static_cast<const SkPerlinNoiseShader&>(fShader); 375 if (perlinNoiseShader.fStitchTiles) { 376 // Set up TurbulenceInitial stitch values. 377 stitchData = fPaintingData->fStitchDataInit; 378 } 379 SkScalar turbulenceFunctionResult = 0; 380 SkPoint noiseVector(SkPoint::Make(SkScalarMul(point.x(), fPaintingData->fBaseFrequency.fX), 381 SkScalarMul(point.y(), fPaintingData->fBaseFrequency.fY))); 382 SkScalar ratio = SK_Scalar1; 383 for (int octave = 0; octave < perlinNoiseShader.fNumOctaves; ++octave) { 384 SkScalar noise = noise2D(channel, stitchData, noiseVector); 385 SkScalar numer = (perlinNoiseShader.fType == kFractalNoise_Type) ? 386 noise : SkScalarAbs(noise); 387 turbulenceFunctionResult += numer / ratio; 388 noiseVector.fX *= 2; 389 noiseVector.fY *= 2; 390 ratio *= 2; 391 if (perlinNoiseShader.fStitchTiles) { 392 // Update stitch values 393 stitchData.fWidth *= 2; 394 stitchData.fWrapX = stitchData.fWidth + kPerlinNoise; 395 stitchData.fHeight *= 2; 396 stitchData.fWrapY = stitchData.fHeight + kPerlinNoise; 397 } 398 } 399 400 // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2 401 // by fractalNoise and (turbulenceFunctionResult) by turbulence. 402 if (perlinNoiseShader.fType == kFractalNoise_Type) { 403 turbulenceFunctionResult = 404 SkScalarMul(turbulenceFunctionResult, SK_ScalarHalf) + SK_ScalarHalf; 405 } 406 407 if (channel == 3) { // Scale alpha by paint value 408 turbulenceFunctionResult *= SkIntToScalar(getPaintAlpha()) / 255; 409 } 410 411 // Clamp result 412 return SkScalarPin(turbulenceFunctionResult, 0, SK_Scalar1); 413 } 414 415 SkPMColor SkPerlinNoiseShader::PerlinNoiseShaderContext::shade( 416 const SkPoint& point, StitchData& stitchData) const { 417 SkPoint newPoint; 418 fMatrix.mapPoints(&newPoint, &point, 1); 419 newPoint.fX = SkScalarRoundToScalar(newPoint.fX); 420 newPoint.fY = SkScalarRoundToScalar(newPoint.fY); 421 422 U8CPU rgba[4]; 423 for (int channel = 3; channel >= 0; --channel) { 424 rgba[channel] = SkScalarFloorToInt(255 * 425 calculateTurbulenceValueForPoint(channel, stitchData, newPoint)); 426 } 427 return SkPreMultiplyARGB(rgba[3], rgba[0], rgba[1], rgba[2]); 428 } 429 430 SkShader::Context* SkPerlinNoiseShader::onCreateContext(const ContextRec& rec, 431 void* storage) const { 432 return SkNEW_PLACEMENT_ARGS(storage, PerlinNoiseShaderContext, (*this, rec)); 433 } 434 435 size_t SkPerlinNoiseShader::contextSize() const { 436 return sizeof(PerlinNoiseShaderContext); 437 } 438 439 SkPerlinNoiseShader::PerlinNoiseShaderContext::PerlinNoiseShaderContext( 440 const SkPerlinNoiseShader& shader, const ContextRec& rec) 441 : INHERITED(shader, rec) 442 { 443 SkMatrix newMatrix = *rec.fMatrix; 444 newMatrix.preConcat(shader.getLocalMatrix()); 445 if (rec.fLocalMatrix) { 446 newMatrix.preConcat(*rec.fLocalMatrix); 447 } 448 // This (1,1) translation is due to WebKit's 1 based coordinates for the noise 449 // (as opposed to 0 based, usually). The same adjustment is in the setData() function. 450 fMatrix.setTranslate(-newMatrix.getTranslateX() + SK_Scalar1, -newMatrix.getTranslateY() + SK_Scalar1); 451 fPaintingData = SkNEW_ARGS(PaintingData, (shader.fTileSize, shader.fSeed, shader.fBaseFrequencyX, shader.fBaseFrequencyY, newMatrix)); 452 } 453 454 SkPerlinNoiseShader::PerlinNoiseShaderContext::~PerlinNoiseShaderContext() { 455 SkDELETE(fPaintingData); 456 } 457 458 void SkPerlinNoiseShader::PerlinNoiseShaderContext::shadeSpan( 459 int x, int y, SkPMColor result[], int count) { 460 SkPoint point = SkPoint::Make(SkIntToScalar(x), SkIntToScalar(y)); 461 StitchData stitchData; 462 for (int i = 0; i < count; ++i) { 463 result[i] = shade(point, stitchData); 464 point.fX += SK_Scalar1; 465 } 466 } 467 468 void SkPerlinNoiseShader::PerlinNoiseShaderContext::shadeSpan16( 469 int x, int y, uint16_t result[], int count) { 470 SkPoint point = SkPoint::Make(SkIntToScalar(x), SkIntToScalar(y)); 471 StitchData stitchData; 472 DITHER_565_SCAN(y); 473 for (int i = 0; i < count; ++i) { 474 unsigned dither = DITHER_VALUE(x); 475 result[i] = SkDitherRGB32To565(shade(point, stitchData), dither); 476 DITHER_INC_X(x); 477 point.fX += SK_Scalar1; 478 } 479 } 480 481 ///////////////////////////////////////////////////////////////////// 482 483 #if SK_SUPPORT_GPU 484 485 class GrGLPerlinNoise : public GrGLFragmentProcessor { 486 public: 487 GrGLPerlinNoise(const GrProcessor&); 488 virtual ~GrGLPerlinNoise() {} 489 490 virtual void emitCode(GrGLFPBuilder*, 491 const GrFragmentProcessor&, 492 const char* outputColor, 493 const char* inputColor, 494 const TransformedCoordsArray&, 495 const TextureSamplerArray&) override; 496 497 void setData(const GrGLProgramDataManager&, const GrProcessor&) override; 498 499 static inline void GenKey(const GrProcessor&, const GrGLSLCaps&, GrProcessorKeyBuilder* b); 500 501 private: 502 503 GrGLProgramDataManager::UniformHandle fStitchDataUni; 504 SkPerlinNoiseShader::Type fType; 505 bool fStitchTiles; 506 int fNumOctaves; 507 GrGLProgramDataManager::UniformHandle fBaseFrequencyUni; 508 GrGLProgramDataManager::UniformHandle fAlphaUni; 509 510 private: 511 typedef GrGLFragmentProcessor INHERITED; 512 }; 513 514 ///////////////////////////////////////////////////////////////////// 515 516 class GrPerlinNoiseEffect : public GrFragmentProcessor { 517 public: 518 static GrFragmentProcessor* Create(SkPerlinNoiseShader::Type type, 519 int numOctaves, bool stitchTiles, 520 SkPerlinNoiseShader::PaintingData* paintingData, 521 GrTexture* permutationsTexture, GrTexture* noiseTexture, 522 const SkMatrix& matrix, uint8_t alpha) { 523 return SkNEW_ARGS(GrPerlinNoiseEffect, (type, numOctaves, stitchTiles, paintingData, 524 permutationsTexture, noiseTexture, matrix, alpha)); 525 } 526 527 virtual ~GrPerlinNoiseEffect() { 528 SkDELETE(fPaintingData); 529 } 530 531 const char* name() const override { return "PerlinNoise"; } 532 533 virtual void getGLProcessorKey(const GrGLSLCaps& caps, 534 GrProcessorKeyBuilder* b) const override { 535 GrGLPerlinNoise::GenKey(*this, caps, b); 536 } 537 538 GrGLFragmentProcessor* createGLInstance() const override { 539 return SkNEW_ARGS(GrGLPerlinNoise, (*this)); 540 } 541 542 const SkPerlinNoiseShader::StitchData& stitchData() const { return fPaintingData->fStitchDataInit; } 543 544 SkPerlinNoiseShader::Type type() const { return fType; } 545 bool stitchTiles() const { return fStitchTiles; } 546 const SkVector& baseFrequency() const { return fPaintingData->fBaseFrequency; } 547 int numOctaves() const { return fNumOctaves; } 548 const SkMatrix& matrix() const { return fCoordTransform.getMatrix(); } 549 uint8_t alpha() const { return fAlpha; } 550 551 private: 552 bool onIsEqual(const GrFragmentProcessor& sBase) const override { 553 const GrPerlinNoiseEffect& s = sBase.cast<GrPerlinNoiseEffect>(); 554 return fType == s.fType && 555 fPaintingData->fBaseFrequency == s.fPaintingData->fBaseFrequency && 556 fNumOctaves == s.fNumOctaves && 557 fStitchTiles == s.fStitchTiles && 558 fAlpha == s.fAlpha && 559 fPaintingData->fStitchDataInit == s.fPaintingData->fStitchDataInit; 560 } 561 562 void onComputeInvariantOutput(GrInvariantOutput* inout) const override { 563 inout->setToUnknown(GrInvariantOutput::kWillNot_ReadInput); 564 } 565 566 GrPerlinNoiseEffect(SkPerlinNoiseShader::Type type, 567 int numOctaves, bool stitchTiles, 568 SkPerlinNoiseShader::PaintingData* paintingData, 569 GrTexture* permutationsTexture, GrTexture* noiseTexture, 570 const SkMatrix& matrix, uint8_t alpha) 571 : fType(type) 572 , fNumOctaves(numOctaves) 573 , fStitchTiles(stitchTiles) 574 , fAlpha(alpha) 575 , fPermutationsAccess(permutationsTexture) 576 , fNoiseAccess(noiseTexture) 577 , fPaintingData(paintingData) { 578 this->initClassID<GrPerlinNoiseEffect>(); 579 this->addTextureAccess(&fPermutationsAccess); 580 this->addTextureAccess(&fNoiseAccess); 581 fCoordTransform.reset(kLocal_GrCoordSet, matrix); 582 this->addCoordTransform(&fCoordTransform); 583 } 584 585 GR_DECLARE_FRAGMENT_PROCESSOR_TEST; 586 587 SkPerlinNoiseShader::Type fType; 588 GrCoordTransform fCoordTransform; 589 int fNumOctaves; 590 bool fStitchTiles; 591 uint8_t fAlpha; 592 GrTextureAccess fPermutationsAccess; 593 GrTextureAccess fNoiseAccess; 594 SkPerlinNoiseShader::PaintingData *fPaintingData; 595 596 private: 597 typedef GrFragmentProcessor INHERITED; 598 }; 599 600 ///////////////////////////////////////////////////////////////////// 601 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrPerlinNoiseEffect); 602 603 GrFragmentProcessor* GrPerlinNoiseEffect::TestCreate(SkRandom* random, 604 GrContext* context, 605 const GrDrawTargetCaps&, 606 GrTexture**) { 607 int numOctaves = random->nextRangeU(2, 10); 608 bool stitchTiles = random->nextBool(); 609 SkScalar seed = SkIntToScalar(random->nextU()); 610 SkISize tileSize = SkISize::Make(random->nextRangeU(4, 4096), random->nextRangeU(4, 4096)); 611 SkScalar baseFrequencyX = random->nextRangeScalar(0.01f, 612 0.99f); 613 SkScalar baseFrequencyY = random->nextRangeScalar(0.01f, 614 0.99f); 615 616 SkShader* shader = random->nextBool() ? 617 SkPerlinNoiseShader::CreateFractalNoise(baseFrequencyX, baseFrequencyY, numOctaves, seed, 618 stitchTiles ? &tileSize : NULL) : 619 SkPerlinNoiseShader::CreateTurbulence(baseFrequencyX, baseFrequencyY, numOctaves, seed, 620 stitchTiles ? &tileSize : NULL); 621 622 SkPaint paint; 623 GrColor paintColor; 624 GrFragmentProcessor* effect; 625 SkAssertResult(shader->asFragmentProcessor(context, paint, 626 GrTest::TestMatrix(random), NULL, 627 &paintColor, &effect)); 628 629 SkDELETE(shader); 630 631 return effect; 632 } 633 634 GrGLPerlinNoise::GrGLPerlinNoise(const GrProcessor& processor) 635 : fType(processor.cast<GrPerlinNoiseEffect>().type()) 636 , fStitchTiles(processor.cast<GrPerlinNoiseEffect>().stitchTiles()) 637 , fNumOctaves(processor.cast<GrPerlinNoiseEffect>().numOctaves()) { 638 } 639 640 void GrGLPerlinNoise::emitCode(GrGLFPBuilder* builder, 641 const GrFragmentProcessor&, 642 const char* outputColor, 643 const char* inputColor, 644 const TransformedCoordsArray& coords, 645 const TextureSamplerArray& samplers) { 646 sk_ignore_unused_variable(inputColor); 647 648 GrGLFragmentBuilder* fsBuilder = builder->getFragmentShaderBuilder(); 649 SkString vCoords = fsBuilder->ensureFSCoords2D(coords, 0); 650 651 fBaseFrequencyUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility, 652 kVec2f_GrSLType, kDefault_GrSLPrecision, 653 "baseFrequency"); 654 const char* baseFrequencyUni = builder->getUniformCStr(fBaseFrequencyUni); 655 fAlphaUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility, 656 kFloat_GrSLType, kDefault_GrSLPrecision, 657 "alpha"); 658 const char* alphaUni = builder->getUniformCStr(fAlphaUni); 659 660 const char* stitchDataUni = NULL; 661 if (fStitchTiles) { 662 fStitchDataUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility, 663 kVec2f_GrSLType, kDefault_GrSLPrecision, 664 "stitchData"); 665 stitchDataUni = builder->getUniformCStr(fStitchDataUni); 666 } 667 668 // There are 4 lines, so the center of each line is 1/8, 3/8, 5/8 and 7/8 669 const char* chanCoordR = "0.125"; 670 const char* chanCoordG = "0.375"; 671 const char* chanCoordB = "0.625"; 672 const char* chanCoordA = "0.875"; 673 const char* chanCoord = "chanCoord"; 674 const char* stitchData = "stitchData"; 675 const char* ratio = "ratio"; 676 const char* noiseVec = "noiseVec"; 677 const char* noiseSmooth = "noiseSmooth"; 678 const char* floorVal = "floorVal"; 679 const char* fractVal = "fractVal"; 680 const char* uv = "uv"; 681 const char* ab = "ab"; 682 const char* latticeIdx = "latticeIdx"; 683 const char* bcoords = "bcoords"; 684 const char* lattice = "lattice"; 685 const char* inc8bit = "0.00390625"; // 1.0 / 256.0 686 // This is the math to convert the two 16bit integer packed into rgba 8 bit input into a 687 // [-1,1] vector and perform a dot product between that vector and the provided vector. 688 const char* dotLattice = "dot(((%s.ga + %s.rb * vec2(%s)) * vec2(2.0) - vec2(1.0)), %s);"; 689 690 // Add noise function 691 static const GrGLShaderVar gPerlinNoiseArgs[] = { 692 GrGLShaderVar(chanCoord, kFloat_GrSLType), 693 GrGLShaderVar(noiseVec, kVec2f_GrSLType) 694 }; 695 696 static const GrGLShaderVar gPerlinNoiseStitchArgs[] = { 697 GrGLShaderVar(chanCoord, kFloat_GrSLType), 698 GrGLShaderVar(noiseVec, kVec2f_GrSLType), 699 GrGLShaderVar(stitchData, kVec2f_GrSLType) 700 }; 701 702 SkString noiseCode; 703 704 noiseCode.appendf("\tvec4 %s;\n", floorVal); 705 noiseCode.appendf("\t%s.xy = floor(%s);\n", floorVal, noiseVec); 706 noiseCode.appendf("\t%s.zw = %s.xy + vec2(1.0);\n", floorVal, floorVal); 707 noiseCode.appendf("\tvec2 %s = fract(%s);\n", fractVal, noiseVec); 708 709 // smooth curve : t * t * (3 - 2 * t) 710 noiseCode.appendf("\n\tvec2 %s = %s * %s * (vec2(3.0) - vec2(2.0) * %s);", 711 noiseSmooth, fractVal, fractVal, fractVal); 712 713 // Adjust frequencies if we're stitching tiles 714 if (fStitchTiles) { 715 noiseCode.appendf("\n\tif(%s.x >= %s.x) { %s.x -= %s.x; }", 716 floorVal, stitchData, floorVal, stitchData); 717 noiseCode.appendf("\n\tif(%s.y >= %s.y) { %s.y -= %s.y; }", 718 floorVal, stitchData, floorVal, stitchData); 719 noiseCode.appendf("\n\tif(%s.z >= %s.x) { %s.z -= %s.x; }", 720 floorVal, stitchData, floorVal, stitchData); 721 noiseCode.appendf("\n\tif(%s.w >= %s.y) { %s.w -= %s.y; }", 722 floorVal, stitchData, floorVal, stitchData); 723 } 724 725 // Get texture coordinates and normalize 726 noiseCode.appendf("\n\t%s = fract(floor(mod(%s, 256.0)) / vec4(256.0));\n", 727 floorVal, floorVal); 728 729 // Get permutation for x 730 { 731 SkString xCoords(""); 732 xCoords.appendf("vec2(%s.x, 0.5)", floorVal); 733 734 noiseCode.appendf("\n\tvec2 %s;\n\t%s.x = ", latticeIdx, latticeIdx); 735 fsBuilder->appendTextureLookup(&noiseCode, samplers[0], xCoords.c_str(), kVec2f_GrSLType); 736 noiseCode.append(".r;"); 737 } 738 739 // Get permutation for x + 1 740 { 741 SkString xCoords(""); 742 xCoords.appendf("vec2(%s.z, 0.5)", floorVal); 743 744 noiseCode.appendf("\n\t%s.y = ", latticeIdx); 745 fsBuilder->appendTextureLookup(&noiseCode, samplers[0], xCoords.c_str(), kVec2f_GrSLType); 746 noiseCode.append(".r;"); 747 } 748 749 #if defined(SK_BUILD_FOR_ANDROID) 750 // Android rounding for Tegra devices, like, for example: Xoom (Tegra 2), Nexus 7 (Tegra 3). 751 // The issue is that colors aren't accurate enough on Tegra devices. For example, if an 8 bit 752 // value of 124 (or 0.486275 here) is entered, we can get a texture value of 123.513725 753 // (or 0.484368 here). The following rounding operation prevents these precision issues from 754 // affecting the result of the noise by making sure that we only have multiples of 1/255. 755 // (Note that 1/255 is about 0.003921569, which is the value used here). 756 noiseCode.appendf("\n\t%s = floor(%s * vec2(255.0) + vec2(0.5)) * vec2(0.003921569);", 757 latticeIdx, latticeIdx); 758 #endif 759 760 // Get (x,y) coordinates with the permutated x 761 noiseCode.appendf("\n\tvec4 %s = fract(%s.xyxy + %s.yyww);", bcoords, latticeIdx, floorVal); 762 763 noiseCode.appendf("\n\n\tvec2 %s;", uv); 764 // Compute u, at offset (0,0) 765 { 766 SkString latticeCoords(""); 767 latticeCoords.appendf("vec2(%s.x, %s)", bcoords, chanCoord); 768 noiseCode.appendf("\n\tvec4 %s = ", lattice); 769 fsBuilder->appendTextureLookup(&noiseCode, samplers[1], latticeCoords.c_str(), 770 kVec2f_GrSLType); 771 noiseCode.appendf(".bgra;\n\t%s.x = ", uv); 772 noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal); 773 } 774 775 noiseCode.appendf("\n\t%s.x -= 1.0;", fractVal); 776 // Compute v, at offset (-1,0) 777 { 778 SkString latticeCoords(""); 779 latticeCoords.appendf("vec2(%s.y, %s)", bcoords, chanCoord); 780 noiseCode.append("\n\tlattice = "); 781 fsBuilder->appendTextureLookup(&noiseCode, samplers[1], latticeCoords.c_str(), 782 kVec2f_GrSLType); 783 noiseCode.appendf(".bgra;\n\t%s.y = ", uv); 784 noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal); 785 } 786 787 // Compute 'a' as a linear interpolation of 'u' and 'v' 788 noiseCode.appendf("\n\tvec2 %s;", ab); 789 noiseCode.appendf("\n\t%s.x = mix(%s.x, %s.y, %s.x);", ab, uv, uv, noiseSmooth); 790 791 noiseCode.appendf("\n\t%s.y -= 1.0;", fractVal); 792 // Compute v, at offset (-1,-1) 793 { 794 SkString latticeCoords(""); 795 latticeCoords.appendf("vec2(%s.w, %s)", bcoords, chanCoord); 796 noiseCode.append("\n\tlattice = "); 797 fsBuilder->appendTextureLookup(&noiseCode, samplers[1], latticeCoords.c_str(), 798 kVec2f_GrSLType); 799 noiseCode.appendf(".bgra;\n\t%s.y = ", uv); 800 noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal); 801 } 802 803 noiseCode.appendf("\n\t%s.x += 1.0;", fractVal); 804 // Compute u, at offset (0,-1) 805 { 806 SkString latticeCoords(""); 807 latticeCoords.appendf("vec2(%s.z, %s)", bcoords, chanCoord); 808 noiseCode.append("\n\tlattice = "); 809 fsBuilder->appendTextureLookup(&noiseCode, samplers[1], latticeCoords.c_str(), 810 kVec2f_GrSLType); 811 noiseCode.appendf(".bgra;\n\t%s.x = ", uv); 812 noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal); 813 } 814 815 // Compute 'b' as a linear interpolation of 'u' and 'v' 816 noiseCode.appendf("\n\t%s.y = mix(%s.x, %s.y, %s.x);", ab, uv, uv, noiseSmooth); 817 // Compute the noise as a linear interpolation of 'a' and 'b' 818 noiseCode.appendf("\n\treturn mix(%s.x, %s.y, %s.y);\n", ab, ab, noiseSmooth); 819 820 SkString noiseFuncName; 821 if (fStitchTiles) { 822 fsBuilder->emitFunction(kFloat_GrSLType, 823 "perlinnoise", SK_ARRAY_COUNT(gPerlinNoiseStitchArgs), 824 gPerlinNoiseStitchArgs, noiseCode.c_str(), &noiseFuncName); 825 } else { 826 fsBuilder->emitFunction(kFloat_GrSLType, 827 "perlinnoise", SK_ARRAY_COUNT(gPerlinNoiseArgs), 828 gPerlinNoiseArgs, noiseCode.c_str(), &noiseFuncName); 829 } 830 831 // There are rounding errors if the floor operation is not performed here 832 fsBuilder->codeAppendf("\n\t\tvec2 %s = floor(%s.xy) * %s;", 833 noiseVec, vCoords.c_str(), baseFrequencyUni); 834 835 // Clear the color accumulator 836 fsBuilder->codeAppendf("\n\t\t%s = vec4(0.0);", outputColor); 837 838 if (fStitchTiles) { 839 // Set up TurbulenceInitial stitch values. 840 fsBuilder->codeAppendf("\n\t\tvec2 %s = %s;", stitchData, stitchDataUni); 841 } 842 843 fsBuilder->codeAppendf("\n\t\tfloat %s = 1.0;", ratio); 844 845 // Loop over all octaves 846 fsBuilder->codeAppendf("\n\t\tfor (int octave = 0; octave < %d; ++octave) {", fNumOctaves); 847 848 fsBuilder->codeAppendf("\n\t\t\t%s += ", outputColor); 849 if (fType != SkPerlinNoiseShader::kFractalNoise_Type) { 850 fsBuilder->codeAppend("abs("); 851 } 852 if (fStitchTiles) { 853 fsBuilder->codeAppendf( 854 "vec4(\n\t\t\t\t%s(%s, %s, %s),\n\t\t\t\t%s(%s, %s, %s)," 855 "\n\t\t\t\t%s(%s, %s, %s),\n\t\t\t\t%s(%s, %s, %s))", 856 noiseFuncName.c_str(), chanCoordR, noiseVec, stitchData, 857 noiseFuncName.c_str(), chanCoordG, noiseVec, stitchData, 858 noiseFuncName.c_str(), chanCoordB, noiseVec, stitchData, 859 noiseFuncName.c_str(), chanCoordA, noiseVec, stitchData); 860 } else { 861 fsBuilder->codeAppendf( 862 "vec4(\n\t\t\t\t%s(%s, %s),\n\t\t\t\t%s(%s, %s)," 863 "\n\t\t\t\t%s(%s, %s),\n\t\t\t\t%s(%s, %s))", 864 noiseFuncName.c_str(), chanCoordR, noiseVec, 865 noiseFuncName.c_str(), chanCoordG, noiseVec, 866 noiseFuncName.c_str(), chanCoordB, noiseVec, 867 noiseFuncName.c_str(), chanCoordA, noiseVec); 868 } 869 if (fType != SkPerlinNoiseShader::kFractalNoise_Type) { 870 fsBuilder->codeAppendf(")"); // end of "abs(" 871 } 872 fsBuilder->codeAppendf(" * %s;", ratio); 873 874 fsBuilder->codeAppendf("\n\t\t\t%s *= vec2(2.0);", noiseVec); 875 fsBuilder->codeAppendf("\n\t\t\t%s *= 0.5;", ratio); 876 877 if (fStitchTiles) { 878 fsBuilder->codeAppendf("\n\t\t\t%s *= vec2(2.0);", stitchData); 879 } 880 fsBuilder->codeAppend("\n\t\t}"); // end of the for loop on octaves 881 882 if (fType == SkPerlinNoiseShader::kFractalNoise_Type) { 883 // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2 884 // by fractalNoise and (turbulenceFunctionResult) by turbulence. 885 fsBuilder->codeAppendf("\n\t\t%s = %s * vec4(0.5) + vec4(0.5);", outputColor, outputColor); 886 } 887 888 fsBuilder->codeAppendf("\n\t\t%s.a *= %s;", outputColor, alphaUni); 889 890 // Clamp values 891 fsBuilder->codeAppendf("\n\t\t%s = clamp(%s, 0.0, 1.0);", outputColor, outputColor); 892 893 // Pre-multiply the result 894 fsBuilder->codeAppendf("\n\t\t%s = vec4(%s.rgb * %s.aaa, %s.a);\n", 895 outputColor, outputColor, outputColor, outputColor); 896 } 897 898 void GrGLPerlinNoise::GenKey(const GrProcessor& processor, const GrGLSLCaps&, 899 GrProcessorKeyBuilder* b) { 900 const GrPerlinNoiseEffect& turbulence = processor.cast<GrPerlinNoiseEffect>(); 901 902 uint32_t key = turbulence.numOctaves(); 903 904 key = key << 3; // Make room for next 3 bits 905 906 switch (turbulence.type()) { 907 case SkPerlinNoiseShader::kFractalNoise_Type: 908 key |= 0x1; 909 break; 910 case SkPerlinNoiseShader::kTurbulence_Type: 911 key |= 0x2; 912 break; 913 default: 914 // leave key at 0 915 break; 916 } 917 918 if (turbulence.stitchTiles()) { 919 key |= 0x4; // Flip the 3rd bit if tile stitching is on 920 } 921 922 b->add32(key); 923 } 924 925 void GrGLPerlinNoise::setData(const GrGLProgramDataManager& pdman, const GrProcessor& processor) { 926 INHERITED::setData(pdman, processor); 927 928 const GrPerlinNoiseEffect& turbulence = processor.cast<GrPerlinNoiseEffect>(); 929 930 const SkVector& baseFrequency = turbulence.baseFrequency(); 931 pdman.set2f(fBaseFrequencyUni, baseFrequency.fX, baseFrequency.fY); 932 pdman.set1f(fAlphaUni, SkIntToScalar(turbulence.alpha()) / 255); 933 934 if (turbulence.stitchTiles()) { 935 const SkPerlinNoiseShader::StitchData& stitchData = turbulence.stitchData(); 936 pdman.set2f(fStitchDataUni, SkIntToScalar(stitchData.fWidth), 937 SkIntToScalar(stitchData.fHeight)); 938 } 939 } 940 941 ///////////////////////////////////////////////////////////////////// 942 943 bool SkPerlinNoiseShader::asFragmentProcessor(GrContext* context, const SkPaint& paint, 944 const SkMatrix& viewM, 945 const SkMatrix* externalLocalMatrix, 946 GrColor* paintColor, GrFragmentProcessor** fp) const { 947 SkASSERT(context); 948 949 *paintColor = SkColor2GrColorJustAlpha(paint.getColor()); 950 951 SkMatrix localMatrix = this->getLocalMatrix(); 952 if (externalLocalMatrix) { 953 localMatrix.preConcat(*externalLocalMatrix); 954 } 955 956 SkMatrix matrix = viewM; 957 matrix.preConcat(localMatrix); 958 959 if (0 == fNumOctaves) { 960 if (kFractalNoise_Type == fType) { 961 uint32_t alpha = paint.getAlpha() >> 1; 962 uint32_t rgb = alpha >> 1; 963 *paintColor = GrColorPackRGBA(rgb, rgb, rgb, alpha); 964 } else { 965 *paintColor = 0; 966 } 967 return true; 968 } 969 970 // Either we don't stitch tiles, either we have a valid tile size 971 SkASSERT(!fStitchTiles || !fTileSize.isEmpty()); 972 973 SkPerlinNoiseShader::PaintingData* paintingData = 974 SkNEW_ARGS(PaintingData, (fTileSize, fSeed, fBaseFrequencyX, fBaseFrequencyY, matrix)); 975 SkAutoTUnref<GrTexture> permutationsTexture( 976 GrRefCachedBitmapTexture(context, paintingData->getPermutationsBitmap(), NULL)); 977 SkAutoTUnref<GrTexture> noiseTexture( 978 GrRefCachedBitmapTexture(context, paintingData->getNoiseBitmap(), NULL)); 979 980 SkMatrix m = viewM; 981 m.setTranslateX(-localMatrix.getTranslateX() + SK_Scalar1); 982 m.setTranslateY(-localMatrix.getTranslateY() + SK_Scalar1); 983 if ((permutationsTexture) && (noiseTexture)) { 984 *fp = GrPerlinNoiseEffect::Create(fType, 985 fNumOctaves, 986 fStitchTiles, 987 paintingData, 988 permutationsTexture, noiseTexture, 989 m, paint.getAlpha()); 990 } else { 991 SkDELETE(paintingData); 992 *fp = NULL; 993 } 994 return true; 995 } 996 997 #else 998 999 bool SkPerlinNoiseShader::asFragmentProcessor(GrContext*, const SkPaint&, const SkMatrix&, 1000 const SkMatrix*, GrColor*, 1001 GrFragmentProcessor**) const { 1002 SkDEBUGFAIL("Should not call in GPU-less build"); 1003 return false; 1004 } 1005 1006 #endif 1007 1008 #ifndef SK_IGNORE_TO_STRING 1009 void SkPerlinNoiseShader::toString(SkString* str) const { 1010 str->append("SkPerlinNoiseShader: ("); 1011 1012 str->append("type: "); 1013 switch (fType) { 1014 case kFractalNoise_Type: 1015 str->append("\"fractal noise\""); 1016 break; 1017 case kTurbulence_Type: 1018 str->append("\"turbulence\""); 1019 break; 1020 default: 1021 str->append("\"unknown\""); 1022 break; 1023 } 1024 str->append(" base frequency: ("); 1025 str->appendScalar(fBaseFrequencyX); 1026 str->append(", "); 1027 str->appendScalar(fBaseFrequencyY); 1028 str->append(") number of octaves: "); 1029 str->appendS32(fNumOctaves); 1030 str->append(" seed: "); 1031 str->appendScalar(fSeed); 1032 str->append(" stitch tiles: "); 1033 str->append(fStitchTiles ? "true " : "false "); 1034 1035 this->INHERITED::toString(str); 1036 1037 str->append(")"); 1038 } 1039 #endif 1040