Stroke breite absolut in px setzen?

Status
Nicht offen für weitere Antworten.

0x7F800000

Top Contributor
Geht nicht, oder wie? ;(
Forumsuche ergibt nichts sinnvolles.
Bei google habe ich nur weitere verzweifelte gefunden...

Was tun? Alle zu zeichnenden Objekte umtransformieren und dann in lokalen Bildschirmkoordinaten mit BasicStroke(1) zeichnen?

Das kann doch nicht wahr sein...:autsch:
 

0x7F800000

Top Contributor
"Alle zu zeichnenden Objekte Transformieren" war wohl auch zu leicht gesagt: AffineTransform ist aus irgendeinem Grunde nur auf Points anwendbar...

Soll ich jetzt für jedes polygon dauernd manuell 4832768 punkte in einer schleife einzeln umtransformieren, um sie wieder zu einem polygon zusammenzupixeln oder was?

Ich bin momentan wirklich vollkommen ratlos :bahnhof:
Weiss einer was?
 
Zuletzt bearbeitet:

slawaweis

Bekanntes Mitglied
was meinst Du mit "absolut in px"? Was genau willst Du eigentlich machen?

Falls Du eine Art Zoom hast und immer eine Linienbreite von 1px haben willst, musst Du BasicStroke an den Zoom anpassen, mit BasicStroke(1f/zoom).

Slawa
 

0x7F800000

Top Contributor
Ja, genau das will ich haben. Ich habe eine Komponente, auf der ich wild herumzoomen will. und darauf will ich unter anderem "eindimensionale" also unendlich dünne Objekte wie Kreise und geraden darstellen. Sie haben ja keine Dicke. Sie müssen bei jeder Zoomstufe gleich aussehen, zB. sollten die sichtbaren Linien immer die Breite ~1px haben.

BasicStroke(1/zoom)? Hab kurz auch dran gedacht, aber schnell verworfen. Frage ist: Was ist "zoom"? Ich hab da kein einheitliches "zoom" in alle richtungen. Ich will es auch erlauben, den gezeigten Bereich unproportional zu Skalieren, sodass etwa [0,1]x[0,100] auf einem Quadratischen JComponent mit Abmessungen 500x500px dargestellt wird. Was sol ich da als "zoom" nehmen? 1? 50? 100? Wenn ich da einfach irgendeinen festen wert nehme, dann erscheinen die flachen Stücke dünner und die steilen Kurvenabschnitte dicker, und zwar um faktor 100, egal wie man es dreht. Das sieht dann einfach nur total zum verschrotten aus.
Das bringt mich leider nicht weiter...

Aber danke für den Vorschlag schonmal.

Weißt du zufälligerweise, wie ich mir das mit Shape#getPathIterator() vorzustellen habe? Da wird ja ein Pfad aus Bezierkurven zurückgeliefert. Ein Kreis ist definitiv nicht durch solche Bezierkurven darstellbar, höchstens approximierbar. Was wird da zurückgeliefert? Annäherung durch 2 Beziers? Durch 8 Beziers? Durch 1000 beziers?
Was ist aber, wenn ich da so stark reinzoome, dass die Unterschiede zwischen Kreis und der beziers-approximation groß genug sind, um die mit bloßem auge zu sehen? Wäre doch total übel... Was liefert also Ellipse2D#getPathIterator() denn da zurück????:L
 

0x7F800000

Top Contributor
Ah, okay, ich glaub ich hab's... Zumindest was das verhalten von PathIterator angeht:
http://java.sun.com/javase/6/docs/api/java/awt/geom/Ellipse2D.Double.html#setFrame(double,%20double,%20double,%20double)
Da kann man das Rechteck festlegen, in dem der Kreis dargestellt wird. In diesem rechteck wird der Kreis dann wohl auch hinreichend genau dargestellt, sodass man zumindest mit dem bloßen auge schonmal keine groben fehler entdecken kann.

Gut, dann versuche ich jetzt folgendes:
1) Shape's erstellen
2) getPathIterator(AffineTransform t) darauf aufrufen, wobei die Transformation die lokalen Koordinaten des sichtbaren bereiches auf bildschirmkoordinaten umwandelt
3) Diesen Path dann wiederum mit der Identitäts-Transformation und BasicStroke(1) direkt in Bildschirmkoordinaten zeichnen

müsste doch klappen, oder? ???:L

melde mich gleich wieder. :)
 

Ebenius

Top Contributor
Ich habe dazu (ist schon eine ganze Weile her und den Code besitze ich nicht) die Inverse Transformation (der von Dir angehängten Transformation) genutzt, mit dieser ein Rechteck [0,0,1,1] übersetzt und die durchschnittliche/kleinere/größere Kantenlänge oder Diagonalenlänge als Breite für den Basicstroke benutzt. Das klappte wunderbar, auch wenn ich eine Weile gebraucht habe, um die Lösung so zu finden.

Nachtrag: Die Identitätstransformation ist keine Referenz; das Graphics-Objekt kann gut auch schon mit einer Transformation daher kommen. GraphicsConfiguration.getDefaultTransform() und evtl. auch GraphicsConfiguration.getNormalizingTransform() sind auch noch einen Blick wert.

Ebenius
 
Zuletzt bearbeitet:

0x7F800000

Top Contributor
@Ebenius: sorry, nicht ganz nachvollzogen, was du da wegen der Identitätstransformation meintest? :bahnhof:

Also, hab's jetzt imho ganz gut hinbekommen:
[highlight=Java]

Shape shape=...// irgendwas in den lokalen koordinaten

AffineTransform fromRealToPixel = ...//transformation, die die lokalen koordinaten in bildschirmkoordinaten umwandelt

//g eigentlich so lassen wie es ist
g.setTransform(new AffineTransform()); //alles pixel zu pixel so übernehmen wie es ist
g.setStroke(new BasicStroke(1)); //1 px breite Linie beim beliebigen zoom

//das zu zeichnende shape mit der fromRealToPixel-Transform umwandeln
GeneralPath path=new GeneralPath();
path.append(shape.getPathIterator(fromRealToPixel),false);

//zeichnen
g.draw(path);
[/highlight]
So wird die Form korrekt ausgerichtet, und zoomunabhängig mit einer 1px-breiten Linie ohne jegliche Verzerrungen hingezeichnet. Mir gefällt's so ganz gut.:toll:
 

0x7F800000

Top Contributor
So, jetzt bitte nicht umfallen^^

Ich habe mir eben mithilfe der Reflection eine Wrapperklasse zu Graphics2D erstellen lassen, die eine einzige methode überschreibt:
[highlight=Java]
package gui;
import java.awt.*;
import java.awt.RenderingHints.Key;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ImageObserver;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.RenderableImage;
import java.text.AttributedCharacterIterator;
import java.util.Map;

