pyembroidery/pyembroidery/PngWriter.py

541 wiersze
18 KiB
Python

import struct
import zlib
from math import sqrt
from .EmbConstant import *
from .EmbThread import EmbThread
SEQUIN_CONTINGENCY = CONTINGENCY_SEQUIN_STITCH
FULL_JUMP = True
# Static characters for writing to image.
characters = {
"0": [
[9, 9, 4, 1, 0, 1, 5, 9, 9],
[9, 3, 0, 0, 0, 0, 0, 5, 9],
[7, 0, 1, 7, 9, 6, 0, 0, 9],
[4, 0, 5, 9, 9, 9, 4, 0, 6],
[3, 0, 8, 9, 9, 9, 6, 0, 4],
[2, 0, 9, 5, 0, 6, 8, 0, 3],
[2, 0, 9, 4, 0, 6, 8, 0, 3],
[2, 0, 9, 9, 9, 9, 8, 0, 3],
[3, 0, 8, 9, 9, 9, 6, 0, 4],
[4, 0, 5, 9, 9, 9, 4, 0, 6],
[7, 0, 1, 7, 9, 6, 0, 0, 8],
[9, 3, 0, 0, 0, 0, 0, 5, 9],
[9, 9, 4, 1, 0, 1, 5, 9, 9],
],
"1": [
[9, 8, 5, 2, 0, 1, 9, 9, 9],
[9, 1, 0, 0, 0, 1, 9, 9, 9],
[9, 2, 4, 7, 1, 1, 9, 9, 9],
[9, 9, 9, 9, 1, 1, 9, 9, 9],
[9, 9, 9, 9, 1, 1, 9, 9, 9],
[9, 9, 9, 9, 1, 1, 9, 9, 9],
[9, 9, 9, 9, 1, 1, 9, 9, 9],
[9, 9, 9, 9, 1, 1, 9, 9, 9],
[9, 9, 9, 9, 1, 1, 9, 9, 9],
[9, 9, 9, 9, 1, 1, 9, 9, 9],
[9, 9, 9, 9, 1, 1, 9, 9, 9],
[9, 3, 0, 0, 0, 0, 0, 0, 3],
[9, 3, 0, 0, 0, 0, 0, 0, 3],
],
"2": [
[8, 5, 2, 1, 1, 2, 6, 9, 9],
[4, 0, 0, 0, 0, 0, 0, 4, 9],
[5, 4, 7, 8, 8, 5, 0, 0, 8],
[9, 9, 9, 9, 9, 9, 3, 0, 7],
[9, 9, 9, 9, 9, 9, 3, 0, 9],
[9, 9, 9, 9, 9, 8, 0, 4, 9],
[9, 9, 9, 9, 8, 1, 2, 9, 9],
[9, 9, 9, 8, 2, 2, 9, 9, 9],
[9, 9, 8, 1, 2, 9, 9, 9, 9],
[9, 8, 1, 2, 9, 9, 9, 9, 9],
[7, 1, 2, 9, 9, 9, 9, 9, 9],
[3, 0, 0, 0, 0, 0, 0, 0, 6],
[3, 0, 0, 0, 0, 0, 0, 0, 6],
],
"3": [
[9, 6, 2, 1, 1, 2, 6, 9, 9],
[5, 0, 0, 0, 0, 0, 0, 4, 9],
[6, 3, 7, 8, 9, 6, 1, 0, 8],
[9, 9, 9, 9, 9, 9, 3, 0, 8],
[9, 9, 9, 9, 8, 6, 1, 1, 9],
[9, 9, 4, 0, 0, 0, 2, 8, 9],
[9, 9, 4, 0, 0, 0, 2, 8, 9],
[9, 9, 9, 9, 8, 6, 1, 1, 8],
[9, 9, 9, 9, 9, 9, 5, 0, 6],
[9, 9, 9, 9, 9, 9, 5, 0, 5],
[3, 5, 7, 9, 8, 6, 1, 0, 7],
[2, 0, 0, 0, 0, 0, 0, 3, 9],
[8, 4, 2, 0, 1, 2, 5, 9, 9],
],
"4": [
[9, 9, 9, 9, 7, 0, 0, 7, 9],
[9, 9, 9, 9, 2, 1, 0, 7, 9],
[9, 9, 9, 5, 2, 4, 0, 7, 9],
[9, 9, 8, 1, 7, 4, 0, 7, 9],
[9, 9, 3, 4, 9, 4, 0, 7, 9],
[9, 6, 1, 9, 9, 4, 0, 7, 9],
[8, 1, 6, 9, 9, 4, 0, 7, 9],
[4, 3, 9, 9, 9, 4, 0, 7, 9],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[9, 9, 9, 9, 9, 4, 0, 7, 9],
[9, 9, 9, 9, 9, 4, 0, 7, 9],
[9, 9, 9, 9, 9, 4, 0, 7, 9],
],
"5": [
[7, 0, 0, 0, 0, 0, 0, 5, 9],
[7, 0, 0, 0, 0, 0, 0, 5, 9],
[7, 0, 5, 9, 9, 9, 9, 9, 9],
[7, 0, 5, 9, 9, 9, 9, 9, 9],
[7, 0, 1, 1, 0, 2, 6, 9, 9],
[7, 0, 0, 0, 0, 0, 0, 5, 9],
[7, 4, 7, 9, 8, 4, 0, 0, 9],
[9, 9, 9, 9, 9, 9, 3, 0, 6],
[9, 9, 9, 9, 9, 9, 5, 0, 6],
[9, 9, 9, 9, 9, 9, 3, 0, 6],
[3, 5, 8, 9, 8, 5, 0, 0, 9],
[2, 0, 0, 0, 0, 0, 0, 6, 9],
[8, 4, 1, 0, 1, 2, 7, 9, 9],
],
"6": [
[9, 9, 7, 2, 1, 0, 3, 8, 9],
[9, 6, 0, 0, 0, 0, 0, 3, 9],
[8, 0, 0, 4, 8, 9, 6, 4, 9],
[5, 0, 4, 9, 9, 9, 9, 9, 9],
[3, 0, 8, 9, 9, 9, 9, 9, 9],
[2, 1, 8, 3, 0, 1, 3, 8, 9],
[2, 2, 2, 0, 0, 0, 0, 1, 8],
[2, 0, 2, 7, 9, 8, 2, 0, 5],
[3, 0, 7, 9, 9, 9, 7, 0, 3],
[4, 0, 7, 9, 9, 9, 7, 0, 3],
[7, 0, 2, 7, 9, 8, 2, 0, 5],
[9, 3, 0, 0, 0, 0, 0, 1, 8],
[9, 9, 4, 1, 0, 1, 3, 8, 9],
],
"7": [
[2, 0, 0, 0, 0, 0, 0, 0, 5],
[2, 0, 0, 0, 0, 0, 0, 0, 7],
[9, 9, 9, 9, 9, 9, 3, 2, 9],
[9, 9, 9, 9, 9, 8, 0, 5, 9],
[9, 9, 9, 9, 9, 4, 0, 8, 9],
[9, 9, 9, 9, 9, 1, 3, 9, 9],
[9, 9, 9, 9, 6, 0, 6, 9, 9],
[9, 9, 9, 9, 2, 1, 9, 9, 9],
[9, 9, 9, 7, 0, 4, 9, 9, 9],
[9, 9, 9, 3, 0, 8, 9, 9, 9],
[9, 9, 8, 0, 2, 9, 9, 9, 9],
[9, 9, 5, 0, 6, 9, 9, 9, 9],
[9, 9, 1, 0, 9, 9, 9, 9, 9],
],
"8": [
[9, 8, 3, 1, 0, 1, 4, 8, 9],
[8, 1, 0, 0, 0, 0, 0, 1, 9],
[5, 0, 3, 8, 9, 7, 2, 0, 6],
[5, 0, 7, 9, 9, 9, 5, 0, 6],
[7, 0, 3, 8, 9, 7, 2, 1, 8],
[9, 7, 2, 0, 0, 0, 2, 7, 9],
[9, 5, 1, 0, 0, 0, 1, 6, 9],
[6, 0, 3, 8, 9, 7, 2, 0, 7],
[2, 0, 9, 9, 9, 9, 7, 0, 4],
[2, 0, 9, 9, 9, 9, 7, 0, 3],
[3, 0, 3, 8, 9, 7, 2, 0, 4],
[7, 0, 0, 0, 0, 0, 0, 1, 8],
[9, 8, 3, 1, 0, 1, 4, 8, 9],
],
"9": [
[9, 7, 3, 0, 0, 2, 6, 9, 9],
[7, 0, 0, 0, 0, 0, 0, 5, 9],
[3, 0, 4, 8, 9, 7, 1, 0, 9],
[1, 0, 9, 9, 9, 9, 5, 0, 6],
[1, 1, 9, 9, 9, 9, 5, 0, 5],
[3, 0, 4, 8, 9, 7, 1, 0, 4],
[7, 0, 0, 0, 0, 0, 3, 0, 4],
[9, 7, 2, 0, 1, 4, 7, 0, 4],
[9, 9, 9, 9, 9, 9, 5, 0, 5],
[9, 9, 9, 9, 9, 9, 2, 0, 7],
[9, 3, 7, 9, 8, 3, 0, 2, 9],
[9, 1, 0, 0, 0, 0, 0, 7, 9],
[9, 7, 2, 0, 1, 3, 8, 9, 9],
],
"-": [
[9, 9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 1, 0, 0, 0, 3, 9, 9],
[9, 9, 1, 0, 0, 0, 3, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9, 9],
],
"m": [
[9, 9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9, 9],
[9, 9, 9, 9, 9, 9, 9, 9, 9],
[0, 5, 2, 1, 7, 6, 1, 2, 8],
[0, 1, 0, 0, 1, 1, 0, 0, 3],
[0, 2, 9, 4, 0, 4, 8, 2, 1],
[0, 5, 9, 6, 0, 7, 9, 4, 1],
[0, 5, 9, 6, 0, 7, 9, 4, 0],
[0, 5, 9, 6, 0, 7, 9, 4, 0],
[0, 5, 9, 6, 0, 7, 9, 4, 0],
[0, 5, 9, 6, 0, 7, 9, 4, 0],
[0, 5, 9, 6, 0, 7, 9, 4, 0],
[0, 5, 9, 6, 0, 7, 9, 4, 0],
],
}
def write_png(buf, width, height):
"""
Writes PNG file to disk. Buffer must be RGBA * width * height
"""
width_byte_4 = width * 4
raw_data = b"".join(
b"\x00" + buf[span : span + width_byte_4]
for span in range(0, height * width * 4, width_byte_4)
)
def png_pack(png_tag, data):
chunk_head = png_tag + data
return (
struct.pack("!I", len(data))
+ chunk_head
+ struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))
)
return b"".join(
[
b"\x89PNG\r\n\x1a\n",
png_pack(b"IHDR", struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)),
png_pack(b"IDAT", zlib.compress(raw_data, 9)),
png_pack(b"IEND", b""),
]
)
class PngBuffer:
def __init__(self, width, height):
self.width = int(width + 3)
self.height = int(height + 3)
self.buf = bytearray(4 * self.width * self.height)
self.line_width = 3
self.fancy = True
self._red = 0
self._green = 0
self._blue = 0
self._alpha = 0
self._distance_from_black = 0
self._gradient_shade_ends = 0.65
self._gradient_shade_edge = 1.1
self._gradient_shade_center = 1.55
self._gradient_color_position1 = 0.40
self._gradient_color_position2 = 0.50
self._gradient_color_position3 = 0.70
def modify_gradient(
self,
gradient_shade_ends=0.65,
gradient_shade_edge=1.1,
gradient_shade_center=1.55,
gradient_color_position1=0.40,
gradient_color_position2=0.50,
gradient_color_position3=0.70,
):
self._gradient_shade_ends = gradient_shade_ends
self._gradient_shade_edge = gradient_shade_edge
self._gradient_shade_center = gradient_shade_center
self._gradient_color_position1 = gradient_color_position1
self._gradient_color_position2 = gradient_color_position2
self._gradient_color_position3 = gradient_color_position3
def set_color(self, r, g, b, a=255):
self._red = r
self._green = g
self._blue = b
self._alpha = a
rmean = int(r / 2)
self._distance_from_black = sqrt(
(((512 + rmean) * r * r) >> 8) + 4 * g * g + (((767 - rmean) * b * b) >> 8)
)
def gradient(self, position_in_line):
"""
This function gives different value scales between 0 and 1 which are used to multiply
all the components of the color to create a darker value. This generally works save for
black which is already (0,0,0) and can't increase value.
"""
if (
position_in_line <= self._gradient_color_position1
): # start_of_grdient -> Position 1
from_shade = self._gradient_shade_ends
to_shade = self._gradient_shade_edge
range = self._gradient_color_position1 - 0 # Range of transition
amount = (position_in_line - 0) * (1 / range)
# light to extra_light
elif (
position_in_line <= self._gradient_color_position2
): # Position 1 -> Position 2.
from_shade = self._gradient_shade_edge
to_shade = self._gradient_shade_center
range = self._gradient_color_position2 - self._gradient_color_position1
amount = (position_in_line - self._gradient_color_position1) * (1 / range)
# center of gradient
# extra_light to light
elif (
position_in_line <= self._gradient_color_position3
): # Position 2 -> Position 3
from_shade = self._gradient_shade_center
to_shade = self._gradient_shade_edge
range = self._gradient_color_position3 - self._gradient_color_position2
amount = (position_in_line - self._gradient_color_position2) * (1 / range)
# light to dark
elif position_in_line <= 1: # Position 3 -> end_of_gradient
from_shade = self._gradient_shade_edge
to_shade = self._gradient_shade_ends
range = 1 - self._gradient_color_position3
amount = (position_in_line - self._gradient_color_position3) * (1 / range)
else:
raise ValueError("Did not occur within line.")
v = amount * (to_shade - from_shade) + from_shade
return max(min(v, 1.0), 0.0)
def background(self, red, green, blue, alpha):
for i in range(0, len(self.buf), 4):
self.buf[i] = red
self.buf[i + 1] = green
self.buf[i + 2] = blue
self.buf[i + 3] = alpha
def plot(self, x, y, v=None, a=None):
"""
Plot a particular point in the canvas.
:param x: x position to plot
:param y: y position to plot
:param v: value scale of particular color (0-1)
:param a: alpha to set for this particular plot if not self.alpha
:return:
"""
a = int(a) if a is not None else self._alpha
if v is None:
v = 1.0
try:
x += 1
y += 1
pos = (self.width * y) + x
idx = pos * 4
background_a = self.buf[idx + 3]
# get rgb and check if close to black and make off dark gray
# this makes black have highlights
if self._distance_from_black < 15:
r = 35
g = 35
b = 35
r = r * v
g = g * v
b = b * v
# end of check and make gray
else:
r = self._red * v
g = self._green * v
b = self._blue * v
if 0 > r:
r = 0
if 0 > g:
g = 0
if 0 > b:
b = 0
if r > 255:
r = 255
if g > 255:
g = 255
if b > 255:
b = 255
if background_a != 0 and a != 255:
s_alpha = a / 255.0
s_background_a = background_a / 255.0
one_minus_salpha = 1 - s_alpha
background_r = self.buf[idx]
background_g = self.buf[idx + 1]
background_b = self.buf[idx + 2]
r = r * s_alpha + one_minus_salpha * background_r
g = g * s_alpha + one_minus_salpha * background_g
b = b * s_alpha + one_minus_salpha * background_b
a = (s_alpha + one_minus_salpha * s_background_a) * 255
self.buf[idx] = int(r)
self.buf[idx + 1] = int(g)
self.buf[idx + 2] = int(b)
self.buf[idx + 3] = (
int(a) - 4
) # remove just a little opacity for background color tint show thru
except IndexError:
pass
def draw_line(self, x0, y0, x1, y1):
dy = y1 - y0 # BRESENHAM LINE DRAW ALGORITHM
dx = x1 - x0
if dy < 0:
dy = -dy
step_y = -1
else:
step_y = 1
odx = abs(dx)
ody = abs(dy)
if dx < 0:
dx = -dx
step_x = -1
else:
step_x = 1
i = 0
if dx > dy:
dy <<= 1 # dy is now 2*dy
dx <<= 1
fraction = dy - (dx >> 1) # same as 2*dy - dx
self.line_for_point(x0, y0, False, odx, i)
i += 1
while x0 != x1:
if fraction >= 0:
y0 += step_y
fraction -= dx # same as fraction -= 2*dx
x0 += step_x
fraction += dy # same as fraction += 2*dy
self.line_for_point(x0, y0, False, odx, i)
i += 1
else:
dy <<= 1 # dy is now 2*dy
dx <<= 1 # dx is now 2*dx
fraction = dx - (dy >> 1)
self.line_for_point(x0, y0, True, ody, i)
i += 1
while y0 != y1:
if fraction >= 0:
x0 += step_x
fraction -= dy
y0 += step_y
fraction += dx
self.line_for_point(x0, y0, True, ody, i)
def line_for_point(self, x, y, dy, max_pos, index):
w = self.line_width
left = w >> 1
right = w - left
if self.fancy:
v = self.gradient(index / max_pos)
else:
v = 1.0
if dy:
for pos in range(-left, right):
self.plot(x + pos, y, v)
else:
for pos in range(-left, right):
self.plot(x, y + pos, v)
def draw_text(self, x, y, string, rotate=False):
for c in string:
m = characters[c]
for cx in range(len(m[0])):
for cy in range(len(m)):
v = m[cy][cx]
if v == 9:
continue
if rotate:
gx = x + (len(m) - cy) + 1
gy = y + cx + 2
else:
gx = x + cx + 2
gy = y + cy + 2
pos = (self.width * gy) + gx
idx = pos * 4
a2 = (9.0 - v) / 9.0
r = (1.0 - a2) * self.buf[idx]
g = (1.0 - a2) * self.buf[idx + 1]
b = (1.0 - a2) * self.buf[idx + 2]
a1 = self.buf[idx + 3] / 255.0
a = a2 + a1 * (1.0 - a2)
try:
self.buf[idx] = int(r)
self.buf[idx + 1] = int(g)
self.buf[idx + 2] = int(b)
self.buf[idx + 3] = int(a * 255)
except IndexError:
pass
if rotate:
y += 11
else:
x += 11
def draw_guides(draw_buff, extends):
width = int(extends[2] - extends[0])
height = int(extends[3] - extends[1])
draw_buff.set_color(0, 0, 0, 255)
draw_buff.line_width = 1
min_x = int(extends[0])
min_y = int(extends[1])
points = 50
draw_buff.draw_text(0, 0, "mm")
for x in range(points - (min_x % points), width - 30, points):
if x < 30:
continue
draw_buff.draw_text(x, 0, str(int((x + min_x) / 10)), rotate=True)
draw_buff.draw_line(x, 0, x, 30)
for y in range(points - (min_y % points), height - 30, points):
if y < 30:
continue
draw_buff.draw_text(0, y, str(int((y + min_y) / 10)))
draw_buff.draw_line(0, y, 30, y)
def write(pattern, f, settings=None):
guides = settings.get("guides", False)
extends = pattern.bounds()
pattern.translate(-extends[0], -extends[1])
width = int(extends[2] - extends[0])
height = int(extends[3] - extends[1])
draw_buff = PngBuffer(width, height)
draw_buff.fancy = settings.get("fancy", False)
if settings is not None:
background = settings.get("background")
if background is not None:
b = EmbThread()
b.set(background)
draw_buff.background(b.get_red(), b.get_green(), b.get_blue(), 0xFF)
linewidth = settings.get("linewidth")
if linewidth is not None and isinstance(linewidth, int):
draw_buff.line_width = linewidth
for stitchblock in pattern.get_as_stitchblock():
block = stitchblock[0]
thread = stitchblock[1]
draw_buff.set_color(
thread.get_red(), thread.get_green(), thread.get_blue(), 255
)
last_x = None
last_y = None
for stitch in block:
x = int(stitch[0])
y = int(stitch[1])
if last_x is not None:
draw_buff.draw_line(last_x, last_y, x, y)
last_x = x
last_y = y
if guides:
draw_guides(draw_buff, extends)
f.write(write_png(draw_buff.buf, draw_buff.width, draw_buff.height))