TAOCP 7.1.3 Exercise 189

Let the bitmap be stored as $8$ consecutive rows of bytes per block column.

Section 7.1.3: Bitwise Tricks and Techniques

Exercise 189. [25] [25] Given a 64 × 64 bitmap, what’s a good way (a) to transpose it, or (b) to rotate it counterclockwise by 90◦, using operations on 64-bit numbers?

Verified: no
Solve time: 1m14s


Solution

Let the bitmap be stored as $8$ consecutive rows of bytes per block column. Since $n = 64n'$, each row consists of $8n'$ bytes, and each $8 \times 8$ subarray is contained in exactly $8$ consecutive rows and one group of $8$ consecutive bits inside a byte.

For each $8 \times 8$ block, let $k$ be the number of 1-bits in the block. The required gray level is

$g(k) = \left\lfloor 255\left(1 - \frac{k}{64}\right)^{1/\gamma} + \frac{1}{2} \right\rfloor,$

with $\gamma = 1.3$, so $g(k)$ depends only on $k \in {0,\dots,64}$.

A direct computation of $k$ is reduced to byte operations by splitting each $8 \times 8$ block into $8$ bytes $b_0,\dots,b_7$, where each byte represents one row of the block. Then

$k = \sum_{i=0}^{7} \mathrm{pop}(b_i),$

where $\mathrm{pop}(b)$ is the number of 1-bits in a byte.

Precompute two tables:

$\mathrm{POP}[b] = \mathrm{pop}(b), \quad 0 \le b < 256,$

and

$\mathrm{GRAY}[k] = \left\lfloor 255\left(1 - \frac{k}{64}\right)^{1/\gamma} + \frac{1}{2} \right\rfloor, \quad 0 \le k \le 64.$

Then each block is converted by table lookups only.

Let BITMAP be addressed as byte array $B$, and GRAYMAP as byte array $G$. Each row of GRAYMAP has $n/8 = 8n'$ entries.

MMIX routine:

        LOC     Data_Segment

POP     IS      @           % 256-byte table
GRAY    IS      @           % 65-byte table

BITMAP  GREG
GRAYMAP GREG

m       GREG
nblocks GREG

k       GREG
i       GREG
j       GREG
p       GREG

        LOC     #100

Convert SET     i,0

RowLp   CMP     i,m
        BZ      Done

        SET     j,0

ColLp   CMP     j,nblocks
        BZ      NextRow

        SET     k,0

        % address of 8x8 block in BITMAP
        SET     p,BITMAP
        MUL     t,i,8*nblocks
        ADD     p,p,t
        ADD     p,p,j

        % load 8 bytes and accumulate popcount
        LDB     t,p,0
        LDB     t2,p,1
        LDB     t3,p,2
        LDB     t4,p,3
        LDB     t5,p,4
        LDB     t6,p,5
        LDB     t7,p,6
        LDB     t8,p,7

        LDB     t,POP,t
        ADD     k,k,t
        LDB     t,POP,t2
        ADD     k,k,t
        LDB     t,POP,t3
        ADD     k,k,t
        LDB     t,POP,t4
        ADD     k,k,t
        LDB     t,POP,t5
        ADD     k,k,t
        LDB     t,POP,t6
        ADD     k,k,t
        LDB     t,POP,t7
        ADD     k,k,t
        LDB     t,POP,t8
        ADD     k,k,t

        LDB     t,GRAY,k

        % store result pixel
        SET     p,GRAYMAP
        MUL     t2,i,nblocks
        ADD     p,p,t2
        ADD     p,p,j
        STB     t,p,0

        INCL    j,1
        JMP     ColLp

NextRow INCL    i,1
        JMP     RowLp

Done    TRAP    0,Halt,0

Each output pixel is written exactly once because the loops partition the image into disjoint $8 \times 8$ blocks indexed by $(i,j)$. Each $k$ is computed solely from the corresponding block bytes, and each byte contributes exactly one lookup into $\mathrm{POP}$, so the total popcount is correct by definition of $\mathrm{POP}$. The gray level follows from a single lookup into $\mathrm{GRAY}$, which encodes the required function of $k$. ∎