Russian Qt Forum
Март 29, 2024, 02:28 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: Вращение QPixmap в QLabel  (Прочитано 18201 раз)
viktand
Гость
« : Сентябрь 28, 2013, 09:13 »

Дано: QLabel, в нем QPixmap.

Задача состоит в том, чтобы повернуть этот QPixmap внутри Qlabel. «Гугление» показывает, что вопрос периодически поднимается, как-то решается, но ясного рецепта найти не получается. Вообще странно, что QPixmap не имеет такого метода. Ну да ладно.

Все, что будет написано ниже, ни в коей степени не претендует на единственную истину. Если кто-то скажет, что можно проще, иначе, правильней, эффективней и т.п., то я сразу согласен. Я только хочу показать по-настоящему работавший алгоритм от начала до конца. Примеры работают в Qt 4.8.4, но думаю, что это не принципиально.

В целом алгоритм получается такой:

1. Скопировать QPixmap из QLabel во временный QPixmap;
2. Создать QPainter;
3. Задать в QPainter поворот;
4. Отрисовать QPixmap;
5. Забрать из QPainter результат;
6. Вставить его в QLabel.

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

Для начала разберем поворот на 90 градусов. Он проще и не портит (об этом ниже) картинку.

Пусть на форме у нас имеется QLabel, а имя у него rLabel. Создаем временный QPixmap:

Код:
QPixmap tPixmap(*ui->rLabel->pixmap());

Далее нам понадобится информация о размерах и координатах, поэтому создадим еще QSize, QRect и несколько int’ов:

Код:
QSize size;
QRect rc;
int w, h, x, y;

Заполним переменные значениями:

Код:
x=tPixmap.width();
y=tPixmap.height();
size=tPixmap.size();
rc=ui->rLabel->geometry();
w=rc.width();
h=rc.height();

Поменяем местами высоту и ширину в QRect – именно это произойдет в результате поворота на 90 градусов:

Код:
rc.setWidth(h);
rc.setHeight(w);

Создадим еще один пустой QPixmap удвоенного размера – он будет служить холстом для рисования:

Код:
QPixmap rotatePixmap(size*2);

Создадим QPainter, связанный со вторым QPixmap:

Код:
QPainter p(&rotatePixmap);

Сдвинем систему координат QPainter’а в его (точнее QPixmap’а) центр:

Код:
p.translate(rotatePixmap.size().width()/2,   rotatePixmap.size().height()/2);

В принципе, с математической точки зрения, это делать не надо, но в нашем случае мы этим сдвигом несколько упрощаем дальнейшие расчеты и можем использовать холст всего лишь удвоенного размера. Если не сдвигать координаты, то картинка будет вращаться вокруг верхнего левого угла и нам потребуется холст со стороной 2*sqrt(h*h+w*w). Его площадь, а значит оперативная память под него и вычислительные ресурсы на его обслуживание, будет больше в (2*sqrt(h*h+w*w))^2/(2*h*2*w) раз.
Не трудно посчитать, что для любого квадрата прирост ресурсов составит ровно в два раза.

Зададим поворот:

Код:
p.rotate(90);

Вернем координаты на место:

Код:
p.translate(-rotatePixmap.size().width()/2, -rotatePixmap.size().height()/2);

Нарисуем на холсте исходный QPixmap, в этот момент он и повернется:

Код:
p.drawPixmap(y,x, tPixmap);

Закончим работу с QPainter:

Код:
p.end();

Т.к. рисовали мы от точки (y,x), то результат поворота окажется точно в нижнем правом углу холста (это чистая геометрия, опустим ее). Забираем результат, т.е. просто вырезаем:

Код:
tPixmap=rotatePixmap.copy(0, 2*y-x, y, x);

Использование аргументов x и y здесь тоже в чисто виде геометрия, кому интересно может сам разобраться.
Теперь остается только поменять размеры Qlabel под новый формат:

Код:
ui->rLabel->setGeometry(rc);

и поместить результат на место:

Код:
ui->rLabel->setPixmap(tPixmap);

Все.

Работающий код примера здесь : git
« Последнее редактирование: Сентябрь 28, 2013, 11:26 от xintrea » Записан
viktand
Гость
« Ответ #1 : Сентябрь 28, 2013, 09:31 »

Продолжаем.

