CNC Milling Photos With A Halftone Generator

Looking for an awesome way to mill out a photo or graphic? Check out [Matt Venn]’s halftone gcode generator which creates halftone CNC toolpaths from any image file. We’ve run across some halftone generators before, but [Matt]’s generator has some interesting features and makes for some pretty unique output.

[Matt] initially wrote a simple command line program in Python, but just rewrote his script with a more user-friendly UI that renders a preview of the output as you change options.  The UI lets you change parameters like drill depth, number of lines, and the step size to tweak the output. It even has an option to map the halftone points along a sine wave which makes an interesting effect as shown in the image above.

[Matt]’s program generates standard gcode that you can use to run your CNC machine. [Matt] recommends milling a material with layers of different colors, but you can always mill a solid material and fill the routed areas with paint or dye instead. Want to grab the script or check out the source code? Head over to [Matt]’s GitHub repository.

Thanks for the tip, [Keith O].

22 thoughts on “CNC Milling Photos With A Halftone Generator

      1. Nick,

        Here is the code I used. I need to reinstall MATLAB, so it hasn’t been tested in a while.

        Also, I’m not sure if there are rules around here about taking up space in comments and pasting code. Sorry if this is frowned on (it is an old post…).

        %Script 1:
        clear

        % Will Hobbs. 2012.
        % Ideas from:
        %http://g-fav.blogspot.com/2008/09/math-art-click-stand-back-and-squint.htm
        %l

        img=imread(‘thefam.jpg’); %Change this. Image to be used.
        rowSpacing=16; %Change this. Vertical spacing (pixels).
        colSpacing=8; %Change this. Horizontal spacing (pixels).
        areaExponent=1.3; %Change this. Higher number gives more contrast?

        imgGR=rgb2gray(img);
        %imgGR=histeq(imgGR);

        startRow=1;
        startCol=1;
        numRows=size(imgGR,1);
        numCols=size(imgGR,2);

        for i=1:numRows/rowSpacing-1
        for j=1:numCols/colSpacing-1
        dotDiam(i,j)=256-mean2(imgGR(startRow:startRow+rowSpacing-1,startCol:startCol+colSpacing-1));
        x(i,j)=j;
        y(i,j)=i;
        startCol=startCol+colSpacing;
        end
        startCol=1;
        startRow=startRow+rowSpacing;
        end

        dotArea=(dotDiam./2).^areaExponent*pi;
        scatter(reshape(x,1,[]),-reshape(y,1,[]),reshape(dotArea/20,1,[]),’filled’,’k’)

        set(gca,’Visible’,’off’)
        set(gcf,’color’,’w’)
        daspect([rowSpacing colSpacing 1])

        %—————
        %Script 2:
        clear

        % Will Hobbs. 2012.
        % Ideas and some code from:
        %http://g-fav.blogspot.com/2008/09/math-art-click-stand-back-and-squint.htm
        %l

        %http://www.mathworks.com/matlabcentral/newsreader/view_thread/300190

        face=imread(‘IMG_9735.jpg’);
        faceGR=rgb2gray(face);
        %faceGR=histeq(faceGR);

        %————————-
        %Concentric Circles
        numRows=size(faceGR,1);
        numCols=size(faceGR,2);
        diag=round(sqrt(numRows^2+numCols^2));

        sp=12; %even number. CHANGE THIS

        for r=1:sp:(diag)
        x1=(-r:r)’;
        x2=(-r:r)’;
        y1=sqrt(r^2-x1.^2);
        y2=-sqrt(r^2-x2.^2);
        dxy1=diff([x1,y1],1);
        dxy2=diff([x2,y2],1);
        d=sqrt(sum(dxy1.^2,2));
        d=[0; cumsum(d)];
        di=linspace(0,d(end),pi*r/14);
        xyi1=interp1(d,[x1,y1],di);
        xi1=xyi1(:,1);
        yi1=xyi1(:,2);
        xyi2=interp1(d,[x2,y2],di);
        xi2=xyi2(:,1);
        yi2=xyi2(:,2);
        if r>1
        Xi=[Xi;xi1;xi2];
        Yi=[Yi;yi1;yi2];
        else
        Xi=[xi1;xi2];
        Yi=[yi1;yi2];
        end
        end
        %————————-

        % %————————-
        % %Sine Waves
        % numRows=size(faceGR,1);
        % numCols=size(faceGR,2);
        % diag=round(sqrt(numRows^2+numCols^2));

        % sp=18; %even number. CHANGE THIS
        % spH=.5; % CHANGE THIS

        % for r=1:sp:numRows
        % x1=(1:spH:numCols)’;
        % y1=5*sin(x1/40)+r; % these values can be changed
        %
        % if r>1
        % Xi=[Xi;x1];
        % Yi=[Yi;y1];
        % else
        % Xi=[x1];
        % Yi=[y1];
        % end
        % end
        % %————————-

        XY=[Xi,Yi];
        rowStart=-100;
        colStart=-80;
        XY2=XY(XY(:,1)>(colStart+sp)&XY(:,1)<(numCols+colStart-sp)&XY(:,2)(-rowStart+sp-numRows),:);

        Xi=numCols-(XY2(:,1)-colStart);

        Yi=XY2(:,2)+numRows+rowStart;

        for i=1:length(Xi)
        row=numRows-round(Yi(i));
        col=numCols-round(Xi(i));
        dotDiam2(i)=double(256-mean2(faceGR(row-sp/2:row+sp/2,col-sp/2:col+sp/2)));

        dotArea2(i)=(dotDiam2(i)./2).^1.9*pi; %CHANGE THIS. The exponent can be changed.

        end

        figure
        scatter(-Xi,Yi,dotArea2/100,’filled’,’k’)
        axis equal
        axis([-numCols+sp,-sp,sp,numRows-sp])
        set(gca,’Visible’,’off’)
        set(gcf,’color’,’w’)

  1. Mmmm – I’m trying to send a comment about a real live 9 meter tall half-tone image made of steel, with some links, but the comment never shows up here. If I try send it again, it says I’ve duplicated my comment. @Ethan Zonca maybe you can see it. Any ideas why?

  2. … trying to send the comment in parts to see where it gets rejected:

    Readers may be interested in a unique massive half-tone image in tribute to Nelson Mandela, made out of 50 steel columns varying in thickness, in the KwaZulu Natal province in South Africa. The tallest column is over 9 meters high, and when viewed from the “focus”, it shows a clear half-tone image of Mr. Mandela’s likeness.

  3. Hackaday has something against this link. Please remove the spaces or hack hackaday:
    h t t p : / / w w w . p a r t s a n d l a b o u r . c o . z a / p o r t f o l i o / n e l s o n – m a n d e l a – c a p t u r e – s i t e /

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.