Dateien nach "frontend" hochladen
This commit is contained in:
@@ -1,33 +1,106 @@
|
||||
import { createCanvas } from 'canvas';
|
||||
// Generates icon-192.png and icon-512.png without external dependencies
|
||||
// Uses a minimal PNG encoder written in pure JS
|
||||
import { writeFileSync } from 'fs';
|
||||
import { deflateSync } from 'zlib';
|
||||
|
||||
function generateIcon(size) {
|
||||
const canvas = createCanvas(size, size);
|
||||
const ctx = canvas.getContext('2d');
|
||||
const r = size * 0.22;
|
||||
function createPNG(size) {
|
||||
const r = Math.round(size * 0.22);
|
||||
|
||||
const grad = ctx.createLinearGradient(0, 0, size, size);
|
||||
grad.addColorStop(0, '#4ecdc4');
|
||||
grad.addColorStop(1, '#ff6b9d');
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(r, 0); ctx.lineTo(size - r, 0);
|
||||
ctx.quadraticCurveTo(size, 0, size, r);
|
||||
ctx.lineTo(size, size - r);
|
||||
ctx.quadraticCurveTo(size, size, size - r, size);
|
||||
ctx.lineTo(r, size); ctx.quadraticCurveTo(0, size, 0, size - r);
|
||||
ctx.lineTo(0, r); ctx.quadraticCurveTo(0, 0, r, 0);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fill();
|
||||
// Draw pixels
|
||||
const pixels = new Uint8Array(size * size * 4);
|
||||
|
||||
ctx.fillStyle = '#0d0d0f';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.font = `bold ${size * 0.52}px serif`;
|
||||
ctx.fillText('⚒', size / 2, size * 0.72);
|
||||
const setPixel = (x, y, R, G, B, A = 255) => {
|
||||
if (x < 0 || x >= size || y < 0 || y >= size) return;
|
||||
const i = (y * size + x) * 4;
|
||||
pixels[i] = R; pixels[i+1] = G; pixels[i+2] = B; pixels[i+3] = A;
|
||||
};
|
||||
|
||||
return canvas.toBuffer('image/png');
|
||||
// Gradient background with rounded corners
|
||||
for (let y = 0; y < size; y++) {
|
||||
for (let x = 0; x < size; x++) {
|
||||
// Rounded corner check
|
||||
const inCornerTL = x < r && y < r && (x-r)**2 + (y-r)**2 > r**2;
|
||||
const inCornerTR = x >= size-r && y < r && (x-(size-r))**2 + (y-r)**2 > r**2;
|
||||
const inCornerBL = x < r && y >= size-r && (x-r)**2 + (y-(size-r))**2 > r**2;
|
||||
const inCornerBR = x >= size-r && y >= size-r && (x-(size-r))**2 + (y-(size-r))**2 > r**2;
|
||||
if (inCornerTL || inCornerTR || inCornerBL || inCornerBR) continue;
|
||||
|
||||
// Linear gradient: top-left #4ecdc4 → bottom-right #ff6b9d
|
||||
const t = (x + y) / (size * 2);
|
||||
const R = Math.round(0x4e + (0xff - 0x4e) * t);
|
||||
const G = Math.round(0xcd + (0x6b - 0xcd) * t);
|
||||
const B = Math.round(0xc4 + (0x9d - 0xc4) * t);
|
||||
setPixel(x, y, R, G, B);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw ⚒ as a simple hammer shape (dark color on gradient)
|
||||
const cx = Math.floor(size / 2);
|
||||
const cy = Math.floor(size / 2);
|
||||
const s = Math.floor(size * 0.18);
|
||||
|
||||
// Hammer head (rectangle)
|
||||
for (let dy = -s; dy <= 0; dy++) {
|
||||
for (let dx = -s; dx <= s; dx++) {
|
||||
setPixel(cx + dx, cy + dy, 13, 13, 15);
|
||||
}
|
||||
}
|
||||
// Handle
|
||||
for (let dy = 0; dy <= s * 2; dy++) {
|
||||
for (let dx = -Math.floor(s*0.25); dx <= Math.floor(s*0.25); dx++) {
|
||||
setPixel(cx + dx, cy + dy, 13, 13, 15);
|
||||
}
|
||||
}
|
||||
|
||||
// Encode as PNG
|
||||
const width = size, height = size;
|
||||
const signature = Buffer.from([137,80,78,71,13,10,26,10]);
|
||||
|
||||
// IHDR
|
||||
const ihdr = Buffer.alloc(13);
|
||||
ihdr.writeUInt32BE(width, 0); ihdr.writeUInt32BE(height, 4);
|
||||
ihdr[8] = 8; ihdr[9] = 2; // 8-bit RGB... wait we need RGBA
|
||||
// Use RGBA (color type 6)
|
||||
ihdr[9] = 6;
|
||||
const ihdrChunk = makeChunk('IHDR', ihdr);
|
||||
|
||||
// IDAT - raw image data with filter bytes
|
||||
const raw = Buffer.alloc(height * (1 + width * 4));
|
||||
for (let y = 0; y < height; y++) {
|
||||
raw[y * (1 + width * 4)] = 0; // filter type None
|
||||
for (let x = 0; x < width; x++) {
|
||||
const si = (y * width + x) * 4;
|
||||
const di = y * (1 + width * 4) + 1 + x * 4;
|
||||
raw[di] = pixels[si];
|
||||
raw[di+1] = pixels[si+1];
|
||||
raw[di+2] = pixels[si+2];
|
||||
raw[di+3] = pixels[si+3] || (pixels[si] || pixels[si+1] || pixels[si+2] ? 255 : 0);
|
||||
}
|
||||
}
|
||||
const compressed = deflateSync(raw);
|
||||
const idatChunk = makeChunk('IDAT', compressed);
|
||||
const iendChunk = makeChunk('IEND', Buffer.alloc(0));
|
||||
|
||||
return Buffer.concat([signature, ihdrChunk, idatChunk, iendChunk]);
|
||||
}
|
||||
|
||||
writeFileSync('public/icon-192.png', generateIcon(192));
|
||||
writeFileSync('public/icon-512.png', generateIcon(512));
|
||||
function makeChunk(type, data) {
|
||||
const len = Buffer.alloc(4); len.writeUInt32BE(data.length);
|
||||
const typeB = Buffer.from(type);
|
||||
const crc = crc32(Buffer.concat([typeB, data]));
|
||||
const crcB = Buffer.alloc(4); crcB.writeUInt32BE(crc >>> 0);
|
||||
return Buffer.concat([len, typeB, data, crcB]);
|
||||
}
|
||||
|
||||
function crc32(buf) {
|
||||
let crc = 0xFFFFFFFF;
|
||||
for (const b of buf) {
|
||||
crc ^= b;
|
||||
for (let i = 0; i < 8; i++) crc = (crc >>> 1) ^ (crc & 1 ? 0xEDB88320 : 0);
|
||||
}
|
||||
return (crc ^ 0xFFFFFFFF) >>> 0;
|
||||
}
|
||||
|
||||
writeFileSync('public/icon-192.png', createPNG(192));
|
||||
writeFileSync('public/icon-512.png', createPNG(512));
|
||||
console.log('Icons generated.');
|
||||
|
||||
Reference in New Issue
Block a user