This commit is contained in:
Akko
2023-05-15 16:23:02 +02:00
63 changed files with 1067 additions and 948 deletions

View File

@@ -1,5 +0,0 @@
v 100 150
v 42.3 50
v 157.7 50
f 1 2 3

View File

@@ -3,12 +3,14 @@ import torch
import skimage
import numpy as np
# Use GPU if available
pydiffvg.set_use_gpu(torch.cuda.is_available())
def run():
canvas_width, canvas_height = 256, 256
num_control_points = torch.tensor([2, 2, 2])
points = torch.tensor([[20.0, 30.0], # base
# Use GPU if available
pydiffvg.set_use_gpu(torch.cuda.is_available())
canvas_width, canvas_height = 256, 256
num_control_points = torch.tensor([2, 2, 2])
points = torch.tensor([[20.0, 30.0], # base
[50.0, 60.0], # control point
[ 90.0, 198.0], # control point
[ 60.0, 218.0], # base
@@ -17,51 +19,51 @@ points = torch.tensor([[20.0, 30.0], # base
[230.0, 90.0], # base
[220.0, 70.0], # control point
[130.0, 55.0]]) # control point
path = pydiffvg.Path(num_control_points = num_control_points,
path = pydiffvg.Path(num_control_points = num_control_points,
points = points,
is_closed = True)
shapes = [path]
path_group = pydiffvg.ShapeGroup(shape_ids = torch.tensor([0]),
shapes = [path]
path_group = pydiffvg.ShapeGroup(shape_ids = torch.tensor([0]),
fill_color = torch.tensor([0.3, 0.6, 0.3, 1.0]))
shape_groups = [path_group]
scene_args = pydiffvg.RenderFunction.serialize_scene(\
shape_groups = [path_group]
scene_args = pydiffvg.RenderFunction.serialize_scene(\
canvas_width, canvas_height, shapes, shape_groups)
render = pydiffvg.RenderFunction.apply
img = render(256, # width
render = pydiffvg.RenderFunction.apply
img = render(256, # width
256, # height
2, # num_samples_x
2, # num_samples_y
0, # seed
None,
*scene_args)
# The output image is in linear RGB space. Do Gamma correction before saving the image.
pydiffvg.imwrite(img.cpu(), 'results/test_curve/target.png', gamma=2.2)
target = img.clone()
# The output image is in linear RGB space. Do Gamma correction before saving the image.
pydiffvg.imwrite(img.cpu(), 'results/test_curve/target.png', gamma=2.2)
target = img.clone()
# Load the obj file, get the vertices/control points
obj = "imgs/Triangle.obj"
vertices_tmp, faces_tmp = pydiffvg.obj_to_scene(obj)
print(float(vertices_tmp[1][1]))
print(faces_tmp)
# Load the obj file, get the vertices/control points
obj = "imgs/Triangle.obj"
vertices_tmp, faces_tmp = pydiffvg.obj_to_scene(obj)
print(float(vertices_tmp[1][1]))
print(faces_tmp)
vertices = []
faces = []
for v in vertices_tmp:
vertices = []
faces = []
for v in vertices_tmp:
vertices.append([float(v[1]), float(v[2])])
for f in faces_tmp:
for f in faces_tmp:
vs_count = len(f)
tmp = []
for v in f[1:]:
tmp.append(int(v))
faces.append(tmp)
print(vertices)
print(faces)
print(vertices)
print(faces)
# Ternary subdivision
# Move the path to produce initial guess
# normalize points for easier learning rate
points_n = torch.tensor([[vertices[0][0]/256.0, vertices[0][1]/256.0], # base
# Ternary subdivision
# Move the path to produce initial guess
# normalize points for easier learning rate
points_n = torch.tensor([[vertices[0][0]/256.0, vertices[0][1]/256.0], # base
[70.0/256.0, 140.0/256.0], # control point
[50.0/256.0, 100.0/256.0], # control point
[vertices[1][0]/256.0, vertices[1][1]/256.0], # base
@@ -71,24 +73,25 @@ points_n = torch.tensor([[vertices[0][0]/256.0, vertices[0][1]/256.0], # base
[150.0/256.0, 100.0/256.0], # control point
[130.0/256.0, 140.0/256.0]], # control point
requires_grad = True)
color = torch.tensor([0.3, 0.2, 0.5, 1.0], requires_grad=True)
path.points = points_n * 256
path_group.fill_color = color
scene_args = pydiffvg.RenderFunction.serialize_scene(\
color = torch.tensor([0.3, 0.2, 0.5, 1.0], requires_grad=True)
path.points = points_n * 256
path_group.fill_color = color
scene_args = pydiffvg.RenderFunction.serialize_scene(\
canvas_width, canvas_height, shapes, shape_groups)
img = render(256, # width
return scene_args
img = render(256, # width
256, # height
2, # num_samples_x
2, # num_samples_y
1, # seed
None,
*scene_args)
pydiffvg.imwrite(img.cpu(), 'results/test_curve/init.png', gamma=2.2)
pydiffvg.imwrite(img.cpu(), 'results/test_curve/init.png', gamma=2.2)
# Optimize
optimizer = torch.optim.Adam([points_n, color], lr=1e-2)
# Run 100 Adam iterations.
for t in range(100):
# Optimize
optimizer = torch.optim.Adam([points_n, color], lr=1e-2)
# Run 100 Adam iterations.
for t in range(100):
print('iteration:', t)
optimizer.zero_grad()
# Forward pass: render the image.
@@ -104,7 +107,8 @@ for t in range(100):
None,
*scene_args)
# Save the intermediate render.
pydiffvg.imwrite(img.cpu(), 'results/test_curve/iter_{}.png'.format(t), gamma=2.2)
pydiffvg.imwrite(img.cpu(),
'results/test_curve/iter_{}.png'.format(t), gamma=2.2)
# Compute the loss function. Here it is L2.
loss = (img - target).pow(2).sum()
print('loss:', loss.item())
@@ -121,22 +125,24 @@ for t in range(100):
print('points:', path.points)
print('color:', path_group.fill_color)
# Render the final result.
scene_args = pydiffvg.RenderFunction.serialize_scene(\
# Render the final result.
scene_args = pydiffvg.RenderFunction.serialize_scene(\
canvas_width, canvas_height, shapes, shape_groups)
img = render(256, # width
img = render(256, # width
256, # height
2, # num_samples_x
2, # num_samples_y
102, # seed
None,
*scene_args)
# Save the images and differences.
pydiffvg.imwrite(img.cpu(), 'results/test_curve/final.png')
# Save the images and differences.
pydiffvg.imwrite(img.cpu(), 'results/test_curve/final.png')
# Convert the intermediate renderings to a video.
from subprocess import call
call(["ffmpeg", "-framerate", "24", "-i",
# Convert the intermediate renderings to a video.
from subprocess import call
call(["ffmpeg", "-framerate", "24", "-i",
"results/test_curve/iter_%d.png", "-vb", "20M",
"results/test_curve/out.mp4"])
if __name__ == "__main__":
run()

13
color.h
View File

@@ -7,7 +7,8 @@
enum class ColorType {
Constant,
LinearGradient,
RadialGradient
RadialGradient,
PatchGradient
};
struct Constant {
@@ -61,3 +62,13 @@ struct RadialGradient {
float *stop_offsets;
float *stop_colors; // rgba
};
/*
struct PatchGradient {
PatchGradient(const Vector2f &topLeft,
const Vector2f &topRight,
const Vector2f &bottomLeft,
const Vector2f &bottomRight,
const )
}
*/

View File

@@ -1490,6 +1490,11 @@ void render(std::shared_ptr<Scene> scene,
bool use_prefiltering,
ptr<float> eval_positions,
int num_eval_positions) {
/////////////////
// Setup stuff //
/////////////////
#ifdef __NVCC__
int old_device_id = -1;
if (scene->use_gpu) {
@@ -1519,6 +1524,11 @@ void render(std::shared_ptr<Scene> scene,
}
}
///////////////////////////////////////////////////////////////////
// IDK what this does but I think it relates to the prefiltering //
// TODO //
///////////////////////////////////////////////////////////////////
if (render_image.get() != nullptr || d_render_image.get() != nullptr ||
render_sdf.get() != nullptr || d_render_sdf.get() != nullptr) {
if (weight_image != nullptr) {
@@ -1533,6 +1543,11 @@ void render(std::shared_ptr<Scene> scene,
}, width * height * num_samples_x * num_samples_y, scene->use_gpu);
}
////////////////////////////////////////////////
// Think this relates to the actual rendering //
////////////////////////////////////////////////
auto num_samples = eval_positions.get() == nullptr ?
width * height * num_samples_x * num_samples_y : num_eval_positions;
parallel_for(render_kernel{
@@ -1555,6 +1570,11 @@ void render(std::shared_ptr<Scene> scene,
}, num_samples, scene->use_gpu);
}
/////////////////////////////////////////////////////////////////////
// The reason for this is described in the diffvg paper //
// I don't think it's especially important for the gradient meshes //
/////////////////////////////////////////////////////////////////////
// Boundary sampling
if (!use_prefiltering && d_render_image.get() != nullptr) {
auto num_samples = width * height * num_samples_x * num_samples_y;

1104
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -586,13 +586,17 @@ def obj_to_scene(filename):
"""
Load from a obj file and convert to PyTorch tensors.
"""
f = open(filename)
with open(filename, 'r') as f:
lines = f.readlines()
data_lines = []
vertices = []
faces = []
for line in lines:
l=line.split()
# Ignore empty lines
if not l:
continue
#vertex
if(l[0] == "v"):
vertices.append(l)

View File

@@ -7,6 +7,9 @@ import warnings
print_timing = False
def popmult(lst, n):
return (lst[:n], lst[n:])
def set_print_timing(val):
global print_timing
print_timing=val
@@ -35,98 +38,128 @@ class RenderFunction(torch.autograd.Function):
"""
num_shapes = len(shapes)
num_shape_groups = len(shape_groups)
args = []
args.append(canvas_width)
args.append(canvas_height)
args.append(num_shapes)
args.append(num_shape_groups)
args.append(output_type)
args.append(use_prefiltering)
args.append(eval_positions.to(pydiffvg.get_device()))
args = [canvas_width,
canvas_height,
num_shapes,
num_shape_groups,
output_type,
use_prefiltering,
eval_positions.to(pydiffvg.get_device())]
for shape in shapes:
use_thickness = False
if isinstance(shape, pydiffvg.Circle):
assert(shape.center.is_contiguous())
args.append(diffvg.ShapeType.circle)
args.append(shape.radius.cpu())
args.append(shape.center.cpu())
assert shape.center.is_contiguous()
args += [
diffvg.ShapeType.circle,
shape.radius.cpu(),
shape.center.cpu()
]
elif isinstance(shape, pydiffvg.Ellipse):
assert(shape.radius.is_contiguous())
assert(shape.center.is_contiguous())
args.append(diffvg.ShapeType.ellipse)
args.append(shape.radius.cpu())
args.append(shape.center.cpu())
assert shape.radius.is_contiguous()
assert shape.center.is_contiguous()
args += [
diffvg.ShapeType.ellipse,
shape.radius.cpu(),
shape.center.cpu()
]
elif isinstance(shape, pydiffvg.Path):
assert(shape.num_control_points.is_contiguous())
assert(shape.points.is_contiguous())
assert(shape.points.shape[1] == 2)
assert(torch.isfinite(shape.points).all())
args.append(diffvg.ShapeType.path)
args.append(shape.num_control_points.to(torch.int32).cpu())
args.append(shape.points.cpu())
assert shape.num_control_points.is_contiguous()
assert shape.points.is_contiguous()
assert shape.points.shape[1] == 2
assert torch.isfinite(shape.points).all()
args += [
diffvg.ShapeType.path,
shape.num_control_points.to(torch.int32).cpu(),
shape.points.cpu()
]
if len(shape.stroke_width.shape) > 0 and shape.stroke_width.shape[0] > 1:
assert(torch.isfinite(shape.stroke_width).all())
assert torch.isfinite(shape.stroke_width).all()
use_thickness = True
args.append(shape.stroke_width.cpu())
else:
args.append(None)
args.append(shape.is_closed)
args.append(shape.use_distance_approx)
args += [shape.is_closed, shape.use_distance_approx]
elif isinstance(shape, pydiffvg.Polygon):
assert(shape.points.is_contiguous())
assert(shape.points.shape[1] == 2)
assert shape.points.is_contiguous()
assert shape.points.shape[1] == 2
args.append(diffvg.ShapeType.path)
if shape.is_closed:
args.append(torch.zeros(shape.points.shape[0], dtype = torch.int32))
else:
args.append(torch.zeros(shape.points.shape[0] - 1, dtype = torch.int32))
args.append(shape.points.cpu())
args.append(None)
args.append(shape.is_closed)
args.append(False) # use_distance_approx
args += [
shape.points.cpu(),
None,
shape.is_closed(),
False # use_distance_approx
]
elif isinstance(shape, pydiffvg.Rect):
assert(shape.p_min.is_contiguous())
assert(shape.p_max.is_contiguous())
args.append(diffvg.ShapeType.rect)
args.append(shape.p_min.cpu())
args.append(shape.p_max.cpu())
assert shape.p_min.is_contiguous()
assert shape.p_max.is_contiguous()
args += [
diffvg.ShapeType.rect,
shape.p_min.cpu(),
shape.p_max.cpu()
]
else:
assert(False)
assert False
if use_thickness:
args.append(torch.tensor(0.0))
else:
args.append(shape.stroke_width.cpu())
for shape_group in shape_groups:
assert(shape_group.shape_ids.is_contiguous())
assert shape_group.shape_ids.is_contiguous()
args.append(shape_group.shape_ids.to(torch.int32).cpu())
# Fill color
if shape_group.fill_color is None:
args.append(None)
elif isinstance(shape_group.fill_color, torch.Tensor):
assert(shape_group.fill_color.is_contiguous())
args.append(diffvg.ColorType.constant)
args.append(shape_group.fill_color.cpu())
assert shape_group.fill_color.is_contiguous()
args += [
diffvg.ColorType.constant,
shape_group.fill_color.cpu()
]
elif isinstance(shape_group.fill_color, pydiffvg.LinearGradient):
assert(shape_group.fill_color.begin.is_contiguous())
assert(shape_group.fill_color.end.is_contiguous())
assert(shape_group.fill_color.offsets.is_contiguous())
assert(shape_group.fill_color.stop_colors.is_contiguous())
args.append(diffvg.ColorType.linear_gradient)
args.append(shape_group.fill_color.begin.cpu())
args.append(shape_group.fill_color.end.cpu())
args.append(shape_group.fill_color.offsets.cpu())
args.append(shape_group.fill_color.stop_colors.cpu())
assert shape_group.fill_color.begin.is_contiguous()
assert shape_group.fill_color.end.is_contiguous()
assert shape_group.fill_color.offsets.is_contiguous()
assert shape_group.fill_color.stop_colors.is_contiguous()
args += [
diffvg.ColorType.linear_gradient,
shape_group.fill_color.begin.cpu(),
shape_group.fill_color.end.cpu(),
shape_group.fill_color.offsets.cpu(),
shape_group.fill_color.stop_colors.cpu(),
]
elif isinstance(shape_group.fill_color, pydiffvg.RadialGradient):
assert(shape_group.fill_color.center.is_contiguous())
assert(shape_group.fill_color.radius.is_contiguous())
assert(shape_group.fill_color.offsets.is_contiguous())
assert(shape_group.fill_color.stop_colors.is_contiguous())
args.append(diffvg.ColorType.radial_gradient)
args.append(shape_group.fill_color.center.cpu())
args.append(shape_group.fill_color.radius.cpu())
args.append(shape_group.fill_color.offsets.cpu())
args.append(shape_group.fill_color.stop_colors.cpu())
assert shape_group.fill_color.center.is_contiguous()
assert shape_group.fill_color.radius.is_contiguous()
assert shape_group.fill_color.offsets.is_contiguous()
assert shape_group.fill_color.stop_colors.is_contiguous()
args += [
diffvg.ColorType.radial_gradient,
shape_group.fill_color.center.cpu(),
shape_group.fill_color.radius.cpu(),
shape_group.fill_color.offsets.cpu(),
shape_group.fill_color.stop_colors.cpu()
]
if shape_group.fill_color is not None:
# go through the underlying shapes and check if they are all closed
@@ -138,35 +171,44 @@ class RenderFunction(torch.autograd.Function):
# Stroke color
if shape_group.stroke_color is None:
args.append(None)
elif isinstance(shape_group.stroke_color, torch.Tensor):
assert(shape_group.stroke_color.is_contiguous())
args.append(diffvg.ColorType.constant)
args.append(shape_group.stroke_color.cpu())
assert shape_group.stroke_color.is_contiguous()
args += [ diffvg.ColorType.constant, shape_group.stroke_color.cpu() ]
elif isinstance(shape_group.stroke_color, pydiffvg.LinearGradient):
assert(shape_group.stroke_color.begin.is_contiguous())
assert(shape_group.stroke_color.end.is_contiguous())
assert(shape_group.stroke_color.offsets.is_contiguous())
assert(shape_group.stroke_color.stop_colors.is_contiguous())
assert(torch.isfinite(shape_group.stroke_color.stop_colors).all())
args.append(diffvg.ColorType.linear_gradient)
args.append(shape_group.stroke_color.begin.cpu())
args.append(shape_group.stroke_color.end.cpu())
args.append(shape_group.stroke_color.offsets.cpu())
args.append(shape_group.stroke_color.stop_colors.cpu())
assert shape_group.stroke_color.begin.is_contiguous()
assert shape_group.stroke_color.end.is_contiguous()
assert shape_group.stroke_color.offsets.is_contiguous()
assert shape_group.stroke_color.stop_colors.is_contiguous()
assert torch.isfinite(shape_group.stroke_color.stop_colors).all()
args += [
diffvg.ColorType.linear_gradient,
shape_group.stroke_color.begin.cpu(),
shape_group.stroke_color.end.cpu(),
shape_group.stroke_color.offsets.cpu(),
shape_group.stroke_color.stop_colors.cpu()
]
elif isinstance(shape_group.stroke_color, pydiffvg.RadialGradient):
assert(shape_group.stroke_color.center.is_contiguous())
assert(shape_group.stroke_color.radius.is_contiguous())
assert(shape_group.stroke_color.offsets.is_contiguous())
assert(shape_group.stroke_color.stop_colors.is_contiguous())
assert(torch.isfinite(shape_group.stroke_color.stop_colors).all())
args.append(diffvg.ColorType.radial_gradient)
args.append(shape_group.stroke_color.center.cpu())
args.append(shape_group.stroke_color.radius.cpu())
args.append(shape_group.stroke_color.offsets.cpu())
args.append(shape_group.stroke_color.stop_colors.cpu())
args.append(shape_group.use_even_odd_rule)
# Transformation
args.append(shape_group.shape_to_canvas.contiguous().cpu())
assert shape_group.stroke_color.center.is_contiguous()
assert shape_group.stroke_color.radius.is_contiguous()
assert shape_group.stroke_color.offsets.is_contiguous()
assert shape_group.stroke_color.stop_colors.is_contiguous()
assert torch.isfinite(shape_group.stroke_color.stop_colors).all()
args += [
diffvg.ColorType.radial_gradient,
shape_group.stroke_color.center.cpu(),
shape_group.stroke_color.radius.cpu(),
shape_group.stroke_color.offsets.cpu(),
shape_group.stroke_color.stop_colors.cpu()
]
args += [ shape_group.use_even_odd_rule,
shape_group.shape_to_canvas.contiguous().cpu() ]
args.append(filter.type)
args.append(filter.radius.cpu())
return args
@@ -184,52 +226,31 @@ class RenderFunction(torch.autograd.Function):
Forward rendering pass.
"""
# Unpack arguments
current_index = 0
canvas_width = args[current_index]
current_index += 1
canvas_height = args[current_index]
current_index += 1
num_shapes = args[current_index]
current_index += 1
num_shape_groups = args[current_index]
current_index += 1
output_type = args[current_index]
current_index += 1
use_prefiltering = args[current_index]
current_index += 1
eval_positions = args[current_index]
current_index += 1
args = list(args)
(canvas_width, canvas_height, num_shapes,
num_shape_groups, output_type, use_prefiltering, eval_positions), args = popmult(args, 7)
shapes = []
shape_groups = []
shape_contents = [] # Important to avoid GC deleting the shapes
color_contents = [] # Same as above
for shape_id in range(num_shapes):
shape_type = args[current_index]
current_index += 1
shape_type = args.pop(0)
if shape_type == diffvg.ShapeType.circle:
radius = args[current_index]
current_index += 1
center = args[current_index]
current_index += 1
(radius, center), args = popmult(args, 2)
shape = diffvg.Circle(radius, diffvg.Vector2f(center[0], center[1]))
elif shape_type == diffvg.ShapeType.ellipse:
radius = args[current_index]
current_index += 1
center = args[current_index]
current_index += 1
(radius, center), args = popmult(args, 2)
shape = diffvg.Ellipse(diffvg.Vector2f(radius[0], radius[1]),
diffvg.Vector2f(center[0], center[1]))
elif shape_type == diffvg.ShapeType.path:
num_control_points = args[current_index]
current_index += 1
points = args[current_index]
current_index += 1
thickness = args[current_index]
current_index += 1
is_closed = args[current_index]
current_index += 1
use_distance_approx = args[current_index]
current_index += 1
(num_control_points, points, thickness,
is_closed, use_distance_approx), args = popmult(args, 5)
shape = diffvg.Path(diffvg.int_ptr(num_control_points.data_ptr()),
diffvg.float_ptr(points.data_ptr()),
diffvg.float_ptr(thickness.data_ptr() if thickness is not None else 0),
@@ -238,55 +259,36 @@ class RenderFunction(torch.autograd.Function):
is_closed,
use_distance_approx)
elif shape_type == diffvg.ShapeType.rect:
p_min = args[current_index]
current_index += 1
p_max = args[current_index]
current_index += 1
(p_min, p_max), args = popmult(args, 2)
shape = diffvg.Rect(diffvg.Vector2f(p_min[0], p_min[1]),
diffvg.Vector2f(p_max[0], p_max[1]))
else:
assert(False)
stroke_width = args[current_index]
current_index += 1
assert False
stroke_width = args.pop(0)
shapes.append(diffvg.Shape(\
shape_type, shape.get_ptr(), stroke_width.item()))
shape_contents.append(shape)
for shape_group_id in range(num_shape_groups):
shape_ids = args[current_index]
current_index += 1
fill_color_type = args[current_index]
current_index += 1
(shape_ids, fill_color_type), args = popmult(args, 2)
if fill_color_type == diffvg.ColorType.constant:
color = args[current_index]
current_index += 1
color = args.pop(0)
fill_color = diffvg.Constant(\
diffvg.Vector4f(color[0], color[1], color[2], color[3]))
elif fill_color_type == diffvg.ColorType.linear_gradient:
beg = args[current_index]
current_index += 1
end = args[current_index]
current_index += 1
offsets = args[current_index]
current_index += 1
stop_colors = args[current_index]
current_index += 1
assert(offsets.shape[0] == stop_colors.shape[0])
(beg, end, offsets, stop_colors), args = popmult(args, 4)
assert offsets.shape[0] == stop_colors.shape[0]
fill_color = diffvg.LinearGradient(diffvg.Vector2f(beg[0], beg[1]),
diffvg.Vector2f(end[0], end[1]),
offsets.shape[0],
diffvg.float_ptr(offsets.data_ptr()),
diffvg.float_ptr(stop_colors.data_ptr()))
elif fill_color_type == diffvg.ColorType.radial_gradient:
center = args[current_index]
current_index += 1
radius = args[current_index]
current_index += 1
offsets = args[current_index]
current_index += 1
stop_colors = args[current_index]
current_index += 1
assert(offsets.shape[0] == stop_colors.shape[0])
(center, radius, offsets, stop_colors), args = popmult(args, 4)
assert offsets.shape[0] == stop_colors.shape[0]
fill_color = diffvg.RadialGradient(diffvg.Vector2f(center[0], center[1]),
diffvg.Vector2f(radius[0], radius[1]),
offsets.shape[0],
@@ -295,39 +297,28 @@ class RenderFunction(torch.autograd.Function):
elif fill_color_type is None:
fill_color = None
else:
assert(False)
stroke_color_type = args[current_index]
current_index += 1
assert False
stroke_color_type = args.pop(0)
if stroke_color_type == diffvg.ColorType.constant:
color = args[current_index]
current_index += 1
color = args.pop(0)
stroke_color = diffvg.Constant(\
diffvg.Vector4f(color[0], color[1], color[2], color[3]))
elif stroke_color_type == diffvg.ColorType.linear_gradient:
beg = args[current_index]
current_index += 1
end = args[current_index]
current_index += 1
offsets = args[current_index]
current_index += 1
stop_colors = args[current_index]
current_index += 1
assert(offsets.shape[0] == stop_colors.shape[0])
(beg, end, offsets, stop_colors), args = popmult(args, 4)
assert offsets.shape[0] == stop_colors.shape[0]
stroke_color = diffvg.LinearGradient(diffvg.Vector2f(beg[0], beg[1]),
diffvg.Vector2f(end[0], end[1]),
offsets.shape[0],
diffvg.float_ptr(offsets.data_ptr()),
diffvg.float_ptr(stop_colors.data_ptr()))
elif stroke_color_type == diffvg.ColorType.radial_gradient:
center = args[current_index]
current_index += 1
radius = args[current_index]
current_index += 1
offsets = args[current_index]
current_index += 1
stop_colors = args[current_index]
current_index += 1
assert(offsets.shape[0] == stop_colors.shape[0])
(center, radius, offsets, stop_colors), args = popmult(args, 4)
assert offsets.shape[0] == stop_colors.shape[0]
stroke_color = diffvg.RadialGradient(diffvg.Vector2f(center[0], center[1]),
diffvg.Vector2f(radius[0], radius[1]),
offsets.shape[0],
@@ -335,17 +326,18 @@ class RenderFunction(torch.autograd.Function):
diffvg.float_ptr(stop_colors.data_ptr()))
elif stroke_color_type is None:
stroke_color = None
else:
assert(False)
use_even_odd_rule = args[current_index]
current_index += 1
shape_to_canvas = args[current_index]
current_index += 1
assert False
(use_even_odd_rule, shape_to_canvas), args = popmult(args, 2)
if fill_color is not None:
color_contents.append(fill_color)
if stroke_color is not None:
color_contents.append(stroke_color)
shape_groups.append(diffvg.ShapeGroup(\
diffvg.int_ptr(shape_ids.data_ptr()),
shape_ids.shape[0],
@@ -356,10 +348,7 @@ class RenderFunction(torch.autograd.Function):
use_even_odd_rule,
diffvg.float_ptr(shape_to_canvas.data_ptr())))
filter_type = args[current_index]
current_index += 1
filter_radius = args[current_index]
current_index += 1
(filter_type, filter_radius), args = popmult(args, 2)
filt = diffvg.Filter(filter_type, filter_radius)
start = time.time()
@@ -367,15 +356,16 @@ class RenderFunction(torch.autograd.Function):
shapes, shape_groups, filt, pydiffvg.get_use_gpu(),
pydiffvg.get_device().index if pydiffvg.get_device().index is not None else -1)
time_elapsed = time.time() - start
global print_timing
if print_timing:
print('Scene construction, time: %.5f s' % time_elapsed)
if output_type == OutputType.color:
assert(eval_positions.shape[0] == 0)
assert eval_positions.shape[0] == 0
rendered_image = torch.zeros(height, width, 4, device = pydiffvg.get_device())
else:
assert(output_type == OutputType.sdf)
assert output_type == OutputType.sdf
if eval_positions.shape[0] == 0:
rendered_image = torch.zeros(height, width, 1, device = pydiffvg.get_device())
else:
@@ -386,9 +376,10 @@ class RenderFunction(torch.autograd.Function):
if background_image.shape[2] == 3:
raise NotImplementedError('Background image must have 4 channels, not 3. Add a fourth channel with all ones via torch.ones().')
background_image = background_image.contiguous()
assert(background_image.shape[0] == rendered_image.shape[0])
assert(background_image.shape[1] == rendered_image.shape[1])
assert(background_image.shape[2] == 4)
assert background_image.shape[0] == rendered_image.shape[0]
assert background_image.shape[1] == rendered_image.shape[1]
assert background_image.shape[2] == 4
start = time.time()
diffvg.render(scene,
@@ -407,7 +398,7 @@ class RenderFunction(torch.autograd.Function):
use_prefiltering,
diffvg.float_ptr(eval_positions.data_ptr()),
eval_positions.shape[0])
assert(torch.isfinite(rendered_image).all())
assert torch.isfinite(rendered_image).all()
time_elapsed = time.time() - start
if print_timing:
print('Forward pass, time: %.5f s' % time_elapsed)
@@ -438,55 +429,33 @@ class RenderFunction(torch.autograd.Function):
*args):
if not grad_img.is_contiguous():
grad_img = grad_img.contiguous()
assert(torch.isfinite(grad_img).all())
assert torch.isfinite(grad_img).all()
args = list(args)
# Unpack arguments
current_index = 0
canvas_width = args[current_index]
current_index += 1
canvas_height = args[current_index]
current_index += 1
num_shapes = args[current_index]
current_index += 1
num_shape_groups = args[current_index]
current_index += 1
output_type = args[current_index]
current_index += 1
use_prefiltering = args[current_index]
current_index += 1
eval_positions = args[current_index]
current_index += 1
(canvas_width, canvas_height, num_shapes,
num_shape_groups, output_type, use_prefiltering,
eval_positions), args = popmult(args, 7)
shapes = []
shape_groups = []
shape_contents = [] # Important to avoid GC deleting the shapes
color_contents = [] # Same as above
for shape_id in range(num_shapes):
shape_type = args[current_index]
current_index += 1
shape_type = args.pop(0)
if shape_type == diffvg.ShapeType.circle:
radius = args[current_index]
current_index += 1
center = args[current_index]
current_index += 1
(radius, center), args = popmult(args, 2)
shape = diffvg.Circle(radius, diffvg.Vector2f(center[0], center[1]))
elif shape_type == diffvg.ShapeType.ellipse:
radius = args[current_index]
current_index += 1
center = args[current_index]
current_index += 1
(radius, center), args = popmult(args, 2)
shape = diffvg.Ellipse(diffvg.Vector2f(radius[0], radius[1]),
diffvg.Vector2f(center[0], center[1]))
elif shape_type == diffvg.ShapeType.path:
num_control_points = args[current_index]
current_index += 1
points = args[current_index]
current_index += 1
thickness = args[current_index]
current_index += 1
is_closed = args[current_index]
current_index += 1
use_distance_approx = args[current_index]
current_index += 1
(num_control_points, points, thickness,
is_closed, use_distance_approx), args = popmult(args, 5)
shape = diffvg.Path(diffvg.int_ptr(num_control_points.data_ptr()),
diffvg.float_ptr(points.data_ptr()),
diffvg.float_ptr(thickness.data_ptr() if thickness is not None else 0),
@@ -495,114 +464,93 @@ class RenderFunction(torch.autograd.Function):
is_closed,
use_distance_approx)
elif shape_type == diffvg.ShapeType.rect:
p_min = args[current_index]
current_index += 1
p_max = args[current_index]
current_index += 1
(p_min, p_max), args = popmult(args, 2)
shape = diffvg.Rect(diffvg.Vector2f(p_min[0], p_min[1]),
diffvg.Vector2f(p_max[0], p_max[1]))
else:
assert(False)
stroke_width = args[current_index]
current_index += 1
assert False
stroke_width = args.pop(0)
shapes.append(diffvg.Shape(\
shape_type, shape.get_ptr(), stroke_width.item()))
shape_contents.append(shape)
for shape_group_id in range(num_shape_groups):
shape_ids = args[current_index]
current_index += 1
fill_color_type = args[current_index]
current_index += 1
(shape_ids, fill_color_type), args = popmult(args, 2)
if fill_color_type == diffvg.ColorType.constant:
color = args[current_index]
current_index += 1
color = args.pop(0)
fill_color = diffvg.Constant(\
diffvg.Vector4f(color[0], color[1], color[2], color[3]))
elif fill_color_type == diffvg.ColorType.linear_gradient:
beg = args[current_index]
current_index += 1
end = args[current_index]
current_index += 1
offsets = args[current_index]
current_index += 1
stop_colors = args[current_index]
current_index += 1
assert(offsets.shape[0] == stop_colors.shape[0])
(beg, end, offsets, stop_colors), args = popmult(args, 4)
assert offsets.shape[0] == stop_colors.shape[0]
fill_color = diffvg.LinearGradient(diffvg.Vector2f(beg[0], beg[1]),
diffvg.Vector2f(end[0], end[1]),
offsets.shape[0],
diffvg.float_ptr(offsets.data_ptr()),
diffvg.float_ptr(stop_colors.data_ptr()))
elif fill_color_type == diffvg.ColorType.radial_gradient:
center = args[current_index]
current_index += 1
radius = args[current_index]
current_index += 1
offsets = args[current_index]
current_index += 1
stop_colors = args[current_index]
current_index += 1
assert(offsets.shape[0] == stop_colors.shape[0])
(center, radius, offsets, stop_colors), args = popmult(args, 4)
assert offsets.shape[0] == stop_colors.shape[0]
fill_color = diffvg.RadialGradient(diffvg.Vector2f(center[0], center[1]),
diffvg.Vector2f(radius[0], radius[1]),
offsets.shape[0],
diffvg.float_ptr(offsets.data_ptr()),
diffvg.float_ptr(stop_colors.data_ptr()))
elif fill_color_type is None:
fill_color = None
else:
assert(False)
stroke_color_type = args[current_index]
current_index += 1
assert False
stroke_color_type = args.pop(0)
if stroke_color_type == diffvg.ColorType.constant:
color = args[current_index]
current_index += 1
color = args.pop(0)
stroke_color = diffvg.Constant(\
diffvg.Vector4f(color[0], color[1], color[2], color[3]))
elif stroke_color_type == diffvg.ColorType.linear_gradient:
beg = args[current_index]
current_index += 1
end = args[current_index]
current_index += 1
offsets = args[current_index]
current_index += 1
stop_colors = args[current_index]
current_index += 1
assert(offsets.shape[0] == stop_colors.shape[0])
(beg, end, offsets, stop_colors) = popmult(args, 4)
assert offsets.shape[0] == stop_colors.shape[0]
stroke_color = diffvg.LinearGradient(diffvg.Vector2f(beg[0], beg[1]),
diffvg.Vector2f(end[0], end[1]),
offsets.shape[0],
diffvg.float_ptr(offsets.data_ptr()),
diffvg.float_ptr(stop_colors.data_ptr()))
elif stroke_color_type == diffvg.ColorType.radial_gradient:
center = args[current_index]
current_index += 1
radius = args[current_index]
current_index += 1
offsets = args[current_index]
current_index += 1
stop_colors = args[current_index]
current_index += 1
assert(offsets.shape[0] == stop_colors.shape[0])
(center, radius, offsets, stop_colors), args = popmult(args, 4)
assert offsets.shape[0] == stop_colors.shape[0]
stroke_color = diffvg.RadialGradient(diffvg.Vector2f(center[0], center[1]),
diffvg.Vector2f(radius[0], radius[1]),
offsets.shape[0],
diffvg.float_ptr(offsets.data_ptr()),
diffvg.float_ptr(stop_colors.data_ptr()))
elif stroke_color_type is None:
stroke_color = None
else:
assert(False)
use_even_odd_rule = args[current_index]
current_index += 1
shape_to_canvas = args[current_index]
current_index += 1
assert False
(use_even_odd_rule, shape_to_canvas), args = popmult(args, 2)
if fill_color is not None:
color_contents.append(fill_color)
if stroke_color is not None:
color_contents.append(stroke_color)
shape_groups.append(diffvg.ShapeGroup(\
diffvg.int_ptr(shape_ids.data_ptr()),
shape_ids.shape[0],
@@ -613,10 +561,8 @@ class RenderFunction(torch.autograd.Function):
use_even_odd_rule,
diffvg.float_ptr(shape_to_canvas.data_ptr())))
filter_type = args[current_index]
current_index += 1
filter_radius = args[current_index]
current_index += 1
(filter_type, filter_radius), args = popmult(args, 2)
filt = diffvg.Filter(filter_type, filter_radius)
scene = diffvg.Scene(canvas_width, canvas_height,
@@ -624,20 +570,23 @@ class RenderFunction(torch.autograd.Function):
pydiffvg.get_device().index if pydiffvg.get_device().index is not None else -1)
if output_type == OutputType.color:
assert(grad_img.shape[2] == 4)
assert grad_img.shape[2] == 4
else:
assert(grad_img.shape[2] == 1)
assert grad_img.shape[2] == 1
if background_image is not None:
background_image = background_image.to(pydiffvg.get_device())
if background_image.shape[2] == 3:
background_image = torch.cat((\
background_image, torch.ones(background_image.shape[0], background_image.shape[1], 1,
device = background_image.device)), dim = 2)
background_image = torch.cat((
background_image, torch.ones(background_image.shape[0],
background_image.shape[1],
1,
device=background_image.device)),
dim=2)
background_image = background_image.contiguous()
assert(background_image.shape[0] == rendered_image.shape[0])
assert(background_image.shape[1] == rendered_image.shape[1])
assert(background_image.shape[2] == 4)
assert background_image.shape[0] == rendered_image.shape[0]
assert background_image.shape[1] == rendered_image.shape[1]
assert background_image.shape[2] == 4
translation_grad_image = \
torch.zeros(height, width, 2, device = pydiffvg.get_device())
@@ -661,7 +610,7 @@ class RenderFunction(torch.autograd.Function):
time_elapsed = time.time() - start
if print_timing:
print('Gradient pass, time: %.5f s' % time_elapsed)
assert(torch.isfinite(translation_grad_image).all())
assert torch.isfinite(translation_grad_image).all()
return translation_grad_image
@@ -670,7 +619,7 @@ class RenderFunction(torch.autograd.Function):
grad_img):
if not grad_img.is_contiguous():
grad_img = grad_img.contiguous()
assert(torch.isfinite(grad_img).all())
assert torch.isfinite(grad_img).all()
scene = ctx.scene
width = ctx.width
@@ -710,20 +659,11 @@ class RenderFunction(torch.autograd.Function):
if print_timing:
print('Backward pass, time: %.5f s' % time_elapsed)
d_args = []
d_args.append(None) # width
d_args.append(None) # height
d_args.append(None) # num_samples_x
d_args.append(None) # num_samples_y
d_args.append(None) # seed
d_args.append(d_background_image)
d_args.append(None) # canvas_width
d_args.append(None) # canvas_height
d_args.append(None) # num_shapes
d_args.append(None) # num_shape_groups
d_args.append(None) # output_type
d_args.append(None) # use_prefiltering
d_args.append(None) # eval_positions
# [width, height, num_samples_x, num_samples_y, seed,
# d_background_image, canvas_width, canvas_height, num_shapes,
# num_shape_groups, output_type, use_prefiltering, _eval_positions]
d_args = [None] * 5 + [d_background_image] + [None] * 7
for shape_id in range(scene.num_shapes):
d_args.append(None) # type
d_shape = scene.get_d_shape(shape_id)
@@ -731,21 +671,21 @@ class RenderFunction(torch.autograd.Function):
if d_shape.type == diffvg.ShapeType.circle:
d_circle = d_shape.as_circle()
radius = torch.tensor(d_circle.radius)
assert(torch.isfinite(radius).all())
assert torch.isfinite(radius).all()
d_args.append(radius)
c = d_circle.center
c = torch.tensor((c.x, c.y))
assert(torch.isfinite(c).all())
assert torch.isfinite(c).all()
d_args.append(c)
elif d_shape.type == diffvg.ShapeType.ellipse:
d_ellipse = d_shape.as_ellipse()
r = d_ellipse.radius
r = torch.tensor((d_ellipse.radius.x, d_ellipse.radius.y))
assert(torch.isfinite(r).all())
assert torch.isfinite(r).all()
d_args.append(r)
c = d_ellipse.center
c = torch.tensor((c.x, c.y))
assert(torch.isfinite(c).all())
assert torch.isfinite(c).all()
d_args.append(c)
elif d_shape.type == diffvg.ShapeType.path:
d_path = d_shape.as_path()
@@ -757,9 +697,9 @@ class RenderFunction(torch.autograd.Function):
d_path.copy_to(diffvg.float_ptr(points.data_ptr()), diffvg.float_ptr(thickness.data_ptr()))
else:
d_path.copy_to(diffvg.float_ptr(points.data_ptr()), diffvg.float_ptr(0))
assert(torch.isfinite(points).all())
assert torch.isfinite(points).all()
if thickness is not None:
assert(torch.isfinite(thickness).all())
assert torch.isfinite(thickness).all()
d_args.append(None) # num_control_points
d_args.append(points)
d_args.append(thickness)
@@ -769,17 +709,17 @@ class RenderFunction(torch.autograd.Function):
d_rect = d_shape.as_rect()
p_min = torch.tensor((d_rect.p_min.x, d_rect.p_min.y))
p_max = torch.tensor((d_rect.p_max.x, d_rect.p_max.y))
assert(torch.isfinite(p_min).all())
assert(torch.isfinite(p_max).all())
assert torch.isfinite(p_min).all()
assert torch.isfinite(p_max).all()
d_args.append(p_min)
d_args.append(p_max)
else:
assert(False)
assert False
if use_thickness:
d_args.append(None)
else:
w = torch.tensor((d_shape.stroke_width))
assert(torch.isfinite(w).all())
assert torch.isfinite(w).all()
d_args.append(w)
for group_id in range(scene.num_shape_groups):
@@ -802,7 +742,7 @@ class RenderFunction(torch.autograd.Function):
d_linear_gradient.copy_to(\
diffvg.float_ptr(offsets.data_ptr()),
diffvg.float_ptr(stop_colors.data_ptr()))
assert(torch.isfinite(stop_colors).all())
assert torch.isfinite(stop_colors).all()
d_args.append(offsets)
d_args.append(stop_colors)
elif d_shape_group.fill_color_type == diffvg.ColorType.radial_gradient:
@@ -816,11 +756,11 @@ class RenderFunction(torch.autograd.Function):
d_radial_gradient.copy_to(\
diffvg.float_ptr(offsets.data_ptr()),
diffvg.float_ptr(stop_colors.data_ptr()))
assert(torch.isfinite(stop_colors).all())
assert torch.isfinite(stop_colors).all()
d_args.append(offsets)
d_args.append(stop_colors)
else:
assert(False)
assert False
d_args.append(None) # stroke_color_type
if d_shape_group.has_stroke_color():
if d_shape_group.stroke_color_type == diffvg.ColorType.constant:
@@ -838,7 +778,7 @@ class RenderFunction(torch.autograd.Function):
d_linear_gradient.copy_to(\
diffvg.float_ptr(offsets.data_ptr()),
diffvg.float_ptr(stop_colors.data_ptr()))
assert(torch.isfinite(stop_colors).all())
assert torch.isfinite(stop_colors).all()
d_args.append(offsets)
d_args.append(stop_colors)
elif d_shape_group.fill_color_type == diffvg.ColorType.radial_gradient:
@@ -852,15 +792,15 @@ class RenderFunction(torch.autograd.Function):
d_radial_gradient.copy_to(\
diffvg.float_ptr(offsets.data_ptr()),
diffvg.float_ptr(stop_colors.data_ptr()))
assert(torch.isfinite(stop_colors).all())
assert torch.isfinite(stop_colors).all()
d_args.append(offsets)
d_args.append(stop_colors)
else:
assert(False)
assert False
d_args.append(None) # use_even_odd_rule
d_shape_to_canvas = torch.zeros((3, 3))
d_shape_group.copy_to(diffvg.float_ptr(d_shape_to_canvas.data_ptr()))
assert(torch.isfinite(d_shape_to_canvas).all())
assert torch.isfinite(d_shape_to_canvas).all()
d_args.append(d_shape_to_canvas)
d_args.append(None) # filter_type
d_args.append(torch.tensor(scene.get_d_filter_radius()))

View File

@@ -5,8 +5,11 @@ description = ""
authors = ["Marco Lee <marco@goodnotesapp.com>"]
[tool.poetry.dependencies]
python = ">=3.8,<3.11"
python = ">=3.9,<3.11"
pygame = "^2.0.1"
torch = "^2.0.1"
ipython = "^8.13.2"
pytest-metadata = "^2.0.4"
[tool.poetry.dev-dependencies]
torch = "^2.0.0"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB