Развитие QtE5. Загрузка ресурсов

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

Ресурсы Qt

Ресурсы — важная составляющая приложения. Отличие ресурсов Qt от общепринятых в Windows в том, что они (ресурсы и механизм их работы) кросплатформенные. Один раз скомпилировав (не важно на какой платформе) файл ресурсов, он будет корректно работать на любой системе, чего нельзя сказать о ресурсах Windows. Ресурсы в Qt могут быть задействованы автоматом, через проект, но на конечном этапе сборки нужен линкёр. Поэтому нам такой способ не подходит, нет у нас под руками С++ с линкёром. Однако, в Qt, оказывается, есть динамический способ загрузки ресурсов. Он напоминает динамическую загрузку DLL. Далее рассматриваем только динамические ресурсы.

Основная идея проста. Прочитать файл ресурсов в память, получив указатель на начало. Однако, в связи с тем, что ресурсы могут быть разные, и, главное, что их может быть много, встает вопрос, как в этом буфере памяти понять, где начинается один ресурс, или заканчивается другой. Однако, Qt всё делает за нас. Имеется универсальный механизм доступа, к ресурсам. Пример: Qpixmap.load(“:icons/exit_icon.ico”). Двоеточие в начале строки говорит о том, что файл грузится из ресурса. Таким образом, как обратится к ресурсу понятно (можно подробнее почитать в документации к Qt), а вот как его (ресурс создать и загрузить) – это проблема.

Создание ресурса. Для этого используем компилятор ресурсов rcc.exe входящий в состав Qt. Структура исходного файла ресурса описана в документации Qt. В качестве примера приведем файл описания ресурса для нашей демо программы.

Это файл: t.qrc




  ICONS/exit_icon.png
  ICONS/about_icon.png


То есть, тут сказано: «возьми два файла которые лежат в каталоге ICONS и изготовь из них файл ресурсов». Команда для выполнения этого задания следующая:

rcc -binary t.qrc -o t.rcc

Таким образом, у нас получился файл t.rcc который содержит в сжатом виде наши два файла PNG.

Загрузка ресурса. Есть две почти одинаковых возможности загрузить файл ресурсов. Один способ чисто Qt, второй D + Qt. Разница, как будет видно далее, небольшая.

Способ 1: чистый Qt в QtE5:

QResource res = new QResource();
Bool r = res.registerResource("путьДоФайлаРесурсовRCC");

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

Способ 2: D + Qt в QtE5:

QResource res = new QResource();
auto data = cast(ubyte[]) read("путьДоФайлаРесурсовRCC");
r = res.registerResource(cast(ubyte*)data);

Фактически, средствами D читаем файл как набор байтов в буфер, после, передав указатель на буфер функции registerResource, создаем объект работы с ресурсом. Если на выходе Истина (true), то ресурс загружен. Далее обращаемся к ресурсу через двоеточие. Из этого смешанного способа можно получить еще один вариант (который я еще не пробовал, но думаю он должен сработать). Это способ загрузки двоичных файлов в программу на D посредством «import» (Отличный пример программа Олега Бахарева “Пишем валентинку на D”) с дальнейшим получением указателя на буфер. Например так:

QResource res = new QResource();
auto mp3data = cast(ubyte[]) import("путьДоФайлаРесурсовRCC");
r = res.registerResource(cast(ubyte*)data.ptr);

QPixmap и Qbitmap

Эти классы (и плюс имеющийся уже QImage) вносят новые возможности по контекстно-зависимому способу отображения графики. На Qpixmap можно рисовать так же как и на Qwidget и Qimage. Главное, что не происходит мгновенной перерисовки экрана до момента вставки (оканчания рисования, end()). Применение нескольких painter позволяет создавать интересные эффекты. Следует обратить внимание на то, что методы в QtE5 максимально (по возможности) приближены к методам Qt. Это позволяет, рассматривая примеры по Qt в интернете, фактически повторить то же самое в QtE5 на D. Основное отличие в том, что в C++, как правило, объекты созданы на стеке, а в QtE5 только в «куче» ( heap). Типичный пример:

C++ стек:   Qwidget w(NULL); w.show;
C++ куча:   Qwidget w = new Qwidget(NULL); w->show();
D:          Qwidget w = new Qwidget(null); w.show();

Следующий пример я срисовал с интернета. Цель: получить строку, на которую вместо цвета натянута фактура (в примере PNG файл).

// Тестирование загрузки ресурсов 08/10/17
// ------------------------------
// dmd testres.d qte5.d asc1251
//
// Файл ресурсов: t.qrc
/*

	
		ICONS/exit_icon.png
		ICONS/about_icon.png
	
	
*/
// Компиляция ресурсов только rcc.exe Qt:
// rcc -binary t.qrc -o t.rcc
//
// Запуск программы:
// testres qt t.rcc

