IconProcs

by Lummox JR
A library for manipulating icons and colors
ID:51608
 

A lot of BYOND developers have had trouble figuring out the alpha transparency in BYOND 4.0's icons. Really I can't blame them--alpha is not as intuitive to work with as anyone tends to think, since it isn't a regular color like red or green or blue.

Here's an example. Suppose you want to make an icon partially transparent. A lot of folks do this:

icon -= rgb(0, 0, 0, 128)

What they don't realize is that they're not subtracting 128 from the alpha channel--they're just cutting the existing alpha in half, because when you add or subtract two icons--or an icon and a color--their transparency is combined. So when they try to add rgb(0,0,0,128) back to the icon, it gets even more transparent.

The easy way to avoid this kind of headache is to bypass it completely. That's what this library is for. As new ideas come along, it will probably grow bigger and gain more abilities.

The IconProcs library is broken up into two sections. One of them is for manipulating icons, the other for colors.

Icon Manipulation

The example I used above is much easier to do with IconProcs. Create an /icon datum and use ChangeOpacity() like so:

var/icon/my_icon = new('pizza.dmi')
// ghost pizza
my_icon.ChangeOpacity(0.5)

That will multiply all alpha values by 0.5, giving you 50% opacity. This is reversible, so you can multiply alpha by 2 again to get the regular icon back.

// ghost pizza materializes
my_icon.ChangeOpacity(2)

And if you don't want any transparency at all, you can cut out the middleman and call Opaque(). You call this with an argument that says what background color you want--the default is black.

// pizza, with blue background
my_icon.Opaque(rgb(0,0,255))

Another thing people might want to do with alpha channels is apply alpha masks to their icons. For instance, if you had an icon that was a solid block of color and wanted to change it to a ball, you could apply an alpha mask in the shape of a ball. This is done in Photoshop all the time. You can take the alpha values of a mask icon and apply them to the icon you already have with UseAlphaMask().

var/icon/my_icon = new('pizza.dmi')
my_icon.UseAlphaMask('brick.dmi')

+

Ah, brick-shaped pizza. Any transparent parts of the original icon will become black if the new alpha mask covers them, so you might just see some black corners in that brick that used to be outside the pizza's circle. Or you can combine alpha masks instead of replacing them.

var/icon/my_icon = new('pizza.dmi')
my_icon.AddAlphaMask('brick.dmi')

+

There, now you have brick pizza with the corners cut off.

But what if you want to make an alpha mask out of a simple grayscale icon? Say I've airbrushed a white smudge on a black background and I want to make that the alpha channel for another icon. Then I'd use BecomeAlphaMask()

var/icon/my_icon = new('pizza.dmi')
var/icon/mask = new('smudge.dmi')
mask.BecomeAlphaMask()
my_icon.UseAlphaMask(mask)

+

Voila. A smudge pizza!

One of the reasons the MapColors() proc was created was to allow you to do special effects like changing an icon into black-and-white grayscale. If you don't remember the right way to go about that, don't worry--it's easy with the GrayScale() proc.

var/icon/my_icon = new('pizza.dmi')
my_icon.GrayScale()

Now that's an interesting effect, but we can do even better. Have you seen how some old movies use sepia tones, where instead of black and white they have a kind of brownish tint? You can do that here with ColorTone(). For the sake of example let's say we want a yellow tone so the pizza is in shades of gold.

var/icon/my_icon = new('pizza.dmi')
my_icon.ColorTone(rgb(255,255,0))

And now you have golden pizza. If you're anything like me, you're already thinking of how you could use this in a game. You could use it to have enemies of different colors for instance, so a blue slime isn't quite as strong as a red slime, which is outclassed further by a green slime.

A while back I also thought it would be neat to have more modes for Blend() to use, like how right now it has ICON_ADD, ICON_OR, ICON_OVERLAY, etc. There isn't really any equivalent of min() and max(), but there is now.

var/icon/my_icon = new('pizza.dmi')
my_icon.MinColors('smudge.dmi')

+

The MinColors() proc combines two icons--or an icon and a color--and like regular adding/subtracting it makes the result more transparent. Since the smudge icon is solid, what you see here is sort of a pizza-shaped black disc with a smudge of pizza on it. The reason you see a smudge of pizza is that those colors are all less than white. But closer to the edges of the smudge where there are shades of gray, the gray starts to show through more. Let's try MaxColors next.

var/icon/my_icon = new('pizza.dmi')
my_icon.MaxColors('smudge.dmi')

+

This proc takes two icons and finds the maximum color, not the minimum. And it combines their opacity just like a blend with ICON_OR does; this icon is totally opaque since the smudge was opaque. In the center of the smudge is just white, but toward the edges it starts to look more like pizza.

