feature: add background

This commit is contained in:
Alikia2x 2024-05-01 16:50:14 +08:00
parent 75422f7aaf
commit 5afdaec8a0
11 changed files with 374 additions and 7 deletions

View File

@ -1,5 +1,6 @@
{
"useTabs": true,
"useTabs": false,
"tabWidth": 4,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,

View File

@ -2,7 +2,7 @@
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"checkJs": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,

View File

@ -28,5 +28,8 @@
"vite": "^5.0.3",
"vitest": "^1.2.0"
},
"type": "module"
"type": "module",
"dependencies": {
"localforage": "^1.10.0"
}
}

View File

@ -4,6 +4,11 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
localforage:
specifier: ^1.10.0
version: 1.10.0
devDependencies:
'@sveltejs/adapter-auto':
specifier: ^3.0.0
@ -1271,6 +1276,10 @@ packages:
engines: {node: '>= 4'}
dev: true
/immediate@3.0.6:
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
dev: false
/import-fresh@3.3.0:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
engines: {node: '>=6'}
@ -1389,6 +1398,12 @@ packages:
type-check: 0.4.0
dev: true
/lie@3.1.1:
resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==}
dependencies:
immediate: 3.0.6
dev: false
/lilconfig@2.1.0:
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
engines: {node: '>=10'}
@ -1402,6 +1417,12 @@ packages:
pkg-types: 1.1.0
dev: true
/localforage@1.10.0:
resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
dependencies:
lie: 3.1.1
dev: false
/locate-character@3.0.0:
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
dev: true

View File

@ -0,0 +1,44 @@
<script lang="ts">
import { processImage } from '$lib/graphics';
import localforage from 'localforage';
// Initialize IndexedDB
localforage.config({
driver: localforage.INDEXEDDB,
name: 'audioDB'
});
export let coverId: string;
let canvas: HTMLCanvasElement;
localforage.getItem(`${coverId}-cover`, function (err, file) {
console.log(file);
console.log(err);
if (file) {
const path = URL.createObjectURL(file as File);
processImage(16, 4, 96, path, canvas);
}
});
</script>
<div class="bg">
<canvas bind:this={canvas}></canvas>
</div>
<style>
.bg {
overflow: hidden;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: -1;
overflow: hidden;
}
canvas {
position: relative;
width: 110%;
height: 110%;
object-fit: cover;
}
</style>

55
src/lib/graphics/index.ts Normal file
View File

@ -0,0 +1,55 @@
import { gaussianBlurHorizontal, gaussianBlurVertical, applyFilter } from "./utils";
export async function processImage(blockSize: number, resolutionFactor:number, radius:number, path: string, canvas: HTMLCanvasElement) {
const ctx = canvas.getContext("2d")!;
const image = new Image();
image.src = path; // Replace with your image path
image.onload = function () {
// Resize image to 1/9 of the original resolution
const resizedWidth = image.width / resolutionFactor;
const resizedHeight = image.height / resolutionFactor;
const num_blocks_x = Math.floor(resizedWidth / blockSize);
const num_blocks_y = Math.floor(resizedHeight / blockSize);
canvas.width = resizedWidth;
canvas.height = resizedHeight;
for (let i = 0; i < num_blocks_y; i++) {
for (let j = 0; j < num_blocks_x; j++) {
const block = document.createElement("canvas");
block.width = blockSize;
block.height = blockSize;
const blockCtx = block.getContext("2d")!;
blockCtx.drawImage(
image,
j * blockSize * resolutionFactor,
i * blockSize * resolutionFactor,
blockSize * resolutionFactor,
blockSize * resolutionFactor,
0,
0,
blockSize,
blockSize
);
applyFilter(block);
ctx.drawImage(block, j * blockSize, i * blockSize);
}
}
// Horizontal Gaussian blur
const imageDataHorizontal = ctx.getImageData(0, 0, canvas.width, canvas.height);
const blurredImageDataHorizontal = gaussianBlurHorizontal(imageDataHorizontal, radius);
// Vertical Gaussian blur
const imageDataVertical = blurredImageDataHorizontal;
const blurredImageData = gaussianBlurVertical(imageDataVertical, radius);
ctx.putImageData(blurredImageData, 0, 0);
};
}

191
src/lib/graphics/utils.js Normal file
View File

