Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change image size with PIL in a Google Cloud Storage Bucket (from a VM in GCloud)

This is what I need: when a user upload an image, verify if that image exceeds some size, if so change the size. This code works with no errors, but the image saved has the size without changes. The image is in a Google Cloud Storage Bucket, it is uploaded just before, but it works fine. Any idea is welcome. Thanks in advance.

from PIL import Image
from django.core.files.storage import default_storage
from google.cloud import storage
from google.cloud.storage import Blob
import io

if default_storage.exists(image_path):
    client = storage.Client()
    bucket = client.get_bucket('mybucket.appspot.com')
    blob = Blob(image_path, bucket)
    contenido = blob.download_as_string()
    fp = io.BytesIO(contenido)
    im = Image.open(fp)
    x, y = im.size
    if x>450 or y>450:
        im.thumbnail((450,450))
        im.save(fp, "JPEG")
        # im.show() here it shows the image thumbnail (thumbnail works)
        blob.upload_from_string(fp.getvalue(), content_type="image/jpeg")
        blob_dest = Blob('new_image.jpg', bucket)
        blob.download_as_string()
        blob_dest.rewrite(blob)
like image 926
Fabián Rivadeneyra Avatar asked May 01 '19 18:05

Fabián Rivadeneyra


3 Answers

You've got a lot of extra stuff happening here, including saving the image to the local filesystem, which is unnecessary. This minimal example should work:

import io 

from PIL import Image
from django.core.files.storage import default_storage
from google.cloud import storage

if default_storage.exists(image_path):
    client = storage.Client()
    bucket = client.get_bucket('mybucket.appspot.com')

    # Download the image
    blob = bucket.get_blob(data['name']).download_as_string()
    bytes = io.BytesIO(blob)
    im = Image.open(bytes)

    x, y = im.size

    if x>450 or y>450:
        # Upload the new image
        thumbnail_blob = bucket.blob('new_image.jpg')
        thumbnail_blob.upload_from_string(im.resize(450, 450).tobytes())
like image 189
Dustin Ingram Avatar answered Oct 20 '22 23:10

Dustin Ingram


I've tried the solution from @dustin-ingram and it happened to me that the file ended up corrupted when downloading it again. Using the code from this answer I reached a solution.

import io 

from PIL import Image
from google.cloud import storage

__max_size = 450, 450

image_name = 'my_images/adasdasadas7c2a7367cf1f.jpg'

client = storage.Client()
bucket = client.bucket('my-bucket')
# Download the image
blob = bucket.blob(image_name).download_as_string()
blob_in_bytes = io.BytesIO(blob)
# Translating into PIL Image object and transform
pil_image = Image.open(blob_in_bytes)
pil_image.thumbnail(__max_size, Image.ANTIALIAS)
# Creating the "string" object to use upload_from_string
img_byte_array = io.BytesIO()
pil_image.save(img_byte_array, format='JPEG')
# Create the propper blob using the same bucket and upload it with it's content type
thumbnail_blob = bucket.blob(image_name)
thumbnail_blob.upload_from_string( img_byte_array.getvalue(), content_type="image/jpeg")
like image 45
vperezb Avatar answered Oct 21 '22 00:10

vperezb


Regardless of what Cloud Storage you are using, you can use this method to resize the uploaded image in memory, then you can upload the image or manipulate it as you wish:

from io import BytesIO
from PIL import Image as PilImage
import os
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile

def resize_uploaded_image(image, max_width, max_height):
    size = (max_width, max_height)

    # Uploaded file is in memory
    if isinstance(image, InMemoryUploadedFile):
        memory_image = BytesIO(image.read())
        pil_image = PilImage.open(memory_image)
        img_format = os.path.splitext(image.name)[1][1:].upper()
        img_format = 'JPEG' if img_format == 'JPG' else img_format

        if pil_image.width > max_width or pil_image.height > max_height:
            pil_image.thumbnail(size)

        new_image = BytesIO()
        pil_image.save(new_image, format=img_format)

        new_image = ContentFile(new_image.getvalue())
        return InMemoryUploadedFile(new_image, None, image.name, image.content_type, None, None)

    # Uploaded file is in disk
    elif isinstance(image, TemporaryUploadedFile):
        path = image.temporary_file_path()
        pil_image = PilImage.open(path)

        if pil_image.width > max_width or pil_image.height > max_height:
            pil_image.thumbnail(size)
            pil_image.save(path)
            image.size = os.stat(path).st_size

    return image

In case you are taking the image from a post form, you can do this:

image = request.FILES['image']
...
image = resize_uploaded_image(image, 450, 450)
...
thumbnail_blob.upload_from_string(image.read(), image.content_type)

A better way is to use it in the clean method of the image field in your form:

class ImageForm(forms.Form):
    IMAGE_WIDTH = 450
    IMAGE_HEIGHT = 450

    image = forms.ImageField()

    def clean_image(self):
        image = self.cleaned_data.get('image')
        image = resize_uploaded_image(image, self.IMAGE_WIDTH, self.IMAGE_HEIGHT)
        return image
like image 2
Omar Lajam Avatar answered Oct 20 '22 23:10

Omar Lajam