Java Image durch Verzerrung räumlich erscheinen lassen

Hallo Leute.

Ich will ein kleines “Pseudo 3D” programm schreiben, und brauche dafür die möglich keit
ein bild durhc verzerrung dreidimensional wirken zu lassen.
Geht das mit java grundsachen, oder brauch ich da sowas wie jai?
Und wenn ja, (ich benutze nicht gerne andere libraries…) wo könnte ich mich informieren und
mal versuchen einen eigenen algorithmus dafür aufzubauen, oder ist das viel zu schwer?

Vielen Dank!

Zur verdeutlichung:

Vielleicht ist dir mit einer Scherung oder einer ähnlichen Transformation geholfen?
http://www.java2s.com/Tutorial/Java/0261__2D-Graphics/ShearingaDrawnImage.htm

Gruß

Tja, das ist das Problem: Das Ding heißt AffineTransform. Was vorher parallel war, ist auch nachher parallel. Und für diesen Pseudo-3D-Effekt will man ja gerade, dass die obere und untere Kante NICHT mehr parallel sind.

Dafür gibt es nichts vorgefertigtes in Swing/Java2D. Das geht nämlich schon fast in die Richtung von “Texture Mapping”, und mit einem Schlag ergeben sich viele Freiheitsgrade und Fragen: Wie soll der “Winkel” sein, mit dem das ganze einklappt? Für die obere und untere Kante gleich? Soll das ganze auch noch perspektivisch “richtig” sein, d.h. nach hinten hin dichter gesamplet werden?

An anderer Stelle hatte mal jemand einen “Coverflow” in reinem Swing gemacht, mit genau so einer Verzerrung. Die Methode, die er dafür verwendet hat, war auch die einzige, die mir damals spontan eingefallen wäre, von der ich aber gedacht hätte, dass sie untragbar ineffizient wäre - aber wenn man es einigermaßen geschickt macht, geht es von der Performance her sogar: Das Bild wird in 1 Pixel breiten Streifen gezeichnet, die nach hinten hin kürzer werden.

Wenn man nicht sooo viel Wert auf das “Pseudo”-sein legt, kann man auch einfach LWJGL verwenden, und damit 100000 solcher (völlig beliebig (!)) “verzerrter Bilder” mit 60 fps über den Bildschirm tanzen lassen …