class AbsoluteStrokeGraphics2D extends Graphics2D{

private Graphics2D g2D;

public AbsoluteStrokeGraphics2D(Graphics2D g){
g2D=g;
}

@Override
public void draw(Shape shape){
GeneralPath path=new GeneralPath();
AffineTransform affineTransform;
path.append(shape.getPathIterator(affineTransform=g2D.getTransform()),false);
g2D.setTransform(new AffineTransform());
g2D.draw(path);
g2D.setTransform(affineTransform);
}

@Override public void transform(AffineTransform arg_0){ g2D.transform( arg_0); }
@Override public void fill(Shape arg_0){ g2D.fill( arg_0); }
@Override public void rotate(double arg_0,double arg_1,double arg_2){ g2D.rotate( arg_0, arg_1, arg_2); }
@Override public void rotate(double arg_0){ g2D.rotate( arg_0); }
@Override public void scale(double arg_0,double arg_1){ g2D.scale( arg_0, arg_1); }
@Override public void translate(double arg_0,double arg_1){ g2D.translate( arg_0, arg_1); }
@Override public Color getBackground(){ return g2D.getBackground(); }
@Override public void setBackground(Color arg_0){ g2D.setBackground( arg_0); }
@Override public boolean drawImage(Image arg_0,AffineTransform arg_1,ImageObserver arg_2){ return g2D.drawImage( arg_0, arg_1, arg_2); }
@Override public void drawImage(BufferedImage arg_0,BufferedImageOp arg_1,int arg_2,int arg_3){ g2D.drawImage( arg_0, arg_1, arg_2, arg_3); }
@Override public AffineTransform getTransform(){ return g2D.getTransform(); }
@Override public void setTransform(AffineTransform arg_0){ g2D.setTransform( arg_0); }
@Override public void shear(double arg_0,double arg_1){ g2D.shear( arg_0, arg_1); }
@Override public void setRenderingHint(Key arg_0,Object arg_1){ g2D.setRenderingHint( arg_0, arg_1); }
@Override public void clip(Shape arg_0){ g2D.clip( arg_0); }
@Override public GraphicsConfiguration getDeviceConfiguration(){ return g2D.getDeviceConfiguration(); }
@Override public void drawGlyphVector(GlyphVector arg_0,float arg_1,float arg_2){ g2D.drawGlyphVector( arg_0, arg_1, arg_2); }
@Override public void drawString(AttributedCharacterIterator arg_0,float arg_1,float arg_2){ g2D.drawString( arg_0, arg_1, arg_2); }
@Override public void drawString(String arg_0,float arg_1,float arg_2){ g2D.drawString( arg_0, arg_1, arg_2); }
@Override public FontRenderContext getFontRenderContext(){ return g2D.getFontRenderContext(); }
@Override public RenderingHints getRenderingHints(){ return g2D.getRenderingHints(); }
@Override public void setComposite(Composite arg_0){ g2D.setComposite( arg_0); }
@Override
@SuppressWarnings("unchecked")
public void addRenderingHints(Map arg_0){ g2D.addRenderingHints( arg_0); }
@Override public void drawRenderableImage(RenderableImage arg_0,AffineTransform arg_1){ g2D.drawRenderableImage( arg_0, arg_1); }
@Override public void drawRenderedImage(RenderedImage arg_0,AffineTransform arg_1){ g2D.drawRenderedImage( arg_0, arg_1); }
@Override public Composite getComposite(){ return g2D.getComposite(); }
@Override public Paint getPaint(){ return g2D.getPaint(); }
@Override public Object getRenderingHint(Key arg_0){ return g2D.getRenderingHint( arg_0); }
@Override public Stroke getStroke(){ return g2D.getStroke(); }
@Override public boolean hit(Rectangle arg_0,Shape arg_1,boolean arg_2){ return g2D.hit( arg_0, arg_1, arg_2); }
@Override public void setPaint(Paint arg_0){ g2D.setPaint( arg_0); }
@Override
@SuppressWarnings("unchecked")
public void setRenderingHints(Map arg_0){ g2D.setRenderingHints( arg_0); }
@Override public void setStroke(Stroke arg_0){ g2D.setStroke( arg_0); }

@Override public void finalize(){ g2D.finalize(); }
@Override public String toString(){ return g2D.toString(); }
@Override public Graphics create(int arg_0,int arg_1,int arg_2,int arg_3){ return g2D.create( arg_0, arg_1, arg_2, arg_3); }
@Override public Graphics create(){ return g2D.create(); }
@Override public Font getFont(){ return g2D.getFont(); }
@Override public void dispose(){ g2D.dispose(); }
@Override public void clearRect(int arg_0,int arg_1,int arg_2,int arg_3){ g2D.clearRect( arg_0, arg_1, arg_2, arg_3); }
@Override public void setFont(Font arg_0){ g2D.setFont( arg_0); }
@Override public void translate(int arg_0,int arg_1){ g2D.translate( arg_0, arg_1); }
@Override public Shape getClip(){ return g2D.getClip(); }
@Override public FontMetrics getFontMetrics(Font arg_0){ return g2D.getFontMetrics( arg_0); }
@Override public FontMetrics getFontMetrics(){ return g2D.getFontMetrics(); }
@Override public void setClip(Shape arg_0){ g2D.setClip( arg_0); }
@Override public void setClip(int arg_0,int arg_1,int arg_2,int arg_3){ g2D.setClip( arg_0, arg_1, arg_2, arg_3); }
@Override public boolean drawImage(Image arg_0,int arg_1,int arg_2,Color arg_3,ImageObserver arg_4){ return g2D.drawImage( arg_0, arg_1, arg_2, arg_3, arg_4); }
@Override public boolean drawImage(Image arg_0,int arg_1,int arg_2,ImageObserver arg_3){ return g2D.drawImage( arg_0, arg_1, arg_2, arg_3); }
@Override public boolean drawImage(Image arg_0,int arg_1,int arg_2,int arg_3,int arg_4,int arg_5,int arg_6,int arg_7,int arg_8,ImageObserver arg_9){ return g2D.drawImage( arg_0, arg_1, arg_2, arg_3, arg_4, arg_5, arg_6, arg_7, arg_8, arg_9); }
@Override public boolean drawImage(Image arg_0,int arg_1,int arg_2,int arg_3,int arg_4,int arg_5,int arg_6,int arg_7,int arg_8,Color arg_9,ImageObserver arg_10){ return g2D.drawImage( arg_0, arg_1, arg_2, arg_3, arg_4, arg_5, arg_6, arg_7, arg_8, arg_9, arg_10); }
@Override public boolean drawImage(Image arg_0,int arg_1,int arg_2,int arg_3,int arg_4,Color arg_5,ImageObserver arg_6){ return g2D.drawImage( arg_0, arg_1, arg_2, arg_3, arg_4, arg_5, arg_6); }
@Override public boolean drawImage(Image arg_0,int arg_1,int arg_2,int arg_3,int arg_4,ImageObserver arg_5){ return g2D.drawImage( arg_0, arg_1, arg_2, arg_3, arg_4, arg_5); }
@Override public void fillRect(int arg_0,int arg_1,int arg_2,int arg_3){ g2D.fillRect( arg_0, arg_1, arg_2, arg_3); }
@Override public void setColor(Color arg_0){ g2D.setColor( arg_0); }
@Override public Color getColor(){ return g2D.getColor(); }
@Override public void copyArea(int arg_0,int arg_1,int arg_2,int arg_3,int arg_4,int arg_5){ g2D.copyArea( arg_0, arg_1, arg_2, arg_3, arg_4, arg_5); }
@Override public void drawArc(int arg_0,int arg_1,int arg_2,int arg_3,int arg_4,int arg_5){ g2D.drawArc( arg_0, arg_1, arg_2, arg_3, arg_4, arg_5); }
@Override public void drawChars(char[] arg_0,int arg_1,int arg_2,int arg_3,int arg_4){ g2D.drawChars( arg_0, arg_1, arg_2, arg_3, arg_4); }
@Override public void drawLine(int arg_0,int arg_1,int arg_2,int arg_3){ g2D.drawLine( arg_0, arg_1, arg_2, arg_3); }
@Override public void drawOval(int arg_0,int arg_1,int arg_2,int arg_3){ g2D.drawOval( arg_0, arg_1, arg_2, arg_3); }
@Override public void drawPolygon(Polygon arg_0){ g2D.drawPolygon( arg_0); }
@Override public void drawPolygon(int[] arg_0,int[] arg_1,int arg_2){ g2D.drawPolygon( arg_0, arg_1, arg_2); }
@Override public void drawPolyline(int[] arg_0,int[] arg_1,int arg_2){ g2D.drawPolyline( arg_0, arg_1, arg_2); }
@Override public void drawRect(int arg_0,int arg_1,int arg_2,int arg_3){ g2D.drawRect( arg_0, arg_1, arg_2, arg_3); }
@Override public void drawRoundRect(int arg_0,int arg_1,int arg_2,int arg_3,int arg_4,int arg_5){ g2D.drawRoundRect( arg_0, arg_1, arg_2, arg_3, arg_4, arg_5); }
@Override public void drawString(String arg_0,int arg_1,int arg_2){ g2D.drawString( arg_0, arg_1, arg_2); }
@Override public void drawString(AttributedCharacterIterator arg_0,int arg_1,int arg_2){ g2D.drawString( arg_0, arg_1, arg_2); }
@Override public void fillArc(int arg_0,int arg_1,int arg_2,int arg_3,int arg_4,int arg_5){ g2D.fillArc( arg_0, arg_1, arg_2, arg_3, arg_4, arg_5); }
@Override public void fillOval(int arg_0,int arg_1,int arg_2,int arg_3){ g2D.fillOval( arg_0, arg_1, arg_2, arg_3); }
@Override public void fillPolygon(Polygon arg_0){ g2D.fillPolygon( arg_0); }
@Override public void fillPolygon(int[] arg_0,int[] arg_1,int arg_2){ g2D.fillPolygon( arg_0, arg_1, arg_2); }
@Override public void fillRoundRect(int arg_0,int arg_1,int arg_2,int arg_3,int arg_4,int arg_5){ g2D.fillRoundRect( arg_0, arg_1, arg_2, arg_3, arg_4, arg_5); }
@Override public void clipRect(int arg_0,int arg_1,int arg_2,int arg_3){ g2D.clipRect( arg_0, arg_1, arg_2, arg_3); }
@Override public void drawBytes(byte[] arg_0,int arg_1,int arg_2,int arg_3,int arg_4){ g2D.drawBytes( arg_0, arg_1, arg_2, arg_3, arg_4); }
@Override public Rectangle getClipBounds(Rectangle arg_0){ return g2D.getClipBounds( arg_0); }
@Override public Rectangle getClipBounds(){ return g2D.getClipBounds(); }
@Override public boolean hitClip(int arg_0,int arg_1,int arg_2,int arg_3){ return g2D.hitClip( arg_0, arg_1, arg_2, arg_3); }
@Override public void setPaintMode(){ g2D.setPaintMode(); }
@Override public void setXORMode(Color arg_0){ g2D.setXORMode( arg_0); }
@Override public void draw3DRect(int arg_0,int arg_1,int arg_2,int arg_3,boolean arg_4){ g2D.draw3DRect( arg_0, arg_1, arg_2, arg_3, arg_4); }
@Override public void fill3DRect(int arg_0,int arg_1,int arg_2,int arg_3,boolean arg_4){ g2D.fill3DRect( arg_0, arg_1, arg_2, arg_3, arg_4); }
@Override
@Deprecated public Rectangle getClipRect(){ return g2D.getClipRect(); }

}

