QPixmap mit Binärdaten laden

Alles rund um die Programmierung mit Qt
Antworten
qtNiko
Beiträge: 216
Registriert: 6. April 2007 21:26
Wohnort: München

QPixmap mit Binärdaten laden

Beitrag 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?
Zuletzt geändert von qtNiko am 24. Dezember 2008 11:33, insgesamt 2-mal geändert.
Gruß von qtNiko

Core i5 760, GT 240, Suse Linux11.3, Eclipse-CDT-Helios, QT4.4, QT-Integration
Volker
Beiträge: 343
Registriert: 30. Juni 2005 05:27

Beitrag 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.
Bitte seid so nett und ändert den Titel von Beiträgen die gelöst wurden, auf [gelöst] Beitragstitel
qtNiko
Beiträge: 216
Registriert: 6. April 2007 21:26
Wohnort: München

Beitrag 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?
Gruß von qtNiko

Core i5 760, GT 240, Suse Linux11.3, Eclipse-CDT-Helios, QT4.4, QT-Integration
CaptnChaos
Beiträge: 605
Registriert: 28. Juni 2007 15:01
Kontaktdaten:

Beitrag 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));
qtNiko
Beiträge: 216
Registriert: 6. April 2007 21:26
Wohnort: München

Beitrag 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.
Gruß von qtNiko

Core i5 760, GT 240, Suse Linux11.3, Eclipse-CDT-Helios, QT4.4, QT-Integration
qtNiko
Beiträge: 216
Registriert: 6. April 2007 21:26
Wohnort: München

Beitrag 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.
Gruß von qtNiko

Core i5 760, GT 240, Suse Linux11.3, Eclipse-CDT-Helios, QT4.4, QT-Integration
qtNiko
Beiträge: 216
Registriert: 6. April 2007 21:26
Wohnort: München

Beitrag 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?
Gruß von qtNiko

Core i5 760, GT 240, Suse Linux11.3, Eclipse-CDT-Helios, QT4.4, QT-Integration
NothingSpecial
Beiträge: 28
Registriert: 5. Juli 2009 16:06

Das steht versteckt im Sourcecode, leider nicht hilfreich

Beitrag 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 ;)
NothingSpecial
Beiträge: 28
Registriert: 5. Juli 2009 16:06

Beitrag 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 :D

Glückliche Grüße,

Nothing Special
Volker
Beiträge: 343
Registriert: 30. Juni 2005 05:27

Beitrag 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.
Bitte seid so nett und ändert den Titel von Beiträgen die gelöst wurden, auf [gelöst] Beitragstitel
NothingSpecial
Beiträge: 28
Registriert: 5. Juli 2009 16:06

Beitrag 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!
Antworten