Map image to cylinder

I’m interested to learn how to map an image to a cylinder and have the image look undistorted when viewed from one point.

1 Like

May 2012

/// "ScribbleTube" by TfGuy44
PGraphics BC;
float rot = 0.0;
PImage twoDpart;

void setup() {
  size( 400, 200, P2D);
  BC = createGraphics(200, 200, P3D);
  background(0);
  twoDpart = createImage(200, 200, RGB);
  strokeWeight(3);
}

void draw() {
  stroke(255);
  line(mouseX, mouseY, pmouseX, pmouseY);
  simulateRenderAndDraw3D();
  fill(0, 0, 0, 20);
  noStroke();
  rect(0, 0, 200, 200);
}

void simulateRenderAndDraw3D() {
  rot=(rot+0.01)%360;
  BC.beginDraw();
  BC.background(color(128));
  BC.noFill();//color(0,0,255));
  BC.lights();
  BC.translate(100, 100, 0);
  BC.rotateX(rot);
  BC.rotateY(2*rot);
  BC.scale(80);
  drawTube();
  BC.endDraw();    
  image( BC.get(), 200, 0);
  twoDpart = get( 0, 0, 200, 200 );
}

void drawTube() {
  drawTube(30);
}
void drawTube(int detail) {
  float angle = 0;
  float angleIncrement = TWO_PI / detail;
  BC.noStroke();
  BC.pushMatrix();
  BC.beginShape(QUAD_STRIP);
  if ( twoDpart != null ) {    
    texture( twoDpart );
    BC.texture( twoDpart );
  }
  for (int i=0; i<detail+1; ++i) {
    BC.vertex( cos(angle)/2.0, -.5, sin(angle)/2.0, map(angle, 0, TWO_PI, 0, 200), 0 );
    BC.vertex( cos(angle)/2.0, .5, sin(angle)/2.0, map(angle, 0, TWO_PI, 0, 200), height );
    angle += angleIncrement;
  }
  BC.endShape();
  BC.popMatrix();
}
1 Like

Do you want the resulting image to appear to the viewer like a rounded object, or like a flat object?

You may want to check out SurfaceMapper / SurfaceMapperGUI and this previous thread on cylinder projection:

Thanks, that’s a good starting point.

I appreciate the input. I’ll do some experimentation. My goal is to print onto a cylinder an image that looks flat. I discovered that a Mercator equal-area projection works for a circle, which may lead somewhere. I’m also interested in those images that are stretched in an arc that look correct reflected in a cylinder. I can’t help think a solution lies hidden in them.

The term of art in perspective is anamorphosis – specifically an anamorphic projection of a plane onto a cylinder, so that the cylinder appears to be a plane when viewed from a particular angle – cylindrical anamorphoses. A famous example of this is the anamorphosis of the Church of St Ignacio in Rome, although that is concave.

https://www.google.com/search?q=cylinder+to+plane+Anamorphosis&tbm=isch

Note that techniques may be different from different setups – a front projector (convex), an inner projector (concave), a curved screen or wrapped print, et cetera. The images in an arc that you mentioned are typically a floor image reflected off a mirrored tube, so they have huge radial distortion.

If you are printing a wrapper – or displaying to a screen wrapped around a column, or rendering texture onto a 3D graphical cylinder – then a key concept is that your perspective centerline has increasing distortion growing as you move out horizontally. Here is an overhead view of texture as seen on the column (left side), and then a front view of how the vertical pixel ranks need to be sampled using cosine (right side). Notice that the unwrapped texture has big bands on the sides, but that when wrapped they become visually equal in width to the center bands.

Here is how to create such an image – you can print it out, or you can texture a cylinder with it in Processing.

/**
 * AnamorphicCylinder
 * 2019-11 Processing 3.4 Jeremy Douglass
 * adapted from Texture Cylinder https://processing.org/examples/texturecylinder.html
 * discussion: https://discourse.processing.org/t/map-image-to-cylinder/15670/2
 */

PImage img;
PImage warp;