[/highlight]

Okay, es funktioniert. Es benimmt sich wie ein stinknormales Graphics2D-Objekt, mit dem kleinen aber feinen Unterschied, dass Strokes nicht mitskaliert werden, sondern immer konstant bleiben.

Aber es ist doch einfach nur dermaßen shice hässlich :autsch:
Irgendwelche vorschläge, wie ich dasselbe ausformuliere, ohne 432 Milliarden methoden sinnlos zu wrappen? Irgendeine konstruktion mit anonymen inneren Klassen fällt mir leider nicht ein, da die verdammten Graphics2D ja keinerlei Konstruktoren zur verfügung stellen, weil sie ja irgendwie Klassen aber so abstrakt wie Interfaces sind^^ Bwwaaah... Was mach ich jetzt? Diesen Mist einfach so lassen und freuen dass es funzt? :bahnhof:
 
Zuletzt bearbeitet:
S

Spacerat

Gast
vllt. machts Sinn sich bei jedem "scale" den Kehrwert davon zu merken, damit man "stroke" ggf. damit multiplizieren kann. ... dann nur noch draw(Shape shape, double zoomfactor) implementieren... evoila.
 

slawaweis

Bekanntes Mitglied
Ich habe mir eben mithilfe der Reflection eine Wrapperklasse zu Graphics2D erstellen lassen ...
ich frage mich, wo Du da Reflections gebraucht hast.

