geloest: LED Matrix Texture Scaling

Moin,

es geht um lineares runterskalieren eines 2d Byte Arrays in ein Rechteck.

x1, y1, x2, y2 sind Eck-Koordinaten als Bytes, noise ist ein 2d Byte Array. kMatrixWidth und kMatrixHight ist die Groesse des Noise Arrays (16x16).

  for(int i = x1; i <= x2; i++) {
    for(int j = y1; j <= y2; j++) {
      uint8_t index = noise[i * kMatrixWidth / (x2-x1)][j * kMatrixHeight / (y2-y1)];
      uint8_t bri =   255;
      CRGB color = ColorFromPalette( currentPalette, index, bri);
      leds[XY(i,j)] = color;
    }
  }

Funktioniert nicht wie geplant. Immer Geflacker in der letzten Zeile/Spalte, bei machen Koordinatenkombinationen nur ein gleichfarbiges Rechteck.

Warum? Rundungsfehler bei der Division?

Andere Formel bzw. diese Formel umstellen oder mit 16 Bit Werten rechnen und am Ende nur die oberen 8 Bit verwenden?

Oder wie wuerdet Ihr das machen?

Beste Gruesse

Helmuth

edit: Ok, habe es hinbekommen.

[code]
  for(int i = x1; i <= x2; i++) {
    for(int j = y1; j <= y2; j++) {
      uint8_t index = noise[i * (kMatrixWidth-1) / (x2-x1)][j * (kMatrixHeight-1) / (y2-y1)];
      uint8_t bri =   255;
      CRGB color = ColorFromPalette( currentPalette, index, bri);
      leds[XY(i,j)] = color;
    }
  }

[/code]

Doch nicht. Es gibt immer mal wieder Koordinatenkombinationen, wo das Ergebnis Scheixxe aussieht.

Warum?

Vielleicht ein Integer-Überlauf bei der Multiplikation? Weiß nicht ob das Zwischenergebnis so hoch werden kann.

Dachte ich auch, aber nein: kMatrixWidth und kMatrixHeight sind je 16, die uebergebene Koordinate maximal 15, Ergebnis passt also auf jeden Fall in ein Byte.

Hier mal ein kurzes Video. Wenn x1 und y1 jeweils 0 sind (in meiner Matrix oben links) ist alles fein.

Das Problem tritt nur manchmal auf, wenn x1 und x2 ungleich 0 sind...

Hi Leute,

ich verzweifle langsam. Habe mir jetzt die halbe Nacht um die Ohren geschlagen und ich finde den verdammten Fehler einfach nicht.

Ich habe aus dem Sketch alles unnoetige rausgeworfen, es ist nur noch dieses Gerippe uebrig:

#include<FastLED.h>

#define LED_PIN     13
#define BRIGHTNESS  100
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB

const uint8_t kMatrixWidth  = 16;
const uint8_t kMatrixHeight = 16;
const bool    kMatrixSerpentineLayout = true;

// Counter für die Bewegung des Rechtecks
byte c1 = 0;
byte c2 = 64;
byte c3;

#define NUM_LEDS (kMatrixWidth * kMatrixHeight)
#define MAX_DIMENSION ((kMatrixWidth>kMatrixHeight) ? kMatrixWidth : kMatrixHeight)

CRGB leds[kMatrixWidth * kMatrixHeight];

uint8_t noise[MAX_DIMENSION][MAX_DIMENSION];

CRGBPalette16 currentPalette( RainbowStripeColors_p );

uint8_t       colorLoop = 0;

void setup() {
  delay(500);
  LEDS.addLeds<LED_TYPE,LED_PIN,COLOR_ORDER>(leds+5,NUM_LEDS-5);
  LEDS.setBrightness(BRIGHTNESS);
}

// Fuellt noise[16][16] einfach mit 0-255 
void fillnoise8() {
  for(int i = 0; i < MAX_DIMENSION; i++) {
    for(int j = 0; j < MAX_DIMENSION; j++) {
      byte data = i * kMatrixWidth + j;
      noise[i][j] = data;
    }
  }
}

void MapNoiseToRectangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2)
{
  static uint8_t ihue=0;
  for(int i = x1; i <= x2; i++) {
    for(int j = y1; j <= y2; j++) {

      uint8_t index = noise[i * (kMatrixWidth-1) / (x2-x1)][j * (kMatrixHeight-1) / (y2-y1)];
      uint8_t bri =   255;

      if( colorLoop) {
        index += ihue;
      }

      CRGB color = ColorFromPalette( currentPalette, index, bri);
      leds[XY(i,j)] = color;
    }
  }
  ihue+=1;
}

