Icon Processor

by Forum_account
An easy way to generate and process icons.
ID:520814
 
This library handles the overhead for processing icons. If you want to run a custom operation of every pixel of every icon state in an icon, there are lots of loops and other things you need to write. This library handles all of that.

The library works by providing the IconProcessor datum which contains procs for manipulating icons. It has procs that are automatically called for every icon state and for each pixel - all you have to do is override these procs to define the operation you want to perform. The IconProcessor object also has procs to perform useful operations (interpolation, sampling, etc.).

To change the opacity of an entire icon, here's all you have to do:

Fade
parent_type = /IconProcessor

var
opacity = 0

New(o)
opacity = o

// to apply the fade effect we process each state
// pixel by pixel.
state()
per_pixel()

// output the icon, state name, direction, and frame number
show_progress()

pixel()

// get the color at the current x/y
var/Color/c = get(x, y)

// if the pixel isn't already more than transparent
// than desired, set its alpha value.
if(c.a > opacity)
c.a = opacity

return c

And to run it:

// create an instance of the fade processor that makes the
// icons 20% opaque
var/Fade/fade = new(0.2)

// process the sample icon
var/icon/result = fade.process('icon-processor-demo-icons.dmi')

// save the result as "fade.dmi"
src << ftp(result, "fade.dmi")
The library can also be used to generate icons from scratch. You just create an icon processor object that generates what you need and pass it a blank icon. The icon you're processing needs to have the correct states - the processor doesn't generate states it only modifies existing ones. So, to create a blank icon to be processed you can use the template object:

// create a template with states named 0 - 15
for(var/i = 0 to 15)
template.add_state(i)

// create the outline generator
var/Outline/outline = new()

// process the template
var/icon/result = outline.process(template)

// save the result as "outline.dmi"
src << ftp(result, "outline.dmi")

The template object's add_state() proc can also take a number of directions (1, 4, or 8) and the number of frames of animation. This way you can generate directional icons or animated icons if you'd like.
I got the update ready faster than I expected.

I posted an update which adds a Perlin noise function and a demo that uses it. A noise function can be used to add randomness to images. But, unlike the rand() proc, the randomness is somewhat controlled. Each value isn't completely random, there's some pattern to how it's generated, which lets you use it in meaningful ways.

Perlin noise is often used to add random, natural-looking variations to patterns. The demo takes an icon that contains a white circle and uses Perlin noise to offset each pixel to create a new icon. Here's the difference:

Before:


After:
You could also generate a heightmap using Perlin noise and probably use getpixel to generate a random world. I think minecraft does this...
That white circle after the Perlin Noise almost looks like a dust cloud or something. I guess this function could be used to have each explosion be different. Can't think of other examples off the top of my head.
In response to Albro1
Kaiochao wrote:
You could also generate a heightmap using Perlin noise and probably use getpixel to generate a random world. I think minecraft does this...

A Miner Adventure uses Perlin noise for generating random maps for certain game modes. You don't have to use the PerlinNoise object with an icon processor, so the output doesn't have to be an icon.

Edit: It's not from A Miner Adventure, but here's an image of random maps generated using Perlin noise.

Albro1 wrote:
That white circle after the Perlin Noise almost looks like a dust cloud or something. I guess this function could be used to have each explosion be different. Can't think of other examples off the top of my head.

You can use it to add natural-looking variations to any icon. For example, you could draw tree bark as vertical lines of different shades of brown and apply this same technique to distort the lines in a natural-looking way.

Edit: You can also use it for placing objects, not just for modifying icons. For example, if you have a section of the map that's dirt you could use Perlin noise to determine where clumps of grass and bushes are placed to give them a nicer-looking grouping than just purely random placement (ex: each turf has a 10% chance to have a clump of grass).
Are there speed benchmarks for this too, or is it too noticeably slow to even bother?
In response to Kaiochao
Kaiochao wrote:
Are there speed benchmarks for this too, or is it too noticeably slow to even bother?

For what? I'm not too concerned with performance here. This is the kind of thing you'll use to generate an icon, save it as a .dmi file, and use the saved icon in another project.
Here's another example of how this effect can be used. You can take two very simple drawings:



Those images look plain and boring, but when you distort them they become more interesting:

In response to Forum_account
In my various top-down projects, I sometimes generate generally 360 icon states for every rotating object at runtime, storing them in an icon with 360 icon_states. Sometimes, I save them into .dmi files.