Поворот на угол, не кратный 90 градусов сопровождается неминуемой потерей качества исходной картинки.

Чтобы понять, о чем идет речь, проведем простой опыт (можно в уме). Нарисуем на бумаге сетку – это будет матрица пикселей экрана. Теперь наложим сверху кальку и скопируем сетку на нее – это будет изображение. Можно разукрасить квадратики для большей реалистичности. Проколем иголкой один квадратик по центру и повернем кальку на иголке на какой-нибудь угол – это будет поворот изображения.

Нетрудно заметить, что практически ни один квадратик на кальке не совпадет целиком с квадратиком на «экране». Ну, может за редким исключением. Вот с этого места и начинаются искажения, т.к. пиксель экрана может показать только один цвет. Разными алгоритмами подгоняют цвет этого пикселя так, чтобы он более-менее отражал то, что на него попало, как-то смешивая и усредняя цвета покрывающих его пикселей картинки. Но в любом случае получается потеря качества.

Если сделать несколько поворотов подряд, то картинка может размыться до полного тумана. Никаких способов кардинальной борьбы с этим нет, есть только алгоритмы улучшения. Поэтому первое, что нужно сделать – это сохранить исходную копию QPixmap’a, чтобы не накапливать потерю качества. Т.е. мы будем каждый раз поворачивать QPixmap из исходного положения.

Второй нюанс состоит в том, что все отображаемые виджеты имеют прямоугольную форму, со сторонами, параллельными краям экрана. Вообще, форма в теории может быть любой, но это мы не будем рассматривать. Таким образом, чтобы нарисовать на экране прямоугольник со сторонами a – ширина на b - высота, повернутый на α градусов, нам понадобится холст в виде прямоугольника со сторонами:

h = a*sin(α) + b*cos(α) – это высота  
w = a*cos(α) + b*sin(α) – это ширина


Тут надо заметить, что эти формулы нормально работают только от 0 до 90 градусов. Потом синус и косинус иногда будут отрицательными. Поэтому в программе будем брать их по модулю.

В этот прямоугольник аккуратно впишется наш повернутый QPixmap, свободное место по углам надо закрасить в прозрачный цвет, чтобы зрителям казалось, что они видят действительно повернутую картинку.

Ну и третий вопрос, который следует решить, прежде чем писать программу, а где на холсте QPainter’a, в котором будем крутить, искать результат кручения? Т.е. нам надо будет вырезать из холста прямоугольник, размеры которого рассчитаны выше, но сначала надо узнать его координаты х,y – верхний левый угол.

Как и в предыдущем случае, мы будем сдвигать систему координат в центр, таким образом, у нас будет опорная точка (центр) xc, yc , от которой можно легко вычислить координаты верхнего левого угла:

x=xc-(w/2)
y=yc-(h/2)


На этом теория заканчивается, начинаем писать программу.

Предположим, что на форме имеется QLabel с именем rLabel, в котором содержится QPixmap, который надо повернуть.

Подключим и объявим необходимое:

Код:
#include <QSize>
#include <QPainter>
#include <math.h>
#define PI 3.14159265

Для хранения оригинального QPixmap объявим в области глобальных переменных указатель на соответствующий виджет:

Код:
QPixmap *mem_pix;

Не будем создавать сам виджет, т.к. если программа часть большого проекта, то весьма вероятно, что крутить картинку будет нужно не всегда. В файле .h Объявим функцию (слот):

Код:
QPixmap rotor(int a, QPixmap pix);

Функция будет получать в качестве аргументов угол a, на который надо повернуть картинку и саму картинку pix. Возвращать будет уже повернутый QPixmap.

Первое, что нужно сделать при первом вызове функции вращения – сохранить оригинальный QPixmap:

Код:
if (mem_pix==0)  
mem_pix = new QPixmap(*ui->rLabel->pixmap());

Теперь можно и повернуть:

Код:
ui->rLabel->setPixmap(rotor(a, *mem_pix));

Разберемся непосредственно с вращениемю т.е. со слотом rotor. На входе в слот сразу переведем градусы в радианы (потом пригодится):

Код:
double g=a*PI/180;

Создадим холст достаточной величины:

Код:
QSize sz=pix.size();
QPixmap canv_pix(sz*2);
canv_pix.fill(Qt::transparent); // залить пустотой

Для удобства чтения кода (это необязательно) добавим пару переменных:

