Resizing uploaded files in django using PIL - python

I am using PIL to resize an uploaded file using this method:
def resize_uploaded_image(buf):
imagefile = StringIO.StringIO(buf.read())
imageImage = Image.open(imagefile)
(width, height) = imageImage.size
(width, height) = scale_dimensions(width, height, longest_side=240)
resizedImage = imageImage.resize((width, height))
return resizedImage
I then use this method to get the resizedImage in my main view method:
image = request.FILES['avatar']
resizedImage = resize_uploaded_image(image)
content = django.core.files.File(resizedImage)
acc = Account.objects.get(account=request.user)
acc.avatar.save(image.name, content)
However, this gives me the 'read' error.
Trace:
Exception Type: AttributeError at /myapp/editAvatar Exception Value:
read
Any idea how to fix this? I have been at it for hours!
Thanks!
Nikunj

Here's how you can take a file-like object, manipulate it as an image in PIL, then turn it back into a file-like object:
def resize_uploaded_image(buf):
image = Image.open(buf)
(width, height) = image.size
(width, height) = scale_dimensions(width, height, longest_side=240)
resizedImage = image.resize((width, height))
# Turn back into file-like object
resizedImageFile = StringIO.StringIO()
resizedImage.save(resizedImageFile , 'PNG', optimize = True)
resizedImageFile.seek(0) # So that the next read starts at the beginning
return resizedImageFile
Note that there's already a handy thumbnail() method for PIL images. This is a variant of the thumbnail code I use in my own project:
def resize_uploaded_image(buf):
from cStringIO import StringIO
import Image
image = Image.open(buf)
maxSize = (240, 240)
resizedImage = image.thumbnail(maxSize, Image.ANTIALIAS)
# Turn back into file-like object
resizedImageFile = StringIO()
resizedImage.save(resizedImageFile , 'PNG', optimize = True)
resizedImageFile.seek(0) # So that the next read starts at the beginning
return resizedImageFile

It would be better for you to save the uploaded image and then display and resize it in template as you wish. This way you will be able to resize images at runtime. sorl-thumbnail is djano app which you can use for template image resizing, it is easy to use and you can use it in a view too. Here are examples for this app.

Related

Python, JSignature, and ReportLab

I'm looking to write a signature to PDF. I'm using JSignature and Reportlab. My code works successfully for writing the data to a file and the database. I just cannot figure out how to write the signature to the canvas. Has anyone passed the signature into the canvas successfully?
Thank you in advance.
Here's a look at my code:
pdf.py
import io
from django.core.files.base import ContentFile
from reportlab.lib.units import inch
from reportlab.pdfgen import canvas
from reportlab.lib.utils import ImageReader
def create_pdf(parent):
# create a file-like buffer to receive PDF data
buffer = io.BytesIO()
# create the pdf object, using the buffer as its "file"
p = canvas.Canvas(buffer)
# create text
textobject = p.beginText()
# start text at top left of page
textobject.setTextOrigin(inch, 11*inch)
# set font and size
textobject.setFont("Helvetica-Bold", 18)
textobject.textLine("My Document")
textobject.textLine("")
# write page 1
textobject.setFont("Helvetica", 12)
p_name = f'Name: {participant.first_name} {participant.middle_initial} {participant.last_name}'
textobject.textLine(p_name)
sig = f'Signature:'
textobject.textLine(sig)
----insert signature here----
# write created text to canvas
p.drawText(textobject)
# close the pdf canvas
p.showPage()
p.save()
buffer.seek(0)
# get content of buffer
pdf_data = buffer.getvalue()
# save to django File object
file_data = ContentFile(pdf_data)
# name the file
file_data.name = f'{participant.last_name}.pdf'
#
participant.pdf = file_data
participant.save()
Model:
class Participant(models.Model):
first_name = models.CharField(max_length=50)
middle_initial = models.CharField(max_length=50, blank=True)
last_name = models.CharField(max_length=50, blank=True)
signature = JSignatureField()
pdf = models.FileField(blank=True, null=True)
For those interested in how I was able to get this functioning. The primary issue was The image would be completely black when pulling it into the PDF. Here’s what is required:
In your View:
Use the Jsignature draw_signature function and get the image:
rsr_image = draw_signature(signature)
save the signature as a PNG and then store
# save signature as png to prevent darkening, save to model
rsr_file_name = str(new_parent.id)+'_rsr.png'
buffer = BytesIO()
rsr_image.save(buffer, 'PNG')
new_parent.rsr_image.save(rsr_file_name, File(buffer))
Create the following function, in order to…
Open the image, create a new background for the image, and save it.
def get_jpeg_image(new_parent):
# open png image
png_image = Image.open(new_parent.rsr_image)
# create new image with 'RGB' mode which is compatible with jpeg,
# with same size as old and with white(255,255,255) background
bg = Image.new("RGB", png_image.size, (255, 255, 255))
# paste old image pixels in new background
bg.paste(png_image, png_image)
# give image file name
file_name_jpeg = str(new_parent.id)+'.jpg'
bg.save(file_name_jpeg)
return file_name_jpeg
Reference that function inside your create PDF function to convert the PNG to JPG
jpeg_image = get_jpeg_image(participant)
Hope this helps someone.

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)
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())
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")
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
I achieved some results when combining your question with the answer of Dustin:
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 > 128 or y > 128:
thumbnail_blob = bucket.blob('new_image.jpg')
thumbnail_blob.upload_from_string(im.resize((128, 128), 2).tobytes())
The 2 in resize is for resampling filter:
Use Image.NEAREST (0), Image.LANCZOS (1), Image.BILINEAR (2), Image.BICUBIC (3), Image.BOX (4) or Image.HAMMING (5)

