Nginx and the Image format wars.

We've talked about the videos, we've talked about the images. Now we talk about the web server, Nginx.

In this entry, we're going to get crazy technical with how we deploy all of these new image formats that are being introduced in what feels like "The Great Video/Image War" to finally put GIFs, JPEGs, PNGs, and MP4s to bed. But instead, we're going to talk about a section that seems skipped a lot. How do we actually implement these new formats? Because if we do a review on an image format, it's good. Then as bloggers, it would be hypocritical to not apply these formats to our site. It however opens up questions. How do we maintain the security of our readers while at the same time giving them the very best image with the lowest bandwidth conceivable? Thankfully since we have a VPS a lot of the encoding is handled via CRON. While a lot of the deciding factors of which browser gets served and what file are all in the hands of NGINX web daemon.

Read on if you want to know more.

Deployment.

Now, if we were on a shared hosting plan, we would have to rely on whatever libraries that shared hosting provider would have in place plus whatever PHP based scripts that can access those libraries. We -used- to use EWWW image optimizer back in the days when we were on shared hosting. but the settings it uses for WebP conversion were rather poor. To top it all off it will probably take years if it all for them to implement AV1F image formats.

Thankfully NGINX allows for such magical levels of fuckery. Combine this with CRON jobs and you have an unstoppable force that never has to leave the VPS and expose the users data.

Disclaimer.

Here at S-Config.com before you perform any script modifications or additions to your operating system you should always back up your data to preserve anything that happens from here. S-Config.com is not responsible for any damages that occur including, mental, physical, or technological. Always act responsibly and double-check your data as not even WE are experts in these matters.

Nginx MIME action.

You've probably noticed in the network logs of our Tor/Onion browser that we are requesting a PNG or JPG but the "Type" category is a WebP. This is because the Nginx web server is intercepting the request from my blog and converting the file path on the flow.

Let's dive into Nginx to get this whole process going here.

First, we need to tell Nginx what the *.avif file really is. So we need to open our mime.types within the root of Nginx and add the following lines:

image/png png         apng;
image/webp            webp;
image/avif            avif;
image/avif-sequence   avifs;
image/jxl             jxl;

and save.

Future versions of Nginx may already have these format-types set. But as of 1.19.x We certainly know AVIF is not a part of Nginx.

Moving onto the real action.

-=OLD METHOD=- Nginx /sites-available/ redirection magic.

This is probably the point where you want to go to your /nginx/sites-available folder and find the site profile that you wish to modify. This is all done after your locations are defined within your profile.

    # for PNG and JPG - This will test if a browser can run AVIF and fall back to WEBP

    set $webp_suffix "";
    if ($http_accept ~* "webp") {
        set $webp_suffix ".webp";
    }
    if ($http_accept ~* "jxl") {
        set $webp_suffix ".jxl";
    }
    if ($http_accept ~* "avif") {
        set $webp_suffix ".avif";
    }

    location ~ \.(jpg|png)$ {
       expires 1y;
       add_header Cache-Control "public, no-transform";
       add_header Vary "Accept-Encoding";
       try_files $uri$webp_suffix $uri $uri/ =404;
    }

    # for GIF - This will test for WEBP and then fallback to APNG files.


     set $gifv_suffix "";
    if ($http_accept ~* "apng") {
        set $gifv_suffix ".apng";
    }
    if ($http_accept ~* "webp") {
        set $gifv_suffix ".webp";
    }

    if ($http_accept ~* "avif") {
        set $gifv_suffix ".avif";
    }

    location ~ \.(gif)$ {
       expires 1y;
       add_header Cache-Control "public, no-transform";
       add_header Vary "Accept-Encoding";
       try_files $uri$gifv_suffix $uri $uri/ =404;
    }

This is essentially a modified version of the Nginx script to force WebP if it's available. It firsts asks the client if they can accept a certain format such as WEBP first, then AVIF . This way, If a Tor/Onion user enters my site they will at least get the advantage of WebP. If a Google Chrome user enters my site then it will bypass WebP imaging and go right for AVIF.

