关于将位图旋转90°

时间:2017-02-19 15:59:54

标签: c++ bit-manipulation

我最近找到that SO question

Rhubbarb接受的答案和答案很好,但我不明白它们是如何工作的。而且我不想在我的项目中使用我不理解的代码。我知道基本位操作是什么(移位,AND,OR等),但我不明白这些操作组合最终如何做他们正在做的事情。

感谢您查看此问题并希望能够帮助我。

1 个答案:

答案 0 :(得分:4)

64位整数value表示为8乘8的块 - 让我们假装我们理解每个单元格的“内容”如下:

 1  2  3  4  5  6  7  8
 9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32
33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56
57 58 59 60 61 62 63 64

虽然value实际上按顺序存储为

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 ...

我们还说将它向左移动四(value << 4)会导致

  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 ...

  5  6  7  8  9 10 11 12 
 13 14 15 16 17 18 19 20 ...

并将其向右移动四(value >> 4)给出

  0  0  0  0  1  2  3  4  
  5  6  7  8  9 10 11 12 ...

现在让我们来看看

uint64 reflect_vert (uint64 value)
{
    value = ((value & 0xFFFFFFFF00000000ull) >> 32) | ((value & 0x00000000FFFFFFFFull) << 32);
    value = ((value & 0xFFFF0000FFFF0000ull) >> 16) | ((value & 0x0000FFFF0000FFFFull) << 16);
    value = ((value & 0xFF00FF00FF00FF00ull) >>  8) | ((value & 0x00FF00FF00FF00FFull) <<  8);
    return value;
}

这里,类似0xFFFFFFFF00000000ull的片段是位掩码,它与AND操作一起从value中选择位。 另请注意,0xFF对应于设置了8位的一个字节,因此0xFFFFFFFF有效地描述了4*8=32个选定位。由于每行的长度为8位,因此对应4行。

具体地说,value & 0xFFFFFFFF00000000ull选择(保持!)value的高32位,即前四行,并丢弃其余的行,而value & 0x00000000FFFFFFFFull选择低32位并丢弃首先。 (它实际上并不丢弃任何东西,而是将那些不匹配的元素/位置的值设置为零。)

中的轮班操作
((value & 0xFFFFFFFF00000000ull) >> 32)
((value & 0x00000000FFFFFFFFull) << 32)

然后将这些位向下移动(>> 32)到低32位或更高位置(<< 32)。通过将它们组合在一起,

value = ((value & 0xFFFFFFFF00000000ull) >> 32) | ((value & 0x00000000FFFFFFFFull) << 32);

你已经有效地交换了它们。现在由于低32位对应于块的“下半部分”,我们只是像这样交换行:

33 34 35 36 37 38 39 40 \
41 42 43 44 45 46 47 48 |__
49 50 51 52 53 54 55 56 |  |
57 58 59 60 61 62 63 64 /  |
 1  2  3  4  5  6  7  8 \  |
 9 10 11 12 13 14 15 16 |__|
17 18 19 20 21 22 23 24 |
25 26 27 28 29 30 31 32 /

使用0xFFFF0000FFFF0000ull0x0000FFFF0000FFFFull执行相同操作,使用宽度为16的移位与其邻居交换两行:

49 50 51 52 53 54 55 56 \__
57 58 59 60 61 62 63 64 /  |
33 34 35 36 37 38 39 40 \__|
41 42 43 44 45 46 47 48 /
17 18 19 20 21 22 23 24 \__
25 26 27 28 29 30 31 32 /  |
 1  2  3  4  5  6  7  8 \__|
 9 10 11 12 13 14 15 16 /

最后,0xFF00FF00FF00FF00ull0x00FF00FF00FF00FFull的班次为8,每隔一行交换一次,产生

57 58 59 60 61 62 63 64 _
49 50 51 52 53 54 55 56
41 42 43 44 45 46 47 48 _
33 34 35 36 37 38 39 40
25 26 27 28 29 30 31 32 _
17 18 19 20 21 22 23 24
 9 10 11 12 13 14 15 16 _
 1  2  3  4  5  6  7  8

此时块已成功垂直翻转。

reflect_diag方法使用相同的方法有选择地交换位。这里要注意的是0x0100000000000000选择八位(中间的左上角)位:

0000 0001 0000 0000
0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 0000 0000

0x0000000000000080选择八个最低点(底行,中间右侧)

0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 1000 0000

位。它们相距49位,因此将它们移动49会交换它们的位置。

另一个例子,模式0x4020100804020100选择位

0100 0000 0010 0000 
0001 0000 0000 1000
0000 0100 0000 0010
0000 0001 0000 0000

而其对应0x0080402010080402选择

0000 0000 1000 0000
0100 0000 0010 0000
0001 0000 0000 1000
0000 0100 0000 0010

你会注意到这些位之间的距离形成了一个模式,它允许整个块移动,使它们彼此对齐原始位置。

另请注意,与水平和垂直翻转版本相比,此代码不会覆盖原始值,而是组成新输出。 Michiel的代码执行就地转换并对八进制的变换进行编码,因此>> 010实际上意味着>> 802016,依此类推。