Как создать изображение PNG в PIL и отобразить его в шаблоне Jinja2 с помощью FastAPI?

У меня есть конечная точка FastAPI, которая создает изображения PIL. Затем я хочу отправить полученное изображение в виде потока на Jinja2 TemplateResponse. Это упрощенная версия того, что я делаю:

import io
from PIL import Image

@api.get("/test_image", status_code=status.HTTP_200_OK)
def test_image(request: Request):
    '''test displaying an image from a stream.
    '''
    test_img = Image.new('RGBA', (300,300), (0, 255, 0, 0))

    # I've tried with and without this:
    test_img = test_img.convert("RGB")

    test_img = test_img.tobytes()
    base64_encoded_image = base64.b64encode(test_img).decode("utf-8")

    return templates.TemplateResponse("display_image.html", {"request": request,  "myImage": base64_encoded_image})

С помощью этого простого html:

<html>
   <head>
      <title>Display Uploaded Image</title>
   </head>
   <body>
      <h1>My Image<h1>
      <img src = "data:image/jpeg;base64,{{ myImage | safe }}">
   </body>
</html>

Я работал над этими ответами и пробовал несколько их перестановок:

Как отобразить загруженное изображение на HTML-странице с помощью FastAPI и Jinja2?

Как преобразовать объект PIL Image.image в строку base64?

Как я могу отобразить изображение PIL в html с колбой render_template?

Кажется, это должно быть очень просто, но все, что я получаю, это значок html для изображения, которое не отображается.

Что я делаю не так? Спасибо.

Я использовал ответ Марка Сетчелла, который ясно показывает, что я делал неправильно, но все еще не получаю изображение в html. Мой FastAPI:

@api.get("/test_image", status_code=status.HTTP_200_OK)
def test_image(request: Request):
# Create image
    im = Image.new('RGB',(1000,1000),'red')

    im.save('red.png')

    print(im.tobytes())

    # Create buffer
    buffer = io.BytesIO()

    # Tell PIL to save as PNG into buffer
    im.save(buffer, 'PNG')

    # get the PNG-encoded image from buffer
    PNG = buffer.getvalue()

    print()
    print(PNG)

    base64_encoded_image = base64.b64encode(PNG)

    return templates.TemplateResponse("display_image.html", {"request": request,  "myImage": base64_encoded_image})

И мой html:

<html>
   <head>
      <title>Display Uploaded Image</title>
   </head>
   <body>
      <h1>My Image 3<h1>
      <img src = "data:image/png;base64,{{ myImage | safe }}">
   </body>
</html>

Когда я запускаю, если я создаю изображение 1x1, я получаю точные распечатки в ответе Марка. Если я запускаю эту версию с изображением 1000x1000, она сохраняет red.png, который я могу открыть и посмотреть. Но, в конце концов, на html-странице есть заголовок и значок, означающий, что изображение не отображается. Сейчас я явно делаю что-то не так, когда отправляю в html.

Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
Потяните за рычаг выброса энергососущих проектов
Потяните за рычаг выброса энергососущих проектов
На этой неделе моя команда отменила проект, над которым я работал. Неделя усилий пошла насмарку.
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Библиотека для работы с мороженым
Библиотека для работы с мороженым
Лично я попрощался с операторами print() в python. Без шуток.
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Привет, люди RPA, это снова я и я несу подарки! В очередном моем приключении о том, как создавать ботов для облегчения рутины. Вот, думаю, стоит...
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
2
0
71
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Здесь есть несколько проблем. Я сделаю новый раздел для каждого, чтобы четко разделить его.


Если вы хотите отправить PNG в кодировке base64, вам нужно изменить свой HTML на:

<img src = "data:image/png;base64,{{ myImage | safe }}">

Если вы создадите изображение одного красного пикселя следующим образом:

im = Image.new('RGB',(1,1),'red')
print(im.tobytes())

Ты получишь:

b'\xff\x00\x00'

Это не изображение в формате PNG, как это могло быть - вы не сказали PIL, что вам нужен PNG, или JPEG, или TIFF, поэтому он не может знать. Это просто дает вам 3 необработанных пикселя RGB в виде байтов #ff0000.