Die Lösung nach der Du suchst sieht so aus:

[highlight="java"]
Graphics2D g2 = (Graphics2D)g;

Graphics2D g2_normal = (Graphics2D)g2.create();
Graphics2D g2_scaled = (Graphics2D)g2.create();

g2_scaled.setTransform(...);
[/highlight]

EDIT:
Weißt du zufälligerweise, wie ich mir das mit Shape#getPathIterator() vorzustellen habe? Da wird ja ein Pfad aus Bezierkurven zurückgeliefert. Ein Kreis ist definitiv nicht durch solche Bezierkurven darstellbar, höchstens approximierbar. Was wird da zurückgeliefert? Annäherung durch 2 Beziers? Durch 8 Beziers? Durch 1000 beziers?
durch genau 4 Bezier Kurven.

Slawa
 
Zuletzt bearbeitet:

0x7F800000

Top Contributor
vllt. machts Sinn sich bei jedem "scale" den Kehrwert davon zu merken, damit man "stroke" ggf. damit multiplizieren kann. ... dann nur noch draw(Shape shape, double zoomfactor) implementieren... evoila.
Hat slawaweis auch schon vorgeschlagen... Es hilft leider nicht, weil es keinen einheitlichen "zoom"-faktor gibt, der ist für x und y verschieden. Wenn ich dann ein 1x1000-Rechteck auf eine 500x500px JComponent zeichne, sind bei derselben Kurve alle vertikale abschnitte megabreit, und alle horizontalen praktisch unsichtbar. Das möchte ich nicht, das ist noch verwirrender als nichts zu zeichnen...

ich frage mich, wo Du da Reflections gebraucht hast.
zum erzeugen des codes? Ich tippe doch nicht diese 150 methoden per hand ab...
Die Lösung nach der Du suchst sieht so aus:

[highlight="java"]
Graphics2D g2 = (Graphics2D)g;

Graphics2D g2_normal = (Graphics2D)g2.create();
Graphics2D g2_scaled = (Graphics2D)g2.create();

g2_scaled.setTransform(...);
[/highlight]
was genau davon soll die Lösung meines Problems sein? ???:L
Zur erinnerung: mein Problem ist, dass sich bei Java2D die Transformation der Strokes nicht abschalten lässt. Ich will einfach nur, dass alle mögliche formen, beliebig skaliert und zerquätscht werden können, und dennoch überall mit gleicher Linienstärke gezeichnet werden.

Zur veranschaulichung:
 

slawaweis

Bekanntes Mitglied
zum erzeugen des codes? Ich tippe doch nicht diese 150 methoden per hand ab...
manche IDE macht Delegator Klassen automatisch.

was genau davon soll die Lösung meines Problems sein? ???:L
Zur erinnerung: mein Problem ist, dass sich bei Java2D die Transformation der Strokes nicht abschalten lässt. Ich will einfach nur, dass alle mögliche formen, beliebig skaliert und zerquätscht werden können, und dennoch überall mit gleicher Linienstärke gezeichnet werden.
dann erstellst Du dir 2 Graphics2D Objekte. Das eine ist skaliert und das andere nicht. Mit dem ersten werden die Formen gezeichnet, bei dem anderen wird zuerst die Geometrie transformiert und dann der Umriss gezeichnet. Ich verstehe nicht, wozu Du einen Graphics2D Wrapper brauchst.

Slawa
 

0x7F800000

Top Contributor
manche IDE macht Delegator Klassen automatisch.
Tatsache, wo du es sagst, habe ich die Option auch bei eclipse entdeckt^^ :D
Naja, egal, jetzt weiß ich zumindest auch etwas genauer wie eclipse das anstellt, war doch auch eine witzige übung....

Aber btt:
dann erstellst Du dir 2 Graphics2D Objekte. Das eine ist skaliert und das andere nicht. Mit dem ersten werden die Formen gezeichnet, bei dem anderen wird zuerst die Geometrie transformiert und dann der Umriss gezeichnet. Ich verstehe nicht, wozu Du einen Graphics2D Wrapper brauchst.
Genau das sollte doch die überschriebene methode bewerkstelligen:
[highlight=Java]
@Override
public void draw(Shape shape){
GeneralPath path=new GeneralPath();
AffineTransform affineTransform;
path.append(shape.getPathIterator(affineTransform=g2D.getTransform()),false);
g2D.setTransform(new AffineTransform());
g2D.draw(path);
g2D.setTransform(affineTransform);
}
[/highlight]
...transformiert zuerst den Rand, zieht diesen dann mit zB. BasicStroke(1) in Bildschirmkoordinaten nach. Ohne diesen umweg geht es ja anscheinend nicht.

Ob ich das als Graphics2D-Objekt wirklich brauche? Ich überleg's mir nochmal. Vielleicht ist es besser, alles bei einer kleinen statischen Hilfsmethode zu lassen.

Danke an Alle.
 
Zuletzt bearbeitet:

Ebenius

Top Contributor
Wie wäre denn dieser Vorschlag: [HIGHLIGHT=Java]import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;

public class StrokeTransformer implements Stroke {

private final AffineTransform transform;
private final Stroke stroke;
private AffineTransform inverseTransform;

public StrokeTransformer(Stroke stroke, AffineTransform transform)
throws NoninvertibleTransformException {
this(stroke, transform, transform.createInverse());
}

public StrokeTransformer(
Stroke stroke,
AffineTransform transform,
AffineTransform inverseTransform) {
this.stroke = stroke;
this.transform = transform;
this.inverseTransform = inverseTransform;
}

public Shape createStrokedShape(Shape p) {
return inverseTransform.createTransformedShape(stroke
.createStrokedShape(transform.createTransformedShape(p)));
}
}[/HIGHLIGHT]
Ein fertiges Code-Beispiel dazu. Die hellen Pastelltöne sind mit Original-Stroke, die vollfarbigen mit StrokeTransformer: [HIGHLIGHT=Java]final JPanel panel = new JPanel() {

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);

// Insets
final Insets insets = getInsets();
final int l = insets.left;
final int t = insets.top;
final int r = getWidth() - insets.right;
final int b = getHeight() - insets.bottom;

// just need a test rectangle that is always the same according to
// screen transform
Shape line = new Line2D.Double(l, t, r, b);
Shape rect =
new Rectangle2D.Double(l + 20, t + 20, r - l - 40, b - t - 40);
Shape arc =
new Arc2D.Double(l + 60, t + 60, r - l - 120, b - t - 120, 0,
200 * Math.PI, Arc2D.CHORD);

final Graphics2D g2d = (Graphics2D) g.create();
try {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);

// Some transform
final AffineTransform trans = new AffineTransform();
trans.scale(3. / 5, 40);
trans.rotate(Math.PI / 2);
trans.shear(3., 0);
final AffineTransform inverseTrans = trans.createInverse();
g2d.transform(trans);

// shapes in view coords
rect = inverseTrans.createTransformedShape(rect);
line = inverseTrans.createTransformedShape(line);
arc = inverseTrans.createTransformedShape(arc);

// draw light, semi-transparent with original stroke
g2d.setColor(new Color(0x7f, 0x7f, 0xff, 0x7f));
g2d.draw(line);
g2d.setColor(new Color(0xff, 0x7f, 0x7f, 0x7f));
g2d.draw(rect);
g2d.setColor(new Color(0x7f, 0xff, 0x7f, 0x7f));
g2d.draw(arc);

// draw with stroke transformer (1px)
g2d.setStroke(new StrokeTransformer(g2d.getStroke(), trans,
inverseTrans));
g2d.setColor(Color.BLUE);
g2d.draw(line);
g2d.setColor(Color.RED);
g2d.draw(rect);
g2d.setColor(Color.GREEN);
g2d.draw(arc);
} catch (NoninvertibleTransformException ex) {
// in that case we simply don't draw anything, here
} finally {
g2d.dispose();
}
}
};

final JFrame f = new JFrame("Stroke Fun");
f.setContentPane(new JScrollPane(panel));
f.setSize(400, 400);
f.setLocationRelativeTo(null);
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.setVisible(true);[/HIGHLIGHT]
Ich finde diesen Ansatz insofern angenehmer, als dass der Stroke sich um die Strichbreite kümmert (was ja seine Aufgabe ist) und nicht ein Graphics-Objekt. Hilft Dir das?

Ebenius
 

0x7F800000

Top Contributor
Ich finde diesen Ansatz insofern angenehmer, als dass der Stroke sich um die Strichbreite kümmert (was ja seine Aufgabe ist) und nicht ein Graphics-Objekt. Hilft Dir das?
Das ist natürlich vom Stil her wesentlich schöner. Aber dennoch bleibt da bei mir ein ungutes gefühl, wegen:
Form
-> hintransformieren, Umriss erzeugen
<- rücktrasformieren
-> hintransformieren, Umriss zeichnen
das ist schon ein ziemlicher Umweg, da programmiert man für OOP gegen die Machine :roll:

Ich behalte jedenfalls alle Ansätze erstmal im Auge, und schaue erstmal wieviel ich denn wirklich brauche... Vielleicht reicht eine kleine hilfsmethode auch schon...
 

Ebenius

Top Contributor
Du kannst ja mal den Vergleich machen; ich gehe davon aus, dass das gar nicht so teuer wird. Aber ich weiß es nicht.

Wenn ich Zeit hätte, würde ich einfach einen SimpleStroke selber implementieren; aber da geht ja schnell mal ein Tag drauf, wenn man die com.sun-Tools nicht mitbenutzen will. :)

Ebenius
 

0x7F800000

Top Contributor
Du kannst ja mal den Vergleich machen; ich gehe davon aus, dass das gar nicht so teuer wird. Aber ich weiß es nicht.
wahrscheinlich wird das vernachlässigbar, wie so oft. Aber wenn ich irgendsowas schreibe, will ich nun mal später irgendwann nicht feststellen müssen, dass es um 20% langsamer und ruckeliger ist, als es sein könnte, wenn man von anfang an statt einer Hübschen Klasse eine etwas hässlichere Hilfsmethode genommen hätte... Das Problem in diesem konkreten Fall: da lässt sich später ja nicht einfach so die Implementierung austauschen...

Wenn ich Zeit hätte, würde ich einfach einen SimpleStroke selber implementieren; aber da geht ja schnell mal ein Tag drauf, wenn man die com.sun-Tools nicht mitbenutzen will. :)
Simple? Wie willst du das denn simpler hinbekommen, als es jetzt schon ist? Du musst doch am ende irgendwie eine Form erzeugen, die nach der Transformation wie eine 1px Linie aussieht. Da kommst du um das yakshavige hin- und hertransformieren imho nicht herum.
 

Ebenius

Top Contributor
Simple, weil der BasicStroke dashing kann, Miters mit einstellbarem Limit, verschiedene Joins... Soweit würde ich in dieser Implementierung aber wahrscheinlich nicht gehen. Immerhin wird die ganze eigentliche Arbeit des BasicStroke in com.sun-Paketen gemacht also kann man sie nicht benutzen, ohne sich von der JRE-Implementation abhängig zu machen. So ergibt "Simple" nen Sinn, oder?

Ebenius
 

0x7F800000

Top Contributor
Simple, weil der BasicStroke dashing kann, Miters mit einstellbarem Limit, verschiedene Joins... Soweit würde ich in dieser Implementierung aber wahrscheinlich nicht gehen. Immerhin wird die ganze eigentliche Arbeit des BasicStroke in com.sun-Paketen gemacht also kann man sie nicht benutzen, ohne sich von der JRE-Implementation abhängig zu machen. So ergibt "Simple" nen Sinn, oder?
Ja, das schon. Aber von der anzahl der Transformationen wird's nicht besser aussehen, dazu ist die art und weise wie Graphics2D damit umgeht einfach zu unflexibel.
 

Ebenius

Top Contributor
Ja, das schon. Aber von der anzahl der Transformationen wird's nicht besser aussehen, dazu ist die art und weise wie Graphics2D damit umgeht einfach zu unflexibel.
Wenn BasicStroke StrokeSize zweidimensional hätte, wäre doch alles Tuck. So würde ich's dann auch implementieren. Dann hättest Du nicht mehr oder weniger Transformation als sonst auch.

Ebenius
 
Status
Nicht offen für weitere Antworten.

Neue Themen


Oben