Компромисс между качеством и размером файла: как сохранить очень детальное изображение в файл разумного размера (<1 МБ)?

Я столкнулся с небольшой (большой) проблемой: я хочу создать узор спеклов с высоким разрешением и сохранить его как файл, который можно импортировать в лазерный гравер. Это может быть PNG, JPEG, PDF, SVG или TIFF.

Мой скрипт неплохо справляется с созданием нужного мне шаблона:

Пользователю необходимо сначала определить входные данные, а именно:

############
#  INPUTS  #
############
dpi = 1000 # dots per inch
dpmm = 0.03937 * dpi # dots per mm
widthOfSampleMM = 50 # mm
heightOfSampleMM = 50 # mm
patternSizeMM = 0.1 # mm
density = 0.75 # 1 is very dense, 0 is not fine at all
variation = 0.75 # 1 is very bad, 0 is very good
############

После этого я создаю пустую матрицу и заполняю ее черными фигурами, в данном случае кругом.

# conversions to pixels
widthOfSamplesPX = int(np.ceil(widthOfSampleMM*dpmm)) # get the width
widthOfSamplesPX = widthOfSamplesPX + 10 - widthOfSamplesPX % 10 # round up the width to nearest 10
heightOfSamplePX = int(np.ceil(heightOfSampleMM*dpmm)) # get the height
heightOfSamplePX = heightOfSamplePX + 10 - heightOfSamplePX % 10 # round up the height to nearest 10
patternSizePX = patternSizeMM*dpmm # this is the size of the pattern, so far I am going with circles
# init an empty image
im = 255*np.ones((heightOfSamplePX, widthOfSamplesPX), dtype = np.uint8)
# horizontal circle centres
numPoints = int(density*heightOfSamplePX/patternSizePX) # get number of patterns possible
if numPoints==1:
    horizontal = [heightOfSamplePX // 2]
else:
    horizontal = [int(i * heightOfSamplePX / (numPoints + 1)) for i in range(1, numPoints + 1)]
# vertical circle centres
numPoints = int(density*widthOfSamplesPX/patternSizePX)
if numPoints==1:
    vertical = [widthOfSamplesPX // 2]
else:
    vertical = [int(i * widthOfSamplesPX / (numPoints + 1)) for i in range(1, numPoints + 1)]
for i in vertical:
    for j in horizontal:
        # generate the noisy information
        iWithNoise = i+variation*np.random.randint(-2*patternSizePX/density, +2*patternSizePX/density)
        jWithNoise = j+variation*np.random.randint(-2*patternSizePX/density, +2*patternSizePX/density)
        patternSizePXWithNoise = patternSizePX+patternSizePX*variation*(np.random.rand()-0.5)/2
        cv2.circle(im, (int(iWithNoise),int(jWithNoise)), int(patternSizePXWithNoise//2), 0, -1) # add circle

После этого шага я могу получить im, вот пример низкого качества на dpi=1000:

А вот один с моим целевым разрешением (5280):

Теперь я хотел бы сохранить im удобным способом и в высоком качестве (DPI>1000). Есть какой-либо способ сделать это?


Вещи, которые я пробовал до сих пор:

  1. построение и сохранение изображения в форматах PNG, TIFF, SVG, PDF с разными значениями DPI plt.savefig() с разным разрешением dpi
  2. cv2.imwrite() слишком большой файл, единственное решение здесь — уменьшить DPI, что также снижает качество
  3. SVG записываем из матрицы: Я разработал эту функцию, но в конечном итоге файлы оказались слишком большими:
import svgwrite
def matrix_to_svg(matrix, filename, padding = 0, cellSize=1):
    # get matrix dimensions and extremes
    rows, cols = matrix.shape
    minVal = np.min(matrix)
    maxVal = np.max(matrix)
    # get a drawing
    dwg = svgwrite.Drawing(filename, profile='tiny', 
                           size = (cols*cellSize+2*padding,rows*cellSize+2*padding))
    # define the colormap, in this case grayscale since black and white
    colorScale = lambda val: svgwrite.utils.rgb(int(255*(val-minVal)/(maxVal-minVal)),
                                                 int(255*(val-minVal)/(maxVal-minVal)),
                                                 int(255*(val-minVal)/(maxVal-minVal)))
    # get the color of each pixel in the matrix and draw it
    for i in range(rows):
        for j in range(cols):
            color = colorScale(matrix[i, j])
            dwg.add(dwg.rect(insert=(j * cellSize + padding, i * cellSize + padding),
                             size=(cellSize, cellSize),
                             fill=color))
    dwg.save() # save
  1. PIL.save(). Файлы слишком большие

Проблему также можно решить, создав более совершенные формы. Это тоже не будет препятствием. Я готов переписать, используя другой метод, был бы признателен, если бы кто-нибудь просто указал мне в правильном направлении.

Определите «разумный размер» и «слишком большой» (и любые другие субъективные описания, которые есть в посте). И помните, что метакомментарии, такие как «заранее спасибо», не относятся к вопросам.

Dan Mašek 18.07.2024 11:05

Если мои расчеты верны, вы хотите иметь как минимум 400 пикселей на мм (т. е. 160 тысяч пикселей на квадратный мм) и иметь это для квадрата 50 мм (то есть 2500 квадратных мм).... то есть изображение 400 мегапикселей или больше?

Dan Mašek 18.07.2024 11:10

Как именно размер становится проблемой?

Dan Mašek 18.07.2024 11:15

Эй, спасибо за комментарии. Я скорректировал пост и ваши расчеты верны. Размер файла становится проблемой при его импорте в лазерный гравер. Он не может хорошо обрабатывать большие файлы и работает очень медленно, а иногда вообще не импортирует их. Лазерный гравер также может работать с PDF или SVG. @ДанМашек

Tino D 18.07.2024 11:18

Хм, так что на самом деле ограничивающим фактором является количество необработанных пикселей (произведение DPI и физического размера). В этом случае не имеет значения, будет ли это PNG, JPG, TIFF или любой другой растровый формат. | Интересно, что он делает с векторными файлами внутри... не удивлюсь, если он их растрирует, и в этот момент мы возвращаемся к тому, с чего начали, и формат файла не имеет особого значения.

Dan Mašek 18.07.2024 11:34

Однако, глядя на генерацию SVG, кажется, что вы просто рисуете сетку прямоугольников, по одному на каждый пиксель, и, о боже, я не хочу представлять ее размер на диске. Я ожидал, что у меня будут круги, по одному для каждого места, которое вы сгенерировали ранее (и пусть средство визуализации SVG превратит их в пиксели с любым необходимым разрешением). Знаете, настоящее векторное изображение.

Dan Mašek 18.07.2024 11:38

Спасибо за комментарий. Я мог бы использовать функцию для преобразования матрицы в SVG, чтобы нарисовать SVG с самого начала. Я переписываю и изображение теперь в пределах 1 МБ при том DPI, который я хочу :D

Tino D 18.07.2024 11:42

@DanMašek опубликуй это как ответ, ты заставил меня увидеть то, чего я не мог раньше

Tino D 18.07.2024 11:43

Я рад, что вы больше не создаете файлы SVG, состоящие из пикселей.

Christoph Rackwitz 18.07.2024 12:09

Детализация и размер файла — сопряженная пара. Маленькие файлы подразумевают меньше данных и высокую степень сжатия исходного материала. Случайные битовые комбинации практически несжимаемы. Лучшее сжатие для ваших почти случайных спекл-данных, скорее всего, — это исходный код алгоритма генерации и запуск его в месте назначения. Требование файла <1 МБ кажется неоправданно ограничительным в наши дни, когда большие диски и USB-накопители настолько дешевы. Набор хорошо подобранных фрактальных правил аффинного преобразования может сделать именно то, что вы хотите.

Martin Brown 18.07.2024 12:24

@MartinBrown хорошие рекомендации. Лазерный гравер имеет очень ограниченный пользовательский интерфейс и не может выполнять все эти функции. Если бы это было возможно, это действительно было бы плохим решением.

Tino D 18.07.2024 12:28

Можете ли вы приказать лазерному граверу замостить плоскость поворотами и/или переворотами исходного файла? Этого может быть достаточно, чтобы обмануть глаз и заставить его не видеть границ. Соседние плитки с симметрией отражения были бы слишком заметны.

Martin Brown 18.07.2024 12:39

если вы хотите еще больше сжать SVG, усеките значения с плавающей запятой. если они получены случайным образом, они, скорее всего, будут занимать 8–16 десятичных знаков. однако, если это целые числа, все будет в порядке.

Christoph Rackwitz 18.07.2024 13:05

@MartinBrown нет, лазер просто позволяет вам импортировать данные изображения, хотя это можно сделать заранее, например, в Gimp... здесь мы также можем потерять качество или получить файлы большего размера

Tino D 18.07.2024 14:54

@ChristophRackwitz спасибо за рекомендацию, я подумаю :D

Tino D 18.07.2024 14:54

@KJ, выглядит неплохо, как ты это сделал?

Tino D 18.07.2024 16:20

@TinoD Внес еще несколько изменений и обновил ответ. Спасибо за интересное упражнение :) (кстати, в следующий раз лучший MCVE не помешает, потребовалось некоторое время, чтобы сгладить несоответствия (например, WidthOfSample_mm против widthOfSampleMM) и устранить часть копипасты ;))

Dan Mašek 18.07.2024 22:29

@DanMašek Думаю, я скопировал первый раздел, скорректировал текст в другом, а затем опубликовал вопрос 😅 Извините, исправлю это в вопросе

Tino D 19.07.2024 08:14
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
4
18
211
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

Давайте сделаем некоторые наблюдения о последствиях изменения DPI:

DPI 1000   Height=1970   Width=1970    # Spots=140625  Raw pixels: 3880900
DPI 10000  Height=19690  Width=19690   # Spots=140625  Raw pixels: 387696100

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

К сожалению, способ создания SVG ошибочен, поскольку вы фактически превратили его в крайне неэффективное растровое представление. Это связано с тем, что вы генерируете прямоугольник для каждого отдельного пикселя (даже для тех, которые технически являются фоновыми). Учтите, что в 8-битном изображении в оттенках серого, таком как созданные вами PNG, требуется 1 байт для представления необработанного пикселя. С другой стороны, ваше SVG-представление одного пикселя выглядит примерно так:

<rect fill = "rgb(255,255,255)" height = "1" width = "1" x = "12345" y = "15432" />

Использование ~70 байт на пиксель, когда мы говорим о десятках мегапикселей... явно не лучший вариант.

Однако напомним, что количество роликов не зависит от DPI. Можем ли мы просто представить пятна каким-нибудь эффективным способом? На самом деле пятна представляют собой круги, параметризованные положением, радиусом и цветом. SVG поддерживает круги, и их представление выглядит следующим образом:

<circle cx = "84" cy = "108" fill = "rgb(0,0,0)" r = "2" />

Давайте теперь посмотрим на последствия изменения DPI.

DPI 1000   # Spots=140625  Raw pixels: 3880900    SVG size: 7435966
DPI 10000  # Spots=140625  Raw pixels: 387696100  SVG size: 7857942

Небольшое увеличение размера связано с увеличением диапазона значений положения/радиуса.


Я несколько реорганизовал ваш пример кода. Вот результат, демонстрирующий вывод SVG.

import numpy as np
import cv2
import svgwrite

MM_IN_INCH = 0.03937

def round_int_to_10s(value):
    int_value = int(value)
    return int_value + 10 - int_value % 10

def get_sizes_pixels(height_mm, width_mm, pattern_size_mm, dpi):
    dpmm = MM_IN_INCH * dpi # dots per mm
    width_px = round_int_to_10s(np.ceil(width_mm * dpmm))
    height_px = round_int_to_10s(np.ceil(height_mm * dpmm))
    pattern_size_px = pattern_size_mm * dpmm
    return height_px, width_px, pattern_size_px
 
def get_grid_positions(size, pattern_size, density):
    count = int(density * size / pattern_size) # get number of patterns possible
    if count == 1:
        return [size // 2]
    return [int(i * size / (count + 1)) for i in range(1, count + 1)]
 
def get_spot_grid(height_px, width_px, pattern_size_px, density):
    vertical = get_grid_positions(height_px, pattern_size_px, density)
    horizontal = get_grid_positions(width_px, pattern_size_px, density)
    return vertical, horizontal

def generate_spots(vertical, horizontal, pattern_size, density, variation):
    spots = []
    noise_halfspan = 2 * pattern_size / density;
    noise_min, noise_max = (-noise_halfspan, noise_halfspan)
    for i in vertical:
        for j in horizontal:
            # generate the noisy information
            center = tuple(map(int, (j, i) + variation * np.random.randint(noise_min, noise_max, 2)))
            d = int(pattern_size + pattern_size * variation * (np.random.rand()-0.5) / 2)
            spots.append((center, d//2)) # add circle params
    return spots

def render_raster(height, width, spots):
    im = 255 * np.ones((height, width), dtype=np.uint8)
    for center, radius in spots:
        cv2.circle(im, center, radius, 0, -1) # add circle
    return im
    
def render_svg(height, width, spots):
    dwg = svgwrite.Drawing(profile='tiny', size = (width, height))
    fill_color = svgwrite.utils.rgb(0, 0, 0)
    for center, radius in spots:
        dwg.add(dwg.circle(center, radius, fill=fill_color)) # add circle
    return dwg.tostring()


#  INPUTS  #
############
dpi = 100 # dots per inch
WidthOfSample_mm = 50 # mm
HeightOfSample_mm = 50 # mm
PatternSize_mm = 1 # mm
density = 0.75 # 1 is very dense, 0 is not fine at all
Variation = 0.75 # 1 is very bad, 0 is very good
############

height, width, pattern_size = get_sizes_pixels(HeightOfSample_mm, WidthOfSample_mm, PatternSize_mm, dpi)
vertical, horizontal = get_spot_grid(height, width, pattern_size, density)
spots = generate_spots(vertical, horizontal, pattern_size, density, Variation)

img = render_raster(height, width, spots)
svg = render_svg(height, width, spots)

print(f"Height = {height}  Width = {width}   # Spots = {len(spots)}")
print(f"Raw pixels: {img.size}")
print(f"SVG size: {len(svg)}")

cv2.imwrite("timo.png", img)
with open("timo.svg", "w") as f:
    f.write(svg)

Это генерирует следующий вывод:

PNG | Отрисованный SVG

Примечание. Поскольку здесь невозможно загрузить SVG, я помещаю его в Pastebin и предоставляю его захват, обработанный Firefox.


Возможны дальнейшие улучшения размера SVG. Например, в настоящее время мы используем один и тот же цвет поверх наложения. Стилизация или группировка должна помочь устранить эту избыточность.

Вот пример, в котором все пятна группируются в одну группу с постоянным цветом заливки:

def render_svg(height, width, spots):
    dwg = svgwrite.Drawing(profile='tiny', size = (width, height))
    dwg_spots = dwg.add(dwg.g(id='spots', fill='black'))
    for center, radius in spots:
        dwg_spots.add(dwg.circle(center, radius)) # add circle
    return dwg.tostring()

Вывод выглядит так же, но размер файла теперь составляет 4904718 байт вместо 7435966 байт.

Альтернатива (указана AKX), если вы хотите рисовать только черным цветом, вы можете опустить спецификацию fill, а также группировку, поскольку цвет заливки SVG по умолчанию — черный.


Следующее, на что следует обратить внимание, это то, что большинство пятен имеют одинаковый радиус — фактически, при использовании настроек при разрешении 1000 уникальные радиусы равны [1, 2], а при разрешении 10 000 — [15, 16, 17, 18, 19, 20, 21, 22, 23].

Как мы могли бы избежать многократного указания одного и того же радиуса? (Насколько я могу судить, мы не можем использовать группы для его определения). Фактически, как мы можем не указывать неоднократно, что это круг? В идеале мы бы просто сказали ему: «Нарисуйте эту отметку во всех этих позициях» и просто предоставили список точек.

Оказывается, есть две особенности SVG, которые позволяют нам сделать именно это. Прежде всего, мы можем указать пользовательские маркеры , а затем ссылаться на них по идентификатору.

<marker id = "id1" markerHeight = "2" markerWidth = "2" refX = "1" refY = "1">
  <circle cx = "1" cy = "1" fill = "black" r = "1" />
</marker>

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

<polyline fill = "none" marker-end = "url(#id1)" marker-mid = "url(#id1)" marker-start = "url(#id1)"
  points = "2,5 8,22 11,26 9,46 8,45 2,70 ... and so on" stroke = "none" />

Вот код:

def group_by_radius(spots):
    radii = set([r for _,r in spots])
    groups = {r: [] for r in radii}
    for c, r in spots:
        groups[r].append(c)
    return groups

def render_svg_v2(height, width, spots):
    dwg = svgwrite.Drawing(profile='full', size=(width, height))
    by_radius = group_by_radius(spots)
    dwg_grp = dwg.add(dwg.g(stroke='none', fill='none'))
    for r, centers in by_radius.items():
        dwg_marker = dwg.marker(id=f'r{r}', insert=(r, r), size=(2*r, 2*r))
        dwg_marker.add(dwg.circle((r, r), r=r))
        dwg.defs.add(dwg_marker)
        dwg_line = dwg_grp.add(dwg.polyline(centers))
        dwg_line.set_markers((dwg_marker, dwg_marker, dwg_marker))
    return dwg.tostring()

Выходной SVG по-прежнему выглядит так же, но теперь размер файла при разрешении 1000 DPI уменьшился до 1248852 байт.


При достаточно высоком разрешении многие координаты будут состоять из 3, 4 или даже 5 цифр. Если мы объединим координаты в плитки по 100 или 1000 пикселей, мы сможем воспользоваться элементом use, который позволяет нам применить смещение к объекту, на который ссылаются. Таким образом, мы можем ограничить координаты полилинии двумя или тремя цифрами ценой некоторых дополнительных накладных расходов (что, как правило, того стоит).

Вот первоначальная (неуклюжая) реализация этого:

def bin_points(points, bin_size):
    bins = {}
    for x,y in points:
        bin = (max(0, x // bin_size), max(0, y // bin_size))
        base = (bin[0] * bin_size, bin[1] * bin_size)
        offset = (x - base[0], y - base[1])
        if base not in bins:
            bins[base] = []
        bins[base].append(offset)
    return bins

def render_svg_v3(height, width, spots, bin_size):
    dwg = svgwrite.Drawing(profile='full', size=(width, height))
    by_radius = group_by_radius(spots)
    dwg_grp = dwg.add(dwg.g(stroke='none', fill='none'))
    polyline_counter = 0
    for r, centers in by_radius.items():
        dwg_marker = dwg.marker(id=f'm{r}', insert=(r, r), size=(2*r, 2*r))
        dwg_marker.add(dwg.circle((r, r), r=r, fill='black'))
        dwg.defs.add(dwg_marker)
       
        dwg_marker_grp = dwg_grp.add(dwg.g())
        marker_iri = dwg_marker.get_funciri()
        for kind in ['start','end','mid']:
            dwg_marker_grp[f'marker-{kind}'] = marker_iri
        
        bins = bin_points(centers, bin_size)
        for base, offsets in bins.items():
            dwg_line = dwg.defs.add(dwg.polyline(id=f'p{polyline_counter}', points=offsets))
            polyline_counter += 1            
            dwg_marker_grp.add(dwg.use(dwg_line, insert=base))
    return dwg.tostring()

Если размер ячейки установлен на 100, а DPI — 1000, мы получим размер файла 875012 байт, что означает около 6,23 байта на место. Это не так уж и плохо для формата на основе XML. При DPI, равном 10 000, нам нужен размер ячейки 1 000, чтобы добиться значительного улучшения, что дает примерно 1349325 байт (~ 9,6 Б/спот).

Разве цвет заливки SVG по умолчанию не черный? Весь атрибут fill, вероятно, можно было бы опустить...

AKX 18.07.2024 14:54

@AKX Правда, хороший улов, добавлю в ответ. Хотя по сравнению с использованием группы это всего на ~30 символов меньше.

Dan Mašek 18.07.2024 15:07

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

Dan Mašek 18.07.2024 22:02

@DanMašek Большое спасибо, Дэн, я работал над сценарием на основе твоего ответа, и он работает очень хорошо. Если что-то будет опубликовано, я свяжусь с вами, чтобы отдать должное. Хорошей пятницы и приятных выходных :D

Tino D 19.07.2024 08:12

Я знаю, что комментарии не предназначены для комплиментов, но это действительно замечательный ответ. Съешьте этот ChatGPT... 😀

Costantino Grana 19.07.2024 10:25

Преимущество изображения в PDF заключается в том, что каждый пиксель по умолчанию очень похож на векторный квадрат. Мы можем увидеть это, увеличив описанное целевое изображение размером 5028x5028.

Здесь протестировано как черное лучшее сжатие Tiff. Его размер будет 138 КБ в пределах желаемого 1 МБ, поскольку он сжимается как ФАКС. Но есть и другие сжатия, более эффективные в PDF. Основное решение заключается в том, как использовать приложение для внедрения в качестве «выгружаемого» объекта. Python имеет тенденцию использовать ImageMagick (с Ghostscript) или Pillow, которые не всегда являются наиболее сжимающим маршрутом. Однако в этом случае все должно работать хорошо. Что малоизвестно, так это то, что Acrobat Reader можно запустить для применения сжатия на основе Adobe, и я использовал «Сохранить как» в Adobe Reader, чтобы получить первоначально «Большой» результат, а затем повторно сжал его. Однако TIFF должен быть достаточно хорош.

К сожалению, оно было настолько хорошо сжато в формате PDF с помощью Acrobat Reader, что не принимается здесь как допустимое изображение! Итак, несколько скриншотов.
Размер страницы составляет 72 пункта = 1 дюйм по умолчанию. Помните, что в PDF-файле нет понятия DPI, но я уверяю вас, что в обоих направлениях X и Y имеется 5028 точек.

Давайте увеличим масштаб до максимального масштаба = 6400%. И рандомизированные капли довольно хороши, без видимых артефактов «ореолов» и значительного «сглаживания» краев. Конечно, он не будет таким равномерным и гладким, как круговой векторный узор, если не добавить серый цвет AntiAlias. Все это занимает 96,5 КБ, что намного меньше, чем байты, необходимые для описания нескольких кругов PDF. Используя ряд компрессоров, его можно уменьшить до 10 КБ, но затрачиваемое на это время нежелательно, и чем больше вы сжимаете, тем непропорционально увеличивается время чтения путем распаковки. Следовательно, скорость факса является оптимальным балансом.

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

ОДИН маленький кружок SVG, сохраненный в браузере «Сохранить как PDF». Вектор начинается с перемещения на 46 59 (коротко и приятно), но затем он тратит огромное количество байтов, рисуя закругленные края, как трехпенсовую монету, но с 16 краями. Сжатие Gzip поможет, но оно далеко не так эффективно, как сжатие пикселей.

Вы можете видеть, что этот радиус составляет 2 единицы, поскольку он привязан к 42 57 к 46 61 и возвращается к 46 59.

46 59 m
45.999998 59.265214 45.94925 59.520334 45.847757 59.76536 c
45.74626 60.010389 45.60175 60.22667 45.414216 60.41421 c
45.226675 60.601747 45.01039 60.746259 44.76536 60.847757 c
44.520334 60.94925 44.265214 60.999998 44 61 c

43.73478 60.999998 43.479658 60.94925 43.234628 60.847757 c
42.989599 60.746259 42.77332 60.601747 42.585786 60.41421 c
42.398248 60.22667 42.25373 60.010389 42.152238 59.765359 c
42.050745 59.520334 41.999998 59.265214 42 59 c

41.999998 58.734785 42.050745 58.47966 42.152238 58.23463 c
42.25373 57.9896 42.398248 57.773317 42.585786 57.58578 c
42.77332 57.398248 42.989599 57.25373 43.234628 57.152238 c
43.479658 57.050748 43.73478 57 44 57 c

44.265214 57 44.520334 57.050748 44.76536 57.152238 c
45.01039 57.25373 45.226675 57.398248 45.414216 57.58578 c
45.60175 57.773317 45.74626 57.989599 45.847757 58.234628 c
45.94925 58.479658 45.999998 58.734785 46 59 c
h
f

Мы можем использовать собственное описание векторных кругов в формате PDF, но размер каждого из них все равно будет превышать 100 байт.

Чтобы уложиться в требование в 1 000 000 байт, мы должны были бы ограничиться потенциальным максимумом менее 10 000, и чем сложнее распространение, тем меньше можно будет разместить.

Векторные круги в PDF, безусловно, имеют свое применение, но их следует использовать с осторожностью.

Узор из ограниченного количества можно легко многократно выкладывать плиткой более эффективным способом. Но для совершенно случайного макета это феноменальный объем данных. Обычно преобразование чисто векторных рисунков из PDF в экранные пиксели занимает много времени.

В виде пикселей LoadDocument: 104.86 ms
В виде векторов Slow rendering: 214.33 ms, page: 1
Таким образом, примерно в два раза дольше в грубом тесте.

Просто из любопытства, сколько пятен/кружков вы там нарисовали (по сравнению с моими тестами их 140625)? Просто чтобы мы сравнивали яблоки с яблоками :) Очевидно, что PDF — это не XML, как SVG, и он имеет гораздо более краткое представление, не говоря уже о сжатии, поэтому я ожидаю, что даже в качестве чистого векторного рисунка он победит.

Dan Mašek 18.07.2024 20:12

Конечно, денежный вопрос в том, как именно вы создаете эти PDF-файлы? Мне сейчас трудно найти это из вашего ответа.

Dan Mašek 18.07.2024 20:14

Как упомянул Дэн, мне также было бы очень интересно узнать, как вы создаете эти изображения. О потере «круглости» кругов не стоит упоминать, поскольку лазер все равно не сможет их сделать.

Tino D 19.07.2024 08:13

Изображения, включенные в вопрос, представляют собой скопированные графики matplotlib. Чтобы получить истинное изображение с разрешением 5028 точек на дюйм, вам нужно запустить код. @КейДжей

Tino D 19.07.2024 13:32

лазерный гравер с радостью нарисует векторные траектории. векторы являются подходящим форматом ввода для такого устройства. PDF здесь никого не волнует. это всего лишь средство для достижения цели.

Christoph Rackwitz 19.07.2024 16:37

Вы можете использовать TIFF с плотностью 1 бит на пиксель со сжатием факса Group4. Для изображения 9842x9842 (5000 dpi) размер файла составляет около 1 Мб. (~0,09 бит на пиксель)

from PIL import Image, ImageDraw
from random import randint
dpi = 5000  # dots per inch
dpmm = 0.03937 * dpi  # dots per mm
widthOfSampleMM = 50  # mm
heightOfSampleMM = 50  # mm
patternSizeMM = 0.1  # mm
sz = (int(widthOfSampleMM*dpmm), int(heightOfSampleMM*dpmm))
numPoints = 140625
im = Image.new('1', sz, 'white')
draw = ImageDraw.Draw(im)
for i in range(numPoints):
    r = randint(0, int(patternSizeMM*dpmm))
    x = randint(0, sz[0])
    y = randint(0, sz[1])
    draw.ellipse(((x, y), (x+r, y+r)), fill='black')
im.save('test.tif', compression='group4')

Это тоже потрясающе! Я немного почитаю об этих методах сжатия. Есть ли у вас какие-либо рекомендации? стоит ли мне просто проверить документацию на подушку?

Tino D 19.07.2024 22:30

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

Dan Mašek 20.07.2024 01:18

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