namespace cpp

C++ lernen, kennen, anwenden

Benutzer-Werkzeuge

Webseiten-Werkzeuge


anwenden:images

Image : Grafik-Programmierung ohne Grafik-Treiber

Du sollst dir kein Bildnis noch irgendein Gleichnis machen,
weder des, das oben im Himmel,
noch des, das unten auf Erden,
oder des, das im Wasser unter der Erde ist.
— 2. Mose 20, 4

Wir verstoßen ständig gegen das zweite Gebot. Schlachten wir eine weitere heilige Kuh: "Grafikprogrammierung ist nichts für Anfänger. Dazu braucht man schnelle Rechner, teure Grafikkarten und Programmierbibliotheken mit hohem Einarbeitungsaufwand (OpenGL, DirectX, SDL, …)." Nichts da. Es geht nicht um 3D-Ego-Shooter. Es geht nicht um: "Mein Grafikprozessorkühler ist 2 cm länger als deiner." Servergenerierte Aktiencharts brauchen keine Framerate.

Vor dem Jahr 1990 habe ich 9-Nadel-Drucker punktweise angesteuert im Epson-Escape(P)-Befehlssatz, weil ich computerberechnete Ergebnisse in Bildform ausdrucken wollte. Grafikkarte jenseits von Basic-Kleincomputern? Fehlanzeige. Man braucht einen geordneten Zugriff auf Bildpunktdaten im Hauptspeicher und einen Speicher, auf dem Bilder abzulegen sind. Alles weitere ist Routine. Und es gibt eine Menge dabei zu lernen. Willkommen im Bereich der Retroprogrammierung.

Aufgabe

Erstelle die Grundlagen der Computergrafik. Gehe in folgenden Schritten vor:

  1. Definiere 32-Bit-Farben (je 8 Bit für rot, grün, blau, Transparenz).
  2. Bilder beliebiger Größe sind als Klasse zu vereinbaren.
  3. Speichere das Bild als Datei (z.B. BMP, einfaches Datenformat).

Der Zugriff auf die Farben einzelner Bildpunkte soll einfach und robust sein.

Lösung

Klasse Color

Die vier Farbinformationen werden in einer Klasse gebündelt.

color.h
//: color.h : Grafik-Bibliothek ohne Grafiktreiber - R.Richter 2011-01-15
/////////////////////////////////////////////////////////////////////////
 
#ifndef COLOR_H
#define COLOR_H
 
// Farben, 24 bit Farbtiefe mit Transparenz (Alpha-Kanal)
 
class Color
{
public:
  typedef unsigned char Channel;
  typedef unsigned long RGBAlpha;
 
  enum 
  {
    NONE   = 0xFF000000,
    BLACK  = 0x00000000,
    WHITE  = 0x00FFFFFF,
    RED    = 0x00FF0000,
    GREEN  = 0x0000FF00,
    BLUE   = 0x000000FF,
    YELLOW = 0x00FFFF00
  };
 
  Color(Channel red, Channel green, Channel blue, Channel alpha = 0)
  : value( rgba(red, green, blue, alpha ) )
  {
  }
 
  Color(RGBAlpha color = 0xff000000) // default: transparent
  : value(color)
  {
  }
 
  Channel alpha() const { return Channel(value>>24); }
  Channel red()   const { return Channel(value>>16); }
  Channel green() const { return Channel(value>>8);  }
  Channel blue()  const { return Channel(value);     }
 
  operator unsigned long() const { return value; }
private:
  RGBAlpha value;
 
  static RGBAlpha rgba(Channel red, Channel green, Channel blue, Channel alpha)
  {
    return   RGBAlpha(alpha)<<24 | RGBAlpha(red)<<16
	   | RGBAlpha(green)<<8  | RGBAlpha(blue);
  }
};
 
#endif // COLOR_H
//~

Intern werden die Daten zu einer Langzahl zusammengefasst. Für den einfachen Umgang werden einige Standardfarben festgelegt. Die Farbklasse ist unvollständig:

  • Welche weiteren Farben sind als Standardfarben zu definieren?
  • Welche Rechengesetze gelten für Farben ?

Klasse Image

Ein Bild hat eine bestimmte Breite und eine bestimmte Länge, die nachfolgend nicht mehr änderbar ist. Solange noch kein Bildpunkt bemalt wurde, ist das Bild durchsichtig, es sei denn, eine Hintergrundfarbe wurde festgelegt. Einzelne Bildpunkte können als pixel(x,y) abgefragt und auf eine neue Farbe gesetzt werden. Der Zugriff auf Punkte außerhalb des Bildes bleibt folgenlos.

image.h
//: image.h : Grafik-Bibliothek ohne Grafiktreiber - R.Richter 2011-01-15
/////////////////////////////////////////////////////////////////////////
 
#ifndef IMAGE_H
#define IMAGE_H
 
#include <algorithm>
#include <vector>
#include "color.h"
 
class Image
{
public:
  Image(unsigned int width, unsigned int height, Color background = 0)
  : width_(width), height_(height), pixels_(width * height, background)
  {
  }
 
  unsigned int width()  const { return width_;  }
  unsigned int height() const { return height_; }
 
  Color& pixel(unsigned int x, unsigned int y) 
  {
    return x>=width() || y>=height() ? outside() : pixels_[y*width() + x];
  }
 
  const Color& pixel(unsigned int x, unsigned int y) const 
  {
    return x>=width() || y>=height() ? outside() : pixels_[y*width() + x];
  }
private:
  unsigned int width_, height_;
  std::vector<Color> pixels_; // width_ x height_ pixels
 
  static Color& outside() { static Color c; return c = Color::NONE; }
};
 
// ===[ Speichern als BMP 24bit unkomprimiert, ohne Transparenz ]===========
 
#include <fstream>
#include <iostream>
#include <iomanip>
#include <string>
 
// Achtung, nicht portabel: Intel Byte-order vorausgesetzt
 
inline  // a bit too long for ...
void saveBMP(std::string filename, const Image& image)
{
  const unsigned long linesize  = 3*image.width();  // 24 bit per pixel
  const unsigned long fillbytes = (4-linesize%4)%4; // at end of line to quad boundary
  const unsigned long imagesize = (linesize+fillbytes)*image.height();
 
  // byte alignment, no padding
  // #if defined( _MSC_VER ) || defined( __MINGW32__ )
  #pragma pack(1)
  // #endif
  struct BMPheader
  {
    // file info
    char ID[2];
    unsigned long filesize, reserved, offset;
 
    // BMP info
    unsigned long  infosize, width, height;
    unsigned short planes, bitsperpixel;
    unsigned long  compression, imagesize,
                   xPixelPerMeter, yPixelPerMeter,
                   colorsUsed, colorsImportant;
  } header =
  { 
    // file info
    { 'B', 'M' },
    unsigned(sizeof(header)) + imagesize, // 24 bits per pixel
    0, sizeof(header),          // 0x36
 
    // BMP info
    40, image.width(), image.height(),
    1, 24,
    0, imagesize,
    1000, 1000,
    0, 0
  };
  // #if defined( _MSC_VER ) || defined( __MINGW32__ )
  #pragma pack()
  // #endif
 
  std::ofstream file(filename.c_str(), std::ios::out|std::ios::binary);
  file.write( (char*)&header, sizeof(header)); // Intel byte order
 
  for (unsigned y = 0; y < image.height(); ++y)
  {
    for (unsigned x = 0; x < image.width(); ++x)
    {
      Color c= image.pixel(x,y);
      unsigned char b = c.blue(), g = c.green(), r = c.red();
      file.write( (char*) &b, 1);
      file.write( (char*) &g, 1);
      file.write( (char*) &r, 1);
    }
    if (fillbytes)
    {
      int zero = 0;
      file.write( (char*) &zero, fillbytes);
    }
  }
}
 
#endif // IMAGE_H
//~

Dateiformat

Das BMP-Format wurde hier gewählt, weil es einen relativ einfachen Aufbau hat (ohne Kompression) und 24-Bit-Farben erlaubt. Jedoch geht die Transparenz-Infornation verloren. Für das PNG-Format (mit Transparenz) gibt es einen quelloffenen Code, welcher die Bytefolge ABGR statt der in SDL, HTML und hier verwendeten Folge (A)RGB benutzt. Andere Dateiformate (Targa, GIF, JPEG, …) sind weitergehende Übungsaufgaben. Quellen: Günter Born, Dateiformate.

Testprogramm

Das Hauptprogramm zeigt, wie einfach der Umgang mit Bildern ist. Der Inhalt eines Bildes kann als Zahlenkolonne (plain text) oder als Bitmap-Datei gespeichert werden:

grafik.cpp
//: grafik.cpp : Grafik-Bibliothek ohne Grafiktreiber - R.Richter 2011-01-15
////////////////////////////////////////////////////////////////////////////
 
#include <fstream>
#include <iostream>
#include <iomanip>
#include "image.h"
 
void print(std::ostream& out, const Image& image)
{
  out << "\n\nRGB-Alpha-Bild "
      << image.width() << " x " << image.height() << '\n';
 
  for (unsigned y = 0; y < image.height(); ++y)
  {
    for (unsigned x = 0; x < image.width(); ++x)
    {
      out << std::setw(9) << std::hex << image.pixel(x,y);
    }
    out << '\n';
  }
}
 
int main()
{
  Color c(255, 0, 0, 127); // red, 50% transparency
  Image image(100, 75);
 
  for (unsigned x = 0; x < image.height(); ++x)
  {
    image.pixel(x, x) = c;                             // rising line
    image.pixel(x, image.height()/2  ) = Color::BLUE;  // mid horizon line
    image.pixel(x, image.height()-1-x) = Color::GREEN; // falling line
  }
  image.pixel(-1, 1) = Color::WHITE; // outside ignored, doesn't crash!
 
  std::ofstream file("image.dat");
  print(file, image);
  saveBMP("image24.bmp", image);
 
  return 0;
}
//~

Das Testprogramm erzeugt eine Bilddatei mit

  • aufsteigender roter Linie
  • waagerechter blauer Linie und
  • fallender grüner Linie:

Weitergehende Übungen führen in verschiedene Richtungen:

  • 2D-Grafik (Linien, Balken, Polygone, Kurven, Farbverläufe),
  • Bildbearbeitung (Überlagern, Ausschneiden, Bildeffekte),
  • 3D-Grafik (Projektion, Verdeckungsrechnung, Beleuchtungsgeometrie),
  • Simulation und Visualisierung (2D, 3D),
  • Bild-Datei-Konversion (Kompressionsalgorithmen).

Viel Spaß!

anwenden/images.txt · Zuletzt geändert: 2017-10-07 12:23 von rrichter