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

PDF图像中心旋转输出异常:C++实现问题问询

图像绕中心旋转输出PDF失败问题解决

问题背景

项目基于C++实现图像输出到PDF,坐标系原点为左上角,需要实现图像绕自身中心旋转,但当前使用变换矩阵后,图像始终绕左上角旋转,无法达到预期效果。

现有代码片段

变换矩阵构建代码

MatrixTransform aTrans;
aTrans.translate(-halfwidth, -halfheight);
aTrans.rotate(0.7853981634);
aTrans.translate(it->m_dLeft+halfwidth, it->m_dTop+halfheight);
DrawImage(sImageName, 0, 0, imageWidth, imageHeight);

MatrixTransform类核心实现

class MatrixTransform {
public:
    MatrixTransform(): m_matrix({0}){
        m_matrix[0][0] = 1.0f;
        m_matrix[1][1] = 1.0f;
        m_matrix[2][2] = 1.0f;
    }

    void translate(float tx, float ty) {
        m_matrix[0][2] += tx;
        m_matrix[1][2] += ty;
    }

    void rotate(float angle) {
        float cosAngle = std::cos(angle);
        float sinAngle = std::sin(angle);
        float rotationMatrix[3][3] = {
            {cosAngle, -sinAngle, 0.0f},
            {sinAngle, cosAngle, 0.0f},
            {0.0f, 0.0f, 1.0f}
        };
        multiplyMatrix(rotationMatrix);
    }

public:
    float m_matrix[3][3];

    void multiplyMatrix(float matrix[3][3]) {
        float resultMatrix[3][3] = {0};
        for (int row = 0; row < 3; ++row) {
            for (int col = 0; col < 3; ++col) {
                for (int i = 0; i < 3; ++i) {
                    resultMatrix[row][col] += m_matrix[row][i] * matrix[i][col];
                }
            }
        }
        memcpy(m_matrix, resultMatrix, sizeof(m_matrix));
    }
};

DrawImage及相关页面变换代码

void DrawImage(...) {
    m_pPage->Save();
    // 原变换参数调整
    m_pPage->SetTransform(
        aTrans.m_matrix[0][0],
        -aTrans.m_matrix[0][1],
        -aTrans.m_matrix[1][0], 
        aTrans.m_matrix[1][1],
        aTrans.m_matrix[0][2] + aTrans.m_matrix[1][0] * PageHeight,
        PageHeight - PageHeight * aTrans.m_matrix[1][1] - aTrans.m_matrix[1][2]);

    m_pPage->Draw(sImageName, dX, m_dPageHeight - dY - dH, dW, dH);
    m_pPage->Restore();
}

void Page::SetTransform(double dM11, double dM12, double dM21, double dM22, double dX, double dY)
{
    Matrix oInverse = m_pState->m_Matrix.Inverse();
    Matrix oResult;
    oResult.m11 = dM11 * oInverse.m11 + dM12 * oInverse.m21;
    oResult.m12 = dM11 * oInverse.m12 + dM12 * oInverse.m22;
    oResult.m21 = dM21 * oInverse.m11 + dM22 * oInverse.m21;
    oResult.m22 = dM21 * oInverse.m12 + dM22 * oInverse.m22;
    oResult.x   =   dX * oInverse.m11 +   dY * oInverse.m21 + oInverse.x;
    oResult.y   =   dX * oInverse.m12 +   dY * oInverse.m22 + oInverse.y;

    Concat(oResult.m11, oResult.m12, oResult.m21, oResult.m22, oResult.x, oResult.y);
}

void Page::Draw(const char* sImageName, double dX, double dY, double dWidth, double dHeight)
{
    Save();
    Concat(dWidth, 0, 0, dHeight, dX, dY);
    m_pStream->WriteEscapeName(sImageName);
    m_pStream->WriteStr(" Do\012");
    Restore();
}

