Here's a little sniplet from TBPaint that I thought I'd share. It is used to grayscale a bitmap. The bitmap must be selected into a DC, in this case the hMemDC. For an example of what it looks like check out ToolBar paint in the IDE forum. It requires two parameters, the width and height of the image:
GrayScale proc uses edi esi imageX:DWORD,imageY:DWORD

LOCAL red :DWORD
LOCAL green :DWORD
LOCAL blue :DWORD

mov edi,imageX
dec edi
mov esi,imageY
dec esi
.REPEAT
.REPEAT
invoke GetPixel, hMemDC, edi, esi

movzx ecx,al
mov red,ecx
shr eax,8
movzx ecx,al
mov green,ecx
shr eax,8
movzx ecx,al
mov blue,ecx

mov eax,66
imul red
mov ecx,eax

mov eax,129
imul green
add ecx,eax

mov eax,25
imul blue
add ecx,eax

add ecx,128
shr ecx,8
add ecx,16

xor eax,eax

mov al,cl
shl eax,8
mov al,cl
shl eax,8
mov al,cl

invoke SetPixelV ,hMemDC, edi, esi, eax
dec esi
.UNTIL SIGN?
mov esi,imageY
dec esi
dec edi
.UNTIL SIGN?

ret
GrayScale endp
Posted on 2003-08-28 08:27:10 by donkey
	movzx ecx,al	; red

imul ecx,ecx,66

movzx edx,ah ; green
imul edx,edx,129
add edx,ecx

shr eax,16 ; blue
imul eax,eax,25
add eax,edx

add eax,128
shr eax,8
add eax,16

imul eax, eax, 010101h
Posted on 2003-08-28 08:51:39 by bitRAKE
Bitrake,

What about using mmx to cut down the imul to one multiplication? Will that be faster?
Posted on 2003-08-28 08:54:22 by roticv
Thanks bitRAKE,

I had originally used imul with three operands but I was having trouble and rolled it out, I never rolled it back up since it worked, and if it aint broke.... Never hurts to make things faster.

I have completely avoided MMX in order to make my paint program as compatible as possible. To date I have only one API that cannot be used from NT4/Win95 up, and I am in the process of writing a replacement for it (GradientFill). I may use MMX in the end though, there are some functions that would truly benefit from it.
Posted on 2003-08-28 09:00:43 by donkey
Something like


.data
mask dw 0, 25, 129, 66
.code
pxor mm0,mm0
movd mm1, eax
punpcklbw mm0,mm1
pmaddwd mm0, mask
movq mm1, mm0
pshrlq mm1, 32
paddd mm0,mm1
movd eax, mm0
add eax, 4224 ; 4224 = 128 + 16 shl 8
shr eax, 8
imul eax, 10101h

