Название: Вращение QPixmap в QLabel Отправлено: 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; Заполним переменные значениями: Код: x=tPixmap.width(); Поменяем местами высоту и ширину в QRect – именно это произойдет в результате поворота на 90 градусов: Код: rc.setWidth(h); Создадим еще один пустой 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 (https://github.com/viktand/rlab90) Название: Re: Вращение QPixmap в QLabel Отправлено: viktand от Сентябрь 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> Для хранения оригинального QPixmap объявим в области глобальных переменных указатель на соответствующий виджет: Код: QPixmap *mem_pix; Не будем создавать сам виджет, т.к. если программа часть большого проекта, то весьма вероятно, что крутить картинку будет нужно не всегда. В файле .h Объявим функцию (слот): Код: QPixmap rotor(int a, QPixmap pix); Функция будет получать в качестве аргументов угол a, на который надо повернуть картинку и саму картинку pix. Возвращать будет уже повернутый QPixmap. Первое, что нужно сделать при первом вызове функции вращения – сохранить оригинальный QPixmap: Код: if (mem_pix==0) Теперь можно и повернуть: Код: ui->rLabel->setPixmap(rotor(a, *mem_pix)); Разберемся непосредственно с вращениемю т.е. со слотом rotor. На входе в слот сразу переведем градусы в радианы (потом пригодится): Код: double g=a*PI/180; Создадим холст достаточной величины: Код: QSize sz=pix.size(); Для удобства чтения кода (это необязательно) добавим пару переменных: Код: // центр холста Добавим художника: Код: QPainter p(canv_pix); Подвинем координаты и зададим поворот – все как в первой части: Код: 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)); Теперь rLabel содержит повернутую картинку, однако она плохо вписана в его геометрию. Поэтому слегка поправим его: Код: QSize sz; В проекте с примером, ссылка на который будет сейчас, rLabel имеет рамку в своих свойствах. Это не нужно. Добавлено в учебных целях для наглядности, чтобы было видно как виджет трансформируется под содержимое. git (https://github.com/viktand/rlaball) Название: Re: Вращение QPixmap в QLabel Отправлено: viktand от Сентябрь 28, 2013, 09:44 Второй пример не только более функциональный, но и более верный с точки зрения написания большого проекта, т.к. функция поворота не привязана к конкретному QPixmap. Остается сделать последний шаг и создать новый класс, унаследованный от QLabel, который будет содержать метод вращения содержимого.
Не будем разбирать здесь теорию наследования, просто предложим готовый код. Этот класс можно вставить в любой проект, скопировав в него файлы rlabel.h и rlabel.cpp и подключив (#include “rlabel.h”) к основному файлу проекта. Теперь Вы сможете крутить свои лабелы: Код: QrLabel *rLabel; // создать указатель на вращающийся Qlabel Код примера доступен на github (https://github.com/viktand/rlabelclass). Название: Re: Вращение QPixmap в QLabel Отправлено: Bepec от Сентябрь 28, 2013, 10:13 Ммм... Тема интересна, но оформление страдает. Такую тонну текста даже без абзацев мне даже читать не хочется.
PS интересно поведение компоновщика при перевороте лейбла. Какое оно? Название: Re: Вращение QPixmap в QLabel Отправлено: viktand от Сентябрь 28, 2013, 11:50 Я специально написал много букв, чтобы все подробно объяснить. Желающие смогут разобраться и подогнать алгоритм под свои нужды.
Кому нужны просто примеры, может скачать их в конце каждого раздела. Во всех случаях размер QLabel подгоняется под фактический размер QPixmap после поворота. Позиция в первом и третьем примере - с постоянными координатами верхнего левого угла, во втором примере с фиксированным центом, вокруг которого и вертится картинка. Название: Re: Вращение QPixmap в QLabel Отправлено: _OLEGator_ от Сентябрь 28, 2013, 12:03 Assistant подсказывает, что это можно сделать несколько проще:
Код
Название: Re: Вращение QPixmap в QLabel Отправлено: viktand от Сентябрь 28, 2013, 12:34 Assistant подсказывает, что это можно сделать несколько проще: Код
Я сразу сказал, что не претендую на высшую истину. Ваш пример сам по себе ничего не делает. Чтобы вызвать QPixmap::transformed, надо предварительно подготовить аргументы, потом вернуть все в QLabel, потом обновить геометрию QLabel. В итоге никакого упрощения. Всего лишь замена нескольких ясных и коротких строк кода на одну длинную и малопонятную. Об эффективности рассуждать не буду, потому что не знаю наверняка. И еще. QPixmap::transformed после нескольких вызовов испортит картинку, а у меня качество сохранится. Название: Re: Вращение QPixmap в QLabel Отправлено: Igors от Сентябрь 28, 2013, 13:17 Как-то у Вас получается "ни то - ни се". Масса текста посвящена вещам которые даже начинающий программист должен уметь делать сам, такое разжевывание совершенно излишне. С др стороны Вы все время увиливаете от простой геометрии, ну вот напр
h = a*sin(α) + b*cos(α) – это высота Заметим что transformed просто выдаст имедж такого размера, что удобнее. Ладно, хотите посчитать сами - тогда делвйте это каптиальнее. Поворот есть матричное, линейное преобразование, и оно опредедено для любого угла, с какой же стати у Вас работает только до 90 градусов и взятие по модулю?w = a*cos(α) + b*sin(α) – это ширина Тут надо заметить, что эти формулы нормально работают только от 0 до 90 градусов. Потом синус и косинус иногда будут отрицательными. Поэтому в программе будем брать их по модулю. А пока получается прав Олег - лучше пошерстить букварь и найти нужную ф-цию, чем вот так вникать в какие-то огрызки. Надеюсь моя критика конструктивна :) Название: Re: Вращение QPixmap в QLabel Отправлено: _OLEGator_ от Сентябрь 28, 2013, 13:32 to viktand
если вы пишете урок или статью, то уже претендуете на качественную реализацию. Вся ваша портянка кода укладывается в 3-4 строки, в которых используется функционал фреймворка. Название: Re: Вращение QPixmap в QLabel Отправлено: viktand от Сентябрь 28, 2013, 13:50 Хорошо, все началось с того, что мне надо было покутить картинку. Попробуйте через поиск найти готовый рецепт, а не огрызок из середины. Это очень сложно. И основная масса ищущих - это не профессионалы, которые додумают то, чего не хватает. В итоге я предлагаю готовое на 100% решение (а не огрызки), которое просто будет работать, а если кому-то потребуется что-то немного другое, то имея подробную инструкцию, он сможет быстро решить свою задачу, не вникая матрицы и трансформеры. Короче, эта статья для начинающих.
Что касается синусов, то вы явно недопоняли. Это не имеет отношения к теме вопроса, но раз уж вы спросили, то советую нарисовать все на бумаге и рассмотреть все случаи. Когда синус (косинус) становится отрицательным, то и соответствующая ему сторона разворачивается в другую сторону, т.е. тоже становится отрицательной по длине. В результате произведение получается положительным. В нашем случае проще всегда брать положительное значение длины, а синус(косинус) по модулю. Если вы привыкли использовать матрицы в программе, то это не значит, что у вас другая тригонометрия. Просто Qt все учитывает и дает готовый результат, а вы не задумываетесь о деталях. Название: Re: Вращение QPixmap в QLabel Отправлено: viktand от Сентябрь 28, 2013, 13:53 to viktand если вы пишете урок или статью, то уже претендуете на качественную реализацию. Вся ваша портянка кода укладывается в 3-4 строки, в которых используется функционал фреймворка. С удовольствием посмотрю как вы развернете лабел за 3-4 строки. Название: Re: Вращение QPixmap в QLabel Отправлено: Igors от Сентябрь 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 мерном пр-ве тоже легко написать используя эти простенькие формулы. Словом, Вам нужно подходить более концептуально, а не сбиваться на мелочные инструкции для идиотов (здесь таких нет) |