Automatically compress images to your Pull Requests with this GitHub Action

The folks at Calibre have release a GitHub Action named “Image Actions” and I must say, it looks amazing insane:

Image actions will automatically compress jpeg and png images in GitHub Pull Requests.

  • Compression is fast, efficient and lossless
  • Uses mozjpeg + libvips, the best image compression available
  • Runs in GitHub Actions, so it’s visible to everyone

Never ship unoptimised graphics again!

Once the workflow is added to your repo, Compression levels and source paths exclusions can easily be configured using a .github/calibre/image-actions.yml file:

jpeg:
  quality: 80
png:
  quality: 80
ignorePaths:
  - "node_modules/**"

Calibre Blog: Automatically compress images on Pull Requests →
GitHub Actions Marketplace: Image actions →

🤔 In case you’re wondering why you should compress your images be sure to read Addy Osmani’s free ebook “Essential Image Optimization”

Unraveling the JPEG

“Unraveling the JPEG” is a great deep dive into the JPEG image format.

This article is about how to decode a JPEG image. In other words, it’s about what it takes to convert the compressed data stored on your computer to the image that appears on the screen. It’s worth learning about not just because it’s important to understand the technology we all use everyday, but also because, as we unravel the layers of compression, we learn a bit about perception and vision, and about what details our eyes are most sensitive to.

Comes with interactive editors which allow you to adjust the JPEG data, and directly see how it affects the image.

Great stuff to play with!

Unraveling the JPEG →

Native image lazy-loading for the web with [loading="lazy"]

Addy Osmani, on an upcoming web feature which is about to land in Chrome 75:

The loading attribute allows a browser to defer loading offscreen images and iframes until users scroll near them. loading supports three values:

  • lazy: is a good candidate for lazy loading.
  • eager: is not a good candidate for lazy loading. Load right away.
  • auto: browser will determine whether or not to lazily load.

Not specifying the attribute at all will have the same impact as setting loading=auto.

You can also feature-detect is using this little snippet:

if ('loading' in HTMLImageElement.prototype) { 
    // Browser supports `loading`..
} else {
   // Fetch and apply a polyfill/JavaScript library for lazy-loading instead.
}

In case the browser does not support it natively, you could load up a script that uses IntersectionObserver to lazyload images.

Native image lazy-loading for the web →

👨‍🔬 Feeling experimental? You can try it out in Chrome 74 by enabling “Enable lazy frame loading” and “Enable lazy image loading” through chrome://flags

Lazy Loading images with IntersectionObserver

Smashing Magazine has an extensive article on using the aforementioned IntersectionObserver to lazy load image assets on your page.

The article first explains the difference between a regular Event and an Observer, before diving into the IntersectionObserver.

const images = document.querySelectorAll('[data-src]');
const config = {
  rootMargin: '0px 0px 50px 0px',
  threshold: 0
};
let loaded = 0;

let observer = new IntersectionObserver(function (entries, self) {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // console.log(`Image ${entry.target.src} is in the viewport!`);
      preloadImage(entry.target);
      // Stop watching and load the image
      self.unobserve(entry.target);
    }
  });
}, config);

images.forEach(image => {
  observer.observe(image);
});

Now You See Me: How To Defer, Lazy-Load And Act With IntersectionObserver

How Discord Resizes 150 Million Images Every Day with Go and C++

Great in-depth writeup on how Discord struggled to resizing lots of images via their Image Proxy Service:

As Discord grew, the Image Proxy started to show signs of strain. The biggest problem was the Image Proxy did not have an even workload distribution, which hampered its throughput. Image proxying requests saw a wide variance of response times, with some taking multiple seconds to complete.

Eventually they created Lilliput, to resize a scale of images and generating first-frame stills of video:

Lilliput relies on mature, high-performance C libraries to do most of the work of decompressing, resizing and compressing images. It aims to do as little memory allocation as possible and especially not to create garbage in Go. As a result, it is suitable for very high throughput image resizing services.

Lilliput supports resizing JPEG, PNG, WEBP and animated GIFs. It can also convert formats. Lilliput also has some support for getting the first frame from MOV and WEBM videos.

How Discord Resizes 150 Million Images Every Day with Go and C++ →
Lilliput (GitHub) →

SQIP – SVG-Based Image Placeholder

In an in-depth analysis on how Medium loads up their images, José M. Pérez explains how their “blur-up technique” works:

  1. Display a resized version of the original at the original size, with a blur filter on top to hide the artifacts.
  2. Load in the bigger one.
  3. Once the big version is loaded, replace the small image with the big one.

Now, at Medium they don’t just swap out the small image for the big one, but animate between the two. For this they render everything on a <canvas> element. The author of the post has recreated the animation using CSS filters:

See the Pen Reproducing Medium loading image effect by José Manuel Pérez (@jmperez) on CodePen.

In a follow-up post on using SVGs as placeholders he researches how SVGs can be used as placeholders.

Now I must say his self-made results aren’t that satisfying … the jump between the lo-res SVG and the original is too big. What caught my eye in said article however, is the mention of SQIP which yields some very – very – nice results:

