News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Using AVX instructions to change pixel color in a bitmap file

Started by Mark44, March 10, 2014, 02:31:34 PM

Previous topic - Next topic

Mark44

A while back I ran into an internet article titled "Image Processing with SSE" - http://supercomputingblog.com/windows/image-processing-with-sse/. This seemed to be an interesting application of the vector processing instructions, so I thought I would play around with it. I've attached my work, which includes my source code (BitmapFiddler.c and FindBluePix.asm), the release build of my code, and a sample bitmap image (Sample.bmp).

The image is pretty tiny, consisting of three rows of pixels, with 8 pixels in each row. You can view this file in a browser, but you'll need to zoom in to see the pixels. Note that the code assumes that the image is in a particular place, so you will probably need to modify the code in main slightly so that it can find the image file.

About all I used of the article I cited above is the basic algorithm. My version uses AVX instructions that work on the 256-bit YMM registers, instead of the SSE3 instructions of the article, that work on the 128-bit XMM registers. Also, the sample code in the article uses intrinsics, while my code uses the plain AVX instructions, which I prefer.

Most of the code in the application is standard C code, but there's a call to an assembly routine that does the heavy lifting of modifying the bits in the blue channel for each pixel.

.data
; Each element in the BlueMask array is a bitmask that zeroes out
;  the blue channel of a pixel.
;  byte 0 (LSB) - alpha channel
;  byte 1 - blue channel
;  byte 2 - green channel
;  byte 3 (MSB) - red channel
BlueMask dd 0FFFF00FFh, 0FFFF00FFh, 0FFFF00FFh, 0FFFF00FFh,
            0FFFF00FFh, 0FFFF00FFh, 0FFFF00FFh, 0FFFF00FFh
ByteCount dq ?

.code
; ChangeBluePixels reads eight pixels at a time (32 bytes), and searches for blue pixels
; in an array of BI_BITFIELDS (32 bits/pixel) data.
; If blue pixels are found, the bits in the blue channel are changed to 0's.
; Parameters:
;   Address of the array in RCX.
;   Number of pixels in RDX.
; Returns: nothing.
; To do: Add functionality to deal with arrays that aren't a multiple of 32 bytes.

ChangeBluePixels PROC C
mov ByteCount, rdx
sub rsi, rsi
vmovups ymm0, ymmword ptr [BlueMask]
Loop1:
vmovups ymm1, ymmword ptr[rcx + rsi]  ; Copy 32 bytes (8 pixels) to YMM1
vandps ymm2, ymm1, ymm0   ; And YMM1 with the array of bitmasks, and store result in YMM2.
vmovups ymmword ptr[rcx + rsi], ymm2  ; Copy the modified pixel data back to the original array.
add rsi, 32   ; Increment the array pointer by 32 bytes to get the next 8 pixels.
cmp rsi, ByteCount   ; Are we done yet?
jb Loop1   ; No, so loop again.
ret

ChangeBluePixels ENDP
END


This application is a proof of concept, and being a work in progress, could stand some improvements. The most significant is that the code currently assumes that the pixel data in a bitmap file is a multiple of 32 bytes. Inasmuch as my sample bitmap file has exactly 96 bytes of pixel data, no problem. The fix for this is to add some code that works on a final block of pixel data that is less than 32 bytes.

There is also no mechanism to modify colors other than blue. It wouldn't be hard to let the user choose which channel to wipe out, but I haven't done that, yet. 

I don't have any times yet. When I get a chance I'll write some C code and see how my routine compares to the code that the compiler generates.

In preparation for this project, I needed to learn a bunch of stuff about the format of a bitmap file. Much of what I learned came from this wiki article - http://en.wikipedia.org/wiki/BMP_file_format.

My app displays most of the bitmap metadata, so even if you don't want to actually modify the pixels, you can drill down and display this data. Here is from one run of the app:

