PHP (Gute) Zufallsfarben generieren

  • Ersteller Ersteller It'sNever2Late!
  • Erstellt am Erstellt am
I

It'sNever2Late!

Gast
Hi,

ich sitze gerade an einem kleinen Modul für Drupal 6 welches ich fertig gestellt habe.
Es soll dazu dienen vorhandene Formulare (Webform) zu analysieren und im Endeffekt eine Grafik anzuzeigen.

Das klappt auch soweit ganz gut.
Ich habe ein Select-Feld eingebaut, bei dem ich mir das Farbschema aussuchen kann.
Und ich habe mir noch ne kleinere Funktion geschrieben, die mir (je nach Farbschema) einen anderen Farbton liefert.

Ich möchte mit meiner Funktion keine absoluten zufalls-Farben bekommen sondern beispielsweise verschiedene Rot-Töne.

Die einzigen Bedingungen die ich habe sind, dass ich das nicht irgendwie mit Zufallsklassen (also per CSS) regeln möchte, da das in diesem Fall gar nicht geht und, dass die Farben als Hex-Zahl vorliegen müssen.

Meine Funktion wird für jeden einzelnen Vergleichswert aufgerufen und sieht wie folgt aus:
PHP:
function chart_give_color($chart_id, $i){
  $res = db_query("SELECT color FROM chart_items WHERE chart_id = %d", $chart_id);
  $color = db_result($res);

  if($i == 0) $i = 60;
    else $i = rand(10,99);

  $tmp = rand(0,9);
  $sw = array();
  $sw[] = '000000'; $sw[] = '1a1a1a'; $sw[] = '333333'; $sw[] = '4c4c4c'; $sw[] = '666666';
  $sw[] = '808080'; $sw[] = '999999'; $sw[] = 'b2b2b2'; $sw[] = 'cccccc'; $sw[] = 'e6e6e6';
  $custom1 = $sw[$tmp];
  $ac = array();
  $ac[] = 'ff0000'; $ac[] = '0000ff'; $ac[] = '00ff00'; $ac[] = 'ffff00'; $ac[] = '660099';
  $ac[] = 'ff6600'; $ac[] = '33ffcc'; $ac[] = 'ff00ff'; $ac[] = '0066ff'; $ac[] = '990000';
  $custom2 = $ac[$tmpz];

  switch($color){
    case 'or': return NULL;                 // Standard (Orange)
    case 'ro': return $i."0000";            // Rot
    case 'bl': return "0000".$i;            // Blau
    case 'gr': return "00".$i."00";         // Grün
    case 'sw': return $custom1;             // Schwarz/Weiß
    case 'ac': return $custom2;             // Bunt
  }
}

$chart_id =ID vom Chart
$i = Aktueller Vergleichswert

Blau (z.B.) sieht aktuell so aus, was nicht so toll ist:
chart_bl7dxap.png


Ich hoffe, dass ihr mich verstanden habt und mir helfen könnt.

Gruß
 
Also ich würde zunächst 3 Zufallszahlen von x bis y erzeugen, je nachdem welches farbspektrum man will.
Angenommen erstmal 0 bis 255, je eine für R/G/B.
Dann kann man eventuell noch vergleichen, ob sich die Farben genug unterscheiden (etc), und anschließen in
hex konvertieren.
Code:
$r1, g1, b1;
r1=rand(0,255);
g1=rand(0,255);
b1=rand(0,255);
if(r1-25<g1){...} //experimentieren
$farbe = dechex(r1) . dechex(g1) . dechex(b1);
Ich denke mal, dass man, wenn erst dezimale zahlen benutzt werden, noch besser filtern kann.
MfG
Damon
 
Ich würde statt Zufallszahlen vielleicht eher eine Art Berechungsstrategie überlegen, die auch immer zu den selben optischen Ergebnissen führt.
 
Wenn es echt zufällig sein soll im Sinne von:" Es gibt endlos viele Möglichkeiten und diese sehen alle zueinander ähnlich unterschiedlich aus" solltest du unbedingt einen anderen Farbraum als RGB benutzen.
Dh das Generieren der Farben passiert zB in LAB und du konvertierst anschließend nach RGB-Hex für die Anzeige.