关键问题分析

  1. 坐标系差异处理冲突
    PDF原生坐标系原点在左下角,而项目使用左上角原点,原代码通过m_dPageHeight - dY - dH和SetTransform中的Y轴符号调整做转换,但这种转换与自定义MatrixTransform的变换逻辑叠加后,破坏了绕中心旋转的坐标计算。

  2. SetTransform函数的设计逻辑误解
    SetTransform内部使用当前状态矩阵的逆矩阵,其作用是设置绝对变换(重置当前变换状态为目标矩阵),而非追加变换。但用户的MatrixTransform是按“平移中心→旋转→平移到目标位置”的追加顺序计算的,两者逻辑不匹配,导致变换顺序错乱。

  3. 矩阵乘法顺序的隐含规则
    矩阵乘法是右乘逻辑,即先定义的变换会后执行。用户的MatrixTransform中translate(-half,-half)→rotate→translate(pos+half, pos+half)的顺序是正确的绕中心旋转逻辑,但SetTransform的逆矩阵操作打乱了这个顺序的实际效果。

解决方案

方案1:直接使用Page::Concat追加变换(推荐)

绕过SetTransform的逆矩阵干扰,直接按顺序追加变换,同时兼容PDF坐标系:

// 计算图像在项目坐标系中的中心位置
double centerX = it->m_dLeft + imageWidth / 2.0;
double centerY = it->m_dTop + imageHeight / 2.0;
// 转换为PDF坐标系(Y轴翻转)
double pdfCenterY = m_dPageHeight - centerY;

m_pPage->Save();
// 步骤1:将原点平移到PDF坐标系中的图像中心
m_pPage->Concat(1, 0, 0, 1, centerX, pdfCenterY);
// 步骤2:绕当前原点(图像中心)旋转45度
double angle = 0.7853981634;
double cosA = cos(angle);
double sinA = sin(angle);
// PDF旋转矩阵与常规左上角坐标系的区别:sin符号调整
m_pPage->Concat(cosA, sinA, -sinA, cosA, 0, 0);
// 步骤3:将原点平移回图像左上角(相对于中心)
m_pPage->Concat(1, 0, 0, 1, -imageWidth/2.0, -imageHeight/2.0);
// 绘制图像(此时图像左上角对应变换后的原点,实现绕中心旋转)
m_pPage->Draw(sImageName, 0, 0, imageWidth, imageHeight);
m_pPage->Restore();

方案2:修改MatrixTransform以适配PDF坐标系

如果必须使用自定义MatrixTransform,需先处理Y轴翻转,再执行绕中心旋转逻辑,最后直接用Concat追加变换:

MatrixTransform aTrans;
// 先添加PDF坐标系的Y轴翻转变换
aTrans.translate(0, m_dPageHeight);
aTrans.scale(1, -1); // 需给MatrixTransform添加scale方法,实现缩放矩阵乘法
// 执行绕中心旋转的常规步骤
aTrans.translate(it->m_dLeft + halfwidth, it->m_dTop + halfheight);
aTrans.rotate(0.7853981634);
aTrans.translate(-halfwidth, -halfheight);

m_pPage->Save();
// 直接用Concat追加变换,跳过SetTransform的逆矩阵操作
m_pPage->Concat(
    aTrans.m_matrix[0][0], aTrans.m_matrix[0][1],
    aTrans.m_matrix[1][0], aTrans.m_matrix[1][1],
    aTrans.m_matrix[0][2], aTrans.m_matrix[1][2]
);
m_pPage->Draw(sImageName, 0, 0, imageWidth, imageHeight);
m_pPage->Restore();

原代码疑问解答

  • DrawImage中m_dPageHeight - dY - dH的作用:将项目左上角坐标系的图像右下角Y坐标,转换为PDF左下角坐标系的图像左下角Y坐标,完成Y轴翻转。
  • SetTransform中逆矩阵的作用:该函数设计为设置绝对变换,通过逆矩阵抵消当前已有的变换,再叠加目标矩阵,最终将页面变换状态重置为目标状态。若需叠加多个变换(如平移+旋转),应使用Concat函数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 07:54:57