Finally, the script TRIES the file to see if an appropriate AVIF or WEBP is out there. for example, if our blog has a link to "./logo.png" it will search for "./logo.png.avif" or "./logo.png.webp" whichever the browser admits to accepting. If the browser does not respond to Nginx or the user is operating on a Potato for a PC then Nginx will naturally serve them the default .jpg or .png files.

-=NEW METHOD=- Configuring NGINX better for image conversion.

In my /etc/nginx/conf.d folder we made a text file for images.conf . By default, your nginx.conf should have a line to scan for any text files in the /etc/nginx/conf.d folder.

## Chrome/65 accept : image/webp,image/apng,image/*,*/*;q=0.8
## Firefox/58 accept: */*
## iPhone5s accept: */*
map $http_accept $img_suffix {
"~*jxl" ".jxl";
"~*avif" ".avif";
"~*webp" ".webp";
"~*apng" ".apng";
}

## https://github.com/cdowdy/Nginx-Content-Negotiation/blob/master/nginx.conf
map $msie $cache_control {
"1" "private";
}
map $msie $vary_header {
default "Accept";
"1" "";
}

Once i've saved this script we went back to our /etc/nginx/sites-available/WEBSITE text file.

# Media: images, icons, video, audio, ETC
location ~* \.(?:jpg|jpeg|jxl|avif|avifs|gif|png|ico|cur|gz|svg|bmp|tiff|svgz|mp4|ogg|ogv|webm|htc)$ {
expires 1M;
add_header Vary $vary_header;
add_header Cache-Control $cache_control;
add_header Cache-Control "public";
## Comment to enable the access-accept.log scraper:
access_log off;
## Hotlink Protect
valid_referers none blocked ~IPADDRESS ~.WEBSITE. ~.google. ~.bing. ~.yahoo. ~.facebook. ~.duckduckgo. ~.twitter. ~.pinterest. ~.instagram. server_names ~($host) ;
if ($invalid_referer) {
return 403;
}

try_files $uri$img_suffix $uri =404;
}

This made life way easier because then we could enable hotlink protection AND try for JXL first because it's beta right now. AVIF second, WEBP files third, and (If it's animated) resort to APNG much later. Since the other formats will not have WEBP such as fonts as an example it will pass along perfectly fine underneath the same filter. The added benefit to doing it this way is we can enable hotlink support easier with NGINX too so people can't just slap your images on their website using your bandwidth all the while.

Apache time.

We haven't really tested this as we moved away from apache long ago. But as this too is based on the WebP format you could just force everyone to AVIF all the same through the .htaccess file of your shared provider:

AddType image/avif .avif

<IfModule mod_rewrite.c>
  RewriteEngine On

  RewriteCond %{HTTP_ACCEPT} image/avif
  RewriteCond %{REQUEST_FILENAME} -f
  RewriteCond %{REQUEST_FILENAME}.avif -f
  RewriteRule ^/?(.+?)\.(jpe?g|png|gif)$ /$1.$2.avif [NC,T=image/avif,E=EXISTING:1,E=ADDVARY:1,L]

  <IfModule mod_headers.c>
     <FilesMatch "(?i)\.(jpe?g|png|gif)$">
     Header append "Vary" "Accept"
  </FilesMatch>
 </IfModule>
</IfModule>

CRON time!

Alright, so we addressed if the files are there or not. if we had a small website we could probably use a service like "squoosh" for example. No need to install files onto the server. Just convert the files you need to upload with the proper extensions and be done with it. However, as the image format wars wage onward and we like trying new things we needed a methodology mass conversion.

We're about to show our CRON job. We are not fantastic at scripting so to those Linux users that are slapping their foreheads with the way we wrote it. Let us know. We would very much like to improve as well.

#!/bin/bash

Username=WebUser

