/**
 * pixelEditorUtils.ts — Drawing algorithms for the pixel editor.
 * Bresenham line, midpoint ellipse, flood fill, selection, brush shapes,
 * pressure-aware blending, dithering, gradient, symmetry etc.
 */
const DEFAULT_BRUSH = {
    size: 1,
    shape: 'square',
    opacity: 100,
    flow: 100,
    spacing: 25,
    pressureSize: true,
    pressureOpacity: false,
    hardness: 100,
};
/** Read a pixel from ImageData at (x,y). Returns [r,g,b,a]. */
function getPixel(data, x, y) {
    if (x < 0 || y < 0 || x >= data.width || y >= data.height)
        return [0, 0, 0, 0];
    const i = (y * data.width + x) * 4;
    return [data.data[i], data.data[i + 1], data.data[i + 2], data.data[i + 3]];
}
/** Set a pixel in ImageData at (x,y). */
function setPixel(data, x, y, color) {
    if (x < 0 || y < 0 || x >= data.width || y >= data.height)
        return;
    const i = (y * data.width + x) * 4;
    data.data[i] = color[0];
    data.data[i + 1] = color[1];
    data.data[i + 2] = color[2];
    data.data[i + 3] = color[3];
}
/** Blend a pixel onto ImageData at (x,y) with alpha compositing. */
function blendPixel(data, x, y, color, strength = 1) {
    if (x < 0 || y < 0 || x >= data.width || y >= data.height)
        return;
    const i = (y * data.width + x) * 4;
    const sa = (color[3] / 255) * strength;
    if (sa <= 0)
        return;
    const da = data.data[i + 3] / 255;
    const oa = sa + da * (1 - sa);
    if (oa <= 0)
        return;
    data.data[i] = Math.round((color[0] * sa + data.data[i] * da * (1 - sa)) / oa);
    data.data[i + 1] = Math.round((color[1] * sa + data.data[i + 1] * da * (1 - sa)) / oa);
    data.data[i + 2] = Math.round((color[2] * sa + data.data[i + 2] * da * (1 - sa)) / oa);
    data.data[i + 3] = Math.round(oa * 255);
}
/** Generate brush mask: returns array of {dx, dy, strength} offsets. */
function getBrushMask(cfg, pressure = 0.5) {
    const sz = cfg.pressureSize ? Math.max(1, Math.round(cfg.size * pressure)) : cfg.size;
    const half = Math.floor(sz / 2);
    const flowMul = cfg.flow / 100;
    const opaMul = (cfg.opacity / 100) * (cfg.pressureOpacity ? pressure : 1);
    const mask = [];
    for (let dy = -half; dy < sz - half; dy++) {
        for (let dx = -half; dx < sz - half; dx++) {
            let inBrush = false;
            let dist = 0;
            switch (cfg.shape) {
                case 'square':
                    inBrush = true;
                    dist = Math.max(Math.abs(dx), Math.abs(dy)) / Math.max(half, 0.5);
                    break;
                case 'circle': {
                    const r = Math.sqrt(dx * dx + dy * dy);
                    inBrush = r <= half + 0.5;
                    dist = half > 0 ? r / half : 0;
                    break;
                }
                case 'diamond':
                    inBrush = Math.abs(dx) + Math.abs(dy) <= half;
                    dist = half > 0 ? (Math.abs(dx) + Math.abs(dy)) / half : 0;
                    break;
                case 'spray':
                    inBrush = Math.random() < 0.3 && Math.sqrt(dx * dx + dy * dy) <= half + 0.5;
                    dist = 0;
                    break;
            }
            if (!inBrush)
                continue;
            // hardness: 100 = hard edge, 0 = soft falloff
            const hardFactor = cfg.hardness / 100;
            const edge = dist > hardFactor ? Math.max(0, 1 - (dist - hardFactor) / (1 - hardFactor + 0.001)) : 1;
            mask.push({ dx, dy, strength: edge * flowMul * opaMul });
        }
    }
    return mask;
}
/** Apply brush stamp at (x,y) with all brush settings. */
function applyBrushStamp(data, x, y, color, cfg, pressure = 0.5, erase = false) {
    const mask = getBrushMask(cfg, pressure);
    for (const { dx, dy, strength } of mask) {
        const gx = x + dx, gy = y + dy;
        if (gx < 0 || gy < 0 || gx >= data.width || gy >= data.height)
            continue;
        if (erase) {
            const cur = getPixel(data, gx, gy);
            const newA = Math.max(0, cur[3] - Math.round(255 * strength));
            setPixel(data, gx, gy, [cur[0], cur[1], cur[2], newA]);
        }
        else {
            blendPixel(data, gx, gy, color, strength);
        }
    }
}
/** Get symmetry points for a coordinate. */
function getSymmetryPoints(x, y, w, h, mode) {
    const pts = [{ x, y }];
    switch (mode) {
        case 'h':
            pts.push({ x: w - 1 - x, y });
            break;
        case 'v':
            pts.push({ x, y: h - 1 - y });
            break;
        case 'both':
            pts.push({ x: w - 1 - x, y }, { x, y: h - 1 - y }, { x: w - 1 - x, y: h - 1 - y });
            break;
        case 'radial4': {
            const cx = w / 2, cy = h / 2;
            const dx = x - cx, dy = y - cy;
            pts.push({ x: Math.round(cx - dy), y: Math.round(cy + dx) }, { x: Math.round(cx - dx), y: Math.round(cy - dy) }, { x: Math.round(cx + dy), y: Math.round(cy - dx) });
            break;
        }
    }
    return pts;
}
/** Draw a linear gradient between two points on ImageData. */
function drawGradient(data, x0, y0, x1, y1, color1, color2, sel) {
    var _a, _b, _c, _d;
    const dx = x1 - x0, dy = y1 - y0;
    const len = Math.sqrt(dx * dx + dy * dy);
    if (len === 0)
        return;
    const sx = (_a = sel === null || sel === void 0 ? void 0 : sel.x) !== null && _a !== void 0 ? _a : 0, sy = (_b = sel === null || sel === void 0 ? void 0 : sel.y) !== null && _b !== void 0 ? _b : 0;
    const sw = (_c = sel === null || sel === void 0 ? void 0 : sel.w) !== null && _c !== void 0 ? _c : data.width, sh = (_d = sel === null || sel === void 0 ? void 0 : sel.h) !== null && _d !== void 0 ? _d : data.height;
    for (let py = sy; py < sy + sh; py++) {
        for (let px = sx; px < sx + sw; px++) {
            const t = Math.max(0, Math.min(1, ((px - x0) * dx + (py - y0) * dy) / (len * len)));
            const c = [
                Math.round(color1[0] + (color2[0] - color1[0]) * t),
                Math.round(color1[1] + (color2[1] - color1[1]) * t),
                Math.round(color1[2] + (color2[2] - color1[2]) * t),
                Math.round(color1[3] + (color2[3] - color1[3]) * t),
            ];
            setPixel(data, px, py, c);
        }
    }
}
/** Ordered dithering 4x4 Bayer matrix. */
const BAYER4 = [
    [0, 8, 2, 10],
    [12, 4, 14, 6],
    [3, 11, 1, 9],
    [15, 7, 13, 5],
];
/** Apply dithered brush stamp. */
function applyDitherStamp(data, x, y, color, size, pressure = 0.5) {
    const half = Math.floor(size / 2);
    const threshold = pressure * 16;
    for (let dy = -half; dy < size - half; dy++) {
        for (let dx = -half; dx < size - half; dx++) {
            const gx = x + dx, gy = y + dy;
            if (gx < 0 || gy < 0 || gx >= data.width || gy >= data.height)
                continue;
            const bayer = BAYER4[((gy % 4) + 4) % 4][((gx % 4) + 4) % 4];
            if (bayer < threshold) {
                setPixel(data, gx, gy, color);
            }
        }
    }
}
/** Copy pixels from a selection region. */
function copySelection(data, sel) {
    const out = new ImageData(sel.w, sel.h);
    for (let y = 0; y < sel.h; y++) {
        for (let x = 0; x < sel.w; x++) {
            const px = getPixel(data, sel.x + x, sel.y + y);
            setPixel(out, x, y, px);
        }
    }
    return out;
}
/** Paste ImageData at position. */
function pasteAt(dst, src, ox, oy) {
    for (let y = 0; y < src.height; y++) {
        for (let x = 0; x < src.width; x++) {
            const px = getPixel(src, x, y);
            if (px[3] > 0)
                blendPixel(dst, ox + x, oy + y, px, 1);
        }
    }
}
/** Clear pixels in selection region. */
function clearSelection(data, sel) {
    for (let y = sel.y; y < sel.y + sel.h; y++) {
        for (let x = sel.x; x < sel.x + sel.w; x++) {
            setPixel(data, x, y, [0, 0, 0, 0]);
        }
    }
}
/** Bresenham line — calls callback for each pixel along the line. */
function bresenhamLine(x0, y0, x1, y1, cb) {
    const dx = Math.abs(x1 - x0), dy = -Math.abs(y1 - y0);
    const sx = x0 < x1 ? 1 : -1, sy = y0 < y1 ? 1 : -1;
    let err = dx + dy;
    for (;;) {
        cb(x0, y0);
        if (x0 === x1 && y0 === y1)
            break;
        const e2 = 2 * err;
        if (e2 >= dy) {
            err += dy;
            x0 += sx;
        }
        if (e2 <= dx) {
            err += dx;
            y0 += sy;
        }
    }
}
/** Flood fill — fills connected region of same color with new color. */
function floodFill(data, startX, startY, fillColor) {
    const w = data.width, h = data.height;
    if (startX < 0 || startY < 0 || startX >= w || startY >= h)
        return;
    const target = getPixel(data, startX, startY);
    if (colorsEqual(target, fillColor))
        return;
    const stack = [startX, startY];
    const visited = new Uint8Array(w * h);
    while (stack.length > 0) {
        const y = stack.pop();
        const x = stack.pop();
        const key = y * w + x;
        if (x < 0 || y < 0 || x >= w || y >= h)
            continue;
        if (visited[key])
            continue;
        visited[key] = 1;
        const current = getPixel(data, x, y);
        if (!colorsEqual(current, target))
            continue;
        setPixel(data, x, y, fillColor);
        stack.push(x - 1, y, x + 1, y, x, y - 1, x, y + 1);
    }
}
/** Check if two RGBA colors are equal (with small tolerance for alpha). */
function colorsEqual(a, b, tolerance = 0) {
    return Math.abs(a[0] - b[0]) <= tolerance &&
        Math.abs(a[1] - b[1]) <= tolerance &&
        Math.abs(a[2] - b[2]) <= tolerance &&
        Math.abs(a[3] - b[3]) <= tolerance;
}
/** Parse hex color string to RGBA. */
function hexToRGBA(hex, alpha = 255) {
    const h = hex.startsWith('#') ? hex.slice(1) : hex;
    return [
        parseInt(h.slice(0, 2), 16) || 0,
        parseInt(h.slice(2, 4), 16) || 0,
        parseInt(h.slice(4, 6), 16) || 0,
        alpha,
    ];
}
/** Convert RGBA to hex string. */
function rgbaToHex(c) {
    return '#' + [c[0], c[1], c[2]].map(v => v.toString(16).padStart(2, '0')).join('');
}
/** Create blank ImageData. */
function createBlankImageData(width, height) {
    return new ImageData(width, height);
}
/** Clone ImageData. */
function cloneImageData(src) {
    const dst = new ImageData(src.width, src.height);
    dst.data.set(src.data);
    return dst;
}
/** Flip ImageData horizontally. */
function flipH(src) {
    const dst = new ImageData(src.width, src.height);
    for (let y = 0; y < src.height; y++)
        for (let x = 0; x < src.width; x++) {
            const si = (y * src.width + x) * 4;
            const di = (y * src.width + (src.width - 1 - x)) * 4;
            dst.data[di] = src.data[si];
            dst.data[di + 1] = src.data[si + 1];
            dst.data[di + 2] = src.data[si + 2];
            dst.data[di + 3] = src.data[si + 3];
        }
    return dst;
}
/** Flip ImageData vertically. */
function flipV(src) {
    const dst = new ImageData(src.width, src.height);
    for (let y = 0; y < src.height; y++)
        for (let x = 0; x < src.width; x++) {
            const si = (y * src.width + x) * 4;
            const di = ((src.height - 1 - y) * src.width + x) * 4;
            dst.data[di] = src.data[si];
            dst.data[di + 1] = src.data[si + 1];
            dst.data[di + 2] = src.data[si + 2];
            dst.data[di + 3] = src.data[si + 3];
        }
    return dst;
}
/** Rotate ImageData 90° clockwise. Returns new ImageData with swapped dimensions. */
function rotateCW(src) {
    const dst = new ImageData(src.height, src.width);
    for (let y = 0; y < src.height; y++)
        for (let x = 0; x < src.width; x++) {
            const si = (y * src.width + x) * 4;
            const di = (x * dst.width + (dst.width - 1 - y)) * 4;
            dst.data[di] = src.data[si];
            dst.data[di + 1] = src.data[si + 1];
            dst.data[di + 2] = src.data[si + 2];
            dst.data[di + 3] = src.data[si + 3];
        }
    return dst;
}
// Bayer matrices for ordered dithering
const BAYER_2x2 = [
    [0, 2],
    [3, 1],
];
const BAYER_8x8 = [
    [0, 32, 8, 40, 2, 34, 10, 42],
    [48, 16, 56, 24, 50, 18, 58, 26],
    [12, 44, 4, 36, 14, 46, 6, 38],
    [60, 28, 52, 20, 62, 30, 54, 22],
    [3, 35, 11, 43, 1, 33, 9, 41],
    [51, 19, 59, 27, 49, 17, 57, 25],
    [15, 47, 7, 39, 13, 45, 5, 37],
    [63, 31, 55, 23, 61, 29, 53, 21],
];
/** Find closest color in palette */
function findClosestPaletteColor(r, g, b, palette) {
    let minDist = Infinity;
    let closest = palette[0];
    for (const color of palette) {
        const dr = r - color[0];
        const dg = g - color[1];
        const db = b - color[2];
        const dist = dr * dr + dg * dg + db * db;
        if (dist < minDist) {
            minDist = dist;
            closest = color;
        }
    }
    return closest;
}
/** Apply ordered dithering with Bayer matrix */
function ditherOrdered(data, palette, matrix = BAYER4, strength = 1.0) {
    const result = cloneImageData(data);
    const matrixSize = matrix.length;
    const maxMatrixValue = matrixSize * matrixSize;
    for (let y = 0; y < data.height; y++) {
        for (let x = 0; x < data.width; x++) {
            const i = (y * data.width + x) * 4;
            const a = data.data[i + 3];
            if (a < 128)
                continue; // Skip transparent pixels
            const threshold = (matrix[y % matrixSize][x % matrixSize] / maxMatrixValue - 0.5) * strength * 64;
            const r = Math.max(0, Math.min(255, data.data[i] + threshold));
            const g = Math.max(0, Math.min(255, data.data[i + 1] + threshold));
            const b = Math.max(0, Math.min(255, data.data[i + 2] + threshold));
            const closest = findClosestPaletteColor(r, g, b, palette);
            result.data[i] = closest[0];
            result.data[i + 1] = closest[1];
            result.data[i + 2] = closest[2];
        }
    }
    return result;
}
/** Floyd-Steinberg error diffusion dithering */
function ditherFloydSteinberg(data, palette) {
    const result = cloneImageData(data);
    const errors = new Float32Array(data.width * data.height * 3);
    for (let y = 0; y < data.height; y++) {
        for (let x = 0; x < data.width; x++) {
            const idx = (y * data.width + x);
            const i = idx * 4;
            const ei = idx * 3;
            if (data.data[i + 3] < 128)
                continue;
            const oldR = Math.max(0, Math.min(255, data.data[i] + errors[ei]));
            const oldG = Math.max(0, Math.min(255, data.data[i + 1] + errors[ei + 1]));
            const oldB = Math.max(0, Math.min(255, data.data[i + 2] + errors[ei + 2]));
            const newColor = findClosestPaletteColor(oldR, oldG, oldB, palette);
            result.data[i] = newColor[0];
            result.data[i + 1] = newColor[1];
            result.data[i + 2] = newColor[2];
            const errR = oldR - newColor[0];
            const errG = oldG - newColor[1];
            const errB = oldB - newColor[2];
            // Distribute error: [7/16, 3/16, 5/16, 1/16]
            const distribute = (dx, dy, factor) => {
                const nx = x + dx, ny = y + dy;
                if (nx >= 0 && nx < data.width && ny >= 0 && ny < data.height) {
                    const ni = (ny * data.width + nx) * 3;
                    errors[ni] += errR * factor;
                    errors[ni + 1] += errG * factor;
                    errors[ni + 2] += errB * factor;
                }
            };
            distribute(1, 0, 7 / 16);
            distribute(-1, 1, 3 / 16);
            distribute(0, 1, 5 / 16);
            distribute(1, 1, 1 / 16);
        }
    }
    return result;
}
/** Jarvis-Judice-Ninke dithering (larger error diffusion kernel) */
function ditherJarvisJudiceNinke(data, palette) {
    const result = cloneImageData(data);
    const errors = new Float32Array(data.width * data.height * 3);
    for (let y = 0; y < data.height; y++) {
        for (let x = 0; x < data.width; x++) {
            const idx = (y * data.width + x);
            const i = idx * 4;
            const ei = idx * 3;
            if (data.data[i + 3] < 128)
                continue;
            const oldR = Math.max(0, Math.min(255, data.data[i] + errors[ei]));
            const oldG = Math.max(0, Math.min(255, data.data[i + 1] + errors[ei + 1]));
            const oldB = Math.max(0, Math.min(255, data.data[i + 2] + errors[ei + 2]));
            const newColor = findClosestPaletteColor(oldR, oldG, oldB, palette);
            result.data[i] = newColor[0];
            result.data[i + 1] = newColor[1];
            result.data[i + 2] = newColor[2];
            const errR = oldR - newColor[0];
            const errG = oldG - newColor[1];
            const errB = oldB - newColor[2];
            const distribute = (dx, dy, factor) => {
                const nx = x + dx, ny = y + dy;
                if (nx >= 0 && nx < data.width && ny >= 0 && ny < data.height) {
                    const ni = (ny * data.width + nx) * 3;
                    errors[ni] += errR * factor;
                    errors[ni + 1] += errG * factor;
                    errors[ni + 2] += errB * factor;
                }
            };
            // JJN kernel: /48
            distribute(1, 0, 7 / 48);
            distribute(2, 0, 5 / 48);
            distribute(-2, 1, 3 / 48);
            distribute(-1, 1, 5 / 48);
            distribute(0, 1, 7 / 48);
            distribute(1, 1, 5 / 48);
            distribute(2, 1, 3 / 48);
            distribute(-2, 2, 1 / 48);
            distribute(-1, 2, 3 / 48);
            distribute(0, 2, 5 / 48);
            distribute(1, 2, 3 / 48);
            distribute(2, 2, 1 / 48);
        }
    }
    return result;
}
/** Atkinson dithering (preserves details better) */
function ditherAtkinson(data, palette) {
    const result = cloneImageData(data);
    const errors = new Float32Array(data.width * data.height * 3);
    for (let y = 0; y < data.height; y++) {
        for (let x = 0; x < data.width; x++) {
            const idx = (y * data.width + x);
            const i = idx * 4;
            const ei = idx * 3;
            if (data.data[i + 3] < 128)
                continue;
            const oldR = Math.max(0, Math.min(255, data.data[i] + errors[ei]));
            const oldG = Math.max(0, Math.min(255, data.data[i + 1] + errors[ei + 1]));
            const oldB = Math.max(0, Math.min(255, data.data[i + 2] + errors[ei + 2]));
            const newColor = findClosestPaletteColor(oldR, oldG, oldB, palette);
            result.data[i] = newColor[0];
            result.data[i + 1] = newColor[1];
            result.data[i + 2] = newColor[2];
            // Atkinson only diffuses 6/8 = 3/4 of error
            const errR = (oldR - newColor[0]) / 8;
            const errG = (oldG - newColor[1]) / 8;
            const errB = (oldB - newColor[2]) / 8;
            const distribute = (dx, dy) => {
                const nx = x + dx, ny = y + dy;
                if (nx >= 0 && nx < data.width && ny >= 0 && ny < data.height) {
                    const ni = (ny * data.width + nx) * 3;
                    errors[ni] += errR;
                    errors[ni + 1] += errG;
                    errors[ni + 2] += errB;
                }
            };
            distribute(1, 0);
            distribute(2, 0);
            distribute(-1, 1);
            distribute(0, 1);
            distribute(1, 1);
            distribute(0, 2);
        }
    }
    return result;
}
/** Random dithering */
function ditherRandom(data, palette, strength = 32) {
    const result = cloneImageData(data);
    for (let y = 0; y < data.height; y++) {
        for (let x = 0; x < data.width; x++) {
            const i = (y * data.width + x) * 4;
            if (data.data[i + 3] < 128)
                continue;
            const noise = (Math.random() - 0.5) * strength * 2;
            const r = Math.max(0, Math.min(255, data.data[i] + noise));
            const g = Math.max(0, Math.min(255, data.data[i + 1] + noise));
            const b = Math.max(0, Math.min(255, data.data[i + 2] + noise));
            const closest = findClosestPaletteColor(r, g, b, palette);
            result.data[i] = closest[0];
            result.data[i + 1] = closest[1];
            result.data[i + 2] = closest[2];
        }
    }
    return result;
}
/** Apply dithering with specified algorithm */
function applyDithering(data, palette, algorithm) {
    switch (algorithm) {
        case 'none':
            // Just quantize without dithering
            return ditherOrdered(data, palette, [[0]], 0);
        case 'ordered-2x2':
            return ditherOrdered(data, palette, BAYER_2x2);
        case 'ordered-4x4':
            return ditherOrdered(data, palette, BAYER4);
        case 'ordered-8x8':
            return ditherOrdered(data, palette, BAYER_8x8);
        case 'floyd-steinberg':
            return ditherFloydSteinberg(data, palette);
        case 'jarvis-judice-ninke':
            return ditherJarvisJudiceNinke(data, palette);
        case 'atkinson':
            return ditherAtkinson(data, palette);
        case 'random':
            return ditherRandom(data, palette);
        default:
            return ditherFloydSteinberg(data, palette);
    }
}
/** Generate palette from image using median cut algorithm */
function extractPalette(data, maxColors) {
    const colors = [];
    // Collect all non-transparent pixels
    for (let i = 0; i < data.data.length; i += 4) {
        if (data.data[i + 3] >= 128) {
            colors.push([data.data[i], data.data[i + 1], data.data[i + 2]]);
        }
    }
    if (colors.length === 0)
        return [[0, 0, 0, 255]];
    // Simple median cut
    function medianCut(colors, depth) {
        if (depth === 0 || colors.length <= 1) {
            // Return average color
            const r = Math.round(colors.reduce((s, c) => s + c[0], 0) / colors.length);
            const g = Math.round(colors.reduce((s, c) => s + c[1], 0) / colors.length);
            const b = Math.round(colors.reduce((s, c) => s + c[2], 0) / colors.length);
            return [[r, g, b]];
        }
        // Find channel with largest range
        const ranges = [0, 1, 2].map(ch => {
            const vals = colors.map(c => c[ch]);
            return Math.max(...vals) - Math.min(...vals);
        });
        const channel = ranges.indexOf(Math.max(...ranges));
        // Sort by channel and split
        colors.sort((a, b) => a[channel] - b[channel]);
        const mid = Math.floor(colors.length / 2);
        return [
            ...medianCut(colors.slice(0, mid), depth - 1),
            ...medianCut(colors.slice(mid), depth - 1),
        ];
    }
    const depth = Math.ceil(Math.log2(maxColors));
    const palette = medianCut(colors, depth).slice(0, maxColors);
    return palette.map(c => [c[0], c[1], c[2], 255]);
}
/** Convert hex palette to RGBA array */
function hexPaletteToRGBA(hexColors) {
    return hexColors.map(hex => hexToRGBA(hex));
}
/** Create reference overlay from file */
async function createReferenceOverlay(file, targetWidth, targetHeight) {
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.onload = () => {
            // Scale to fit within target dimensions
            const scale = Math.min(targetWidth / img.width, targetHeight / img.height, 1 // Don't scale up
            );
            const canvas = document.createElement('canvas');
            canvas.width = Math.round(img.width * scale);
            canvas.height = Math.round(img.height * scale);
            const ctx = canvas.getContext('2d');
            ctx.imageSmoothingEnabled = false;
            ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
            resolve({
                image: ctx.getImageData(0, 0, canvas.width, canvas.height),
                x: 0,
                y: 0,
                opacity: 0.4,
                scale: 1,
                locked: false,
            });
        };
        img.onerror = reject;
        img.src = URL.createObjectURL(file);
    });
}
/** Render reference overlay onto canvas */
function renderReferenceOverlay(ctx, ref, zoom, panX, panY) {
    ctx.save();
    ctx.globalAlpha = ref.opacity;
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = ref.image.width;
    tempCanvas.height = ref.image.height;
    tempCanvas.getContext('2d').putImageData(ref.image, 0, 0);
    ctx.imageSmoothingEnabled = false;
    ctx.drawImage(tempCanvas, (ref.x + panX) * zoom, (ref.y + panY) * zoom, ref.image.width * ref.scale * zoom, ref.image.height * ref.scale * zoom);
    ctx.restore();
}

export { DEFAULT_BRUSH, applyBrushStamp, applyDitherStamp, applyDithering, blendPixel, bresenhamLine, clearSelection, cloneImageData, colorsEqual, copySelection, createBlankImageData, createReferenceOverlay, ditherAtkinson, ditherFloydSteinberg, ditherJarvisJudiceNinke, ditherOrdered, ditherRandom, drawGradient, extractPalette, flipH, flipV, floodFill, getBrushMask, getPixel, getSymmetryPoints, hexPaletteToRGBA, hexToRGBA, pasteAt, renderReferenceOverlay, rgbaToHex, rotateCW, setPixel };
//# sourceMappingURL=pixelEditorUtils.js.map
