Как я могу оптимизировать размер изображения палитры с помощью PIL?

#python #image #python-imaging-library

#python #изображение #python-imaging-library

Вопрос:

Моя цель — нарисовать несколько полигонов на черном изображении таким образом, чтобы общий размер результирующего изображения был как можно меньше.

Итак, я прочитал статью в wiki об индексированных цветах (ссылка), и мне кажется, что это хороший выбор для меня (поскольку я должен поддерживать только черный цвет и 5 других, т. Е. всего 6 цветов), а png формат изображения должен поддерживать режим «P» (т. Е. изображения палитры).

Вот почему я создал этот фрагмент кода, чтобы посмотреть, какой размер изображения я получу для 6 цветов и 1224x1024 изображения:

 from PIL import Image, ImageDraw
# Create a black 1224x1024 image
img = Image.new('P', (1224, 1024))
img.putpalette([
    0, 0, 0,  # black background
    236, 98, 98, # red color
    236, 98, 97,
    236, 98, 96,
    236, 98, 95,
    236, 98, 94,
])

draw = ImageDraw.Draw(img)
# Draw a random red polygon at the top left corner of the image
draw.polygon(xy=list(1,1,2,2,3,3,4,4), fill=1)
del draw

img.save('1.png', format='PNG')
  

Результирующий размер изображения составляет 768 байты, что кажется мне слишком большим.

Могу ли я что-нибудь исправить в своем коде, чтобы сделать размер результирующего изображения еще меньше?

Ответ №1:

768 байт не кажутся мне необоснованными для представления изображения с разрешением 1,2 мегапикселя. Вы могли бы попробовать запустить файл, созданный с помощью PIL, через pngcrush вот так, чтобы посмотреть, сможет ли он сократить несколько байтов:

 pngcrush input.png result.png
  

Если вы действительно хотите нарисовать только несколько однотонных полигонов на черном фоне, я бы посоветовал вам обратиться к векторному формату, такому как SVG, пример здесь, а не к растровому формату PNG и др..

Вы также можете использовать rsvg для рендеринга SVG-изображений в PNG, если вам нужно, но ни ваше приложение, ни причина, по которой вам нужны такие маленькие изображения, не ясны из вашего вопроса, поэтому я понятия не имею, подходит ли вам такой вариант.

Вот 300-байтовое SVG-изображение с черным фоном, 2 прямоугольниками и многоугольником в форме красной звезды вверху слева:

 <svg width="1224" height="1024">
  <rect width="100%" height="100%" fill="black"/>
  <polygon points="9.9, 1.1, 3.3, 21.78, 19.8, 8.58, 0, 8.58, 16.5, 21.78" fill="red"/>
  <rect x="100" y="200" width="100" height="400" fill="blue"/>
  <rect x="800" y="280" width="100" height="200" fill="lime"/>
</svg>
  

введите описание изображения здесь


Вы могли бы загрузить SVG в массив Numpy следующим образом:

 #!/usr/bin/env python3

import cairosvg
import io
from PIL import Image

def svgRead(filename):
   """Load an SVG file and return image in Numpy array"""
   # Make memory buffer
   mem = io.BytesIO()
   # Convert SVG to PNG in memory
   cairosvg.svg2png(url=filename, write_to=mem)
   # Convert PNG to Numpy array
   return np.array(Image.open(mem))

# Read SVG file into Numpy array
res = svgRead('image.svg')
  

Если вы одержимы желанием сделать файлы еще меньше ценой снижения совместимости с другими средствами просмотра изображений, вы могли бы разработать свой собственный очень простой формат, несколько похожий на SVG, чтобы вы могли сохранить изображение, представленное в примере SVG, который я привел, в виде простого текстового файла:

 b,1224,1024,#00000
r,100,200,100,400,#0000ff
r,800,280,100,200,#00ff00
p,9.9,1.1,3.3,21.78,19.8,8.58,0,8.58,16.5,21.78,#ff0000
  

И у меня получается 127 байт.


Вы можете просмотреть свои SVG-файлы, загрузив их в любой веб-браузер, или преобразовать их в PNG с помощью ImageMagick в терминале следующим образом:

 magick input.svg result.png
  

Комментарии:

1. Отметьте, вам, вероятно, следует добавить -density XX перед SVG-файлом, чтобы пользователь мог решить, какого размера сделать PNG. Также обратите внимание, что существует 3 средства визуализации SVG, которые ImageMagick может использовать: MSVG / XML (внутренняя визуализация ImageMagick в формате SVG), RSVG delegate и Inkscape (в порядке повышения точности).

2. @fmw42 Фред, спасибо тебе за твой вклад и идеи, как всегда. Я согласен, что -density здесь полезно, но получение хорошего PNG на самом деле не было моей целью. Моей целью было использовать векторный формат, точка. Это более сжато / эффективно для простых сплошных полигонов на черном фоне, и размер файла, очевидно, является серьезной проблемой для операционной системы, если 94% сжатия недостаточно.

3. @Mark Да, я бы согласился. Используйте векторный формат для меньшего размера файла. Я просто добавил примечание о растрировании для полноты картины.