# Vektor skalieren



## Schuriko (11. Mrz 2020)

Ich bin gerade dabei ein kleines Grafikprogramm zu schreiben. Hierbei habe ich eine Liste von Punkten(x, y)
Um allen Punkten befindet sich eine Selektionbox mit den acht Buttons: links/oben, links/mittig, links/unten, mittig/oben, mittig/unten, rechts/oben, rechts/mittig und rechts/unten.
Wie müsste ich die Punkte für die Skalierung für die Punkte berechnen, wenn ich z.B. an einem der Buttons ziehe? .


----------



## mihe7 (11. Mrz 2020)

Schuriko hat gesagt.:


> Wie müsste ich die Punkte für die Skalierung für die Punkte berechnen, wenn ich z.B. an einem der Buttons ziehe? .


?!?


----------



## Xyz1 (11. Mrz 2020)

Zwei Herangehensweisen (insofern Du das überhaupt so meinst):

```
void normieren(MathContext mc, float... fs) {
		float betrag = 0;
		for (float f : fs) {
			betrag += f * f;
		}
		betrag = 1f / (float) Math.sqrt(betrag);
		for (int i = 0; i < fs.length; i++) {
			fs[i] *= betrag;
			if (mc != null)
				fs[i] = BigDecimal.valueOf(fs[i]).round(mc).floatValue();
		}
	}

	float[] normieren_(MathContext mc, float... fs) {
		float betrag = 0;
		for (float f : fs) {
			betrag += f * f;
		}
		betrag = 1f / (float) Math.sqrt(betrag);
		float[] fs2 = new float[fs.length];
		for (int i = 0; i < fs2.length; i++) {
			fs2[i] = betrag * fs[i];
			if (mc != null)
				fs2[i] = BigDecimal.valueOf(fs2[i]).round(mc).floatValue();
		}
		return fs2;
	}

	public static void main(String[] args) {
		// MathContext mc = MathContext.DECIMAL32;
		MathContext mc = new MathContext(6, RoundingMode.HALF_EVEN);
		float[] f = { 1, 2, 3 };
		new Test2().normieren(mc, f);
		System.out.println(Arrays.toString(f));
		System.out.println(Arrays.toString(new Test2().normieren_(mc, f)));
	}
```


----------



## Schuriko (12. Mrz 2020)

mihe7 hat gesagt.:


> ?!?


Was ich meine ist: Ich hab mal eine kleine Zeichnung angefertigt:

Die roten Punkte geben die jeweiligen Punkte der Zeichnung an. Dieses sind Vektor-Punkte (x, y). Der orangene Rahmen soll den Markierungsrahmen symboliesieren mit den grünen Button, wodurch der Rahmen verändert (entsprechend der Richtung - lila) werden kann .
Jetzt suche ich einen Algorithmus, der je nachdem welchen Richtung ich einen bestimmten Button bewege die Punkte neu berechnet.
Ihr kennt es auch aus etlichen Vektorprogrammen (z.B. OpenOffice Draw, mit dem die Skizze erstellt wurde).


----------



## kneitzel (12. Mrz 2020)

Wenn Du dir einen Punkt 0,0 an eine Ecke legst und alle Punkte relativ dazu hast, dann hast Du doch für x und y einen Faktor. Und dann kannst Du den Faktor verwenden.

Also z.B. legen wir den "Nullpunkt" in die Ecke unten links. (Du betrachtest also dann nur den Ausschnitt, den du skalieren möchtest, unabhängig von der Position). Dann wird der Punkt (0|0) zu (0|1), der Punkt (8|-1) zu (8|0) u.s.w.

