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

Autor podstrony: Krzysztof Zajączkowski

Stronę tą wyświetlono już: 3546 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:

QT += core QT -= gui CONFIG += c++11 TARGET = ImageExamples CONFIG += console CONFIG -= app_bundle TEMPLATE = app SOURCES += main.cpp

Gdzie linijkę 2 należy zamienić na:

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:

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:

#include <QCoreApplication> #include <QDebug> #include <QImage> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QImage image("fog.jpg"); qDebug() << "Rozmiar bitmapy:" << image.size(); qDebug() << "Głębia kolorów:" << image.depth(); return a.exec(); }

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:

int x = 100; int y = 100; QRgb color = image.pixel(x, y); 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:

int x = 100; int y = 100; QRgb color = image.pixel(x, y); 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)); image.setPixel(x, y, qRgba(0,0,0,255)); color = image.pixel(x, y); 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:

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

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:

image.save("fog2.jpg"); // z domyślną kompresją image.save("fog3.jpg", "jpg", 0); // z najniższą możliwą kompresją 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
Propozycje książek