Core Web Vitals
LCP Image Entropy

LCP Image Entropy

Checks if images qualify as LCP candidates based on their entropy (bits per pixel). Since Chrome 112 (opens in a new tab), low-entropy images are ignored for LCP measurement.

Based on Stoyan Stefanov's BPP check (opens in a new tab).

What is BPP (Bits Per Pixel)?

BPP measures image complexity/entropy:

BPP = (file size in bits) / (width × height)
    = (encodedBodySize × 8) / (width × height)

LCP eligibility threshold:

BPPEntropyLCP EligibleExample
< 0.05🔴 Low❌ NoSolid colors, simple gradients, placeholders
≥ 0.05🟢 Normal✅ YesPhotos, complex graphics

Why this matters: If your largest image has low entropy, Chrome will skip it and use the next largest element for LCP. This can cause unexpected LCP values.

Snippet

// LCP Image Entropy Check
// https://webperf-snippets.nucliweb.net
 
(() => {
  const formatBytes = (bytes) => {
    if (!bytes) return "-";
    const k = 1024;
    const sizes = ["B", "KB", "MB"];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return (bytes / Math.pow(k, i)).toFixed(1) + " " + sizes[i];
  };
 
  const LCP_THRESHOLD = 0.05; // Chrome's threshold for low-entropy
 
  // Get current LCP element
  let lcpElement = null;
  let lcpUrl = null;
 
  const lcpObserver = new PerformanceObserver((list) => {
    const entries = list.getEntries();
    const lastEntry = entries[entries.length - 1];
    if (lastEntry) {
      lcpElement = lastEntry.element;
      lcpUrl = lastEntry.url;
    }
  });
 
  lcpObserver.observe({ type: "largest-contentful-paint", buffered: true });
 
  // Wait a tick to ensure LCP is captured
  setTimeout(() => {
    lcpObserver.disconnect();
 
    // Get all images
    const images = [...document.images]
      .filter((img) => {
        const src = img.currentSrc || img.src;
        return src && !src.startsWith("data:image");
      })
      .map((img) => {
        const src = img.currentSrc || img.src;
        const resource = performance.getEntriesByName(src)[0];
        const fileSize = resource?.encodedBodySize || 0;
        const pixels = img.naturalWidth * img.naturalHeight;
        const bpp = pixels > 0 ? (fileSize * 8) / pixels : 0;
 
        const isLowEntropy = bpp > 0 && bpp < LCP_THRESHOLD;
        const isLCP = lcpElement === img || lcpUrl === src;
 
        return {
          element: img,
          src,
          shortSrc: src.split("/").pop()?.split("?")[0] || src,
          width: img.naturalWidth,
          height: img.naturalHeight,
          fileSize,
          bpp,
          isLowEntropy,
          isLCP,
          lcpEligible: !isLowEntropy && bpp > 0,
        };
      })
      .filter((img) => img.bpp > 0); // Only images with measurable BPP
 
    console.group("%c🖼️ Image Entropy Analysis", "font-weight: bold; font-size: 14px;");
 
    if (images.length === 0) {
      console.log("   No images with measurable entropy found.");
      console.log("   (Data URLs and cross-origin images without CORS are excluded)");
      console.groupEnd();
      return;
    }
 
    // Summary
    const lowEntropy = images.filter((img) => img.isLowEntropy);
    const normalEntropy = images.filter((img) => !img.isLowEntropy);
    const lcpImage = images.find((img) => img.isLCP);
 
    console.log("");
    console.log("%cSummary:", "font-weight: bold;");
    console.log(`   Total images analyzed: ${images.length}`);
    console.log(`   🟢 Normal entropy (LCP eligible): ${normalEntropy.length}`);
    console.log(`   🔴 Low entropy (LCP ineligible): ${lowEntropy.length}`);
 
    if (lcpImage) {
      const icon = lcpImage.isLowEntropy ? "⚠️" : "✅";
      console.log("");
      console.log(`%c${icon} Current LCP image:`, "font-weight: bold;");
      console.log(`   ${lcpImage.shortSrc}`);
      console.log(`   BPP: ${lcpImage.bpp.toFixed(4)} ${lcpImage.isLowEntropy ? "(LOW - may be skipped!)" : "(OK)"}`);
    }
 
    // Table
    console.log("");
    console.log("%cAll Images:", "font-weight: bold;");
 
    const tableData = images
      .sort((a, b) => b.bpp - a.bpp)
      .map((img) => ({
        Image: img.shortSrc.length > 30 ? "..." + img.shortSrc.slice(-27) : img.shortSrc,
        Dimensions: `${img.width}×${img.height}`,
        Size: formatBytes(img.fileSize),
        BPP: img.bpp.toFixed(4),
        Entropy: img.isLowEntropy ? "🔴 Low" : "🟢 Normal",
        "LCP Eligible": img.lcpEligible ? "✅" : "❌",
        "Is LCP": img.isLCP ? "👈" : "",
      }));
 
    console.table(tableData);
 
    // Warnings
    if (lowEntropy.length > 0) {
      console.log("");
      console.log("%c⚠️ Low Entropy Images:", "font-weight: bold; color: #f59e0b;");
      console.log("   These images will NOT be considered for LCP in Chrome 112+:");
      lowEntropy.forEach((img) => {
        console.log(`   • ${img.shortSrc} (BPP: ${img.bpp.toFixed(4)})`, img.element);
      });
    }
 
    if (lcpImage && lcpImage.isLowEntropy) {
      console.log("");
      console.log("%c🚨 Warning:", "font-weight: bold; color: #ef4444;");
      console.log("   Your LCP image has low entropy and may be skipped by Chrome!");
      console.log("   Chrome will use the next largest element instead.");
      console.log("");
      console.log("%c💡 Solutions:", "font-weight: bold; color: #3b82f6;");
      console.log("   • Replace placeholder with actual content image");
      console.log("   • Use a text element as LCP instead");
      console.log("   • Ensure hero image loads with sufficient detail");
    }
 
    // Elements for inspection
    console.log("");
    console.log("%c🔎 Inspect elements:", "font-weight: bold;");
    images.forEach((img, i) => {
      const icon = img.isLowEntropy ? "🔴" : "🟢";
      const lcpMark = img.isLCP ? " 👈 LCP" : "";
      console.log(`   ${i + 1}. ${icon} ${img.shortSrc}${lcpMark}`, img.element);
    });
 
    console.groupEnd();
  }, 100);
})();

Understanding the Results

Table Columns:

ColumnDescription
ImageFilename
DimensionsWidth × Height in pixels
SizeEncoded file size
BPPBits per pixel (entropy measure)
Entropy🔴 Low (< 0.05) or 🟢 Normal
LCP EligibleWhether Chrome considers it for LCP
Is LCP👈 marks the current LCP element

What causes low entropy:

  • Solid color images
  • Simple gradients
  • Loading placeholders (blur-up, LQIP)
  • Very small images scaled up
  • Highly compressed images with little detail

Common Issues

IssueImpactSolution
Placeholder as LCPChrome skips it, unexpected LCPLoad real image faster
Gradient heroNot counted as LCPUse image with detail or text LCP
Lazy-loaded heroInitial low-res placeholder skippedEager load hero image

Further Reading