Код:
// центр холста
int x=sz.width();
int y=sz.height();

Добавим художника:

Код:
QPainter p(canv_pix);

Подвинем координаты и зададим поворот – все как в первой части:

Код:
p.translate(x,y);
p.rotate(a);
p.translate(-x,-y);

Нарисуем на холсте исходный QPixmap, в этот момент он повернется и окажется в центре:

Код:
p.drawPixmap(x/2,y/2, pix);

Точка рисования (x/2;y/2) обеспечивает рисование картинки точно в центре холста. Закончим работу с QPainter:

Код:
p.end();

Теперь в центре холста у нас нарисована повернутая картинка. Просто вырежем ее.

Возвращаемся к нашей тригонометрии:

Код:
int h=x*fabs(sin(g))+ y*fabs(cos(g));
int w=x*fabs(cos(g))+ y*fabs(sin(g));
x=x-w/2;
y=y-h/2;
pix=canv_pix.copy(x, y, w, h);
return pix;

Теперь rLabel содержит повернутую картинку, однако она плохо вписана в его геометрию. Поэтому слегка поправим его:

Код:
QSize sz;
QRect rc;
QPoint pn;
rc=ui->rLabel->geometry();
pn.setX(rc.x()+rc.width()/2);
pn.setY(rc.y()+rc.height()/2);
sz=ui->rLabel->pixmap()->size();
rc.setWidth(sz.width());
rc.setHeight(sz.height());
rc.setX(pn.x()-rc.width()/2);
rc.setY(pn.y()-rc.height()/2);
ui->rLabel->setGeometry(rc);
ui->label->setText(QString::number(value));

В проекте с примером, ссылка на который будет сейчас, rLabel имеет рамку в своих свойствах. Это не нужно. Добавлено в учебных целях для наглядности, чтобы было видно как виджет трансформируется под содержимое. git
« Последнее редактирование: Сентябрь 28, 2013, 11:46 от xintrea » Записан
viktand
Гость
« Ответ #2 : Сентябрь 28, 2013, 09:44 »

Второй пример не только более функциональный, но и более верный с точки зрения написания большого проекта, т.к. функция поворота  не привязана к конкретному QPixmap. Остается сделать последний шаг и создать новый класс, унаследованный от QLabel, который будет содержать метод вращения содержимого.

