PShape texture only applies colour not text?

Hi folks

I’m trying to create a PShape - a rounded rectangle, with a texture consisting of a background colour and some text overlaid. There may be simpler ways, but this is for a project where I’ll be displaying hundreds of these on screen together so I want to avoid redrawing many times by creating a reusable shape.

I can get the desired result with a simple rect, but the rounded rect version applies the background colour but NOT the text??

I had thought this was down to the UV texture mapping as it’s a ‘custom’ shape, but even forcing the UV values (texture and shape are the same size for simplicity) doesn’t help.

Much googling for setTextureUV has failed to drop the penny. Is this is one of those binary “can’t be done OR trivial user error” questions?

The forum suggested this link as similar (Placing a texture on a shape) but that’s concerned with generating a masked image from a shape, whereas I want a shape using the image as a texture, which feels like the opposite proposition?

PFont font;
MyShape myShape;

static final int windowWidth  = 1280;
static final int windowHeight = 968;

void setup() {
  size(1280, 968, P3D);
  font = createFont("Helvetica", 24.0);
  myShape = new MyShape();
}

void draw() {
  if ( mousePressed ) {
    myShape.regenerate();
  }
  shape(myShape.shape, ( windowWidth - myShape.shapeWidth ) / 2.0, ( windowHeight - myShape.shapeHeight ) / 2.0);
}

class MyShape {

  PShape shape;

  int shapeWidth = 400;
  int shapeHeight = 200;

  int radius = 50;

  MyShape() {
    regenerate();
  }

  void regenerate() {

    PShape tempShape, regularRect, roundRect;      
    PGraphics textBoxPGraphics = createGraphics(shapeWidth, shapeHeight);

    // Create texture (background colour and some text) for use later

    noStroke();
    textBoxPGraphics.beginDraw();

    textBoxPGraphics.fill(255); // Text colour white
    textBoxPGraphics.textSize(24.0);
    textBoxPGraphics.textAlign(CENTER, CENTER);

    textBoxPGraphics.background(color(0, 0, 255));
    textBoxPGraphics.text("Blue!", shapeWidth / 2, shapeHeight / 2 );

    textBoxPGraphics.endDraw();

    // updatePixels() has no beneficial effect/   .
    // textBoxPGraphics.updatePixels();
    // loadPixels() has no beneficial effect/   .
    // textBoxPGraphics.loadPixels();

    // Create regularRect using the textBoxPGraphics as a texture - works fine.
    regularRect = createShape();
    regularRect.setTexture(textBoxPGraphics);
    regularRect.beginShape();
    // vertex method includes the UV mappings, the y parameter is offset to enable both shapes to be seen later without overlap 
    // vertex ( x, y, texturex, texturey) 
    regularRect.vertex( 0,          -1.5 * shapeHeight, 0,          0 );
    regularRect.vertex( shapeWidth, -1.5 * shapeHeight, shapeWidth, 0 );
    regularRect.vertex( shapeWidth, -0.5 * shapeHeight, shapeWidth, shapeHeight );
    regularRect.vertex( 0,          -0.5 * shapeHeight, 0,          shapeHeight );
    regularRect.endShape();

    // Create rounded rect using the textBoxPGraphics as a texture - background colour works,
    // *** BUT *** text does not display??

    roundRect = createShape(RECT, 0, 0, shapeWidth, shapeHeight, radius);
    
    // Attempting  to force the UV mappings into the new roundRect PShape doesn't make any beneficial difference

    //for (int i = 0; i < roundRect.getVertexCount(); i++) {
    //  // println(i, roundRect.getVertex(i).x, roundRect.getVertex(i).y);
    //  roundRect.setTextureUV(i, roundRect.getVertex(i).x, roundRect.getVertex(i).y);
    //}

    // roundRect picks up the background colour, but NOT the text.
    roundRect.setTexture(textBoxPGraphics);

    // Add both regular and rounded rects to the shape for display in the draw() method.
    tempShape = createShape(GROUP);
    tempShape.addChild(roundRect);
    tempShape.addChild(regularRect);

    shape = tempShape;
  }
}
2 Likes

