Instructables

3D Printed Photograph

Featured
Picture of 3D Printed Photograph
print.jpg
ansel.jpg
teddy cropped.jpg
satrun cropped.jpg
saturn side.jpg
The 3d printer in our office (an Objet Connex500) prints with a rigid, semitransparent white material that can be used to create these unique black and white photographic prints.  These prints may be indecipherable when viewed from the side, but when backlit with a diffuse light, they recreate images with surprisingly high precision and even add some subtle dimensionality and texture to the scene.

By varying the thickness of a region of this semitransparent print you can control the amount of light that is able to pass through, thereby controlling the brightness (thinner regions of material will appear brighter and thicker regions darker).  In this project, I've converted each individual greyscale pixel of an image to thickness, allowing me to precisely reproduce any greyscale image.  The photos I've printed include an adorable picture my mom took of our cat Teddy (fig 4), Saturn and its moon Titan taken by the Cassini space probe (fig 5 and 6), and a huge print (19x16") of Mt. Williamson by Ansel Adams (fig 1, 2, and 3).
 
Remove these adsRemove these ads by Signing Up

Step 1: The Code

Picture of The Code
ansel.jpg

All of these 3D models were generated algorithmically from Processing using the ModelBuilder library by Marius Watz. This library allows you to save 3D geometries in the STL file format, STL files that form a watertight mesh can be printed by a 3D printer.

To get started using this code yourself, download the latest version of the ModelBuilder library, unzip the file, and copy the folder into Processing's "libraries" folder. If you have installed the predecessor to the ModelBuilder library (called the Unlekker library), you will need to delete it. Once this is done restart Processing.

//image to 3d printable heightmap/lithophane
//by Amanda Ghassaei
//May 2013
//http://www.instructables.com/id/3D-Printed-Photograph/

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
*/
//libraries
import processing.opengl.*;
import unlekker.util.*;
import unlekker.modelbuilder.*;
import ec.util.*;


String name = "your_file_name_here.jpg";//name of file (with extension - this also works with pngs)

//storage for dimensions
int widthRes;
int heightRes;
float widthDim = 5;//width dimension (in inches)
float widthScaled;
float heightScaled;
float zDim = 0.1;//max vertical displacement (in inches)
float thickness = 0.02;//base thickness (in inches)

boolean invert = true;//if true, then white areas are lower than black, if not true white areas are taller

PImage img;//storage for image
float pixeldata[];//storage for pixel array
UVertexList v1,v2,v3,v4;//storage for verticies
UGeometry geo;//storage for stl geometry

void setup(){
  
  img = loadImage(name);//load image
  //get dimensions of image
  widthRes = img.width;
  heightRes =img.height;
  
  size(widthRes,heightRes,P3D);//set dimensions of output
  
  image(img, 0,0);//display image
  loadPixels();//poad pixels into array
  
  pixeldata = new float[widthRes*heightRes];//initialize storage for pixel data
  for(int index=0;index<widthRes*heightRes;index++){
    int getPixelData = pixels[index];//get data from pixels[] array
    pixeldata[index] = getPixelData&255;//convert to greyscale byte (0-255)
  }
  
  
  //initialize storage for stl
  geo = new UGeometry();
  v1 = new UVertexList();
  v2 = new UVertexList();
  v3 = new UVertexList();
  v4 = new UVertexList();
  
  //draw stl
  
  if(invert){
    //draw top
    for(int i=0;i<(heightRes-1);i++){
      v1.reset();
      v2.reset();
      for(int j=0;j<widthRes;j++){
        widthScaled = j/float(widthRes)*widthDim;
        //top layer
        v1.add(widthScaled,i/float(widthRes)*widthDim,(255-pixeldata[widthRes*i+j])*zDim/255+thickness);
        v2.add(widthScaled,(i+1)/float(widthRes)*widthDim,(255-pixeldata[widthRes*(i+1)+j])*zDim/255+thickness);
      }
      geo.quadStrip(v1,v2);
    }
    //draw sides
    v1.reset();
    v2.reset();
    v3.reset();
    v4.reset();
    for(int j=0;j<widthRes;j++){
      widthScaled = j/float(widthRes)*widthDim;
      v1.add(widthScaled,0,(255-pixeldata[j])*zDim/255+thickness);
      v2.add(widthScaled,0,0);
      v3.add(widthScaled,(heightRes-1)/float(widthRes)*widthDim,(255-pixeldata[widthRes*(heightRes-1)+j])*zDim/255+thickness);
      v4.add(widthScaled,(heightRes-1)/float(widthRes)*widthDim,0);
    }
    geo.quadStrip(v2,v1);
    geo.quadStrip(v3,v4);
    //draw sides
    v1.reset();
    v2.reset();
    v3.reset();
    v4.reset();
    for(int i=0;i<heightRes;i++){
      heightScaled = i/float(widthRes)*widthDim;
      v1.add(0,heightScaled,(255-pixeldata[widthRes*i])*zDim/255+thickness);
      v2.add(0,heightScaled,0);
      v3.add((widthRes-1)/float(widthRes)*widthDim,heightScaled,(255-pixeldata[widthRes*(i+1)-1])*zDim/255+thickness);
      v4.add((widthRes-1)/float(widthRes)*widthDim,heightScaled,0);
    }
    geo.quadStrip(v1,v2);
    geo.quadStrip(v4,v3);
  }
  else{
        //draw top
    for(int i=0;i<(heightRes-1);i++){
      v1.reset();
      v2.reset();
      for(int j=0;j<widthRes;j++){
        widthScaled = j/float(widthRes)*widthDim;
        //top layer
        v1.add(widthScaled,i/float(widthRes)*widthDim,(pixeldata[widthRes*i+j])*zDim/255+thickness);
        v2.add(widthScaled,(i+1)/float(widthRes)*widthDim,(pixeldata[widthRes*(i+1)+j])*zDim/255+thickness);
      }
      geo.quadStrip(v1,v2);
    }
    //draw sides
    v1.reset();
    v2.reset();
    v3.reset();
    v4.reset();
    for(int j=0;j<widthRes;j++){
      widthScaled = j/float(widthRes)*widthDim;
      v1.add(widthScaled,0,(pixeldata[j])*zDim/255+thickness);
      v2.add(widthScaled,0,0);
      v3.add(widthScaled,(heightRes-1)/float(widthRes)*widthDim,(pixeldata[widthRes*(heightRes-1)+j])*zDim/255+thickness);
      v4.add(widthScaled,(heightRes-1)/float(widthRes)*widthDim,0);
    }
    geo.quadStrip(v2,v1);
    geo.quadStrip(v3,v4);
    //draw sides
    v1.reset();
    v2.reset();
    v3.reset();
    v4.reset();
    for(int i=0;i<heightRes;i++){
      heightScaled = i/float(widthRes)*widthDim;
      v1.add(0,heightScaled,(pixeldata[widthRes*i])*zDim/255+thickness);
      v2.add(0,heightScaled,0);
      v3.add((widthRes-1)/float(widthRes)*widthDim,heightScaled,(pixeldata[widthRes*(i+1)-1])*zDim/255+thickness);
      v4.add((widthRes-1)/float(widthRes)*widthDim,heightScaled,0);
    }
    geo.quadStrip(v1,v2);
    geo.quadStrip(v4,v3);
  }
    
  
  //draw bottom
  v1.reset();
  v2.reset();
  //add bottom four corners
  v1.add(0,0,0);
  v1.add(0,(heightRes-1)/float(widthRes)*widthDim,0);
  v2.add((widthRes-1)/float(widthRes)*widthDim,0,0);
  v2.add((widthRes-1)/float(widthRes)*widthDim,(heightRes-1)/float(widthRes)*widthDim,0);
  geo.quadStrip(v1,v2);
  
  //change extension of file name
  int dotPos = name.lastIndexOf(".");
  if (dotPos > 0)
    name = name.substring(0, dotPos);

  geo.writeSTL(this,name+".stl");

  exit();
  
  println("Finished");

}

Download the latest version of the Processing sketch from GitHub (download as a zip by clicking on the cloud button). Open the folder called Lithograph3DPrint. Copy any greyscale images you want to convert into this folder.

To run the sketch, replace the part in quotes in following line:

String name = "your_file_name_here";

with the name of your greyscale image. I believe .gif, .jpg, .tga, and .png files will all work fine, but I have only tested .jpg so far. Run the sketch, after a minute or two Processing will tell you that it is writing an STL file and eventually it will tell you that it is finished. The resulting file will be located in the sketch's folder named "NAME_OF_ORIGINAL_FILE.stl" You can open the stl file with a variety of CAD software and stl viewers, I like MeshLab for simple viewing (it's free and open source).

