Visual

FAVICON GENERATOR

Generate favicons from text, emoji, or an uploaded image. Download PNG in multiple sizes. 100% browser-based.

Preview (256×256)

Click to upload or drag and drop an image
PNG, JPG, SVG — square images work best
Developer Reference

Core Algorithm & Standalone Script

Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.

const SIZES = [16, 32, 48, 64, 128, 180, 512];

  async function createIcoBlob(srcCanvas) {
    const icoCanvas = document.createElement('canvas');
    icoCanvas.width = 32; icoCanvas.height = 32;
    icoCanvas.getContext('2d').drawImage(srcCanvas, 0, 0, 32, 32);
    
    const pngBlob = await new Promise(resolve => icoCanvas.toBlob(resolve, 'image/png'));
    const pngBuffer = await pngBlob.arrayBuffer();
    const pngSize = pngBuffer.byteLength;
    
    const icoBuffer = new ArrayBuffer(6 + 16 + pngSize);
    const view = new DataView(icoBuffer);
    
    // Header
    view.setUint16(0, 0, true);
    view.setUint16(2, 1, true); // ICO=1
    view.setUint16(4, 1, true); // Count=1
    
    // Entry
    view.setUint8(6, 32); // Width
    view.setUint8(7, 32); // Height
    view.setUint8(8, 0);  // Palette
    view.setUint8(9, 0);  // Reserved
    view.setUint16(10, 1, true); // Planes
    view.setUint16(12, 32, true); // BPP
    view.setUint32(14, pngSize, true); // Size
    view.setUint32(18, 22, true); // Offset
    
    const icoArray = new Uint8Array(icoBuffer);
    icoArray.set(new Uint8Array(pngBuffer), 22);
    return new Blob([icoBuffer], {type: 'image/x-icon'});
  }

  function switchTab(id, el) {
    document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
    document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
    el.classList.add('active');
    document.getElementById('panel-' + id).classList.add('active');
  }

  function renderText() {
    const text = document.getElementById('faviconText').value || '?';
    const bg = document.getElementById('bgColor').value;
    const fg = document.getElementById('textColor').value;
    const sizePct = parseInt(document.getElementById('fontSize').value) / 100;
    const canvas = document.getElementById('textCanvas');
    const ctx = canvas.getContext('2d');
    const s = 256;
    ctx.clearRect(0, 0, s, s);
    ctx.fillStyle = bg;
    ctx.fillRect(0, 0, s, s);
    const fontSize = Math.round(s * sizePct);
    ctx.font = `${fontSize}px serif`;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillStyle = fg;
    ctx.fillText(text, s/2, s/2);
  }

  function loadImage(file) {
    if (!file) return;
    const reader = new FileReader();
    reader.onload = e => {
      const img = new Image();
      img.onload = () => {
        const src = document.getElementById('sourceCanvas');
        src.width = img.width; src.height = img.height;
        src.getContext('2d').drawImage(img, 0, 0);
        const canvas = document.getElementById('imgCanvas');
        const ctx = canvas.getContext('2d');
        ctx.clearRect(0, 0, 256, 256);
        const min = Math.min(img.width, img.height);
        const sx = (img.width - min) / 2, sy = (img.height - min) / 2;
        ctx.drawImage(img, sx, sy, min, min, 0, 0, 256, 256);
        document.getElementById('imgPreviewWrap').style.display = '';
      };
      img.src = e.target.result;
    };
    reader.readAsDataURL(file);
  }

  // Drag-and-drop
  const dz = document.getElementById('dropZone');
  dz.addEventListener('dragover', e => { e.preventDefault(); dz.classList.add('dragover'); });
  dz.addEventListener('dragleave', () => dz.classList.remove('dragover'));
  dz.addEventListener('drop', e => { e.preventDefault(); dz.classList.remove('dragover'); loadImage(e.dataTransfer.files[0]); });

  function getSourceCanvas(mode) {
    return mode === 'text' ? document.getElementById('textCanvas') : document.getElementById('imgCanvas');
  }

  function generateSizes(mode) {
    const src = getSourceCanvas(mode);
    const grid = document.getElementById('sizeGrid');
    grid.innerHTML = SIZES.map(sz => {
      return `<div class="size-item">
        <div class="size-label">${sz}×${sz} (PNG)</div>
        <button class="size-dl" onclick="downloadSize(${sz}, '${mode}')">download</button>
      </div>`;
    }).join('');

    // Add ICO option
    const icoDiv = document.createElement('div');
    icoDiv.className = 'size-item';
    icoDiv.innerHTML = `
      <div class="size-label">32×32 (ICO)</div>
      <button class="size-dl" onclick="downloadIco('${mode}')">download</button>
    `;
    grid.prepend(icoDiv);

    const snippet = `<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">`;
    document.getElementById('htmlSnippet').textContent = snippet;
    document.getElementById('sizesSection').style.display = '';

    // Save reference
    document.getElementById('sizesSection').dataset.mode = mode;
  }

  function downloadSize(sz, mode) {
    const src = getSourceCanvas(mode);
    const canvas = document.createElement('canvas');
    canvas.width = sz; canvas.height = sz;
    canvas.getContext('2d').drawImage(src, 0, 0, sz, sz);
    canvas.toBlob(blob => {
      const a = document.createElement('a');
      a.href = URL.createObjectURL(blob);
      const name = sz === 180 ? 'apple-touch-icon' : `favicon-${sz}x${sz}`;
      a.download = `${name}.png`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(a.href);
    });
  }

  async function downloadIco(mode) {
    const src = getSourceCanvas(mode);
    const blob = await createIcoBlob(src);
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = 'favicon.ico';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(a.href);
  }

  async function downloadAll() {
    const mode = document.getElementById('sizesSection').dataset.mode;
    const src = getSourceCanvas(mode);
    const zip = new JSZip();

    // Add ICO
    const icoBlob = await createIcoBlob(src);
    zip.file('favicon.ico', icoBlob);
    
    const promises = SIZES.map(sz => {
      return new Promise(resolve => {
        const canvas = document.createElement('canvas');
        canvas.width = sz; canvas.height = sz;
        const ctx = canvas.getContext('2d');
        ctx.imageSmoothingEnabled = true;
        ctx.imageSmoothingQuality = 'high';
        ctx.drawImage(src, 0, 0, sz, sz);
        canvas.toBlob(blob => {
          const name = sz === 180 ? 'apple-touch-icon' : `favicon-${sz}x${sz}`;
          zip.file(`${name}.png`, blob);
          resolve();
        }, 'image/png');
      });
    });

    try {
      await Promise.all(promises);
      const content = await zip.generateAsync({type: "blob"});
      const a = document.createElement('a');
      a.href = URL.createObjectURL(content);
      a.download = `favicons.zip`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(a.href);
    } catch (err) {
      console.error("Zip generation failed", err);
    }
  }

  function copySnippet(btn) {
    const el = document.getElementById('htmlSnippet');
    const text = el.textContent;
    navigator.clipboard.writeText(text).catch(() => {});
    btn.textContent = 'copied!'; btn.classList.add('copied');
    setTimeout(() => { btn.textContent = 'copy'; btn.classList.remove('copied'); }, 2000);
  }

  renderText();