It makes use of Primitive to generate a SVG consisting of several simple shapes that approximate the main features visible inside the image, optimizes the SVG using SVGO and adds a Gaussian Blur filter to it. This produces a SVG placeholder which weighs in at only ~800–1000 bytes, looks smooth on all screens and provides an visual cue of image contents to come.

# Generate a SVG placeholder and print an example <img> tag to stdout
sqip input.jpg

# Save the placeholder SVG to a file instead of printing the <img> to stdout
sqip -o output.svg input.jpg

# Customize the number of primitive SVG shapes (default=8) to influence bytesize or level of detail
sqip -n 4 input.jpg

There’s also a Node implementation available.

SQIP – SVG-Based Image Placeholder →

UPDATE 2017.12.03: There’s an article available over at perfplanet on how to combine SQIP with the Intersection Observer.

Essential Image Optimization

Essential Image Optimization is an free and online eBook by Addy Osmani:

Images take up massive amounts of internet bandwidth because they often have large file sizes. According to the HTTP Archive, 60% of the data transferred to fetch a web page is images composed of JPEGs, PNGs and GIFs. As of July 2017, images accounted for 1.7MB of the content loaded for the 3.0MB average site.

Per Tammy Everts, adding images to a page or making existing images larger have been proven to increase conversion rates. It’s unlikely that images will go away and so investing in an efficient compression strategy to minimize bloat becomes important.

Essentially Addy pushes forward two key factors:

  1. We should all be automating our image compression
  2. Everyone should be compressing their images efficiently

Essential Image Optimization →

PngPong Image Manipulation Library

For the recent UK Elections the folks The Guardian wanted to show big images along with their push notifications. Being bandwidth-aware they wanted a solution in which they could use a template image (which could then be cached) and then draw some stuff onto it. Only problem: Service Workers don’t have access to the Canvas API.

Enter PngPong, an basic image manipulation library, they’ve created:

PngPong is a very, very basic replacement for the Canvas API in environments that do not support it – primarily, service workers. Instead, it manually manipulates the bytes of a PNG file to copy the contents of another image, or draw basic shapes (currently only rectangles).

Their code to handle push notifications looked something like this:

self.addEventListener('push', (e) => {
    e.waitUntil(
        caches.match('/notification_template.png')
        .then((res) => res.arrayBuffer())
        .then((arrayBuffer) => {
            addResultsToTemplate(arrayBuffer, e.data);
            return blobToDataURL(new Blob([arrayBuffer]))
        })
        .then((imageURL) => {
            return self.registration.showNotification("Title", {
                image: imageURL
            })
        })
    )
})

Inside addResultsToTemplate they heavy lifting would be done by PngPong, drawing some small rectangles onto the template pictured below, yielding a push notification as pictured at the top of this post.


The template used

Example PngPong usage:

import {
  PngPong,
  PngPongShapeTransformer,
  PngPongImageCopyTransformer
} from 'png-pong';

// Create new PngPong instance
const pngPong = new PngPong(imageArrayBuffer);

// Draw a 30px red square 10px from the top and 10px from the left
const shape = new PngPongShapeTransformer(pngPong);
shape.drawRect(10, 10, 30, 30, [255, 0, 0])

// Copy a 50x50 image 10px from the top left of the source image,
// and draw it 30px into our target image. 
const toCopyFrom = new ArrayBuffer();
const imageCopy = new PngPongImageCopyTransformer(toCopyFrom, pngPong);
imageCopy.copy(10, 10, 50, 50, 30, 30);

// Run the transforms
pngPong.run();

PngPong (GitHub) →
PngPong Use Case: Generating Images in JavaScript Without Using the Canvas API →

Opera 46 and Chrome 59 now support APNG (Animated PNG)

This part of the Opera 46 release notes got me very excited:

Opera now supports animated PNG, or APNG for short. APNG is a file format that works similarly to GIF. The difference is that APNG is smaller and supports both 24-bit images and 8-bit transparency.

Ever since APNG landed in Firefox (10 years ago to be precise, with the release of Firefox 3) I’ve been waiting for other browsers to implement support for this format too: animated images with true transparency, what’s not to like?!

Great to see that Opera now supports it (again). Safari already supports it, as does the latest version of Chrome (Chrome 59).

Edge has no support, but you can upvote the issue on UserVoice (which you should).

What’s new in Chromium 59 and Opera 46 for Developers →
APNG Demo →

Easily manipulate images in PHP with spatie/image

Great new package by the folks at Spatie to working with images in PHP, powered by Glide. Glide itself is great, but uses an URL based approach (which has its benefits); yet in most cases I find myself using a code based approach. This is where spatie/image comes into play:

spatie/image wraps up Glide so all its methods can be used using PHP code.

Like so:

Image::load('new-york.jpg')
    ->sepia()
    ->blur(50)
    ->save();

Easy peasy 🙂

spatie/image
spatie/image Documentation →
spatie/image introductory post →