You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用QTransform将任意四边形转为指定尺寸矩形?代码调试求助

QTransform实现任意四边形转指定矩形的问题排查

问题背景

在Android平台可通过SkMatrix的setPolyToPoly方法实现任意四边形到指定矩形的图像变换,但QTransform没有原生类似方法,因此将SkMatrix的相关源码改写为Qt/C++版本,已考虑两者矩阵常量的位置差异,但应用生成的QTransform后屏幕显示结果异常,需排查代码或应用逻辑问题。

原实现代码

// SkMatrix Constants
static constexpr int kMScaleX   = 0; //!< horizontal scale factor
static constexpr int kMSkewX    = 1; //!< horizontal skew factor
static constexpr int kMTransX   = 2; //!< horizontal translation
static constexpr int kMSkewY    = 3; //!< vertical skew factor
static constexpr int kMScaleY   = 4; //!< vertical scale factor
static constexpr int kMTransY   = 5; //!< vertical translation
static constexpr int kMPersp0   = 6; //!< input x perspective factor
static constexpr int kMPersp1   = 7; //!< input y perspective factor
static constexpr int kMPersp2   = 8; //!< perspective bias

// Constants for Qt
static constexpr int qtScaleX       = 0; // 11 // scale
static constexpr int qtSkewY        = 1; // 12 // shear
static constexpr int qtPerspY       = 2; // 13 // влияет rotate вокгуг YAxias / = Persp1
static constexpr int qtSkewX        = 3; // 21 // shear
static constexpr int qtScaleY       = 4; // 22 // scale
static constexpr int qtPerspX       = 5; // 23 // влияет rotate вокгуг XAxias / = Persp0
static constexpr int qtTransX       = 6; // 31 // translate
static constexpr int qtTransY       = 7; // 32 // translate
static constexpr int qtPerspZ       = 8; // 33 // не удалось повлиять на значение / = Persp2
/*
  ScaleX     SkewY     PerspY
  SkewX      ScaleY    PerspX
  TransX     TransY    PerspZ
 */

void convertSkToQt(const QTransform& skSrc, QTransform& qtDst)
{
    QList<qreal> skLine(9, 0);
    skLine[kMScaleX   ] = skSrc.m11();
    skLine[kMSkewX    ] = skSrc.m21();
    skLine[kMTransX   ] = skSrc.m31();
    skLine[kMSkewY    ] = skSrc.m12();
    skLine[kMScaleY   ] = skSrc.m22();
    skLine[kMTransY   ] = skSrc.m32();
    skLine[kMPersp0   ] = skSrc.m23();
    skLine[kMPersp1   ] = skSrc.m13();
    skLine[kMPersp2   ] = skSrc.m33();

    /*
                ScaleX     SkewY     PerspY
                SkewX      ScaleY    PerspX
                TransX     TransY    PerspZ
    */
    qtDst.setMatrix(
        skLine[kMScaleX ], skLine[kMSkewY ], skLine[kMPersp1   ]
      , skLine[kMSkewX  ], skLine[kMScaleY], skLine[kMPersp0   ]
      , skLine[kMTransX ], skLine[kMTransY], skLine[kMPersp2   ]
    );
}

bool poly4Proc(const QList<QPointF>& srcPt, QTransform& dst) {
    qreal a1, a2;
    qreal x0, y0, x1, y1, x2, y2;
    x0 = srcPt[2].x() - srcPt[0].x();
    y0 = srcPt[2].y() - srcPt[0].y();
    x1 = srcPt[2].x() - srcPt[1].x();
    y1 = srcPt[2].y() - srcPt[1].y();
    x2 = srcPt[2].x() - srcPt[3].x();
    y2 = srcPt[2].y() - srcPt[3].y();
    /* check if abs(x2) > abs(y2) */
    if ( x2 > 0 ? y2 > 0 ? x2 > y2 : x2 > -y2 : y2 > 0 ? -x2 > y2 : x2 < y2) {
        float denom = sk_ieee_float_divide(x1 * y2, x2) - y1;
        if (checkForZero(denom)) {
            return false;
        }
        a1 = (((x0 - x1) * y2 / x2) - y0 + y1) / denom;
    } else {
        float denom = x1 - sk_ieee_float_divide(y1 * x2, y2);
        if (checkForZero(denom)) {
            return false;
        }
        a1 = (x0 - x1 - sk_ieee_float_divide((y0 - y1) * x2, y2)) / denom;
    }
    /* check if abs(x1) > abs(y1) */
    if ( x1 > 0 ? y1 > 0 ? x1 > y1 : x1 > -y1 : y1 > 0 ? -x1 > y1 : x1 < y1) {
        float denom = y2 - sk_ieee_float_divide(x2 * y1, x1);
        if (checkForZero(denom)) {
            return false;
        }
        a2 = (y0 - y2 - sk_ieee_float_divide((x0 - x2) * y1, x1)) / denom;
    } else {
        float denom = sk_ieee_float_divide(y2 * x1, y1) - x2;
        if (checkForZero(denom)) {
            return false;
        }
        a2 = (sk_ieee_float_divide((y0 - y2) * x1, y1) - x0 + x2) / denom;
    }

    QList<qreal> lineMatrix(9, 0);
    lineMatrix[kMScaleX] = a2 * srcPt[3].x() + srcPt[3].x() - srcPt[0].x();
    lineMatrix[kMSkewY]  = a2 * srcPt[3].y() + srcPt[3].y() - srcPt[0].y();
    lineMatrix[kMPersp0] = a2;
    lineMatrix[kMSkewX]  = a1 * srcPt[1].x() + srcPt[1].x() - srcPt[0].x();
    lineMatrix[kMScaleY] = a1 * srcPt[1].y() + srcPt[1].y() - srcPt[0].y();
    lineMatrix[kMPersp1] = a1;
    lineMatrix[kMTransX] = srcPt[0].x();
    lineMatrix[kMTransY] = srcPt[0].y();
    lineMatrix[kMPersp2] = 1;

    dst.setMatrix(  lineMatrix[0], lineMatrix[1], lineMatrix[2]
                  , lineMatrix[3], lineMatrix[4], lineMatrix[5]
                  , lineMatrix[6], lineMatrix[7], lineMatrix[8]
    );

    return true;
}