cd /home/of/your/www/
shopt -s globstar
for i in **/*.jpg;
do
  if [ ! -f ${i}.flag ]; then
    echo "optimizing jpg ${i} "
    /usr/bin/nice -n 20 sudo -u $Username /usr/bin/jpegoptim --strip-all ${i}
    /usr/bin/nice -n 20 sudo -u $Username /usr/bin/convert -strip -sampling-factor 4:2:0 -interlace Plane -quality 75% -colorspace RGB -define jpeg:dct-method=float ${i} ${i}

    if [ ! -f ${i}.avif ]; then
      echo "optimizing avif ${i} "
      /usr/bin/nice -n 20 sudo -u $Username /usr/bin/avifenc --min 0 --max 63 --minalpha 0 --maxalpha 63 -a end-usage=q -a cq-level=18 -a tune=ssim -s 0 -j 1 "${i}" "${i}".avif
      fileorg=$(stat -c%s "${i}")
      filepass=$(stat -c%s "${i}".avif)
      if (( filepass > fileorg )); then
           echo "${i}  Destroying AVIF as JPG retained better compression!"
           /usr/bin/nice -n 20 sudo -u $Username rm "${i}".avif
      else
           echo "${i} AVIF is smaller then original JPG - Saving!"
      fi

    if [ ! -f "${i}".jxl ]; then
      echo "optimizing jxl ${i}"
      /usr/bin/nice -n 20 sudo -u $Username /usr/local/bin/cjxl "${i}" -d 1.0 --lossless_jpeg=1 -e 9 --brotli_effort=11 "${i}".test1
      /usr/bin/nice -n 20 sudo -u $Username /usr/local/bin/cjxl "${i}" -d 0.0 --lossless_jpeg=1 -e 9 --brotli_effort=11 "${i}".test
      filestart=$(stat -c%s "${i}".test1)
      fileend=$(stat -c%s "${i}".test2)
      if (( filestart < fileend )); then echo "Keeping Original Algorithim for ${i}" /usr/bin/nice -n 20 sudo -u $Username mv "${i}".test1 "${i}".test3 /usr/bin/nice -n 20 sudo -u $Username rm "${i}".test2 else echo "Using Lossless Algorithim for ${i}" /usr/bin/nice -n 20 sudo -u $Username mv "${i}".test2 "${i}".test3 /usr/bin/nice -n 20 sudo -u $Username rm "${i}".test1 fi fileorg=$(stat -c%s "${i}") filepass=$(stat -c%s "${i}".test3) if (( filepass > fileorg )); then
           echo "${i}  Destroying JXL as PNG retained better compression!"
           /usr/bin/nice -n 20 sudo -u $Username rm "${i}".test3
      else
           echo "${i} jxl is smaller then original PNG - Saving!"
           /usr/bin/nice -n 20 sudo -u $Username mv "${i}".test3 "${i}".jxl
      fi
   fi


    if [ ! -f ${i}.webp ]; then
      echo "optimizing webp ${i} "
      /usr/bin/nice -n 20 sudo -u $Username /usr/bin/cwebp "${i}" -q 90 -m 6 -pass 10 -o "${i}".webp
      fileorg=$(stat -c%s "${i}")
      filepass=$(stat -c%s "${i}".webp)
      if (( filepass > fileorg )); then
           echo "${i}  Destroying WEBP as JPG retained better compression!"
           /usr/bin/nice -n 20 sudo -u $Username rm "${i}".jxl
      else
           echo "${i} WEBP is smaller then original JPG - Saving!"
      fi
    fi

    echo "adding flag ${i}"
    /usr/bin/nice -n 20 sudo -u $Username echo "Optimization Complete Flag." >> ${i}.flag
    /bin/chown $Username:$Username ${i}.flag
  fi
done

for i in **/*.png;
do
  if [ ! -f ${i}.flag ]; then
    echo "optimizing png ${i} "
    /usr/bin/nice -n 20 sudo -u $Username /usr/bin/optipng -o7 -zm1-9 -strip all ${i}

    if [ ! -f ${i}.avif ]; then
      echo "processing avif ${i}"
      /usr/bin/nice -n 20 sudo -u $Username /usr/bin/avifenc --min 0 --max 63 --minalpha 0 --maxalpha 63 -a end-usage=q -a cq-level=18 -a tune=ssim -s 0 -j 1 "${i}" "${i}".avif
      fileorg=$(stat -c%s "${i}")
      filepass=$(stat -c%s "${i}".avif)
      if (( filepass > fileorg )); then
           echo "${i}  Destroying AVIF as PNG retained better compression!"
           /usr/bin/nice -n 20 sudo -u $Username rm "${i}".avif
      else
           echo "${i} AVIF is smaller then original PNG - Saving!"
      fi
    fi

    if [ ! -f "${i}".jxl ]; then
      echo "optimizing jxl ${i}"
      /usr/bin/nice -n 20 sudo -u $Username /usr/local/bin/cjxl "${i}" -d 1.0 --lossless_jpeg=1 -e 9 --brotli_effort=11 "${i}".test1
      /usr/bin/nice -n 20 sudo -u $Username /usr/local/bin/cjxl "${i}" -d 0.0 --lossless_jpeg=1 -e 9 --brotli_effort=11 "${i}".test
      filestart=$(stat -c%s "${i}".test1)
      fileend=$(stat -c%s "${i}".test2)
      if (( filestart < fileend )); then echo "Keeping Original Algorithim for ${i}" /usr/bin/nice -n 20 sudo -u $Username mv "${i}".test1 "${i}".test3 /usr/bin/nice -n 20 sudo -u $Username rm "${i}".test2 else echo "Using Lossless Algorithim for ${i}" /usr/bin/nice -n 20 sudo -u $Username mv "${i}".test2 "${i}".test3 /usr/bin/nice -n 20 sudo -u $Username rm "${i}".test1 fi fileorg=$(stat -c%s "${i}") filepass=$(stat -c%s "${i}".test3) if (( filepass > fileorg )); then
           echo "${i}  Destroying JXL as PNG retained better compression!"
           /usr/bin/nice -n 20 sudo -u $Username rm "${i}".test3
      else
           echo "${i} jxl is smaller then original PNG - Saving!"
           /usr/bin/nice -n 20 sudo -u $Username mv "${i}".test3 "${i}".jxl
      fi
   fi




    if [ ! -f ${i}.webp ]; then
      echo "processing webp ${i}"
      /usr/bin/nice -n 20 sudo -u $Username /usr/bin/cwebp "${i}" -q 90 -m 6 -pass 10 -o "${i}".test1
      /usr/bin/nice -n 20 sudo -u $Username /usr/bin/cwebp "${i}" -near_lossless 40 -q 90 -m 6 -pass 10 -o "${i}".test2
      filestart=$(stat -c%s "${i}".test1)
      fileend=$(stat -c%s "${i}".test2)
      if (( filestart > fileend )); then
           echo "Keeping Original Algorithim"
           /usr/bin/nice -n 20 sudo -u $Username mv "${i}".test1 "${i}".test3
           /usr/bin/nice -n 20 sudo -u $Username rm "${i}".test2
      else
           echo "Using near lossless 40 Algorithim"
           /usr/bin/nice -n 20 sudo -u $Username mv "${i}".test1 "${i}".test3
           /usr/bin/nice -n 20 sudo -u $Username rm "${i}".test2
      fi

      fileorg=$(stat -c%s "${i}")
      filepass=$(stat -c%s "${i}".test3)
      if (( filepass > fileorg )); then
           echo "Destroying WebP as PNG retained better compression!"
           /usr/bin/nice -n 20 sudo -u $Username rm "${i}".test3
           /usr/bin/nice -n 20 sudo -u $Username rm "${i}".webp
      else
           echo "WebP is smaller then original PNG - Saving!"
           /usr/bin/nice -n 20 sudo -u $Username mv "${i}".test3 "${i}".webp
      fi

    fi
    echo "adding flag ${i}"
    /usr/bin/nice -n 20 sudo -u $Username echo "Optimization Complete Flag." >> ${i}.flag
    /bin/chown $Username:$Username ${i}.flag
  fi