Не будем разбирать здесь теорию наследования, просто предложим готовый код. Этот класс можно вставить в любой проект, скопировав в него файлы rlabel.h и rlabel.cpp и подключив (#include “rlabel.h”) к основному файлу проекта. Теперь Вы сможете крутить свои лабелы:

Код:
QrLabel *rLabel; // создать указатель на вращающийся Qlabel
...
// Создать QrLabel
rLabel=new QrLabel(ui->centralWidget);
     QImage im(":/new/prefix1/cat"); // тут может быть что-то ваше
     rLabel->setPixmap(QPixmap::fromImage(im));
     QRect rc;
     rc.setX(100);
     rc.setY(70);
     rc.setWidth(im.width());
     rc.setHeight(im.height());
     rLabel->setGeometry(rc);
     rLabel->show();
...

rlabel.rotor(a) // повернуть на угол а

Код примера доступен на github.
« Последнее редактирование: Сентябрь 28, 2013, 11:48 от xintrea » Записан
Bepec
Гость
« Ответ #3 : Сентябрь 28, 2013, 10:13 »

Ммм... Тема интересна, но оформление страдает. Такую тонну текста даже без абзацев мне даже читать не хочется.

PS интересно поведение компоновщика при перевороте лейбла. Какое оно?
Записан
viktand
Гость
« Ответ #4 : Сентябрь 28, 2013, 11:50 »

Я специально написал много букв, чтобы все подробно объяснить. Желающие смогут разобраться и подогнать алгоритм под свои нужды.
Кому нужны просто примеры, может скачать их в конце каждого раздела.

Во всех случаях размер QLabel подгоняется под фактический размер QPixmap после поворота. Позиция в первом и третьем примере - с постоянными координатами верхнего левого угла, во втором примере с фиксированным центом, вокруг которого и вертится картинка.
Записан
_OLEGator_
Гость
« Ответ #5 : Сентябрь 28, 2013, 12:03 »

Assistant подсказывает, что это можно сделать несколько проще:
Код
C++ (Qt)
QPixmap QPixmap::transformed ( const QTransform & transform, Qt::TransformationMode mode = Qt::FastTransformation ) const
QTransform & QTransform::rotate ( qreal angle, Qt::Axis axis = Qt::ZAxis )
Записан
viktand
Гость
« Ответ #6 : Сентябрь 28, 2013, 12:34 »

Assistant подсказывает, что это можно сделать несколько проще:
Код
C++ (Qt)
QPixmap QPixmap::transformed ( const QTransform & transform, Qt::TransformationMode mode = Qt::FastTransformation ) const
QTransform & QTransform::rotate ( qreal angle, Qt::Axis axis = Qt::ZAxis )

Я сразу сказал, что не претендую на высшую истину.
Ваш пример сам по себе ничего не делает. Чтобы вызвать  QPixmap::transformed, надо предварительно подготовить аргументы, потом вернуть все в QLabel, потом обновить геометрию QLabel.
В итоге никакого упрощения. Всего лишь замена нескольких ясных и коротких строк кода на одну длинную и малопонятную. Об эффективности рассуждать не буду, потому что не знаю наверняка.

И еще. QPixmap::transformed после нескольких вызовов испортит картинку, а у меня качество сохранится.
« Последнее редактирование: Сентябрь 28, 2013, 12:54 от viktand » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #7 : Сентябрь 28, 2013, 13:17 »

Как-то у Вас получается "ни то - ни се". Масса текста посвящена вещам которые даже начинающий программист должен уметь делать сам, такое разжевывание совершенно излишне. С др стороны Вы все время увиливаете от простой геометрии, ну вот напр

h = a*sin(α) + b*cos(α) – это высота 
w = a*cos(α) + b*sin(α) – это ширина


Тут надо заметить, что эти формулы нормально работают только от 0 до 90 градусов. Потом синус и косинус иногда будут отрицательными. Поэтому в программе будем брать их по модулю.
Заметим что transformed просто выдаст имедж такого размера, что удобнее. Ладно, хотите посчитать сами - тогда делвйте это каптиальнее. Поворот есть матричное, линейное преобразование, и оно опредедено для любого угла, с какой же стати у Вас работает только до 90 градусов и взятие по модулю?

А пока получается прав Олег - лучше пошерстить букварь и найти нужную ф-цию, чем вот так вникать в какие-то огрызки. Надеюсь моя критика конструктивна  Улыбающийся
Записан
_OLEGator_
Гость
« Ответ #8 : Сентябрь 28, 2013, 13:32 »

to viktand
если вы пишете урок или статью, то уже претендуете на качественную реализацию.
Вся ваша портянка кода укладывается в 3-4 строки, в которых используется функционал фреймворка.
Записан
viktand
Гость
« Ответ #9 : Сентябрь 28, 2013, 13:50 »

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

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

Записан
viktand
Гость
« Ответ #10 : Сентябрь 28, 2013, 13:53 »

to viktand
если вы пишете урок или статью, то уже претендуете на качественную реализацию.
Вся ваша портянка кода укладывается в 3-4 строки, в которых используется функционал фреймворка.

С удовольствием посмотрю как вы развернете лабел за 3-4 строки.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #11 : Сентябрь 28, 2013, 17:46 »

Что касается синусов, то вы явно недопоняли. Это не имеет отношения к теме вопроса, но раз уж вы спросили, то советую нарисовать все на бумаге и рассмотреть все случаи. Когда синус (косинус) становится отрицательным, то и соответствующая ему сторона разворачивается в другую сторону, т.е. тоже становится отрицательной по длине. В результате произведение получается положительным. В нашем случае проще всегда брать положительное значение длины, а синус(косинус) по модулю.
Не злитесь на слово "огрызки"  Улыбающийся А так молодец, хотите осмыслить, а не запомнить. Про кальку тоже доходчиво написали

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

cos(a + b) = cos(a) * cos(b) - sin(a) * sin(b);
sin(a + b) = cos(a) * sin(b) + sin(a) * cos(b);

Кстати поворот в 3-x мерном пр-ве тоже легко написать используя эти простенькие формулы. Словом, Вам нужно подходить более концептуально, а не сбиваться на мелочные инструкции для идиотов (здесь таких нет)
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.101 секунд. Запросов: 22.