Instructables
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.

1-40 of 149Next »

Hi ! I am kind of stuck at the beginning when run the sketch.

It shows "No library found for unlekker.util...."

Please help!

Thank you!

problems 01.jpg
giladaya1 year ago
This is so cool!
The results look surprisingly good, rich with details.

I would use this technique to make a pendant that's smooth and plain on one side, but reveals an image when held against the light.

It took me some time but I ended up implementing a service for creating such pendants. If anyone is interested it's available at http://lithofun.com

The generated models can be downloaded for printing at home.

Great site, gilad, thank you for sharing.

Thank you Amanda, iv'e been looking to try this method for a long time.

I'm new to this Github things so i will probably ask a lot of dumb questions.

1: after i download the "lithograph3DPrint file" where do i save it? is it inside the modelbuilder library?

2: how can i change the lines "String name = "your_file_name_here"" its seems unavailable.

JanH216 days ago

Firstly, thank you amandaghassaei for excelent job! I love your method of creating lithophanes.

There are only two things I would like to ask how to solve:

1) The bottom two faces seems to be somehow corrupted? maybe wrong normal or I don't know - Meshmixer shows it and Repetier also stuck loading the file (http://snag.gy/DQCDK.jpg)
2) Is there any way how to get more smooth result of (e.g vector graphic) lines, directly from Processing? because printer then print the perimeters with lot of noise. (http://snag.gy/Rkfh3.jpg)

mombott1 year ago
This would make fantastic inserts in place of the glass used in kitchen cabinet doors. I'd do a series of fruit and veg pictures from 'old masters' and back light them.
realguy mombott4 months ago

great idea! The whole idea of 2D to 3D for similar purposes (faux leaded glass!)

A 3D printer is next when I can justify getting it and writing off for printing, um, 3D…uh, business documents!!!

Echochrome35 months ago

I have never really used processing, and so I don't know what alot of this means. So could you help fix the problem?

Screen Shot 2014-04-17 at 10.05.10 AM.png
amandaghassaei (author)  Echochrome35 months ago
Sounds like the image isn't loading, did you put it in the same folder as the code?

Where would that be, you said to download the file from Github I then extracted the folder to my desktop. I opened the folder opened the sketch, it said it need to make its folder for the sketch and then it did. I put the image in there, and click run. It gives me the error you see and highlights widthRes = img.width; saying could not find method to load kitten.

amandaghassaei (author)  Echochrome35 months ago

can you post the image and the code you're running?

http://pastebin.com/iZiRHkmu

Kitten.jpg
amandaghassaei (author)  Echochrome35 months ago

looks like the image is in rgb even though it contains only black/white/grey pixels. I converted it to greyscale for you in photoshop. I also made a smaller version, you might want to scale the image down even more if you find that the stl is bigger than 300mb. To download the originals, click the image, bringing up a pop up viewer, then click again, you'll get send to a page with a url like this:

http://www.instructables.com/image/F7SXS98HU8P1NY5

then click on the link for "original file"

F7SXS98HU8P1NY5Small.jpgF7SXS98HU8P1NY5.jpg

This is a great idea, for at least two reasons.

First, you can scan a common 2D picture and convert it to an item that portrays 3D content.

Second, it is a unique application for the 3D printer. How else would you do this on a single item level?

fragor6 months ago

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

amandaghassaei (author)  fragor5 months ago

thanks!

cadsage5 months 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!

Radioxi6 months 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.

cadsage Radioxi5 months ago

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 Radioxi6 months ago

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

Radioxi Radioxi6 months 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)  Radioxi6 months ago

great! did you get a model out?

Yeah, works like a charm now.

arquimana6 months ago
(removed by author or community request)
amandaghassaei (author)  arquimana6 months ago

which line?

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

Sorry for deleting the comment...

ryanrpd6 months 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)  ryanrpd6 months 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

cpitts0410 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)  cpitts0410 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)  rjupijn6 months 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)  rjupijn6 months 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!7 months 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)  power8826 months 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!7 months 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
1-40 of 149Next »