void CLS() {
  for(int i = 0; i <NUM_LEDS; i++) {
    leds[i]=0x000000;
  }
}

void loop() {
  CLS();
  fillnoise8();
  
  c3++;
  if (c3%4==0) {
    c1++;
    c2+=2;
  }
  
  // sin8 liefert einen Wert zw. 0 u. 255 zurueck
  // die ersten 2 Wete bestimmen die Ecke oben links
  // 15/15 ist die Ecke unten rechts
  MapNoiseToRectangle(sin8(c1)/20, sin8(c2)/20, 15, 15);
  
  LEDS.show();
}

// LED Mapping
uint16_t XY( uint8_t x, uint8_t y)
{
  uint16_t i;
  if( kMatrixSerpentineLayout == false) {
    i = (y * kMatrixWidth) + x;
  }
  if( kMatrixSerpentineLayout == true) {
    if( y & 0x01) {
      uint8_t reverseX = (kMatrixWidth - 1) - x;
      i = (y * kMatrixWidth) + reverseX;
    } 
    else {
      i = (y * kMatrixWidth) + x;
    }
  }
  return i;
}

Wenn x1 und y1 (obere linke Ecke) jeweils 0 sind, funktioniert die Skalierung fuer alle x2 und y2.

Aber wenn x1 und y2 andere Werte annehmen, funktioniert es bei manchen Kombinationen, bei anderen nicht.

Die meisten Probleme treten auf, wenn x2 und y2 den Wert 15 haben (entspricht der unteren rechten Ecke). → siehe Video unten

Offensichtlich tritt das Problem auch nur in y Richtung auf, waehrend exakt die gleiche Formel in x Richtung immer funktioniert.

Hier noch ein Video des Verhaltens mit verschiedenen Koordinaten fuer die obere linke Ecke. Der “gestreifte Regenbogen” ist das korrekte Bild welches ich proportional verkleinert zu sehen wuensche.

Mir faellt wirklich nichts mehr ein, warum das nicht funktioniert.

Koennt ihr bitte mal ueber den Sketch schauen und mir jede noch so absurde Vermutung mitteilen, was das verdammt Problem sein koennte. Danke.

Helmuth.

Hi
Als erstes würde ich mal die beiden skalierten x und y Koordinaten per Serial ausgeben lassen:

uint8_t index = noise[i * (kMatrixWidth-1) / (x2-x1)][j * (kMatrixHeight-1) / (y2-y1)];

umwandeln in

uint8_t scX = i * (kMatrixWidth-1) / (x2-x1);
uint8_t scY = j * (kMatrixHeight-1) / (y2-y1);

Serial.print(scX);
Serial.print(",");
Serial.println(scY);

uint8_t index = noise[scX][scY];

Da müsste man eigentlich sofort sehen was das Problem ist. Habs nicht ausprobiert. Ist bestimmt ein Rundungsproblem.

Gruß
Jarny

Und noch was ist mir aufgefallen, was aber von dem eigentlichen Problem unabhängig ist:

a) Wenn das Zielrechteck kleiner wird bleiben die Pixel am Rand natürlich nocht sichtbar von der alten Skalierung als der Zielbereich noch größer war. Du beschreibst ja nur die neuen Pixel und lässt die alten so wie sie vorher waren.

b) Kleiner Schönheitsfehler beim Programmieren: Die beiden verschachtelten Schleifen für die X- und Y-Richtung programmiert man üblicherweise so, dass die Schleife für Y (also die Zeilen) außen stehen und die Schleife für X innen. Da entspricht der Reihenfolge wie früher Fernsehbilder gezeichnet wurden oder wie man ein Buch liest etc.. .
Ist aber nur ein ästhetischer Aspekt und das kann jeder machen wie er will.

c) Kleine Optimierung wenn denn das eigentliche Problem gelöst ist: (x2-x1) und (y2-y1) kannst du aus der innersten Schleife rausziehen und vor den Schleifen nur einmal ausrechnen. Spart dir ne Menge Taktzyklen wegen der doppelten Verschachtelung.

Gruß
Jarny

Hi Jarny,

danke fuer Deine Antwort und Anmerkungen! Werde die Formel mal aufdroeseln und alle Operanden einzeln beobachten.

