zoukankan      html  css  js  c++  java
  • Qt绘图(使用QPainter)翻转图像的两种方法

    我想要创造一个小人,它可以向四个方向走。我用定时器实现了绘图的循环执行,并从这个图片中把各个帧裁切下来并画出来。

    但是,我发现小人的行走动画是向右的。当小人向左走的时候就非常不自然。我想要在这种时候把图像翻转。

    于是我尝试着在QPainter中找到一个flip函数——结果居然没有!翻转这样一个非常常用的功能居然没有!

    方法一

    没有的话,我们就只能自己实现了。我所熟悉的对坐标系进行变形的函数四个:

    void shear(qreal sh, qreal sv);
    void scale(qreal sx, qreal sy);
    void rotate(qreal angle);
    void translate(qreal dx, qreal dy)
    

    其中最麻烦的是剪切函数shear。经过探索,它对于右手系的改变相当于乘上一个矩阵:(使用笛卡尔坐标系而不是默认坐标系)

    [left[ egin{array}{l} 1 &-sh\ -sv &1 end{array} ight] ]

    而对于左手系的改变则是:

    [left[ egin{array}{l} 1 & sh\ sv &1 end{array} ight] ]

    这是四个函数中唯一一个可以改变坐标系两个基底的夹角的函数。因为翻转需要我们改变整个坐标系的手性,所以这个函数是必不可少的。

    对于放缩函数scale,显然它对坐标系的改变相当于乘上一个矩阵:

    [left[ egin{array}{l} sx &0 \ 0 & sy end{array} ight] ]

    要是sx可以为负数,我的问题其实就已经解决了。但是这个函数不支持负数。

    对于旋转函数rotate,它对坐标系的改变相当于乘上一个矩阵:

    [left[ egin{array}{l} cos alpha & -sin alpha \ sin alpha & cos alpha end{array} ight] ]

    translate的作用是改变原点位置,不是对坐标系进行线性变换,所以与矩阵无关。

    于是下面就是数学推导。把初始两个基底向量((1, 0))((0, 1))变成竖向,放在矩阵里(参考3b1b的线性代数相关视频):

    [left[ egin{array}{l} 1 & 0 \ 0 & 1 end{array} ight] ]

    不妨进行shear(2, 2)

    [left[ egin{array}{l} 1 & -2\ -2 & 1 end{array} ight] ]

    此时坐标系就已经变成左手系了。还需要进行一些转换。进行rotate(-90)

    [left[ egin{array}{l} -2 & -1\ 1 & 2 end{array} ight] ]

    记它为矩阵(A)

    我们的目标是水平翻转,所以目标坐标系的两个基底向量是((-1, 0))((0, 1))。所以现在我们要找到一个矩阵(X),使得(X cdot A = left[egin{array}{l}-1&0\0&1end{array} ight])

    求出(A)的逆元为:

    [A^{-1}= left[ egin{array}{l} -frac{2}{3} & -frac{1}{3}\ frac{1}{3} & frac{2}{3} end{array} ight] ]

    所以可得

    [X = left[egin{array}{l}-1&0\0&1end{array} ight] cdot A^{-1} = left[ egin{array}{l} frac{2}{3} & frac{1}{3}\ frac{1}{3} & frac{2}{3} end{array} ight] = left[ egin{array}{l} frac{2}{3} & 0\ 0 & frac{2}{3} end{array} ight] cdot left[ egin{array}{l} 1 & frac{1}{2}\ frac{1}{2} & 1 end{array} ight] ]

    所以再分别进行一次shear(0.5, 0.5)scale(2.0/3, 2.0/3)就可以完成翻转了。

    综上所述,要在一个以((cx,cy))为左上角的区域内输出一个宽wid的被水平翻转的QPixmap变量frame,使用以下代码:

    void drawPixmapFlippedHorizentally(QPainter &qpainter, int cx, int cy, int wid, const QPixmap &frame) {
            qpainter.translate(cx + wid - 1, cy);
            qpainter.shear(2, 2);
            qpainter.rotate(-90);
            qpainter.shear(0.5, 0.5);
            qpainter.scale(2.0/3, 2.0/3);
    
            qpainter.drawPixmap(QPoint(0, 0), frame);
    
            qpainter.shear(2, 2);
            qpainter.rotate(-90);
            qpainter.shear(0.5, 0.5);
            qpainter.scale(2.0/3, 2.0/3);
            qpainter.translate(-cx - wid + 1, -cy);
    }
    

    打包成一个函数就可以了。真是不容易啊。

    方法二

    使用QPainter自带的setViewport函数,对展示的坐标系进行转换。

    void setViewport(int x, int y, int width, int height)
    

    作用是把画布转换为以((x,y))为原点,(width)为宽,(height)为高的画布。

    原始的画布的原点是((0,0)),宽是(width()),高是(height())(注:这两个函数返回的是窗口的宽和高)。所以,执行函数qpainter.setViewport(0, 0, width(), height())就等于把画布还原到默认状态。

    这个函数也能实现坐标系的变换。改变原点位置与矩阵无关,故只有后面的两个参数对坐标系需要纳入考虑。相当于下面这个矩阵:

    [left[ egin{array}{l} frac{width}{width()} & 0\ 0 & frac{height}{height()} end{array} ight] ]

    其中(width)(height)是函数的参数,而(width())(height())则是窗口的大小。

    所以,只需要以下代码就可以输出一个翻转的图像:

    void drawPixmapFlippedHorizentally(QPainter &qpainter, int cx, int cy, int wid, const QPixmap &frame) {
            qpainter.setViewport(cx + wid - 1, cy, -width(), height());
            qpainter.drawPixmap(QPoint(0, 0), frame);
            qpainter.setViewport(0, 0, width(), height());
    }
    

    这是我第一次在生活实践中大量使用线性代数的知识。

  • 相关阅读:
    自动布局
    初探 iOS8 中的 Size Class
    iOS数据安全性问题
    iOS应用程序之间的跳转
    iOS 关于xml解析的问题
    iOS中的一些基础知识点
    关于iOS项目中使用ZBarSDK
    iOS中关于google地图的用法
    基于4.5Framework web程序、SQLSERVER数据库打包
    docker私有仓库搭建
  • 原文地址:https://www.cnblogs.com/lightmain-blog/p/15004000.html
Copyright © 2011-2022 走看看