This commit is contained in:
David Haz
2025-07-12 14:52:40 +03:00
parent d4ff1787eb
commit 62cdbc2dba
7 changed files with 256 additions and 249 deletions

View File

@@ -1,6 +1,6 @@
import code from '@content/Animations/MetallicPaint/MetallicPaint.vue?raw' import code from '@content/Animations/MetallicPaint/MetallicPaint.vue?raw';
import utility from '@content/Animations/MetallicPaint/parseImage.ts?raw' import utility from '@content/Animations/MetallicPaint/parseImage.ts?raw';
import type { CodeObject } from '../../../types/code' import type { CodeObject } from '../../../types/code';
export const metallicPaint: CodeObject = { export const metallicPaint: CodeObject = {
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/MetallicPaint`, cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/MetallicPaint`,
@@ -44,4 +44,4 @@ import { ref, onMounted } from 'vue';
</script>`, </script>`,
code, code,
utility utility
} };

View File

@@ -1,5 +1,5 @@
import code from "@content/Backgrounds/GridMotion/GridMotion.vue?raw"; import code from '@content/Backgrounds/GridMotion/GridMotion.vue?raw';
import type { CodeObject } from "../../../types/code"; import type { CodeObject } from '../../../types/code';
export const gridMotion: CodeObject = { export const gridMotion: CodeObject = {
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/GridMotion`, cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/GridMotion`,
@@ -17,5 +17,5 @@ export const gridMotion: CodeObject = {
const numberOfImages = 30; const numberOfImages = 30;
const images = Array.from({ length: numberOfImages }, () => imageUrl); const images = Array.from({ length: numberOfImages }, () => imageUrl);
</script>`, </script>`,
code, code
}; };

View File

@@ -3,20 +3,20 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue' import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
interface ShaderParams { interface ShaderParams {
patternScale: number patternScale: number;
refraction: number refraction: number;
edge: number edge: number;
patternBlur: number patternBlur: number;
liquid: number liquid: number;
speed: number speed: number;
} }
interface Props { interface Props {
imageData: ImageData imageData: ImageData;
params?: ShaderParams params?: ShaderParams;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@@ -28,14 +28,14 @@ const props = withDefaults(defineProps<Props>(), {
liquid: 0.07, liquid: 0.07,
speed: 0.3 speed: 0.3
}) })
}) });
const canvasRef = ref<HTMLCanvasElement | null>(null) const canvasRef = ref<HTMLCanvasElement | null>(null);
const gl = ref<WebGL2RenderingContext | null>(null) const gl = ref<WebGL2RenderingContext | null>(null);
const uniforms = ref<Record<string, WebGLUniformLocation>>({}) const uniforms = ref<Record<string, WebGLUniformLocation>>({});
const totalAnimationTime = ref(0) const totalAnimationTime = ref(0);
const lastRenderTime = ref(0) const lastRenderTime = ref(0);
const animationId = ref<number>() const animationId = ref<number>();
const vertexShaderSource = `#version 300 es const vertexShaderSource = `#version 300 es
precision mediump float; precision mediump float;
@@ -46,7 +46,7 @@ out vec2 vUv;
void main() { void main() {
vUv = .5 * (a_position + 1.); vUv = .5 * (a_position + 1.);
gl_Position = vec4(a_position, 0.0, 1.0); gl_Position = vec4(a_position, 0.0, 1.0);
}` }`;
const liquidFragSource = `#version 300 es const liquidFragSource = `#version 300 es
precision mediump float; precision mediump float;
@@ -200,123 +200,123 @@ void main() {
color *= opacity; color *= opacity;
fragColor = vec4(color, opacity); fragColor = vec4(color, opacity);
} }
` `;
function updateUniforms() { function updateUniforms() {
if (!gl.value || !uniforms.value) return if (!gl.value || !uniforms.value) return;
gl.value.uniform1f(uniforms.value.u_edge, props.params.edge) gl.value.uniform1f(uniforms.value.u_edge, props.params.edge);
gl.value.uniform1f(uniforms.value.u_patternBlur, props.params.patternBlur) gl.value.uniform1f(uniforms.value.u_patternBlur, props.params.patternBlur);
gl.value.uniform1f(uniforms.value.u_time, 0) gl.value.uniform1f(uniforms.value.u_time, 0);
gl.value.uniform1f(uniforms.value.u_patternScale, props.params.patternScale) gl.value.uniform1f(uniforms.value.u_patternScale, props.params.patternScale);
gl.value.uniform1f(uniforms.value.u_refraction, props.params.refraction) gl.value.uniform1f(uniforms.value.u_refraction, props.params.refraction);
gl.value.uniform1f(uniforms.value.u_liquid, props.params.liquid) gl.value.uniform1f(uniforms.value.u_liquid, props.params.liquid);
} }
function createShader(gl: WebGL2RenderingContext, sourceCode: string, type: number) { function createShader(gl: WebGL2RenderingContext, sourceCode: string, type: number) {
const shader = gl.createShader(type) const shader = gl.createShader(type);
if (!shader) { if (!shader) {
return null return null;
} }
gl.shaderSource(shader, sourceCode) gl.shaderSource(shader, sourceCode);
gl.compileShader(shader) gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader)) console.error('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
gl.deleteShader(shader) gl.deleteShader(shader);
return null return null;
} }
return shader return shader;
} }
function getUniforms(program: WebGLProgram, gl: WebGL2RenderingContext) { function getUniforms(program: WebGLProgram, gl: WebGL2RenderingContext) {
const uniformsObj: Record<string, WebGLUniformLocation> = {} const uniformsObj: Record<string, WebGLUniformLocation> = {};
const uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS) const uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (let i = 0; i < uniformCount; i++) { for (let i = 0; i < uniformCount; i++) {
const uniformName = gl.getActiveUniform(program, i)?.name const uniformName = gl.getActiveUniform(program, i)?.name;
if (!uniformName) continue if (!uniformName) continue;
uniformsObj[uniformName] = gl.getUniformLocation(program, uniformName) as WebGLUniformLocation uniformsObj[uniformName] = gl.getUniformLocation(program, uniformName) as WebGLUniformLocation;
} }
return uniformsObj return uniformsObj;
} }
function initShader() { function initShader() {
const canvas = canvasRef.value const canvas = canvasRef.value;
const glContext = canvas?.getContext('webgl2', { const glContext = canvas?.getContext('webgl2', {
antialias: true, antialias: true,
alpha: true alpha: true
}) });
if (!canvas || !glContext) { if (!canvas || !glContext) {
return return;
} }
const vertexShader = createShader(glContext, vertexShaderSource, glContext.VERTEX_SHADER) const vertexShader = createShader(glContext, vertexShaderSource, glContext.VERTEX_SHADER);
const fragmentShader = createShader(glContext, liquidFragSource, glContext.FRAGMENT_SHADER) const fragmentShader = createShader(glContext, liquidFragSource, glContext.FRAGMENT_SHADER);
const program = glContext.createProgram() const program = glContext.createProgram();
if (!program || !vertexShader || !fragmentShader) { if (!program || !vertexShader || !fragmentShader) {
return return;
} }
glContext.attachShader(program, vertexShader) glContext.attachShader(program, vertexShader);
glContext.attachShader(program, fragmentShader) glContext.attachShader(program, fragmentShader);
glContext.linkProgram(program) glContext.linkProgram(program);
if (!glContext.getProgramParameter(program, glContext.LINK_STATUS)) { if (!glContext.getProgramParameter(program, glContext.LINK_STATUS)) {
console.error('Unable to initialize the shader program: ' + glContext.getProgramInfoLog(program)) console.error('Unable to initialize the shader program: ' + glContext.getProgramInfoLog(program));
return null return null;
} }
const uniformsObj = getUniforms(program, glContext) const uniformsObj = getUniforms(program, glContext);
uniforms.value = uniformsObj uniforms.value = uniformsObj;
const vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]) const vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
const vertexBuffer = glContext.createBuffer() const vertexBuffer = glContext.createBuffer();
glContext.bindBuffer(glContext.ARRAY_BUFFER, vertexBuffer) glContext.bindBuffer(glContext.ARRAY_BUFFER, vertexBuffer);
glContext.bufferData(glContext.ARRAY_BUFFER, vertices, glContext.STATIC_DRAW) glContext.bufferData(glContext.ARRAY_BUFFER, vertices, glContext.STATIC_DRAW);
glContext.useProgram(program) glContext.useProgram(program);
const positionLocation = glContext.getAttribLocation(program, 'a_position') const positionLocation = glContext.getAttribLocation(program, 'a_position');
glContext.enableVertexAttribArray(positionLocation) glContext.enableVertexAttribArray(positionLocation);
glContext.bindBuffer(glContext.ARRAY_BUFFER, vertexBuffer) glContext.bindBuffer(glContext.ARRAY_BUFFER, vertexBuffer);
glContext.vertexAttribPointer(positionLocation, 2, glContext.FLOAT, false, 0, 0) glContext.vertexAttribPointer(positionLocation, 2, glContext.FLOAT, false, 0, 0);
gl.value = glContext gl.value = glContext;
} }
function resizeCanvas() { function resizeCanvas() {
if (!canvasRef.value || !gl.value || !uniforms.value || !props.imageData) return if (!canvasRef.value || !gl.value || !uniforms.value || !props.imageData) return;
const imgRatio = props.imageData.width / props.imageData.height const imgRatio = props.imageData.width / props.imageData.height;
gl.value.uniform1f(uniforms.value.u_img_ratio, imgRatio) gl.value.uniform1f(uniforms.value.u_img_ratio, imgRatio);
const side = 1000 const side = 1000;
canvasRef.value.width = side * devicePixelRatio canvasRef.value.width = side * devicePixelRatio;
canvasRef.value.height = side * devicePixelRatio canvasRef.value.height = side * devicePixelRatio;
gl.value.viewport(0, 0, canvasRef.value.height, canvasRef.value.height) gl.value.viewport(0, 0, canvasRef.value.height, canvasRef.value.height);
gl.value.uniform1f(uniforms.value.u_ratio, 1) gl.value.uniform1f(uniforms.value.u_ratio, 1);
gl.value.uniform1f(uniforms.value.u_img_ratio, imgRatio) gl.value.uniform1f(uniforms.value.u_img_ratio, imgRatio);
} }
function setupTexture() { function setupTexture() {
if (!gl.value || !uniforms.value) return if (!gl.value || !uniforms.value) return;
const existingTexture = gl.value.getParameter(gl.value.TEXTURE_BINDING_2D) const existingTexture = gl.value.getParameter(gl.value.TEXTURE_BINDING_2D);
if (existingTexture) { if (existingTexture) {
gl.value.deleteTexture(existingTexture) gl.value.deleteTexture(existingTexture);
} }
const imageTexture = gl.value.createTexture() const imageTexture = gl.value.createTexture();
gl.value.activeTexture(gl.value.TEXTURE0) gl.value.activeTexture(gl.value.TEXTURE0);
gl.value.bindTexture(gl.value.TEXTURE_2D, imageTexture) gl.value.bindTexture(gl.value.TEXTURE_2D, imageTexture);
gl.value.texParameteri(gl.value.TEXTURE_2D, gl.value.TEXTURE_MIN_FILTER, gl.value.LINEAR) gl.value.texParameteri(gl.value.TEXTURE_2D, gl.value.TEXTURE_MIN_FILTER, gl.value.LINEAR);
gl.value.texParameteri(gl.value.TEXTURE_2D, gl.value.TEXTURE_MAG_FILTER, gl.value.LINEAR) gl.value.texParameteri(gl.value.TEXTURE_2D, gl.value.TEXTURE_MAG_FILTER, gl.value.LINEAR);
gl.value.texParameteri(gl.value.TEXTURE_2D, gl.value.TEXTURE_WRAP_S, gl.value.CLAMP_TO_EDGE) gl.value.texParameteri(gl.value.TEXTURE_2D, gl.value.TEXTURE_WRAP_S, gl.value.CLAMP_TO_EDGE);
gl.value.texParameteri(gl.value.TEXTURE_2D, gl.value.TEXTURE_WRAP_T, gl.value.CLAMP_TO_EDGE) gl.value.texParameteri(gl.value.TEXTURE_2D, gl.value.TEXTURE_WRAP_T, gl.value.CLAMP_TO_EDGE);
gl.value.pixelStorei(gl.value.UNPACK_ALIGNMENT, 1) gl.value.pixelStorei(gl.value.UNPACK_ALIGNMENT, 1);
try { try {
gl.value.texImage2D( gl.value.texImage2D(
@@ -329,58 +329,66 @@ function setupTexture() {
gl.value.RGBA, gl.value.RGBA,
gl.value.UNSIGNED_BYTE, gl.value.UNSIGNED_BYTE,
props.imageData?.data props.imageData?.data
) );
gl.value.uniform1i(uniforms.value.u_image_texture, 0) gl.value.uniform1i(uniforms.value.u_image_texture, 0);
} catch (e) { } catch (e) {
console.error('Error uploading texture:', e) console.error('Error uploading texture:', e);
} }
} }
function render(currentTime: number) { function render(currentTime: number) {
if (!gl.value || !uniforms.value) return if (!gl.value || !uniforms.value) return;
const deltaTime = currentTime - lastRenderTime.value const deltaTime = currentTime - lastRenderTime.value;
lastRenderTime.value = currentTime lastRenderTime.value = currentTime;
totalAnimationTime.value += deltaTime * props.params.speed totalAnimationTime.value += deltaTime * props.params.speed;
gl.value.uniform1f(uniforms.value.u_time, totalAnimationTime.value) gl.value.uniform1f(uniforms.value.u_time, totalAnimationTime.value);
gl.value.drawArrays(gl.value.TRIANGLE_STRIP, 0, 4) gl.value.drawArrays(gl.value.TRIANGLE_STRIP, 0, 4);
animationId.value = requestAnimationFrame(render) animationId.value = requestAnimationFrame(render);
} }
function startAnimation() { function startAnimation() {
if (animationId.value) { if (animationId.value) {
cancelAnimationFrame(animationId.value) cancelAnimationFrame(animationId.value);
} }
lastRenderTime.value = performance.now() lastRenderTime.value = performance.now();
animationId.value = requestAnimationFrame(render) animationId.value = requestAnimationFrame(render);
} }
onMounted(async () => { onMounted(async () => {
await nextTick() await nextTick();
initShader() initShader();
updateUniforms() updateUniforms();
resizeCanvas() resizeCanvas();
setupTexture() setupTexture();
startAnimation() startAnimation();
window.addEventListener('resize', resizeCanvas) window.addEventListener('resize', resizeCanvas);
}) });
onUnmounted(() => { onUnmounted(() => {
if (animationId.value) { if (animationId.value) {
cancelAnimationFrame(animationId.value) cancelAnimationFrame(animationId.value);
} }
window.removeEventListener('resize', resizeCanvas) window.removeEventListener('resize', resizeCanvas);
}) });
watch(() => props.params, () => { watch(
updateUniforms() () => props.params,
}, { deep: true }) () => {
updateUniforms();
},
{ deep: true }
);
watch(() => props.imageData, () => { watch(
setupTexture() () => props.imageData,
resizeCanvas() () => {
}, { deep: true }) setupTexture();
resizeCanvas();
},
{ deep: true }
);
</script> </script>

View File

@@ -1,97 +1,97 @@
export function parseImage(file: File): Promise<{ imageData: ImageData; pngBlob: Blob }> { export function parseImage(file: File): Promise<{ imageData: ImageData; pngBlob: Blob }> {
const canvas = document.createElement('canvas') const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d') const ctx = canvas.getContext('2d');
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!file || !ctx) { if (!file || !ctx) {
reject(new Error('Invalid file or context')) reject(new Error('Invalid file or context'));
return return;
} }
const img = new Image() const img = new Image();
img.crossOrigin = 'anonymous' img.crossOrigin = 'anonymous';
img.onload = function () { img.onload = function () {
if (file.type === 'image/svg+xml') { if (file.type === 'image/svg+xml') {
img.width = 1000 img.width = 1000;
img.height = 1000 img.height = 1000;
} }
const MAX_SIZE = 1000 const MAX_SIZE = 1000;
const MIN_SIZE = 500 const MIN_SIZE = 500;
let width = img.naturalWidth let width = img.naturalWidth;
let height = img.naturalHeight let height = img.naturalHeight;
if (width > MAX_SIZE || height > MAX_SIZE || width < MIN_SIZE || height < MIN_SIZE) { if (width > MAX_SIZE || height > MAX_SIZE || width < MIN_SIZE || height < MIN_SIZE) {
if (width > height) { if (width > height) {
if (width > MAX_SIZE) { if (width > MAX_SIZE) {
height = Math.round((height * MAX_SIZE) / width) height = Math.round((height * MAX_SIZE) / width);
width = MAX_SIZE width = MAX_SIZE;
} else if (width < MIN_SIZE) { } else if (width < MIN_SIZE) {
height = Math.round((height * MIN_SIZE) / width) height = Math.round((height * MIN_SIZE) / width);
width = MIN_SIZE width = MIN_SIZE;
} }
} else { } else {
if (height > MAX_SIZE) { if (height > MAX_SIZE) {
width = Math.round((width * MAX_SIZE) / height) width = Math.round((width * MAX_SIZE) / height);
height = MAX_SIZE height = MAX_SIZE;
} else if (height < MIN_SIZE) { } else if (height < MIN_SIZE) {
width = Math.round((width * MIN_SIZE) / height) width = Math.round((width * MIN_SIZE) / height);
height = MIN_SIZE height = MIN_SIZE;
} }
} }
} }
canvas.width = width canvas.width = width;
canvas.height = height canvas.height = height;
const shapeCanvas = document.createElement('canvas') const shapeCanvas = document.createElement('canvas');
shapeCanvas.width = width shapeCanvas.width = width;
shapeCanvas.height = height shapeCanvas.height = height;
const shapeCtx = shapeCanvas.getContext('2d')! const shapeCtx = shapeCanvas.getContext('2d')!;
shapeCtx.drawImage(img, 0, 0, width, height) shapeCtx.drawImage(img, 0, 0, width, height);
const shapeImageData = shapeCtx.getImageData(0, 0, width, height) const shapeImageData = shapeCtx.getImageData(0, 0, width, height);
const data = shapeImageData.data const data = shapeImageData.data;
const shapeMask = new Array(width * height).fill(false) const shapeMask = new Array(width * height).fill(false);
for (let y = 0; y < height; y++) { for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) { for (let x = 0; x < width; x++) {
const idx4 = (y * width + x) * 4 const idx4 = (y * width + x) * 4;
const r = data[idx4] const r = data[idx4];
const g = data[idx4 + 1] const g = data[idx4 + 1];
const b = data[idx4 + 2] const b = data[idx4 + 2];
const a = data[idx4 + 3] const a = data[idx4 + 3];
shapeMask[y * width + x] = !((r === 255 && g === 255 && b === 255 && a === 255) || a === 0) shapeMask[y * width + x] = !((r === 255 && g === 255 && b === 255 && a === 255) || a === 0);
} }
} }
function inside(x: number, y: number) { function inside(x: number, y: number) {
if (x < 0 || x >= width || y < 0 || y >= height) return false if (x < 0 || x >= width || y < 0 || y >= height) return false;
return shapeMask[y * width + x] return shapeMask[y * width + x];
} }
const boundaryMask = new Array(width * height).fill(false) const boundaryMask = new Array(width * height).fill(false);
for (let y = 0; y < height; y++) { for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) { for (let x = 0; x < width; x++) {
const idx = y * width + x const idx = y * width + x;
if (!shapeMask[idx]) continue if (!shapeMask[idx]) continue;
let isBoundary = false let isBoundary = false;
for (let ny = y - 1; ny <= y + 1 && !isBoundary; ny++) { for (let ny = y - 1; ny <= y + 1 && !isBoundary; ny++) {
for (let nx = x - 1; nx <= x + 1 && !isBoundary; nx++) { for (let nx = x - 1; nx <= x + 1 && !isBoundary; nx++) {
if (!inside(nx, ny)) { if (!inside(nx, ny)) {
isBoundary = true isBoundary = true;
} }
} }
} }
if (isBoundary) { if (isBoundary) {
boundaryMask[idx] = true boundaryMask[idx] = true;
} }
} }
} }
const interiorMask = new Array(width * height).fill(false) const interiorMask = new Array(width * height).fill(false);
for (let y = 1; y < height - 1; y++) { for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) { for (let x = 1; x < width - 1; x++) {
const idx = y * width + x const idx = y * width + x;
if ( if (
shapeMask[idx] && shapeMask[idx] &&
shapeMask[idx - 1] && shapeMask[idx - 1] &&
@@ -99,82 +99,79 @@ export function parseImage(file: File): Promise<{ imageData: ImageData; pngBlob:
shapeMask[idx - width] && shapeMask[idx - width] &&
shapeMask[idx + width] shapeMask[idx + width]
) { ) {
interiorMask[idx] = true interiorMask[idx] = true;
} }
} }
} }
const u = new Float32Array(width * height).fill(0) const u = new Float32Array(width * height).fill(0);
const newU = new Float32Array(width * height).fill(0) const newU = new Float32Array(width * height).fill(0);
const C = 0.01 const C = 0.01;
const ITERATIONS = 300 const ITERATIONS = 300;
function getU(x: number, y: number, arr: Float32Array) { function getU(x: number, y: number, arr: Float32Array) {
if (x < 0 || x >= width || y < 0 || y >= height) return 0 if (x < 0 || x >= width || y < 0 || y >= height) return 0;
if (!shapeMask[y * width + x]) return 0 if (!shapeMask[y * width + x]) return 0;
return arr[y * width + x] return arr[y * width + x];
} }
for (let iter = 0; iter < ITERATIONS; iter++) { for (let iter = 0; iter < ITERATIONS; iter++) {
for (let y = 0; y < height; y++) { for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) { for (let x = 0; x < width; x++) {
const idx = y * width + x const idx = y * width + x;
if (!shapeMask[idx] || boundaryMask[idx]) { if (!shapeMask[idx] || boundaryMask[idx]) {
newU[idx] = 0 newU[idx] = 0;
continue continue;
} }
const sumN = getU(x + 1, y, u) + getU(x - 1, y, u) + getU(x, y + 1, u) + getU(x, y - 1, u) const sumN = getU(x + 1, y, u) + getU(x - 1, y, u) + getU(x, y + 1, u) + getU(x, y - 1, u);
newU[idx] = (C + sumN) / 4 newU[idx] = (C + sumN) / 4;
} }
} }
u.set(newU) u.set(newU);
} }
let maxVal = 0 let maxVal = 0;
for (let i = 0; i < width * height; i++) { for (let i = 0; i < width * height; i++) {
if (u[i] > maxVal) maxVal = u[i] if (u[i] > maxVal) maxVal = u[i];
} }
const alpha = 2.0 const alpha = 2.0;
const outImg = ctx.createImageData(width, height) const outImg = ctx.createImageData(width, height);
for (let y = 0; y < height; y++) { for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) { for (let x = 0; x < width; x++) {
const idx = y * width + x const idx = y * width + x;
const px = idx * 4 const px = idx * 4;
if (!shapeMask[idx]) { if (!shapeMask[idx]) {
outImg.data[px] = 255 outImg.data[px] = 255;
outImg.data[px + 1] = 255 outImg.data[px + 1] = 255;
outImg.data[px + 2] = 255 outImg.data[px + 2] = 255;
outImg.data[px + 3] = 255 outImg.data[px + 3] = 255;
} else { } else {
const raw = u[idx] / maxVal const raw = u[idx] / maxVal;
const remapped = Math.pow(raw, alpha) const remapped = Math.pow(raw, alpha);
const gray = 255 * (1 - remapped) const gray = 255 * (1 - remapped);
outImg.data[px] = gray outImg.data[px] = gray;
outImg.data[px + 1] = gray outImg.data[px + 1] = gray;
outImg.data[px + 2] = gray outImg.data[px + 2] = gray;
outImg.data[px + 3] = 255 outImg.data[px + 3] = 255;
} }
} }
} }
ctx.putImageData(outImg, 0, 0) ctx.putImageData(outImg, 0, 0);
canvas.toBlob( canvas.toBlob(blob => {
blob => { if (!blob) {
if (!blob) { reject(new Error('Failed to create PNG blob'));
reject(new Error('Failed to create PNG blob')) return;
return }
} resolve({
resolve({ imageData: outImg,
imageData: outImg, pngBlob: blob
pngBlob: blob });
}) }, 'image/png');
}, };
'image/png'
)
}
img.onerror = () => reject(new Error('Failed to load image')) img.onerror = () => reject(new Error('Failed to load image'));
img.src = URL.createObjectURL(file) img.src = URL.createObjectURL(file);
}) });
} }

