Inverting the Y-Axis.

I seem to be missing something really basic with Java2D.

I have a world model of Shapes in standard graph coordinates (+x right, +y up).

Java2D uses view coordinates (+x right, +y down).

If I create a transform for the Graphics2D that inverts the y-axis, it also draws all Shapes and text upside-down.

How do I preserve the integrity of my model coordinate system and use Java2D transforms to draw the Shapes and text placed and oriented correctly?

[479 byte] By [Joe_Publica] at [2007-10-3 11:09:11]
# 1
Don' t transform the graphics. Just convert your y values.
zadoka at 2007-7-15 13:32:14 > top of Java-index,Security,Cryptography...
# 2

That makes no sense.

For example: I have a Rectangle2D.Double Shape in my world model located at (25, 25) in the world coordinate system and with a size of 50 x 50.

To do as you suggest I would have to either:

1. Change the location of the world Shape object to (25, -25). However, this violates the world model coordinate system and corrupts that Shape object.

2. Create a clone of that Shape object with a location of (25, -25). This preserves the world model integrity but creates unwanted object garbage.

In either case I can then use g2d.draw(shape) to draw the rectangle, but I had to write custom code for that Shape to manually perform this transformation.

The whole stated idea of Java2D transforms is to have the graphics environment transform points or Shapes between coordinate systems instead of writing custom code for each object that transforms them when drawn and without generating unwanted intermediate clone garbage.

Transforming between a +y up and a +y down coordinate system would seem the most basic example of this, but while I can invert the y-axis with a global transform I can find no way to indicate to Shapes and text that their local y-axis should also be inverted so they will not be drawn upside-down.

Is this an actual flaw in the Java2D design that everyone has been coding around, or am I missing something?

Joe_Publica at 2007-7-15 13:32:14 > top of Java-index,Security,Cryptography...
# 3
There is not a flaw in the Java2D design. Either transform (without flipping it), change your world model, or convert the Y values.
zadoka at 2007-7-15 13:32:14 > top of Java-index,Security,Cryptography...
# 4
Wow! Imperious declarations with neither reason nor logic. Thank you for your help but I am looking for the right way to do this within the context of the design of Java2D and not just any old slipshod patch.Can anyone else who is actually knowledgeable in this subject help me out?
Joe_Publica at 2007-7-15 13:32:14 > top of Java-index,Security,Cryptography...
# 5

> Wow! Imperious declarations with neither reason nor

> logic. Thank you for your help but I am looking for

> the right way to do this within the context of the

> design of Java2D and not just any old slipshod

> patch.

>

> Can anyone else who is actually knowledgeable in this

> subject help me out?

1. You ask for help.

2. I give you an option.

3. you don't like it.

4. I present other options

5. you say I have no knowledge.

Yes, I did not put much time or thought into my answer, but my suggestions were free. If you don't like them, don't complain. If you wait around, someone else might answer, but I would suggest you don't insult them if they do answer you.

Also, it is silly of you to judge my knowledge when you know nothing about me.

I would suggest you read this:

http://catb.org/esr/faqs/smart-questions.html

zadoka at 2007-7-15 13:32:14 > top of Java-index,Security,Cryptography...
# 6

I am looking for reasoned answers or discussion, not petulant ego.

I am confronted by a design discrepancy where the docs state that coordinate model and drawing process can be encapsulated, but I'm having to write object-type specific glue code to manually perform a coordinate transformation: a violation of that statement. I'm looking to see if anyone knows a way consistent with that design statement to deal with this, a reason why the design requires it I've missed, or if this is a known inconsistency with which a coder must live.

Again, I thank you for your efforts, but your replies neither address this inconsistency nor provide reasoning (Perhaps a "Smart Replies" FAQ would be more useful). Please allow someone else the chance to educate the both of us and send no further replies.

Joe_Publica at 2007-7-15 13:32:14 > top of Java-index,Security,Cryptography...
# 7

Java gives us tools which we can use to do low–level drawing/graphics. We have to

use imagination and ingenuity to figure out how to use these tools to create what we want.

You can create about anything you want this way.

Using AffineTransform to bridge any model into the view can be challenging. How you use it

depends on what you are trying to do and how you are trying to go about it. There are many

possibilities.

The whole stated idea of Java2D transforms is to have the graphics environment

transform points or Shapes between coordinate systems

It does this really well.

instead of writing custom code for each object that transforms them when drawn

Sometimes this is necessary, usually not.

and without generating unwanted intermediate clone garbage.

Sometimes it is useful to save the originals and create what we need from them as we

proceed. It just depends on what you need.

The trick is to set things up so you can write simple, easy–to–read transforms to get the

job done. This can take some experimenting.

Here's a couple of different ways to transform Polygons from a (world) model coordinate

system, as you defined it with y values increasing upward, into the java/view coordinate

system in which y values increase downward. I chose Polygons with odd numbers of sides

because they are more difficult: their center (origin of radius R to vertices) does not

coincide with the center of their bounding rectangle. You can use the Shape bounds for

everything that has a bounding box, eg, arcs, ellipses, rectangles and GeneralPath and get

good results, fairly easily.

The blue polygons are the originals (naively) drawn with their model coordinates. The red

and green polygons are the polygons transformed into the java/view coordinate system.

