Skalowanie obrazu 0,5x

Autor: Wojciech Muła
Dodany:2002
Aktualizacja:30.04.2008

Treść

Najprostszy sposób to pominąć co drugi pixel (i co drugą linię):

|a|b|c|d|e|f|... -> |a|c|e|...

Można również przeprowadzić skalowanie z filtrowaniem bilinearnym:

|a|b|c|d|e|f|... -> |(a+b)/2|(c+d)/2|(e+f)/2|...

Proste skalowanie

Obraz grayscale:

mov eax, [edi] ; eax = |d|c|b|a| -- pobierz 4 pixele
mov  ah, al    ; eax = |d|c|a|a|
shr eax, 8     ; eax = |0|d|c|a|

mov [esi],  ax ; -- zapisz 2 pixele

Powyższy kod ma jedną dość istotną wadę: przeskalowanie 8 pixeli to 2 zapisy do pamięci — po prostu musi zostać dwukrotnie wykonany; kolejny program omija tę wadę.

mov eax, [edi]   ; eax = |d|c|b|a|
mov ebx, [edi+4] ; ebx = |h|g|f|e|

mov  ah, al      ; eax = |d|c|a|a|
mov  bh, bl      ; ebx = |h|g|e|e|

shl eax, 8       ; eax = |c|a|a|0|
shr ebx, 8       ; ebx = |0|h|g|e|

shrd eax, ebx, 8 ; eax = |g|e|c|a|

mov [esi], eax

Użycie MMX bardzo ułatwia sprawę.

pcmpeqb  mm2, mm2 ; generowanie maski 0x00ff00ff00ff00ff
psrlw    mm2, 8   ;

mov      ecx, img_width/8

_scalehalf:
        movq mm0, [edi]   ; mm0 = |h|g|f|e|d|c|b|a|
        movq mm1, [edi+8] ; mm1 = |p|o|n|m|l|k|j|i|

        pand mm0, mm2     ; mm0 = |-|g|-|e|-|c|-|a|
        pand mm1, mm2     ; mm1 = |-|o|-|m|-|k|-|i|

        packuswb mm0, mm1 ; mm0 = |o|m|k|i|h|e|c|a|

        movq [esi], mm0

        add  edi, 8
        add  esi, 8

        loop _scalehalf

Skalowanie z filtrowaniem bilinearnym

Proszę zapoznać się z Średnia arytmetyczna.

kod x86

mov eax, [edi] ; eax = |d|c|b|a|

add  al, ah    ;
rcr  al, 1     ; eax = |   d   |   c   |   b   |(a+b)/2|

bswap eax      ; eax = |(a+b)/2|   b   |   c   |   d   |

add  al, ah    ;
rcr  al, 1     ; eax = |(a+b)/2|   b   |   c   |(d+c)/2|

rcl eax, 8     ; eax = |   b   |   c   |(d+c)/2|(a+b)/2| ***

mov [esi], ax

Uwaga taka sama jak poprzednio: warto przeskalować 8 pixeli, by zapisać równocześnie 4.

mov eax, [edi]
mov eax, [edi+4]

; średnia w ax

; średnia w bx -- w miejscu oznaczonym (***) zamiast
;                 rozkazu rcl, należy użyć RCR.

and eax, 0x0000ffff ;
and ebx, 0xffff000  ; zamaskowanie słów zawierających średnie

or  eax, ebx        ; połączenie wyników

mov [esi], eax

kod MMX

Tu również MMX ułatwi działanie.

; mm4 = 0x00ff00ff00ff00ff

                    ; [edi] = |i|h|g|f|e|d|c|b|a|

movq  mm0, [edi]    ; mm0 = |h|g|f|e|d|c|b|a|
movq  mm1, [edi+1]  ; mm1 = |i|h|g|f|e|d|c|b|

movq  mm2, [edi+8]  ; skalowane będzie 16 pixeli równocześnie
movq  mm3, [edi+9]  ;

pand  mm0, mm4      ; mm0 = |-|g|-|e|-|c|-|a|
pand  mm1, mm4      ; mm1 = |-|h|-|f|-|d|-|b|
pand  mm2, mm4
pand  mm3, mm4

paddw mm0, mm1      ; mm0 = |g+h |e+f |c+d |a+b |
psrlw mm0, 1        ; mm0 = | gh | ef | cd | ab | -- 'ab' oznacza '(a+b)/2'
paddw mm2, mm2
psrlw mm2, 1        ; mm2 = | op | mn | kl | ij |

packuswb mm0, mm2   ; mm0 = |op|mn|kl|ij|gh|ef|cd|ab|

movq [esi], mm0

kod MMX2/SSE

Z filtrowanie bilinearnym:

movdqa    (%eax), %xmm0         ; xmm0 = |a b c d e f g h|...|
movdqa  16(%eax), %xmm2         ; xmm2 = |i j k l m n o p|...|

movdqa     %xmm0, %xmm1
movdqa     %xmm2, %xmm3

pand        MASK, %xmm0         ; xmm0 = |a _ c _ e _ g _|...|      _ = 0
pand        MASK, %xmm2         ; xmm2 = |i _ k _ m _ o _|...|
                                ; MASK = packed_word(0x00ff)

psrlw         $8, %xmm1         ; xmm1 = |b _ d _ f _ h _|...|
psrlw         $8, %xmm3         ; xmm3 = |j _ l _ n _ p _|...|

packuswb   %xmm1, %xmm0         ; xmm0 = |b d f h j l n p|...|
packuswb   %xmm3, %xmm2         ; xmm2 = |a c e g i k m o|...|

pavgb      %xmm2, %xmm0         ; xmm0 = |(a+b+1)/2 (d+c+1)/2 (f+e+1)/2 (h+g+1)/2 ... |