blob: c7afc3e9fb9d7ea74ea02056c09a6b0071f75fb4 [file] [log] [blame]
/*
* Copyright 2013 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkPerlinNoiseShaderImpl_DEFINED
#define SkPerlinNoiseShaderImpl_DEFINED
#include "include/core/SkAlphaType.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkColorType.h"
#include "include/core/SkFlattenable.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkPoint.h"
#include "include/core/SkScalar.h"
#include "include/core/SkSize.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkFloatingPoint.h"
#include "include/private/base/SkMath.h"
#include "include/private/base/SkOnce.h"
#include "src/shaders/SkShaderBase.h"
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <memory>
class SkReadBuffer;
enum class SkPerlinNoiseShaderType;
struct SkStageRec;
class SkWriteBuffer;
class SkPerlinNoiseShader : public SkShaderBase {
private:
static constexpr int kBlockSize = 256;
static constexpr int kBlockMask = kBlockSize - 1;
static constexpr int kPerlinNoise = 4096;
static constexpr int kRandMaximum = SK_MaxS32; // 2**31 - 1
public:
struct StitchData {
StitchData() = default;
StitchData(SkScalar w, SkScalar h)
: fWidth(std::min(SkScalarRoundToInt(w), SK_MaxS32 - kPerlinNoise))
, fWrapX(kPerlinNoise + fWidth)
, fHeight(std::min(SkScalarRoundToInt(h), SK_MaxS32 - kPerlinNoise))
, fWrapY(kPerlinNoise + fHeight) {}
bool operator==(const StitchData& other) const {
return fWidth == other.fWidth && fWrapX == other.fWrapX && fHeight == other.fHeight &&
fWrapY == other.fWrapY;
}
int fWidth = 0; // How much to subtract to wrap for stitching.
int fWrapX = 0; // Minimum value to wrap.
int fHeight = 0;
int fWrapY = 0;
};
struct PaintingData {
PaintingData(const SkISize& tileSize,
SkScalar seed,
SkScalar baseFrequencyX,
SkScalar baseFrequencyY) {
fBaseFrequency.set(baseFrequencyX, baseFrequencyY);
fTileSize.set(SkScalarRoundToInt(tileSize.fWidth),
SkScalarRoundToInt(tileSize.fHeight));
this->init(seed);
if (!fTileSize.isEmpty()) {
this->stitch();
}
}
void generateBitmaps() {
SkImageInfo info = SkImageInfo::MakeA8(kBlockSize, 1);
fPermutationsBitmap.installPixels(info, fLatticeSelector, info.minRowBytes());
fPermutationsBitmap.setImmutable();
info = SkImageInfo::Make(kBlockSize, 4, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
fNoiseBitmap.installPixels(info, fNoise[0][0], info.minRowBytes());
fNoiseBitmap.setImmutable();
}
PaintingData(const PaintingData& that)
: fSeed(that.fSeed)
, fTileSize(that.fTileSize)
, fBaseFrequency(that.fBaseFrequency)
, fStitchDataInit(that.fStitchDataInit)
, fPermutationsBitmap(that.fPermutationsBitmap)
, fNoiseBitmap(that.fNoiseBitmap) {
memcpy(fLatticeSelector, that.fLatticeSelector, sizeof(fLatticeSelector));
memcpy(fNoise, that.fNoise, sizeof(fNoise));
}
int fSeed;
uint8_t fLatticeSelector[kBlockSize];
uint16_t fNoise[4][kBlockSize][2];
SkISize fTileSize;
SkVector fBaseFrequency;
StitchData fStitchDataInit;
private:
SkBitmap fPermutationsBitmap;
SkBitmap fNoiseBitmap;
int random() {
// See https://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement
// m = kRandMaximum, 2**31 - 1 (2147483647)
static constexpr int kRandAmplitude = 16807; // 7**5; primitive root of m
static constexpr int kRandQ = 127773; // m / a
static constexpr int kRandR = 2836; // m % a
int result = kRandAmplitude * (fSeed % kRandQ) - kRandR * (fSeed / kRandQ);
if (result <= 0) {
result += kRandMaximum;
}
fSeed = result;
return result;
}
// Only called once. Could be part of the constructor.
void init(SkScalar seed) {
// According to the SVG spec, we must truncate (not round) the seed value.
fSeed = SkScalarTruncToInt(seed);
// The seed value clamp to the range [1, kRandMaximum - 1].
if (fSeed <= 0) {
fSeed = -(fSeed % (kRandMaximum - 1)) + 1;
}
if (fSeed > kRandMaximum - 1) {
fSeed = kRandMaximum - 1;
}
for (int channel = 0; channel < 4; ++channel) {
for (int i = 0; i < kBlockSize; ++i) {
fLatticeSelector[i] = i;
fNoise[channel][i][0] = (random() % (2 * kBlockSize));
fNoise[channel][i][1] = (random() % (2 * kBlockSize));
}
}
for (int i = kBlockSize - 1; i > 0; --i) {
int k = fLatticeSelector[i];
int j = random() % kBlockSize;
SkASSERT(j >= 0);
SkASSERT(j < kBlockSize);
fLatticeSelector[i] = fLatticeSelector[j];
fLatticeSelector[j] = k;
}
// Perform the permutations now
{
// Copy noise data
uint16_t noise[4][kBlockSize][2];
for (int i = 0; i < kBlockSize; ++i) {
for (int channel = 0; channel < 4; ++channel) {
for (int j = 0; j < 2; ++j) {
noise[channel][i][j] = fNoise[channel][i][j];
}
}
}
// Do permutations on noise data
for (int i = 0; i < kBlockSize; ++i) {
for (int channel = 0; channel < 4; ++channel) {
for (int j = 0; j < 2; ++j) {
fNoise[channel][i][j] = noise[channel][fLatticeSelector[i]][j];
}
}
}
}
// Half of the largest possible value for 16 bit unsigned int
static constexpr SkScalar kHalfMax16bits = 32767.5f;
// Compute gradients from permuted noise data
static constexpr SkScalar kInvBlockSizef = 1.0 / SkIntToScalar(kBlockSize);
for (int channel = 0; channel < 4; ++channel) {
for (int i = 0; i < kBlockSize; ++i) {
SkPoint gradient =
SkPoint::Make((fNoise[channel][i][0] - kBlockSize) * kInvBlockSizef,
(fNoise[channel][i][1] - kBlockSize) * kInvBlockSizef);
gradient.normalize();
// Put the normalized gradient back into the noise data
fNoise[channel][i][0] = SkScalarRoundToInt((gradient.fX + 1) * kHalfMax16bits);
fNoise[channel][i][1] = SkScalarRoundToInt((gradient.fY + 1) * kHalfMax16bits);
}
}
}
// Only called once. Could be part of the constructor.
void stitch() {
SkScalar tileWidth = SkIntToScalar(fTileSize.width());
SkScalar tileHeight = SkIntToScalar(fTileSize.height());
SkASSERT(tileWidth > 0 && tileHeight > 0);
// When stitching tiled turbulence, the frequencies must be adjusted
// so that the tile borders will be continuous.
if (fBaseFrequency.fX) {
SkScalar lowFrequencx =
SkScalarFloorToScalar(tileWidth * fBaseFrequency.fX) / tileWidth;
SkScalar highFrequencx =
SkScalarCeilToScalar(tileWidth * fBaseFrequency.fX) / tileWidth;
// BaseFrequency should be non-negative according to the standard.
// lowFrequencx can be 0 if fBaseFrequency.fX is very small.
if (sk_ieee_float_divide(fBaseFrequency.fX, lowFrequencx) <
highFrequencx / fBaseFrequency.fX) {
fBaseFrequency.fX = lowFrequencx;
} else {
fBaseFrequency.fX = highFrequencx;
}
}
if (fBaseFrequency.fY) {
SkScalar lowFrequency =
SkScalarFloorToScalar(tileHeight * fBaseFrequency.fY) / tileHeight;
SkScalar highFrequency =
SkScalarCeilToScalar(tileHeight * fBaseFrequency.fY) / tileHeight;
// lowFrequency can be 0 if fBaseFrequency.fY is very small.
if (sk_ieee_float_divide(fBaseFrequency.fY, lowFrequency) <
highFrequency / fBaseFrequency.fY) {
fBaseFrequency.fY = lowFrequency;
} else {
fBaseFrequency.fY = highFrequency;
}
}
fStitchDataInit =
StitchData(tileWidth * fBaseFrequency.fX, tileHeight * fBaseFrequency.fY);
}
public:
const SkBitmap& getPermutationsBitmap() const {
SkASSERT(!fPermutationsBitmap.drawsNothing());
return fPermutationsBitmap;
}
const SkBitmap& getNoiseBitmap() const {
SkASSERT(!fNoiseBitmap.drawsNothing());
return fNoiseBitmap;
}
}; // struct PaintingData
static const int kMaxOctaves = 255; // numOctaves must be <= 0 and <= kMaxOctaves
SkPerlinNoiseShader(SkPerlinNoiseShaderType type,
SkScalar baseFrequencyX,
SkScalar baseFrequencyY,
int numOctaves,
SkScalar seed,
const SkISize* tileSize);
ShaderType type() const override { return ShaderType::kPerlinNoise; }
SkPerlinNoiseShaderType noiseType() const { return fType; }
int numOctaves() const { return fNumOctaves; }
bool stitchTiles() const { return fStitchTiles; }
SkISize tileSize() const { return fTileSize; }
std::unique_ptr<PaintingData> getPaintingData() const {
return std::make_unique<PaintingData>(fTileSize, fSeed, fBaseFrequencyX, fBaseFrequencyY);
}
bool appendStages(const SkStageRec& rec, const SkShaders::MatrixRec& mRec) const override;
protected:
void flatten(SkWriteBuffer&) const override;
private:
SK_FLATTENABLE_HOOKS(SkPerlinNoiseShader)
const SkPerlinNoiseShaderType fType;
const SkScalar fBaseFrequencyX;
const SkScalar fBaseFrequencyY;
const int fNumOctaves;
const SkScalar fSeed;
const SkISize fTileSize;
const bool fStitchTiles;
mutable SkOnce fInitPaintingDataOnce;
std::unique_ptr<PaintingData> fPaintingData;
friend void SkRegisterPerlinNoiseShaderFlattenable();
};
#endif