Как я могу оптимизировать размер изображения палитры с PIL? - PullRequest
2 голосов
/ 31 марта 2019

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

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

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

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 Ответ

3 голосов
/ 31 марта 2019

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>

enter image description here


Вы можете загрузить 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
...