#!/usr/local/bin/perl

# hmap.pl
# 3/28/03 By SigArch @ UIUC for its CNC Router project
# http://www.acm.uiuc.edu/sigarch/

use constant WIDTH  => 5.0;	# x
use constant HEIGHT => 2.75;	# y
use constant DEPTH  => -0.5;	# z axis max (negative)

use constant BITLENGTH => 0.5; #0.0625*8;

use constant COLORS => 256;
use constant CSTEP => - COLORS/(DEPTH/BITLENGTH);
use constant ZSTEP => - DEPTH/COLORS;

use constant XSTEP => 0.0625/2;
use constant YSTEP => 0.0625/2;

use GD;

sub min
{
    ($a, $b) = @_;
    return ($a < $b) ? $a : $b;
}

sub clamp
{
    ($val, $min, $max) = @_;
    return ($val > $max) ? $max : (($val < $min) ? $min : $val);
}

sub avg
{
    ($u0, $v0, $u1, $v1) = @_;
    $n = $s = 0;
    $u0 = clamp($u0, 0, $w-1); $u1 = clamp($u1, 0, $w-1);
    $v0 = clamp($v0, 0, $h-1); $v1 = clamp($v1, 0, $h-1);
    for ($cu = $u0; $cu <= $u1; ++$cu) {
	for ($cv = $v0; $cv <= $v1; ++$cv) {
	    ($r, $Z, $Z) = $img->rgb($img->getPixel($cu, $cv));
	    #print "$r $cu $cv\n";
	    $s += $r; $n+=1.0;
	}
    }
    #print "region: $u0 $v0 $u1 $v1\n";
    return $s/$n;
}

sub scan
{
    ($x, $y, $z, $c) = @_;
    $k = avg(($x/WIDTH)*$w, ($y/HEIGHT)*$h,
	     (($x+XSTEP)/WIDTH)*$w, (($y+YSTEP)/HEIGHT)*$h);
    #print "color: $k\n";
    $zdepth = 0;
    if ($k < $c) {
	# cut to depth
	$cdepth = min($c-$k, CSTEP);
	$zdepth = $z - ZSTEP * $cdepth;
    }
    return $zdepth;
}

@ARGV==1 or die("Usage: hmap.pl file.jpg");
chomp($f = $ARGV[0]);
$img = GD::Image->newFromJpeg($f);
($w, $h) = $img->getBounds();

print "F25.0\nG0 Z0\nG0 X0 Y0\n";

for ($z = 0, $c = COLORS-1; $z > DEPTH; $z -= BITLENGTH, $c -= CSTEP) {
    for ($x = 0; $x<WIDTH; $x += XSTEP) {
	$x = 0 if (abs($x) <= .00001);
	print "G1 X$x\n";
	for ($y = - YSTEP; $y<HEIGHT; $y += YSTEP) {
	    $depth = scan($x, $y, $z, $c);
	    if ($depth != $last_depth) {
		if ($depth < $last_depth) {
		    $yn = $y + YSTEP;
		    $yn = 0 if ( abs($yn) <= .00001);
		    if ($last_depth == 0) { print "G0 Y$yn\nG1 Z$depth\n"; }
		    else { print "G1 Y$yn Z$depth\n"; }
		} else {
		    if ($depth == 0) { print "G0 Z$depth\nG0 Y$y\n"; }
		    else { print "G1 Y$y Z$depth\n"; }
		}
	    }
	    $last_depth = $depth;
	}
	$yn = HEIGHT+YSTEP;
	print "G1 Y$yn\n";

	$x+= XSTEP;
	last if ($x >= WIDTH);
	$x = 0 if (abs($x) <= .00001);

	print "G1 X$x\n";
	for ($y = HEIGHT; $y>=0; $y -= YSTEP) {
	    $depth = scan($x, $y, $z, $c);
	    if ($depth != $last_depth) {
		if ($depth < $last_depth) {
		    $yn = $y + YSTEP;
		    $yn = 0 if ( abs($yn) <= .00001);
		    if ($last_depth == 0) { print "G0 Y$yn\nG1 Z$depth\n"; }
		    else { print "G1 Y$yn Z$depth\n"; }
		} else {
		    if ($depth == 0) { print "G0 Z$depth\nG0 Y$y\n"; }
		    else { print "G1 Y$y Z$depth\n"; }
		}
	    }
	    $last_depth = $depth;
	}
	$yn = - YSTEP;
	$yn = 0 if (abs($yn) <= .00001);
	print "G1 Y$yn\n";
    }
    $z -= BITLENGTH;
    $c -= CSTEP;
    last if $z < DEPTH;
    for ($x = WIDTH - XSTEP; $x>=0; $x -= XSTEP) {
	$x = 0 if (abs($x) <= .00001);
	print "G1 X$x\n";
	for ($y = HEIGHT; $y>=0; $y -= YSTEP) {
	    $depth = scan($x, $y, $z, $c);
	    if ($depth != $last_depth) {
		if ($depth < $last_depth) {
		    $yn = $y + YSTEP;
		    $yn = 0 if ( abs($yn) <= .00001);
		    if ($last_depth == 0) { print "G0 Y$yn\nG1 Z$depth\n"; }
		    else { print "G1 Y$yn Z$depth\n"; }
		} else {
		    if ($depth == 0) { print "G0 Z$depth\nG0 Y$y\n"; }
		    else { print "G1 Y$y Z$depth\n"; }
		}
	    }
	    $last_depth = $depth;
	}
	$yn = - YSTEP;
	print "G1 Y$yn\n";
	$x-= XSTEP;
	last if ($x < 0);
	$x = 0 if (abs($x) <= .00001);
	print "G1 X$x\n";
	for ($y = - YSTEP; $y<HEIGHT; $y += YSTEP) {
	    $depth = scan($x, $y, $z, $c);
	    if ($depth != $last_depth) {
		if ($depth < $last_depth) {
		    $yn = $y + YSTEP;
		    $yn = 0 if ( abs($yn) <= .00001);
		    if ($last_depth == 0) { print "G0 Y$yn\nG1 Z$depth\n"; }
		    else { print "G1 Y$yn Z$depth\n"; }
		} else {
		    if ($depth == 0) { print "G0 Z$depth\nG0 Y$y\n"; }
		    else { print "G1 Y$y Z$depth\n"; }
		}
	    }
	    $last_depth = $depth;
	}
	$yn = HEIGHT + YSTEP;
	$yn = 0 if (abs($yn) <= .00001);
	print "G1 Y$yn\n";
    }
}