Если вы сохраните это изображение на диск в формате PNG и выгрузите его, вы получите:

im.save('red.png')

Затем сбросьте это:

xxd red.png

00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
00000010: 0000 0001 0000 0001 0802 0000 0090 7753  ..............wS
00000020: de00 0000 0c49 4441 5478 9c63 f8cf c000  .....IDATx.c....
00000030: 0003 0101 00c9 fe92 ef00 0000 0049 454e  .............IEN
00000040: 44ae 4260 82                             D.B`.

Теперь вы можете увидеть подпись PNG в начале. Итак, нам нужно создать то же самое, но только в памяти, не заморачиваясь на диске:

import io
import base64
from PIL import image

# Create image
im = Image.new('RGB',(1,1),'red')

# Create buffer
buffer = io.BytesIO()

# Tell PIL to save as PNG into buffer
im.save(buffer, 'PNG')

Теперь мы можем получить изображение в формате PNG из буфера:

PNG = buffer.getvalue()

И если мы его распечатаем, он будет подозрительно идентичен PNG на диске:

b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x9cc\xf8\xcf\xc0\x00\x00\x03\x01\x01\x00\xc9\xfe\x92\xef\x00\x00\x00\x00IEND\xaeB`\x82'

Теперь вы можете закодировать его в base64 и отправить:

base64_encoded_image = base64.b64encode(PNG)

Примечание. Я сделал 1x1 только в демонстрационных целях, чтобы показать вам весь файл. Сделайте его больше 1x1 при тестировании, иначе вы его никогда не увидите 😀

Спасибо за этот очень поучительный ответ, я вижу, что я делал неправильно с PNG, но он все еще не работает. Я отредактировал свой ответ, чтобы показать, что у меня есть сейчас. Я получаю результаты именно так, как вы говорите (в отпечатках и сохраненном файле), но все равно не получаю отрендеренное изображение в html. Любая идея, что я делаю неправильно здесь?

Brad Allen 11.01.2023 16:26

Зайдите в свой веб-браузер, перейдите в «Инструменты разработчика» и откройте «Исходный код страницы» или, как они называются в вашем браузере, и посмотрите, что ваше приложение отправило в виде HTML, в частности часть <img src = "...">.

Mark Setchell 11.01.2023 16:33

Спасибо, я не расшифровывал base64 PNG. Хотя в первом коде в вопросе, который я разместил, он был, я потерял его в своей версии. Чтобы было понятнее, я опубликовал ответ с полным функциональным кодом на основе вашего ответа. Спасибо.

Brad Allen 12.01.2023 04:22

Я использовал ответ и комментарии Марка Сетчелла, чтобы придумать этот полный код. Я подумал, что полезно показать, что работает:

import base64
from PIL import Image

@api.get("/test_image", status_code=status.HTTP_200_OK)
def test_image(request: Request):
# Create image
    im = Image.new('RGB',(1000,1000),'red')

    # Create buffer
    buffer = io.BytesIO()

    # Tell PIL to save as PNG into buffer
    im.save(buffer, 'PNG')

    # get the PNG-encoded image from buffer
    PNG = buffer.getvalue()

    # the only difference is the .decode("utf-8") added here:
    base64_encoded_image = base64.b64encode(PNG).decode("utf-8")

    return templates.TemplateResponse("display_image.html", {"request": request,  "myImage": base64_encoded_image})
<html>
   <head>
      <title>Display Uploaded Image</title>
   </head>
   <body>
      <h1>My Image 3<h1>
      <img src = "data:image/png;base64,{{ myImage | safe }}">
   </body>
</html>

Это включало устранение некоторых неполадок: Как отобразить изображение байтового типа в шаблоне HTML/Jinja2 с помощью FastAPI?

Пожалуйста, не забудьте правильно close объекты Image и BytesIO, чтобы освободить их память (см. соответствующие ответы здесь , а также здесь).

Chris 13.01.2023 14:42

Другие вопросы по теме