Also er vernünftigste Tipp bisher war der mit LWJGL (bzw JOGL usw). Richtig funktionieren tur es natürlich nur mit einer 3D-Matrix. Und da man es ohnehin nur Pixel für Pixel bewerkstelligen kann (auch mit Marcos vereinfachtem “Vorschlag”), kann man die 3D-Matrix beliebig einstellen (Drehung, Translation, usw) und dann für jeden Pixel eine 2D-Transformation vornehmen, wobei jeder Pixel eine 0 als Z-Achse bekommt.

	double xx, xy, xz;
	double yx, yy, yz;
	double zx, zy, zz;

	Matrix3d() {
		xx = 1.0f;
		yy = 1.0f;
		zz = 1.0f;
	}

	void scale(double f) {
		scale(f, f, f);
	}

	void scale(double xf, double yf, double zf) {
		xx *= xf;
		xy *= xf;
		xz *= xf;
		yx *= yf;
		yy *= yf;
		yz *= yf;
		zx *= zf;
		zy *= zf;
		zz *= zf;
	}

	void yrot(double theta) {
		double ct = Math.cos(theta);
		double st = Math.sin(theta);

		double Nxx = xx * ct + zx * st;
		double Nxy = xy * ct + zy * st;
		double Nxz = xz * ct + zz * st;

		double Nzx = zx * ct - xx * st;
		double Nzy = zy * ct - xy * st;
		double Nzz = zz * ct - xz * st;

		xx = Nxx;
		xy = Nxy;
		xz = Nxz;
		zx = Nzx;
		zy = Nzy;
		zz = Nzz;
	}

	void xrot(double theta) {
		double ct = Math.cos(theta);
		double st = Math.sin(theta);

		double Nyx = yx * ct + zx * st;
		double Nyy = yy * ct + zy * st;
		double Nyz = yz * ct + zz * st;

		double Nzx = zx * ct - yx * st;
		double Nzy = zy * ct - yy * st;
		double Nzz = zz * ct - yz * st;

		yx = Nyx;
		yy = Nyy;
		yz = Nyz;
		zx = Nzx;
		zy = Nzy;
		zz = Nzz;
	}

	void zrot(double theta) {
		double ct = Math.cos(theta);
		double st = Math.sin(theta);

		double Nyx = yx * ct + xx * st;
		double Nyy = yy * ct + xy * st;
		double Nyz = yz * ct + xz * st;

		double Nxx = xx * ct - yx * st;
		double Nxy = xy * ct - yy * st;
		double Nxz = xz * ct - yz * st;

		yx = Nyx;
		yy = Nyy;
		yz = Nyz;
		xx = Nxx;
		xy = Nxy;
		xz = Nxz;
	}

	void mult(Matrix3d rhs) {
		double lxx = xx * rhs.xx + yx * rhs.xy + zx * rhs.xz;
		double lxy = xy * rhs.xx + yy * rhs.xy + zy * rhs.xz;
		double lxz = xz * rhs.xx + yz * rhs.xy + zz * rhs.xz;

		double lyx = xx * rhs.yx + yx * rhs.yy + zx * rhs.yz;
		double lyy = xy * rhs.yx + yy * rhs.yy + zy * rhs.yz;
		double lyz = xz * rhs.yx + yz * rhs.yy + zz * rhs.yz;

		double lzx = xx * rhs.zx + yx * rhs.zy + zx * rhs.zz;
		double lzy = xy * rhs.zx + yy * rhs.zy + zy * rhs.zz;
		double lzz = xz * rhs.zx + yz * rhs.zy + zz * rhs.zz;

		xx = lxx;
		xy = lxy;
		xz = lxz;

		yx = lyx;
		yy = lyy;
		yz = lyz;

		zx = lzx;
		zy = lzy;
		zz = lzz;
	}

	void unit() {
		xx = 1;
		xy = 0;
		xz = 0;
		yx = 0;
		yy = 1;
		yz = 0;
		zx = 0;
		zy = 0;
		zz = 1;
	}

	void transform(Point3d pIn, Point3d pOut) {
		pOut.x = pIn.x * xx + pIn.y * xy + pIn.z * xz;
		pOut.y = pIn.x * yx + pIn.y * yy + pIn.z * yz;
		pOut.z = pIn.x * zx + pIn.y * zy + pIn.z * zz;
	}

	@Override
	public String toString() {
		return ("[" + xx + "," + xy + "," + xz + ";" + yx + "," + yy + "," + yz
				+ ";" + zx + "," + zy + "," + zz + "]");
	}
}```Diese Matrix baut auf der Matrix aus der WireFrame-Demo des JDKs auf, dewegen fehlt dort auch der Translations-Anteil. Im Anhang gibts dann noch ein Demo mit 3D-Punktwolken, welches man durchtracen kann, damit man die Funktion dieser Matrix - insbesondere ihre "transform"-Methode - versteht. Im 3D-Modus kann man die Ansicht im übrigen mit der Maus verändern.

Ok danke euch erstmal. Genau das ist das problem marco, deshalb geht es nicht mit shear. Ich werde mich mal nach texture mapping umsehen, kann ja nicht schaden.
Erstmal wuerde ich fuer sowas jogl vermeiden, denn es sind ohnehin nur zwei bilder - eins links eins rechts.
Aber wie macht jogl das denn, ist das bekannt.?

[QUOTE=mymaksimus]Ok danke euch erstmal. Genau das ist das problem marco, deshalb geht es nicht mit shear. Ich werde mich mal nach texture mapping umsehen, kann ja nicht schaden.
Erstmal wuerde ich fuer sowas jogl vermeiden, denn es sind ohnehin nur zwei bilder - eins links eins rechts.
Aber wie macht jogl das denn, ist das bekannt.?[/QUOTE]Sagte ich oben schon… Über eine 3D-Matrix und 2D-Projektion, nur halt auf Hardwarebasis.

Also so mit java nicht realisierbar?
(also wegen hardware…)

Jup JOGL ist ein OpenGL Wrapper der es in Java ermorglicht, hardwarenahe Operation durchzufuehren. OpenGL arbeitet halt auf nativer Ebene und kann nur dadurch ebeb das ermoeglichen. Zum Beispiel waere dann BufferFlipping und son Kram damit recht einfach zu erstellen und man hat halt eine super Performance damit.

Das ist mit Java genauso viel oder wenig realisierbar wie mit C++ - zumindest weiß ehrlich gesagt auch nicht, ob und wie man mit C++ OpenGL-Befehle absetzen kann, OHNE OpenGL als Bibliothek zu verwenden (ja, Inline Assembler FTW, ich meinte was realistisches ;)).

Im endeffekt macht ja die OpenGL Bibliothek nichts anderes ;D

[QUOTE=groggy]Im endeffekt macht ja die OpenGL Bibliothek nichts anderes ;D[/QUOTE]Was anderes als LWJGL oder JOGL? Natürlich machen die nichts anderes, es sind Wrapper… :wink:

Aber es geht trotzdem auch ohne Hardwareunterstützung. Man kann die einzelnen Pixel einer 2D-Grafik als Ebene mit 0 Tiefe betrachten. So kann man die Ebene per View-Matrix im Raum verschieben, drehen und skalieren und per Projektionsmatrix darstellen… (anders machts OpenGL oder JOGL, LWJGL usw auch nicht ;)). Man benutzt auf SW-Basis dazu aber keine Hardware-Lib, sondern eher APIs wie VecMath (mit Point3D, Matrix4D usw).

Im Anhang mal ein simples Beispiel für Software-Texturing… Das es nicht ganz trivial ist, sieht man z.B. daran, dass manchmal der ein oder andere Punkt nicht gezeichnet wird (Moiré-Effekte im Bild). Darüber hinaus, fehlt da immer noch die Projektionsmatrix. Ohne diese wird dafür zwangsläufig eine Einheitsmatrix (Orthogonal) verwendet, mit welcher die hier gewünschten Effekte nicht erzielt werden können.

Bin gerade nochmal über diesen Thread gestolpert (eigentlich such’ ich aber einen ganz anderen :rolleyes: ) nur der Vollständigkeit halber:

(Basierend auf der Methode, die in http://math.stackexchange.com/a/339033/133498 beschrieben ist)


import java.awt.Color;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

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

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        BufferedImage image = null;
        try
        {
            image = ImageIO.read(new File("lena512color.png"));
        }
        catch (IOException e)
        {
            e.printStackTrace();
            return;
        }
        f.getContentPane().setLayout(new GridLayout(1,2));
        f.getContentPane().add(new JLabel(new ImageIcon(image)));
        f.getContentPane().add(new Pseudo3DImagePanel(image));
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}

class Pseudo3DImagePanel extends JPanel
    implements MouseListener, MouseMotionListener
{
    private final BufferedImage inputImage;
    private final Point2D p0;
    private final Point2D p1;
    private final Point2D p2;
    private final Point2D p3;
    private Point2D draggedPoint;
    
    Pseudo3DImagePanel(BufferedImage inputImage)
    {
        this.inputImage = inputImage;
        this.p0 = new Point2D.Double(30,20);
        this.p1 = new Point2D.Double(50,400);
        this.p2 = new Point2D.Double(450,300);
        this.p3 = new Point2D.Double(430,100);
        addMouseListener(this);
        addMouseMotionListener(this);
    }
    
    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        
        BufferedImage image = Pseudo3D.computeImage(inputImage, p0, p1, p2, p3);
        g.drawImage(image, 0, 0, null);
        
        int r = 8;
        g.setColor(Color.RED);
        g.fillOval((int)p0.getX()-r, (int)p0.getY()-r, r+r, r+r);
        g.fillOval((int)p1.getX()-r, (int)p1.getY()-r, r+r, r+r);
        g.fillOval((int)p2.getX()-r, (int)p2.getY()-r, r+r, r+r);
        g.fillOval((int)p3.getX()-r, (int)p3.getY()-r, r+r, r+r);
    }
    
    

    @Override
    public void mousePressed(MouseEvent e)
    {
        Point p = e.getPoint();
        int r = 8;
        if (p.distance(p0) < r) draggedPoint = p0; 
        if (p.distance(p1) < r) draggedPoint = p1; 
        if (p.distance(p2) < r) draggedPoint = p2; 
        if (p.distance(p3) < r) draggedPoint = p3; 
    }
    
    @Override
    public void mouseDragged(MouseEvent e)
    {
        if (draggedPoint != null)
        {
            draggedPoint.setLocation(e.getX(), e.getY());
            repaint();
        }
    }

    @Override
    public void mouseReleased(MouseEvent e)
    {
        draggedPoint = null;
    }
    
    @Override
    public void mouseMoved(MouseEvent e) {}
    @Override
    public void mouseClicked(MouseEvent e) {}
    @Override
    public void mouseEntered(MouseEvent e) {}
    @Override
    public void mouseExited(MouseEvent e) {}
}



class Pseudo3D
{
    static BufferedImage computeImage(
        BufferedImage image,
        Point2D p0, Point2D p1, Point2D p2, Point2D p3)
    {
        int w = image.getWidth();
        int h = image.getHeight();
        
        BufferedImage result =
            new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

        Point2D ip0 = new Point2D.Double(0,0);
        Point2D ip1 = new Point2D.Double(0,h);
        Point2D ip2 = new Point2D.Double(w,h);
        Point2D ip3 = new Point2D.Double(w,0);
        
        Matrix3D m = computeProjectionMatrix(
            new Point2D[] {  p0,  p1,  p2,  p3 },
            new Point2D[] { ip0, ip1, ip2, ip3 });
        Matrix3D mInv = new Matrix3D(m);
        mInv.invert();
        
        for (int y = 0; y < h; y++)
        {
            for (int x = 0; x < w; x++)
            {
                Point2D p = new Point2D.Double(x,y);
                mInv.transform(p);
                int ix = (int)p.getX();
                int iy = (int)p.getY();
                if (ix >= 0 && ix < w && iy >= 0 && iy < h)
                {
                    int rgb = image.getRGB(ix, iy);
                    result.setRGB(x, y, rgb);
                }
            }
        }
        return result;
    }

    // From http://math.stackexchange.com/questions/296794
    private static Matrix3D computeProjectionMatrix(Point2D p0[], Point2D p1[])
    {
        Matrix3D m0 = computeProjectionMatrix(p0);
        Matrix3D m1 = computeProjectionMatrix(p1);
        m1.invert();
        m0.mul(m1);
        return m0;
    }
    
    // From http://math.stackexchange.com/questions/296794
    private static Matrix3D computeProjectionMatrix(Point2D p[])
    {
        Matrix3D m = new Matrix3D(
            p[0].getX(), p[1].getX(), p[2].getX(), 
            p[0].getY(), p[1].getY(), p[2].getY(), 
            1, 1, 1);
        Point3D p3 = new Point3D(p[3].getX(), p[3].getY(), 1);
        Matrix3D mInv = new Matrix3D(m);
        mInv.invert();
        mInv.transform(p3);
        m.m00 *= p3.x;
        m.m01 *= p3.y;
        m.m02 *= p3.z;
        m.m10 *= p3.x;
        m.m11 *= p3.y;
        m.m12 *= p3.z;
        m.m20 *= p3.x;
        m.m21 *= p3.y;
        m.m22 *= p3.z;
        return m;
    }

    private static class Point3D
    {
        double x;
        double y;
        double z;
        
        Point3D(double x, double y, double z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }
    }

    private static class Matrix3D
    {
        double m00;
        double m01;
        double m02;
        double m10;
        double m11;
        double m12;
        double m20;
        double m21;
        double m22;

        Matrix3D(
            double m00, double m01, double m02, 
            double m10, double m11, double m12, 
            double m20, double m21, double m22)
        {
            this.m00 = m00;
            this.m01 = m01;
            this.m02 = m02;
            this.m10 = m10;
            this.m11 = m11;
            this.m12 = m12;
            this.m20 = m20;
            this.m21 = m21;
            this.m22 = m22;
        }

        Matrix3D(Matrix3D m)
        {
            this.m00 = m.m00;
            this.m01 = m.m01;
            this.m02 = m.m02;
            this.m10 = m.m10;
            this.m11 = m.m11;
            this.m12 = m.m12;
            this.m20 = m.m20;
            this.m21 = m.m21;
            this.m22 = m.m22;
        }

        // From http://www.dr-lex.be/random/matrix_inv.html
        void invert()
        {
            double invDet = 1.0 / determinant();
            double nm00 = m22 * m11 - m21 * m12;
            double nm01 = -(m22 * m01 - m21 * m02);
            double nm02 = m12 * m01 - m11 * m02;
            double nm10 = -(m22 * m10 - m20 * m12);
            double nm11 = m22 * m00 - m20 * m02;
            double nm12 = -(m12 * m00 - m10 * m02);
            double nm20 = m21 * m10 - m20 * m11;
            double nm21 = -(m21 * m00 - m20 * m01);
            double nm22 = m11 * m00 - m10 * m01;
            m00 = nm00 * invDet;
            m01 = nm01 * invDet;
            m02 = nm02 * invDet;
            m10 = nm10 * invDet;
            m11 = nm11 * invDet;
            m12 = nm12 * invDet;
            m20 = nm20 * invDet;
            m21 = nm21 * invDet;
            m22 = nm22 * invDet;
        }

        // From http://www.dr-lex.be/random/matrix_inv.html
        double determinant()
        {
            return 
                m00 * (m11 * m22 - m12 * m21) + 
                m01 * (m12 * m20 - m10 * m22) +
                m02 * (m10 * m21 - m11 * m20);
        }

        final void mul(double factor)
        {
            m00 *= factor;
            m01 *= factor;
            m02 *= factor;

            m10 *= factor;
            m11 *= factor;
            m12 *= factor;

            m20 *= factor;
            m21 *= factor;
            m22 *= factor;
        }

        void transform(Point3D p) 
        {
            double x = m00 * p.x + m01 * p.y + m02 * p.z;
            double y = m10 * p.x + m11 * p.y + m12 * p.z;
            double z = m20 * p.x + m21 * p.y + m22 * p.z;
            p.x = x;
            p.y = y;
            p.z = z;
        }
        
        void transform(Point2D pp) 
        {
            Point3D p = new Point3D(pp.getX(), pp.getY(), 1.0);
            transform(p);
            pp.setLocation(p.x / p.z, p.y / p.z);
        }
        
        void mul(Matrix3D m)
        {
            double nm00 = m00 * m.m00 + m01 * m.m10 + m02 * m.m20;
            double nm01 = m00 * m.m01 + m01 * m.m11 + m02 * m.m21;
            double nm02 = m00 * m.m02 + m01 * m.m12 + m02 * m.m22;

            double nm10 = m10 * m.m00 + m11 * m.m10 + m12 * m.m20;
            double nm11 = m10 * m.m01 + m11 * m.m11 + m12 * m.m21;
            double nm12 = m10 * m.m02 + m11 * m.m12 + m12 * m.m22;

            double nm20 = m20 * m.m00 + m21 * m.m10 + m22 * m.m20;
            double nm21 = m20 * m.m01 + m21 * m.m11 + m22 * m.m21;
            double nm22 = m20 * m.m02 + m21 * m.m12 + m22 * m.m22;

            m00 = nm00;
            m01 = nm01;
            m02 = nm02;
            m10 = nm10;
            m11 = nm11;
            m12 = nm12;
            m20 = nm20;
            m21 = nm21;
            m22 = nm22;
        }
    }
    
}