codec: compare 7 image encoding formats
authorMischa POSLAWSKY <perl@shiar.org>
Fri, 26 Feb 2021 11:15:03 +0000 (12:15 +0100)
committerMischa POSLAWSKY <perl@shiar.org>
Tue, 9 Nov 2021 03:14:15 +0000 (04:14 +0100)
An initial summary of current technologies following the completion of
JPEG-XL.  Started out with original research, but then encountered an
article by Jon Sneyers with very similar results, so copied that instead:
https://cloudinary.com/blog/time_for_next_gen_codecs_to_dethrone_jpeg

codec.inc.pl [new file with mode: 0644]
codec.plp [new file with mode: 0644]

diff --git a/codec.inc.pl b/codec.inc.pl
new file mode 100644 (file)
index 0000000..a76b04d
--- /dev/null
@@ -0,0 +1,391 @@
+use utf8;
++{
+codec => {
+       jpeg => {
+               name => '<abbr title="Joint Photographic Experts Group">JPEG</abbr>',
+               available => 1992,
+       },
+       png => {
+               name => '<abbr title="Portable Network Graphics">PNG</abbr>',
+               available => 1996,
+       },
+       jp2k => {
+               name => 'JPEG 2000',
+               available => 2000,
+       },
+       webp => {
+               name => 'WebP',
+               available => 2010,
+       },
+       heic => {
+               name => '<abbr title="High Efficiency Image Container (HEVC in HEIF)">HEIC</abbr>',
+               available => 2015,
+       },
+       avif => {
+               name => '<abbr title="AV1 Image File Format">AVIF</abbr>',
+               available => 2019,
+       },
+       jxl => {
+               name => 'JPEG XL',
+               available => 2021,
+       },
+},
+feature => {
+       quality_photo => {
+               name => 'compression (photo)',
+               score => {
+                       jpeg => 3,
+                       png  => 1,
+                       jp2k => 4,
+                       webp => 3,
+                       heic => 5,
+                       avif => 5,
+                       jxl  => 5,
+               },
+       },
+       quality_photo_1 => {
+               name => 'low fidelity',
+               score => {
+                       jpeg => 2,
+                       png  => 1,
+                       jp2k => 3,
+                       webp => 3,
+                       heic => 5,
+                       avif => 5,
+                       jxl  => 4,
+               },
+       },
+       quality_photo_2 => {
+               name => 'medium fidelity',
+               score => {
+                       jpeg => 3,
+                       png  => 1,
+                       jp2k => 4,
+                       webp => 3,
+                       heic => 4,
+                       avif => 5,
+                       jxl  => 5,
+               },
+       },
+       quality_photo_3 => {
+               name => 'high fidelity',
+               score => {
+                       jpeg => 3,
+                       png  => 2,
+                       jp2k => 4,
+                       webp => 2,
+                       heic => 3,
+                       avif => 4,
+                       jxl  => 5,
+               },
+       },
+       quality_photo_ll => {
+               name => 'lossless',
+               score => {
+                       jpeg => 1,
+                       png  => 2,
+                       jp2k => 4,
+                       webp => 3,
+                       heic => 3,
+                       avif => 3,
+                       jxl  => 5,
+               },
+       },
+       quality_art => {
+               name => 'compression (other images)',
+               score => {
+                       jpeg => 2,
+                       png  => 3,
+                       jp2k => 2,
+                       webp => 4,
+                       heic => 3,
+                       avif => 4.5,
+                       jxl  => 5,
+               },
+       },
+       quality_art_2 => {
+               name => 'lossy non-photographic',
+               score => {
+                       jpeg => 2,
+                       png  => 3,
+                       jp2k => 2,
+                       webp => 4,
+                       heic => 3,
+                       avif => 5,
+                       jxl  => 5,
+               },
+       },
+       quality_art_ll => {
+               name => 'lossless non-photographic',
+               score => {
+                       jpeg => 1,
+                       png  => 4,
+                       jp2k => 2,
+                       webp => 5,
+                       heic => 2,
+                       avif => 3,
+                       jxl  => 5,
+               },
+       },
+       quality_art_mixed => {
+               name => 'mixed photo/nonphoto',
+               score => {
+                       jpeg => 2,
+                       png  => 2,
+                       jp2k => 2,
+                       webp => 3,
+                       heic => 3,
+                       avif => 5,
+                       jxl  => 5,
+               },
+       },
+       speed => {
+               score => {
+                       jpeg => 5,
+                       png  => 4,
+                       jp2k => 3,
+                       webp => 4,
+                       heic => 3,
+                       avif => 3,
+                       jxl  => 5,
+               },
+       },
+       speed_encode => {
+               name => 'single-core encode',
+               score => {
+                       jpeg => 5,
+                       png  => 3,
+                       jp2k => 4,
+                       webp => 4,
+                       heic => 3,
+                       avif => 2,
+                       jxl  => 5,
+               },
+       },
+       speed_decode => {
+               name => 'single-core decode',
+               score => {
+                       jpeg => 5,
+                       png  => 5,
+                       jp2k => 4,
+                       webp => 5,
+                       heic => 3,
+                       avif => 3,
+                       jxl  => 5,
+               },
+       },
+       speed_parallel => {
+               name => 'pararellizable',
+               score => {
+                       jpeg => 2,
+                       png  => 2,
+                       jp2k => 4,
+                       webp => 2,
+                       heic => 4,
+                       avif => 4,
+                       jxl  => 5,
+               },
+       },
+       limits => {
+               score => {
+                       jpeg => 3,
+                       png  => 4,
+                       jp2k => 5,
+                       webp => 2,
+                       heic => 4,
+                       avif => 4.5,
+                       jxl  => 5,
+               },
+       },
+       max_dimensions => {
+               name => 'maximum image dimensions',
+               data => {
+                       jpeg => '65k²',  # 2**16
+                       png  => '2G²',   # 2**31
+                       jp2k => '4G²',   # 2**32
+                       webp => '16k²',  # 2**14
+                       heic => '8k×4k+', # 2**13
+                       avif => '8k×4k+',
+                       jxl  => '1G²',   # 2**30
+               },
+       },
+       max_bitdepth => {
+               name => 'precision (max. bit depth)',
+               data => {
+                       jpeg => 8,
+                       png  => 16,
+                       jp2k => 38,
+                       webp => 8,
+                       heic => 10,
+                       avif => 10,
+                       jxl  => 32,
+               },
+       },
+       color_444 => {
+               name => 'can do (lossy) 4:4:4',
+               score => {
+                       jpeg => 'y',
+                       png  => 'y',
+                       jp2k => 'y',
+                       webp => 'n',
+                       heic => 'n',
+                       avif => 'y',
+                       jxl  => 'y',
+               },
+       },
+       hdr => {
+               name => 'wide gamut/HDR',
+               score => {
+                       jpeg => 'n',
+                       png  => 'y',
+                       jp2k => 'y',
+                       webp => 'n',
+                       heic => 'y',
+                       avif => 'y',
+                       jxl  => 'y',
+               },
+       },
+       max_channels => {
+               name => 'maximum number of channels',
+               data => {
+                       jpeg => 4, # cmyk
+                       png  => 4, # cmyk
+                       jp2k => 2**15,
+                       webp => 4,
+                       heic => 5,
+                       avif => 5,
+                       jxl  => 4099,
+               },
+       },
+       features => {
+               score => {
+                       jpeg => 2,
+                       png  => 3,
+                       jp2k => 4,
+                       webp => 2,
+                       heic => 4,
+                       avif => 4,
+                       jxl  => 5,
+               },
+       },
+       animation => {
+               name => 'supports animation',
+               score => {
+                       jpeg => 'n',
+                       png  => 'y',
+                       jp2k => 'n',
+                       webp => 'y',
+                       heic => 'y',
+                       avif => 'y',
+                       jxl  => 'y',
+               },
+               data => {
+                       jpeg => 'MJPEG',
+                       png  => 'APNG',
+                       jp2k => 'MJP2',
+               },
+       },
+       progressive => {
+               name => 'progressive decoding',
+               score => {
+                       jpeg => 4,
+                       png  => 2,
+                       jp2k => 5,
+                       webp => 'n',
+                       heic => 'n',
+                       avif => 'n',
+                       jxl  => 5,
+               },
+       },
+       alpha => {
+               name => 'alpha transparency',
+               score => {
+                       jpeg => 'n',
+                       png  => 'y',
+                       jp2k => 'y',
+                       webp => 'y',
+                       heic => 'y',
+                       avif => 'y',
+                       jxl  => 'y',
+               },
+       },
+       depthmap => {
+               name => 'depth map',
+               score => {
+                       jpeg => 'n',
+                       png  => 'n',
+                       jp2k => 'n',
+                       webp => 'n',
+                       heic => 'y',
+                       avif => 'y',
+                       jxl  => 'y',
+               },
+       },
+       overlays => {
+               name => 'overlays',
+               score => {
+                       jpeg => 'n',
+                       png  => 'n',
+                       jp2k => 'n',
+                       webp => 'n',
+                       heic => 'y',
+                       avif => 'y',
+                       jxl  => 'y',
+               },
+       },
+       authoring => {
+               name => 'authoring workflow suitability',
+               score => {
+                       jpeg => 2,
+                       png  => 3,
+                       jp2k => 3,
+                       webp => 2,
+                       heic => 2,
+                       avif => 2,
+                       jxl  => 5,
+               },
+       },
+       reencode => {
+               name => 'generation loss resilience',
+               score => {
+                       jpeg => 4,
+                       png  => 'n/a',
+                       jp2k => 3,
+                       webp => 2,
+                       heic => 3,
+                       avif => 3,
+                       jxl  => 4,
+               },
+               data => {
+                       png  => 'n/a',
+               },
+       },
+       compat_jpeg => {
+               name => 'lossless JPEG recompression',
+               score => {
+                       jpeg => 0,
+                       png  => 'n',
+                       jp2k => 'n',
+                       webp => 'n',
+                       heic => 'n',
+                       avif => 'n',
+                       jxl  => 'y',
+               },
+               data => {
+                       jpeg => 'n/a',
+               },
+       },
+       royalties => {
+               name => 'royalty-free',
+               score => {
+                       jpeg => 'y',
+                       png  => 'y',
+                       jp2k => 'y',
+                       webp => 'y',
+                       heic => 'n',
+                       avif => 'y',
+                       jxl  => 'y',
+               },
+       },
+},
+}
diff --git a/codec.plp b/codec.plp
new file mode 100644 (file)
index 0000000..2cc454a
--- /dev/null
+++ b/codec.plp
@@ -0,0 +1,50 @@
+<(common.inc.plp)><:
+
+Html({
+       title => 'Codecs',
+       version => '1.0',
+       description => [
+       ],
+       keywords => [qw'
+       '],
+       stylesheet => [qw'light circus dark red'],
+       data => [qw'codec.inc.pl'],
+});
+
+my $info = do 'codec.inc.pl';
+$info and %{$info} > 1 or Abort("cannot open operator include", 500, $@ // $!);
+
+my %BOOLSCORE = (y => 5, n => 1);
+:>
+<h1>Image codecs</h1>
+
+<div class="section">
+<table class="mapped">
+       <col><colgroup span=2><colgroup span=2><colgroup span=3>
+<thead><tr><th rowspan=2>feature
+<:
+my @codecs = sort {
+       $info->{codec}->{$a}->{available} <=> $info->{codec}->{$b}->{available}
+} keys $info->{codec}->%*;
+
+print '<th>', $_->{name} for @{$info->{codec}}{@codecs};
+print "\n<tr>";
+print '<td>', $_->{available} for @{$info->{codec}}{@codecs};
+say '</thead>';
+
+print '<tbody>';
+for my $feat (sort keys %{$info->{feature}}) {
+       my $featinfo = $info->{feature}->{$feat};
+       printf '<tr><th>%s', $featinfo->{name} // $feat;
+       printf('<td class="l%d">%s',
+               (map { $_ && $BOOLSCORE{$_} || $_ || 0 } $featinfo->{score}->{$_}),
+               $featinfo->{data}->{$_} // (map {
+                       $BOOLSCORE{$_} ? ($_ eq 'y' ? '✔' : '✘') : '•' x ($_ - 1)
+               } $featinfo->{score}->{$_}),
+       ) for @codecs;
+       say '</td>';
+}
+
+:></table>
+</div>
+