@visactor/vrender-animate
Version:
This module provides a graph-based animation system for VRender.
156 lines (150 loc) • 9.52 kB
JavaScript
import { Canvas2DEffectBase } from "./base/CustomEffectBase";
import { ImageProcessUtils } from "./base/ImageProcessUtils";
export class Glitch extends Canvas2DEffectBase {
constructor(from, to, duration, easing, params) {
var _a, _b;
super(from, to, duration, easing, params), this.glitchConfig = {
effectType: (null === (_a = null == params ? void 0 : params.options) || void 0 === _a ? void 0 : _a.effectType) || "rgb-shift",
intensity: void 0 !== (null === (_b = null == params ? void 0 : params.options) || void 0 === _b ? void 0 : _b.intensity) ? params.options.intensity : .5
};
}
applyCanvas2DEffect(canvas) {
if (this.glitchConfig.intensity <= 0) {
const outputCanvas = this.createOutputCanvas(canvas);
return outputCanvas ? outputCanvas.canvas : null;
}
try {
switch (this.glitchConfig.effectType) {
case "rgb-shift":
default:
return this.applyRGBShiftGlitch(canvas);
case "digital-distortion":
return this.applyDigitalDistortionGlitch(canvas);
case "scan-lines":
return this.applyScanLineGlitch(canvas);
case "data-corruption":
return this.applyDataCorruptionGlitch(canvas);
}
} catch (error) {
return console.warn("Glitch effect failed:", error), null;
}
}
applyRGBShiftGlitch(canvas) {
const outputCanvas = this.createOutputCanvas(canvas);
if (!outputCanvas) return null;
const {ctx: ctx} = outputCanvas;
try {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const dynamicIntensity = ImageProcessUtils.calculateDynamicStrength(this.glitchConfig.intensity, this.getAnimationTime()), maxOffset = Math.floor(20 * dynamicIntensity), redOffset = this.generateRandomOffset(maxOffset), greenOffset = this.generateRandomOffset(maxOffset, .3), blueOffset = this.generateRandomOffset(-maxOffset), tempCanvas = ImageProcessUtils.createTempCanvas(canvas.width, canvas.height), tempCtx = tempCanvas.getContext("2d");
tempCtx.drawImage(canvas, 0, 0);
const originalImageData = tempCtx.getImageData(0, 0, canvas.width, canvas.height), redChannelData = ImageProcessUtils.extractChannel(originalImageData, 0), greenChannelData = ImageProcessUtils.extractChannel(originalImageData, 1), blueChannelData = ImageProcessUtils.extractChannel(originalImageData, 2);
return ctx.globalCompositeOperation = "screen", tempCtx.clearRect(0, 0, canvas.width, canvas.height),
tempCtx.putImageData(redChannelData, 0, 0), ctx.drawImage(tempCanvas, redOffset.x, redOffset.y),
tempCtx.clearRect(0, 0, canvas.width, canvas.height), tempCtx.putImageData(greenChannelData, 0, 0),
ctx.drawImage(tempCanvas, greenOffset.x, greenOffset.y), tempCtx.clearRect(0, 0, canvas.width, canvas.height),
tempCtx.putImageData(blueChannelData, 0, 0), ctx.drawImage(tempCanvas, blueOffset.x, blueOffset.y),
ctx.globalCompositeOperation = "source-over", outputCanvas.canvas;
} catch (error) {
return console.warn("RGB shift glitch failed:", error), null;
}
}
applyDigitalDistortionGlitch(canvas) {
const outputCanvas = this.createOutputCanvas(canvas);
if (!outputCanvas) return null;
const {ctx: ctx} = outputCanvas;
try {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height), dynamicIntensity = ImageProcessUtils.calculateDynamicStrength(this.glitchConfig.intensity, this.getAnimationTime()), distortedImageData = this.processDigitalDistortion(imageData, dynamicIntensity);
return ctx.clearRect(0, 0, canvas.width, canvas.height), ctx.putImageData(distortedImageData, 0, 0),
outputCanvas.canvas;
} catch (error) {
return console.warn("Digital distortion glitch failed:", error), null;
}
}
applyScanLineGlitch(canvas) {
const outputCanvas = this.createOutputCanvas(canvas);
if (!outputCanvas) return null;
const {ctx: ctx} = outputCanvas;
try {
const dynamicIntensity = ImageProcessUtils.calculateDynamicStrength(this.glitchConfig.intensity, this.getAnimationTime()), lineSpacing = Math.max(2, Math.floor(10 - 8 * dynamicIntensity));
ctx.globalCompositeOperation = "multiply";
for (let y = 0; y < canvas.height; y += lineSpacing) if (Math.random() < dynamicIntensity) {
const opacity = .1 + .4 * dynamicIntensity;
ctx.fillStyle = `rgba(0, 0, 0, ${opacity})`, ctx.fillRect(0, y, canvas.width, 1);
}
ctx.globalCompositeOperation = "screen";
const brightLineCount = Math.floor(20 * dynamicIntensity);
for (let i = 0; i < brightLineCount; i++) {
const y = Math.random() * canvas.height, opacity = .3 * dynamicIntensity;
ctx.fillStyle = `rgba(255, 255, 255, ${opacity})`, ctx.fillRect(0, Math.floor(y), canvas.width, 1);
}
return ctx.globalCompositeOperation = "source-over", outputCanvas.canvas;
} catch (error) {
return console.warn("Scan line glitch failed:", error), null;
}
}
applyDataCorruptionGlitch(canvas) {
const outputCanvas = this.createOutputCanvas(canvas);
if (!outputCanvas) return null;
const {ctx: ctx} = outputCanvas;
try {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height), dynamicIntensity = ImageProcessUtils.calculateDynamicStrength(this.glitchConfig.intensity, this.getAnimationTime()), corruptedImageData = this.processDataCorruption(imageData, dynamicIntensity);
return ctx.clearRect(0, 0, canvas.width, canvas.height), ctx.putImageData(corruptedImageData, 0, 0),
outputCanvas.canvas;
} catch (error) {
return console.warn("Data corruption glitch failed:", error), null;
}
}
generateRandomOffset(maxOffset, scale = 1) {
return {
x: (Math.random() - .5) * maxOffset,
y: (Math.random() - .5) * maxOffset * scale
};
}
processDigitalDistortion(imageData, intensity) {
const {data: data, width: width, height: height} = imageData, result = new Uint8ClampedArray(data), sliceCount = Math.floor(20 * intensity) + 5, sliceHeight = Math.floor(height / sliceCount);
for (let i = 0; i < sliceCount; i++) if (Math.random() < intensity) {
const y = i * sliceHeight, sliceEnd = Math.min(y + sliceHeight, height), offset = Math.floor((Math.random() - .5) * width * intensity * .1);
this.shiftSliceHorizontal(result, width, height, y, sliceEnd, offset);
}
const noiseIntensity = .3 * intensity;
for (let i = 0; i < data.length; i += 4) Math.random() < noiseIntensity && (result[i] = 255 * Math.random(),
result[i + 1] = 255 * Math.random(), result[i + 2] = 255 * Math.random());
return new ImageData(result, width, height);
}
shiftSliceHorizontal(data, width, height, startY, endY, offset) {
const tempRow = new Uint8ClampedArray(4 * width);
for (let y = startY; y < endY; y++) {
const rowStart = y * width * 4;
for (let x = 0; x < 4 * width; x++) tempRow[x] = data[rowStart + x];
for (let x = 0; x < width; x++) {
const targetIndex = rowStart + 4 * x, sourceIndex = 4 * ((x - offset + width) % width);
data[targetIndex] = tempRow[sourceIndex], data[targetIndex + 1] = tempRow[sourceIndex + 1],
data[targetIndex + 2] = tempRow[sourceIndex + 2], data[targetIndex + 3] = tempRow[sourceIndex + 3];
}
}
}
processDataCorruption(imageData, intensity) {
const {data: data, width: width, height: height} = imageData, result = new Uint8ClampedArray(data), stripeCount = Math.floor(15 * intensity) + 5;
for (let i = 0; i < stripeCount; i++) if (Math.random() < intensity) {
const x = Math.floor(Math.random() * width), stripeWidth = Math.floor(5 * Math.random()) + 1, color = Math.random() < .5 ? 0 : 255;
for (let y = 0; y < height; y++) for (let dx = 0; dx < stripeWidth && x + dx < width; dx++) {
const index = 4 * (y * width + x + dx);
result[index] = color, result[index + 1] = color, result[index + 2] = color;
}
}
const corruptionCount = Math.floor(20 * intensity);
for (let i = 0; i < corruptionCount; i++) {
const blockX = Math.floor(Math.random() * width), blockY = Math.floor(Math.random() * height), blockW = Math.floor(20 * Math.random()) + 5, blockH = Math.floor(10 * Math.random()) + 2;
this.corruptBlock(result, width, height, blockX, blockY, blockW, blockH);
}
return new ImageData(result, width, height);
}
corruptBlock(data, width, height, x, y, w, h) {
for (let dy = 0; dy < h && y + dy < height; dy++) for (let dx = 0; dx < w && x + dx < width; dx++) {
const index = 4 * ((y + dy) * width + (x + dx));
Math.random() < .7 && (data[index] = 255 * Math.random(), data[index + 1] = 255 * Math.random(),
data[index + 2] = 255 * Math.random());
}
}
}
//# sourceMappingURL=glitch.js.map