import java.awt.*;

import java.awt.event.*;

import java.awt.geom.*;

import javax.swing.*;

public class WorldTransform extends JPanel {

Polygon[] polygons;

boolean methodOne = true;

protected void paintComponent(Graphics g) {

super.paintComponent(g);

Graphics2D g2 = (Graphics2D)g;

g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,

RenderingHints.VALUE_ANTIALIAS_ON);

if(polygons == null) initPolygons();

for(int j = 0; j < polygons.length; j++) {

g2.setPaint(Color.blue);

g2.draw(polygons[j]);

if(methodOne) {

g2.setPaint(Color.red);

g2.draw(modelToView1(polygons[j]));

} else {

g2.setPaint(Color.green.darker());

g2.draw(modelToView2(polygons[j]));

}

}

}

/** Tranlate reflected center point of Polygon. */

private Shape modelToView1(Polygon p) {

Rectangle r = p.getBounds();

double cy = r.getCenterY() + getVerticalOffset(r.height, p.xpoints.length);

Point2D src = new Point2D.Double(r.getCenterX(), cy);

Point2D dest = new Point2D.Double();

// Move to polygon center from bottom of view [y = getHeight()].

AffineTransform at = AffineTransform.getTranslateInstance(-r.getCenterX(),

getHeight()-cy);

// Flip polygon about its center.

at.scale(1, -1);

// Transform the center point of the polygon.

at.transform(src, dest);

//System.out.printf("src=[%5.1f, %5.1f] dest=[%.1f, %6.1f]%n",

//src.getX(), src.getY(), dest.getX(), dest.getY());

// Use this transformed center point to translate the polygon

// to its location in the java/view coordinate system.

at.setToTranslation(dest.getX(), dest.getY());

return at.createTransformedShape(p);

}

/**

* Offset between center of bounds and center of polygon

* for polygons which have an odd number of sides. The offset

* for polygons which have an even number of sides is zero.

*/

private double getVerticalOffset(int height, int sides) {

// R + r = height

// r = R * cos(PI/sides)

double R = height/(1.0 + Math.cos(Math.PI/sides));

double r = R * Math.cos(Math.PI/sides);

return (R - r)/2;

}

/** Reflect polygon about abcissa and flip it about its center. */

private Shape modelToView2(Polygon p) {

// Translate to model abcissa [where y = 0], the bottom of the view.

AffineTransform at = AffineTransform.getTranslateInstance(0, getHeight());

// Reflect about model abcissa, polygon will be inverted.

at.scale(1, -1);

// Move to polygon center and flip it about its center:

// (we are located at the bottom of the view (the JPanel), looking up)

Rectangle r = p.getBounds();

double cy = r.getCenterY() + getVerticalOffset(r.height, p.xpoints.length);

// move to polygon center;

at.translate(0, cy);

// flip polygon about its center;

at.scale(1, -1);

// and move back to abcissa.

at.translate(0, -cy);

// We are now showing polygon in java/view space.

return at.createTransformedShape(p);

}

private void initPolygons() {

polygons = new Polygon[3];

int w = getWidth();

int h = getHeight();

int R = Math.min(w, h)/8;

int[][] xy = generateShapeArrays(w/4, h*3/4, R, 3);

polygons[0] = new Polygon(xy[0], xy[1], 3);

xy = generateShapeArrays(w/2, h/2, R, 5);

polygons[1] = new Polygon(xy[0], xy[1], 5);

xy = generateShapeArrays(w*3/4, h/4, R, 7);

polygons[2] = new Polygon(xy[0], xy[1], 7);

}

private int[][] generateShapeArrays(int cx, int cy, int R, int sides) {

int radInc = 0;

if(sides % 2 == 0)

radInc = 1;

int[] x = new int[sides];

int[] y = new int[sides];

for(int i = 0; i < sides; i++) {

x[i] = cx + (int)(R * Math.sin(radInc*Math.PI/sides));

y[i] = cy - (int)(R * Math.cos(radInc*Math.PI/sides));

radInc += 2;

}

// keep base of triangle level

if(sides == 3)

y[2] = y[1];

return new int[][] { x, y };

}

private JPanel getUIPanel() {

String[] ids = { "method 1", "method 2" };

ButtonGroup group = new ButtonGroup();

ActionListener l = new ActionListener() {

public void actionPerformed(ActionEvent e) {

String ac = e.getActionCommand();

methodOne = ac.equals("method 1");

repaint();

}

};

JPanel panel = new JPanel();

for(int j = 0; j < ids.length; j++) {

JRadioButton rb = new JRadioButton(ids[j], j==0);

rb.setActionCommand(ids[j]);

group.add(rb);

rb.addActionListener(l);

panel.add(rb);

}

return panel;

}

public static void main(String[] args) {

WorldTransform test = new WorldTransform();

JFrame f = new JFrame();

f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

f.getContentPane().add(test);

f.getContentPane().add(test.getUIPanel(), "Last");

f.setSize(400,400);

f.setLocation(200,200);

f.setVisible(true);

}

}

It always takes some experimenting until I can see what I'm doing with the transforms and

then it gets easier. Sometimes I'm unable to find a way to do the transform(s) and have to

give up.

crwooda at 2007-7-15 13:32:14 > top of Java-index,Security,Cryptography...