아래 사이트의 png2icojs.js를 사용해서 PNG를 ICO로 저장하자.

https://github.com/datvm/PNG2ICOjs

 

 

HTML 소스

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Convert PNG Files to ICO</title>
</head>
<body>
    <div>
        <h1>Convert PNG to ICO</h1>
        <form id="fileSelect">
            <div>
                <input type="file" id="txt-files" accept="image/png" multiple required />
            </div>
            <div>
                <input type="checkbox" id="chk-ignore-size">
                <label for="chk-ignore-size">Ignore Image Size</label>
            </div>
            <p>
                <button type="submit">Convert</button>
            </p>
        </form>

        <form id="download">
            <div>
                <input id="txt-name" placeholder="Default download name: favicon.ico" />
                <button type="submit" id="btn-download" target="_blank" disabled>Download</button>
            </div>
        </form>
        <hr />
        원본사이트 : https://github.com/datvm/PNG2ICOjs
    </div>

    <script>
    const MaxSize = 256; // 1 << 8
    const MaxFiles = 65536; // 1 << 16
    const FileHeaderSize = 6;
    const ImageHeaderSize = 16;
    const IcoMime = "image/x-icon";
    class PngIcoConverter {
        async convertToBlobAsync(inputs, mime = IcoMime) {
            const arr = await this.convertAsync(inputs);
            return new Blob([arr], {
                type: mime,
            });
        }
        async convertAsync(inputs) {
            const inLen = inputs.length;
            if (inLen > MaxFiles) {
                throw new Error("TOO_MANY_FILES");
            }
            // File Format: https://en.wikipedia.org/wiki/ICO_(file_format)
            // File Header + Image Header + Image Content
            const headersLen = FileHeaderSize + ImageHeaderSize * inLen;
            const totalLen = headersLen + this.sumInputLen(inputs);
            const arr = new Uint8Array(totalLen);
            // File Header
            arr.set([0, 0, 1, 0, ...this.to2Bytes(inLen)], 0);
            // Image Headers & Data
            let imgPos = headersLen;
            for (let i = 0; i < inputs.length; i++) {
                const currPos = FileHeaderSize + ImageHeaderSize * i, input = inputs[i];
                const blob = this.toBlob(input.png), img = await this.loadImageAsync(blob), w = img.naturalWidth, h = img.naturalHeight;
                if (!input.ignoreSize &&
                    (w > MaxSize || h > MaxSize)) {
                    throw new Error("INVALID_SIZE");
                }
                // Header
                arr.set([
                    w > MaxSize ? 0 : w,
                    h > MaxSize ? 0 : h,
                    0,
                    0,
                    0, 0,
                    ...(input.bpp ? this.to2Bytes(input.bpp) : [0, 0]),
                    ...this.to4Bytes(blob.size),
                    ...this.to4Bytes(imgPos),
                ], currPos);
                // Image
                const buffer = input.png instanceof ArrayBuffer ? input.png : await input.png.arrayBuffer();
                arr.set(new Uint8Array(buffer), imgPos);
                imgPos += blob.size;
            }
            return arr;
        }
        loadImageAsync(png) {
            return new Promise((r, rej) => {
                const img = new Image();
                img.onload = () => r(img);
                img.onerror = () => rej("INVALID_IMAGE");
                img.src = URL.createObjectURL(png);
            });
        }
        toBlob(input, type = "image/png") {
            return input instanceof Blob ? input : new Blob([input], {
                type,
            });
        }
        to2Bytes(n) {
            return [n & 255, (n >> 8) & 255];
        }
        to4Bytes(n) {
            return [n & 255, (n >> 8) & 255, (n >> 16) & 255, (n >> 24) & 255];
        }
        sumInputLen(inputs) {
            let total = 0;
            for (const i of inputs) {
                const png = i.png;
                if (png instanceof Blob) {
                    total += png.size;
                }
                else {
                    total += png.byteLength;
                }
            }
            return total;
        }
    }
   
    const btnDownload = document.getElementById("btn-download");

    function init() {
        document.getElementById("fileSelect").addEventListener("submit", e => {
            e.preventDefault();
            convert();
         });
         document.getElementById("download").addEventListener("submit", e => {
            e.preventDefault();
            onDownload();
        });
    }

    let currBlob;
    function onDownload() {
        if (!currBlob) { return; }

        const url = URL.createObjectURL(currBlob);
        const a = document.createElement("a");
        a.href = url;

        const name = document.querySelector("#txt-name").value || "favicon.ico";
        a.download = name;

        a.click();
    }
    async function convert() {
        const files = document.querySelector("#txt-files").files;
        if (!files.length) {
            alert("Please choose at least a file");
            return;
        }

        const converter = new PngIcoConverter();
        const ignoreSize = document.querySelector("#chk-ignore-size").checked;
        const inputs = [...files].map(file => ({
            png: file,
            ignoreSize,
        }));

        try {
            currBlob = await converter.convertToBlobAsync(inputs);
            btnDownload.removeAttribute("disabled");
        } catch (e) {
            console.error(e);
            const msg = e.message;
            if (msg) {
                alert("Error converting: " + (ErrorMessages[msg] ?? msg));
            }
        }
    }
    init();
    </script>
</body>
</html>
728x90

+ Recent posts