I don’t understand this. “I want a shape using the image as a texture” is what the linked example does. What do you want that is “the opposite”? All you have to do is change PImage photo to PGraphics photo, load the image, then add some text on it.

Edit … ah, I see from your test sketch – I get it now. You are right, surprising that texture() isn’t acting as expected.

When you remove the ’ radius’ from the rectangle the text is being displayed, but with rounded corners it vanishes. The text isn’t displayed on an ellipse either, but the blue background colour is

Hi @Tiemen,

I tried this out using getTessellation and NORMAL texture mode. The NORMAL mode sets the UV coordinates to the scale [0, 1].

import processing.opengl.PShapeOpenGL;
import processing.awt.PGraphicsJava2D;

PGraphics2D graphics;

PShapeOpenGL rect;

void setup() {
  size(256, 256, P2D);

  graphics = (PGraphics2D)getGraphics();
  graphics.rectMode(RADIUS);
  graphics.textMode(MODEL);
  graphics.noStroke();

  PGraphicsJava2D img = (PGraphicsJava2D)createGraphics(512, 512, JAVA2D);
  img.beginDraw();
  img.background(0xff0000ff);
  img.fill(0xffffffff);
  img.textSize(48.0);
  img.textAlign(CENTER, CENTER);
  img.text("BLUE!", img.width * 0.5, img.height * 0.5);
  img.endDraw();

  // Create a rect of dimensions [-1, 1].
  rect = (PShapeOpenGL)graphics.createShape(RECT, 
    0.0, 0.0, 1.0, 1.0, 0.25);
  rect = (PShapeOpenGL)rect.getTessellation();

  int vertCount = rect.getVertexCount();
  PVector vert = new PVector();
  for (int i = 0; i < vertCount; ++i) {
    rect.getVertex(i, vert);

    // Shift from [-1, 1] to [0, 1].
    float u = vert.x * 0.5 + 0.5;
    float v = vert.y * 0.5 + 0.5;
    rect.setTextureUV(i, u, v);
  }

  rect.setTextureMode(NORMAL);
  rect.setTexture(img);
  rect.scale(96.0);
}

void draw() {
  graphics.background(255.0);
  graphics.pushMatrix();
  graphics.translate(width * 0.5, height * 0.5);
  graphics.shape(rect);
  graphics.popMatrix();
}

I made sure the rectangle’s original dimensions were in [-1, 1] so I could convert the rectangle’s vertices to UV coordinates through the formula vt = v * 0.5 + (0.5, 0.5) , then scaled up the shape to its display size.

Hope that helps!

2 Likes

Ah, yes. I believe that texture() isn’t compatible with the createShape auto-shape generators. This is because:

The texture() function must be called between beginShape() and endShape() and before any calls to vertex() . texture() / Reference / Processing.org

…but when you pass createShape a magic shape to pregenerate, it calls vertex. So now it is too late to call texture and have it work correctly.

Options:

  1. use the mask method, as above, or
  2. call beginShape(), then texture(), then draw your rounded rect with vertext()
  3. modify the shape with .getTesselation()
1 Like

For discussion related to Processing API open bug, see: https://github.com/processing/processing/issues/2508

Here is a generic workaround with getTessellation() listed there – designed to work with rounded rects, ellipses, etc. Just replace the call to .setTexture with the function call, and define the function, in your class or out.

    //roundRect.setTexture(textBoxPGraphics);
    addTextureUV(roundRect, textBoxPGraphics);
/**
 * https://github.com/processing/processing/issues/2508#issuecomment-50774212
 */
void addTextureUV(PShape s, PImage img) {
  s = s.getTessellation(); // github.com/processing/processing/issues/2508#issuecomment-52296723
  s.setStroke(false);
  s.setTexture(img);
  s.setTextureMode(NORMAL);
  for (int i = 0; i < s.getVertexCount (); i++) {
    PVector v = s.getVertex(i);
    s.setTextureUV(i, map(v.x, 0, img.width, 0, 1), map(v.y, 0, img.height, 0, 1));
  }
}