Wieso? RGB ist aus menschlicher Sicht sehr unlinear. Dh wenn du zwei Farben miteinander vergleichst wie 100/0/0 zu 150/0/0 und einen "Abstand" im Sinne von "Ähnlichkeit" ermittelst würde jeder Mensch die Differenz der beiden Farben stark anders beurteilen als den Abstand zwischen 0/100/0 und 0/150/0, obwohl rein auf die Zahlen geguckt die Differenz die selbe ist. Das liegt daran, dass das Menschliche Auge für die unterschiedlichen Farbkanäle unterschiedlich empfindlich ist. Ums noch zu verkomplizieren verändert sich diese Empfindlichkeit mit der Helligkeit.
Genau für diese Problematik gibt es eben Farbräume wie LAB und LUV die fürs menschliche Auge "normiert" sind. (L ist hierbei die Farbunabhängige Helligkeit, dh der Farbton besteht nur aus ab bzw uv).

Generierst du jetzt ganz simpel zwei Zufallszahlen a und b und stellst dir das wie in der Schule als x/y Ebene vor kannst du beliebig weitere Zufallszahlen mit einem vorgegebenen Abstand generieren und erhälst so immer gleiche "Kontraste" in dem Sinne dass jede Kombination von Farben immer gleich gut oder schlecht lesbar sein wird. Außerdem ist es so ganz leicht dafür zu sorgen, dass der Hintergrund immer zB 40% dunkler als der Rest der Seite ist.

Für den Anfang reichts natürlich auch mit RGB.. die Erweiterung um einen sinnvolleren Farbraum kann man auch später ergänzen.
Wenn du nur den Punkt mit der getrennt berechenbaren Helligkeit interessant findest und dir LAB/LUV zu kompliziert ist könntest du auch HSV benutzen. Hier gibts auch nen eigenen Helligkeitskanal aber der Farbraum ist nicht auf die menschliche Wahrnehmung normiert. Außerdem ist die Konvertierung von RGB von und nach HSV einfacher zu programmieren.
 
Zuletzt bearbeitet:
Hi,

erstmal danke für die Antworten.

Um ehrlich zu sein finde ich die Antwort von 1668mib am sinnvollsten, da es bei Zufallszahlen immer die realistische Wahrscheinlichkeit gibt 2 aufeinanderfolgende sehr ähnliche Farbtöne zu erwischen, was mich kein Stück weiter bringt.

Nur muss ich mir wahrscheinlich eine sehr gute Formel überlegen die als wichtigsten Wert $i miteinbezieht und dann immer in einem Abstand von x einen anderen Farbton liefert.
Ich glaube aber, dass ich innerhalb dieser Formel einen anderen Farbraum nutzen muss, da ich dann (zumindest glaube ich das) eine größere Spannweite habe.

@DaMoN1993
Ich möchte in jedem Fall eine Schleife vermeiden, da ich meist 2 - 7 Vergleichswerte habe und insgesamt 8 - 10 Diagramme auf seiner Seite anzeigen lasse und da würden mehrere Schleifen mit x if-Abfragen vermutlich (rel.) lange brauchen.

@Crys
10 verschiedene Farben sind leider zu wenig, du siehst ja wie sehr sich die nacheinanderfolgenden Farben ähneln.

Ich möchte noch betonen, dass es nicht immer eine ganz andere Farbe sein muss, man sollte einfach (stark) unterschiedliche verschiedene nacheinanderfolgende Blautöne sehen um das Diagramm gut deuten zu können.
 
Zuletzt bearbeitet:
Danke, kuddlmuddl. Da habe ich auch noch was dazu gelernt! :freaky:

Ich hab mich mal versucht das in php um zurechnen, nach diesen Formeln, aber ich bin gescheitert:
PHP:
<?php
// rgb2 in xyz umrechnen
function _rgb2xyz($r, $g, $b) {
	$x = 0.041556*$r + 0.357580*$g + 0.180423*$b;
	$y = 0.212671*$r + 0.715160*$g + 0.072169*$b;
	$z = 0.019334*$r + 0.119193*$g + 0.950227*$b;
	$xyz = array($x, $y, $z);
		
	foreach ($xyz as &$wert) {
		$wert = round($wert);
	}
		
	return $xyz;
}

// xyz in rgb oder hex umrechnen
function _xyz2rgb($x, $y, $z, $hex = false) {
	$r =  3.240479*$x + -1.537150*$y + -0.498535*$z;
	$g = -0.969256*$x +  1.875992*$y +  0.041556*$z;
	$b =  0.055648*$x + -0.204043*$y +  1.057311*$z;
	$rgb = array($r, $g, $b);
		
	foreach ($rgb as &$wert) {
		$wert = round($wert);
		
		if ($wert > 255) {
			$wert = 255;
		} elseif ($wert < 0) {
			$wert = 0;
		}
	}
		
	if ($hex == true) {
		return str_pad(dechex($rgb[0]), 2, 0, STR_PAD_LEFT).str_pad(dechex($rgb[1]), 2, 0, STR_PAD_LEFT).str_pad(dechex($rgb[2]), 2, 0, STR_PAD_LEFT);
	} else {
		return $rgb;
	}
}