import std.stdio;
import qte5;				// Графическая библиотека QtE5
import core.runtime;		// Обработка входных параметров
import asc1251;
import std.file;

extern (C) {
	void  onPaintWidget(CView* uk, void* ev, void* qpaint)  { (*uk).runPaint(ev, qpaint); };
}
class CView : QWidget {
	QImage im;
	QPoint pointer;
	QPixmap pm1;

	this() {
		super(null);	resize(600, 250);
		pm1 = new QPixmap();
		pm1.load(":ICONS/exit_icon.png","PNG");
		setPaintEvent(&onPaintWidget, aThis());
	}
	// ______________________________________________________________
	// Перерисовать себя
	void runPaint(void* ev, void* qpaint) { //-> Перерисовка области
		QBitmap bm = new QBitmap(new QSize(pm1.width, pm1.height)); 
				bm.setNoDelete(true); // В Linux без этого валится ...
		bm.fill();

		QPainter qp2 = new QPainter(bm);
		qp2.setPen( new QPen(new QColor(QtE.GlobalColor.color1)) );
		QFont fnt = new QFont(); fnt.setPointSize(160); fnt.setBold(true);
		qp2.setFont( fnt );
		qp2.drawText(50, 150, "Привет из QtE5");
		qp2.end();
		
		pm1.setMask(bm);
		
		QPainter qp = new QPainter('+', qpaint);
		qp.drawPixmap(pm1,  0, 0, pm1.width, pm1.height);
		qp.end();
	}
}

void help() {
	writeln(toCON("Запуск: testres.exe qt|d имяФайлаРесурсов.rcc"));
}

int main(string[] ards) {
	bool fDebug = true; 
	if (1 == LoadQt(dll.QtE5Widgets, fDebug)) return 1;
	QApplication app = new QApplication(&Runtime.cArgs.argc, Runtime.cArgs.argv, 1);

	if(ards.length != 3) { help(); return 1; }
	QResource res = new QResource();
	bool r;
	try {
		switch(ards[1]) {
			case "qt":
				r = res.registerResource(ards[2]);
				break;
			case "d":
				auto data = cast(ubyte[]) read(ards[2]);
				r = res.registerResource(cast(ubyte*)data);
				break;
			default:
				help(); return 1;
		}
	} catch(Throwable) { help(); return 1; }
	if(!r) {
		writeln(toCON("Файл ресурсов загружен, но он кривой ...")); return 1;
	}
	CView widget = new CView(); widget.saveThis(&widget); widget.show();
	return app.exec();
}

Все манипуляции делаются в методе Paint, который вызывается на событие PaintWidget. Не буду останавливаться на том, как включить данное событие. Рассмотрим то, что происходит в Paint методе. В демо он называется runPaint(…).

  • Первое. Создается объект bm класса Qbitmap, по размерам объекта pm1 типа Qpixmap (строки в программе 43-45). Следует заметить, что PNG-файл (или другой файл изображения, например, JPG) следует брать больше по размеру, чем будет размер виджета.
  • Заполняем (45 строка) данными по умолчанию (fill()), тем самым получив однородную маску. Это подготовительная операция. Наверное её надо вынести в конструктор … ну да ладно.
  • Строки (47 – 52). Создается «рисовальщик» qp2, которому сказано, что рисовать он будет строго цветом color1 (QtE.GlobalColor.color1) в нашей битовой матрице bm. Далее просто рисуется большая жирная надпись. Фраза (52) заканчивает модификацию битовой матрицы.
  • Строка (54). pm1.setMask(bm) — фактически происходит наложение битовой матрицы на изображение из PNG файла. При этом все пикселы в матрице имеющие цвет color1 становятся прозрачными. А таким цветом у нас написан тект. Получается модификация нашего графического файла лежащего в Qpixmap, таким образом, что там где была надпись, пикселы сохранились, а там где надписи не было забились фоном виджета.
  • Строки (56-58) просто отображают наш Qpixmap на виджет.

Более подробно можно почитать в инете, где народ на форумах обсуждает данные моменты. Еще одно замечание: не обязательно смотреть С++ примеры. С таким же успехом можно смотреть примеры на python (PyQt5) и т.д.

Посмотрел ради интереса на Lazarus (паскаль). Интересная штука. Попробовал прикрутить к нему (к паскалю) QtE5. Работает!

Посмотрел Go. Там тоже есть работа с Qt (правда 4.7), но проработка значительно сильнее, чем у меня, хотя подход такой же. Методов там раз в 10 больше, чем у меня.

Ну а главное достоинство QtE5 в том, что новые методы и классы вполне можно добавить самому, при этом не нужно ставить кучу дополнительного софта типа Cmake, и т.д и т.п. Достаточно одного QtCreator.

Добавить комментарий