KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программирование » Жасмин Бланшет - QT 4: программирование GUI на С++

Жасмин Бланшет - QT 4: программирование GUI на С++

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Жасмин Бланшет, "QT 4: программирование GUI на С++" бесплатно, без регистрации.
Перейти на страницу:

01 QImageIOHandler *CursorPlugin::create(QIODevice *device,

02 const QByteArray &format) const

03 {

04 CursorHandler *handler = new CursorHandler;

05 handler->setDevice(device);

06 handler->setFormat(format);

07 return handler;

08 }

Когда файл курсора открыт (например, с помощью класса QImageReader), будет вызвана функция оболочки подключаемого модуля create() с передачей указателя устройства и формата «cur». Мы создаем экземпляр CursorHandler для заданного устройства и формата. Вызывающая программа становится владельцем обработчика и удалит его, когда он не станет нужен. Если приходится считывать несколько файлов, для каждого из них создается новый обработчик.

Q_EXPORT_PLUGIN2(cursorplugin, CursorPlugin)

В конце файла .cpp мы используем макрос Q_EXPORT_PLUGIN2(), чтобы гарантировать распознавание в Qt подключаемого модуля. В первом параметре задается произвольное имя, используемое нами для подключаемого модуля. Второй параметр содержит имя класса подключаемого модуля.

Подкласс QImageIOPlugin создается достаточно просто. Реальная работа подключаемого модуля делается обработчиком. Обработчики форматов изображений должны создать подкласс QImageIOHandler и переопределить некоторые или все его открытые функции. Сначала рассмотрим заголовочный файл:

01 class CursorHandler : public QImageIOHandler

02 {

03 public:

04 CursorHandler();

05 bool canRead() const;

06 bool read(QImage *image);

07 bool jumpToNextImage();

08 int currentImageNumber() const;

09 int imageCount() const;

10 private:

11 enum State { BeforeHeader, BeforeImage, AfterLastImage, Error };

12 void readHeaderIfNecessary() const;

13 QBitArray readBitmap(int width, int height, QDataStream &in) const;

14 void enterErrorState() const;

15 mutable State state;

16 mutable int currentImageNo;

17 mutable int numImages;

18 };

Открытые функции имеют фиксированную сигнатуру. Здесь нет некоторых функций, которые не надо переопределять в обработчике, обеспечивающем только чтение, в частности отсутствует функция write(). Переменные—члены объявляются с ключевым словом mutable, потому что они изменяются внутри константных функций.

01 CursorHandler::CursorHandler()

02 {

03 state = BeforeHeader;

04 currentImageNo = 0;

05 numImages = 0;

06 }

После создания обработчика мы сначала настраиваем его параметры. Номер текущего изображения курсора устанавливается на первый курсор, но поскольку переменная количества изображений numImages принимает значение 0, ясно, что у нас пока еще нет изображений.

01 bool CursorHandler::canRead() const

02 {

03 if (state == BeforeHeader) {

04 return device()->peek(4) == QByteArray("2", 4);

05 } else {

06 return state != Error;

07 }

08 }

Функция canRead() может вызываться в любой момент для определения возможности считывания обработчиком изображений дополнительных данных с устройства. Если функция вызывается до чтения данных в состоянии BeforeHeader, выполняется проверка конкретной метки, по которой опознаются файлы курсоров в Windows. Вызов QIODevice::peek() считывает первые четыре байта без изменения указателя файла на данном устройстве. Если функция canRead() вызывается позже, мы возвращаем true при отсутствии ошибки.

01 int CursorHandler::currentImageNumber() const

02 {

03 return currentImageNo;

04 }

Эта простая функция возвращает номер курсора, на который позиционирован указатель файла устройства.

После создания обработчика пользователь может вызвать любую его открытую функцию, причем последовательность вызовов функций может быть произвольной. В этом кроется потенциальная проблема, поскольку необходимо исходить из того, что файл можно читать только последовательно, поэтому сначала надо один раз считать заголовок файла и затем выполнять какие-то другие действия. Эту проблему решаем путем вызова readHeaderIfNecessary() в тех функциях, для которых требуется предварительное считывание заголовка файла.

01 int CursorHandler::imageCount() const

02 {

03 readHeaderIfNecessary();

04 return numImages;

05 }

