Last active
February 28, 2021 01:44
-
-
Save jes/285fb480fe91ee9b18016e7e4194c07d to your computer and use it in GitHub Desktop.
Convert a black-and-white PNG into an STL stencil
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
# XXX: This program is super unfinished. It works for my use case, but you will certainly | |
# have to modify the constants in order to use it for your use case. Make sure the input | |
# image is black-and-white. Note that the output triangle mesh is super unoptimised. | |
# | |
# This is very MVP. | |
use strict; | |
use warnings; | |
use GD; | |
my $stencil_width = 100; # mm | |
my $stencil_height = 100; # mm | |
my $thickness = 3; # mm | |
my $img = GD::Image->new("test.png"); | |
# black px is solid, white px is space | |
my ($width, $height) = $img->getBounds; | |
my $width_scale = $stencil_width / $width; | |
my $height_scale = $stencil_height / $height; | |
# draw a 1px white border | |
my $white = $img->colorAllocate(255,255,255); | |
$img->line(0,0, $width-1,0, $white); | |
$img->line($width-1,0, $width-1,$height-1, $white); | |
$img->line($width-1,$height-1, 0,$height-1, $white); | |
$img->line(0,$height-1, 0,0, $white); | |
begin(); | |
for my $y (1 .. $height-1) { | |
for my $x (1 .. $width-1) { | |
# add face at top and bottom if this px is black | |
if (is_black($img, $x, $y)) { | |
add_face([$x,$y,0], [$x+1,$y,0], [$x+1,$y+1,0], [$x,$y+1,0]); | |
add_face([$x,$y,1], [$x+1,$y,1], [$x+1,$y+1,1], [$x,$y+1,1]); | |
} | |
# add face at left if this px is different to the px to its left | |
if (is_different($img, $x-1, $y, $x, $y)) { | |
add_face([$x,$y,0], [$x,$y,1], [$x,$y+1,1], [$x,$y+1,0]); | |
} | |
# add face at top if this px is different to the px to its top | |
if (is_different($img, $x, $y-1, $x, $y)) { | |
add_face([$x,$y,0], [$x,$y,1], [$x+1,$y,1], [$x+1,$y,0]); | |
} | |
} | |
} | |
end(); | |
sub begin { | |
print "solid stencil\n"; | |
} | |
sub end { | |
print "endsolid stencil\n"; | |
} | |
sub transform { | |
my ($p) = @_; | |
return [$p->[0] * $width_scale, $p->[1] * $height_scale, $p->[2] * $thickness]; | |
} | |
sub add_triangle { | |
my ($p1, $p2, $p3) = @_; | |
$p1 = transform($p1); | |
$p2 = transform($p2); | |
$p3 = transform($p3); | |
print "facet normal 0 0 0\n"; | |
print "\touter loop\n"; | |
print "\t\tvertex $p1->[0] $p1->[1] $p1->[2]\n"; | |
print "\t\tvertex $p2->[0] $p2->[1] $p2->[2]\n"; | |
print "\t\tvertex $p3->[0] $p3->[1] $p3->[2]\n"; | |
print "\tendloop\n"; | |
print "endfacet\n"; | |
} | |
sub distance { | |
my ($p1, $p2) = @_; | |
my $dx = abs($p1->[0] - $p2->[0]); | |
my $dy = abs($p1->[1] - $p2->[1]); | |
my $dz = abs($p1->[2] - $p2->[2]); | |
return sqrt($dx*$dx + $dy*$dy + $dz*$dz); | |
} | |
# must be a rectangle! | |
sub add_face { | |
my ($p1, $p2, $p3, $p4) = @_; | |
# add a triangle on the first 3 points | |
add_triangle($p1, $p2, $p3); | |
# now find which point is furthest away from p4, so we | |
# know which point to leave out of the second triangle | |
# (this simplification is only valid for rectangles) | |
my $d1 = distance($p1, $p4); | |
my $d2 = distance($p2, $p4); | |
my $d3 = distance($p3, $p4); | |
# add a triangle using p4 and the 2 nearest points to p4 | |
if ($d1 > $d2 && $d1 > $d3) { | |
add_triangle($p2, $p3, $p4); | |
} elsif ($d2 > $d1 && $d2 > $d3) { | |
add_triangle($p1, $p3, $p4); | |
} else { | |
add_triangle($p1, $p2, $p4); | |
} | |
} | |
sub is_black { | |
my ($img, $x, $y) = @_; | |
my $px = $img->getPixel($x, $y); | |
my ($r,$g,$b) = $img->rgb($px); | |
return $r<128 && $g<128 && $b<128; | |
} | |
sub is_different { | |
my ($img, $x1, $y1, $x2, $y2) = @_; | |
my $px1 = $img->getPixel($x1, $y1); | |
my $px2 = $img->getPixel($x2, $y2); | |
my ($r1,$g1,$b1) = $img->rgb($px1); | |
my ($r2,$g2,$b2) = $img->rgb($px2); | |
return $r1!=$r2 || $g1!=$g2 || $b1!=$b2; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment