Text um vertikale Achsel drehen

Hallo liebe Community!

Ich bin schon seit Stunden vergeblich auf der Suche wie man mithilfe von Java einen Text als Animation drehen kann. (2D)
Der Text soll sich so drehen wie es dieses Video ab 11:54 zeigt.

https://www.youtube.com/watch?v=QK_cnHKN0NA#t=11m53s

Ich habe es schon mit rotate() versucht nur dreht dies den Text dann in die falsche Richtung.
Der Text soll also um die vertikale Achse gedreht werden. Weiß jemand hier wie das funktioniert?

Hoffe ihr könnt mir helfen
Arelnasel

Zeig mal, was Du bisher hast.

bye
TT

Ich habe ja nichts wirkliches. Habe es mit rotate() und shear() versucht. Bringt beides aber nicht die gewünschten Positionen.

        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        
        g2d.shear(0,-0.8);
       
        //AffineTransform orig = g2d.getTransform();
        //g2d.rotate(-0.2);
        
        g2d.setFont(new Font("Arial", Font.BOLD, 26));
        g2d.drawString("Text", 400, 400);
        //g2d.setTransform(orig);
}

Hmja das geht so ohne weiteres erstmal nicht. Das ist ja ein “3D”-Effekt: Der Teil des Textes, der weiter hinten ist, muss ja kleiner gezeichnet werden, damit der Eindruck einer Perspektive entsteht. Wenn das nicht notwendig wäre, wäre ein relativ einfacher “Fake” dieses Drehens möglich: Man könnte den Text horizontal skalieren, von der Breite 1.0 auf 0.0 bis -1.0 und wieder über 0.0 bis 1.0 zurück. Könnte schon ganz OK aussehen, aber wohl nicht 100% überzeugend…

@Marco13 Aber vom skalieren wird der Text nun auch nicht gedreht wie bekommt den das hin?!

Hat jmd. vielleicht sonst noch eine Idee wie man es genauer umsetzen kann?

Naja dafür müsstest du dein Bild, deine Schrift in gewisser Form als 3D Object betrachten und das richtig rendern

Ja, er wird nicht echt gedreht. Aber es könnte fast so aussehen, als würde er gedreht: Von vorne betrachtet und ohne perspektivische Verzerrung auf 2D projiziert sieht so eine Drehung tatsächlich einfach nur so aus, als ob das Objekt schmaler und wieder breiter wird (der Skalierungsfaktor ist einfach cos(winkel)).

Wie gesagt, keine Perspektive, und nicht perfekt, aber… im Vergleich zu dem Aufwand, der damit verbunden wäre, das “richtig” zu machen, doch schon recht nah dran:

package bytewelt;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class Pseudo3DText
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }

    private static void createAndShowGUI()
    {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new GridLayout(1, 1));
        
        Pseudo3DTextPanel p = new Pseudo3DTextPanel();
        startAnimation(p);
        
        frame.getContentPane().add(p);
        frame.setSize(800, 300);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
    
    private static void startAnimation(final Pseudo3DTextPanel p)
    {
        Timer timer = new Timer(40, new ActionListener()
        {
            double angleRad = 0.0;
            
            @Override
            public void actionPerformed(ActionEvent e)
            {
                angleRad += 0.1;
                p.setAngle(angleRad);
            }
        });
        timer.start();
    }
}

class Pseudo3DTextPanel extends JPanel
{
    private double angleRad = 0;
    
    void setAngle(double angleRad)
    {
        this.angleRad = angleRad;
        repaint();
    }
    
    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
        g.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING, 
            RenderingHints.VALUE_ANTIALIAS_ON);
        String string = "Not rotating";
        Font font = g.getFont().deriveFont(100.0f);
        g.setFont(font);

        AffineTransform oldAT = g.getTransform();
        g.translate(400, 100);
        double scaling = Math.cos(angleRad);
        g.scale(scaling,  1.0);
        g.fill(createShape(font, string));
        
        g.setTransform(oldAT);
    }
    
    /**
     * Create a shape for the given string with the given
     * font, centered at the origin.
     * 
     * @param font The font
     * @param string The string
     * @return The shape for the string
     */
    public static Shape createShape(Font font, String string)
    {
        final FontRenderContext fontRenderContext = 
            new FontRenderContext(null, true, true);
        GlyphVector glyphVector = font.createGlyphVector(
            fontRenderContext, string);
        Shape shape = glyphVector.getOutline(0,0);
        Rectangle2D bounds = shape.getBounds2D();
        double cx = bounds.getCenterX();
        double cy = bounds.getCenterY();
        AffineTransform at = 
            AffineTransform.getTranslateInstance(-cx, -cy);
        return at.createTransformedShape(shape);
    }
    
}

(Das ginge auch direkt mit g.drawString, aber wegen einiger “Eigenheiten” beim Font-Rendern würde das dann etwas zitteriger aussehen…)

EDIT: Wobei… wenn man erstmal den Path hat, wäre selbst eine Perspektivische Verzerrung nicht mehr sooo viel aufwändiger…

[QUOTE=Arelnasel]@Marco13 Aber vom skalieren wird der Text nun auch nicht gedreht wie bekommt den das hin?!

Hat jmd. vielleicht sonst noch eine Idee wie man es genauer umsetzen kann?[/QUOTE]

Also ich finde, dass das ne ganz brauchbare Lösung ist, ist halt quasi Parallelprojektion und nicht viel Perspektive.
Um den Eindruck der Rotation zu verstärken, könnt man vielleicht noch die Farbe der Schrift abhängig vom Winkel von hell nach dunkel variieren…

z.B. mal folgendes in das KSKB von Marco in paintComponent eingeflickt, nach Graphics2D g = (Graphics2D)gr;

		int rgb = 2791095;
        int maxrgbr = rgb % 255;
		int maxrgbg = (rgb/255) %255;
		int maxrgbb = (rgb/65025) % 255;
		double h = (1 - Math.abs(Math.sin(angleRad)/2.0));
		maxrgbr = (int) Math.abs((h * maxrgbr) % 255);
		maxrgbg = (int) Math.abs((h * maxrgbg) % 255);
		maxrgbb = (int) Math.abs((h * maxrgbb) % 255);
		g.setPaint(new Color(maxrgbr,maxrgbg,maxrgbb));

Ja, sowas würde sicher schon helfen. Ich hatte auch noch überlegt, wie man das “einfach” “schöner” machen kann, und dachte dann kurz daran, die Buchstaben zu skalieren, aber … das, was man dafür braucht, könnte man dann gleich verwenden, um es richtig zu machen: Man könnte über den Path des Text-Shapes laufen, und die Koordinaten der Punkte verändern. Wie gesagt, nicht sooo kompliziert, aber ein bißchen was müßte man da schon noch drumrumschreiben.

@Marco13 Das reicht für meine Bedürfnisse schon. Hätte nicht gedacht, dass man das mit scale() so gut hinbekommt.

Nur wäre da noch ein Problem. Und zwar möchte ich den Text nicht nur einzeilig haben sondern zweizeilig oder mehr.
Ich habe das ganze über eine TextPane gemacht.


public MyLabel() {
        super();
        this.setEditorKit(new HorizontalArea());
        StyledDocument doc = this.getStyledDocument();
        SimpleAttributeSet center = new SimpleAttributeSet();
        StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER);
        doc.setParagraphAttributes(0, doc.getLength(), center, false);
}


public void paintComponent(Graphics g) {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D) gr;
        g.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        

        g.setFont(font);

        AffineTransform oldAT = g.getTransform();
        g.translate(400, 100);
        double scaling = Math.cos(angleRad);
        g.scale(scaling, 1.0);
        g.fill(createShape(font,text));
        g.setTransform(oldAT);
}

}```

Von einer anderen Klasse habe ich es dann so aufgerufen:
```label= new MyLabel("Langer Text der durch setBounds formatiert werden soll.. z.B. auf eins/zwei/drei/... Reihen ");
        label.setBounds(510,190,190,95);
        add(label);```

Würde ich statt deiner Methode(createShape)  g.drawString(text) aufrufen klappt es so, dass er mir alles richtig formatiert, sodass ich den Text immer neu verändern kann und 
die TextPane sich automatisch anpasst (und es auf mehrere Zeilen anpasst)

Nun hast du aber auch gesagt:

> (Das ginge auch direkt mit g.drawString, aber wegen einiger "Eigenheiten" beim Font-Rendern würde das dann etwas zitteriger aussehen...)

**Ja genau dieses "zittrige" finde ich auch lästig und deswegen frage ich gibt es die Möglichkeit, dass der Text nicht zittrig ist und zugleich auch mehrzeilig dargestellt werden kann?**

Geht doch nichts über eine genaue Beschreibung der Anforderungen von Anfang an :wink:

Den Zeilenumbruch kann man im einfachsten Fall selbst reindengeln. Allerdings kann das natürlich beliebig kompliziert werden, wenn die Zeilenumbrüche automatisch auf Basis des verfügbaren Platzes gefunden werden sollen. Natürlich ist das alles möglich, aber … ab einem bestimmten Punkt sollte man nicht mehr versuchen, diese Funktionalität (die nichts mehr mit „rotierendem Text“ zu tun hat) in dieses Pseudo3DText-Beispiel reinzudengeln, sondern sich ein standalone-Beispiel zu machen, mit einer Methode wie

String[] splitIntoLines(Font font, String string, float availableWidth) { ... }

Ob man das dann in dieser Methode „selbst“ macht, oder eine TextArea verwendet, die einem diese umbrüche schon berechnet, muss man sich dann überlegen.

Wenn es nur darum geht, einen String in mehrere Zeilen aufzuteilen, wenn er explizit angegebene Zeilenumbrüche "
" enthält, wäre das noch einfach. Aber alles darüber hinausgehende sollte man vermutlich getrennt davon besprechen…

import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
 
public class Pseudo3DText
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
 
    private static void createAndShowGUI()
    {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new GridLayout(1, 1));
       
        Pseudo3DTextPanel p = new Pseudo3DTextPanel();
        startAnimation(p);
       
        frame.getContentPane().add(p);
        frame.setSize(800, 300);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
   
    private static void startAnimation(final Pseudo3DTextPanel p)
    {
        Timer timer = new Timer(40, new ActionListener()
        {
            double angleRad = 0.0;
           
            @Override
            public void actionPerformed(ActionEvent e)
            {
                angleRad += 0.1;
                p.setAngle(angleRad);
            }
        });
        timer.start();
    }
}
 
class Pseudo3DTextPanel extends JPanel
{
    private double angleRad = 0;
   
    void setAngle(double angleRad)
    {
        this.angleRad = angleRad;
        repaint();
    }
   
    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
        g.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        String string = "Not rotating
Multiline";
        Font font = g.getFont().deriveFont(100.0f);
        g.setFont(font);
 
        AffineTransform oldAT = g.getTransform();
        g.translate(400, 100);
        double scaling = Math.cos(angleRad);
        g.scale(scaling,  1.0);
        g.fill(createMultilineShape(font, string));
       
        g.setTransform(oldAT);
    }
    
    public static Shape createMultilineShape(Font font, String string)
    {
        final FontRenderContext fontRenderContext =
            new FontRenderContext(null, true, true);
        Path2D path = new Path2D.Double();
        String lines[] = string.split("
");
        double offsetY = 0;
        for (String line : lines)
        {
            GlyphVector glyphVector = font.createGlyphVector(
                fontRenderContext, line);
            Shape lineShape = glyphVector.getOutline(0,0);
            AffineTransform at = 
                AffineTransform.getTranslateInstance(0, offsetY);
            path.append(lineShape.getPathIterator(at), false);
            offsetY += lineShape.getBounds().height;
        }
        return center(path);
    }
   
    /**
     * Create a shape for the given string with the given
     * font, centered at the origin.
     *
     * @param font The font
     * @param string The string
     * @return The shape for the string
     */
    public static Shape createShape(Font font, String string)
    {
        final FontRenderContext fontRenderContext =
            new FontRenderContext(null, true, true);
        GlyphVector glyphVector = font.createGlyphVector(
            fontRenderContext, string);
        Shape shape = glyphVector.getOutline(0,0);
        return center(shape);
    }
    
    private static Shape center(Shape shape)
    {
        Rectangle2D bounds = shape.getBounds2D();
        double cx = bounds.getCenterX();
        double cy = bounds.getCenterY();
        AffineTransform at =
            AffineTransform.getTranslateInstance(-cx, -cy);
        return at.createTransformedShape(shape);
    }
    
   
}