done

for i in **/*.gif;
do
  if [ ! -f ${i}.flag ]; then

    if [ ! -f ${i}.webp ]; then
      echo "processing webp. ${i}"
      /usr/bin/nice -n 20 sudo -u $Username /usr/bin/gif2webp -q 90 -m 6 ${i} -o ${i}.webp
    fi

    if [ ! -f ${i}.avif ]; then
      /usr/bin/nice -n 20 sudo -u $Username /usr/bin/ffmpeg -i ${i} -hide_banner -loglevel panic -pix_fmt yuv420p ${i}.y4m
      echo "processing to animated avif ${i}"
      if [ -f ${i}.y4m ]; then
        echo "processing to animated avif ${i}"
        /usr/bin/nice -n 20 sudo -u $Username /usr/bin/avifenc --min 0 --max 63 -a end-usage=q -a cq-level=18 -a tune=ssim -s 0 -j 1 ${i}.y4m ${i}.avif
        if [ ! -f ${i}.apng ]; then
          echo "appending animated apng ${i}"
          /usr/bin/nice -n 20 sudo -u $Username /usr/bin/gif2apng -z0 ${i} ${i}.apng
        fi
      fi
    fi

    if [ ! -f ${i}.y4m ]; then
      if [ ! -f ${i}.apng ]; then
        echo "processing non-animated apng ${i}"
        /usr/bin/nice -n 20 sudo -u $Username /usr/bin/gif2apng -z0 ${i} ${i}.apng
      fi
    fi
    rm -f ${i}.y4m
    echo "adding flag ${i}"
    /usr/bin/nice -n 20 sudo -u $Username echo "Optimization Complete Flag." >> ${i}.flag
    /bin/chown $Username:$Username ${i}.flag
  fi
done

for i in **/*.webp;
do if [ ! -f ${i:0:-5} ]; then
echo "removing ${i} as ${i:0:-5} is no longer existing."
rm ${i}
fi
done