Bitmap type: BM
File size: 234
DIB header size: 124
DIB header type: BITMAPV5HEADER
Bitmap width: 8
Bitmap height: 3
Bits per pixel: 32
Compression type: 3 - BI_BITFIELDS
Image size (bytes): 96
Red channel bit mask: ff000000
Green channel bit mask: ff0000
Blue channel bit mask: ff00
Alpha channel bit mask: ff



Gunther

Thank you for sharing your code, Mark. Interesting link to the blog.

Gunther
You have to know the facts before you can distort them.

dedndave


Gunther

You have to know the facts before you can distort them.

Mark44

In case anyone is interested, I updated the code I posted earlier so that it now works with somewhat more general bitmap files. In the earlier version, the number of pixels had to be a multiple of 8. The code now handles bitmap files with an arbitrary number of pixels. I have attached a zip file with the source code, the executable, and a couple of teensy bitmap files - one with 24 pixels, and one with 27 pixels.

My code seems to work with both files, but that has been the extent of my testing.

As before, the code requires the bitmap to be 32 bits/pixel in ARGB, and zeroes out the blue component in the pixels. This is a toy program that isn't/wasn't meant to be a "real" program, just a excuse for me to try out some AVX instructions and to learn a little about working with bitmap files.

Here's the assembly code. All the rest of the code is written in C.
; FindBluePix.asm - contains the ChangeBluePixels proc.

.data
; Each element in the BlueMask array is a bitmask that zeroes out
;  the blue channel of a pixel.
;  byte 0 (LSB) - alpha channel
;  byte 1 - blue channel
;  byte 2 - green channel
;  byte 3 (MSB) - red channel
BlueMask dd 0FFFF00FFh, 0FFFF00FFh, 0FFFF00FFh, 0FFFF00FFh,
            0FFFF00FFh, 0FFFF00FFh, 0FFFF00FFh, 0FFFF00FFh

BytesRemaining dq ? ; The number of bytes in the bitmap image that have not been processed. In this code, 1 pixel == 4 bytes.
BytesPerIter dq 32

.code


; ChangeBluePixels reads eight pixels at a time (32 bytes), and searches for blue pixels
; in an array of BI_BITFIELDS (32 bits/pixel) data.
; If blue pixels are found, the bits in the blue channel are changed to 0's.
; Parameters:
;     Address of the array in RCX.
;     Number of bytes in RDX.
; Returns: nothing.
; To do: Add functionality to deal with arrays that aren't a multiple of 32 bytes.

ChangeBluePixels PROC C
cmp rdx, 32
jb TailLoop
mov BytesRemaining, rdx
sub rsi, rsi
vmovups ymm0, ymmword ptr [BlueMask]

Loop1:
cmp BytesRemaining, 0   ; If equal, we're done.
je Finished
cmp BytesRemaining, 32   ; Exit loop if fewer than 32 bytes left.
jb TailLoop           ; Fewer than 32 bytes (8 pixels) remain.
vmovups ymm1, ymmword ptr[rcx + rsi]  ; Copy 32 bytes (8 pixels) to YMM1
vandps ymm2, ymm1, ymm0   ; And YMM1 with the array of bitmasks, and store result in YMM2.
vmovups ymmword ptr[rcx + rsi], ymm2  ; Copy the modified pixel data back to the original array.
add rsi, 32   ; Increment the array pointer by 32 bytes to get the next 8 pixels.
sub BytesRemaining, 32   ; Are we done yet?
ja Loop1   ; If BytesRemaining > 32, iterate again.
jb TailLoop        ; If BytesRemaining < 32, not enough for another iteration.
jz Finished   ; If BytesRemaining - 32 == 0, all pixels are processed, and we're done.

TailLoop:
mov eax, dword ptr[rcx + rsi]
and eax, dword ptr[BlueMask]
sub BytesRemaining, 4
ja TailLoop

Finished:
ret

ChangeBluePixels ENDP


END