How to change image format without writing it to disk using Python Pillow

I got Pillow image that i got from the Internet:
response= urllib2.urlopen(<url to gif image>)
img = Image.open(cStringIO.StringIO(response.read()))
I want to use it with tesserocr but it wont work with GIF images.
If I save the image as PNG img.save("tmp.png") and load it img = Image.open("tmp.png") everything works.
Is there a way to do this conversion without writing to disk?
import io
from PIL import Image
def convertImageFormat(imgObj, outputFormat=None):
"""Convert image format
Args:
imgObj (Image): the Pillow Image instance
outputFormat (str): Image format, eg: "JPEG"/"PNG"/"BMP"/"TIFF"/...
more refer: https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
Returns:
bytes, binary data of Image
Raises:
"""
newImgObj = imgObj
if outputFormat and (imgObj.format != outputFormat):
imageBytesIO = io.BytesIO()
imgObj.save(imageBytesIO, outputFormat)
newImgObj = Image.open(imageBytesIO)
return newImgObj
call example:
pngImgFile = "xxx.png"
pngImgObj = Image.open(pngImgFile)
convertToFormat = "JPEG"
convertedJpgImgBytes = convertImageFormat(pngImgObj, convertToFormat)
advanced version convertImageFormat can refer my lib crifanPillow.py
import io
from PIL import Image
def convertImageFormat(imgObj, outputFormat=None, isOptimize=False, isKeepPrevValues=True):
"""Convert image format
Args:
imgObj (Image): the Pillow Image instance
outputFormat (str): Image format, eg: "JPEG"/"PNG"/"BMP"/"TIFF"/...
more refer: https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
isOptimize (bool): do optimize when using save to convert format
isKeepPrevValues (bool): keep previous property values, such as: filename
Returns:
bytes, binary data of Image
Raises:
"""
newImgObj = imgObj
if outputFormat and (imgObj.format != outputFormat):
imageBytesIO = io.BytesIO()
if isOptimize:
imgObj.save(imageBytesIO, outputFormat, optimize=True)
else:
imgObj.save(imageBytesIO, outputFormat)
newImgObj = Image.open(imageBytesIO)
if isKeepPrevValues:
if imgObj.filename:
newImgObj.filename = imgObj.filename
return newImgObj
The solution was very simple:
response= urllib2.urlopen(<url to gif image>)
img = Image.open(cStringIO.StringIO(response.read()))
img = img.convert("RGB")
Note that you need to remove the alpha channel info to make image compatible with tesserocr

Use custom scrapy imagePipeline to download images and overwrite existing images

I am practising using scrapy to crop image with a custom imagePipeline.
I am using this code:
class MyImagesPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
for image_url in item['image_urls']:
yield Request(image_url)
def convert_image(self, image, size=None):
if image.format == 'PNG' and image.mode == 'RGBA':
background = Image.new('RGBA', image.size, (255, 255, 255))
background.paste(image, image)
image = background.convert('RGB')
elif image.mode != 'RGB':
image = image.convert('RGB')
if size:
image = image.copy()
image.thumbnail(size, Image.ANTIALIAS)
else:
# cut water image TODO use defined image replace Not cut
x,y = image.size
if(y>120):
image = image.crop((0,0,x,y-25))
buf = StringIO()
try:
image.save(buf, 'JPEG')
except Exception, ex:
raise ImageException("Cannot process image. Error: %s" % ex)
return image, buf
It works well but have a problem.
If there are original images in the folder,
then run the spider,
the images it download won't replace the original one.
How can I get it to over-write the original images ?
There is an expiration setting, it is by default 90 days.

Converting an UploadedFile to PIL image in Django

I'm trying to check an image's dimension, before saving it. I don't need to change it, just make sure it fits my limits.
Right now, I can read the file, and save it to AWS without a problem.
output['pic file'] = request.POST['picture_file']
conn = myproject.S3.AWSAuthConnection(aws_key_id, aws_key)
filedata = request.FILES['picture'].read()
content_type = 'image/png'
conn.put(
bucket_name,
request.POST['picture_file'],
myproject.S3.S3Object(filedata),
{'x-amz-acl': 'public-read', 'Content-Type': content_type},
)
I need to put a step in the middle, that makes sure the file has the right size / width dimensions. My file isn't coming from a form that uses ImageField, and all the solutions I've seen use that.
Is there a way to do something like
img = Image.open(filedata)
image = Image.open(file)
#To get the image size, in pixels.
(width,height) = image.size()
#check for dimensions width and height and resize
image = image.resize((width_new,height_new))
I've done this before but I can't find my old snippet... so here we go off the top of my head
picture = request.FILES.get['picture']
img = Image.open(picture)
#check sizes .... probably using img.size and then resize
#resave if necessary
imgstr = StringIO()
img.save(imgstr, 'PNG')
imgstr.reset()
filedata = imgstr.read()
The code bellow creates the image from the request, as you want:
from PIL import ImageFile
def image_upload(request):
for f in request.FILES.values():
p = ImageFile.Parser()
while 1:
s = f.read(1024)
if not s:
break
p.feed(s)
im = p.close()
im.save("/tmp/" + f.name)

Categories