bool setPolyToPoly(QTransform& matrix, const QList<QPointF>& src, const QList<QPointF>& dst)
{
    QTransform tempMap, result;
    if (!poly4Proc(src, tempMap))
        return false;

    bool done = false;
    result = tempMap.inverted(&done);
    if (!done) {
        qDebug().noquote() << "Can't invert matrix:" << tempMap;
        return false;
    }

    if (!poly4Proc(dst, tempMap))
        return false;

    QTransform matrixRes0 =  tempMap * result;

    convertSkToQt(matrixRes0, matrix);

    return true;
}

void test()
{
    painter->save();

    QTransform polyToPolymatrix;
    int deform = 20;

    QList<QPointF> src(4, QPointF(0, 0));
        src[0] = QPointF(0, 0);
        src[1] = QPointF(200, 0);
        src[2] = QPointF(200, 200);
        src[3] = QPointF(0, 200);

    QList<QPointF> dst(4, QPointF(0, 0));
        dst[0] = QPointF(0, 0);
        dst[1] = QPointF(200-deform, deform);
        dst[2] = QPointF(200-deform, 200-deform);
        dst[3] = QPointF(0, 200);

    setPolyToPoly(polyToPolymatrix, src, dst);

    painter->setTransform(polyToPolymatrix);

    painter->drawPixmap(QPointF(0, 0), m_pixmap, QRectF(0, 0, 200, 200));

    painter->restore();
}

排查关键点与修复方案

1. 矩阵乘法顺序错误

SkMatrix与QTransform的乘法顺序逻辑相反:

  • SkMatrix中A * B表示先应用变换B,再应用变换A
  • QTransform中A * B表示先应用变换A,再应用变换B

原代码中矩阵乘法顺序不符合Qt逻辑,需修改为:

QTransform matrixRes0 = result * tempMap;

2. 矩阵元素对应冗余

原convertSkToQt函数中通过中间列表映射元素存在冗余,直接基于QTransform的mXX方法对应SkMatrix元素即可,简化后:

void convertSkToQt(const QTransform& skSrc, QTransform& qtDst)
{
    qtDst.setMatrix(
        skSrc.m11(), skSrc.m12(), skSrc.m13(),  // m11=ScaleX, m12=SkewY, m13=PerspY
        skSrc.m21(), skSrc.m22(), skSrc.m23(),  // m21=SkewX, m22=ScaleY, m23=PerspX
        skSrc.m31(), skSrc.m32(), skSrc.m33()   // m31=TransX, m32=TransY, m33=PerspZ
    );
}

3. 浮点类型不统一

代码中混合qreal和float导致精度损失,统一替换为qreal,并使用Qt原生方法处理零值判断:

bool checkForZero(qreal value) {
    return qFuzzyIsNull(value);
}

qreal sk_ieee_float_divide(qreal numerator, qreal denominator) {
    return numerator / denominator;
}

4. 变换方向逻辑错误

Qt的setTransform是对画布进行变换,相当于将绘制内容从源坐标映射到目标坐标。若显示异常,可尝试交换setPolyToPoly的src和dst传入参数:

setPolyToPoly(polyToPolymatrix, dst, src);

5. 透视参数验证

QTransform的透视参数(m13、m23)对绘制效果影响较大,可通过打印矩阵元素值,验证poly4Proc中计算的a1、a2是否符合预期。

内容的提问来源于stack exchange,提问作者pier_nasos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 20:30:53