Продолжаем.
Поворот на угол, не кратный 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