View File

@@ -546,4 +546,4 @@
.drawer-header { .drawer-header {
padding: 0 1em; padding: 0 1em;
} }
} }

View File

@@ -89,53 +89,55 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue';
import TabbedLayout from '../../components/common/TabbedLayout.vue' import TabbedLayout from '../../components/common/TabbedLayout.vue';
import PropTable from '../../components/common/PropTable.vue' import PropTable from '../../components/common/PropTable.vue';
import CliInstallation from '../../components/code/CliInstallation.vue' import CliInstallation from '../../components/code/CliInstallation.vue';
import CodeExample from '../../components/code/CodeExample.vue' import CodeExample from '../../components/code/CodeExample.vue';
import Customize from '../../components/common/Customize.vue' import Customize from '../../components/common/Customize.vue';
import PreviewSlider from '../../components/common/PreviewSlider.vue' import PreviewSlider from '../../components/common/PreviewSlider.vue';
import MetallicPaint from '../../content/Animations/MetallicPaint/MetallicPaint.vue' import MetallicPaint from '../../content/Animations/MetallicPaint/MetallicPaint.vue';
import { metallicPaint } from '@/constants/code/Animations/metallicPaintCode' import { metallicPaint } from '@/constants/code/Animations/metallicPaintCode';
import { parseImage } from '../../content/Animations/MetallicPaint/parseImage' import { parseImage } from '../../content/Animations/MetallicPaint/parseImage';
import { useForceRerender } from '@/composables/useForceRerender' import { useForceRerender } from '@/composables/useForceRerender';
import logo from '@/assets/logos/vue-bits-logo-small-dark.svg' import logo from '@/assets/logos/vue-bits-logo-small-dark.svg';
const imageData = ref<ImageData | null>(null) const imageData = ref<ImageData | null>(null);
const edge = ref(0) const edge = ref(0);
const patternScale = ref(2) const patternScale = ref(2);
const refraction = ref(0.015) const refraction = ref(0.015);
const patternBlur = ref(0.005) const patternBlur = ref(0.005);
const liquid = ref(0.07) const liquid = ref(0.07);
const speed = ref(0.3) const speed = ref(0.3);
const { rerenderKey, forceRerender } = useForceRerender() const { rerenderKey, forceRerender } = useForceRerender();
const propData = [ const propData = [
{ {
name: 'imageData', name: 'imageData',
type: 'ImageData', type: 'ImageData',
default: 'none (required)', default: 'none (required)',
description: 'The processed image data generated from the parseImage utility. This image data is used by the shader to create the liquid paper effect.' description:
'The processed image data generated from the parseImage utility. This image data is used by the shader to create the liquid paper effect.'
}, },
{ {
name: 'params', name: 'params',
type: 'ShaderParams', type: 'ShaderParams',
default: '', default: '',
description: 'An object to configure the shader effect. Properties include: patternScale, refraction, edge, patternBlur, liquid, speed' description:
'An object to configure the shader effect. Properties include: patternScale, refraction, edge, patternBlur, liquid, speed'
} }
] ];
onMounted(async () => { onMounted(async () => {
try { try {
const response = await fetch(logo) const response = await fetch(logo);
const blob = await response.blob() const blob = await response.blob();
const file = new File([blob], 'default.png', { type: blob.type }) const file = new File([blob], 'default.png', { type: blob.type });
const { imageData: processedImageData } = await parseImage(file) const { imageData: processedImageData } = await parseImage(file);
imageData.value = processedImageData imageData.value = processedImageData;
} catch (err) { } catch (err) {
console.error('Error loading default image:', err) console.error('Error loading default image:', err);
} }
}) });
</script> </script>

View File

@@ -55,4 +55,4 @@ const images = Array.from({ length: numberOfImages }, () => imageUrl);
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
} }
</style> </style>