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!
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.
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:
Theurl
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:
Thesource
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 👇🏻
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.