zweidimensionale Zyklische Farbverteilung

  • C#

Es gibt 20 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    zweidimensionale Zyklische Farbverteilung

    Moin Leute.
    Folgendes Problem:
    Ich habe ein quadratisches Raster (Feld beliebig groß), wo in jedem Feld eine Operation durchgeführt wird, in deren Ergebnis ein x- und ein y-Wert herauskommen (momentan je 0 bis 10).
    Das besondere an den x-y-Werten ist, dass sie zyklisch sind, also 11 wäre 0 und -1 wäre 10.
    Man kann also diese Ergebnis-Manigfaltigkeit auf einen Torus abbilden.
    Mein Problem ist nun die Visualisierung dieser Werte.
    Ziel ist, jedem x-y-Wertepaar eine eigene Farbe zuzuordnen mit der Nebenbedingung, dass benachbarte Wertepaare (dx=+-1, dy=0 sowie dx=0, dy=+-1) benachbarte Farben bekommen.
    In einer Dimension wäre das mit der H-Komponente des HSV-Farbraums zu machen.
    Hat jemand von Euch ne Idee, das zweidimensional zu machen?
    Danke für die Info.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    kann man sich nicht iwie eine Farbskala in 10 Schritten von Grün bis Gelb ausdenken, und eine 2. Skala von Grün bis Blau?
    Und dann die eine Skala als X-Achse denken, die annere als Y.
    Vlt. ergeben die Überlagerungen der Skalen iwie ganz brauchbare Farbabstufungen - man wird vmtl. eine Korrektur-Heuristik einbasteln müssen, weil das menschliches Farbempfinden iwie äusserst un-linear ist.
    @ErfinderDesRades Solch Ansatz hatte ich auch, aber das ist so nicht umsetzhar, da bräuchte man 4 Grundfarben, 2 pro Dimension (im Sinne der 3 Farben bei HSV).
    Stell Dir den Torus vor.
    Wenn Du Dich parallel zur "Schlauchmittelachse" bewegst, werden 2 Komponenten variiert, die anderen beiden sind konstant.
    Wenn Du Dich senkrecht dazu bewegst, ist es anders herum.
    Vielleicht ließe es sich lösen, wenn wir die Farben zusammendrücken, also aus 24 Bit RGB (888)-Bit durch Umgruppierung (6666)-Bit machen und dann für jeden Freiheitsgrad 2 mal 6 Bit nehmen.
    Mal probieren.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    RodFromGermany schrieb:

    da bräuchte man 4 Grundfarben

    CMYK ?

    Edit: Die K-Komponente sollte man ggf. halbieren oder dritteln, um noch einen vernünftig sichtbaren Farbverlauf zu erhalten.
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „petaod“ ()

    Mit 3 Farben sieht's gar nicht mal so übel aus:

    Horizontal von grün bis rot, vertikal von grün bis blau.
    Beachte, dass die Interpolation so aussieht:

    VB.NET-Quellcode

    1. Private Shared Function ColorLerp(t As Double, Color0 As Color, Color1 As Color) As Color
    2. Return Color.FromArgb(255, _
    3. CByte(Math.Sqrt((1 - t) * Color0.R ^ 2 + t * Color1.R ^ 2)), _
    4. CByte(Math.Sqrt((1 - t) * Color0.G ^ 2 + t * Color1.G ^ 2)), _
    5. CByte(Math.Sqrt((1 - t) * Color0.B ^ 2 + t * Color1.B ^ 2)))
    6. End Function
    7. '...
    8. For Y = 0 To RectangleSize - 1
    9. For X = 0 To RectangleSize - 1
    10. FillPixel(X, Y, _
    11. ColorLerp(0.5, _
    12. ColorLerp(X / (RectangleSize - 1), HorizontalLeftColor, HorizontalRightColor), _
    13. ColorLerp(Y / (RectangleSize - 1), VerticalTopColor, VerticalBottomColor)))
    14. Next
    15. Next

    Also wie eine normale lineare Interpolation, aber die eingehenden Werte werden zuerst quadriert und vom Ergebnis wird die Wurzel genommen. Dadurch tendiert das Ergebnis bei t != 0.5 mehr zum helleren Farbanteil hin. Macht man das nicht, ist das Ergebnis "matschig":


    Interessant ist auch:
    Nur in der linken, oberen Ecke findet man einen Maximalwert in einer Farbkomponente: 0,255,0 (grün)
    In allen anderen Ecken liegt der höchste Wert bei ca. 180.
    Dadurch kann man bei der rechten bzw. unteren Farbe "übersteuern". System.Drawing.Color erlaubt es zwar nicht, Farbkomponenten mit Werten über 255 zu speichern, aber es ist kein Problem, den Code so umzuschreiben, dass er eine eigene Color-Struktur verwendet, die Doubles beinhaltet.
    Interpoliert man also horizontal von 0,255,0 nach 361,0,0 und vertikal von 0,255,0 nach 0,0,361, dann kommen in den Ecken Farbkomponenten bis ca. 255 heraus und man erhält einen sehr schönen Farbverlauf:
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Niko Ortner“ ()

    @Niko Ortner Deine Bildchen sollten zu einem Torus gewickelt werden können, d.h. oben/unten sowie rechts/links sollte ein nahtloser Übergang sein.
    Deswegen Zyklische Farbverteilung.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Einfach gespiegelt wiederholen ;)


    Bzw. mit einer Kosinus-Interpolation anstelle der linearen Interpolation sieht's noch schöner aus:


    (Beachte: Die Bilder in diesem Post sind 3 mal so groß wie die in Post #5. Also die Bilder in Post #5 sind jeweils in der Mitte und außen rum die Spiegelungen.)
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Hi
    das geht natürlich auch nicht. Du hast sonst Werte doppelt, was den Zweck zunichte machen würde.

    Gesucht ist eine Funktion C(x, y), die einen Farbvektor zurückgibt und folgende Eigenschaften hat:
    1.
    $C(x_1, y_1) = C(x_2, y_2) \Rightarrow \exists k, l \in \mathbb{N}_0 \begin{pmatrix}x_1\\y_1\end{pmatrix} = \begin{pmatrix}x_2\\ y_2\end{pmatrix} + k \begin{pmatrix}1\\0\end{pmatrix} + l \begin{pmatrix}0\\1\end{pmatrix}$

    2.
    $\lim\limits_{h \to 0}{d(C(x, y), C(x + h, y))}=\lim\limits_{h \to 0}{d(C(x, y), C(x, y + h))} =\lim\limits_{h \to 0}{d(C(x, y), C(x + h, y + h))}=0$
    für alle reellen x und y und eine Funktion d, die angibt, wie weit, visuell gesehen, sich zwei Farbwerte unterscheiden (0 bedeutet "gleich").

    Das heißt, man will eine eindeutige Farbe innerhalb eines Rechtecks (1.), wobei die visuelle Wahrnehmung "glatt" sein soll (die Funktion ist also stetig, sodass ein flüssiger Verlauf wahrgenommen wird) (2.). Das sollte ja eigentlich quasi genügen (ist hier kontinuierlich, nicht diskret gerechnet).

    Die beiden Eigenschaften würde man dann später für den Beweis benötigen.

    Jetzt kann man halt z.B. rangehen und nach derartigen Funktionen suchen. Da steige ich gerade noch nicht ganz durch.
    Meine Idee war, die Dreiecksfunktion zu verwenden, um das 2. Verhalten zu erreichen:
    $tri(x) = 1 - \lvert 2(x \bmod 1) - 1 \rvert$

    Die erste habe ich so nicht ganz erfüllt bekommen. Ich habe aber auch gerade anderes zu tun. Ich setze mich später nochmal ran.

    Viele Grüße
    ~blaze~

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „~blaze~“ ()

    @~blaze~
    Hmmm. Ich bin kein Mathe-Profi, aber ich denke, beides gleichzeitig geht nicht.
    Also sowohl kontinuierliche Verläufe, als auch keine Duplikate.

    Vereinfachen wir die Situation kurz auf 1-dimensionale Funktionen.
    Wenn eine Funktion f(x) für 0 <= f(x) <= 1 und 0 <= x < 1 (bzw. f(x) = f(x mod 1), sodass es sich wiederholt, wie bei dir die erste Eigenschaft) gesucht ist, dann muss diese (im Bereich 0 bis 1) entweder monoton steigend oder monoton fallend sein (ignorieren wir mal streng monoton vs einfach nur monoton).
    Alleine durch diese Bedingung ergibt sich, dass die Funktion nie wieder an den Anfangspunkt zurückkehren kann.

    Beispiel: f(x) = x ist kontinuierlich und umkehrbar (hat also keine doppelten Werte). Aber es ist nicht möglich, von f(1 - ε) = 1 - ε auf f(1) = f(0) = 0 zurückzukommen, ohne zu springen *.
    Dieser Sprung kann auch bei einem anderen x liegen: f(x) = (x + 0.25) mod 1. Die Funktion ist von 0 <= x < 0.25 monoton steigend und umkehrbar und auch von 0.25 <= x < 1 monoton steigend und umkehrbar, aber es muss bei x = 0.25 trotzdem gesprungen werden, weil man anders nicht wieder zurück kann.

    Ein Beispiel für die andere Seite:
    Die saw-Funktion von Dir (kenne ich eher als Dreieck-Funktion; Sägezahn wäre z.B. nur Modulo) ist von 0 <= x <= 1 kontinuierlich und hat keine Sprünge, aber für jedes f(x) gibt es auch zwei x, die passen würden. Diese Funktion ist also nicht umkehrbar. Genau genommen ist diese Funktion die 1-dimensionale Variante von dem, was ich im 2-dimensionalen gemacht habe. Der Bereich von 0.5 bis 1 ist der Bereich von 0 bis 0.5, aber gespiegelt.


    *Wäre der Wertebereich von f(x) nach oben und unten offen, könnte man gegen +∞ verschwinden und dann wieder von -∞ hereinkommen, aber auch da würde ich sagen, dass man Polstellen nicht als kontinuierlich bezeichnen kann.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Niko Ortner“ ()

    Mir würde jetzt auch keine Lösung einfallen, was aber nicht belegt, dass es unmöglich ist. Der Beweis entzieht sich mir für den Moment allerdings, also gibt es womöglich schon eine Lösung für das Problem.
    Wenn du dir Hue anschaust, das kehrt zu Rot zurück und beginnt bei Rot, hat aber einen Verlauf. Wären die Graustufen bzgl. Hue unterscheidbar, würde ich diese empfehlen, aber sind sie ja leider nicht - sonst wären Saturation und Brightness meine Wahl.
    Insofern stimmt aber die Monotonieforderung nicht. Ich hatte da aber auch zuerst drangedacht.

    Dreiecksfunktion meinte ich, sorry. Keine Ahnung, wo mir heute der Kopf steht. :D

    Edit:
    Ich denke, man könnte Hue als
    $e^{\mathrm i 2\pi x}$
    darstellen. Dadurch wird das Rot bei 360° erreicht. Wenn man Hue als x-Achse wählt und ein weiteres
    $e^{\mathrm i 2\pi y}$
    wählt, würde man die Anforderungen erfüllen.
    Da man das in zwei Richtungen benötigt, fällt mir keine visuell intuitive Variante ein, das in den 3-dimensionalen Farbraum einzubetten, in welcher Art auch immer. Man hätte quasi einen vierdimensionalen Raum.
    Im Prinzip hat man am Bildschirm folgende Dimensionen: Die wahrgenommenen Farben würde ich als höchstens dreidimensional auffassen egal was man unterscheidet (egal, ob RGB, HSB, HCY, usw.) und zusätzlich noch x und y. x und y sind für deinen Fall festgelegt auf einen Pixel, womit man drei Freiheitsgrade hat.

    Viele Grüße
    ~blaze~

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „~blaze~“ ()

    @~blaze~ Schon mal ein guter Ansatz.
    Ich hab inzwischen folgendes probiert:
    • Winkel 0 bis 360°, insgesamt 64 Werte, davon via Sinus und Cosinus 2 Farbanteile berechnen,
      besser ist es, statt Sinus und Cosinus gleichphasige Zickzack-Linien zu verwenden, da bei den Sinus-Extrema gleiche Farben rauskommen.
    • Das ganze zwei Mal, für x und für y.
    • Nun hab ich, wie oben geschrieben, die 4 Farben mit (6666)BPP gemerged. Das Mergen jedoch vermurkst den Farbverlauf, da bin ich noch am werkeln.
    Zickzack: Sinus:
    Oben ist der niederwertige Verlauf, darunter der höherwertige (in der (6666)-Wichtung).
    Bei Zickzack sind alle der 64^2 = 4096 Pixel verschieden, bei Sinus nur 3568 Pixel.
    Ich hab mal die Farben sortiert, aber da ist die Zyklus-Bedingung flöten gegangen.
    Jou. Für weitere Ideen bin ich dankbar.
    ===
    ZickZack in Originalgröße:

    Spoiler anzeigen

    C#-Quellcode

    1. public struct DoubleVal
    2. : IComparable
    3. {
    4. public uint V1;
    5. public uint V2;
    6. public DoubleVal(double v1, double v2)
    7. {
    8. this.V1 = (uint)Math.Round(v1, MidpointRounding.AwayFromZero);
    9. this.V2 = (uint)Math.Round(v2, MidpointRounding.AwayFromZero);
    10. }
    11. public override string ToString()
    12. {
    13. return string.Format("{{V1 V2}} = {{{0}, {1}}}", this.V1, this.V2);
    14. }
    15. public int CompareTo(object obj)
    16. {
    17. DoubleVal dvObj = (DoubleVal)obj;
    18. // Member vergleichen
    19. if (this.V1 == dvObj.V1)
    20. {
    21. return this.V2.CompareTo(dvObj.V2);
    22. }
    23. return this.V1.CompareTo(dvObj.V1);
    24. }
    25. }
    26. public static DoubleVal AngleToDoubleVal(double angle)
    27. {
    28. // angle ist nun sauber im Bereich (0...359)
    29. angle = ((angle % 360) + 360) % 360;
    30. #if SINUS
    31. // Sinusförmiger Farbverlauf
    32. double sin = (Math.Sin(angle * Math.PI / 180) + 1.0) / 2.0;
    33. double cos = (Math.Cos(angle * Math.PI / 180) + 1.0) / 2.0;
    34. //Console.WriteLine("{0}\t{1}\t{2}", angle, sin, cos);
    35. return new DoubleVal(sin * Cycle.MaxValue, cos * Cycle.MaxValue);
    36. #else
    37. // Zickzack-förmiger Farbverlauf
    38. // "Sinus"-Teil
    39. double valS = 0;
    40. if (angle >= 0 && angle < 90)
    41. {
    42. // 32 ... 63
    43. valS = angle / 90.0 * Cycle.MaxValueH1 + Cycle.MaxValueH;
    44. }
    45. else if (angle >= 90 && angle < 270)
    46. {
    47. // 63 ... 0
    48. valS = Cycle.MaxValue1 - (angle - 90.0) / 180.0 * Cycle.MaxValue1;
    49. }
    50. else
    51. {
    52. // 0 ... 31
    53. valS = (angle - 270.0) / 90.0 * Cycle.MaxValueH1;
    54. }
    55. // "Cosinus"-Teil
    56. double valC = 0;
    57. if (angle >= 0 && angle < 180)
    58. {
    59. // 63 ... 0
    60. valC = (180.0 - angle) / 180.0 * Cycle.MaxValue1;
    61. }
    62. else
    63. {
    64. // 0 ... 63
    65. valC = (angle - 180.0) / 180.0 * Cycle.MaxValue1;
    66. }
    67. //Console.WriteLine("{0}\t{1}\t{2}", angle, valS, valC);
    68. return new DoubleVal(valS, valC);
    69. #endif
    70. }
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „RodFromGermany“ ()

    Rechne am besten erst mal mit Floats im Bereich 0 bis 1. Meine Dreiecksfunktion macht das. Du kannst die zwar auch locker auf Bytes anpassen, aber das macht halt Aufwand. Ich weiß nicht, ob dich das 6666-Schema wirklich zum Ziel führt.
    Ich würde es mal mit dem
    $e^{\mathrm{i} \phi} = \cos \phi + \mathrm i \sin \phi$
    versuchen. Vielleicht lässt sich das ja auch elegant auf das 6666-Schema anwenden. Ich glaube aber, dass das nicht intuitiv für's Auge ist (also nicht elegant ;) ).
    Ich meine an sich ist es ja kein Problem, das auf Intervalle zu mappen, auch die Zyklus-Eigenschaft geht prinzipiell: Du hast ja quasi dein Tupel (die 6666) (a, b, c, d). Wenn du z.B. beschließt, dass
    $a = cos(\phi)$
    und
    $b = sin(\phi)$
    , wäre das Problem ja quasi schon gelöst. Einfach a und b auf die 64 möglichen Werte abbilden und es ist darstellbar. Aber hübsch verlaufend ist das dann nicht, da ja die 6-er-Unterteilung nicht den 8-Bit-Kanälen entspricht.
    Als Abbildung würde ich übrigens die arcus-Funktion wählen, sodass du quasi folgendes erhältst und nicht Sinus und Cosinus berechnen musst:
    $\alpha = \frac{\phi}{\pi}$

    $\beta = 1-\alpha$

    Irgendwie war die Herleitung jetzt dezent umständlich... Das hat jetzt fast nichts mehr mit dem
    $\phi$
    zu tun. ;)

    Für das Paar c und d ist es übrigens analog. Du skalierst a und b entsprechend x (d.h. a = x, b = 1 - x) und c und d entsprechend y (d.h. c = y, d = 1 - y). Beachte, dass x und y zwischen 0 und 1 liegen.

    Viele Grüße
    ~blaze~
    @~blaze~ Vielen Dank für Deine Mühe.
    Das mit a=cos(ϕ), b=sin(ϕ) hab ich ja drinne, es treten in den Extrema halt Dubletten auf.
    Ja, das Mergen ist das Problem, ich hab oben den Code vergessen, ist aber elementar:

    C#-Quellcode

    1. private static Color MergeColor(DoubleVal dv1, DoubleVal dv2)
    2. {
    3. uint value = 0;
    4. value |= dv1.V1;
    5. value <<= 6;
    6. value |= dv1.V2;
    7. value <<= 6;
    8. value |= dv2.V1;
    9. value <<= 6;
    10. value |= dv2.V2;
    11. value |= 0xFF000000; // A-Komponente
    12. return Color.FromArgb((int)value);
    13. }
    Zum Umsortieren ist mir noch eingefallen, dass man die "ästhetische" Reihenfolge sogar von Hand machen kann (jede Achse á 64 Elemente) und dann die Farben per Tabellenzugriff holt und dann das Mergen zu einer einzelnen Or-Verknüpfung wird:

    C#-Quellcode

    1. int col1 = Table1[dv1];
    2. int col2 = Table2[dv2];
    3. return Color.FromArgb(col1 | col2);
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Dir fehlt da noch ein unchecked vor der Konvertierung in int. Allerdings kannst du dir das sogar sparen:

    C#-Quellcode

    1. int value = (0xff << 24) | (dv1.V1 << 18) | (dv1.V2 << 12) | (dv2.V1 << 6) | dv2.V2;

    Du benötigst schon alle 4, da du sonst nicht auf die 24 Bits kommst.
    Verwende übrigens besser kein Color, wenn es sich vermeiden lässt. Die Struktur ist total aufgebläht und besteht nicht nur aus dem ARGB-Farbwert, sondern aus Namen, uvm.
    Wenn du col1 | col2 machst, wie garantierst du, dass die Farben dann eindeutig sind? Eine Funktion durchzulegen halte ich trotzdem für besser, wenn du später das Programm erweiterst. Erfodert einfach weniger Anpassungen.

    Wie gesagt, verwende statt sin und cos am besten einfach direkt
    a = x
    b = 1 - x
    wobei x quasi dein
    $\phi$
    ist. Damit hast du einen linearen Verlauf zwischen den Werten.

    Viele Grüße
    ~blaze~

    ~blaze~ schrieb:

    col1 | col2
    Beides sind Tabellenwerte, die in sich disjunkt sind, da sie über (6666)ARGB generiert wurden.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @~blaze~ Genai so isses. Das verstehe ich unter disjunkt: Keine gemeinsamen Elemente, in diesem Falle Bits.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    ~blaze~ schrieb:

    glatten Verlauf
    Genau das ist mein Problem, siehe die Bilder oben.
    Deswegen reden wir hier, hoffentlich nicht aneinander vorbei. :/
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!