Either way, clients that join a server will lag the server due to the huge resource files (dynamic resource file too) created by it. I guess now I'm complaining about BYOND not having that icon_rotate variable.
It would be nice if we could use vars to control an atom's size, opacity, color, and rotation. GPUs are made to render these kinds of effects. Maybe when there are more projects that show the need for these effects the staff will add it.
Forum_account wrote:
To change the opacity of an entire icon, here's all you have to do:

Isn't using Blend() and/or rgb() far more efficient, and generally simpler?
In response to Falacy
Falacy wrote:
Forum_account wrote:
To change the opacity of an entire icon, here's all you have to do:

Isn't using Blend() and/or rgb() far more efficient, and generally simpler?

For certain operations, yes, but you don't have control over the exact operation being applied. Blend() can only perform a limited number of operations and doesn't help you create anything more complex than what it offers.

I wouldn't use any of these effects at runtime. It's for the kind of effects you'd generate and use the resulting .dmi file instead.
When it comes to runtime icon effects, I can only hope for the software to get better at this. If you generate icons at runtime, players lag the server by having to download the .dynamic resource. If you generate them and save them as .dmi, the .rsc file gets huge. It just shouldn't be like that.
Ideally the client would be capable of performing more operations so you could do something like this:

// make an atom fade in
for(var/i = 1 to 10)
some_atom.opacity = i / 10
sleep(world.tick_lag)

And the effect would be rendered by the client. Currently, you'd have to make the animation in a .dmi file and use flick(). Changing the icon is a hassle because it's an animation. Being able to generate the animation helps - you just need to change the input icon and run the icon processor, but it's still not ideal.

Being able to render these effects on the client most likely depends on this feature request. But, having this ability would be excellent because you can evetually (hopefully) execute limited operations on the client. Not only does the change in opacity get rendered by the client, the code that causes the change runs there too (something like this feature request).

With these features it'd be super easy to add nice looking graphical effects that don't bog down the game in any way. They don't bloat the .rsc, they don't cause extra icons to be downloaded at runtime, they don't cause lots of network traffic (ex: by sending opacity updates every tick), and they don't suffer from latency. But, until there's a serious demand for these features, I don't expect the staff will really consider making any of these changes.
Most icon operations should already be handled by the client http://www.byond.com/forum/?post=92922
In response to Falacy
Falacy wrote:
Most icon operations should already be handled by the client http://www.byond.com/forum/?post=92922

It's not clear what operations are handled by the client and it's still far from ideal. Most effects don't need to be performed as icon operations the way BYOND handles them now (whether it's on the client or server). Unless you're going to save the modified icon to a .dmi file, you don't need to figure out how each operation modifies each pixel. It could just use DirectX to apply changes in color, opacity, angle, size, etc.

When you're playing a game with 3D graphics and the screen becomes tinted blue, the game doesn't generate blue versions of every texture. The GPU just applies the color to each polygon as it's being drawn - that's what the GPU is for. BYOND still isn't making good use of the GPU (which, I think, is partially because they're using sprites instead of quads).
In response to Forum_account
Forum_account wrote:
When you're playing a game with 3D graphics and the screen becomes tinted blue, the game doesn't generate blue versions of every texture.

Wouldn't you just handle that the same way you would in BYOND, by adding a transparent blue screen object? At least for tinting the screen red, as a type of damage effect, this seems to be the most common method.
I guess that wasn't the best example. It doesn't have to be a full screen tint, it could just be applied to a single object.

To the GPU it's probably less costly to make this an additional operation applied to every pixel than to handle this as a single translucent blue polygon placed over the whole screen. To process that, you'd have to render all geometry then process the translucent overlay. You'd have to process everything twice. GPUs are fast because they do a lot of work in parallel. If this is just an extra operation applied to each pixel, you still get the full benefit of parallelization.
In response to Forum_account
Forum_account wrote:
You'd have to process everything twice.

You'd have to tell difference between that particular object and other part of the scene to process it second time, therefore this method isn't used at all (I hope). There's simply color value sent to shader that gets added to every pixel when the object is drawn for the first time:
float4 PixelShader(/* ... */) {
float4 FinalColor(0, 0, 0, 0);
// other calculations such as lighting
return FinalColor + FadeColor; // not an actual code
}

That results only in one extra operation per pixel.

To the GPU it's probably less costly to make this an additional operation applied to every pixel than to handle this as a single translucent blue polygon placed over the whole screen
Partially true. Drawing blue transparent polygon would require more operations per pixel, but difference is extremely low, calling Draw() proc alone costs more than those operations, therefore this way would be fine.
Page: 1 2