B/W Filter

Blitz3D Forums/Blitz3D Programming/B/W Filter

napole0n(Posted 2004) [#1]
I think I already posted this yesterday, but it seems it got lost. If it was removed because it's in the wrong folder or something, please let me know where I should put it instead. Anyway.

I want to change my display to black and white at will (for flashbacks, dreams, etc.), so I wrote the following function called 'BWFilter'. It's called between 'renderworld' and 'flip'. It scans all pixels on the screen and replaces the colour with it's grayscale counterpart:

Function BWFilter()
    LockBuffer(BackBuffer())
    For x = 1 To GraphicsWidth()
        For y = 1 To GraphicsHeight()
	    GetColor(x,y)
	    v = (ColorRed() + ColorGreen() + ColorBlue())/3
	    WritePixelFast x,y,(v Or (v Shl 8) Or (v Shl 16) Or ($ff000000)),BackBuffer()
	Next
    Next
    UnlockBuffer(BackBuffer())
End Function


The function itself works fine, but it's very slow. Even on fast computers I don't get much more out of it than 3-4 frames per second. Any ideas how I can speed it up or are there other ways to achieve the same effect? I was thinking of using this filter on the textures instead of the rendered image, but then I'll probably have to reload them or copy them or something ... All help will be very much appreciated :)

This function is very useful for turning the screen to black and white if you don't have to update it every frame, so feel free to use it if you like to.


SoggyP(Posted 2004) [#2]
Hi Folks,

I'm not sure but I think you could put a semi-transparent entity between the camera and the rest of the world to act as a filter. Ok, it won't do black and white but it should do coloured monochrome.

Perhaps I'm a talking pair of underpants.

Later,

Jes


Odds On(Posted 2004) [#3]
You could try using the gamma commands to do it.

Also worth mentioning that pixels go from 0 to ScreenSize - 1.

Function BWFilter()
    LockBuffer(BackBuffer())
    For x = 0 To GraphicsWidth() - 1
        For y = 0 To GraphicsHeight() - 1
	    GetColor(x,y)
	    v = (ColorRed() + ColorGreen() + ColorBlue())/3
	    WritePixelFast x,y,(v Or (v Shl 8) Or (v Shl 16) Or ($ff000000)),BackBuffer()
	Next
    Next
    UnlockBuffer(BackBuffer())
End Function


Also, depending on how detailed it needs to be, you could just calculate every nth pixel and fill in the inbetween pixels with linear interpolation.


napole0n(Posted 2004) [#4]
@SoggyP: I think using the gamma would be a better option.

@Chris: For monochrome that would work, but also black and white?


Rob(Posted 2004) [#5]
I think the best way is simply to EntityColor all your objects.


napole0n(Posted 2004) [#6]
I've got pretty good results with gamma adjustments. While it's not black/white, I can get a nice old-feeling sepia look which might be even better. Thanks for your help! I've got a nice function to tweak the gamma values, I will make a stand-alone function of it and post it in the codebase for everybody's use.


Difference(Posted 2004) [#7]
A better common grayscale function is
v = ColorRed()*0.3 + ColorGreen()*.059 + ColorBlue()*0.11

but, it will still be very slow.

You could make a set of grayscale textures for all your objects and apply them at runtime. As Rob said, entitycolor might be a good way to go too.


Genexi2(Posted 2004) [#8]
Napolean, you'd be better off writing a function to prerender the images themselves B&W for those sequences, then reload the images afterwards to have them regain their color...


Rob(Posted 2004) [#9]
I think possibly, gamma is best. However how can we keep the HUD in colour? I am even willing to draw in seperate passes but it doesn't work like that...


napole0n(Posted 2004) [#10]
No, it seems that gamma is done on the whole image. Switching the gamma back after renderworld but before drawing things on top just makes the screen flicker.

I think the best thing is to make a subset of textures that are in grayscale, by using the BWFilter on the image instead of the viewport. And then change them back.

It should be possible though. Director and Photoshop have very fast blending modes which can easily achieve this effect. Any idea how they do it?

-Edit-
Maybe it's possible to adjust gamma, grab the screen, write it to a buffer, change back the gamma, write the screenbuffer to the foreground and draw your HUD and then flip buffers? Then you won't even need a second RenderWorld

-2nd Edit-
Nope, the solution above doesn't work. It really affects the way your card displays the image and has nothing to do with pixels apparantly. So grabbing an image will result in the uncorrected version.

-3rd Edit-
The best thing is probably to keep an array of texturehandles you use in the current scene. Then you can quite easily do something like this:

dim save_textures(num_of_textures)

Function Go_BW
For i=1 to num_of_textures
    save_textures(i) = CopyImage (textures(i))
    BWFilter(textures(i))
next
End Function

Function Go_RGB
for i=1 to num_of_textures
    textures(i) = CopyImage (save_textures(i))
next
End Function

Function BWFilter (whichTexture)
 ... do the grayscale operation on the textures
End Function


Do you have to reassign a texture if it's changed within the same handle, or does it update on screen automatically?