Farbsuche in Palette (nearest Color Index)

Hallo,

ich verfolge das Ganze hier schon eine Weile, kann nicht wirklich neue Ideen beisteuern, aber ich habe so den Eindruck, dass hier ein bisschen aneinander vorbeigeredet wird.

Wenn ich Amunras Vorschlag und SlaterBs Interpretation und Umsetzung betrachte und selbst richtig verstanden habe, dann ist die Idee folgende:

Die rgb-Farben mit Werten von (0,0,0) bis (255,255,255) bilden einen 3D-Würfel mit Seitenlänge 256. Dieser Würfel wird jetzt in kleinere, gleichgroße Würfel mit Seitenlänge r unterteilt, hier im Beispiel r=16.
Anschließend wird für jeden Würfel ermittelt, welche Farben der Farbpalette darin liegen.

Bei einer gegebenen Farbe wird nun zuerst geguckt, in welchem Würfel sie liegt. Anschließend werden alle Farben der Palette, die in diesem Würfel liegen, mit der gegebenen Farbe verglichen. Fertig. (Bis auf den Fall, dass auch Farben in den angrenzenden Würfeln in Frage kommen.)

@Spacerat
Bei dir habe ich irgendwie zwei Varianten herausgelesen:

  1. Du willst alle Farben der Palette in einer Sphäre nach dem Abstand zum Mittelpunkt sortieren und dann entweder von außen oder von innen durchsuchen (Je nach dem). Damit vergleichst du im worst case ja immer noch N/2 Farben mit der gesuchten (N…Anzahl der Farben der Palette), wie du selbst schon gesagt hast.

  2. Du unterteilst den rgb-Raum nicht gleichmäßig, sondern so, dass in jeder Sphäre/jedem Würfel genau 1 Farbe der Palette enthalten ist (im Zentrum). Der Vorteil ist dann natürlich, dass du “nur noch” herausfinden musst, in welcher Sphäre die gegebene Farbe liegt. Das können ja (je nach Implementierung) aber auch wieder N Vergleiche sein.

Die oben beschriebene Variante der gleichmäßigen Würfel hat den Vorteil, dass man in konstanter Zeit herausfindet, in welchem Würfel die gegebene Farbe liegt und nur noch sehr viel weniger Farben vergleichen muss.
Nachteile sind u.a.: Wie groß sollte r sein, dass nicht zu viele Farben drinliegen und gleichzeitig nicht zu viele Würfel entstehen? Was, wenn die Farben der Palette nicht schön regelmäßig verteilt sind, sondern an einigen Punkten sehr stark gehäuft? Wie mit den Nachbar-Würfeln umgehen (denn da kann ja auch die nächste Farbe drinliegen)?

Ach ja, blöder Zufall: 161616 (Würfel) = 4096 = 2^12 (12 Bit)

Wenn das alles schon klar war, dann habe ich wohl etwas falsch verstanden und entschuldige mich vielmals.

LG eitelkalk

Nee, falsch verstanden hast du da nichts. Ich habe halt 3 Methoden um eine Farbpalette zu erstellen, von denen eine ein einfacher Algorithmus ist, der die Farben gleichmässig unterteilt, der zweite nur die am häufigsten verwendeten Farben verwendet und der dritte die am häufigsten verwendeten Farbtöne nimmt. Die dritte Methode ist ungefähr mit SlaterBs Würfelunterteilung vergleichbar, dient aber nur der Palettenerstellung.
Zur Zeit sind die Palettenfarben nach verwendeter Häufigkeit sortiert. AmunRa schlug vor sie nach dem Abstand zu einem gemeinsamen Mittelpunkt zu sortieren und SlaterB unterteilte den großen Würfel in 4096 kleinere, also sozusagen 4096 Mittelpunkte. Das Problem, welches ich dabei jetzt habe ist, dass bei SlaterBs Unterteilung gar nicht mehr alle 4096 Bereiche belegt sind und wenn dort eine (eliminierte) Suchfarbe reinrutscht, ist kein Bereich mehr da, in welchem man suchen könnte. In welchem Würfel sollte ich denn dann suchen?

BTW.: 16 = 2^4; 161616 = 2^42^42^4 = 2^(4+4+4) = 2^12. Das ist kein blöder Zufall. :wink: Und „Entschuldige dich nicht immer für solche Kleinigkeiten“, sagt mein Kollege immer. Warum wohl? :wink:

schau dir mein Programm an, für jeden Würfel wird mit der normalen Suche ein paar Farben hinterlegt, aus 1000 insgesamt werden 4096 * ~20 = ~80000, viele Doppelte, aber nicht schlimm, sogar die Version mit bis zu 200 schon ein Fortschritt,
man hat es nun so sortiert dass bei jeder Suche nicht mehr die 1000 angeschaut werden müssen sondern nur ~20 oder 200, das ist das Ziel

so funktioniert es für beliebige 1000 Zufallsfarben als Palette,
falls deine Palette systematisch verteilt ist, dann hättest du es umso einfacher,
dann vielleicht für jeden Würfel die Farbe oder maximal 8 in der Nähe ganz konkret auszurechnen,
(wobei 8 in 2D in Umgebung liegen, in 3D 8 + 9 + 9 = 26? na wäre auch verkraftbar)

der Schritt darf auf jeden Fall nicht fehlen: im Voraus die 4096 Würfel durchgehen und für jeden die Kandidaten bestimmen

Jo… habe es hinbekommen.
Nachdem ich die Kandidaten bestimmt hatte, habe ich einen “Probelauf” mit allen vorhandenen Farben gemacht und wenn dann ein Würfel null war (Fehlfarbe), habe ich wie zuvor den nächsten Farbwert gesucht und dessen Würfel an die Stelle der Fehlfarbe kopiert.

Mit dem Ergebnis, dass viel zu wenig Würfel mit viel zu vielen Farben erstellt werden und die meisten der Unterteilungen verdammt leer bleiben. Die am meisten verwendeten Farben häufen sich also wieder in sehr wenigen Würfeln und das macht die Suche auch wieder nicht schneller, was auch irgendwie logisch ist, wenn man drüber nachdenkt. Und ja, bei Randomfarben (Störungsmustern) geht es definitiv schneller, aber solche “Bilder” sind leider nicht das Hauptziel um möglichst unauffällig Farben zu reduzieren. Mir bleibt wohl nichts anderes übrig, als es bei der Urfassung zu belassen.

ein Bild wird ja wohl 5 oder eher 20 deutlich unterscheidbare Farbschwerpunkte haben

siehe mein Programm, schon mit 200 statt 1000 zu untersuchenden Farben kann man locker linear Faktor 5/ 80% Geschwindigkeitsverbesserungen annehmen,
sofern, sofern ja immer 1000 Vergleiche stattfinden, eine gewisse Statistik, wieviele Vergleiche konkret bei 500.000 Suchen, hast du immer noch nicht genannt, nur eine einzige Zahl interesant :wink:

und der Weg, besonders heftige Würfel weiter aufzuteilen, ist nicht verschlossen,
aber für einzelne Tage vielleicht nicht schaffbare Arbeit

wie auch immer

Die Anzahl der Vergleiche belaufen sich bei dem Bild aus dem Dither-Programm (77757 Farben, 480000 Pixel) bei 1024 Indices auf rd. 500.000.000. Angefangen bei 4 Indices mit rd. 2.000.000 verdoppelt sich die Anzahl der Vergleiche proportional zur Anzahl der Farbindices. Ab 8192 Indices passt die Anzahl der Vergleiche schon gar nicht mehr in ein int. Das alles, obwohl die Suchen bei ColorMatch sofort abgebrochen werden, also nicht immer die ganze Palette durchforstet wird.