Colors and HSV

IconProcs has a nice set of procs for working with RGB and HSV colors. HSV colors are given by Hue, Saturation (colorfulness), and value (brightness).

You're no doubt already familiar with the rgb() proc. It takes three values (four if you include alpha) and creates a string in "#rrggbb" or "#rrggbbaa" format. IconProcs provides a proc called ReadRGB() for doing the opposite:

var/list/components = ReadRGB(rgb_value)
usr << "Red: [components[1]]"
usr << "Green: [components[2]]"
usr << "Blue: [components[3]]"
if(components.len > 3)
usr << "Alpha: [components[4]]"

For working with HSV colors, we have a similar pair of procs. hsv() converts hue, saturation, value, and alpha (optional) into "#hhhssvv" or "#hhhssvvaa" format. And ReadHSV() reads that value and converts it into a list, just like ReadRGB() does for RGB colors.

// hue = 0 (red)
// saturation = 85 (low saturation, closer to grayscale)
// value = 255 (full brightness)
var/hsv_color = hsv(0, 85, 255)
usr << "HSV for pink is [hsv_color]"
// #00055ff is the color

Obviously HSV colors are of no use to you on their own, so you need to convert between HSV and RGB. For this there are RGBtoHSV() and HSVtoRGB() procs.

usr << "HSV for pink is [RGBtoHSV(rgb(255, 170, 170))]"

If you include alpha in your color, the conversion routines will keep it intact.

Many people who use image editors like Photoshop are used to thinking of the hue as an angle. To make life easier for them, there are conversion procs. HueToAngle() takes a hue used by the library and converts it to an angle from 0 to 360, and AngleToHue() does the opposite. You can also modify an existing HSV color with RotateHue().

// Rotate 60°: Red -> yellow
var/result = RotateHue(hsv(0, 255, 255), 60)
// result is "#100ffff"

One of the common uses for HSV colors is blending. IconProcs lets you blend between colors in RGB or HSV mode. If you want a nice blend between red and green, you probably want it to stick with bright colors in between. If you blend with RGB, the red component will get darker while the green gets lighter, and at halfway point you have a disappointing dull gold: rgb(128,128,0). But if you use HSV blending, only the hue changes while the saturation and value can stay the same, so you go from bright red to bright yellow to bright green.

The BlendRGB() proc takes two RGB colors and an amount to blend by, and gives you a blended color somewhere in between. The amount goes from 0 (the first color) to 1 (the second color), so 0.5 is exactly halfway between.

var/msg = "This is a rainbow of text!"
var/len = length(msg)
var/color1 = rgb(255, 0, 0)
var/color2 = rgb(0, 255, 0)
for(var/i=1, i<=len, ++i)
var/c = BlendRGB(color1, color2, (i-1)/(len-1))
usr << "<font color=[c]>[copytext(msg, i, i+1)]</font>\..."
usr << "" // finish the text

That gives you a blend from red to green through RGB colors. As you can see, the middle of the range is gold, not yellow. BlendHSV() will solve that problem.

var/msg = "This is a rainbow of text!"
var/len = length(msg)
var/color1 = hsv(0, 255, 255)
var/color2 = hsv(0x200, 255, 255)
for(var/i=1, i<=len, ++i)
var/c = BlendHSV(color1, color2, (i-1)/(len-1))
usr << "<font color=[HSVtoRGB(c)]>[copytext(msg, i, i+1)]</font>\..."
usr << "" // finish the text

That looks much better. By the way, you can skip a step using BlendRGBasHSV(), which lets you use RGB colors but uses HSV for the blending.

Lastly, remember the GrayScale() and ColorTone() procs from /icon? There are global versions of both procs too. If you call GrayScale(rgb_value) you'll get the gray equivalent of your color, and ColorTone(rgb_value, rgb(255,255,0)) will convert your color to a yellow tone instead of a simple grayscale.

More to Come?

Since the goal of this library is to simplify everyday stuff people do with their icons, I'm sure new cases will pop up where something turns out to be pretty hard to do. In the future this library will be updated to cover those cases that seem to frustrate the everyday developer.

Very cool. Didn't realize you could do all that with BYOND.
Very very nice, I could see alot of cool effects with that that you would have to draw out but could easily do with code, thanks!
omg! what you've done to the pizza!
I'd love to see a fast game that includes these effects in them. They look awesome!
Too bad icon procs usually hit noticeably hard on CPU. I'll play around with them, still :)
Yay! =D
Ruben7 wrote:
omg! what you've done to the pizza!

I do not want to know what that white stuff is over the pizza :P
The transparent would do good with night time effect.
:D Cool! Favuloso xD
Cool