107 lines
3.0 KiB
TypeScript
107 lines
3.0 KiB
TypeScript
import { createPixelImage, type PixelImage } from './types';
|
|
import { hslToRgb } from './palette';
|
|
import { mulberry32 } from './pixel-fx';
|
|
|
|
/** Радужный спектр: оттенок 0..360 вдоль выбранной оси. */
|
|
export function colorSpectrum(
|
|
width: number,
|
|
height: number,
|
|
direction: 'horizontal' | 'vertical',
|
|
saturationPercent: number,
|
|
lightnessPercent: number
|
|
): PixelImage {
|
|
const out = createPixelImage(width, height);
|
|
for (let y = 0; y < height; y++) {
|
|
for (let x = 0; x < width; x++) {
|
|
const t = direction === 'vertical' ? y / Math.max(1, height - 1) : x / Math.max(1, width - 1);
|
|
const { r, g, b } = hslToRgb({
|
|
h: t * 360,
|
|
s: saturationPercent / 100,
|
|
l: lightnessPercent / 100
|
|
});
|
|
const di = (y * width + x) * 4;
|
|
out.data[di] = r;
|
|
out.data[di + 1] = g;
|
|
out.data[di + 2] = b;
|
|
out.data[di + 3] = 255;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** Случайные яркие блоки: детерминировано по seed. */
|
|
export function randomColorBlocks(
|
|
width: number,
|
|
height: number,
|
|
blockSize: number,
|
|
seed: number
|
|
): PixelImage {
|
|
const bs = Math.max(1, Math.round(blockSize));
|
|
const out = createPixelImage(width, height);
|
|
const rng = mulberry32(seed);
|
|
for (let by = 0; by < height; by += bs) {
|
|
for (let bx = 0; bx < width; bx += bs) {
|
|
const { r, g, b } = hslToRgb({
|
|
h: rng() * 360,
|
|
s: 0.65 + rng() * 0.35,
|
|
l: 0.45 + rng() * 0.25
|
|
});
|
|
const yMax = Math.min(by + bs, height);
|
|
const xMax = Math.min(bx + bs, width);
|
|
for (let y = by; y < yMax; y++) {
|
|
for (let x = bx; x < xMax; x++) {
|
|
const di = (y * width + x) * 4;
|
|
out.data[di] = r;
|
|
out.data[di + 1] = g;
|
|
out.data[di + 2] = b;
|
|
out.data[di + 3] = 255;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function lineMask(length: number, divisions: number, lineWidth: number): Uint8Array {
|
|
const mask = new Uint8Array(length);
|
|
const lw = Math.max(1, Math.round(lineWidth));
|
|
for (let i = 0; i <= divisions; i++) {
|
|
const start = Math.round((i * length) / divisions);
|
|
for (let x = start; x < Math.min(start + lw, length); x++) mask[x] = 1;
|
|
}
|
|
return mask;
|
|
}
|
|
|
|
/** Сетка линий на прозрачном или белом фоне. */
|
|
export function drawGrid(
|
|
width: number,
|
|
height: number,
|
|
cols: number,
|
|
rows: number,
|
|
lineWidth: number,
|
|
colorHex: string,
|
|
transparentBg: boolean
|
|
): PixelImage {
|
|
const out = createPixelImage(width, height);
|
|
if (!transparentBg) out.data.fill(255);
|
|
|
|
const m = /^#([0-9a-f]{6})$/i.exec(colorHex.trim());
|
|
const r = m ? parseInt(m[1].slice(0, 2), 16) : 0;
|
|
const g = m ? parseInt(m[1].slice(2, 4), 16) : 0;
|
|
const b = m ? parseInt(m[1].slice(4, 6), 16) : 0;
|
|
|
|
const colMask = lineMask(width, Math.max(1, cols), lineWidth);
|
|
const rowMask = lineMask(height, Math.max(1, rows), lineWidth);
|
|
for (let y = 0; y < height; y++) {
|
|
for (let x = 0; x < width; x++) {
|
|
if (!colMask[x] && !rowMask[y]) continue;
|
|
const di = (y * width + x) * 4;
|
|
out.data[di] = r;
|
|
out.data[di + 1] = g;
|
|
out.data[di + 2] = b;
|
|
out.data[di + 3] = 255;
|
|
}
|
|
}
|
|
return out;
|
|
}
|