By default my sketch will scale images to 8" wide, with a base thickness of 0.02" and feature thickness of up to 0.1", you can change these setting by adjusting the variable at the top of the sketch.

fragor28 days ago

One of the greatest projects on Instructables.
Genius. Simply genius.

amandaghassaei (author)  fragor4 days ago

thanks!

cadsage8 days ago

Is anyone getting the message "could not find a method to load ___.jpg" when they try to run the lithograph code? I can't figure out why it is giving me an error. It also highlights the code line listing: "widthRes = img.width;" Don't know if this is connected to the problem??? Help please!

Radioxi1 month ago

Hello, this program looks excellent! Kudos to you. Unfortunately, every time I try to run the code, Processor tells me I don't have the "unlekker package". You mentioned above that the predecessor was named "unlekker library", which leads me to believe that the version may play some role in this. I downloaded v0007a03 of modbuilder, but perhaps another version would have more success? Any insight would help a lot.

Thanks in advance.

Have a question for you- finally got the above problem figured out (thanks for your post!), but now it is giving me the message "could not find a method to load ___.jpg" Help!!!!

Radioxi Radioxi1 month ago

Update: I found the unlekker folder, but Processor still refuses to acknowledge its existence.

Radioxi Radioxi1 month ago

Another Update: Figured it out; the modelbuilder folder wasn't registering as a library because it was in a replicate folder with the same name

amandaghassaei (author)  Radioxi1 month ago

great! did you get a model out?

Yeah, works like a charm now.

