DEV Community

AM
AM

Posted on • Updated on

Creating Image from dataURL base64 with Pyhton Django

A few days ago, while developing the bazaar article register, I fell into a somewhat complicated situation. Perhaps yours is similar.

When creating a new article in the bazaar, the selected images are read using the FileReader API to generate the dataURL of the images in order to save it allowing the user to leave the bazaar, go back and continue creating the article with the selected images.

As the reference of the file of each image is lost, when uploading the images it was necessary to send the dataURL instead of the file itself, in this case the API should be in charge of generating the image files


Note: Using Python 3.6.9 and Django in version 3.0.3

For this, the following libraries were necessary to carry out this small task.

# python standard lib
import base64, secrets, io

# django and pillow lib
from PIL import Image
from django.core.files.base import ContentFile
Enter fullscreen mode Exit fullscreen mode

I defined this function capable of receiving the dataURL to generate the file and resize it if necessary.

def get_image_from_data_url( data_url, resize=True, base_width=600 ):

    # getting the file format and the necessary dataURl for the file
    _format, _dataurl       = data_url.split(';base64,')
    # file name and extension
    _filename, _extension   = secrets.token_hex(20), _format.split('/')[-1]

    # generating the contents of the file
    file = ContentFile( base64.b64decode(_dataurl), name=f"{_filename}.{_extension}")

    # resizing the image, reducing quality and size
    if resize:

        # opening the file with the pillow
        image = Image.open(file)
        # using BytesIO to rewrite the new content without using the filesystem
        image_io = io.BytesIO()

        # resize
        w_percent    = (base_width/float(image.size[0]))
        h_size       = int((float(image.size[1])*float(w_percent)))
        image        = image.resize((base_width,h_size), Image.ANTIALIAS)

        # save resized image
        image.save(image_io, format=_extension)

        # generating the content of the new image
        file = ContentFile( image_io.getvalue(), name=f"{_filename}.{_extension}" )

    # file and filename
    return file, ( _filename, _extension )
Enter fullscreen mode Exit fullscreen mode

Well, at this point, we don't actually have the generated file yet, just the contents of the file (Using the Django ContentFile API that returns an instance of it) stored in memory and ready to be written to a file in the filesystem

However, with the file that is returned, we only need to do the following to generate the file and save usingdefualt filesystem storage:

Example:

# for example
from .models import User
from .utils.images import get_image_from_data_url

def create_user_view(request)

    username = request.POST.get('username')
    # getting the file instance
    avatar_file = get_image_from_data_url(request.POST.get('avatar'))[0]

    # create a user and generate and save the file using the default filesystem storage
    user = User.objects.create(
        username=username,
        avatar=avatar_file
    )

    # this does not work 😂
    return Response(user, status=status.HTTP_201_CREATED)
Enter fullscreen mode Exit fullscreen mode

Of course you will do more validations, after all in your case it will not be a mere example right 😉


As a bonus 😎, I defined a second function capable of generating the content of the file for the main image and for a second thumbnail image (image copy but with a much smaller size, suitable for lazy load images among other situations).

def get_image_and_thumbnail_from_data_url( data_url, resize=True, base_width=600):
    #
    file, filename = get_image_from_data_url(data_url, resize, base_width)

    #
    thumbnail = Image.open(file)

    #
    thumbnail_io = io.BytesIO()
    thumbnail.thumbnail((128,128), Image.ANTIALIAS)
    thumbnail.save(thumbnail_io, format=filename[1])

    # thumbnail image
    thumbnail = ContentFile( 
        thumbnail_io.getvalue(), 
        name=f"{filename[0]}.thumbnail.{filename[1]}"
    )

    return file, thumbnail
Enter fullscreen mode Exit fullscreen mode

So, that's it for now! I hope this little tip can help you. But be careful that depending on the situation, these operations can be very costly. In case of multiple images, it is advisable to perform operations in the background, using Celery for example (maybe I will do a second article implementing Celery, what do you think? 🤔).

So that the user does not wait long before receiving a response from the server.


Be sure to comment on your debt, criticism or suggestion! Enjoy and follow my work on social networks.

Ageu Matheus: @txiocoder


Discussion (2)

Collapse
horiyomi profile image
horiyomi • Edited on

This came in really handy...thanks

Collapse
txiocoder profile image
AM Author

Amazing to know that I could help you 🥳