for i in **/*.avif;
do if [ ! -f ${i:0:-5} ]; then
echo "removing ${i} as ${i:0:-5} is no longer existing."
rm ${i}
fi
done

for i in **/*.flag;
do if [ ! -f ${i:0:-5} ]; then
echo "removing ${i} as ${i:0:-5} is no longer existing."
rm ${i}
fi
done

for i in **/*.apng;
do if [ ! -f ${i:0:-5} ]; then
echo "removing ${i} as ${i:0:-5} is no longer existing."
rm ${i}
fi
done

Wow, that's a lot of scripting action. Let us try to break it down a little for you.

In the beginning!
#!/bin/bash 

Username=WebUser 

cd /home/of/your/www/
File size checking.

The first part is probably the area you want to point to your VPS if you were to use this script. We are using "user control" because scripts tend to run as root of the OS which would be rather problematic in terms of access rights depending on your web browser.

 ....
      fileorg=$(stat -c%s "${i}")
      filepass=$(stat -c%s "${i}".avif)
      if (( filepass > fileorg )); then
           echo "${i}  Destroying AVIF as JPG retained better compression!"
           /usr/bin/nice -n 20 sudo -u $Username rm "${i}".avif
      else
           echo "${i} AVIF is smaller then original JPG - Saving!"
      fi
....

You will probably notice a pattern in our scripting where we run a file size comparison between the original file and what we just compressed. You may think that this is a giant waste of time. But in the great image format wars. PNG can still potentially destroy all of the new formats coming out. How is this possible? None of the new codecs know how to limit their pallet for efficiency.

Allow us to give an example:

S-Config Logo 15 color with transparancy.This is my 512x512 FavIcon logo that is normally saved in .SVG format. I took the vector and output to raster at 512px-512px 4-bit color. 15 colors + transparency just to make things interesting.
after optimizing with optiPNG, and passing it through my script. Here is the results:

-rw-r--r-- 1 user group 13830 Sep 22 01:14 Bintu-Logo-2.png.webp
-rw-r--r-- 1 user group 15344 Sep 22 01:12 Bintu-Logo.png
-rw-r--r-- 1 user group 20317 Sep 22 01:12 Bintu-Logo.png.avif
-rw-r--r-- 1 user group 7450 Sep 22 01:19 Bintu-Logo.png.jxl
-rw-r--r-- 1 user group 31510 Sep 22 01:15 Bintu-Logo.png.webp
-rw-r--r-- 1 user group 21370 Sep 22 01:21 Bintu-Logo.svg

  • So, my original vector weighs in at around 21KB for Bintu-Logo.svg which isn't bad and it was designed in InkScape 0.9.0.
  • My 4-bit PNG Bintu-Logo.png is coming in at 15KB! Using OptiPNG version 0.7.7.
  • My WebP Bintu-Logo.png.webp is coming in at 31KB! Double the size using the flags -q 90 -m 6 -pass 10 -o. Using CWebP version 1.2.0
  • AVIF Bintu-Logo.png.avif cannot beat PNG and comes in at 20KB using flags --min 0 --max 63 --minalpha 0 --maxalpha 63 -a end-usage=q -a cq-level=18 -a tune=ssim -s 0 -j 1 also using CWebP version 1.2.0
  • JXL Bintu-Logo-png.jxl comes in at a horrific 80KB! Using cjxl Version v0.8.0 8c7c9a0c with flags -d 1.0 --lossless_jpeg=1 -e 9 --brotli_effort=11
  • HOWEVER!!!! JXL Bintu-Logo-png.jxl comes in at a horrific 80KB! Using cjxl Version v0.8.0 8c7c9a0c with flags -d 0.0 --lossless_jpeg=1 -e 9 --brotli_effort=11 - making it the superior tool for single palette works. JXL is wild this way which is why we have to try two separate recipes to ensure we're getting the very best.
  • The only one that could possibly dethrone my 4-bit PNG was a different WebP at 14KB. And that's only because of the -near_lossless 40 flag that does not work all of the time! Especially on pictures! Even with smaller PNG files such as system icons that flag sometimes does not stand a chance.

This is a massive setback for the image format wars because these new codecs treat everything as if the file was a photograph, instead of a screenshot, or a vector with limited colors. Then they lost before the war has even started.

We place conditions where the original file is somehow smaller than the next-generation codecs to keep the original file as those files are likely to not go anywhere in the next 20+ years. This isn't about pledging allegiance to Google to exclusively use WEBP. Everything comes down to bandwidth. A 4-color PNG file will naturally compress exceptionally well! So much so that it beats WEBP any day of the week. If it determines the PNG is still smaller then WEBP scraps the whole calculation.

Conversion of .JPG files.

The script begins with checking for a .jpg.flag file. This is for sanity reasons. We don't want to have the script constantly compressing the same jpg files over and over again. Then, following from what we learned about Jpeg compression from a previous blog we begin compression. If the flag isn't present we begin compressing our jpeg images within our site more.

/usr/bin/convert -strip -sampling-factor 4:2:0 -interlace Plane -quality 75% -colorspace RGB -define jpeg:dct-method=float ${i} ${i}

Deviating a little bit from our earlier blogs about non-destructively compressing jpeg files. You may find yourself running a social network and other types of public services which involve forcing you to compress jpeg files. Especially if you are accepting images from people with mobile phones where they kind of suck at compression. We decided to use ImageMagicks 'convert' feature to re-compress jpeg images into something that search engines would consider 'optimized.'

Keep in mind, although this command is lossy and destructive. It only happens once before the .flag file is dropped and thus is never accessed again. If you don't want to do this simply delete that line out of the script to maintain lossless optimization.

JPG <-> AVIF

Now, we're using colorists avifenc on my site as we can control the threads and memory usage which we simply could not do with Kagami's Go-Avif .

The command we keep on passing to AVIF is the following.

avifenc --min 0 --max 63 -a end-usage=q -a cq-level=18 -a tune=ssim -s 0 -j 1 ${i} ${i}.avif

--min 0 --max 63 --minalpha 0 --maxalpha 63 - This gives a full range as to how it can encode an avif file. by default you don't want to define min/max ranges all by itself or else you'll get blurry images which is why -a end-usage=q is requires to set the quality rate constant and -a cq-level=18 is our quality control level which is around 18. The lower you go the more lossless the image. Finally, we -a tune=ssim tune to SSIM for compatibility reasons over PNSR.

-s 0 which is speed; Even though just like our PNG command is overkill. But it's a background task on my VPS. We don't care.

-j 1 simply sets the amount of threads to 1. Again, the VPS can work all day in this. We simply don't care.

JPG <-> WEBP
/usr/bin/cwebp ${i} -q 90 -m 6 -pass 10 -o ${i}.webp

A lot of this was covered already in this blog article. Yes, we are passing a -q 90 to have some lossless destruction of our files. But we find this setting to be almost indistinguishable from the original file.

Dropping the flag.
 echo "adding flag ${i}" 
/usr/bin/nice -n 20 sudo -u $Username echo "Optimization Complete Flag." >> ${i}.flag 
/bin/chown $Username:$Username ${i}.flag

This is important because once the flag is dropped we don't have to waste CPU time on our VPS to ever have to process this file again.

Conversion of .PNG files.

Now as for my commands passed on PNG files. We discussed all of those options in a previous blog. We are using a more aggressive command to optimize our PNG files however we find this does not affect the VPS in any way as we set ourselves a low priority. If we were processing several hundred png files at a time we may ease off so that daily.cron can keep up. But for now, this is totally fine.

Special notes about PNG <-> WEBP conversion.

You will notice there are a lot of conditional statements when it comes to PNG to WEBP conversion. This is because even though WEBP compresses exceptionally well when it comes to 24-bit PNG files. It has difficulties when it comes to anything with a predefined pallet such as 8-bit PNG files. Because of this, we run two algorithms. One with a -near_lossless 40 flag and one without. The -near_lossless 40 flag helps out with 8-bit color-indexed PNG files. However, even then if the color palette is too low then PNG outperforms WEBP as mentioned above when discussing file size checking.

Conversion of .GIF files.

This is where the script gets a little crazy.

GIF <-> WEBP

This part is identical to how we convert everything else. Thankfully the WebP conversion utility can sense frames without issues.

GIF <-> AVIF

Because version 0.90 of avifenc can how to process animated movies as .avif. HOWEVER, we can't jump immediately from gif to avif.

/usr/bin/ffmpeg -i ${i} -hide_banner -loglevel panic -pix_fmt yuv420p ${i}.y4m

To accomplish this, we turn to ffmpeg to take a GIF and convert it into the .y4m format which is YUV4Mpeg to which a lot of people on the net tend to use this format as a 'master' for digital media as the format is totally uncompressed. And the only time I want to hear from ffmpeg in my cron.daily is if something happens during conversion.

/usr/bin/avifenc --min 0 --max 25 --minalpha 0 --maxalpha 25 --speed 0 ${i}.y4m ${i}.avif

Afterward, we can convert the .y4m to avif and get amazing compression results versus GIF while preserving all of the frames in the animation. After the conversion is complete we delete that y4m as quickly as possible as those files can get huge rather quickly depending on the length of the animated GIF you are pushing through.

GIF <-> APNG

Using the gif2apng utility for Linux allows us to make appropriate animated png files or .apng that can be served to those who cannot understand webp or avif.

Script cleanup.

Each time we are converting an image on this site we echo a .flag file. This is important because we initially run this script outside of CRON to it takes the time to process all of the .png and .jpg files with ridiculous settings for compression. On my site, it took a solid week before it was able to place a flag file on all of them. So it's a good idea to run the script first! We insert the script into our cron.daily so that it skips past all of the ".flag"ed files and only processes new files that come into this blog. Which is not as painful as doing the entire folder!

Finally, it does a reverse lookup on the original file. If we delete something in WordPress (or practically any other CMS script really) then the script will remove the files it has made as well keeping my directories clean.

Final thoughts.

Chrome Network View.

If you press "F12" on your favorite browser and then click on the "Network" tab in either firefox or chrome. You will see something magical happen. That even though the browser is requesting a .jpg or .png file. It's receiving the newer generation codecs based on a query if the browser can accept that format of the image. This ensures that we have total scalability and fall-back mechanisms in the event even the older browsers log in to my site.

We've currently enabled all of the possible formats depending on the browser. Firefox at the time of this posting only uses WebP whereas Google at this time uses AVIF. Only the nightly builds of Google and Firefox use JXL. It's true that saving the same image over and over again eats server space. But in this day and age, that's not as important as bandwidth and speed. To be able to have a fully loaded webpage on a phone where connection ranges from 5G broadband to GSM/CDMA 1x 19.2baud modems. It's not even a question of processor power because it's a still image. We may decide to drop formats in the future. But for now, feel free to switch browsers and try this site As we bounce between screenshots, artwork, and pictures.

The problem with new codecs is we're learning about these features just as you are. So if you come up with a better recipe, let us know. We'd love to try it out!

Thank you for checking out this site and may server protect you.

+++ END OF LINE.

1 thought on “Nginx and the Image format wars.

Leave a Comment to the Void