void setup() {
  size(768, 512, P3D);
  noStroke();
  noFill();
  img = loadImage("https://processing.org/img/processing2-logo-256x256.jpg");
  // create a warp image for covering 1.0 (100%) of cylinder front,
  // flattened 0.07 (7%) on top/bottom
  warp = anamorphCylinder(img, 1.0, 0.07);
}

void draw() {
  background(128);

  // preview before and after images on left side
  image(img, 0, 0);
  image(warp, 0, height/2.0);

  translate(width/2.0, height/2.0);

  // create a half-cylinder texured with square anamorph
  // to simulate projection
  pushMatrix();
  rotateY(spinStopper());
  halfCylinder(100, 200, warp);
  // square viewfinder
  translate(0, 0, 140);
  stroke(255, 0, 0, 128);
  strokeWeight(10);
  rect(-80, -80, 160, 160);
  noStroke();
  popMatrix();

  // alternate view: concave companion half-cylinder
  pushMatrix();
  translate(width*0.3, 0);
  rotateY(spinStopper() + PI*7/8.0);
  scale(-1, 1);
  halfCylinder(100, 200, warp);
  popMatrix();
}

/**
 * Warp image to appear flat when wrapped on cylinder (& viewed straight).
 * xrange -- paints % of the exposed column width, e.g. 1.00
 * ypinch -- flatten % lensing on top/bottom edge, e.g. 0.07 
 */
PImage anamorphCylinder(PImage img, float xrange, float ypinch) {
  PImage out = createImage(img.width, img.height, ARGB);
  out.loadPixels();
  for (int i=0; i<out.pixels.length; i++) out.pixels[i] = color(255);
  img.loadPixels();
  for (int x=0; x<img.width; x++) {
    float xamt = x/(1.0*img.width);
    // source a vertical column of pixels based on cos (circular intersection)
    int cx = int(map(-cos(PI * xamt), -1*xrange, 1*xrange, 0, img.width));
    for (int y=0; y<img.height; y++) {
      // compress the column of pixels based on cos (perspective lensing)
      int yoff = int(map(-cos(TWO_PI * xamt), -1, 1, 0, ypinch * img.height));
      int cy = int(map(y, 0, img.height-1, yoff, img.height-1-yoff));
      out.set(x, cy, img.get(cx, y));
    }
  }
  out.updatePixels();
  return(out);
}

/**
 * An upright half-cylinder based on quad strip.
 */
void halfCylinder(float scale, int segs, PImage txtr) {
  beginShape(QUAD_STRIP);
  texture(txtr);
  float angle = 180.0 / (float)segs;
  for (int i = 0; i<= segs; i++) {
    float x = -cos(radians(i * angle)) * scale;
    float z = sin(radians(i * angle)) * scale;
    float u = warp.width / (float)segs * i;
    vertex(x, -scale, z, u, 0);
    vertex(x, scale, z, u, warp.height);
  }
  endShape();
}

/**
 * turn, then stop, then turn
 */
float spinStopper(){
  return map(constrain((millis()/1000.0)%6,0,4), 0, 4, 0, TWO_PI);
}

  • Upper-left: the original image
  • Lower-left: an anamorphic cylider warp
  • Center: the warp, wrapped around a cylinder. It looks like a square! That is an optical illusion.
  • Right: the same warp, wrapped around a cylinder, but seen convex rather than concave, breaking the illusion.
3 Likes

Thank you, that’s a great help. I hope in time I can return return the favour.

1 Like

One thing to note about this solution is that it doesn’t compute the dimensions of the output warp – when they are applied to the resulting PShape with texture(txtr), it may stretch them automatically because that is how texture works in the graphics pipeline. We loaded a square image into texture, but it was stretched across a rectangular PShape.

So if you are printing a physical label and have a particular target output size in mind, you may want to first resize your input image to those output dimensions, whatever they are, then feed it through this warp process to create the anamorphic illusion. So, if you have a square, first resize square to rectangle (size of label), then warp (as shown above), then print the result and wrap it around your physical object.