绘图
坐标系统
对于给定的绘图设备,在绘制时具有两个坐标系:逻辑坐标系、物理坐标系
物理坐标系:对应于Qt的
QPainter::viewPort
,是建立在绘图设备上的坐标系逻辑坐标系对应于Qt的
QPainter::window
,在绘制时将会绘制到此坐标系
在默认情况下,物理坐标和逻辑坐标是重合的(原点相同,大小相同)
这里说坐标系 |
window - viewport 坐标转换
window 代表的是逻辑坐标,QPainter 在此坐标上进行绘制操作 viewport 代表的是物理坐标,代表了 QWidget,显示时遵循此坐标
世界坐标转换,QPainter绘制在 window 上的图形将被映射到 viewport 上
首先看看 window 和 viewport 的函数签名:
setViewport(int x, int y, int width, int height)
setWindow(int x, int y, int width, int height)
此处,(x, y)
代表 window/viewport 左上角的坐标,(width, height)
代表 window/viewport 的大小。
window 和 viewport 的相对坐标:
而 viewport 代表物理坐标,window 代表逻辑坐标。所谓物理坐标,以 (x, y)
原点,x 轴向右增长,y 轴向下增长。所谓逻辑坐标,以 (x, y)
和 (width, height)
围成的矩形的中心为原点,x 轴向右增长,y 轴向下增长。
如图:
要实现上述效果,使用的代码如下:
qreal w = this->width();
qreal h = this->height();
painter.setViewport(0, 0, w, h); // ①
painter.setWindow(w/(-2),h/(-2),w,h);
需要注意的是 ① ,由于默认情况下 物理坐标与部件的窗口是重叠的,所以此处代码可以省略。
window 和 viewport 的大小转换:
viewport 和 window 的第二种签名如下:
QPainter::setViewport(const QRect &rectangle)
QPainter::setWindow(const QRect &rectangle)
两者对应的 rectangle
将会通过映射使两者大小相同:
如:
设代码如下:
qreal w = width();
qreal h = height();
paint.setViewport(QRect(0, 0, w, h));
paint.setWindow(QRect(0, 0, w/2, h/2));
此时由于 viewport 代表的矩形比 window 代表的矩形大两倍(按长宽来看,而不是按面积),因此,window 的一个单位长度 = viewport 的两个单位长度。
又由于实际上绘图是绘制在 window 上的,因此,看到的图形将会比绘制的图形大两倍(按长宽)
例如:
// window 和 viewport 为 1 : 1
QPainter painter(this);
painter.setPen(Qt::red);
painter.drawRect(20, 20, 40, 40);
// 设置 window 和 viewport 为 2 : 1
painter.setWindow(0, 0, width()*2, height()*2);
painter.setPen(Qt::blue);
painter.drawRect(20, 20, 40, 40);
效果如下:
双缓冲绘图
现在假设要实现下面功能:
使用鼠标在主窗口上画矩形
画的矩形在每次都保留
实现方案1.0:
class MainWindow : public QMainWindow
{
private:
Ui::MainWindow *ui;
protected:
void paintEvent(QPaintEvent *event) override{
QPainter painter(this);
painter.drawRect(QRectF(startPoint,endPoint));
};
void mousePressEvent(QMouseEvent *event) override{
this->startPoint = event->pos();
update();
};
void mouseMoveEvent(QMouseEvent *event) override{
this->endPoint = event->pos();
update();
};
void mouseReleaseEvent(QMouseEvent *event) override{
this->endPoint = event->pos();
update();
};
private:
QPoint startPoint;
QPoint endPoint;
};
但是实际运行后发现每次绘图后上一次绘制的矩形都会丢失。其根本问题在于每次重新绘制后没有保留上一次绘制的结果
实现方案2.0
现在我们先在 QPixmap 进行绘制,然后再把 QPixmap 绘制到 MainWindow,这样就可以保留上一次绘制的结果:
class MainWindow : public QMainWindow
{
private:
Ui::MainWindow *ui;
protected:
void paintEvent(QPaintEvent *event) override{
QPainter painter(&mainPix);
painter.drawRect(QRectF(startPoint,endPoint));
QPainter painter2(this);
painter2.drawPixmap(QRectF(0,0,this->width(),this->height()),mainPix,QRectF(0,0,mainPix.width(),mainPix.height()));
}
};
void mousePressEvent(QMouseEvent *event) override{
this->startPoint = event->pos();
update();
};
void mouseMoveEvent(QMouseEvent *event) override{
this->endPoint = event->pos();
update();
};
void mouseReleaseEvent(QMouseEvent *event) override{
this->endPoint = event->pos();
update();
};
private:
QPoint startPoint;
QPoint endPoint;
QPixmap mainPix;
};
实际运行结果如下:
其原因在于在鼠标移动过程中不断更新了 endPoint ,而且不断触发了 paintEvent , 这导致在鼠标移动过程中的矩形也被保存在 mainPix 中,但是我们并不需要鼠标移动过程中绘制的矩形。
实现方案3.0
现在我们使用两个 QPixmap , 一个 mainPix 用于保存每次绘制的结果,而 tmpPix 则用于绘制鼠标移动过程中的矩形,同时为了消除鼠标移动过程中绘制的矩形,在每次 paintEvent 中都将 mainPix 复制到 tmpPix。这样,中间绘制的矩形就被清除了:
class MainWindow : public QMainWindow
{
private:
Ui::MainWindow *ui;
protected:
void paintEvent(QPaintEvent *event) override{
QPainter painter;
if(!isDone){ // 若绘制未完成,则将矩形绘制到 tmpPix 上
tmpPix = mainPix; // 消除残影
painter.begin(&tmpPix);
painter.drawRect(QRectF(startPoint,endPoint));
painter.end();
painter.begin(this);
painter.drawPixmap(QRectF(0,0,this->width(),this->height()),tmpPix,QRectF(0,0,tmpPix.width(),tmpPix.height()));
painter.end();
}else{ // 如绘制已完成,则将矩形绘制到 mainPix 上
painter.begin(&mainPix);
painter.drawRect(QRectF(startPoint,endPoint));
painter.end();
painter.begin(this);
painter.drawPixmap(QRectF(0,0,this->width(),this->height()),mainPix,QRectF(0,0,mainPix.width(),mainPix.height()));
painter.end();
}
};
void mousePressEvent(QMouseEvent *event) override{
this->startPoint = event->pos();
this->isDone = false;
update();
};
void mouseMoveEvent(QMouseEvent *event) override{
this->endPoint = event->pos();
update();
};
void mouseReleaseEvent(QMouseEvent *event) override{
this->endPoint = event->pos();
this->isDone = true;
update();
};
private:
bool isDone = false;
QPoint startPoint;
QPoint endPoint;
QPixmap mainPix;
QPixmap tmpPix;
};
运行结果如下:
在实现方案3.0中,我们使用了两个 QPixmap 用来绘制矩形,因此又被称为 双缓冲绘图
在上面代码初始化 QPixmap 时,需要将 QPixmap 的初始化代码放置在 |
图片压缩
常见的 PNG 是一种压缩格式,在内存中会进行一次展开,展开大小为:水平像素×垂直像素×每个像素所需位数/8 (字节)。对于常见的真彩色一共是 24 位,还有 32 位色(新增了 8 位用于代表灰度)。 rgba 就是 32 位色
颜色越单一,画面约简单的图片越容易压缩。但是这些在内存中都是会被展开的。尺寸 500MB 的 PNG 照片在内存中展开大小约为 6G
优化方式有两种:
一种方式是对图片的颜色进行减少,例如将 32 位色降低为 24 位色 一种是对图片进行裁剪。对图片进行裁剪会减少占用内存,放大则会增加占用内存。为了快速得到高质量图片,可以使用 Qt 先快速裁剪到目标尺寸的 1.5 倍,再精确裁剪到目标尺寸,这样处理比较快,而且画质比较好