Grayscale - Methoden der Graustufenerzeugung

Graustufen auf einem monochromen (einfarbigen) Display erzeugen? Zur Lösung dieses Problems gibt es generell zwei Lösungsansätze.
Graustufen per Rasterung
Der erste Ansatz nutzt das begrenzte Auflösungsvermögen des menschlichen Auges aus und erzeugt die unterschiedlichen Graustufen mittels Rasterung. Ähnlich der Graustufenrasterung im Druckbereich werden mit der Anzahl gesetzter Pixel verschiedene Helligkeitswerte erreicht. Das wohl bekannteste Muster zur Graustufenerzeugung ist das Schachbrettmuster das den Mittelwert zwischen Schwarz und Weiß markiert. Die meisten Grafikanwendungen bieten die Möglichkeit verschiedene Grautöne mittels Stufenrasterung zu erzeugen. Leider geht das je nach Abstufung (Anzahl der Grautöne) mit einem massiven Verlust an Auflösung einher. Die nachfolgende Abbildung zeigt eine einfache Testanwendung zur Demonstration. Die Rasterung im unteren Farbverlauf ist deutlich zu erkennen. Erst mit genügend Abstand zum Display werden die verschiedenen Pixeldichten als unterschiedliche Grautöne erkannt.
Graustufenerzeugung auf einem monochromen Display
Flackern erzeugt Graustufen (experimentell)
Die zweite Methode ist etwas aufwendiger und ist vergleichbar mit dem Verfahren zur Helligkeitssteuerung von LEDs mittels Pulsweitenmodulation. Einzelne Pixel wirken bei hohen Frequenzen abhängig von ihrer Anzeigedauer dunkler oder heller. In der Demoanwendung wird dies mit einem einfachen Binärzähler (Variable "pattern") erreicht. Im oberen Teil der Anzeige werden so 8 verschiedene Graustufen erzeugt. Das Experiment zeigt, dass manche Grautöne flackern und andere stabil erscheinen. Dies ist auf den Alias-Effekt zurückzuführen. Der Displaycontroller zeigt den Displayinhalt mit einer bestimmten Bildwiederholfrequenz an und es kommt zum Stroboskopeffekt.
Die Demoanwendung kommt mit dem Standardbefehlssatz der Display-API aus. Hier geht's zum Download.
#include "Display.h"
#include "SPI.h"
#include "Bitmap.h"

Display lcd = Display();
byte pattern = 0;

void setup() {
  lcd.init(20);
  lcd.drawBitmap(0, 0, 128, 62, bitmap);
  lcd.show();
}

void loop() {
  lcd.setPageAddress(0);
  lcd.setColumnAddress(0);
  drawWhiteRect();
  for (int i=0; i<8; i++) {
    if(pattern & (i+1) * (i+1)) {
      drawBlackRect();
    } else {
      drawWhiteRect();
    } 
  }
  drawBlackRect();
  pattern++;
}

void drawBlackRect() {
  for (int i=0; i<8; i++) {
    lcd.writeData(0xFF);
  }
}

void drawWhiteRect() {
  for (int i=0; i<8; i++) {
    lcd.writeData(0x00);
  }
}
Links und Referenzen: