Seite 1 von 1
QPixmap mit Binärdaten laden
Verfasst: 17. Dezember 2008 23:19
von qtNiko
hallo,
ich habe eine Matrix von 8bit-Daten, g8bild, die als ein Bild dargestellt werden sollen.
Ich habe mich für QPixmap entschieden, das in einem QLabel darstellbar ist, und Binärdaten übernehmen kann.
Folgendes Programm-Segment:
Code: Alles auswählen
#define BREIT 256
#define HOCH 256
...
QPixmap bild;
uchar g8bild[BREIT * HOCH];
... // hier wird g8bild mit Daten gefüllt
bild.scaled(BREIT, HOCH); // irgendwie muss man ja das Format definieren
bild.loadFromData( g8bild, BREIT * HOCH);
bild_ausgabe->setPixmap(bild); // Bild anzeigen im QLabel bild_ausgabe
Leider sehe ich kein Bild. Was fehlt noch, bzw. ist falsch?
Verfasst: 17. Dezember 2008 23:25
von Volker
Hast Du mal den Rückgabewert von loadFromData geprüft. Ich wette der gibt false zurück, da deine Binärdaten vermutlich keinen gültigen Header für ein unterstütztes Format haben:
If the format is not specified (which is the default), the loader probes the file for a header to guess the file format.
Verfasst: 17. Dezember 2008 23:43
von qtNiko
Danke für die schnelle Antwort.
Den Kommentar "If the format is not specified ..." habe ich gelesen, aber er verwirrte mich eher. Ich dachte, loadFromData() würde reine Binärdaten, wie mein Array g8bild, laden. Dann spricht der Kommentar noch von "file header" und so. Ich habe kein File, sondern nur die Binärdaten.
Aber hier scheint der Knackpunkt zu liegen. Was braucht denn loadFromData() noch neben den Binärdaten? Wie soll der sog. file header aussehen (ich habe kein file)?
Wie kriege ich also die Binärdaten in das Bild?
Verfasst: 18. Dezember 2008 09:00
von CaptnChaos
Versuchs mal mit QImage. zur not kannst du die Pixel dort selbst setzen. Sprich QiImage mit gewünschter größe erstellen und dann per scanLine(int) auf die einzelnen pixel zugreifen.
So zum Beispiel:
Code: Alles auswählen
QImage image(BREIT, HOCH, QImage::Format_Mono);
uchar g8bild[BREIT * HOCH];
for(int y = 0; y < HOCH; y++)
{
QRgb *lineData = image.scanLine(y);
for(int x = 0; x < BREIT; x++)
{
lineData[x] = QRgb(g8blid[x][y], g8blid[x][y], g8blid[x][y]);
}
}
bild_ausgabe->setPixmap(QPixmap::fromImage(image));
Verfasst: 18. Dezember 2008 11:50
von qtNiko
Danke für die ausführliche Beschreibung.
Ich werde das mal ausprobieren. Dann habe ich wenigstens schon mal eine Lösung.
Ich finde, es ist ziemlich umständlich, und ich werde weiter nach einfacheren Lösungen suchen. Ich melde mich wieder, wenn ich fündig geworden bin.
Verfasst: 22. Dezember 2008 00:38
von qtNiko
hallo,
jetzt hab' ich's:
Es klappt sowohl mit QPixmap als auch mit QImage:
1. QPixmap
Der Tipp von Volker mit dem Header war es: Man muss den Bilddaten einen Header voranstellen, der aussieht wie der Header eines entsprechenden Files. Der Einfachheit halber nehme ich hierfür das PGM-Format. Der Code sieht dann so aus:
Code: Alles auswählen
#define BREIT 256
#define HOCH 256
#define HEADER 20 // muss Platz für den Header bieten
...
QPixmap bild;
uchar g8bild[BREIT * HOCH];
uchar zwsp[HEADER + BREIT * HOCH];
... // hier wird g8bild mit Daten gefüllt
sprintf((char*)zwsp,"P5\n%d %d\n255\n",BREIT, HOCH); // PGM-Header schreiben
j = strlen((char*)zwsp);
for (i=j; i<BREIT*HOCH+j; i++) // Daten von g8bild nach dem Header schreiben
zwsp[i] = g8bild[i-j];
bild.loadFromData( zwsp, BREIT * HOCH + j);
bild_ausgabe->setPixmap(bild); // Bild anzeigen im QLabel bild_ausgabe
Das Beispiel funktioniert mit 8Bit-Daten. Obwohl die load() function PGM-Bilder mit 16Bit Daten einlesen und darstellen kann, habe ich es nicht hingekriegt, mit loadFromData() 16bit Daten einzulesen. Ich setzte dazu im Header den maximalen Grauwert (3. Zahl) auf 10000 und fügte diesen Header vor ein unsigned short Array. Es wurden aber von QPixmap immer nur 8bit pro Pixel verwendet.
Ein weiterer Wermutstropfen ist, dass man immer einen Zwischenspeicher benötigt, um erst den Header und dann die Daten zusammen zu bekommen.
Deshalb gefällt mir jetzt doch die zweite Lösung mit QImage besser, danke KernelPanic.
Mit ein paar Änderungen sieht sie jetzt so aus:
2. QImage:
Code: Alles auswählen
#define BREIT 256
#define HOCH 256
QImage image(BREIT, HOCH, QImage::Format_RGB32);
uchar * lineData;
QRgb* linePix;
for(int y = 0; y < HOCH; y++)
{
lineData = image.scanLine(y);
linePix = (QRgb *)lineData; // mein Compiler braucht das so
for(int x = 0; x < BREIT; x++)
{
i=x+y*BREIT;
linePix[x] = qRgb((int)g8bild[i], (int)g8bild[i], (int)g8bild[i]);
}
}
bild_ausgabe->setPixmap(QPixmap::fromImage(image));
Hier wird kein Zwischenspeicher benötigt, sondern es werden direkt die Pixel von QImage beschrieben. Wenn man 16bit Daten hat, wie es in meinem Fall zutrifft, muss man auch hier die Skalierung auf 8bit selber machen.
Ein kleiner Nachteil liegt in diesem Verfahren noch. Man muss die Bildgröße schon vorher wissen.
Verfasst: 24. Dezember 2008 11:41
von qtNiko
Jetzt möchte ich doch noch einmal das Thema ansprechen.
Die Doku gibt einige mehr Aufrufparameter an, als ich bisher verwendete:
Code: Alles auswählen
bool loadFromData ( const uchar * data, uint len, const char * format = 0, Qt::ImageConversionFlags flags = Qt::AutoColor )
data und len sind klar. Über format sagt die Doku nichts aus, und es gibt auch keinen Hyperlink, der mehr Info brächte.
Das Format, das man hier angeben kann, könnte es erübrigen, die Headerinformation vor die Daten zu setzen, wie ich es vorher beschrieben hatte. Leider weiß ich nicht, wie man dieses Format angeben muss. Ein ASCII-String, so wie ein Header einer Bilddatei aussieht, funktioniert jedenfalls nicht.
Kennt sich damit jemand aus, bzw. hat ein Beispiel dafür?
Das steht versteckt im Sourcecode, leider nicht hilfreich
Verfasst: 9. Juli 2009 13:38
von NothingSpecial
Hi,
danke schonmal für deine Erläuterungen, wie du es gemacht hast, das hat mir sehr geholfen. Ich benutze RGB888 und damit kann ich auch das casting zum RGB Datentyp weglassen und die Werte einfach kopieren:
Code: Alles auswählen
void ImageManagement::cpyBuffer2Image(unsigned char* buffer, QImage *image){
unsigned char* lineData;
int pos;
for(int i = 0; i < image->height(); ++i){
lineData = image->scanLine(i);
pos = 3* i * image->width();
for(int k = 0; k < image->width()*3; ++k){
lineData[k] = buffer[pos + k];
}
}
}
Ob das schneller ist, weiß ich aber nicht. Ich gehe aber mal davon aus. Subjektiv ist es aber immer noch langsamer als mit Windowsforms, wenn man mit memcpy direkt die Bits eines Bitmap verändert.
Es gibt scheinbar keine Methode die ein ganzes unsigned char Array (dann mit memcpy) in ein Bild kopiert. Ich denke ich werde mal eine schreiben. Ich melde mich, wenn es funktioniert.
Grüße,
Nothing Special
P.S.: Fast hätte ich es vergessen. Was man als
Format übergeben kann findet sich hier:
http://doc.trolltech.com/4.5/qimageread ... ageFormats (Der QImageReader wird intern von QImage benutzt, wenn es um das Laden des Bildinhaltes geht, die Dateien findet man in: Qt\2009.03\qt\src\gui\image\qimage.cpp oder Qt\2009.03\qt\src\gui\image\qimagereader.cpp)
Komischer Weise keine Rohdaten... Qt ist nicht für Bildmanipulation gebaut

Verfasst: 9. Juli 2009 15:02
von NothingSpecial
looooooool
sry das ist nur ein einzeiler:
Code: Alles auswählen
void ImageManagement::cpyBuffer2ImageBits(unsigned char* buffer, QImage *image){
memcpy(image->bits(),buffer,sizeof(unsigned char)*3*image->width()*image->height());
}
Ich habe gerade beim Durchlesen des codes gelesen, was man hätte auch in der Doku finden können:
http://doc.trolltech.com/4.5/qimage.html#bits
Anbetracht des Alters des Threads schon ein guter Treppenwitz
Glückliche Grüße,
Nothing Special
Verfasst: 9. Juli 2009 15:29
von Volker
Soweit ich weiß, ist bei QImage aber der Speicher immer 4-byte aligned. D.h. wenn dein Bild das nicht ist, bekommst du evtl. Probleme. Bin mir da aber nicht so 100prozentig sicher, hab schon lang nichts mehr damit gemacht.
Verfasst: 9. Juli 2009 16:01
von NothingSpecial
Hi,
Volker hat geschrieben:Soweit ich weiß, ist bei QImage aber der Speicher immer 4-byte aligned.
Wahrscheinlich nicht immer, kommt auf die Bittiefe (QImage::depth()) an. Es funktioniert auf jeden Fall mit meinem RGB (24bit) buffer und Image. Die obere Methode ist für Format_RGB888, wie ich es benutze.
Allgemeiner könnte man folgendes machen, obwohl verschiedene Methoden für verschiedene Bittiefen sicherlich klarer sind und zu weniger Fehlern führen:
Code: Alles auswählen
void ImageManagement::cpyBuffer2ImageBits(unsigned char* buffer, QImage *image){
//image->depth() gibt die Anzahl der Bit pro Pixel, da es sich bei einem char (unsigned oder nicht) aber eigentlich um einen Datentyp der Größe eines Bytes handelt, muss man die Anzahl der Bits durch 8 Teilen.
int bytes = (image->depth() >> 3);
memcpy(image->bits(),buffer,sizeof(unsigned char)*bytes*image->width()*image->height());
}
Effektiver (und nochunleserlicher) ist es natürlich es die Byte- Tiefe direkt im Aufruf von memcpy zu übergeben, gerade wenn man diese Methode für Videos oder ähnliches benutzt.
Trotzdem schützt diese Variante nicht vor Fehlern! Wer nicht weiß, wie die Daten in seinem QImage oder buffer aussehen und versucht nicht passende Daten zusammen zu würfeln bekommt unter Umständen einen Aufhänger und keine Fehlermeldung. Ist bei mir passiert als ich versucht habe einen RGB buffer auf ein ARGB QImage zu kopieren.
Grüße,
NothingSpecial
P.S.: Ich möchte nochmal auf die Doku zurück verwarnen

:
Warning: If you are accessing 32-bpp image data, cast the returned pointer to QRgb* (QRgb has a 32-bit size) and use it to read/write the pixel value. You cannot use the uchar* pointer directly, because the pixel format depends on the byte order on the underlying platform. Use qRed(), qGreen(), qBlue(), and qAlpha() to access the pixels.
http://doc.trolltech.com/4.5/qimage.html#scanLine
Und laut Doku ist bits() nichts anderes als scanline(0), die Warnung trifft also auch hier zu!