Qt - wczytywanie i operowanie na bitmapach za pomocą klasy QImage

Stronę tą wyświetlono już: 24 razy

Wydawać się może to dość dziwne, że informacje na temat klasy QImage służącej do obsługi bitmap opisuję w dziale dotyczącym programowania konsolowego. Podjąłem jednak taką decyzję, gdyż w ten sposób mogę pokazać podstawy operacji możliwych do wykonania na bitmapach bez konieczności posiadania GUI.

Konfiguracja projektu

Ponieważ klasa QImage domyślnie jest przeznaczona dla projektów GUI, więc aby dostęp do pliku nagłówkowego QImage był możliwy konieczna jest edycja pliku z rozszerzeniem .pro, który jest dostępny w każdym projekcie w oknie pokazanym na poniższym rysunku.

Qt Creator - widok drzewa projektu
Rys. 1
Qt Creator - widok drzewa projektu

Po dwukrotnym kliknięciu (w moim przypadku pozycji ImageExample.pro) oczom twym powinien ukazać się następujący kod:

Listing 1
  1. QT += core
  2. QT -= gui
  3. CONFIG += c++11
  4. TARGET = ImageExamples
  5. CONFIG += console
  6. CONFIG -= app_bundle
  7. TEMPLATE = app
  8. SOURCES += main.cpp

Gdzie linijkę 2 należy zamienić na:

Listing 2
  1. QT += gui

aby możliwe było korzystanie z klasy QImage.

Domyślnie obsługiwane formaty plików

Klasa QImage umożliwia odczyt/zapis plików następujących formatów:

FormatOpisObsługa
BMPnieskompresowane bitmapyodczyt/zapis
GIFGraphic Interchange Formatodczyt
JPGJoint Photographic Experts Groupodczyt/zapis
JPEGJoint Photographic Experts Groupodczyt/zapis
PNGPortable Network Graphicsodczyt/zapis
PBMPortable Bitmapodczyt
PGMPortable Graymapodczyt
PPMPortable Pixmapodczyt/zapis
XBMX11 Bitmapodczyt/zapis
XPMX11 Pixmapodczyt/zapis

Odczyt bitmapy z pliku i podstawowe jego parametry

Jak już wspomniałem klasa QImage umożliwia odczyt różnych formatów plików zawierających obraz rastrowy. Taką możliwość wczytania danych daje sam konstruktor klasy QImage:

Listing 3
  1. #include <QCoreApplication>
  2. #include <QDebug>
  3. #include <QImage>
  4. int main(int argc, char *argv[])
  5. {
  6. QCoreApplication a(argc, argv);
  7. QImage image("fog.jpg");
  8. qDebug() << "Rozmiar bitmapy:" << image.size();
  9. qDebug() << "Głębia kolorów:" << image.depth();
  10. return a.exec();
  11. }

W powyższym kodzie odczytany został plik fog.jpg oraz wyświetlona została informacja o jego rozmiarze i głębi (liczbie bitów na piksel). Niestety najwyraźniej klasa QImage zamienia 24 bitową bitmapę automatycznie przy wczytywaniu na 32 bitową (czyli z kanałem alfa).

Wynik działania powyższego kodu:

Rozmiar bitmapy: QSize(600, 400)
Głębia kolorow: 32

Dostęp do pikseli bitmapy

Po wczytaniu bitmapy można pozyskać informację o kolorze danego jej piksela. Do tego celu służy metoda pixel, oto przykład jej użycia:

Listing 4
  1. int x = 100;
  2. int y = 100;
  3. QRgb color = image.pixel(x, y);
  4. qDebug() << "Kolor z pozycji" << x << ";" << y << ":" << QString("czerwony = %1; zielony = %2; niebieski = %3; alfa = %4").arg(qRed(color)).arg(qGreen(color)).arg(qBlue(color)).arg(qAlpha(color));

Wynik działania powyższego kodu:

Kolor z pozycji 100 ; 100 : "czerwony = 198; zielony = 178; niebieski = 145; alfa = 255"

Możliwe jest również ustawienie koloru danego piksela bitmapy za pomocą metody setPixel:

Listing 5
  1. int x = 100;
  2. int y = 100;
  3. QRgb color = image.pixel(x, y);
  4. qDebug() << "Kolor z pozycji" << x << ";" << y << ":" << QString("czerwony = %1; zielony = %2; niebieski = %3; alfa = %4").arg(qRed(color)).arg(qGreen(color)).arg(qBlue(color)).arg(qAlpha(color));
  5. image.setPixel(x, y, qRgba(0,0,0,255));
  6. color = image.pixel(x, y);
  7. qDebug() << "Kolor z pozycji" << x << ";" << y << ":" << QString("czerwony = %1; zielony = %2; niebieski = %3; alfa = %4").arg(qRed(color)).arg(qGreen(color)).arg(qBlue(color)).arg(qAlpha(color));

Wynik działania powyższego kodu:

Kolor z pozycji 100 ; 100 : "czerwony = 198; zielony = 178; niebieski = 145; alfa = 255"
Kolor z pozycji 100 ; 100 : "czerwony = 0; zielony = 0; niebieski = 0; alfa = 255"

Operacje na pikselach za pomocą powyższych metod niestety są bardzo powolne, dlatego można też operować na poszczególnych pikselach bitmapy wykorzystując metodę bits, która zwraca wskaźnik do tablicy typu uchar (unsigned char) reprezentującej składowe kolorów pikseli bitmapy w układzie: czerwony, zielony, niebieski, alfa. Oto przykład wykorzystania tego wskaźnika do rozjaśnienia kolorów bitmapy:

Listing 6
  1. #include <QCoreApplication>
  2. #include <QDebug>
  3. #include <QImage>
  4. void minmax(uchar* value, int add){ // normalizacja wartości by nie przekraczała zakresu 0 - 255
  5. *value = (int)*value + add < 255 ? ((int)*value + add > 0 ? *value + add : 0) : 255;
  6. }
  7. int main(int argc, char *argv[])
  8. {
  9. QCoreApplication a(argc, argv);
  10. QImage image("fog.jpg", "jpg");
  11. uchar* bits = image.bits(); // pozyskuję wskaźnik na początek tablicy bitów
  12. uchar* bitsEnd = bits + image.byteCount(); // obliczam wskaźnik na koniec tablicy
  13. int add = 50; // dodawana wartość
  14. for(uchar* index = bits; index < bitsEnd; index += 4){
  15. minmax(index, add); // składowa czerwona
  16. minmax(index + 1, add); // składowa zielona
  17. minmax(index + 2, add); // składowa niebieska
  18. }
  19. image.save("fog2.jpg");
  20. return a.exec();
  21. }

Wynik działania powyższego kodu na przykładowej bitmapie pokazany został na poniższym rysunku.

a)Obraz przed operacją na pikselachb)Obraz po operacji na pikselach
Rys. 2
Plik fog.jpg i plik fog2.jpg jako rezultat zapisu zmiany kolorów

Zapis obrazu do pliku

Obraz można również zapisać do pliku za pomocą metody save, która w przypadku niektórych formatów umożliwia również ustawienie poziomu kompresji:

Listing 7
  1. image.save("fog2.jpg"); // z domyślną kompresją
  2. image.save("fog3.jpg", "jpg", 0); // z najniższą możliwą kompresją
  3. image.save("fog4.jpg", "jpg", 100); // z największą możliwą kompresją
Strony powiązane
strony powiązane
  1. doc.qt.io/qt-5/qimage.html - opis klasy QImage na stronie dokumentacji Qt

Komentarze