$x = array(128, 128, 128);
print_r($x); echo "<br/>";

$x = _rgb2xyz($x[0], $x[1], $x[2]);
print_r($x); echo "<br/>";

$x = _xyz2rgb($x[0], $x[1], $x[2]);
print_r($x); echo "<br/>";
?>

Es kommt das heraus:
Code:
Array ( [0] => 128 [1] => 128 [2] => 128 )
Array ( [0] => 74 [1] => 128 [2] => 139 )
Array ( [0] => 0 [1] => 174 [2] => 125 )
Zeile 1 und 3 sollte aber gleich sein!

rgb2xyz2rgb.png
Eine Ahnung, was falsch ist?
 
Muss in Zeile 10 nicht folgendes stehen:
PHP:
$wert = round($xyz[$wert]);

Oder übernimmt der Pointer das?
 
It'sNever2Late! schrieb:
@DaMoN1993
Ich möchte in jedem Fall eine Schleife vermeiden, da ich meist 2 - 7 Vergleichswerte habe und insgesamt 8 - 10 Diagramme auf seiner Seite anzeigen lasse und da würden mehrere Schleifen mit x if-Abfragen vermutlich (rel.) lange brauchen.

@Crys
10 verschiedene Farben sind leider zu wenig, du siehst ja wie sehr sich die nacheinanderfolgenden Farben ähneln.

Ich möchte noch betonen, dass es nicht immer eine ganz andere Farbe sein muss, man sollte einfach (stark) unterschiedliche verschiedene nacheinanderfolgende Blautöne sehen um das Diagramm gut deuten zu können.
Verstehe ich nicht ganz, dann würden ja 10 Farben ausreichen, wenn jedes Diagramm nur 2-7 Werte hat!?
Also einfach per Zufall Farbe 1 bis 10 nehmen, dazwischen noch testen ob die benachbarte Farbe gleich ist.
Eine If-Abfrage braucht nur einen hundertstel Bruchteil der Zeit, was das rendern eines Diagramms braucht, dass sollte nicht das Problem sein.
Ergänzung ()

@ It'sNever2Late:
Nein, wenn dann:
PHP:
	foreach ($xyz as $key => $wert) {
		$wert = round($xyz[$key]);
	}
Aber das ist das selbe, wie ich geschrieben habe, nur meins ist kürzer
 
Das kann man leicht übergeben ;)
PHP:
function chart_give_color($chart_id, $i, $letzte_farbe = false){
    // ...
     
	while (1) {
		switch($color){
			case 'or': $return = NULL; // Standard (Orange)
			case 'ro': $return = $i."0000"; // Rot
			case 'bl': $return = "0000".$i; // Blau
			case 'gr': $return = "00".$i."00"; // Grün
			case 'sw': $return = $custom1; // Schwarz/Weiß
			case 'ac': $return = $custom2; // Bunt
		}
		if ($return != $letzte_farbe) return $return;
	}
}
 
Zuletzt bearbeitet:
Leider nicht.

Also, ich lade mir alle Charts (chart_id), die zu der aktuellen Seite (node) gehören und das mache ich mit einer while Schleife.

PHP:
$i = 0;
while($chart_id = db_result($res){
$color = chart_give_color($chart_id, $i);
$i++;
}
Die aktuelle oder vorherige Farbe wird nirgends gespeichert.

Obwohl ich ja auch den dritten Parameter optional machen kann und ein Array mit aktueller und (sofern vorhanden) letzer Farbe zurückgeben kann.

Also immer ein kleines Hin und Her. :)

Wäre sogar sehr simpel umsetzbar und ich bräuchte $i nicht mehr.
 
