← Back to blog

Resizing and Optimizing Images using the Image Processing API Firebase Extensions

Darren Ackers

Lead Developer

Jacob Cable

Software Engineer

22nd November, 2022

Need a custom tool? Invertase can help

Tired of investing in off-the-shelf software that doesn't quite fit your business? Invertase can build solutions tailored to your exact needs. Tell us what you're looking for here and we'll be in touch.

Introduction

This blog post aims to introduce the new Image Firebase Extension by Invertase.

Here we will look at how to display Google Cloud Storage (GCS) images using the extension’s power and optimize them for a high level of performance for web or mobile.

Motivation

In the past decade, web technology has developed to an incredible standard. Techniques such as Server/Pre-rendered content now allow developers to produce incredibly fast and advanced applications that can often resemble their mobile counterparts.

Search Engine Optimisation (SEO)

As every aspect improves and businesses/startups are looking to capitalize on this new era of development, one area, in particular, has become just as important: SEO.

Google has made no secret that speed is now a direct contributing factor in how websites are ranked.

Commerce statistics show that speed directly affects users’ buying habits. If they take too long to load? Customers will have already moved on to a much more efficient and reliable source.

Why, Images?

So, why am I talking about users’ buying habits and Google rankings when we talk about Images? Well, that’s because you can optimize coding bundles, pre-render your content, and have all the keywords you need. But, with the slow performance, your ranking will still be affected.

Furthermore, as this is typically the heaviest element on your website – an efficient website will require efficient usage of imagery.

Ok, enough theory. Let’s get started!

I love traveling and would like to share pictures of inspiration for users to like and share. To save time finding my images for this article, I have downloaded some high-quality images from Pexels, make sure to check them out for your inspiration.

For developers with images stored in GCS, these images may be uploaded from various sources – through the SDK on a website or uploaded manually by a developer with access.

Looking at the GCS bucket, I have a few images that I would like to share!

Tip: Remember to select your security rules if working on a new bucket. The extension will assume these images are available.

These are the images I would like to display…

Note they are pretty heavy in terms of size. We’ll look at that a little later…

Security Rules

At this point, we should ensure that the images we would like to use with the extension are available for selection.

1. Navigate to the security rules for your storage bucket.

https://console.firebase.google.com/project/{PROJECT_ID}/storage/{BUCKET_ID}/rules

2. Add read access to where the images are stored:

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if false;
    }
    
    match /travelling/{allPaths=**} {
      allow read;
    }
  }
}

3. Finally, select Save.

Building the app

We are big fans of Astro here at Invertase. This is a new framework whose tagline states, `Build faster websites. This seems ideal for our performant website.

npm create astro@latest

Let’s add tailwind too.

npx astro add tailwind

Finally, let’s start our development server and run our app on `localhost:3000`.

npm run dev

Now, should I want to add to these images, I will need the Firebase SDK to download the storage item, or is there an easier way?

Let’s install the Invertase Image Extension

  1. Select Install in the console

2. Select your project.

3. Install with the defaults selected.

For this, we will also use the supporting library to download our images. Let’s add the following to our project:

npm i @invertase/image-processing-api --save

Astro comes with built-in typescript support, so let’s add a `components` folder and add a new images.tsx component for displaying our image content.

    <div className="md:p-8">
      <div className="flex flex-wrap w-full md:p-2">
        {images.map((selection: any) => {
          return (
            <div className="p-1 md:w-1/3 lg:w-full">
              <div> No Images found</div>
            </div>
          );
        })}
      </div>
    </div>

For this demo, we will hard code a list of images; the names match the paths of the related images in our storage bucket.

 const images. = [{
    id: 1,
    title: "Mountains",
    image: "travelling/pexels-amine-m'siouri-2108813.jpg",
  }
  //....rest
}];

Next, we’ll add a function to process each image request. The URL in this example will need to match your installed extension’s name.

For this example we will using Googles Webp. Please check cross browser compatibility if you intend to use this for other Web browsers.

const getUrl = (source: string) => {
  const URL = `https://{LOCATION}-{PROJECT_ID}.cloudfunctions.net/ext-image-processing-api-handler/process?operations=`;

  const options = builder()
    .input({
      type: "gcs",
      source,
    })
    .output({ webp: {} })
    .toEncodedString();

  return `${URL}${options}`;
};

