--- /dev/null
+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',
+ },
+ },
+},
+}
--- /dev/null
+<(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>
+