Note: This may be the coolest thing I’ve ever done.
So, over the last couple of weeks I’ve been a bit obsesed with the idea of CRT filters. The concept involves making the graphics look like they’re being displayed on a CRT T.V. by adding in color distortion, scan lines and bloom.
It all started with wanting to get a small CRT T.V. to play the SNES on, I saw one leaving Sean’s and couldn’t stop thinking about the idea of how old games look better on old T.V.s. From there it evolved into the fact that, eventually, there will be no functional CRT T.V.s anywhere, which made me a little sad, I really think some games look better when being displayed via CRT. Then, the idea of simply having an algorithm to reproduce CRT output came up, I was curious the algorithm needed to do something like that. The new Street Fighter III: Third Strike on PSN/Xbox has this option and I was curious if it was accurate or not (did they just put black lines every other pixel to represent scan lines or did color from the surrounding pixels bleed into the scan line like it should be).
And to manage a pixel by pixel analysis of whats on the screen in HTML5 / Canvas:
Then I added in a character class, animation and game loop. I also created a horrible rainbow colored character to move around the screen so you can see performance, and went with a Super Metroid background image to easily see the graphical change.
I don’t claim this is the best, most efficient way to do a CRT filter (I’m sure it’s not). The image is 256 x 224 (SNES resolution) multiplied by 2 (512 x 448). Be sure to note the horrible impact on performance (each update it loops through each pixel and provides a transformation). Should be 10 FPS but with the filter that doesn’t work out.
WARNING: This will only work in modern browsers (seems best on Chrome) and may crash your browser:
Click “CRT Filter Off/On” to turn the filter off and on.
(ScaleTimes.*linspace(0, 1, 255).^(1/2.2)) + ScaleAdd
Which is basically a value * array which…I’m not sure if every element in the array is multiplied or what.
linspace() is also an interesting function I’ve never seen before, that should have converted properly (linearly spaced array values from x to y for z elements).
InterpolatedImage[x, y + 1].red = ScanRange[max(i.red) + 1] * i.red // assign dark scanline values based on brightness of pixel above
InterpolatedImage[x, y + 1].green = ScanRange[max(i.red) + 1] * i.green
InterpolatedImage[x, y + 1].blue = ScanRange[max(i.red) + 1] * i.blue
May have not converted properly. Not sure what the max is doing (it’s a single value, how can max() do anything for one value, also why does it use red’s value as the transformation each time).
I tried to improve performance by removing the 4 loops and just having one loop. Then, every other line it either does a normal transformation or a scan line transformation.
The ghosting is being generated because of the vertical scan lines but…I’m not really sure why. The vertical scan lines effect vertical pixels not horizontal ones.
In general I’m not really sure how they came to the conclusion to use those transformations, but it works. I mostly just ported it to HTML5.
I’ve put the source up on Github here: https://github.com/mikeradvak/html-5-canvas-crt-filter and you can see a working example here: http://mikeradvak.github.io/html-5-canvas-crt-filter/.
Leave a Reply