Hi
I'm looking for a way to process an image, something like this:

- convert the image to grayscale
- apply a color to the image, so all colors in the picture will be the same, at a different intensity (something like looking at things through a coloured glass :grin: )

I know how to convert to grayscale:

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

I just need the second part. I've been trying to figure it out by myself, but didn't succeed. Couldn't find it in google, either.

This is some of the code I have used (uses raw bitmap access). The full source is in the thread "Treeview with background image".


invoke GdiFlush
push esi
push edi
push ebx
mov esi,pbits
mov edi,esi
mov eax,bmi.bmiHeader.biHeight
test eax,eax
.if sign?
neg eax
.endif
mul bmi.bmiHeader.biWidth
shl eax,1
shl eax,1
add edi,eax
mov edx,color_window ;edx = 007F7F7Fh - COLOR_WINDOW
neg dl
add dl,07Fh
neg dh
add dh,07Fh
ror edx,16
neg dl
add dl,07Fh
rol edx,16
.repeat
mov ebx,[esi] ;Get current pixel color
mov al,bl ;Blue
mov ah,30
mul ah
and eax,0FFFFh
mov ecx,eax
mov al,bh ;Green
mov ah,11
mul ah
and eax,0FFFFh
add ecx,eax
shr ebx,16 ;Red
mov al,bl
mov ah,59
mul ah
and eax,0FFFFh
add eax,ecx
mov cl,100 ;Grayscale = .59 * Green + .11 * Blue + .3 * Red
div cl
rcl ah,1
adc al,0
mov ah,al
shl eax,8
mov al,ah
sub al,dl ;Combine with color_window
sub ah,dh ; If you comment out this
ror edx,16 ; part, you will get a
ror eax,16 ; grayscale background,
sub al,dl ; instead of a combination
rol edx,16 ; with the default system
rol eax,16 ; colors.
mov [esi],eax ;Set new pixel color
add esi,4
.until esi >= edi
pop ebx
pop edi
pop esi


This is how it's supposed to work (BUT DOESN'T):
Posted on 2003-07-16 09:32:57 by QvasiModo
By the way, the color for the filter shouldn't be red, green or blue. In those cases it's simpler, just calculate the grayscale value and set it in one of the three RGB components, instead of all of them:

00xxxxxxh grayscale
00xx0000h red
0000xx00h green
000000xxh blue

I'm looking for a way to do this with ANY color...
Posted on 2003-07-16 09:37:30 by QvasiModo
IIRC, Qwerdy was working on this, too.

I don't know how good this solution is, but here goes:

Convert from HSL to RGB - the H and S values are chosen for the whole image based on the color you want, and the L values for each pixel are the cooresponding gray scale values from the picture.

I'm sure there is a better way, but I haven't researched it.
Posted on 2003-07-16 11:37:06 by bitRAKE
1. Convert the image to greyscale
2. Divide each greyscale value by 255 to get a floating-point number between 0 and 1
3. Multiply each RGB component of the "filter" color by the floating-point number
4. Round the result to an integer and put back in the correct format

That ought to give you the effect you want

If you don't want to use floating-point numbers, you can multiply the greyscale and RGB component
directly (in a dword register so it doesn't overflow), and then divide by 255.
Posted on 2003-07-16 12:22:02 by evwr
It depends on how you map the colors to their corresponding greyscale index.

For example, if
XXXXXXXXXXXXXXXX

maps to:
XXXXXXXXXXXXXXXX

Then it's more complicated than taking n/255 * the color you want, since here the gradient goes from black -> green -> white. I think the best method would be to create a 256x3 table and map the colors you want in, then transforming the colors will be as simple as looking up the table index.
Posted on 2003-07-16 13:08:22 by iblis
Thank you for your replies, I was totally lost on this one :)

iblis: You're right, I want the colors to map like you said, but I don't know how to create such color table (I want this done on runtime, based on the system colors). It's still probably more efficient to create that table on process startup, and update it if the system colors change by processing WM_SYSCOLORCHANGE or WM_DISPLAYCHANGE.