zu a) Deshalb das CLS() am Anfang vom Main Loop.
zu b) Fuer solche Stilhinweise bin ich immer dankbar. Zeilen- und nicht spaltenweise scannen macht Sinn.
zu c) Ja, die fixen Operanden nur einmal berechnen geht natuerlich schneller... schaem

Bericht folgt.

Mit den besten Gruessen

Helmuth

Ok, Daniel Garcia hat den Fehler gefunden. Die richtige Formel lautet

uint8_t index = noise[(i-x1) * (kMatrixWidth-1) / (x2-x1)][(j - y1) * (kMatrixHeight-1) / (y2-y1)];

Fuer die korrekte Position in meiner Lookup Tabelle muss ich von den Countern noch die Startposition abziehen. Deshalb hat es mit x1=0 und y1=0 auch funktioniert - und mich zu falschen Schlussfolgerungen verleitet...

Der Teufel steckt im Detail...

Beste Gruesse und guten Morgen,

Helmuth

Freut mich, dass das Problem gelöst ist. :slight_smile:

Noch eine Anmerkung: Richtig schnell skalieren kann man mit dem Bresenham-Algorithmus (Ursprünglich zum schnellen Zeichnen von Linien und Kreisen erfunden, jedoch von der Grundidee her auch fürs Skalieren von Bildern geeignet).
Die zwei Divisionen und zwei Multiplikationen in deiner innersten Schleife sind von den Taktzyklen her gesehen sicherlich Faktor 10 langsamer als eine auf der Bresenham-Idee basierten Skalierung.
Falls solche Skalierungsalgorithmen in die FastLED-Lib Einzug erhalten, hoffe ich, dass Daniel Garcia die Bresenham-Algorithmen kennt. Bis jetzt hat er ja eine Wahnsinnsarbeit in die Optimierung der Lib reingesteckt. Hut ab vor ihm.

Gruß
Jarny

Das ist eine super Idee! Ich verwende diesen Algorithmus selbst zum Linien zeichnen. Klar, beim Skalieren hat man grundsaetzlich das gleiche Problem... tolle Anregung, echt!

Daniel macht in erster Linie die ganzen Low Level Backend Routinen. Was im Moment viel Arbeit ist, weil es jede Menge gute neue Controllerboards gibt, die alle andere Prozessoren draufhaben... Und natuerlich auch neue LED Controller - moegen die scheixxlangsamen WS2801 basierten Mistdinger endlich aussterben! :wink:

Die Idee fuer FastLED 3.0 ist die, Farbmanagement, Effektmanagement, Geschwindigkeitsmanagement und natuerlich die ganze Protokollebene fuer die LED-/Prozessorkommunikation komplett voneinander zu trennen.

Dann kann man problemlos und mit nur minimalen Veraenderungen im Code den Prozessor, das LED Setup (Mapping), den Effekt selbst, die Farben, die Geschwindigkeit und Richtung des Effekts und der Farben, die beeinflussenden Faktoren (Audio, externe Events, Oszillatoren) austauschen...

Eine BasisAPI ist auch vorgesehen, aber da diskutieren wir noch, welchen Ansatz wir verfolgen wollen, welche Hardware voraussetzen, wie universell oder speziell es werden soll, ...

Im Moment testen wir jede Menge Idee und wenn sich eine als gut herausstellt, uebersetzt Daniel oder Mark die Funktion in "schnell". Ich selbst bin da noch ganz am Anfang, was Codeoptimierung angeht... und je mehr ich mich damit beschaeftige und neue Dinge lerne, umso mehr merke ich, dass ich eigentlich noch gar nichts weiss...
Und wenn Daniel einmal eine Funktion schreibt, kann man sich sicher sein, dass da der allerletzte Takt rausoptimiert wurde. Deswegen dauert das auch alles etwas laenger mit der Entwicklung - weil alles ueber Monate hinweg weiteroptimiert wird, bevor es auch nur als Alpha Branch zum Testen rausgeht. Der Qualitaetsanspruch ist wirklich sehr hoch.

Mein Job ist wohl eher das Schreiben von Effekten und Showreels, um zu zeigen, was alles geht. Ich plane auch eine eigene Effektbibo, welche entweder irgendwann eigenstaendig als FastLED Erweiterung existieren wird oder vielleicht in Auszuegen mit bei den Beispielen einziehen wird. Bin gespannt, wo das alles noch hingeht!

Jedenfalls will ich in Kuerze auf keiner LED Installation irgenwo mehr scrollende Rainbows sehen, das ist sooo ...gaehn... 2010. :wink:

Beste Gruesse und gute Ideen sind immer willkommen!

Helmuth