Zumindest scheint die Anzahl der Farbschwerpunkte doch deutlich unter 1000 zu liegen, zumindest waren immer sehr viele Würfel nicht besetzt also null. Nun ist die Frage, wie man die relevanten Bitbreiten ab 13 Bit (also Indices von über 4096 bis 65536 Farben) sinnvoll in höhere Würfelauflösungen als 4096 verteilt und ob das überhaupt noch sinnvoll ist. Wenn man jetzt noch anfängt, nur besonders heftige Würfelunterteilen zu wollen, woher weis man denn dann, welche Würfel heftig waren und welche nicht, wenn am Ende nur eine Suchfarbe als Kriterium gegeben ist? Angesichts der vielen netten Ideen und der miesen Ergebnisse bin ich erstmal ratlos und muss das Unterfangen erstmal aufgeben, bis mir (oder jemand anderem) was anderes einfällt.

@Spacerat : ich hab mir deinen Code noch nicht in der Tiefe angesehen, um zum Problem etwas sinnvolles sagen zu können.
Eine stilistische Anmerkung habe ich aber zum Enum ColorLookUpTable.
Die createMatcher-Methode sollte kein switch auf sich selbst enthalten. Schöner wäre es so:

    UNIFORM_SUBDIVISIONS {
        public ColorMatcher createMatcher() {
            return new UniformSubdivisions();
        }
    },
    POPULARITY_CUT {
        public ColorMatcher createMatcher() {
            return new PopulationCut();
        }
    },
    MEDIAN_CUT {
        public ColorMatcher createMatcher() {
            return new MedianCut();
        }
    };

    public abstract ColorMatcher createMatcher();
    /* ... */
}```

*** Edit ***

Nachdem ich jetzt etwas über das Problem nachgedacht habe, würde ich behaupten, dass ein [Bereichsbaum](http://en.wikipedia.org/wiki/Range_tree) der Schlüssel zum Erfolg ist.
Die Abfragekomplexität ist [tex]O(\log^3 n + a)[/tex] und mit [Fractional Cascading](http://en.wikipedia.org/wiki/Fractional_cascading) sogar nur [tex]O(\log^2 n + a)[/tex] (a ist dabei die Größe des noch zu durchsuchenden Bereiches, das geht nämlich auch da nur in linearer Zeit).
Das von     @Marco13  in [#11](http://forum.byte-welt.net/threads/12287-Farbsuche-in-Palette-(nearest-Color-Index)?p=96035&viewfull=1#post96035) angesprochene Problem, dass man ggf. um 0,00001 daneben liegt, entfällt dadurch.

*** Edit ***
Achso, wichtig ist natürlich auch noch, wie lange es dauert, den Baum aufzubauen. Die Komplexität ist [tex]O(n \log^3 n)[/tex].

Die Komplexität deines bisherigen Algorithmus liegt für den Aufbau und die Suche bei O(n).

Gerade für größere Bittiefen sollte es also **theoretisch** eine Verbesserung geben.

ich habe mir nun einmal das Programm von #13 geladen, macht ja schon einiges her

am Anfang sind 4 Bit = 256 Paletten Farben eingestellt,
wenn ich auf Standard Median bei Programmstart umschalte und die distanceSquared-Aufrufe zählen lasse
komme ich auf eine Ausgabe von um die 120 Mio., stimmt für halbe Mio. Pixel und 256 Farben
(ich habe inzwischen schon einiges von mir eingebaut was die Zählung verfälscht)

die LUT-Palette wird in 7 sec oder so aufgebaut, dann das Bild in ca. 2,3 sec erstellt,
gleichzeitig in der GUI gezeichnet, schicker Effekt, das macht natürlich auch alles Zeitanteile aus,

ich habe wild meinen Ansatz mit 4096 Würfeln eingebaut, radius = 3 * 16 * 1.5 * 16 * 1.5 = 1728
ein Würfel ist am stärksten gefüllt mit 54 der 256 Farben, dahinter ansteigend,
allein 50 haben exakt 10 Farben (10 oder mehr Farben wären ~600 Würfel),
bis zu schließlich 1400 Würfeln mit nur einer Farbe

die Anzahl Vergleiche sinkt auf 22 Mio., die Zeit auf 0,7 sec

mit 12 Bit = 4096 Farben hat ein Würfel fast 1000 Farben, das ist wirklich nicht ganz fein,
insgesamt 330.000 Farb-Objekte gecacht, nicht völlig zu ignorieren, Durchschnitt aber unter 100

aber selbst damit kann sich Ergebnis schon gut sehen lassen:
statt vorher 1.9 Mrd. Vergleiche in 35 sec nun ‚optimiert‘ 350 Mio. in 6 sec

ich habe den Code wahrlich nur irgendwie reingeklatscht, das kriegt man bestimmt noch besser hin,
falls es nicht ganz andere Ideen gibt, ich will gar nicht so sehr auf meiner beharren,
außer in den Hauptpunkten: Vorarbeit leisten um später je Suche nur noch z.B. 100 stattt 1000 Vergleiche zu machen,
dieses Prinzip ist universell, das anzuerkennen wäre die Hauptleistung,
na ist vielleicht auch schon angekommen, nur konkrete Details in diesem Fall wollen nicht :wink:

einzelne Würfel mit bis zu 1000 Farben hier sind natürlich nicht schön,
nochmalige Verdopplung, ergo Reduktion von radius auf 432 bietet sich testweise an,
~32.000 Würfel…, 426 Farben nun Maximum, insgesamt interessanterweise auch nur 400.000 Farben,
an Platz also nicht wesentlich mehr nötig,
aber die Zeit zur Berechnung der Würfel inzwischen ein paar sec

2,5 sec für Rendern des Bildes, Vergleiche nun noch 260 Mio., inzwischen verzerrt: über die Häfte allein zur Befüllung der Würfel,
da muss schließlich für jeden der 32.000 Würfel alle 4000 Farben durchgegangen werden = 130 Mio.,

kann man noch super optimieren:
erstmal nur wenig Würfel, 512 etwa, die 400 davon mit nur wenig Farben (unter 50 von 4000) so lassen, sind super,
nur die 100 mit vielen Farben weiter aufteilen,

so viel weniger Arbeit beim Würfel-Zusammenstellen, Finden des Würfels bisschen mehr Rechnen als bisher,
oder ein großes Array wie bisher, von dem manche Indexe auf große gemeinsame Würfel zeigen, andere auf die feingranularen Einzelwürfel,


was habe ich an Code hinzugefügt?
in HistColor r,g,b und ri,gi,bi wie aus meinem Testprogramm, hashcode (für Sets), equals, compare usw.,

wenn die Farbe mit setARGB() geändert wird, musst ich auch die Werte aktualisieren, nicht ganz verstanden wieso…,
überlege, Color lieber unveränderlich zu lassen,
aber wahrscheinlich du zu viele andere Bearbeitungsstellen, so dass man Paletten-Color-Objekte nicht direkt ins Bild zurückgeben will :wink:

in matchIntern() von MedianCut beim Blick in MEDIAN_CACHE das 4D-room angelegt,
die Suchschleife dann mit einem Würfel statt der Gesamtliste,
alles analog zum Testprogramm

@cmrudolph :
Danke für den Tip. Wusste gar nicht, dass das geht. Immer, wenn ich irgendwas kompliziertes mit Enums versucht habe, hat mir die JVM was von final und nicht modifizierbar vorgemeckert.

@SlaterB :
Machts was aus, den veränderten Code zu posten? Jedenfalls wenn du damit durch bist.
compareTo hatte ich ja schon verwurstet, damit ich ganz einfach nach Häufigkeit sortieren kann. hashCode und equals dann für die die Farbe zu verwenden, erschien mir falsch (hatte angenommen, dass das auch noch so im Code stand, weil ich es tatsächlich mal so hatte ;))! Immutables mag ich gar nicht, ich sträube mich immer dagegen, im Zweifelsfall Millionen Instanzen nur für den GC zu produzieren. HashSonstwas gelangen dadurch zwar ins Hintertreffen, aber was solls, das sind ohnehin nicht die besten Sortiermethoden.

querbeet bei Color und ColorLookUpTable eingepflanzt:
[spoiler]

package dithering;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

class HistColor implements Comparable<HistColor>, Cloneable {
	private static final float RED_SCALE = 0.299f;
	private static final float GREEN_SCALE = 0.587f;
	private static final float BLUE_SCALE = 0.114f;

	int numPixels;
	private int argb;

	int r;
	int g;
	int b;

	int ri;
	int gi;
	int bi;

	final int hash;

	HistColor(int r, int g, int b) {
		this((r << 16) + (g << 8) + b);
	}

	HistColor(int argb) {
		this.argb = argb;

		this.r = getRed();
		this.g = getGreen();
		this.b = getBlue();

		ri = r / ColorLookUpTable.r2;
		gi = g / ColorLookUpTable.r2;
		bi = b / ColorLookUpTable.r2;

		final int prime = 31;
		int result = 1;
		result = prime * result + b;
		result = prime * result + g;
		result = prime * result + r;
		hash = result;
	}

	/**
	 * @inheritDoc
	 */
	@Override
	public int hashCode() {
		return hash;
	}

	/**
	 * @inheritDoc
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		HistColor other = (HistColor) obj;
		if (b != other.b)
			return false;
		if (g != other.g)
			return false;
		if (r != other.r)
			return false;
		return true;
	}

	final int getRed() {
		return (argb >> 16) & 0xFF;
	}

	final void setRed(int red) {
		red &= 0xFF;
		argb &= 0xFF00FFFF;
		argb |= (red << 16);
	}

	final int getGreen() {
		return (argb >> 8) & 0xFF;
	}

	final void setGreen(int green) {
		green &= 0xFF;
		argb &= 0xFFFF00FF;
		argb |= (green << 8);
	}

	final int getBlue() {
		return argb & 0xFF;
	}

	final void setBlue(int blue) {
		blue &= 0xFF;
		argb &= 0xFFFFFF00;
		argb |= blue;
	}

	final int getARGB() {
		return argb;
	}

	final void setARGB(int argb) {
		this.argb = argb;

		r = getRed();
		g = getGreen();
		b = getBlue();

		ri = r / ColorLookUpTable.r2;
		gi = g / ColorLookUpTable.r2;
		bi = b / ColorLookUpTable.r2;
	}

	final void set(HistColor c) {
		this.argb = c.argb;
	}

	final long longARGB() {
		long a = getAlpha() * 257;
		long r = getRed() * 257;
		long g = getGreen() * 257;
		long b = getBlue() * 257;
		return a << 48 | r << 32 | g << 16 | b;
	}

	final void setARGB(long argb) {
		int a = (int) ((argb >> 48 & 0xFFFF) / 257);
		int r = (int) ((argb >> 32 & 0xFFFF) / 257);
		int g = (int) ((argb >> 16 & 0xFFFF) / 257);
		int b = (int) ((argb & 0xFFFF) / 257);
		this.argb = a << 24 | r << 16 | g << 8 | b;
	}

	final int getAlpha() {
		return (argb >> 24) & 0xFF;
	}

	final void setAlpha(int alpha) {
		alpha &= 0xFF;
		argb &= 0x00FFFFFF;
		argb |= (alpha << 24);
	}

	@Override
	protected HistColor clone() {
		try {
			HistColor rc = (HistColor) super.clone();
			rc.numPixels = 0;
			return rc;
		} catch (CloneNotSupportedException e) {
			throw new RuntimeException("accidental not cloned");
		}
	}

	void premultiply() {
		int a = getAlpha();
		if (a != 0xFF) {
			int r = getRed() * a / 255;
			int g = getGreen() * a / 255;
			int b = getBlue() * a / 255;
			if (a != 0) {
				a = 255;
			}
			argb = a << 24 | r << 16 | g << 8 | b;
		}
	}

	float greyValue() {
		float rc = (int) (getRed() * RED_SCALE + getGreen() * GREEN_SCALE + getBlue()
				* BLUE_SCALE);
		rc /= 255.0f;
		if (rc < 0.0f) {
			rc = 0.0f;
		}
		if (rc > 1.0f) {
			rc = 1.0f;
		}
		return rc;
	}

	static long count;

	int distanceSquared(HistColor hc) {
		// int a = getAlpha();
		// if (a == 0) {
		// return ((hc.argb & 0xFF000000) != 0) ? Integer.MAX_VALUE : 0;
		// }
		count++;
		int rd = getRed() - hc.getRed();
		int gd = getGreen() - hc.getGreen();
		int bd = getBlue() - hc.getBlue();
		rd = rd * rd + gd * gd + bd * bd;
		return rd;
	}

	@Override
	public int compareTo(HistColor o) {
		int b = numPixels;
		int a = o.numPixels;
		if (a > b)
			return 1;
		if (a < b)
			return -1;

		int c = r - o.r;
		if (c != 0)
			return c;
		c = g - o.g;
		if (c != 0)
			return c;
		return b - o.b;
	}

	/**
	 * @inheritDoc
	 */
	@Override
	public String toString() {
		return "Col [r=" + r + ", g=" + g + ", b=" + b + "] - [ri=" + ri
				+ ", gi=" + gi + ", bi=" + bi + "]";
	}
}

class ColorRect extends HistColor {
	private Collection<HistColor> rects;
	private int redMin, redMax, greenMin, greenMax, blueMin, blueMax;

	ColorRect(int argb) {
		super(argb);
		blueMin = greenMin = redMin = 0xFF;
		blueMax = greenMax = redMax = 0;
	}

	boolean isDividable() {
		if (numPixels <= 1) {
			return false;
		}
		int rRed = redMax - redMin;
		int rGreen = greenMax - greenMin;
		int rBlue = blueMax - blueMin;
		if (rRed <= 1 && rGreen <= 1 && rBlue <= 1) {
			return false;
		}
		return true;
	}

	ColorRect add(HistColor c) {
		int a = getAlpha();
		if (c.getAlpha() != a) {
			throw new IllegalArgumentException("icompatible color rect");
		}
		numPixels += c.numPixels;
		int r = c.getRed();
		int g = c.getGreen();
		int b = c.getBlue();
		if (a != 0) {
			redMin = Math.min(redMin, r);
			greenMin = Math.min(greenMin, g);
			blueMin = Math.min(blueMin, b);
			redMax = Math.max(redMax, r);
			greenMax = Math.max(greenMax, g);
			blueMax = Math.max(blueMax, b);
			r = (redMax + redMin) / 2;
			g = (greenMax + greenMin) / 2;
			b = (blueMax + blueMin) / 2;
			setARGB(a << 24 | r << 16 | g << 8 | b);
		}
		if (rects == null) {
			rects = new ArrayList<HistColor>();
		}
		rects.add(c);
		return this;
	}

	ColorRect divide() {
		int a = getAlpha();
		if (a == 0) {
			throw new IllegalArgumentException("icompatible color rect");
		}
		int r = getRed();
		int g = getGreen();
		int b = getBlue();
		int rRed = redMax - redMin;
		int rGreen = greenMax - greenMin;
		int rBlue = blueMax - blueMin;
		if ((rRed <= 1 && rGreen <= 1 && rBlue <= 1) || numPixels <= 1) {
			throw new IllegalArgumentException("icompatible color rect");
		}
		ColorRect rc = clone();
		if (rRed > rGreen && rRed > rBlue) {
			rc.redMax = redMin = r;
			rc.setRed((rc.redMin + rc.redMax) / 2);
			setRed((redMin + redMax) / 2);
		} else if (rGreen > rBlue) {
			rc.greenMax = greenMin = g;
			rc.setGreen((rc.greenMin + rc.greenMax) / 2);
			setGreen((greenMin + greenMax) / 2);
		} else {
			rc.blueMax = blueMin = b;
			rc.setBlue((rc.blueMin + rc.blueMax) / 2);
			setBlue((blueMin + blueMax) / 2);
		}
		HistColor cr;
		int j, k;
		reset();
		rc.reset();
		Iterator<HistColor> it = rects.iterator();
		while (it.hasNext()) {
			cr = it.next();
			j = distanceSquared(cr);
			k = rc.distanceSquared(cr);
			r = cr.getRed();
			g = cr.getGreen();
			b = cr.getBlue();
			if (j > k) {
				it.remove();
				rc.numPixels += cr.numPixels;
				rc.redMin = Math.min(rc.redMin, r);
				rc.greenMin = Math.min(rc.greenMin, g);
				rc.blueMin = Math.min(rc.blueMin, b);
				rc.redMax = Math.max(rc.redMax, r);
				rc.greenMax = Math.max(rc.greenMax, g);
				rc.blueMax = Math.max(rc.blueMax, b);
				if (rc.rects == null) {
					rc.rects = new ArrayList<HistColor>();
				}
				rc.rects.add(cr);
			} else {
				numPixels += cr.numPixels;
				redMin = Math.min(redMin, r);
				greenMin = Math.min(greenMin, g);
				blueMin = Math.min(blueMin, b);
				redMax = Math.max(redMax, r);
				greenMax = Math.max(greenMax, g);
				blueMax = Math.max(blueMax, b);
			}
		}
		return (rc.numPixels == 0) ? null : rc;
	}

	private void reset() {
		redMin = greenMin = blueMin = 0xFF;
		numPixels = redMax = greenMax = blueMax = 0;
	}

	@Override
	protected ColorRect clone() {
		ColorRect rc = (ColorRect) super.clone();
		rc.rects = null;
		return rc;
	}
}

package dithering;

import java.awt.image.BufferedImage;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.WeakHashMap;

public enum ColorLookUpTable {
	UNIFORM_SUBDIVISIONS, POPULARITY_CUT, MEDIAN_CUT, ;

	private static final Map<BufferedImage, List<HistColor>[]> HIST_CACHE = new WeakHashMap<BufferedImage, List<HistColor>[]>();
	private static final Map<BufferedImage, Map<Integer, Data>> MEDIAN_CACHE = new WeakHashMap<BufferedImage, Map<Integer, Data>>();
	private static final Map<BufferedImage, Map<Integer, List<HistColor>>> POPULARITY_CACHE = new WeakHashMap<BufferedImage, Map<Integer, List<HistColor>>>();
	/*
	 * private static final Comparator<HistColor> RED_COMPARATOR = new
	 * Comparator<HistColor>() {
	 * 
	 * @Override public int compare(HistColor o1, HistColor o2) { int a =
	 * o1.red; int b = o2.red; return (a > b)? 1 : (a < b)? - 1 : 0; } };
	 * private static final Comparator<HistColor> GREEN_COMPARATOR = new
	 * Comparator<HistColor>() {
	 * 
	 * @Override public int compare(HistColor o1, HistColor o2) { int a =
	 * o1.green; int b = o2.green; return (a > b)? 1 : (a < b)? - 1 : 0; } };
	 * private static final Comparator<HistColor> BLUE_COMPARATOR = new
	 * Comparator<HistColor>() {
	 * 
	 * @Override public int compare(HistColor o1, HistColor o2) { int a =
	 * o1.blue; int b = o2.blue; return (a > b)? 1 : (a < b)? - 1 : 0; } }; //
	 */
	public static final int MIN_BITS = 1;
	public static final int MAX_BITS = 16;

	public ColorMatcher createMatcher() {
		switch (this) {
		case UNIFORM_SUBDIVISIONS:
			return new UniformSubdivisions();
		case POPULARITY_CUT:
			return new PopulationCut();
		default:
		case MEDIAN_CUT:
			return new MedianCut();
		}
	}

	static class Data {
		HistColor[] colorRects;
		HistColor[][][][] room = new HistColor[ColorLookUpTable.r][ColorLookUpTable.r][ColorLookUpTable.r][];

	}

	public static abstract class ColorMatcher implements Cloneable {
		static final int[][] rgbtable = { //
		{ 0, 65535, 0 }, // 1/16=>1/2/1
				{ 65535, 65535, 0 }, // 2/16=>2/2/1
				{ 65535, 65535, 65535 }, // 3/32=>2/2/2
				{ 65535, 32768, 65535 }, // 4/16=>2/3/2
				{ 32768, 32768, 32768 }, // 5/32=>3/3/3
				{ 21845, 21845, 32768 }, // 6/64=>4/4/3
				{ 16384, 16384, 16384 }, // 7/128=>5/5/5
				{ 13107, 10923, 13107 }, // 8/256=>6/7/6
				{ 9363, 9363, 10923 }, // 9/512=>8/8/7
				{ 7282, 7282, 7282 }, // 10/1024=>10/10/10
				{ 5958, 5462, 5958 }, // 11/2048=>12/13/12
				{ 4369, 4369, 4682 }, // 12/4096=>16/16/15
				{ 3450, 3450, 3450 }, // 13/8192=>20/20/20
				{ 2731, 2622, 2731 }, // 14/16384=>25/26/25
				{ 2115, 2115, 2185 }, // 15/32768=>32/32/31
				{ 1681, 1681, 1681 }, // 16/65536=>40/40/40
		};
		static int[] MASKS = { 0xFFFFFFFF, 0xFFFEFEFE, 0xFFFCFCFC, 0xFFF8F8F8,
				0xFFF0F0F0, 0xFFE0E0E0, 0xFFC0C0C0, 0xFF808080, };

		private int bitSize;
		private transient WeakReference<BufferedImage> wrv;

		boolean justMask;
		int sourceColors;

		protected ColorMatcher() {
		}

		public final int getBitSize() {
			return bitSize;
		}

		public final void setSource(BufferedImage source) {
			if (wrv != null && wrv.get() == source) {
				return;
			}
			wrv = new WeakReference<BufferedImage>(source);
			if (bitSize != 0) {
				getHistory(bitSize, source);
				sourceColors = HIST_CACHE.get(source)[0].size();
				justMask = (1 << bitSize) >= sourceColors;
			}
		}

		final Reference<BufferedImage> getSourceReference() {
			return wrv;
		}

		@Override
		public ColorMatcher clone() {
			try {
				return (ColorMatcher) super.clone();
			} catch (CloneNotSupportedException e) {
				throw new RuntimeException("accidental not cloned");
			}
		}

		public final BufferedImage getSource() {
			if (wrv == null) {
				return null;
			}
			return wrv.get();
		}

		public final void setBitSize(int bitSize) {
			if (bitSize < MIN_BITS || bitSize > MAX_BITS) {
				throw new IllegalArgumentException("invalid bitsize " + bitSize);
			}
			this.bitSize = bitSize;
			BufferedImage source = getSource();
			if (source != null) {
				getHistory(bitSize, source);
				sourceColors = HIST_CACHE.get(source)[0].size();
				justMask = (1 << bitSize) >= sourceColors;
			}
		}

		public final void match(HistColor c) {
			if (bitSize == 1) {
				c.setARGB((c.greyValue() < 0.5) ? 0xFF000000 : 0xFFFFFFFF);
				return;
			}
			if (justMask) {
				return;
			}
			matchIntern(c);
		}

		public final boolean justMask() {
			return justMask;
		}

		public int getSourceColors() {
			return sourceColors;
		}

		public final double getProgress() {
			return (justMask) ? 1.0 : progress();
		}

		protected abstract double progress();

		protected abstract void matchIntern(HistColor c);

		static List<HistColor> getHistory(int bits, BufferedImage v) {
			if (v == null) {
				throw new NullPointerException();
			}
			if (bits < MIN_BITS || bits > MAX_BITS) {
				throw new IllegalArgumentException("invalid number of bits");
			}
			synchronized (HIST_CACHE) {
				List<HistColor>[] tmpHist = HIST_CACHE.get(v);
				if (tmpHist == null) {
					@SuppressWarnings("unchecked")
					List<HistColor>[] t = new List[MASKS.length];
					tmpHist = t;
					HIST_CACHE.put(v, tmpHist);
				}
				int mask = MASKS[0];
				int i;
				int numCols = (1 << bits);
				List<HistColor> rc = tmpHist[0];
				if (rc == null) {
					Map<Integer, HistColor> tmp = new TreeMap<Integer, HistColor>();
					HistColor c = new HistColor(0xFF000000);
					int width = v.getWidth(null);
					int height = v.getHeight(null);
					for (int y = 0; y < height; y++) {
						for (int x = 0; x < width; x++) {
							c.setARGB(v.getRGB(x, y));
							c.premultiply();
							i = c.getARGB();
							i = (i & mask);
							HistColor hc = tmp.get(i);
							if (hc == null) {
								hc = new HistColor(i);
								tmp.put(i, hc);
							}
							hc.numPixels++;
						}
					}
					rc = new ArrayList<HistColor>(tmp.values());
					Collections.sort(rc);
					rc = Collections.unmodifiableList(rc);
					tmpHist[0] = rc;
					for (int n = 1; n < MASKS.length; n++) {
						tmp.clear();
						mask = MASKS[n];
						for (HistColor hc2 : tmpHist[n - 1]) {
							i = hc2.getARGB();
							i = (i & mask);
							HistColor hc = tmp.get(i);
							if (hc == null) {
								hc = new HistColor(i);
								tmp.put(i, hc);
							}
							hc.numPixels += hc2.numPixels;
						}
						rc = new ArrayList<HistColor>(tmp.values());
						Collections.sort(rc);
						rc = Collections.unmodifiableList(rc);
						tmpHist[n] = rc;
					}
					rc = tmpHist[0];
				}
				for (List<HistColor> l : tmpHist) {
					if (numCols > l.size()) {
						break;
					}
					rc = l;
				}
				return rc;
			}
		}
	}

	private static class UniformSubdivisions extends ColorMatcher {
		private int rDiv, gDiv, bDiv, bits;

		private UniformSubdivisions() {
		}

		@Override
		protected void matchIntern(HistColor c) {
			int bits = getBitSize();
			if (bits != this.bits) {
				int[] d = rgbtable[bits - MIN_BITS];
				rDiv = d[0];
				gDiv = d[1];
				bDiv = d[2];
				this.bits = bits;
			}
			long argb = c.longARGB();
			long alpha = argb & 0xFFFF000000000000L;
			if (alpha == 0L && bits >= 4) {
				c.setARGB(0);
				return;
			}
			long red = 0, green = 0, blue = 0;
			if (rDiv > 0) {
				red = (int) (argb >> 32) & 0xFFFF;
				red = getModulo(red, rDiv);
				red <<= 32;
			}
			if (gDiv > 0) {
				green = (int) (argb >> 16) & 0xFFFF;
				green = getModulo(green, gDiv);
				green <<= 16;
			}
			if (bDiv > 0) {
				blue = (int) argb & 0xFFFF;
				blue = getModulo(blue, bDiv);
			}
			argb = alpha | red | green | blue;
			c.setARGB(argb);
		}

		@Override
		public double progress() {
			return 1.0;
		}

		private static long getModulo(long component, int cdiv) {
			if (cdiv > 0) {
				long i = (component / cdiv) * cdiv;
				long j = ((component + cdiv) / cdiv) * cdiv;
				j = Math.min(j, 65535);
				component = (component - i < j - component) ? i : j;
			}
			return component;
		}
	}

	private static class PopulationCut extends ColorMatcher {
		private double numColors;
		private int count;

		private PopulationCut() {
		}

		@Override
		public double progress() {
			return count / numColors;
		}

		@Override
		protected void matchIntern(final HistColor c) {
			int bits = getBitSize();
			BufferedImage v = getSource();
			if (bits == 0 || v == null) {
				return;
			}
			Map<Integer, List<HistColor>> tmp = POPULARITY_CACHE.get(v);
			if (tmp == null) {
				tmp = new TreeMap<Integer, List<HistColor>>();
				POPULARITY_CACHE.put(v, tmp);
			}
			numColors = (1 << bits) - 1;
			List<HistColor> colorRects = tmp.get(bits);
			if (colorRects == null) {
				count = 0;
				colorRects = new ArrayList<HistColor>((int) numColors + 1);
				List<HistColor> rects = getHistory(bits, v);
				HistColor alpha = null;
				for (HistColor cr : rects) {
					if (cr.getAlpha() == 0 && alpha == null) {
						alpha = cr;
					} else if (count < numColors) {
						colorRects.add(cr);
						count++;
					}
				}
				colorRects.add((alpha == null) ? rects.get(count) : alpha);
				tmp.put(bits, colorRects);
			}
			count = (int) numColors + 1;
			int argb = c.getARGB();
			if ((argb & 0xFF000000) == 0) {
				c.setARGB(0);
				return;
			}
			/*
			 * if(numColors > 1000) { int red = ((argb >> 16) & 0xFF); int green
			 * = ((argb >> 8) & 0xFF); int blue = (argb & 0xFF); if(red > green
			 * && red > blue) { Collections.sort(colorRects, RED_COMPARATOR); }
			 * else if(green > blue) { Collections.sort(colorRects,
			 * GREEN_COMPARATOR); } else { Collections.sort(colorRects,
			 * BLUE_COMPARATOR); } } //
			 */
			int argb2 = argb;
			int currentError = 0;
			int minError = Integer.MAX_VALUE;
			for (HistColor cr3 : colorRects) {
				currentError = cr3.distanceSquared(c);
				if (minError > currentError) {
					minError = currentError;
					argb2 = cr3.getARGB();
				}
				if (currentError == 0) {
					break;
				}
			}
			c.setARGB(argb2);
		}
	}

	private static class MedianCut extends ColorMatcher {
		private double numColors;
		private int count;

		private MedianCut() {
		}

		@Override
		public double progress() {
			return count / numColors;
		}

		@Override
		protected synchronized void matchIntern(final HistColor c) {
			int bits = getBitSize();
			BufferedImage v = getSource();
			if (bits == 0 || v == null) {
				return;
			}
			Map<Integer, Data> tmp = MEDIAN_CACHE.get(v);
			if (tmp == null) {
				tmp = new TreeMap<Integer, Data>();
				MEDIAN_CACHE.put(v, tmp);
			}
			numColors = (1 << bits);
			Data data = tmp.get(bits);
			if (data == null) {
				System.out.println(System.currentTimeMillis()
						+ " vor generateLUT");
				List<HistColor> colorRects = generateLUT(
						getHistory(MAX_BITS, v), (int) numColors);
				data = new Data();
				data.colorRects = colorRects.toArray(new HistColor[0]);
				Arrays.sort(data.colorRects);
				tmp.put(bits, data);

				for (int i = 0; i < ColorLookUpTable.r; i++)
					for (int j = 0; j < ColorLookUpTable.r; j++)
						for (int k = 0; k < ColorLookUpTable.r; k++) {
							Set<HistColor> cand = new HashSet<HistColor>();
							// debug = i== 4 && j == 5 && k== 5;
							addCandidates((i + 0.5) * ColorLookUpTable.r2,
									(j + 0.5) * ColorLookUpTable.r2, (k + 0.5)
											* ColorLookUpTable.r2, cand,
									data.colorRects);
							HistColor[] candA = cand.toArray(new HistColor[cand
									.size()]);
							Arrays.sort(candA);
							data.room**[j][k] = candA;
						}
				System.out.println(System.currentTimeMillis()
						+ " generateLUT fertig, " + colorRects.size() + ", "
						+ HistColor.count + ", rad: " + ColorLookUpTable.radius);
			}
			count = (int) numColors;

			// search(c, data.colorRects);
			search(c, data.room[c.ri][c.gi][c.bi]);
		}

		static int countC;

		static void search(HistColor c, HistColor[] pal) {
			int argb = c.getARGB();
			if ((argb & 0xFF000000) == 0) {
				c.setARGB(0);
				return;
			}
			int argb2 = argb;
			int currentError = 0;
			int minError = Integer.MAX_VALUE;

			for (HistColor cr3 : pal) {
				currentError = cr3.distanceSquared(c);
				if (minError > currentError) {
					minError = currentError;
					argb2 = cr3.getARGB();
				}
				if (currentError == 0) {
					break;
				}
			}
			c.setARGB(argb2);
		}

		static void addCandidates(double r, double g, double b,
				Set<HistColor> cand, HistColor[] pal) {

			HistColor mid = new HistColor((int) r, (int) g, (int) b);
			int smallestError = Integer.MAX_VALUE;
			int maxError = Integer.MAX_VALUE;
			for (HistColor color2 : pal) {
				int currentError = mid.distanceSquared(color2);
				if (smallestError > currentError) {
					smallestError = currentError;
					maxError = smallestError + ColorLookUpTable.radius;
				}
				if (currentError < maxError) {
					cand.add(color2);
				}
			}
			Iterator<HistColor> iter = cand.iterator();
			while (iter.hasNext()) {
				HistColor next = iter.next();
				int currentError = mid.distanceSquared(next);
				if (currentError > maxError) {
					iter.remove();
				}
			}
		}

		private List<HistColor> generateLUT(List<HistColor> lhc, int numColors) {
			count = 0;
			ColorRect all = new ColorRect(0xFF000000);
			ColorRect alpha = null;
			for (HistColor cr : lhc) {
				if (cr.getAlpha() == 0) {
					if (alpha == null) {
						alpha = new ColorRect(0);
						numColors--;
					}
					alpha.add(cr);
				} else {
					all.add(cr);
				}
			}
			List<ColorRect> lhc1 = new ArrayList<ColorRect>();
			lhc1.add(all);
			count = lhc1.size();
			int index = 0;
			ColorRect add;
			while (count < numColors && index < count) {
				all = lhc1.get(index);
				if (!all.isDividable()) {
					index++;
					continue;
				}
				add = all.divide();
				if (add != null) {
					lhc1.add(add);
					Collections.sort(lhc1);
				}
				count = lhc1.size();
			}
			if (alpha != null) {
				lhc1.add(alpha);
			}
			lhc = new ArrayList<HistColor>();
			for (ColorRect cr : lhc1) {
				lhc.add(new HistColor(cr.getARGB()));
			}
			return lhc;
		}
	}

	static final int r = 32;
	static final int r2 = 256 / r;
	static final int radius = (int) (3 * r2 * 1.5 * r2 * 1.5);
}

[/spoiler]

Ich mach mich mal dran, das nachzuvollziehen. Zuerst muss ich mir aber was einfallen lassen, denn dieses Queerbeet-Einfügen hat eine IllegalArgumentException provoziert. “Comparison method violates its general contract!” Denke mal, das liegt an der veränderten Comparemethode, da werde ich wohl einen oder mehrere Comparatoren für schreiben müssen. Das HashSet zumindest lässt sich schon mal wunderbar durch mein IdentityHashSet (fehlt in Java8 natürlich immernoch) austauschen, von daher wird hashCode und equals eigentlich gar nicht mehr benötigt. Allerdings liegt dennoch die Überlegung nahe, den Comparator für numPixels zu implementieren und für compareTo zwei Farben über ihre Distanz zu vergleichen.

Edit: Hmm… beim ersten erfolgreichen Probelauf hat er nicht eine Farbe reduziert… aber er war zumindest um ein vielfaches schneller.

So, ich weis nun, dass man an andere Suchalgorithmen als den Ursprünglichen einen Haken machen kann.
Das Problem ist ganz einfach dieses, dass wegrationalisierte Farben gar nicht mehr gefunden werden können. Bei Slaters Methode dürfte man nur Würfel erstellen, für in der Pallette vorhandene Farben und deswegen kommen bei wegrationalisierten Farben NullpointerExceptions. Seine Methode funktioniert nur bei der Unterteilung der Ursprünglichen Farben und das auch nur dann, wenn man keine Dithermethode wählt, weil eine solche die Ursprünglichen Farben noch einmal verändert/verfälscht. Kurz gesagt, es kommen recht oft auch Farben, die der Reduzierung zum Opfer gefallen sind oder durch eine Dithermethode berechnet wurden, also im Originalbild gar nicht auftauchten. Dieser Umstand führt zwangsläufig dazu, dass man unbedingt in der gesamten Palette suchen muss.
Das Thema ist durch. :frowning:

wegrationalisierten Farben? wo kommt denn das auf einmal her :wink:
‚meine Methode‘ klingt übertrieben, ich stelle sicher nur exemplarisch und nicht besonders gut etwas so allgemeines wie eine HashMap vor

wenn es unbekannte Faktoren gibt, dann ist es noch kein Problem falls sie noch nicht berücksichtigt sind,
Code kann man verändern, was an gegebenen Aufgaben zu optimieren war klappt unwiderlegbar glänzend
(im ersten Ansatz, Ziel kann noch viel besser werden),

an einer Stelle hast du eine Code-Schleife die wie genannt z.B. 1.9 Mrd. Vergleiche ausführt ohne dabei irgendwas ‚wegzurationalisieren‘,
diese Schleife war auch deine Frage hier und die kann man ganz anders machen, so einfach ist die Aussage

mag theoretisch sein, dass du hinsichtlich weiterer Bedingungen alles durchdacht hast,
aber klingt nicht überzeugend, und der bisherige Thread-Verlauf dürfte allgemein dagegen sprechen, mit Verlaub

wenn Ende dann Ende, ist ja auch irgendwie effizienter, 30 sec. rendern zu lassen als Tage darüber zu reden,
aber falls es dich doch theoretisch interessieren sollte oder irgendwie Dauereinsatz geplant ist,
dann rate ich dir nicht ohne jeden Versuch (für andere zumindest auch) aufzugeben

an meiner Zuversicht ist bisher jedenfalls noch kein Kratzer gekommen,
wechselt manchmal schnell, aber oft dann doch auch nicht,

aber wenn durch dann eben durch

[QUOTE=SlaterB]wegrationalisierten Farben? wo kommt denn das auf einmal her ;)[/QUOTE]Das ist vom Vorhaben der Farbreduzierung geerbt, dagegen kann man nichts machen. Ich erstelle ja diese Paletten nicht umsonst aus den durchschnittlich am häufigsten verwendeten Farben. Das heisst, wenn nur 8 Pixel bzw. Palettenbits zur Verfügung stehen, darf man nur aus 256 Farben wählen und nicht mehr stur alle 77757 Farben aus dem Originalbild wählen. Ich nahm an, das dies von vorne herein klar war. Interessant wird das auch erst bei Paletten mit 10 bis 16 Bit, also 1024 bis 65536 Farben und Originalen mit der Rekordzahl aller 16,7 Millionen möglichen TrueColor farben (ab 4096 * 4096 Pixel). Im Beispielbild lohnt sich die Farbreduzierung auf 16 Bit ja kaum, dauert aber trotzdem recht lange.
Deine 3D-Suche war eine super Idee und ich war auch zuversichtlich, dass es funktionieren sollte, bis ich es zum “Laufen” (bzw. bis zu andauernden Farbverfälschungen und NPEs) bekommen habe. Was solls. Hat nicht sollen sein.

warum bist du in deinen Ansichten immer so endgültig?

ich werde aus deinen Sätzen nicht schlau, versuche dann aber wenigstens nachzufragen,
dass die Palette weniger Farben hat, dass man von Originalfarben aus Palettenfarben wählt, ist in der Tat die Grundvoraussetzung,
die einzige Aussage im ersten Posting und auch völlig klar

was genau könnte die Würfel leisten, wenn nicht exakt das, wo liegt der Unterschied?
was ist die Zahl 77757? sehe jetzt gar keinen Zusammenhang, google findet auch nicht
(während zur sowieso bekannten Zahl 2^16 = 65536 ‚farben in bit‘ direkt vorgeschlagen wird)

(edit: ach halt, das ist ja auch eine Anzeige im Programm, wahrscheinlich die Anzahl unterschiedlicher Farben im Beispielbild?)

was die Standardeinstellung 8 Bit in deinem Programm macht hat mich nicht weiter interessiert,
es kommen 256 Farben in die Palette, anscheinend aus dem gesamten TrueColor-Farbraum nach Wichtigkeit im Bild gewählt,
finde ich gut,
und jede Farbe des Bildes wird dann zu einer von diesen 256, selbstverständlich, worum sonst könnte es gehen?
das wird mit der langen Schleife erreicht, für jeden der 480.000 Pixel eine 256-Schleife um eine der Farben auszuwählen,
oder mit Würfel in (im einfachsten Fall) durchschnittlich 45 Vergleichen, gleiches Ergebnis,

maximal denkbarer Fehler ist Wahl einer minimal anderen Farbe aus Palette, wenn radius zu eng gewählt, aber immer eine Farbe aus der Palette, und Exception komplett undenkbar!

diese ‚Farbverfälschungen‘ aber auch nur wenn bisher quasi Demonstrations-Testcode,
mit ein wenig Mühe nicht nur zigfach schneller zu machen sondern dabei auch beweisbar korrekt,
(= gleiches Ergebnis wie Normal-Suche)

mit VGA = 256 Farben überhaupt in gleichmäßigen Abstand hat das anscheinend nichts zu tun,
die könnte man ja auch direkt herunterrechnen, keine Suche


in deinem Programm hast du auch

	final void setARGB(long argb) {
		int a = (int) ((argb >> 48 & 0xFFFF) / 257); // [Konstante 257 korrekt?]
		int r = (int) ((argb >> 32 & 0xFFFF) / 257);
		int g = (int) ((argb >> 16 & 0xFFFF) / 257);
		int b = (int) ((argb & 0xFFFF) / 257);
		this.argb = a << 24 | r << 16 | g << 8 | b;
	}

16 Bit je Farbe, das wär nochmal was interessantes, falls man das mit Würfeln anginge,
da müsste man bei der Indexberechung neu aufpassen


bei 16,7 Mio. Pixel werden kaum auch alle 16,7 Mio. unterschiedlichen Farben dabei sein, viele Doppelte,
aber ein kleines Bild von nur 480k Pixel profitiert genauso von TrueColor,

und funktioniert/ benötigt/ arbeitet analog hinsichtlich der Suche mit Palette,
ob normal oder mit Würfel, es ist die Anzahl der Suchen auch egal, das hat nichts miteinander zu tun,
jede einzelne Suche muss zu einem Ergebnis führen, nicht mehr und nicht weniger

da der Würfelaufbau kostet, z.B. soviel wie 32.000 Suchen, lohnt er sich natürlich erst ab einer gewissen Zahl X,
immer eine Abwägung, für 480.000 gewiss


so ich habe im wesentlichen nur bekanntes wiederholt, weil ich nicht weiß, was sonst zu sagen :wink:

weiterhin gilt: falls ich nerve, wenn das Thema beendet ist, dann bitte, dann bin ich bald still,

aber soweit funktioniert es in jeder Hinsicht, ich kann mir noch kein Problem vorstellen und aus deinen Aussagen auch nicht herauslesen,
sofern du es doch für eine ‚super Idee‘ hälst, schön auch mal was positives zu lesen :wink: , lohnt sich vielleicht doch Details durchzugehen,

kannst du konkret den Weg zu einer Exception oder sonstwie dramatisches beschreiben/ Code posten?

Eines Vorweg: Nicht du nervst, sondern der Umstand, dass die besten Ideen versagen. Ich bin dir für deine Mühen auch durchaus dankbar. Ich gebe tatsächlich immer recht schnell auf, weil ich noch ganz andere Baustellen habe (z.B. suche ich eine Laderoutine von JPEG-Bildern in purem Java). Dieses Aufgeben ist aber mehr ein auf Eis legen, bis zündende Ideen kommen.

Ich habe deine Würfel nur aus den Palettenfarbenkonstruiert und dabei blieben sehr viele Plätze (Arrays bzw ich verwende lieber Lists) frei, also null. Die im Bild verwendeten oder durch Dithering zusätzlich verfälschten Farben landen nun viel zu oft, an diesen null Plätzen und nun weis ich nicht, wo der nächste Würfel zu finden ist. Die Suchfarbe ist an dieser Stelle auch nicht vom Typ HistColor, so dass sie auch über keinerlei Infos verfügt, welchem sie zugewiesen wurde, dies aber wäre Notwendig. Als Fehlfarben können wie gesagt auch Farben kommen, die bei den Originalfarben gar nicht vorkamen und diese können bei entsprechenden Unterteilungen sogar 1 bis 3 Plätze entfernt von mehreren Würfeln liegen. Das nun auftretende Problem illustriert sich wie folgt:

Das rote Quadrat und der rote Punkt ist die Fehlfarbe in einem fehlenden Würfel. Für diese muss nun ein Ersatzfürfel a oder b gefunden werden. Sortiert man die Würfel nach ihrer Mittelpunktfarbe, würde Würfel a gewählt und dass obwohl die Farben von Würfel b näher an der Farbe liegen, dieser also besser geeignet wäre. Das einzige, was einem in dieser Situation bleibt, ist meines Wissens wieder die Suche in der kompletten Palette, richtig? Zumindest aber die Suche in diesen beiden Würfeln, wobei dann aber längst nicht sicher ist, ob es nicht noch einen Würfel gibt, dessen Farben noch günstiger liegen.

Die Zahl 77757 ist die reale Anzahl der Farben des Beispielbildes. Die Sache mit den 16-Bit-RGB eine Notlösung (*) für die UniformSubdivisions. 16-Bit-Farben lassen sich genauer unterteilen, die Unterteilung R32/G32/B31 wäre bei 256 Farben gar nicht möglich, das wären dann nur R30/G30/B30. Diese Tatsache ist hier aber nicht das Problem.

(*) Die echte API unterstüzt 64-Bit TrueColor (16-Bit pro Kanal) in RGBA, BGRA und ARGB, zumindest kann es sie verwalten. Trotzdem wäre für die Anzeige solcher Bilder noch immer eine spezielle Hardware erforderlich.

ist dir bewußt, dass diese Konstruktion komplett von ‚meiner‘ abweicht?
sowohl in meinem Testprogramm von 220 Zeilen (davon die Hälfte Color-Objekt und ähnlich langweiliges) als auch in dein Programm eingebaut als auch nicht zuletzt zigmal im Text erwähnt?! (nachdem ich es programmiert hatte, vorher hatte ich freilich auch anderes gedacht)
ist der Aufbau doch nun völlig klar als nicht einer von allzu vielen Schritten:

            for (int j = 0; j < r; j++)
                for (int k = 0; k < r; k++)
                {
                    Set<Col> cand = new HashSet<Col>();
                    addCandidates([Mitte des aktuellen Würfels], cand, palette);
                    Col[] candA = cand.toArray(new Col[cand.size()]);
                    Arrays.sort(candA);
                    room**[j][k] = candA;
                }

kein Würfel ist leer, wie oft denn noch?
von jedem Würfel, z.B. den roten im Bild, nimmt man den Mittelpunkt und führt eine normale Suche aus (später kann es noch besser gehen, aber reicht erstmal)
die dunkelblauen Punkte in Würfel a und b sollen wahrscheinlich deren Mittelpunkt darstellen?
am nahesten von der Mitte des roten Würfels ist eh einer der hellblauen Punkte = eine Palettenfarbe aus südwestlichen Teil von Wüfel b,
sofern man in 2D noch von Würfeln spricht,

dies ist der erste Kandidat für den roten Würfel, schon jetzt kann er nicht mehr leer sein,
allerdings muss das nicht für alle denkbaren Suchfarben, die im roten Würfel landen, das beste Ergebnis sein,
vom Südwesten im roten Würfel ist man mit einer Farbe aus Würfel a besser dran

deswegen gibt es nicht nur einen Kandidaten, deswegen gibt es die radius-Variable, alle weiteren Farben, die ca. im Abstand einer Würfeldiagonalen entfernt liegen (vom ersten Fund aus gerechnet!, kann auch der 5. bis 6. Kreis sein, wobei wie gesagt auf die Entfernung mit quadratisch wachsenen Zahlen die radius-Berechnung nicht mehr die gleiche Bedeutung hat wie in der Nähe, kann man aber alles behandeln), werden auch aufgenommen,
so sind die Kandidaten für den roten Würfel letztlich mindestens alle aus a und b,

wenn die Suche über den roten Würfel geht, wird mit all diesen verglichen, teils recht viele, aber immer noch besser als sämtliche Farben der Palette,
je intelligenter man vorgeht, desto kleiner wird die Liste pro Würfel, aber leer ist undenkbar

Ja, das ist mir bewusst. Aber ist dir bewusst, dass bei deiner Kontruktion auch Farben gefunden werden, die nicht in der Palette sind? Als ich es zum ersten mal zum Laufen bekam, wurde da nicht eine Farbe reduziert, dass bedeutete, dass Original und Kopie 1:1 waren, also beide 77757 Farben hatten (du darfst die Farben aus “cand” gar nicht verwenden, sondern nur jene aus “palette”).
Das einzige, was ich an deinem Code geändert hatte, war die “compareTo()”-Methode, wobei ich aber deinen Teil stehen gelassen habe und statt dessen einen extra USAGE_COMPARATOR für “numPixels” implementiert habe. Das war Notwendig, weil es sonst zu oben besagter IAE kam.

ich komme nicht hinter die Logik deiner Einwände,

Als ich es zum ersten mal zum Laufen bekam, wurde da nicht eine Farbe reduziert, dass bedeutete, dass Original und Kopie 1:1 waren

findest du so einen Umstand nicht etwas auffällig?
in dem Sinne dass es vielleicht bei Übertragung von manuell eingefügten Code, hier im Forum gepostet (ich könnte Änderungen in weiteren Klassen vergessen haben) und wer weiß wie vollständig von dir dann übernommen, zu einem Fehler kam,
statt den Algorithmus für ineffektiv zu erklären, wahrscheinlich ohne es dir erklären zu können?

dass weitere Änderungen für andere Programmteile nötig sind kann sehr gut sein,
es geht ja zunächst nur um die Idee, ich habe keine fertige Lösung gebaut

cand, die Kandidaten pro Würfel, enthält eine Teilmenge von palette…,
eben weil es genau das Ziel ist, nicht alle 256 Farben anzuschauen sondern z.B. nur 45 im Umkreis des Würfels,

die Strukturierung ermöglicht es, die Suche etwas zu reduzieren,
in diesen wenigeren Kandidaten wird dann immer noch normal gesucht,
ein Ergebnis kommt so oder so, ob man in allen 256 nachschaut oder günstigerweise nur den 45 in der Nähe,
immer ist eine Farbe der Palette das Ergebnis,
das steht komplett außer Frage, ist ja die Hauptfunktion überhaupt des Algorithmus…,

Ja, der Umstand ist auffällig. Auffällig ist aber auch, dass ich deinen Code immer noch nacharbeiten musste, so dass er läuft. Anzunehmen, dass ich dabei in der Klasse Data die History statt der Palette gespeichert habe, dass ist möglich. Ich habe die Anwendung noch mal überarbeitet aber immer noch recht ecklige Probleme, die sich aber nur auf 3 Dithermethoden (die mit Fehlerstreuung, also FS, JJN und Stucki) beschränken. NONE und die neue ORDERED funktionieren sonderbarer weise. Sonderbarerweise deswegen, weil diese Algos bei der alten Suchmethode noch funktionierten und ORDERED darüber hinaus viel mehr Farben verfälscht, als die anderen und daher viel weniger funktionieren dürfte.