evwr: It's still a good idea, I'll try that first. If it looks good, it might be enough fo what I want. By the way, wich way is more efficient, using FPU instructions or integer operations? I suppose integers are faster... :confused:

bitRake: If evwr's idea fails, I'll try the one you suggested... as soon as I find out how to convert RGB to HSL and visceversa :grin:
Posted on 2003-07-16 16:21:15 by QvasiModo
Actually, it seems that you don't need to convert the image to greyscale first. You can get better-looking results
if you just multiply each component of the source image by the corresponding component of the "filter" color and
divide by 255. This ought to solve the problem that iblis was talking about.

This method may not produce images with all of the same color (although it will be close), it instead acts more like
a real colored filter would, only letting a certain percentage of each color component pass through unblocked.

Integer instructions are going to be much faster in this case, because the conversion between integer
and floating-point is quite expensive compared to an integer divide and multiply. If we were doing a lot
more calculations, or if we didn't need to convert back to an integer, the floating-point might be a more
attractive option.
Posted on 2003-07-16 23:08:27 by evwr
OK, I've implemented evwr's algo (probably needs lots of optimization... but I've seen it run, and i'ts fast enough anyway). It looks good when the mixing color is very bright, but when the color is darker the image looks too dark. I have yet to try without converting to grayscale.



mov eax,pbits
.if eax != 0
invoke GdiFlush
push esi
push edi
push ebx
mov esi,pbits
mov edi,esi
mov eax,bmi.bmiHeader.biHeight
test eax,eax
.if sign?
neg eax
.endif
mul bmi.bmiHeader.biWidth
shl eax,1
shl eax,1
add edi,eax
mov edx,color_window ;edx = COLOR_WINDOW
align DWORD
.repeat
mov ebx,[esi] ;Get current pixel color
mov al,bl ;Blue
mov ah,30
mul ah
and eax,0FFFFh
mov ecx,eax
mov al,bh ;Green
mov ah,11
mul ah
and eax,0FFFFh
add ecx,eax
shr ebx,16 ;Red
mov al,bl
mov ah,59
mul ah
and eax,0FFFFh
add eax,ecx
mov cl,100 ;Grayscale = .59 * Green + .11 * Blue + .3 * Red
div cl
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; This converts the picture to
; grayscale, without combining
; with the system colors
; (you have to comment out the
; next one)
; mov ah,al
; shl eax,8
; mov al,ah
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; This converts the picture to
; grayscale, and combines it
; with the system colors
; (you have to comment out the
; previous one)
and eax,0FFh
push ebx
push ecx
push edx
push edi
push esi
mov edi,eax ;edi gray
mov esi,edx ;esi color
xor ebx,ebx ;ebx result
xor ecx,ecx
mov cl,dl ;blue 0000xx
mul ecx
.if eax != 0
mov cl,255
div ecx
.endif
rcl dl,1
adc eax,0
mov bl,al
mov ecx,esi ;green 00xx00
mov eax,edi
shr ecx,8
and ecx,0FFh
mul ecx
.if eax != 0
mov cl,255
div ecx
.endif
rcl dl,1
adc eax,0
mov bh,al
mov ecx,esi ;red xx0000
mov eax,edi
shr ecx,16
mul ecx
.if eax != 0
mov cl,255
div ecx
.endif
rcl dl,1
adc eax,0
and eax,0FFh
shl eax,16
or eax,ebx
mov edx,esi
pop esi
pop edi
pop edx
pop ecx
pop ebx
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
mov [esi],eax ;Set new pixel color
add esi,4
.until esi >= edi
pop ebx
pop edi
pop esi
.endif


I think BitRake's idea was better, but since I'm after an aesthetical effect, I would make a change:



mov eax,L1 ;luminosity of source pixel
add eax,L2 ;luminosity of mixing color
shr eax,1


