4

For asynchronous file saving, I can use aiofiles library.

To use aiofiles library I'd have to do something like that:

async with aiofiles.open(path, "wb") as file:
   await file.write(data)

How can I asynchronously save the PIL images? Even if I use Image.tobytes function to save it with file.write(data), the saved image isn't correct.

So how can I asynchronously save a PIL image?

Karol
  • 600
  • 7
  • 18
  • 2
    I don't know anything about asyncio, but you could maybe encode your image as JPEG or PNG to a memory buffer and pass that buffer (called `JPEG` in the linked example) to the `file.write()` you mention in your question. See https://stackoverflow.com/a/70275120/2836621 – Mark Setchell Dec 10 '21 at 15:11
  • Cool - glad it works. If you do some timings and find there is some benefit in performance, please write up an answer for others to benefit from... and accept your own answer and grab the points. – Mark Setchell Dec 10 '21 at 18:04

3 Answers3

7

Thanks to the comment posted by @MarkSetchell I managed to find the solution.

async def save_image(path: str, image: memoryview) -> None:
    async with aiofiles.open(path, "wb") as file:
        await file.write(image)


image = Image.open(...)
buffer = BytesIO()
image.save(buffer, format="JPEG")

await save_image('./some/path', buffer.getbuffer())

I don't know how much speed one can gain, but in my case, I'm able to run some data processing code, data downloading code, and image saving code concurrently which gives me a speed up.

Karol
  • 600
  • 7
  • 18
0

@CrazyChucky, thanks for point out the async sync . I worked again on this

from fastapi import File,UploadFile
from PIL import Image,ImageFont, ImageDraw
from io import BytesIO

@router.post("/users/update_user_profile")
async def upload_file(self, image_file : UploadFile = File(None)):         
   out_image_name = 'image_name'+pathlib.Path(image_file.filename).suffix    
   out_image_path = f"images/user/{out_image_name}"
   request_object_content = await image_file.read()
   photo = Image.open(BytesIO(request_object_content)) 
   # make the image editable
   drawing = ImageDraw.Draw(photo)
   black = (3, 8, 12)   
   drawing.text((0,0), 'Your_text', fill=black)#, font=font)
  
   # Create a buffer to hold the bytes
   buf = BytesIO()

   # Save the image as jpeg to the buffer
   photo.save(buf, format='JPEG')#    

   async with aiofiles.open(out_image_path, 'wb') as out_file:       
       outContent = await out_file.write(buf.getbuffer())  # async write    

  return out_image_name 

I referred @Karol suggestion. Check now, this is for fastapi async method

-1

In my case, following worked ( using python fastapi)

from fastapi import File,UploadFile
from PIL import Image,ImageFont, ImageDraw
from io import BytesIO

async def upload_file(image_file : UploadFile = File(None)):       
    out_image_name = str(123)+pathlib.Path(image_file.filename).suffix 
    out_image_path = f"images/user/{out_image_name}"
    request_object_content = await image_file.read()
    photo = Image.open(BytesIO(request_object_content)) 
    # make the image editable
    drawing = ImageDraw.Draw(photo)
    black = (3, 8, 12)
    # font = ImageFont.truetype("Pillow/Tests/fonts/FreeMono.ttf", 40)
    drawing.text((0,0), 'text_to_water_mark', fill=black)#, font=font)
    photo.save(out_image_path)