Edit: looks related to what @behreajj suggested above, although this doesn’t require createShape to be 1.0 x 1.0, I believe …

1 Like

Wow - thanks to all above for your replies, my delayed response is because I’m holiday with poor Internet access this week. I shall try these ideas as soon as I can!
Cheers
Mike

Hi folks. I’ve been trying to use the function that @jeremydouglass provided, but when I replace the suggested line in my code (roundRect.setTexture(textBoxPGraphics);) with the new call (addTextureUV(roundRect, textBoxPGraphics);), the rounded rectangle displays as plain white, no blue fill nor text (and I flipped the text colour to black to make sure I wasn’t being fooled by white-on-white!)
The new code is certainly being called - printlns for proof - and the shape object s is definitely not null after the call to getTesselation…
Could this be related to the particular version of Processing (I’m using 3.5.3 on macOSX 10.14.6 if that’s relevant?) or am I missing something else?

Just a side note - I was able to modify things to operate using PImage rather than PShape, so the original suggestion in this link does provide an answer, if the query above is intractable I’ll rework the non-trivial code to use that approach but I am curious why I’m seeing different results with the PShape method to what Jeremy and others expected…

Terribly sorry, should have actually shared it from my test sketch – perhaps I forgot the return type? Anyhow,

Replace this line:

// roundRect.setTexture(textBoxPGraphics);
roundRect = addTextureUV(roundRect, textBoxPGraphics);

… and add THIS function:

/**
 * https://github.com/processing/processing/issues/2508#issuecomment-50774212
 */
PShape addTextureUV(PShape s, PImage img) {
  PShape s2 = s.getTessellation(); // github.com/processing/processing/issues/2508#issuecomment-52296723
  s2.setStroke(false);
  s2.setTexture(img);
  s2.setTextureMode(NORMAL);
  for (int i = 0; i < s2.getVertexCount (); i++) {
    PVector v = s2.getVertex(i);
    s2.setTextureUV(i, map(v.x, 0, img.width, 0, 1), map(v.y, 0, img.height, 0, 1));
  }
  return s2;
}

Here is the complete working demo sketch.

Summary
PFont font;
MyShape myShape;

static final int windowWidth  = 1280;
static final int windowHeight = 968;

void setup() {
  size(1280, 968, P3D);
  font = createFont("Helvetica", 24.0);
  myShape = new MyShape();
}

void draw() {
  if ( mousePressed ) {
    myShape.regenerate();
  }
  shape(myShape.shape, ( windowWidth - myShape.shapeWidth ) / 2.0, ( windowHeight - myShape.shapeHeight ) / 2.0);
}

class MyShape {

  PShape shape;

  int shapeWidth = 400;
  int shapeHeight = 200;

  int radius = 50;

  MyShape() {
    regenerate();
  }

