← Back to blog

Introducing the Image Processing API Firebase Extension

Mais Alheraki

Open Source Engineer

Yaman Katby

Extensions Developer

7th 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

If you have an image and would like to blur, flip or rotate it, you would usually go to some online tools or use Figma or any other tools. What if you need to process many images, images that are not on your device, or images uploaded by users of your app?

In this article, we’ll look at the new Firebase Image Processing API Extension. We will look at use cases, how to set it up, and finally a simple example to put the knowledge we’ve gained into practice!

Quick look into the Image Processing API

Why Image Processing API?

The Image Processing API extension lets you optimize and transform images via a powerful HTTP API. The API provides more than 30 different operations to enhance and manipulate your images, including image composition, cropping, flipping, color reduction, sharpening, filtering, and much more.

Using this extension, you can:

  • Optimize Storage space usage
  • Make apps and websites load faster
  • Improve SEO

Installation

This extension can be installed from the Firebase extensions marketplace, click here and choose the project which you want to use. Accept all default configurations and install. Make a note of your Project ID and Function Location, you will need them in the coming sections.

Installation process of the extension

Extension Usage

The Image Processing API has a single endpoint method called process. It can be accessed via a GET request and requires a single query param called operations which will be the set of operations we want to apply to the image.

curl -X GET \
    https://{LOCATION}-{PROJECT_ID}.cloudfunctions.net/ext-image-processing-api-handler/process?operations\=operationinputtypeurlurlhttpsimages.pexels.comphotos1287145pexels-photo-1287145.jpegoperationresizewidth400height250operationgrayscalegrayscaletrueoperationblursigma3operationtextvalueInvertasefont48pxsans-seriftextColordarkorangeoperationoutputformatwebp \
    --output test.webp
  • {LOCATION}: The Cloud Functions location that was specified during the installation of the extension.
  • {PROJECT_ID}: The Firebase project ID.

Operations

The operations query param is an array of objects where each object has a required property called operation specifies the type of operation you want to apply.

[
 ...
 { operation: "grayscale" },
 { operation: "blur", sigma: 3 },
 ...
];

Some operations like the above blur operation requires more than the name of the operation to be applied, for that we can pass any other required params in the same operation object.

See a complete list of operations you can apply here.

Input Operation

The first operation in the operations array must be an input operation.

[
 { operation: "input", type: "..." },
 ...
];

The input operation specifies the image we want to apply the operations to. We have 3 input types we can choose from:

  • URL:
    The url property must be a public URL of an existing image on the internet, for example:
{ operation: "input", type: "url", url: "https://example.com/image.jpg" }
  • Google Cloud Storage:
    The source property must be a valid Google Cloud Storage path of an existing image, for example:
{ operation: "input", type: "gcs", source: "bucket/path/to/file" }
  • Create:
    This input type has no additional properties and will generate an image from scratch based on the operations provided, for example:
{ operation: "input", type: "create" }

Output Operation

Similar to the input operation, the last operation in the operations array must be an output operation. The output operation is used to specify the format of the exported image along with export properties such as image quality, color settings, and more.

{ operation: "output", format: "webp" }

The image can be exported to JPEG, PNG, WebP, GIF, AVIF, or TIFF.

See a full list of exporting settings here.

Advanced Features

Formats

The Image Processing API supports a wide variety of media types that can be processed and transformed. It supports reading JPEG, PNG, WebP, GIF, AVIF, TIFF, and SVG images and outputs in JPEG, PNG, WebP, GIF, AVIF, and TIFF formats.

Caching

The image processing API processes the image once and caches it for future requests.

Fast

The Image Processing API extension is built on top of the Sharp open-source library that uses the libvips image processing library which runs at high speed and uses little memory.

The utility library from Invertase

Anytime you want to remember what operations the extension support, and what options they have, you need to go to the docs. Therefore, we created a utility library to help construct your operations without leaving the IDE.

To install it:

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

How to use the utility library?

Previously, you’ve seen the different operations and how to construct a URL query param manually, let’s take the following code where we manually build the request URL:

const URL = `https://${process.env.REACT_APP_LOCATION}-${process.env.REACT_APP_PORIJECT_ID}.cloudfunctions.net/ext-image-processing-api-handler/process?operations=`;

const operations = [
  { operation: 'input', type: 'url', url: IMAGE_URL },
  { operation: 'resize', width: 200, height: 200 },
  { operation: 'flip' },
  { operation: 'output', format: 'webp' },
];

