@amcharts/amcharts5
Version:
amCharts 5
125 lines • 5.7 kB
JavaScript
import { Pattern } from "./Pattern";
import { Color } from "../../util/Color";
/**
* Grain pattern.
*
* Allows to add grain (noise) effect to your [[Graphics]] objects.
*
* Note, grain pattern does not support `fill` and `color` setting.
* Use `colors` setting to define colors of a grain pixels.
*
* Note, rotation setting is not supported by this pattern.
*
* @see {@link https://www.amcharts.com/docs/v5/concepts/colors-gradients-and-patterns/patterns/#Grain_patterns} for more info
* @since 5.5.0
*/
export class GrainPattern extends Pattern {
constructor() {
super(...arguments);
this.canvas = document.createElement("canvas");
this.context = this.canvas.getContext("2d");
this._clearGrain = false;
}
_beforeChanged() {
//document.body.appendChild(this.canvas); // temp
// Assigning canvas.width/height clears and reallocates the backing store,
// so only do it when the dimensions actually change.
const width = this.get("width", 200);
const height = this.get("height", 200);
if (this.canvas.width !== width) {
this.canvas.width = width;
}
if (this.canvas.height !== height) {
this.canvas.height = height;
}
if (this.isDirty("size") || this.isDirty("density") || this.isDirty("minOpacity") || this.isDirty("maxOpacity") || this.isDirty("colors") || this.isDirty("horizontalGap") || this.isDirty("verticalGap")) {
this._clearGrain = true;
}
super._beforeChanged();
}
_changed() {
super._changed();
if (this._clearGrain) {
const width = this.get("width", 200);
const height = this.get("height", 200);
// The canvas was just cleared, so allocate a fresh zeroed buffer
// instead of reading the (blank) canvas back with getImageData.
const patternData = this.context.createImageData(width, height);
const size = Math.max(1, this.get("size", 1));
const minOpacity = this.get("minOpacity", 0);
const maxOpacity = this.get("maxOpacity", 0.3);
const colors = this.get("colors", [this.get("color", Color.fromHex(0x000000))]);
const cols = width / size;
const rows = height / size;
const density = this.get("density", 1);
const horizontalGap = this.get("horizontalGap", 0) + 1;
const verticalGap = this.get("verticalGap", 0) + 1;
const colorCount = colors.length;
const opacityRange = maxOpacity - minOpacity;
// Precompute each palette color's channels once, so the hot loop reads
// plain numbers instead of the Color r/g/b getters (one getter call per
// channel per painted pixel otherwise).
const rCh = new Array(colorCount);
const gCh = new Array(colorCount);
const bCh = new Array(colorCount);
for (let i = 0; i < colorCount; i++) {
rCh[i] = colors[i].r;
gCh[i] = colors[i].g;
bCh[i] = colors[i].b;
}
// Write packed 32-bit RGBA pixels through a Uint32Array view instead of
// four clamped Uint8ClampedArray byte writes per pixel.
const buf32 = new Uint32Array(patternData.data.buffer);
const alwaysDraw = density >= 1;
const hGap = horizontalGap > 1 ? horizontalGap : 0;
const vGap = verticalGap > 1 ? verticalGap : 0;
for (let r = 0; r < rows; r++) {
if (vGap && r % vGap !== 0) {
continue;
}
for (let c = 0; c < cols; c++) {
if (hGap && c % hGap !== 0) {
continue;
}
// Draw only `density` fraction of cells; compute the random
// color/alpha only for the cells we actually paint.
if (alwaysDraw || Math.random() < density) {
const ci = colorCount > 1 ? (Math.random() * colorCount) | 0 : 0;
const alpha = ((minOpacity + Math.random() * opacityRange) * 255) | 0;
// Little-endian RGBA.
const packed = (rCh[ci] | (gCh[ci] << 8) | (bCh[ci] << 16) | (alpha << 24)) >>> 0;
if (size === 1) {
buf32[r * width + c] = packed;
}
else {
this._setRectData(c, r, size, width, buf32, packed);
}
}
}
}
this.context.putImageData(patternData, 0, 0);
this._pattern = this.context.createPattern(this.canvas, "repeat");
}
this._clearGrain = false;
}
_checkDirtyFill() {
return false;
}
_setRectData(col, row, size, width, buf32, packed) {
const x0 = col * size;
const y0 = row * size;
const x1 = x0 + size;
const y1 = y0 + size;
for (var y = y0; y < y1; y++) {
// Write each row as a contiguous run for cache-friendly access.
let i = y * width + x0;
for (var x = x0; x < x1; x++) {
buf32[i] = packed;
i++;
}
}
}
}
GrainPattern.className = "GrainPattern";
GrainPattern.classNames = Pattern.classNames.concat([GrainPattern.className]);
//# sourceMappingURL=GrainPattern.js.map