@ -0,0 +1,191 @@
// Function to apply filter to the block
export function applyFilter(block) {
let imageData = block.getContext("2d").getImageData(0, 0, block.width, block.height);
let data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
let hsv = rgbToHsv(data[i], data[i + 1], data[i + 2]);
// Mainly adjust saturation and value channels
hsv[1] = Math.min(hsv[1] * 1.05 + 30 * Math.log(hsv[1] / 100 + 1), 100);
hsv[2] = Math.min(hsv[2] * 1.3 + 40 * Math.log(hsv[2] / 100 + 1), 100);
let rgb = hsvToRgb(hsv[0], hsv[1], hsv[2]);
data[i] = rgb[0];
data[i + 1] = rgb[1];
data[i + 2] = rgb[2];
}
block.getContext("2d").putImageData(imageData, 0, 0);
}
// Function to perform horizontal Gaussian blur
export function gaussianBlurHorizontal(imageData, radius) {
const width = imageData.width;
const height = imageData.height;
const newData = new Uint8ClampedArray(imageData.data);
const kernel = generateGaussianKernel(radius);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let r = 0,
g = 0,
b = 0,
a = 0;
for (let kx = -radius; kx <= radius; kx++) {
const pixelX = Math.min(width - 1, Math.max(0, x + kx));
const kernelValue = kernel[kx + radius];
const index = (y * width + pixelX) * 4;
r += imageData.data[index] * kernelValue;
g += imageData.data[index + 1] * kernelValue;
b += imageData.data[index + 2] * kernelValue;
a += imageData.data[index + 3] * kernelValue;
}
const dataIndex = (y * width + x) * 4;
newData[dataIndex] = r;
newData[dataIndex + 1] = g;
newData[dataIndex + 2] = b;
newData[dataIndex + 3] = a;
}
}
return new ImageData(newData, width, height);
}
// Function to perform vertical Gaussian blur
export function gaussianBlurVertical(imageData, radius) {
const width = imageData.width;
const height = imageData.height;
const newData = new Uint8ClampedArray(imageData.data);
const kernel = generateGaussianKernel(radius);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let r = 0,
g = 0,
b = 0,
a = 0;
for (let ky = -radius; ky <= radius; ky++) {
const pixelY = Math.min(height - 1, Math.max(0, y + ky));
const kernelValue = kernel[ky + radius];
const index = (pixelY * width + x) * 4;
r += imageData.data[index] * kernelValue;
g += imageData.data[index + 1] * kernelValue;
b += imageData.data[index + 2] * kernelValue;
a += imageData.data[index + 3] * kernelValue;
}
const dataIndex = (y * width + x) * 4;
newData[dataIndex] = r;
newData[dataIndex + 1] = g;
newData[dataIndex + 2] = b;
newData[dataIndex + 3] = a;
}
}
return new ImageData(newData, width, height);
}
// Function to generate Gaussian kernel
function generateGaussianKernel(radius) {
const size = radius * 2 + 1;
const kernel = [];
const sigma = 0.4 * ((radius - 1) * 0.5 - 1) + 0.8;
for (let i = 0; i < size; i++) {
const x = i - radius;
const exponent = Math.exp(-(x * x) / (2 * sigma * sigma));
kernel[i] = exponent / (Math.sqrt(2 * Math.PI) * sigma);
}
// Normalize the kernel
const sum = kernel.reduce((a, b) => a + b, 0);
return kernel.map((value) => value / sum);
}
// Function to convert RGB to HSV
function rgbToHsv(r, g, b) {
(r /= 255), (g /= 255), (b /= 255);
let max = Math.max(r, g, b),
min = Math.min(r, g, b),
h,
s,
v = max;
let d = max - min;
s = max === 0 ? 0 : d / max;
if (max === min) {
h = 0; // achromatic
} else {
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
return [h * 360, s * 100, v * 100];
}
// Function to convert HSV to RGB
function hsvToRgb(h, s, v) {
h /= 360;
s /= 100;
v /= 100;
let i = Math.floor(h * 6),
f = h * 6 - i,
p = v * (1 - s),
q = v * (1 - f * s),
t = v * (1 - (1 - f) * s),
r,
g,
b;
switch (i % 6) {
case 0:
(r = v), (g = t), (b = p);
break;
case 1:
(r = q), (g = v), (b = p);
break;
case 2:
(r = p), (g = v), (b = t);
break;
case 3:
(r = p), (g = q), (b = v);
break;
case 4:
(r = t), (g = p), (b = v);
break;
case 5:
(r = v), (g = p), (b = q);
break;
}
return [r * 255, g * 255, b * 255];
}

View File

@ -1 +0,0 @@
// place files you want to import through the `$lib` alias in this folder.

View File

@ -0,0 +1 @@
export const ssr = false;

View File

@ -1,2 +1,52 @@
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
<script lang="ts">
import { onMount } from 'svelte';
import localforage from 'localforage';
import Background from '../components/background.svelte';
let audioInput: any;
let coverInput: any;
const audioId = 'd5a2e306-ddea-4fc3-9927-c79dcb3a4071';
// Initialize IndexedDB
localforage.config({
driver: localforage.INDEXEDDB,
name: 'audioDB'
});
onMount(() => {
// Handle audio input change
audioInput.addEventListener('change', function (e: any) {
const file: File = e.target.files[0];
console.log(file.size);
if (file) {
localforage.setItem(audioId + '-file', file);
}
});
coverInput.addEventListener('change', function (e: any) {
const file = e.target.files[0];
if (file) {
localforage.setItem(audioId + '-cover', file);
}
});
return () => {
;
};
});
</script>
<p>
Select Audio File:
<input type="file" bind:this={audioInput} />
</p>
<p>
Select Cover File:
<input type="file" bind:this={coverInput} />
</p>
<input
type="text"
placeholder="Input song id"
id="audioId"
value="d5a2e306-ddea-4fc3-9927-c79dcb3a4071"
style="width: 36ch"
/>
<Background coverId={audioId} />

View File

@ -1,4 +1,5 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
@ -7,7 +8,8 @@ const config = {
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter()
}
},
preprocess: vitePreprocess()
};
export default config;