Building an Image Resizer with Flask, React and Vite

Building an Image Resizer with Flask, React and Vite

ยท

6 min read

Introduction

Hello! ๐Ÿ˜Ž

In this tutorial I will show you how to build a simple image resizer using Flask, React and Vite.


Requirements

  • Basic knowledge of Python/React

  • OpenCV installed


Creating The Server Side

First we create the server side using Flask to handle the image upload, convert the image and then provide the converted image in the response. To do this we will use OpenCV and NumPy.

OpenCV is utilized to decode the uploaded image, resize it, and encode it back to a format that can be sent to the client.

NumPy is used to convert the uploaded file into a format that OpenCV can work with.

Following best practices we will code the server using a python virtual environment. This is easily done with Python 3 using the following command:

python3 -m venv env

This will create a new directory called env. Next to activate the virtual environment simply run the following command:

source env/bin/activate

Now you should be in a virtual Python environment.

Now we can install the libraries required for the server side. Simple create a new file called requirements.txt and populate it with the following:

Flask
flask_cors
opencv-python
numpy

To install the libraries just run the following command:

pip install -r requirements.txt

This will install the libraries needed for the server side.

Next we need to write the code to handle the file upload, converting and send the response back to the front end. Create a new file called "main.py" and import the following libraries:

from flask import Flask, request, send_file
from flask_cors import CORS
import cv2
import numpy as np

After that we need to initialize the Flask app and disable CORS, this will allow the React app to access the server without any issues.

app = Flask(__name__)
CORS(app)

Next up is the route that will be used to handle the file upload. Which is as follows:

@app.route('/upload', methods=['POST'])
def upload():
    if 'image' not in request.files:
        return 'No file uploaded', 400

    file = request.files['image']

    if not file.content_type.startswith('image/'):
        return 'File is not an image', 400

    if file.filename == '':
        return 'no selected file', 400

    if file:
        npimg = np.fromfile(file, np.uint8)
        img = cv2.imdecode(npimg, cv2.IMREAD_UNCHANGED)

        width = int(request.form.get('width', 1024))
        height = int(request.form.get('height', 1024))

        resized_image = cv2.resize(img, (width, height))

        convert_status, buffer = cv2.imencode('.jpg', resized_image)

        if not convert_status:
            return 'Failed to convert image', 500

        temp_file = 'temp_image.jpg'
        cv2.imwrite(temp_file, resized_image)

        return send_file(temp_file, as_attachment=True)

The above checks if the file in the request is present, checks whether or not it is an actual image file, failing this a error response is sent back to the client with a HTTP response of 400.

If a file exists and is an image it will be resized and converted into a jpg image, if the conversation is successful the file is returned to the client.

Last up we just need the main function:

if __name__ == '__main__':
    app.run(debug=True)

All the above does is start the app up. Next we will create the React app to handle the frontend. ๐Ÿ˜„


Creating The Client Side

Lastly we need a UI for the user to upload and convert an image. The front end will use React that will be created via Vite. To create a new Vite project simply run the following command:

yarn create vite

Follow the prompts to create a new React app that uses Javascript. We will also be using Bootstrap for the UI and axios to handle the request, these packages can be installed via the following commands:

yarn add axios
yarn add bootstrap

In order to apply the Bootstrap styles add the following line in your "src/main.jsx" file:

import 'bootstrap/dist/css/bootstrap.min.css';

After that we will create a new React component for the image upload form, create a new directory called "components" in the "src" directory and create a new file called "ImageUploadForm.jsx", once created populate it with the following:

import { useState } from 'react';
import axios from 'axios';

const ImageUploadForm = () => {
    const [height, setHeight] = useState('1024');
    const [width, setWidth] = useState('1024');
    const [file, setFile] = useState(null);
    const [image, setImage] = useState(null);

    const handleSubmit = async (e) => {
        e.preventDefault();

        const formData = new FormData();
        formData.append('height', height);
        formData.append('width', width);
        formData.append('image', file);

        try {
            const response = await axios.post('http://localhost:5000/upload', formData, {
                headers: { 'Content-Type': 'multipart/form-data' },
                responseType: 'blob'
            });

            const objectURL = URL.createObjectURL(response.data);
            setImage(objectURL);
        } catch (error) {
            console.error('error uploading file', error);
        }
    };

    return (
        <>
            <form onSubmit={handleSubmit} className="container mt-4">
                <div className="mb-3">
                    <label htmlFor="height" className="form-label">Height:</label>
                    <input type="number" className="form-control" id="height" value={height} onChange={(e) => setHeight(e.target.value)}/>
                </div>
                <div className="mb-3">
                    <label htmlFor="width" className="form-label">Width:</label>
                    <input type="number" className="form-control" id="width" value={width} onChange={(e) => setWidth(e.target.value)} />
                </div>
                <div className="mb-3">
                    <label htmlFor="image" className="form-label">Image:</label>
                    <input type="file" className="form-control" id="image" onChange={(e) => setFile(e.target.files[0])} />
                </div>

                <button type="submit" className="btn btn-primary">Submit</button>
            </form>

            {image && <img src={image} />}
        </>
    );
}

export default ImageUploadForm;

What the above does is display a form containing three elements, one for the image height, one for the image width and one for the image file itself.

Once the user has selected an image and clicked on the submit button the image is sent to the flask server to be converted. Once the image has been converted and returned back to the React app, the converted image is displayed at the bottom of the page for the user to download.

Done now we can run both the server and the app! ๐Ÿ‘


Starting The Application

To start the Flask server simply run the following command in your Python virtual environment:

python main.py

To start the React app just run the following command:

yarn dev

The above command should automatically open your browser and redirect you to the React app.

From here try to select an image file and click on Submit, the image should now be resized and displayed at the bottom of the page. ๐Ÿ˜ฎ


Conclusion

Here I have shown how to handle image uploads with Flask and convert them using OpenCV. We also used React and Vite to create the front end and provide a simple form enabling the user to select and convert an image.

To improve this I would like to handle multiplee image uploads and maybe even support more conversion types than just jpg.

I hope you learned something from this as I had a lot of fun making it and I needed something like this for personal use.

As always you can find the source code for this tutorial on my Github: Server: https://github.com/ethand91/image-convert-server Client: https://github.com/ethand91/image-convert-app

Happy Coding! ๐Ÿ˜Ž


Like my work? I post about a variety of topics, if you would like to see more please like and follow me. Also I love coffee.

โ€œBuy Me A Coffeeโ€

If you are looking to learn Algorithm Patterns to ace the coding interview I recommend the following course

Did you find this article valuable?

Support Development Diary by becoming a sponsor. Any amount is appreciated!

ย