const buildUrl = (operations) =>
    `${URL}?operations=${encodeURIComponent(JSON.stringify(operations))}`;

We can re-write that using the utility library:

output = builder()
  .input({ url: IMAGE_URL })
  .resize({ width: 200, height: 200 })
  .flip()
  .blur({ sigma: 3 })
  .output({
    webp: true,
  })
  .toEncodedString();

Basic Example

Now that we know how the API works, let’s make a quick example and put the knowledge into practice.

Let’s say we have a gallery of user-uploaded images. Images have different sizes and styles and we want to make a unified preview of them. All images will:

  • Have the same width and height
  • Grayscaled
  • Blurred
  • Have a text overlay

The final result would look like this 👇🏻

The final result of the tutorial sample

Let’s create a React component called PreviewImage. This component requests the Image Processing API generates the unified preview image, and displays it:

import React from 'react';

// We will use the utility library to create thee operations query.
import { builder, InputOptions } from '@invertase/image-processing-api';

const API_URL = `https://${process.env.REACT_APP_LOCATION}-${process.env.REACT_APP_PORIJECT_ID}.cloudfunctions.net/ext-image-processing-api-handler/process?operations=`;

export default function PreviewImage(props) {
  if (props.url.length === 0) {
    return (
      <div className='image'>
        <h2>Click on any image</h2>
      </div>
    );
  }

  const query = constructQuery(props.url);

  return <img src={API_URL + query} alt={props.alt} />;
}

This component will take a url prop, which will be transformed using the API by constructing the query using constructQuery() function. This function will create the operations and encode them into the request, like this:

/**
 * Use the utility lib to build the query string.
 *
 * @param {string} URL The image URL to process.
 * @returns {string} The query string.
 *
 * */
function constructQuery(url) {
  var input = builder().input({ url: url });
  var output;

  if (url.startsWith('https://firebasestorage.googleapis.com/v0/b/')) {
    input = gcsUrlInput(url);
  }

  try {
    output = input
      .resize({ width: 400, height: 250 })
      .grayscale()
      .blur({ sigma: 3 })
      .text({ value: 'Invertase', font: '48px sans-serif', textColor: 'darkorange' })
      .output({
        webp: true,
      })
      .toEncodedString();
  } catch (error) {
    console.error(error);
  }

  return output;
}

This function will process 2 types of inputs: url and gcs. If the URL starts with firebasestorage.googleapis.com/v0/b/, we assume it’s a resource from Google Cloud Storage, so we call another function to create the proper input:

/**
 * Use this function to parse a GCS URL into a builder input.
 * @param {string} url The image GCS URL.
 * @returns {InputOptions} A builder with input options.
 */
function gcsUrlInput(url) {
  url = url.split('/o/')[1];
  if (url.includes('?alt=media&token=')) {
    url = url.split('?')[0];
  }

  return builder().input({ source: url });
}

Let’s use this component to process a list of images:

import './App.css';
import React, { useState } from 'react';

import PreviewImage from './PreviewImage';

const images = [
  // GCS source
  'https://firebasestorage.googleapis.com/v0/b/flutter-vikings-satging.appspot.com/o/stories%2FR5jCMSdfnSE70unFfpd2.jpg',
  // HTTP source
  'https://images.pexels.com/photos/2662116/pexels-photo-2662116.jpeg',
  'https://images.pexels.com/photos/1287145/pexels-photo-1287145.jpeg',
  'https://images.pexels.com/photos/1770809/pexels-photo-1770809.jpeg',
];

export default function App() {
  const [url, setUrl] = useState('');

  return (
    <body className='app'>
      <div className='app-body'>
        <div className='images'>
          <table>
            <tbody>
              {images.map((image) => (
                <td>
                  <div
                    className={`margin border-radius animate ${
                      url === image && 'img-overlay'
                    }`}
                    onClick={() => setUrl(image)}
                  >
                    <img
                      className={`image ${url === image && 'border-radius'}`}
                      src={image}
                      alt='logo'
                    />
                  </div>
                </td>
              ))}
            </tbody>
          </table>
        </div>
        <div className='margin h200'>
          <PreviewImage url={url} />
        </div>
      </div>
    </body>
  );
}

Conclusion

You can find the full sample code here, clone it, and play around with different images and operations!

If you faced any issues, or bugs or have feature requests, visit the extension repository here.

Mais Alheraki

Open Source Engineer

Yaman Katby

Extensions Developer

Categories

Tags