*Untested
Posted on 2003-08-28 09:39:54 by roticv
I have a question: I always used this formula for grayscale conversion (wich I don't remember where I found):

Gray = .59 * Green + .11 * Blue + .3 * Red

But the one you use seems to be different. My question (well, questionS :grin: were how can this be, why do they work just the same, and how can this factors be deduced 8that is, what does the formula mean exactly).

It seems to me that .59, .11 and .3 represent how much luminosity has each RGB component, so .59 > .3 because green is brighter than red (duh!) :grin: . But how were this particular values chosen? Are they really arbitrarily chosen values?
Posted on 2003-09-04 14:42:10 by QvasiModo
Gray scale is a perception thing, there is no right or wrong formula just an estimation of how the human eye will perceive it. Both formulae try to calculate an approximation of luma, the relative brightness. If yours looks good then it's just as good as the one I used.
Posted on 2003-09-04 14:48:53 by donkey
So they are arbitrary numbers, and if I choose them differently the converted image will look a little brighter in some areas, and darker in others, compared to a different formula, right?
Posted on 2003-09-04 14:53:43 by QvasiModo
Yes, that's right, the numbers just take the value of red green and blue and each one represents a particular percentage of the luma value (in your case 59%,11% and 30%). That value is then plugged into the RGB components as any color with equal RG&B values will appear gray. It is an estimate as luma is qualitative in reality, though in PC's they tend to misrepresent it as a quantitative value. When I started on my grayscale algo I also thought that there must be some definite formula to calculate luma but apparently there are quite a few, each one slightly different and the results all appear pretty much the same to me.
Posted on 2003-09-04 15:34:34 by donkey
Great, thanks donkey! :alright:
I may be making a quick tool to test this stuff "by hand"... something like three sliders for each luma component, to see how it changes with different values. I don't know if it will be really useful for anything, but what the heck :)
Posted on 2003-09-05 14:31:52 by QvasiModo
Cool,

I was thinking of adding something like that to TBPaint as well. I was going to let the user adjust the relative intensities of the gray scale and save it to preferences. I may still do a YUV thing but it seems a bit much for a toolbar button painter.
Posted on 2003-09-05 15:16:13 by donkey
:grin: GetPixel/SetPixel is ugly



ANTIQUE equ 0
YCrCb equ 0 ;Y=0.257R+0.504G+0.098B+16 --> Y=(66R+129G+25B+128)/256+16
YUV equ 1 ;Y=0.299R+0.587G+0.114B --> Y=(77R+150G+29B+128)/256

GrayScale proc uses esi edi hBMP:DWORD

LOCAL hMem :DWORD
LOCAL nSize :DWORD
LOCAL BMPInfo :BITMAP

invoke GetObject, hBMP, sizeof BMPInfo, ADDR BMPInfo

mov ax, BMPInfo.bmBitsPixel
.if ax < 24 || ax > 32
xor eax, eax
inc eax
ret ;not a 24bit or 32bit bitmap
.endif

mov eax, BMPInfo.bmWidthBytes
mul BMPInfo.bmHeight
mov nSize, eax

invoke GlobalAlloc, GMEM_FIXED, eax
.if eax == 0
mov eax, 2
ret ;not enough memory
.endif
mov hMem, eax

invoke GetBitmapBits, hBMP, nSize, hMem

mov esi, hMem
mov edi, esi
add edi, nSize

.if BMPInfo.bmBitsPixel == 32
.while esi < edi
movzx ecx, byte ptr[esi+2] ; red
imul ecx, ecx, 77

movzx edx, byte ptr[esi+1] ; green
imul edx, edx, 150
add ecx, edx

movzx eax, byte ptr[esi] ; blue
imul eax, eax, 29

lea eax, [eax+ecx+128]
shr eax, 8

mov [esi+2], al
mov [esi+1], al
mov [esi], al

add esi, 4
.endw

.elseif BMPInfo.bmBitsPixel == 24
.while esi < edi

if YCrCb
movzx ecx,byte ptr[esi+2] ; red
imul ecx,ecx,66

movzx edx,byte ptr[esi+1] ; green
imul edx,edx,129
add ecx,edx

movzx eax,byte ptr[esi] ; blue
imul eax,eax,25

lea eax,[eax+ecx+128]
shr eax,8
add eax,16

mov [esi+2],al
mov [esi+1],al
mov [esi],al

elseif YUV and ANTIQUE
movzx edx,byte ptr[esi+2] ; red
lea eax,[edx+edx*8]
lea eax,[edx+eax*2]
lea eax,[edx+eax*4]

movzx edx,byte ptr[esi+1] ; green
lea edx,[edx+edx*4]
lea edx,[edx+edx*4]
lea edx,[edx+edx*2]
lea eax,[eax+edx*2]

movzx edx,byte ptr[esi] ; blue
lea ecx,[edx+edx*2]
lea ecx,[edx+ecx*2]
lea ecx,[edx+ecx*4+128]
add eax,ecx

shr eax,8

mov [esi+2],al
mov [esi+1],al
mov [esi],al

else
movzx ecx,byte ptr[esi+2] ; red
imul ecx,ecx,77

movzx edx,byte ptr[esi+1] ; green
imul edx,edx,150
add ecx,edx

movzx eax,byte ptr[esi] ; blue
imul eax,eax,29

lea eax,[eax+ecx+128]
shr eax,8

mov [esi+2],al
mov [esi+1],al
mov [esi],al

endif

add esi,3
.endw

.endif

invoke SetBitmapBits, hBMP, nSize, hMem

invoke GlobalFree, hMem

xor eax, eax
ret

GrayScale endp
Posted on 2004-06-18 23:40:07 by WWW
Nice,

When I am done the new TBPaint I will post the MMX rotune that it will use, for now everything is in the works and spread all over the place but it is being completely rewritten for MMX minimum in GoAsm syntax. However the plan for now is to publish the full source code this time, though that may change depending on how I feel when I am done. Though the old TBPaint uses SetPixelD which is a direct to DIB SetPixel, the pointer to the bits is stored at creation and all internal bitmaps are 32 bit so it looks like this, GetPixelD looks essentially the same. Ofcourse the routine was written for a maximum of 48x48 bitmap (the largest supported button) so in reality it made little difference anyway, I only rewrote the function to allow for the possibility of Alpha bitmaps which I never implemented.

SetPixelD proc PixelX:DWORD,PixelY:DWORD,cPixel:DWORD

LOCAL pTheBit :DWORD
; Find the appropriate entry in the array of bits
; Calculate the offset to the first entry in scan line
mov eax, imageX
mov ecx,imageY

; Verify the coordinates to make sure they are valid
.IF PixelX < 0 || PixelX >= eax
ret
.endif

.IF PixelY < 0 || PixelY >= ecx
ret
.endif

sub ecx,PixelY
dec ecx
mul ecx
shl eax,2 ; adjust for DWORD size
push eax ; push the result onto the stack

mov eax,PixelX
shl eax,2 ; adjust for DWORD size
pop ecx ; pop the scan line offset off the stack
add eax,ecx

add eax,pDIBits ; add the offset to the DIB bits
mov ecx,cPixel
; RGB order must already be swapped for SetPixelD
mov DWORD PTR [eax],ecx

ret

SetPixelD endp
Posted on 2004-06-19 00:18:34 by donkey
Gray = .59 * Green + .11 * Blue + .3 * Red


if that formula is rather loose then we could make a very similar formula, that would mostly be not visibly that different
by shift lefting each color channel so much then adding back together..

divide by
4 = 1.5625%
8 = 3.125%
16 = 6.25%
32 = 12.5%
64 = 25%
128 = 50%

so

56.25% (shl 7 added with shl 4) or (59.375) or shl 7 added with shl 4 added with added with shl 3)
12.5% (shl 5)
3.125% (shl 3)

it would be a good enough approximation in my books if the alogirthm has to be fast, maybe servicing a graphcs routine at 60 frames per second or such..
i think i'm going to go make a fastgreyscale plugin for vjo with it.
Posted on 2004-06-19 01:30:23 by klumsy