Эта функция возвращает количество изображений, содержащихся в файле. Для правильного файла, при чтении которого не возникает ошибок, она возвращает по крайней мере 1.

Рис. 19.2. Формат файла .cur.

Следующая функция довольно сложная, поэтому мы рассмотрим ее по частям:

01 bool CursorHandler::read(QImage *image)

02 {

03 readHeaderIfNecessary();

04 if (state != BeforeImage)

05 return false;

Функция read() считывает данные изображения, начинающегося в текущей позиции указателя устройства. Если успешно считан заголовок файла или указатель устройства после чтения изображения находится в начале другого изображения, можно считывать следующее изображение.

06 quint32 size;

07 quint32 width;

08 quint32 height;

09 quint16 numPlanes;

10 quint16 bitsPerPixel;

11 quint32 compression;

12 QDataStream in(device());

13 in.setByteOrder(QDataStream::LittleEndian);

14 in >> size;

15 if (size != 40) {

16 enterErrorState();

17 return false;

18 }

19 in >> width >> height >> numPlanes >> bitsPerPixel >> compression;

20 height /= 2;

21 if (numPlanes != 1 || bitsPerPixel != 1 || compression != 0) {

22 enterErrorState();

23 return false;

24 }

25 in.skipRawData((size - 20) + 8);

Мы создаем объект QDataStream для чтения устройства. Необходимо установить порядок байтов в соответствии с тем, который определен спецификацией формата файла .cur. Задавать версию потока QDataStream нет необходимости, поскольку форматы целых чисел и чисел с плавающей запятой не зависят от версии потока данных. Затем считываем элементы заголовка курсора и пропускаем неиспользуемые части заголовка и 8-байтовую таблицу цветов с помощью функции QDataStream::skipRawData().

Необходимо учитывать все характерные особенности формата, например, уменьшая вдвое высоту изображения, потому что она в формате .cur в два раза превышает высоту реального изображения. Переменные bitsPerPixel и compression всегда имеют значения 1 и 0 в монохромных файлах .cur. При возникновении каких-либо проблем вызываем функцию enterErrorState() и возвращаем false.

26 QBitArray xorBitmap = readBitmap(width, height, in);

27 QBitArray andBitmap = readBitmap(width, height, in);

28 if (in.status() != QDataStream::Ok) {

29 enterErrorState();

30 return false;

31 }

Следующими элементами файла являются две битовые маски: одна XOR—маска, а другая AND—маска. Мы их считываем в массивы QBitArray, а не в QBitmap. Класс QBitmap предназначен для выполнения с ним операций рисования и вывода рисунка на экран, а нам нужен простой массив битов.

Завершив чтение файла, проверяем состояние потока QDataStream. Так можно поступать, потому что, если QDataStream переходит в состояние ошибки, это состояние сохраняется в дальнейшем и последующие операции чтения могут выдать только нули. Например, если чтение первого массива бит завершается неудачей, попытка чтения второго массива в результате даст пустой массив QBitArray.

32 *image = QImage(width, height, QImage::Format_ARGB32);

33 for (int i = 0; i < int(height); ++i) {

34 for (int j = 0; j < int(width); ++j) {

35 QRgb color;

36 int bit = (i * width) + j;

37 if (andBitmap.testBit(bit)) {

38 if (xorBitmap.testBit(bit)) {

39 color = 0x7F7F7F7F;

40 } else {

41 color = 0x00FFFFFF;

42 }

43 } else {

44 if (xorBitmap.testBit(bit)) {

45 color = 0xFFFFFFFF;

46 } else {

47 color = 0xFF000000;

48 }

50 }

51 image->setPixel(j, i, color);

52 }

53 }

Мы конструируем новый объект QImage с правильными размерами и устанавливаем на него указатель изображения. Затем проходим по каждому пикселю битовых массивов XOR и AND и преобразуем их в 32-битовый цветовой формат ARGB. С помощью массивов битов AND и XOR цвет каждого пикселя курсора всегда получается в соответствии со следующей таблицей:

С получением черного, белого и прозрачного пикселей нет проблем, однако нельзя получить инвертированный пиксель фона, используя цветовой формат ARGB, если не знаешь цвет исходного пикселя фона. В качестве замены используем полупрозрачный серый цвет (0x7F7F7F7F).

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*