Der 3te-Paramter von meiner Fkt. im letzten Post ist ja schon optional (wenn nicht gegeben ist der false und keine Farbe kann ja false sein)
PHP:
$i = 0;
while($chart_id = db_result($res){
	$color = chart_give_color($chart_id, $i, $color);
	$i++;
}

verstehe aber nicht was das macht ... $color wird ja immer nur überschrieben!?
 
Hi,

sorry für den falschen Code, ich hatte das ausm Kopf gemacht und hatte die Datei nicht vor mir. Hier der korrekte Code:
PHP:
  $i = 0;
  while($bla = db_result($res)){
    $chart['#data'][$i] = $bla;
    $chart['#labels'][$i] = $labels[$i];
    $chart['#data_colors'][$i] = chart_give_color($chart_id, $i);
    $i++;
  }

Ich habe meine Funktion nochmals überarbeitet und finde diese Lösung recht ansehlich:
PHP:
function chart_give_color($chart_id, $i){
  $res = db_query("SELECT color FROM chart_items WHERE chart_id = %d", $chart_id);
  $color = db_result($res);

  $ro = array(); $ro[] = 'ff0000'; $ro[] = '7d0000'; $ro[] = 'ff7979';
  $bl = array(); $bl[] = '0008ff'; $bl[] = '00aeff'; $bl[] = '4566af';
  $gr = array(); $gr[] = '00ff23'; $gr[] = '00fa11'; $gr[] = '7Dff8f';
  $sw = array(); $sw[] = '383838'; $sw[] = '8e8e8e'; $sw[] = 'cccccc';
  $ac = array(); $ac[] = 'ff00e1'; $ac[] = 'ff7a00'; $ac[] = 'fcff00';

  if(($i % 3) == 0) $m = 2;
  else if(($i % 2) == 0) $m = 1;
    else $m = 0;

  switch($color){
    case 'or': return NULL;                 // Standard (Orange)
    case 'ro': return $ro[$m];              // Rot
    case 'bl': return $bl[$m];              // Blau
    case 'gr': return $gr[$m];              // Grün
    case 'sw': return $sw[$m];              // Schwarz/Weiß
    case 'ac': return $ac[$m];              // Bunt
  }
}

Ich muss nur noch die Farben anpassen, danach sollte es nie zwei aufeinander folgenden Farben geben.
Das hat man davon wenn man zu kompliziert denkt.
 
Deine Lösung finde ich ganz gut, weil sie sehr leicht verständlich ist.
Du kannst die Farben gleich alle in eine Map packen.

PHP:
// ...

$colors = [
  'ro' => ['ff0000', '7d0000', 'ff7979'],
  'bl' => ['0008ff', '00aeff', '4566af'],
  'gr' => ['00ff23', '00fa11', '7Dff8f'],
  'sw' => ['383838', '8e8e8e', 'cccccc'],
  'ac' => ['ff00e1', 'ff7a00', 'fcff00']
];

// ...

if (isset($colors[$color]))
  return $colors[$color][$m];
else
  return null;
 
Hi,

ob ich bei Orange ein NULL oder FALSE zurückbekomme ist egal, daher ist die if-Abfrage am Ende nicht notwendig.
Mit folgendem Code klappt es wunderbar:
PHP:
function chart_give_color($chart_id, $i){
  $res = db_query("SELECT color FROM chart_items WHERE chart_id = %d", $chart_id);
  $color = db_result($res);

  if(($i % 3) == 0) $m = 2;
  else if(($i % 2) == 0) $m = 1;
    else $m = 0;

  $colors = array(
    'ro' => array('ff0000', '7d0000', 'ff7979'),
    'bl' => array('0008ff', '00aeff', '4566af'),
    'gr' => array('00ff23', '00fa11', '7Dff8f'),
    'sw' => array('383838', '8e8e8e', 'cccccc'),
    'ac' => array('ff00e1', 'ff7a00', 'fcff00'),
  );

  return $colors[$color][$m];
}

Ich würde gerne wissen warum du eckige Klammern für das verschachtelte Array verwendet hast.
Denn ich habe nach dem Kopieren und Ausführen deiner Zeilen einen Syntax-Fehler bekommen.
 
Ohne das if bekommst du einen Fehler (Notice), wenn aus der DB ein nicht-existenter Key kommt, also bspw. auch bei deinem Fallback/Default "or". Sowas macht man nicht. PHP ist zwar frickelig, aber man muss es nicht provozieren.

Die Array-Syntax ist von PHP 5.4.

In deiner Zeile 14 ist noch ein Komma zu viel.
 
PHP 5.3.6 wird hier aktuell noch verwendet.

Gut, ich werde die if-Abfrage einbauen und das Komma in Zeile 14 entfernen.

Beides gibts zwar keine Fehlermeldung aus, aber ein Notice, welche hier anscheinend deaktiviert worden sind.

PHP ohne Notice und Warnings verwöhnt einen zu sehr für andere Sprachen. :D
 
Zurück
Oben