Skip to content

Utilizing Cloudinary APIs to Build a Custom Instagram-Like Filter

Image enhancements are essential for creating captivating visual content. Platforms like Instagram make image filters and effects a popular feature, allowing users to transform their photos easily. But how do we bring this functionality to our applications? In this blog post, we’ll create a custom Instagram-like filter using Cloudinary’s powerful transformation APIs.

This blog post assumes you have basic knowledge of React.js, Vite and Tailwind CSS, as well as:

  • Node.js and npm installed on your machine.
  • A Cloudinary account

We’ll use React.js powered by Vite, Cloudinary for image storage and transformation, and Tailwind CSS for styling.

Run the following command:

npm create vite@latest cloudinary-image-filter -- --template react
cd cloudinary-image-filter
Code language: CSS (css)

The above command creates a Vite project with React.js as the framework of choice inside a `cloudinary-image-filter’ directory and then navigates to the directory.

Now that we have a Vite project, let’s install and set up Tailwind CSS by running the command below:

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

The above command will install tailwindcss, postcss, and autoprefixer as dev dependencies. Next, generate your tailwind.config.js and postcss.config.js files.

Add the following in your tailwind.config.js file:

/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
Code language: CSS (css)

Now delete the App.css file, remove everything inside the index.css file, and add the @tailwind directives for each of Tailwind’s layers to your ./src/index.css file:

@tailwind base;
@tailwind components;
@tailwind utilities;
Code language: CSS (css)

Create a .env file to store your Cloudinary credentials and API configuration:

VITE_CLOUDINARY_CLOUD_NAME='your_cloud_name'
VITE_CLOUDINARY_UPLOAD_PRESET='your_upload_preset'
Code language: JavaScript (javascript)

Replace the placeholders with your actual Cloudinary details.

Before going further, we’ll need an unsigned upload preset configuration in Cloudinary. The upload preset is essential for managing client-side uploads securely and efficiently, allowing you to configure upload settings, such as transformations and quality, without exposing your API credentials. This ensures that files are uploaded according to your predefined rules.

If you already have an unsigned preset, you can skip this part.

  • Log in to your Cloudinary account.
  • Go to “Settings” > “Upload”.
  • Scroll to Upload presets and click Add upload preset.
  • Name your preset, set it to Unsigned, configure settings, and save.

This preset name is used in your .env file for secure uploads.

Important: Never expose your .env file’s contents or commit it to version control systems.

We’ll need to install the Javascript SDK for image transformation. To install this package, run the following command:

npm install @cloudinary/url-gen
Code language: CSS (css)

Now that we’ve installed the needed dependencies, let’s create a file that exports a method that uploads files to Cloudinary. Create a file inside the src folder called cloudinary.js and add the following line of code inside the file:

export const uploadImage = async (file) => {
  try {
    const url = `https://api.cloudinary.com/v1_1/${
      import.meta.env.VITE_CLOUDINARY_CLOUD_NAME
    }/image/upload`;
    const formatData = new FormData();
    formatData.append('file', file);
    formatData.append(
      'upload_preset',
      import.meta.env.VITE_CLOUDINARY_UPLOAD_PRESET
    );
    const response = await fetch(url, {
      method: 'POST',
      body: formatData,
    });
    if (!response.ok) {
      throw new Error('Failed to upload image');
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error(error);
  }
};
Code language: JavaScript (javascript)

In the above code, we’ll define a function uploadImage that uploads an image file to Cloudinary. It takes a file as a parameter and, using the cloud name from the environment variable, constructs the upload URL. It then makes a post request with the file and the upload preset as form data. This method returns a response object if successful or throws an error if the request fails.

With the uploadImage function defined, we’re all set to start coding!

The Cloudinary image transformation enables you to programmatically generate multiple variations of your high-quality original images by generating a dynamic URL.

With image transformations, you can:

We’ll use @cloudinary/url-gen to apply transformation to an image and create a transformation URL.

Let’s upload an image and transform it to have a sepia filter, which gives the image a warm, brownish tone reminiscent of old photographs.

import { useEffect, useState } from 'react';
import { uploadImage } from './cloudinary';
import { Cloudinary } from '@cloudinary/url-gen';
// Import required actions.
import { sepia } from '@cloudinary/url-gen/actions/effect';

function App() {
  const [imagePublicId, setImagePublicId] = useState();

  const cloudinary = new Cloudinary({
    cloud: {
      cloudName: 'codemario',
    },
    url: {
      secure: true,
    },
  });

  let src = '';
  let originalSrc = '';
  const cldImage = imagePublicId && cloudinary.image(imagePublicId);

  if (cldImage) {
    originalSrc = cldImage.toURL();
    cldImage.effect(sepia());
    src = cldImage.toURL();
  }

  const handleFileUpload = async (e) => {
    const response = await uploadImage(e.target.files[0]);
    setImagePublicId(response.public_id);
  };

  return (
    <div className="flex flex-col mx-auto mt-16 h-full  max-w-2xl">
      <div className="flex flex-col self-center shrink h-1/3 gap-2">
        {src ? (
          <div className="flex items-center gap-5">
            <div className="max-w-60 flex flex-col gap-4">
              <h2>Original</h2>
              <img src={originalSrc} className="w-52 h-52" />
            </div>
            <div className="max-w-60 flex flex-col gap-4">
              <h2>Filter</h2>
              <img src={src} className="w-52 h-52" />
            </div>
          </div>
        ) : (
          <div className="w-52 bg-gray-300 h-52"></div>
        )}
        <input
          type="file"
          accept="image/*"
          onChange={handleFileUpload}
        />
      </div>
    </div>
  );
}
Code language: JavaScript (javascript)

You can also provide more than one transformation to produce different results:

//...
import { cartoonify } from '@cloudinary/url-gen/actions/effect';
import { thumbnail } from "@cloudinary/url-gen/actions/resize";
import { byRadius } from "@cloudinary/url-gen/actions/roundCorners";
//...
cldImage
      .resize(thumbnail().width(150).height(150))
      .roundCorners(byRadius(20))
      .effect(cartoonify());

//...
Code language: JavaScript (javascript)

In the above code snippet, we imported different functions from Cloudinary to perform various image transformations.

  • cartoonify. This function applies a cartoon effect to an image. It processes the image to make it look like a hand-drawn illustration.
  • thumbnail. Thumbnails are smaller versions of images. This function resizes the image to a specified thumbnail size.
  • byRadius. This function rounds the corners of an image by a specified radius.

Now that we’ve seen how it works, let’s ensure a user can choose the effect and combine multiple transformations.

To achieve this, we must use an alternative way of applying multiple transformations rather than chained methods. We will use the object syntax method.

We can use the transformationStringFromObject method to build the transformation and add it to our image using the addTransformation method. The transformationStringFromObject enables adding various transformation effects and enhancements programmatically instead of importing all the individual actions and qualifiers.

There are many effects and enhancements, but for this blog post, we’ll use a few. The same implementation can be applied to all effects.

Let’s add the following code to App.jsx:

import { useEffect, useState } from 'react';
import { uploadImage } from './cloudinary';
import { Cloudinary, transformationStringFromObject } from '@cloudinary/url-gen';

export const FILTERS = [
  {
    name: 'Athena',
    filter: 'art:athena',
  },
  {
    name: 'Audrey',
    filter: 'art:audrey',
  },
  {
    name: 'Daguerre',
    filter: 'art:daguerre',
  },
  {
    name: 'Eucalyptus',
    filter: 'art:eucalyptus',
  },
  {
    name: 'Fes',
    filter: 'art:fes',
  },
  {
    name: 'Incognito',
    filter: 'art:incognito',
  },
  {
    name: 'Peacock',
    filter: 'art:peacock',
  },
  {
    name: 'Quartz',
    filter: 'art:quartz',
  },
  {
    name: 'cartoonify',
    filter: 'cartoonify',
  },
  {
    name: 'Black & white',
    filter: 'cartoonify:20:bw',
  },
  { name: 'sepia', filter: 'sepia' },
  { name: 'Vignette', filter: 'vignette' },
];

const cloudinary = new Cloudinary({
  cloud: {
    cloudName: 'codemario',
  },
  url: {
    secure: true,
  },
});

function App() {
  const [imagePublicId, setImagePublicId] = useState();
  const [imageFilter, setImageFilter] = useState();
  const [opacity, setOpacity] = useState(100);
  const [brightness, setBrightness] = useState(50);
  const [imageURl, setImageURL] = useState();

  // Apply transformations to the image URL when the image filter, opacity, or brightness changes
  useEffect(() => {
    const transformation = transformationStringFromObject([
      { radius: 20 },
      { effect: imageFilter },
      { opacity },
      { effect: `brightness:${brightness}` },
      { fetch_format: 'png' },
    ]);

    const cldImage = imagePublicId && cloudinary.image(imagePublicId);

    if (cldImage) {
      if (imageFilter || opacity !== 100) {
        cldImage.addTransformation(transformation);
      }
      const src = cldImage.toURL();
      setImageURL(src);
    }
  }, [imageFilter, opacity, imagePublicId, brightness]);

  const handleFileUpload = async (e) => {
    const response = await uploadImage(e.target.files[0]);
    setImagePublicId(response.public_id);
  };

  return (
    <div className="flex flex-col mx-auto mt-16 h-full max-w-2xl">
      <div className="flex flex-col self-center shrink h-1/3 gap-2">
        {imageURl ? (
          <div className="max-w-60 flex flex-col gap-4">
            <img src={imageURl} className="w-52 h-52" />
          </div>
        ) : (
          <div className="w-52 bg-gray-300 h-52"></div>
        )}
        <input type="file" accept="image/*" onChange={handleFileUpload} />
      </div>
      {imageURl && (
        <div className="flex flex-col gap-7 w-full">
          <div className="flex items-center gap-5 w-full">
            <div className="w-full">
              <label htmlFor="opacity">Opacity</label>
              <input
                id="opacity"
                name="opacity"
                type="range"
                className="w-full"
                value={opacity}
                onChange={(e) => setOpacity(+e.target.value)}
              />
            </div>
            <div className="w-full">
              <label htmlFor="brightness">Brightness</label>
              <input
                type="range"
                id="brightness"
                name="brightness"
                className="w-full"
                value={brightness}
                onChange={(e) => setBrightness(+e.target.value)}
              />
            </div>
          </div>

          <div className="flex flex-col gap-3">
            <strong>Filters</strong>
            <ul className="flex gap-2 flex-wrap">
              {FILTERS.map(({ filter, name }) => {
                return (
                  <li
                    key={name}
                    className={`${filter === imageFilter ? 'border border-green-500' : ''} p-2`}
                  >
                    <button
                      className="flex flex-col w-24 justify-center items-center"
                      onClick={() => setImageFilter(filter)}
                    >
                      <img
                        className="w-48 h-24"
                        src={cloudinary.image(imagePublicId).effect(`e_${filter}`).toURL()}
                        alt={name}
                      />
                      <span>{name}</span>
                    </button>
                  </li>
                );
              })}
            </ul>
          </div>
        </div>
      )}
    </div>
  );
}
Code language: JavaScript (javascript)

In the above code, we created a dynamic image editor that allows users to upload an image, apply various filters, and adjust the image settings like opacity and brightness in real-time using Cloudinary. When the user selects an image, it’s uploaded to Cloudinary, and the public ID is saved in useState. If the user selects a filter or adjusts the brightness or opacity level, these transformations are applied, and Cloudinary dynamically generates the transformation URL. This transformation URL is constructed using Cloudinary’s transformationStringFromObject method, which combines selected filters and adjustments into a transformation string. Cloudinary applies these transformations to the image, and the updated image URL reflects these changes, resulting in an immediate preview that shows the applied effects.

If the user decides to download the image, it’s downloaded with the effects applied to it.

In this blog post, we used Cloudinary’s image transformation features to build an Instagram-like filter application, demonstrating the powerful capabilities of Cloudinary’s API for building advanced image editing applications. Cloudinary allows you to easily transform your images on the fly to any required format, style, and dimension and apply effects and other visual enhancements. Create an account today.

If you found this blog post helpful and want to discuss it in more detail, head over to the Cloudinary Community forum and its associated Discord.

Back to top

Featured Post