I think this way the luminosity of the mixing color would not be ignored, and so colors white, black, or anything in the gray scale wouldn't map the same color table. If used with a color conversion table for speed optimization, it should be the best choice.
Posted on 2003-07-17 12:17:59 by QvasiModo

If evwr's idea fails, I'll try the one you suggested... as soon as I find out how to convert RGB to HSL and visceversa :grin:


http://www.asmcommunity.net/board/attachment.php?s=&postid=88192
:alright:
Posted on 2003-07-17 18:16:12 by iblis
Yes, I did something similar for my PngSkin program. It was very simple snippet because most of the work was done by Iblis' conversion code :) Of course it wasn't exactly optimal but if it's only startup code that shouldn't matter much.
Posted on 2003-07-18 06:49:13 by Qweerdy
iblis, thanks for the link! :alright:
I've been gloogling for it, but somehow I forgot about searching in the board... (dumb)

evwr, here's a better implementation of your algo, this time it doesn't convert to grayscale, and works just great. I still have to see if there's a way to fix the problem when the mixing color is black (you can't see the picture).


mov eax,pbits
.if eax != 0
invoke GdiFlush
push esi
push edi
push ebx
mov esi,pbits
mov edi,esi
mov eax,bmi.bmiHeader.biHeight
test eax,eax
.if sign?
neg eax
.endif
mul bmi.bmiHeader.biWidth
shl eax,1
shl eax,1
add edi,eax
mov edx,color_window ;edx = COLOR_WINDOW
align DWORD
.repeat
mov ebx,[esi] ;Get current pixel color
mov ecx,3
xor eax,eax
align DWORD
.repeat
mov al,bl
mul dl
.if eax != 0
mov bl,255
div bl
.endif
mov bl,al
ror ebx,8
ror edx,8
dec ecx
.until zero?
ror ebx,8
ror edx,8
mov [esi],ebx ;Set new pixel color
add esi,4
.until esi >= edi
pop ebx
pop edi
pop esi
.endif
Posted on 2003-07-18 09:34:11 by QvasiModo
QvasiModo,
      mov   esi,pbits

xor eax,eax
movzx edx,byte ptr [color_window] ; edx=COLOR_WINDOW my be with BSWAP?
add esi,1 ; edx=7F7F7F00h ->COLOR_WINDOW
F_Loop1:
movzx ebx,[esi+eax]
add eax,1
imul ebx,edx
movzx edx,byte ptr [color_window+eax] ; edx=COLOR_WINDOW
shr ebx,8 ; /256
cmp eax,4
mov [esi+eax-1],bl
jne F_Loop1
movzx edx, byte ptr [color_window] ; edx=COLOR_WINDOW
add esi,eax
xor eax,eax
cmp esi,edi
jc F_Loop1

Regards,
Lingo
Posted on 2003-07-18 14:21:44 by lingo12
@lingo12

I'm afraid this code won't work:

mov ecx,ebx
shr ebx,8 ; /256
add ebx,ecx ; ebx/255

Here's an example:

; ebx = 512
mov ecx, ebx ; ebx = 512, ecx = 512
shr ebx, 8 ; ebx = 2, ecx = 512
add ebx, ecx ; ebx = 514, ecx = 512

ebx ends up being greater than its initial value, not its
initial value / 255. Since we're not looking for scientific results,
I'd say that just a shr ebx, 8 would be fine. The maximum possible
error, (255 * 255) / 255 compared to (255 * 255) / 256,
differs by a little less than one.

@QvasiModo:

Any algorithm that gives reasonable-looking results is going to have
an all-black image when the mixing color is black, since a black mixing
color represents a filter that lets no light through. You should be able
to fudge something though, like:
1. add up the r, g, and b components of the mixing color
2. If the sum is > a certain arbitrary # chosen by trial and error, then
go to the regular mixing routine, otherwise continue
3. Add an arbitrary value # to each of the r, g, and b components of the
mixing color, so we keep the same hue, but increase the brightness
4. Use this new mixing color in the regular routine
Posted on 2003-07-18 15:05:07 by evwr
Thanks evwr,
It was:


shr ebx,8 ; /256
mov ecx,ebx
shr ecx,8 ; /256
add ebx,ecx ; ebx/255

I'll correct it

Regards,
Lingo
Posted on 2003-07-18 17:37:55 by lingo12
QvasiModo, for best results only use the hue of the filter color - providing the best range of color for the image. Black will give a grayscale image in this case.
Posted on 2003-07-18 17:52:43 by bitRAKE
Well, this would be my implemenation of bitRake's first suggestion:
mov eax,pbits

.if eax != 0
invoke GdiFlush
push esi
push edi
push ebx
mov esi,pbits
mov edi,esi
mov eax,bmi.bmiHeader.biHeight
test eax,eax
.if sign?
neg eax
.endif
mul bmi.bmiHeader.biWidth
shl eax,1
shl eax,1
add edi,eax
invoke RGB2HSL,color_window
and eax,0FFFFh
xchg eax,ebx
.repeat
invoke RGB2HSL,dword ptr [esi]
and eax,0FF0000h
or eax,ebx
mov dword ptr [esi],$invoke (HSL2RGB,eax)
add esi,4
.until esi >= edi
pop ebx
pop edi
pop esi
.endif

And this would be my version:
mov eax,pbits

.if eax != 0
invoke GdiFlush
push esi
push edi ;preserve the registers
push ebx
mov esi,pbits
mov edi,esi
mov eax,bmi.bmiHeader.biHeight
test eax,eax
.if sign?
neg eax
.endif
mul bmi.bmiHeader.biWidth
shl eax,1
shl eax,1
add edi,eax
push $invoke (RGB2HSL,color_window)
.repeat
invoke RGB2HSL,dword ptr [esi]
pop edx
push edx
mov ecx,edx
shr eax,16
shr edx,16
add eax,edx
shl eax,15
and ecx,0FFFFh
and eax,0FF0000h
or eax,ecx
mov dword ptr [esi],$invoke (HSL2RGB,eax)
add esi,4
.until esi >= edi
pop eax
pop ebx
pop edi ;restore the registers
pop esi
.endif

The second one looks better... when the mixing color is black, it doesn't map everything to black. The only drawback is, perhaps, that it looks a bit too bright when the mixing color is white (nothing is perfect). And of course it's grayscale. It might be better for applying a texture, not a background image (something like the sample image in my first post). evwr's algo still has the advantage of preserving the image colors.
Originally posted by bitRAKE>
QvasiModo, for best results only use the hue of the filter color - providing the best range of color for the image. Black will give a grayscale image in this case.

I'll try that...*
Posted on 2003-07-18 18:15:26 by QvasiModo
H Hue (color)
S Saturation (richness of color)
L Luminesence (amount of light)

They don't have to be floats - they can be mapped to any range - like RGB values.
Posted on 2003-07-18 18:22:19 by bitRAKE
Luminance (or however it's spelled) was pretty much intuitive :rolleyes: If I have two HSL pairs with the same hue and saturation, but a different luminance, one will look brighter then the other. Right.

Hue and Saturation were confusing me, though... suppose you have two HSL pairs, both with the same saturation and luminance, but a different hue. What does it mean? That they are a different color, with the same brightness and intensity?:confused:
Posted on 2003-07-18 18:51:14 by QvasiModo
QvasiModo, yeah, the colors should look good next to each other on the screen. :)
Posted on 2003-07-18 19:48:55 by bitRAKE
Hue is the color's placement on the 'color wheel' so to speak. It goes from red->orange->yellow->green->cyan->blue->magenta-> and back to red.

Saturation is how far away the color is from grey. It goes from grey -> x.

Luminance is the intensity. It goes from black -> x -> white.


Remember that the RGB2HSL and HSL2RGB routines use the COLORREF format, so to convert them from/to a pixel format, bswap, then shift right by 8.
Posted on 2003-07-19 10:03:44 by iblis