arquimana1 month ago
(removed by author or community request)
amandaghassaei (author)  arquimana1 month ago

which line?

Finally I did it, but it works only in some computers, can´t understand why.

Sorry for deleting the comment...

ryanrpd1 month ago

Hi, I've installed Processing on my mac and manually added ModelBuilder as per the instructions. However, I can't find the lithograph3dprint-master folder to add my image to. A system search shows no sign of it on my computer. Do I need to manually create this folder? Thanks

amandaghassaei (author)  ryanrpd1 month ago

lithograph3dprint-master is just what github names my code when you download it there. follow the link and download the folder using the cloud shaped button

cpitts045 months ago
I'm getting an error on line 41. It says "Could not find a method to load the file name". I have the photo in the "Lithograph3DPrint-master" file. any ideas where i am going wrong?
amandaghassaei (author)  cpitts045 months ago
did you import modelbuilder library?

I'm getting the same error. I'm trying to use your code to print out some braille. I think I imported the library correctly (it shows up under sketch --> import library, under contributed).

Any help would be appreciated!

Greetings from Holland.

amandaghassaei (author)  rjupijn1 month ago

does it show up under examples?

Hi Amanda,

It worked. I just retried everything I could think of, and changing the string to test.jpg instead of just 'test' (without the extension) seemed to do the trick. Which is odd, because I tried that already.

The result looks a bit odd, because of the major height difference between black and white, but I will start fiddling with the parameters and it should turn out great.

Thanks again!

amandaghassaei (author)  rjupijn1 month ago

good, try these settings:

float zDim = 0.1;//max vertical displacement (in inches)

float thickness = 0.02;//base thickness (in inches)

the optimal settings will vary based on your print material.

Thanks for your quick respons!

And yes, it does show up under examples --> contributed libraries.
Could it be the library version? I downloaded v0007a03.. I tried this on a pc and on a mac, both with the same error. I'll keep trying but any input is appreciated!

power882 made it!1 month ago

ok, i think i just rendered the stl file with a faster computer, that's cool! however when i opened the model file on cura. it seems to be something else... quite spiky like gothic style things. Did i do something wrong during the steps or it might be the settings..

Thank you!

屏幕快照 2014-02-22 上午11.17.52.png
amandaghassaei (author)  power8821 month ago

it looks like the settings for base thickness and zDim might be off, here's what they should be:

float zDim = 0.1;//max vertical displacement (in inches)

float thickness = 0.02;//base thickness (in inches)

That's it! I think i just made it with your suggestion! Many thanks amanda!

My Thing in 3D made it!1 month ago

Thanks!!! I'll try some in red with the images inverted ASAP!

I made a couple already in clear. My Form 1 printer's serial "number" is Fuzzy Avocado, so I made an Avocado and also made one of my son and they're both pretty cool!

PIC_0721.JPGFA.jpg
amandaghassaei (author)  My Thing in 3D1 month ago

very cool!

Thanks, I made one in red with the opposite settings that turned out great too, its hard for me to get a good pic but I'll try:

mt.JPG
power8821 month ago

hello, i just wonder how much time would it take to render the stl file in processing? because first time i ran the sketch, it remains me to increase memory, so i did it as set memory to 256MB. and now when i run the sketch, it seems just dead? or maybe it's still going…i could also be my old laptop..

stl file appears in the same folder where the sketch stays, right?

Thanks!

amandaghassaei (author)  power8821 month ago

how big is the file? this will probably not work if the image is thousands of pixels tall/wide

This is awesome! My first time with this code stuff but got it goin. Thanks!

Is there a way I can switch the peaks and valleys to the opposite?

amandaghassaei (author)  My Thing in 3D1 month ago

sorry I meant: boolean invert = false;

amandaghassaei (author)  My Thing in 3D1 month ago

boolean inverse = false;

Ralphferro3 months ago
Is processing.org no longer available?

Google Chro connect.me cannot
amandaghassaei (author)  Ralphferro3 months ago

it was down for a sec, should be fine now

tech_knight4 months ago
For the full-tray prints that you've done on the Connex 500 in VeroWhite, how did you clean them? If you printed in glossy mode to avoid having to clean support material off of the front, did you end up with any reflective "streaking"?
amandaghassaei (author)  tech_knight4 months ago
they're all printed glossy, there is a little bit of streaking but the texture in the photos makes it less noticeable.
Ah, thanks. We had a customer ask us about making one.
Paling3185 months ago
Hi amanda, I've been trying very hard to understand the instruction but coudn't follow it at all, I couldn't find the Libraries Folder you are referring to.

Do I need to install a specific software before downloading the Model Library? Please help
amandaghassaei (author)  Paling3185 months ago
the path is Processing>modes>java>libraries, paste the "modelbuilder" folder in the "libraries" folder
sht01136 months ago
OpenGL error 1282 at top endDraw(): invalid operation

my computer output this massage ;(
what's wrong??
Pro

Get More Out of Instructables

Already have an Account?

close

PDF Downloads
As a Pro member, you will gain access to download any Instructable in the PDF format. You also have the ability to customize your PDF download.

Upgrade to Pro today!