Bei einem Faktor von 3 in jede Richtung hast Du dann nach dem Skallieren: (0|1) => (0|3) und aus (8|0) => (24|0)
(Das Bild ist halt jetzt 3 Mal so groß., Breite statt 8 ist 24 und Höhe statt 4 ist 12!

Das neue Bild musst Du dann natürlich an die richtige Stelle schieben. Je nach verwendetem Pfeil weisst Du ja, welche Ecke fest ist. Wenn Du also die Ecke oben rechts verwendet hast, dann ist die Ecke unten links bei 0|-1 fest. Damit wird dann aus dem Punkt (0|3) => (0|2) und aus (24|0) => (24|-1)

War es aber die Ecke unten, dann bleibt die Ecke oben rechts bei (8|3) fest. Die Ecke unten links ist bei dem größeren, neuen Bild nun bei (-16|-6)
Somit wird aus (0|3) -> (-16|-3) und aus (24|0) -> (8|-6)

Du hast also erst ein Verschieben, so dass du das Bild an dem "Nullpunkt" hast, dann die Skalierung mit einem Faktor um es dann erneut zu verschieben. Wobei die zweite Verschiebung halt anders sein kann als die vorherige.


----------



## Xyz1 (12. Mrz 2020)

Ich habe das mal nachgezeichnet... Nach einiger Zeit treten da Rundungsungenauigkeiten auf und ich weiß noch nicht, warum und wieso:

```
public static void main(String[] args) {
		final ArrayList<Point> points = new ArrayList<Point>();
		points.add(new Point(100, 250));
		points.add(new Point(400, 100));
		points.add(new Point(600, 250));
		points.add(new Point(400, 250));
		points.add(new Point(200, 325));
		points.add(new Point(600, 400));
		JFrame f = new JFrame();
		final Canvas c = new Canvas() {
			private static final long serialVersionUID = 1L;
			@Override
			public void paint(Graphics g) {
				super.paint(g);
				g.clearRect(0, 0, this.getWidth(), this.getHeight());
				for (Point p : points) {
					g.drawOval(p.x, p.y, 10, 10);
				}
			}
		};
		c.addKeyListener(new KeyAdapter() {
			@Override
			public void keyTyped(KeyEvent e) {
				int w_max = 0;
				int w_min = 1000;
				int h_max = 0;
				int h_min = 1000;
				for (Point p : points) {
					if (p.x > w_max)
						w_max = p.x;
					if (p.y > h_max)
						h_max = p.y;
					if (p.x < w_min)
						w_min = p.x;
					if (p.y < h_min)
						h_min = p.y;
				}
				int w = (w_max - w_min) / 2;
				int h = (h_max - h_min) / 2;
				switch (e.getKeyChar()) {
				case 'w':
					for (Point p : points) {
						p.y = (int) Math.round((p.y - h) * 1.25) + h;
					}
					break;
				case 'a':
					for (Point p : points) {
						p.x = (int) Math.round((p.x - w) * 0.8) + w;
					}
					break;
				case 's':
					for (Point p : points) {
						p.y = (int) Math.round((p.y - h) * 0.8) + h;
					}
					break;
				case 'd':
					for (Point p : points) {
						p.x = (int) Math.round((p.x - w) * 1.25) + w;
					}
					break;
				default:
					break;
				}
				c.repaint();
			}

		});
		f.add(c);
		f.setSize(800, 800);
		f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		f.setVisible(true);
	}
```

Weiß jemand warum?


----------



## kneitzel (12. Mrz 2020)

Ich sehe zwei Ungenauigkeiten:
a) Die Mitte, die du zum skalieren nimmst, ist nicht immer genau die Mitte. Dadurch verschiebt es sich etwas.
b) Integer mit 0.8 oder 1.25 multipliziert wird auch immer leichte Ungenauigkeiten mit sich bringen mit der Zeit

Damit sind das zwei Dinge, die leichte Ungenauigkeiten mit sich bringen und so Ungenauigkeiten verstärken sich auch gerne mit der Zeit.

Wenn man nicht die Mitte nimmt sondern einen klaren Rand, dann hat man a) nicht mehr als Ungenauigkeit. Wenn man b umgehen möchte, dann gibt es zwei Möglichkeiten:
1.) Man merkt sich immer das Original und skaliert dies. Damit potenzieren sich Ungenauigkeiten nicht.
2.) Man speichert Werte mit einer größeren Genauigkeit. Also die Koordinaten könnte man statt als int auch als double speichern und nur zur Anzeige in einen int wandeln.

Und die Anforderung nach #4 verstehe ich so, dass man halt an einer Stelle anfängt zu skalieren. Es bleibt also eine Ecke des Objektes immer gleich. Einfach nur die Mitte zu nehmen und da zu skalieren ist also noch nicht die ganze Anforderung.


----------



## Xyz1 (12. Mrz 2020)

Ok, Danke für die Tipps. Ich werde das nachher mal versuchen, diese zu berücksichtigen und das so umzusetzen


----------



## Xyz1 (12. Mrz 2020)

@JustNobody Hättest du noch eine Idee, wie man "ungerade" Punkte (siehe ???) komplett vermeiden kann?

```
public static void main(String[] args) {
        final ArrayList<double[]> points = new ArrayList<double[]>();
        points.add(new double[] { 100, 250 });
        points.add(new double[] { 400, 100 });
        points.add(new double[] { 600, 250 });
        points.add(new double[] { 400, 250 });
        points.add(new double[] { 200, 325 });
        points.add(new double[] { 600, 400 });
        JFrame f = new JFrame();
        final Canvas c = new Canvas() {
            private static final long serialVersionUID = 1L;

            @Override
            public void paint(Graphics g) {
                super.paint(g);
                g.clearRect(0, 0, this.getWidth(), this.getHeight());
                for (double[] p : points) {
                    g.drawOval((int) p[0], (int) p[1], 10, 10);
                    g.drawString(Arrays.toString(p), (int) p[0] + 10, (int) p[1] + 25);
                }
            }
        };
        c.addKeyListener(new KeyAdapter() {
            @Override
            public void keyTyped(KeyEvent e) {
                double w_max = 800;
                double w_min = 0;
                double h_max = 800;
                double h_min = 0;
//                for (double[] p : points) {
//                    if (p[0] > w_max)
//                        w_max = p[0];
//                    if (p[1] > h_max)
//                        h_max = p[1];
//                    if (p[0] < w_min)
//                        w_min = p[0];
//                    if (p[1] < h_min)
//                        h_min = p[1];
//                }
                double w = (w_max - w_min) / 2.0;
                double h = (h_max - h_min) / 2.0;
                switch (e.getKeyChar()) {
                case 'w':
                    for (double[] p : points) {
                        p[1] = (p[1] - h) * 1.25 + h;
                    }
                    break;
                case 'a':
                    for (double[] p : points) {
                        p[0] = (p[0] - w) * 0.8 + w;
                    }
                    break;
                case 's':
                    for (double[] p : points) {
                        p[1] = (p[1] - h) * 0.8 + h;
                    }
                    break;
                case 'd':
                    for (double[] p : points) {
                        p[0] = (p[0] - w) * 1.25 + w;
                    }
                    break;
                default:
                    break;
                }
                // ???
                for (double[] p : points) {
                    p[0] = new BigDecimal(p[0]).round(MathContext.DECIMAL32).doubleValue();
                    p[1] = new BigDecimal(p[1]).round(MathContext.DECIMAL32).doubleValue();
                }
                // ???
                c.repaint();
            }

        });
        f.add(c);
        f.setSize(800, 800);
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.setVisible(true);
    }
```


----------



## kneitzel (12. Mrz 2020)

Das dürfte bezüglich Genauigkeit schon deutlich besser sein. Und Du rechnest ja auch dein h und w in double aus, d.h. Du hast auch bezüglich diesem Punkt die Genauigkeit erhöht.

Wie ist denn das Ergebnis? Das sollte dann doch jetzt deutlich genauer sein, oder siehst Du bei mehreren Transformationen wieder mit der Zeit eine Verzerrung?


----------



## Xyz1 (12. Mrz 2020)

Nein, aber die theoretische Möglichkeit bestünde ja, dass sich das "aufsummiert" und Punkte dann falsch dargestellt würden.


----------



## kneitzel (12. Mrz 2020)

Ja, das ist richtig. Aber Du hast jetzt eine sehr hohe Genauigkeit. Da wirst Du so schnell keine Ungenauigkeit von über einem Pixel bekommen. (Ein Pixel ist einfach - da reicht ja schon eine minimale Abweichung nach unten, so dass der Wert z.b. von x,000... auf (x-1),999... wechselt. Das könnte man noch optimieren, indem man vor der Umwandlung in int noch 0.5f addiert. 

Aber Abweichung von einem Pixel? Das ist in der Regel für den Benutzer nicht bemerkbar.


----------



## Xyz1 (12. Mrz 2020)

Dennoch, auch wenn das praktisch nicht bemerkbar is... und erst bei 1 Mio. Iterationen vielleicht in Erscheinung tritt... Wie kann ich festlegen, wann ich wie runden muss? 1 kann ein valider Wert sein, ebenso 0.95, ebenso 0.995, ebenso 0.9995 usw. ... 

Aber allgemein, Thema gelöst, denke ich


----------



## kneitzel (12. Mrz 2020)

Das ist die zweite Möglichkeit (bzw in #7 das, was ich unter 1) als Möglichkeit genannt habe): Du merkst Dir immer das Original und die aktuelle Skalierung. Dann veränderst Du die Skalierung und berechnest die anzuzeigenden Punkte. Aber die original-Punkte bleiben immer erhalten.
Dadurch hast du bei jeder Berechnung, dass das Bild nicht verzerrt. Du hast evtl. eine kleine Abweichung bei der Skalierung, aber die ist dann immer für alle Punkte gleich.


----------



## Xyz1 (14. Mrz 2020)

Naja @JustNobody durch wiederholtes Drücken von "a" und dem "horizontalen Minimieren oder Verzerren" erreichen die Punkte irgendwann den "Nullpunkt" - und 0 * a ist bekanntlich wieder 0 
Also ist das doch ein diskussionsfähiges Problem.


----------



## kneitzel (14. Mrz 2020)

Ok, den Fall hatte ich nicht bedacht. Da macht es ggf. Sinn, sowas wie ein Minimum und Maximum hinzu zufügen. Also wenn w bzw h einen gewissen Bereich verlassen, dann wird nicht weiter vergrößert oder verkleinert. Also bei kleiner 3 oder  2 hat man evtl. noch genug Genauigkeit es wieder zu vergrößern.... und beim Vergrößern muss man überlegen, was man da als Kriterium wählen möchte .....


----------

