【问题标题】:How to transpose in-place a bitmap in C++如何在 C++ 中就地转置位图
【发布时间】:2019-03-20 23:20:30
【问题描述】:

我试图创建一个函数来转置 就地位图。但是到目前为止,我得到的结果都是一团糟,我找不到我做错了什么。

源位图是 ARGB 格式的一维像素阵列。

void transpose(uint8_t* buffer, const uint32_t width, const uint32_t height)
{
    const size_t stride = width * sizeof(uint32_t);

    for (uint32_t i = 0; i < height; i++)
    {
        uint32_t* row = (uint32_t*)(buffer + (stride * i));
        uint8_t* section = buffer + (i * sizeof(uint32_t));

        for (uint32_t j = i + 1; j < height; j++)
        {
            const uint32_t tmp = row[j];
            row[j] = *((uint32_t*)(section + (stride * j)));
            *((uint32_t*)(section + (stride * j))) = tmp;
        }
    }
}

更新

为了澄清和避免混淆,因为似乎有些人认为这只是一个旋转图像问题。 转置图像由 2 个变换组成:1) 水平翻转 2) 逆时针旋转 90。 (如图片示例所示,查看箭头方向

【问题讨论】:

  • 你的例子有height != widht,所以不太清楚你的意思是就地
  • 就地我的意思是不将结果复制到不同的位图中,也不分配与源具有相同尺寸的临时工作缓冲区。所以,直接转换输入位图缓冲区。
  • 最好将每个像素的地址计算拆分为一个小的内联函数,以减少正在进行的数学运算
  • 我收回我之前关于 stride 的评论,你在 uint32_t* 和 uint8_t* 之间来回切换让我感到困惑

标签: c++ bitmap transpose in-place


【解决方案1】:

我认为问题比您意识到的要复杂得多,并且不仅仅是将x, y 处的像素与y, x 处的像素交换的情况。如果您考虑一个 3*7 像素的图像,其中我将像素标记为 a-u

abcdefg
hijklmn
opqrstu

旋转此图像给出:

aho
bip
cjq
dkr
els
fmt
gnu

将两个图像转换为一维数组给出:

abcdefghijklmnopqrstu

ahobipcjqdkrelsfmtgnu

注意b 已移动到d 的位置,但已被h 取代。

重新考虑您的算法,将其绘制成一个小图像,并在尝试实施之前确保它有效。

由于任务的复杂性,它实际上最终可能会更快地创建一个临时缓冲区,旋转到该缓冲区然后复制回来,因为它最终可能会比您来的就地算法更少的副本(每像素 2 个)跟上。

【讨论】:

  • 这也不仅仅是将x,y 的元素与a,b 其他位置的元素交换。其实我不知道转置矩阵有那么复杂
【解决方案2】:

应该更容易调试的大部分等效代码:

inline uint32_t * addr(uint8_t* buffer, const uint32_t width, uint32_t i, uint32_t j) {
    uint32_t * tmp = buffer;
    return tmp+i*width+j;
}

void transpose(uint8_t* buffer, const uint32_t width, const uint32_t height) {
    for (uint32_t i = 0; i < min(width,height); i++) {
        for (uint32_t j = 0; j < i; j++) {
            uint32_t * a = addr(buffer, width, i, j);
            uint32_t * b = addr(buffer, width, j, i);
            const uint32_t tmp = *a;
            *a = *b;
            *b = tmp;
        }
    }
}

如果这不起作用,它可能不仅需要知道图片的宽度,还需要知道底层缓冲区的宽度。这只会翻转左上角的正方形部分,非正方形位图需要做更多的工作。 (或者在使用之前把所有东西都填平......)

【讨论】:

  • 似乎来自 OP 试图同时更改位图尺寸的 cmets,所以这不起作用
  • 算法不会尝试改变尺寸。这是一个一维像素阵列。转置图像是:1)水平翻转,2)逆时针旋转 90。因此,由于输入是一维像素数组,调用函数知道在转置之后,由于旋转,宽度和高度都必须被视为交换。
  • 如果你得到结果,考虑使用高度 * 宽度寻址的垂直线,什么坐标对应于使用翻转宽度 * 高度寻址的源中的那些内存地址?你得到一条有角度的线,而不是一条垂直线。这将使得很难确定应该用什么交换什么。
【解决方案3】:

请注意,当N!=M 时,在适当位置转置矩阵并非易事。有关详细信息,请参见例如here

原因是当N=M 时,您可以简单地遍历一半矩阵并交换元素。当N!=M 不是这种情况。

为了说明,考虑一个更简单的情况:

首先是一维数据的二维视图:

struct my2dview {
    std::vector<int>& data;
    int width,height;
    my2dview(std::vector<int>& data,int width,int height):data(data),width(width),height(height){}
    int operator()(int x,int y) const { return data[x*width + y]; }
    int& operator()(int x,int y){ return data[x*width + y]; }
    my2dview get_transposed() { return my2dview(data,height,width);}
};


std::ostream& operator<<(std::ostream& out, const my2dview& x){
    for (int h=0;h<x.height;++h){
        for (int w=0;w<x.width;++w){
            out << x(h,w) << " ";
        }
        out << "\n";
    }
    return out;
}

现在转置适用于N=M

my2dview broken_transpose(my2dview x){
    auto res = x.get_transposed();
    for (int i=0;i<x.height;++i){
        for (int j=0;j<x.width;++j){
            res(j,i) = x(i,j);
        }
    }
    return res;
}

将它用于一些小矩阵

int main() {
    std::vector<int> x{1,2,3,4,5,6};
    auto v = my2dview(x,2,3);
    std::cout << v << '\n';
    std::cout << v.get_transposed() << '\n';
    auto v2 = broken_transpose(v);
    std::cout << v2;
}

打印

1 2
3 4
5 6

1 2 3
4 5 6

1 3 2
2 2 6

结论:朴素的交换元素方法不适用于非方阵。

实际上,这个答案只是改写了@Alan Birtles 的答案。我觉得被他挑战了

由于任务的复杂性,创建临时缓冲区实际上最终可能会更快 [...]

只是为了得出同样的结论;)。

【讨论】:

    猜你喜欢
    • 2012-02-29
    • 1970-01-01
    • 2011-02-10
    • 2015-11-14
    • 1970-01-01
    • 1970-01-01
    • 2012-03-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多