  void regenerate() {

    PShape tempShape, regularRect, roundRect;      
    PGraphics textBoxPGraphics = createGraphics(shapeWidth, shapeHeight);

    // Create texture (background colour and some text) for use later

    noStroke();
    textBoxPGraphics.beginDraw();

    textBoxPGraphics.fill(255); // Text colour white
    textBoxPGraphics.textSize(24.0);
    textBoxPGraphics.textAlign(CENTER, CENTER);

    textBoxPGraphics.background(color(0, 0, 255));
    textBoxPGraphics.text("Blue!", shapeWidth / 2, shapeHeight / 2 );

    textBoxPGraphics.endDraw();

    // updatePixels() has no beneficial effect/   .
    // textBoxPGraphics.updatePixels();
    // loadPixels() has no beneficial effect/   .
    // textBoxPGraphics.loadPixels();

    // Create regularRect using the textBoxPGraphics as a texture - works fine.
    regularRect = createShape();
    regularRect.setTexture(textBoxPGraphics);
    regularRect.beginShape();
    // vertex method includes the UV mappings, the y parameter is offset to enable both shapes to be seen later without overlap 
    // vertex ( x, y, texturex, texturey) 
    regularRect.vertex( 0,          -1.5 * shapeHeight, 0,          0 );
    regularRect.vertex( shapeWidth, -1.5 * shapeHeight, shapeWidth, 0 );
    regularRect.vertex( shapeWidth, -0.5 * shapeHeight, shapeWidth, shapeHeight );
    regularRect.vertex( 0,          -0.5 * shapeHeight, 0,          shapeHeight );
    regularRect.endShape();

    // Create rounded rect using the textBoxPGraphics as a texture - background colour works,
    // *** BUT *** text does not display??

    roundRect = createShape(RECT, 0, 0, shapeWidth, shapeHeight, radius);
    
    // Attempting  to force the UV mappings into the new roundRect PShape doesn't make any beneficial difference

    //for (int i = 0; i < roundRect.getVertexCount(); i++) {
    //  // println(i, roundRect.getVertex(i).x, roundRect.getVertex(i).y);
    //  roundRect.setTextureUV(i, roundRect.getVertex(i).x, roundRect.getVertex(i).y);
    //}

    // roundRect picks up the background colour, but NOT the text.
    // roundRect.setTexture(textBoxPGraphics);
    roundRect = addTextureUV(roundRect, textBoxPGraphics);

    // Add both regular and rounded rects to the shape for display in the draw() method.
    tempShape = createShape(GROUP);
    tempShape.addChild(roundRect);
    tempShape.addChild(regularRect);

    shape = tempShape;
  }
}

/**
 * https://github.com/processing/processing/issues/2508#issuecomment-50774212
 */
PShape addTextureUV(PShape s, PImage img) {
  PShape s2 = s.getTessellation(); // github.com/processing/processing/issues/2508#issuecomment-52296723
  s2.setStroke(false);
  s2.setTexture(img);
  s2.setTextureMode(NORMAL);
  for (int i = 0; i < s2.getVertexCount (); i++) {
    PVector v = s2.getVertex(i);
    s2.setTextureUV(i, map(v.x, 0, img.width, 0, 1), map(v.y, 0, img.height, 0, 1));
  }
  return s2;
}

Which does this:

17%20AM

That’s brilliant, thanks Jeremy! I now have two working solutions which is always good.

When I read the original I thought it made sense, clearly I missed a subtlety…

In the first version you sent, is the problem that the reference to roundRect (passed into the function as “s” and replaced inside the function’s scope by the tessellation of s) doesn’t actually alter the roundRect object itself when back outside the scope of the function? E.g. “s” points to a new memory location with the tessellation but “roundRect” still points to the old unmodified PShape?

Passing a new object back out as a replacement for roundRect is certainly clearer but as you can tell I’m not a java native.

Cheers
Mike

1 Like

Yes, exactly.

The problem is that s is a reference. So you can pass that reference, then modify the original object through the reference, but if you replace that reference in a called function, you are replacing a local scope variable only – the caller is still pointing at the original.

Here is an example – modifying a shape (which doesn’t need a return, and can be done in place) and replacing a shape with a new object (which does need a return to change the parent reference).

/**
 * PShape modify vs replace
 * 2019-08-31 Processing 3.4
 */
PShape psmod;
PShape psrep;
void setup() {
  rectMode(CENTER);
  psmod = createShape(ELLIPSE, 0, 0, 20, 20);
  psrep = createShape(ELLIPSE, 0, 0, 20, 20);
  noLoop();
}
void draw() {
  background(128);

  // modify a shape
  shape(psmod, 25, 15);
  shapeMod(psmod);
  shape(psmod, 25, 65);

  // replace a shape
  shape(psrep, 75, 15);
  psrep = shapeReplace(psrep);
  shape(psrep, 75, 65);
}

// modify: scale a shape
void shapeMod(PShape ps) {
  ps.scale(2);
}

// replace: change a shape to a RECT
PShape shapeReplace(PShape ps) {
  return createShape(RECT, 0, 0, 20, 20);
}

PShapeModifyReplace--screenshot

1 Like

Well I certainly got value for money with this thread - two workable solutions and better understanding of pass-by-reference into the bargain!
Many thanks Jeremy, and to the other contributors as well!

1 Like