Skip to content

Bits per pixel of Internet images

Yannis Guyon edited this page Feb 5, 2025 · 2 revisions

The 2024 Web Almanac has a "Bits per pixel" section:
https://almanac.httparchive.org/en/2024/media#bits-per-pixel

It shows aggregated statistics about the compression ratio. But the distribution is across all formats, and only the median is shown for each format.

Here is an excerpt regarding AVIF:
"The median AVIF’s bits-per-pixel went up from approximately 1.0 in 2022 to around 1.4 bits per pixel in 2024—an increase of 47%. Funnily enough, we hypothesize the same root cause. The current, diverse crop of AVIF encoders is likely making different quality/filesize tradeoffs, sacrificing less quality at default settings than AOM’s official libavif encoder was two years ago."

While the hypothesized root cause may be correct, it is hard to draw conclusions with this level of abstraction.
This page is an exploration to obtain more fine-grained results from the same data source, the HTTPArchive:
https://httparchive.org/

Megapixels buckets

Sorting images by dimensions is a first step.
A megapixel is defined as 1,000,000 pixels here.

2024

Desktop

JPEG WebP AVIF
Sample size bpp at percentile Sample size bpp at percentile Sample size bpp at percentile
Megapixels 10th 50th 90th 10th 50th 90th 10th 50th 90th
]0.0:0.1] 14,319,104 1.03 3.07 11.54 25,818,210 0.49 1.69 6.75 3,303,795 0.58 1.63 4.36
]0.1:0.2] 3,930,652 0.68 2.07 6.60 6,787,726 0.28 1.04 2.99 341,594 0.24 0.78 1.98
]0.2:0.3] 2,622,207 0.60 1.95 6.25 4,671,647 0.24 0.85 2.74 235,638 0.20 0.65 1.64
]0.3:0.4] 1,828,927 0.58 1.84 5.88 2,550,373 0.25 0.85 2.58 132,415 0.14 0.53 1.53
]0.4:0.5] 1,270,873 0.57 1.80 5.75 1,601,033 0.25 0.84 2.54 70,478 0.14 0.53 1.38
]0.5:0.6] 821,690 0.54 1.71 5.53 1,144,004 0.23 0.79 2.56 54,462 0.12 0.50 1.36
]0.6:0.7] 892,690 0.44 1.52 5.06 1,243,037 0.21 0.69 2.11 69,695 0.13 0.56 1.47
]0.7:0.8] 601,017 0.49 1.56 4.97 800,376 0.23 0.77 2.40 43,317 0.10 0.46 1.40
]0.8:0.9] 407,706 0.45 1.54 4.87 609,273 0.20 0.70 2.19 34,668 0.09 0.50 1.48
]0.9:1.0] 474,084 0.38 1.44 4.78 682,631 0.20 0.67 2.10 41,268 0.08 0.45 1.28
]1.0:2.0] 2,163,602 0.37 1.33 4.35 3,709,513 0.16 0.59 1.89 227,518 0.07 0.42 1.19
]2.0:3.0] 747,884 0.24 1.13 3.69 1,348,198 0.12 0.49 1.53 80,685 0.04 0.31 1.04
]3.0:4.0] 218,241 0.18 1.03 3.23 523,601 0.03 0.26 1.13 36,173 0.01 0.03 0.58
]4.0:5.0] 175,504 0.23 0.97 2.77 339,405 0.06 0.29 1.05 13,822 0.01 0.15 0.70
]5.0:6.0] 58,806 0.19 1.01 3.39 126,643 0.05 0.24 1.02 5,856 0.01 0.15 0.72
]6.0:7.0] 54,147 0.02 0.89 3.40 68,500 0.05 0.25 0.98 3,481 0.01 0.17 0.75
]7.0:8.0] 34,228 0.24 1.21 3.54 26,946 0.06 0.30 1.17 1,801 0.02 0.20 0.75
]8.0:9.0] 44,330 0.16 0.92 3.48 26,982 0.06 0.28 1.04 2,127 0.03 0.21 0.87
]9.0:10.0] 32,477 0.23 1.14 3.44 25,824 0.07 0.30 1.03 2,402 0.03 0.13 0.65
]10.0:11.0] 18,013 0.23 1.13 3.67 12,157 0.05 0.27 0.95 924 0.05 0.20 0.65
]11.0:12.0] 15,763 0.19 0.91 3.51 12,237 0.05 0.26 0.98 988 0.05 0.26 1.04
]12.0:13.0] 46,731 0.22 1.36 3.24 20,145 0.08 0.40 1.19 1,263 0.05 0.28 0.89
]13.0:∞[ 145,243 0.16 0.89 3.53 74,064 0.03 0.17 0.75 4,371 0.01 0.13 0.61

Mobile

JPEG WebP AVIF
Sample size bpp at percentile Sample size bpp at percentile Sample size bpp at percentile
Megapixels 10th 50th 90th 10th 50th 90th 10th 50th 90th
]0.0:0.1] 15,491,722 1.00 3.03 11.47 31,888,655 0.56 2.15 10.36 2,962,458 0.59 1.95 8.83
]0.1:0.2] 4,593,862 0.49 2.00 6.45 8,173,893 0.28 1.18 5.48 273,858 0.22 0.73 2.16
]0.2:0.3] 3,169,941 0.60 1.96 6.10 5,920,382 0.24 0.83 2.77 202,539 0.22 0.71 1.87
]0.3:0.4] 2,192,025 0.58 1.79 5.77 2,835,247 0.25 0.84 2.70 106,654 0.15 0.58 1.62
]0.4:0.5] 1,616,813 0.58 1.72 5.55 1,886,377 0.26 0.84 2.61 62,121 0.18 0.61 1.65
]0.5:0.6] 1,062,946 0.54 1.64 5.35 1,450,239 0.24 0.78 2.70 61,852 0.17 0.56 1.60
]0.6:0.7] 1,172,916 0.46 1.47 4.75 1,688,866 0.20 0.66 2.09 72,114 0.17 0.63 1.62
]0.7:0.8] 755,590 0.51 1.51 4.78 947,411 0.26 0.80 2.42 45,479 0.14 0.50 1.42
]0.8:0.9] 487,767 0.46 1.51 4.68 631,111 0.21 0.70 2.26 34,771 0.17 0.56 1.70
]0.9:1.0] 537,438 0.42 1.43 4.69 591,732 0.20 0.67 2.21 38,564 0.17 0.55 1.56
]1.0:2.0] 2,397,997 0.39 1.30 4.22 2,939,050 0.15 0.56 1.92 157,157 0.12 0.42 1.21
]2.0:3.0] 728,090 0.33 1.18 3.81 683,120 0.13 0.47 1.62 35,133 0.09 0.39 1.26
]3.0:4.0] 221,220 0.23 1.06 3.29 210,873 0.09 0.40 1.41 9,245 0.04 0.26 0.98
]4.0:5.0] 181,401 0.26 1.00 2.87 211,687 0.08 0.33 1.12 5,606 0.06 0.25 0.92
]5.0:6.0] 66,771 0.00 0.97 3.32 62,227 0.07 0.31 1.11 2,965 0.07 0.29 1.03
]6.0:7.0] 56,167 0.19 0.94 3.46 53,379 0.06 0.29 1.01 1,851 0.04 0.24 0.89
]7.0:8.0] 38,476 0.23 1.19 3.55 24,756 0.07 0.32 1.20 1,086 0.07 0.27 0.75
]8.0:9.0] 49,345 0.24 0.93 3.48 29,251 0.06 0.27 1.05 1,545 0.04 0.23 0.91
]9.0:10.0] 37,847 0.18 1.08 3.36 25,421 0.07 0.30 1.08 1,815 0.03 0.13 0.63
]10.0:11.0] 19,489 0.24 1.13 3.68 12,843 0.07 0.28 0.97 528 0.05 0.24 0.84
]11.0:12.0] 17,863 0.21 0.89 3.53 13,241 0.05 0.27 0.97 588 0.08 0.28 1.11
]12.0:13.0] 56,953 0.18 1.32 3.27 23,260 0.09 0.42 1.21 853 0.05 0.30 1.11
]13.0:∞[ 165,089 0.17 0.90 3.59 82,709 0.04 0.17 0.75 3,216 0.02 0.13 0.66

2022

Desktop

JPEG WebP AVIF
Sample size bpp at percentile Sample size bpp at percentile Sample size bpp at percentile
Megapixels 10th 50th 90th 10th 50th 90th 10th 50th 90th
]0.0:0.1] 13,736,125 0.99 2.90 10.76 9,944,002 0.52 1.84 6.68 193,124 0.54 1.68 5.72
]0.1:0.2] 3,188,089 0.77 2.04 6.23 2,136,409 0.27 1.03 3.08 50,268 0.30 0.80 1.82
]0.2:0.3] 1,954,039 0.64 1.94 6.10 1,352,647 0.26 0.90 2.76 39,528 0.26 0.68 1.59
]0.3:0.4] 1,247,211 0.65 1.90 5.89 810,128 0.26 0.87 2.61 20,272 0.22 0.68 1.70
]0.4:0.5] 864,453 0.65 1.85 5.72 479,032 0.27 0.89 2.60 11,949 0.20 0.62 1.51
]0.5:0.6] 533,730 0.60 1.75 5.54 324,648 0.24 0.84 2.67 7,328 0.17 0.54 1.40
]0.6:0.7] 542,256 0.55 1.57 5.06 319,913 0.23 0.77 2.36 7,814 0.19 0.60 1.50
]0.7:0.8] 359,223 0.54 1.58 5.06 215,650 0.24 0.82 2.64 5,220 0.15 0.52 1.35
]0.8:0.9] 256,110 0.50 1.56 4.83 166,913 0.21 0.74 2.29 4,713 0.19 0.55 1.45
]0.9:1.0] 281,235 0.46 1.47 4.81 173,384 0.20 0.70 2.13 8,461 0.17 0.48 1.29
]1.0:2.0] 1,173,734 0.43 1.36 4.36 882,208 0.17 0.64 1.97 20,813 0.11 0.43 1.24
]2.0:3.0] 359,929 0.32 1.14 3.74 294,927 0.13 0.56 1.80 10,644 0.07 0.32 1.01
]3.0:4.0] 98,776 0.29 1.06 3.40 87,080 0.05 0.38 1.47 2,852 0.02 0.20 0.81
]4.0:5.0] 79,505 0.30 1.03 2.93 56,047 0.07 0.35 1.35 1,607 0.05 0.28 0.90
]5.0:6.0] 28,773 0.26 1.03 3.40 20,810 0.06 0.36 1.44 569 0.05 0.27 0.89
]6.0:7.0] 24,419 0.25 0.96 3.34 12,525 0.07 0.32 1.32 572 0.02 0.18 0.78
]7.0:8.0] 16,062 0.30 1.19 3.44 5,731 0.06 0.33 1.24 213 0.04 0.26 0.92
]8.0:9.0] 21,439 0.27 0.98 3.37 5,647 0.05 0.29 1.28 209 0.03 0.19 0.83
]9.0:10.0] 14,684 0.28 0.93 3.40 5,732 0.06 0.30 1.21 199 0.04 0.18 0.75
]10.0:11.0] 7,909 0.28 1.16 3.80 3,128 0.06 0.31 1.20 64 0.03 0.21 0.89
]11.0:12.0] 7,027 0.16 0.95 3.26 2,764 0.06 0.35 1.27 47 0.04 0.20 0.68
]12.0:13.0] 16,135 0.34 1.43 3.31 3,702 0.09 0.49 1.57 171 0.06 0.32 0.70
]13.0:∞[ 55,662 0.20 0.85 3.24 14,799 0.03 0.23 1.06 357 0.02 0.12 0.65

Mobile

JPEG WebP AVIF
Sample size bpp at percentile Sample size bpp at percentile Sample size bpp at percentile
Megapixels 10th 50th 90th 10th 50th 90th 10th 50th 90th
]0.0:0.1] 17,243,306 0.72 2.80 10.39 12,111,150 0.54 2.10 9.72 186,531 0.49 1.54 5.21
]0.1:0.2] 4,262,640 0.73 2.00 6.21 2,579,674 0.22 1.11 5.69 61,001 0.32 0.88 2.72
]0.2:0.3] 2,756,504 0.63 1.88 5.97 1,686,229 0.26 0.91 3.06 41,968 0.24 0.68 1.61
]0.3:0.4] 1,806,607 0.63 1.83 5.79 947,242 0.25 0.85 2.98 21,942 0.20 0.66 1.64
]0.4:0.5] 1,268,956 0.66 1.80 5.63 569,657 0.27 0.87 2.89 14,951 0.20 0.62 1.53
]0.5:0.6] 807,713 0.61 1.68 5.36 438,206 0.24 0.81 3.01 10,164 0.19 0.59 1.48
]0.6:0.7] 865,898 0.55 1.51 4.82 449,990 0.22 0.76 2.54 10,060 0.16 0.53 1.34
]0.7:0.8] 543,887 0.57 1.56 4.77 260,834 0.26 0.86 2.94 7,060 0.16 0.51 1.32
]0.8:0.9] 354,518 0.51 1.54 4.73 178,546 0.22 0.73 2.45 6,863 0.16 0.56 1.33
]0.9:1.0] 382,035 0.49 1.45 4.69 170,415 0.20 0.69 2.27 7,481 0.17 0.46 1.11
]1.0:2.0] 1,553,290 0.44 1.33 4.26 759,601 0.16 0.59 2.00 18,573 0.11 0.39 1.11
]2.0:3.0] 427,932 0.36 1.16 3.82 165,163 0.13 0.52 1.84 6,137 0.08 0.28 0.86
]3.0:4.0] 124,976 0.34 1.10 3.42 48,351 0.11 0.44 1.56 2,048 0.02 0.14 0.68
]4.0:5.0] 101,880 0.30 1.03 2.87 47,768 0.08 0.33 1.19 942 0.05 0.17 0.77
]5.0:6.0] 36,761 0.30 1.10 3.44 14,382 0.07 0.35 1.32 417 0.05 0.21 0.76
]6.0:7.0] 32,541 0.27 0.97 3.34 12,732 0.07 0.35 1.38 311 0.02 0.10 0.54
]7.0:8.0] 22,436 0.33 1.27 3.51 5,780 0.07 0.35 1.28 131 0.04 0.24 0.76
]8.0:9.0] 29,219 0.28 0.97 3.30 6,014 0.06 0.29 1.32 229 0.03 0.16 0.74
]9.0:10.0] 20,686 0.29 1.03 3.47 6,305 0.07 0.34 1.35 136 0.06 0.21 1.22
]10.0:11.0] 11,455 0.29 1.19 3.90 3,587 0.08 0.31 1.17 89 0.03 0.13 0.83
]11.0:12.0] 9,722 0.23 0.95 3.21 3,204 0.07 0.37 1.32 74 0.05 0.18 0.53
]12.0:13.0] 24,753 0.32 1.43 3.31 4,801 0.10 0.52 1.58 144 0.02 0.27 0.74
]13.0:∞[ 76,439 0.21 0.89 3.32 16,375 0.04 0.25 1.04 431 0.02 0.14 0.71

How to reproduce these results

Please follow the instructions here:
https://github.com/HTTPArchive/httparchive.org/blob/main/docs/gettingstarted_bigquery.md

Example query
#standardSQL
# Measuring img loaded bytes and dimensions, broken down by detected format and megapixel range

CREATE TEMPORARY FUNCTION getSrcsetInfo(responsiveImagesJsonString STRING)
RETURNS ARRAY<STRUCT<imgURL STRING, approximateResourceWidth INT64, approximateResourceHeight INT64, byteSize INT64, bitsPerPixel NUMERIC, isPixel BOOL, isDataURL BOOL, resourceFormat STRING>>
LANGUAGE js AS '''

function pithyType( { contentType, url } ) {
  const subtypeMap = {
      'jpg': 'jpg',
      'png': 'png',
      'gif': 'gif',
      'webp': 'webp',
      'avif': 'avif'
  };

  function normalizeSubtype( subtype ) {
      if ( subtypeMap[ subtype ] ) {
          return subtypeMap[ subtype ];
      }
      return 'unknown'; // switch between:
                        // `subtype`
                        //     to see everything, check if there's anything else worth capturing
                        // `'unknown'`
                        //     to make results manageable
  }

  // if it's a data url, take the mime type from there, done.
  if ( url && typeof url === "string" ) {
      const match = url.toLowerCase().match( /^data:image\\/([\\w\\-\\.\\+]+)/ );
      if ( match && match[ 1 ] ) {
          return normalizeSubtype( match[ 1 ] );
      }
  }

  // if we get a content-type header, use it!
  if ( contentType && typeof contentType === "string" ) {
      const match = contentType.toLowerCase().match( /image\\/([\\w\\-\\.\\+]+)/ );
      if ( match && match[ 1 ] ) {
          return normalizeSubtype( match[ 1 ] );
      }
  }

  // otherwise fall back to extension in the URL
  if ( url && typeof url === "string" ) {
      const splitOnSlashes = url.split("/");
      if ( splitOnSlashes.length > 1 ) {
          const afterLastSlash = splitOnSlashes[ splitOnSlashes.length - 1 ],
                splitOnDots = afterLastSlash.split(".");
          if ( splitOnDots.length > 1 ) {
              return normalizeSubtype(
                  splitOnDots[ splitOnDots.length - 1 ]
                    .toLowerCase()
                    .replace( /^(\\w+)[\\?\\&\\#].*/, '$1' ) // strip query params
              );
          }
      }
  }

  // otherwise throw up our hands
  return 'unknown';
  }

  const parsed = JSON.parse( responsiveImagesJsonString );
  if ( parsed && parsed.map ) {
    const dataRegEx = new RegExp('^data');
    return parsed.map( d => ({
      imgURL: d.url,
      approximateResourceWidth: Math.floor( d.approximateResourceWidth || 0 ),
      approximateResourceHeight: Math.floor( d.approximateResourceHeight || 0 ),
      byteSize: Math.floor( d.byteSize || 0 ),
      bitsPerPixel: parseFloat( d.bitsPerPixel || 0 ),
      isPixel: d.approximateResourceWidth == 1 && d.approximateResourceHeight == 1,
      isDataURL: dataRegEx.test(d.url),
      resourceFormat: pithyType({ contentType: d.mimeType, url: d.url })
    }) );
  }
''';

WITH imgs AS (
  SELECT
    _TABLE_SUFFIX AS client,
    url AS pageURL,
    imgURL,
    approximateResourceWidth,
    approximateResourceHeight,
    byteSize,
    bitsPerPixel,
    isPixel,
    isDataURL,
    (approximateResourceWidth * approximateResourceHeight) / 1000000 AS megapixels,
    (approximateResourceWidth / approximateResourceHeight) AS aspectRatio,
    resourceFormat
  FROM
    `httparchive.pages.2024_06_01_*`,
    UNNEST(getSrcsetInfo(JSON_QUERY(JSON_VALUE(payload, '$._responsive_images'), '$.responsive-images')))
),

imgs_with_histogram_bucket AS (
  SELECT
    *,
    CASE
      WHEN megapixels >= 0  AND megapixels < 0.1 THEN '0.0-0.1MP'
      WHEN megapixels >= 0.1  AND megapixels < 0.2 THEN '0.1-0.2MP'
      WHEN megapixels >= 0.2  AND megapixels < 0.3 THEN '0.2-0.3MP'
      WHEN megapixels >= 0.3  AND megapixels < 0.4 THEN '0.3-0.4MP'
      WHEN megapixels >= 0.4  AND megapixels < 0.5 THEN '0.4-0.5MP'
      WHEN megapixels >= 0.5  AND megapixels < 0.6 THEN '0.5-0.6MP'
      WHEN megapixels >= 0.6  AND megapixels < 0.7 THEN '0.6-0.7MP'
      WHEN megapixels >= 0.7  AND megapixels < 0.8 THEN '0.7-0.8MP'
      WHEN megapixels >= 0.8  AND megapixels < 0.9 THEN '0.8-0.9MP'
      WHEN megapixels >= 0.9  AND megapixels < 1.0 THEN '0.9-1.0MP'
      WHEN megapixels >= 1.0  AND megapixels < 2.0 THEN '1.0-2.0MP'
      WHEN megapixels >= 2.0  AND megapixels < 3.0 THEN '2.0-3.0MP'
      WHEN megapixels >= 3.0  AND megapixels < 4.0 THEN '3.0-4.0MP'
      WHEN megapixels >= 4.0  AND megapixels < 5.0 THEN '4.0-5.0MP'
      WHEN megapixels >= 5.0  AND megapixels < 6.0 THEN '5.0-6.0MP'
      WHEN megapixels >= 6.0  AND megapixels < 7.0 THEN '6.0-7.0MP'
      WHEN megapixels >= 7.0  AND megapixels < 8.0 THEN '7.0-8.0MP'
      WHEN megapixels >= 8.0  AND megapixels < 9.0 THEN '8.0-9.0MP'
      WHEN megapixels >= 9.0  AND megapixels < 10.0 THEN '9.0-10.0MP'
      WHEN megapixels >= 10.0  AND megapixels < 11.0 THEN '10.0-11.0MP'
      WHEN megapixels >= 11.0  AND megapixels < 12.0 THEN '11.0-12.0MP'
      WHEN megapixels >= 12.0  AND megapixels < 13.0 THEN '12.0-13.0MP'
      WHEN megapixels >= 13 THEN '13+MP'
      ELSE 'Other'  -- Handles cases where megapixels might be NULL or negative (shouldn't happen, but good to be safe)
    END AS megapixel_bucket
  FROM
    imgs
  WHERE
    approximateResourceWidth > 1 AND approximateResourceHeight > 1  -- Exclude 1x1 pixel images
),


percentiles AS (
  SELECT
    client,
    resourceFormat,
    megapixel_bucket,
    APPROX_QUANTILES(approximateResourceWidth, 1000) AS resourceWidthPercentiles,
    APPROX_QUANTILES(approximateResourceHeight, 1000) AS resourceHeightPercentiles,
    APPROX_QUANTILES(aspectRatio, 1000) AS aspectRatioPercentiles,
    APPROX_QUANTILES(megapixels, 1000) AS megapixelsPercentiles,
    APPROX_QUANTILES(byteSize, 1000) AS byteSizePercentiles,
    APPROX_QUANTILES(bitsPerPixel, 1000) AS bitsPerPixelPercentiles,
    COUNT(0) AS imgCount
  FROM
    imgs_with_histogram_bucket
  GROUP BY
    client,
    resourceFormat,
    megapixel_bucket
)

SELECT
  percentile,
  client,
  resourceFormat,
  megapixel_bucket,
  imgCount,
  resourceWidthPercentiles[OFFSET(percentile * 10)] AS resourceWidth,
  resourceHeightPercentiles[OFFSET(percentile * 10)] AS resourceHeight,
  aspectRatioPercentiles[OFFSET(percentile * 10)] AS aspectRatio,
  megapixelsPercentiles[OFFSET(percentile * 10)] AS megapixels,
  byteSizePercentiles[OFFSET(percentile * 10)] AS byteSize,
  bitsPerPixelPercentiles[OFFSET(percentile * 10)] AS bitsPerPixel
FROM
  percentiles,
  UNNEST([0, 10, 25, 50, 75, 90, 100]) AS percentile
ORDER BY
  megapixel_bucket,
  imgCount DESC,
  percentile;

Acknowledgments

Many thanks to Philip Jägenstedt for gathering the data, and to the people behind the Web Almanac effort for their appreciated hard work and for considering the filters above.
Thank you Frank Galligan for fetching 2022 and 2024 data.