Add a new image tag to use our new URL utility…

{images.map((selection: any) => {
 return ( 
  <img
   src={getUrl(selection.image)}
   alt={`${selection.title}`} 
  />
)}

Re-run the app, and let’s see how it looks…

We have successfully downloaded the images. It is a good start, but the download time and image sizes are high.

Let’s update our API configuration.

const getUrl = (source: string) => {
  const URL = `https://{LOCATION}-{PROJECT_ID}.cloudfunctions.net/ext-image-processing-api-handler/process?operations=`;

  const options = builder()
    .input({
      type: "gcs",
      source,
    })
    .resize({ width: 200, height: 200 })
    .output({ webp: {} })
    .toEncodedString();

  return `${URL}${options}`;
};

Add the resize option to ensure all images are the same height and width.

Our image sizes are now much more consistent, and download times and image sizes have dramatically reduced.

The API includes an option for level optimization, this defaults at 80. For performance, we can reduce this…

    .output({ webp: { quality: 50 } })

Not only have our image sizes decreased, meaning less payload. We also have faster response times.

There is also no discernible change in quality, either!

Responsive Images

Ok, so that looks great. We now have images that are resized and optimized for desktops. This would provide a global solution for areas with limited internet speed and even reduce bandwidth.

But the Images could be better, stretching to fill their containers and unoptimized for other screen types.

Let’s add variants of the images:

<div className="flex flex-wrap w-full md:p-2">
        {images.map((selection: any) => {
          return (
            <div className="p-1 md:w-1/3 lg:w-full">
              <div className="relative">
                <picture className="w-full">
                  <source
                    srcSet={`${getUrl(selection.image, 200, 200)} 200w`}
                    media={`(min-width: 200px) and (max-width: 400px)`}
                  />
                  <source
                    srcSet={`${getUrl(selection.image, 400, 400)} 400w`}
                    media={`(min-width: 400px) and (max-width: 600px)`}
                  />
                  <source
                    srcSet={`${getUrl(selection.image, 600, 600)} 600w`}
                    media={`(min-width: 600px) and (max-width: 800px)`}
                  />
                  <source
                    srcSet={`${getUrl(selection.image, 800, 800)} 800w`}
                    media={`(min-width: 800px) and (max-width: 1000px)`}
                  />
                  <source
                    srcSet={`${getUrl(selection.image, 1000, 1000)} 1200w`}
                    media={`(min-width: 1200px) and (max-width: 1200px)`}
                  />
                  <source
                    srcSet={`${getUrl(selection.image, 1200, 1200)} 200w`}
                    media={`(min-width: 1200px) `}
                  />
                </picture>
                <div className="absolute bottom-0 right-0">
                  <div className="bold text-white p-4">{selection.title}</div>
                </div>
              </div>
            </div>
          );
        })}
      </div>

We are now ready; let’s see the final results loading as we move between breakpoints!

Lazy loading

Lazy loading is the practice of only asking for resources when required. From our example, you will notice our initial network time still considers the downloaded images as part of the initial loading time.

To do this, ensure that you have added width, height, and loading tags to your main img tag:

<img src={getUrl(selection.image, 200, 200)}
     className="object-cover object-center w-full h-full"
     width="200px"
     height="200px"
     loading="lazy"
/>

Sources should include the height and width too:

<source
 srcSet={`${getUrl(selection.image, 1200, 1200)} 1200w`}
 width="1200px"
 height="1200px"
 media={`(min-width: 1200px)`}
/>

For demo purposes, we have added a blank div to push down the images; as scrolling occurs, the images are lazy loaded.

Conclusion

We have successfully created a responsive image gallery that uses the Invertase Image Extension to generate images on the fly while optimizing for different devices.

For the full example source code, visit the Invertase samples repository.

Please check out our other blogs, guides, and tutorials for other Firebase extension-related content!

Stay tuned for more updates and exciting news that we will share in the future. Follow us on Invertase TwitterLinkedin, and Youtube, and subscribe to our monthly newsletter to stay up-to-date. You may also join our Discord to have an instant conversation with us.

Darren Ackers

Lead Developer

Jacob Cable

Software Engineer

Categories

Tags