Adventure Game Studio

AGS Support => Advanced Technical Forum => Topic started by: GarageGothic on Mon 28/04/2008 05:07:15

Title: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Mon 28/04/2008 05:07:15
Inspired by one of tolworthy's recent blog entries (http://lesmisgame.blogspot.com/2008/04/another-day-another-five-minute-change.html) I decided to script a lightmap module, where the tint values of the character are applied based on the colors in a sprite or an external bitmap file rather than regions. This allows for much smoother (and easier to set up in cases of many shade values) lighting effects. Also, lightmaps can be easily swapped or even animated if room conditions change - e.g. flipping a lightswitch.

Currently retrieving and applying RGB tint data works great. However, I'm having a few problems deciding how to interpret saturation and luminance values based on a lightmap and haven't been able to find any existing implementations for reference. I tried downloading SLUDGE, which alledgedly has the feature, but my virus scanner found a trojan in the .exe (it may have been a false positive, but the next time I restarted the computer I was logged out of windows instantly, even in safe mode - I ended up having to do a full reinstall).

My one solution for it would be to retrieve the brightness values from a secondary black and white bitmap file, but it seems to me that it should be possible to calculate the values from the colored image. Trouble is that whenever I used an average (or even weighted average) of the RGB values, it's only possible to get 100% brightness in pure white light. I want it to be possible for instance to have the character tinted bright red (which with an average calculation of (255,0,0) would give 33% brightness) yet be 100% bright. Whenever I try to implement workarounds, I still get the problem of the luminance dropping whenever two colors mix - so that a character walking through a gradient from pure red to pure green drops in brightness while he's passing through the middle section.

Any suggestions on how to implement saturation/luminance without resorting to separate lightmaps? I'd really like to see this module available to the public - I'm even working on an in-game lightmap painting tool.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: Pumaman on Mon 28/04/2008 11:03:33
Well, the standard AGS tinting routine converts the RGB pixel to HSV, then keeps the V from the pixel and then multiplies it as per the tint luminance setting.

The H and S come from either the pixel or the tint colour or somewhere in between, depending on the saturation.

So if you're trying to emulate an AGS-style tint, you can't do it purely with RGB arithmetic.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Mon 28/04/2008 11:23:46
Thanks for the reply!

I'm using the DrawingSurface.GetPixel function to retrieve the pixel at (character.x, character.y), then converting the color value to RGB and applying it as a Character.Tint. Would it help me in any way to convert the RGB values to HSV and then converting the S and V values to percentages (sat, lum) to use in the Character.Tint(r,g,b,sat,lum)? - I'm still unsure if it makes any sense to try to retrieve saturation from the lightmap, since I guess a less saturated color will already tint the character less noticably (so lowering the tint saturation would make the tint almost invisible).

I didn't quite understand, did you say that tinting already changes the shading of the sprite's pixels and not just their color value? If so, is there any way to counterbalance this through arithmetic when calculating the luminance value?
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: EnterTheStory (aka tolworthy) on Mon 28/04/2008 11:56:38
Quote from: GarageGothic on Mon 28/04/2008 05:07:15I'm having a few problems deciding how to interpret saturation and luminance values based on a lightmap and haven't been able to find any existing implementations for reference. I tried downloading SLUDGE

This is from the SLUDGE help file. If it helps:
QuoteWith a light-map loaded, every character's red, green and blue colour values will be multiplied by the red, green and blue values of the pixel in the imageFile at the corresponding location. Lightmap modes are: PERPIXEL: Each pixel of a character is multiplied by the light map colour in the same position - i.e. one character will have many different colours applied to it in different places. HOTSPOT: Each pixel of a character is multipled by the light map colour at the character's hotspot (base point) - i.e. each character will only have one colour applied to it, determined by where it is currently positioned. The light-map arithmetic is applied before setCharacterDrawMode is taken into consideration.

In Sludge, setCharacterDrawMode makes the character lighter, darker, transparent, etc., and is applied separately. On its own, a light map of pure white would have no effect, and a small amount of gray would create a noticeable shadow. Your idea of animated shadows sounds even better.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Mon 28/04/2008 12:27:45
Thanks tolworthy, that was very helpful. I've been looking for the SLUDGE manual online but couldn't find it anywhere but in that trojan infected installer.

Multiplying the color seems like it would create very dark sprites if it works anything like in Photoshop (I'm not even sure what the process AGS performs is called, colorizing?). As a former SLUDGE user, were you happy with the way the engine interpreted lightmaps (both in regards to color and lightness)? Do you have any preference as to how you'd like to see it implemented in AGS?

Edit: I converted the RGB values to HSV and tried using S and V for saturation and luminance. It was closer to what I'm trying to achieve, but the character still gets VERY dark while passing through blending areas. I have a feeling that it could work, possible using some multipliers, but this whole thing makes my head spin.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: EnterTheStory (aka tolworthy) on Mon 28/04/2008 14:41:28
Quote from: GarageGothic on Mon 28/04/2008 12:27:45Multiplying the color seems like it would create very dark sprites
Yes, I found that! It's very sensitive to small changes in color, so I mostly used white and shades of very pale gray. Gradients worked fine, there was no obvious banding.

Quote from: GarageGothic on Mon 28/04/2008 12:27:45were you happy with the way the engine interpreted lightmaps (both in regards to color and lightness)?

Yes, but I was never very demanding. Most of my scenes needed just regular shadows here and there, and it was quick and easy. I think I used some red shadows for an underground Hades scene or two, or some blue underwater reflections, but I wasn't very adventurous. The most I did was just making black tunnels for characters to walk into and disappear from sight. I seem to recall that fading to white was a little harder, but doable by combining lightmaps and individual tinting.

Quote from: GarageGothic on Mon 28/04/2008 12:27:45Do you have any preference as to how you'd like to see it implemented in AGS?

I'm just amazed that you're doing it! That's great! Ease of use for simple stuff is all that matters to me. Oh, and the ability to have very subtle shades - I have a lot of shades of white.

My next game will be Dante's inferno, so anything that allows flickering flames would be uber-cool. :)

The only thing that ever bugged me about the Sludge implementation (and it's a VERY minor point) is that the lightmap had to be the exact same size as the background. Sometimes, being lazy, I just wanted to create a small lightmap and slap it in a corner of a scene or even reuse it in different scenes. As with the projector example. But that's a very trivial issue.

Beyond that...

Another idea that comes to mind (apart from the obvious one of animation) is to allow the offsetting of light maps according to a characters' position. And even scaling! Imagine a room where an overhead projector is turned on (using lightmaps). A character could walk between the projector and the wall and have the image shown on their face, offset and scaled in a dramatic way. Heck, they could even cast a shadow! But I suspect that would be a lot of work for one gimmick. But imagine if a lightmap could be offset according to the alpha channel on a character. You could have freaky 3D lighting effects on their face as they moved past a window or projector.. just thinking aloud here. Very moody, very atmospheric, but I'd imagine a real pain to implement.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Mon 28/04/2008 14:53:37
I think I misunderstood what lightmaps are in SLUDGE. I thought it was like our region lighting/tint, just with as many "regions" as there are pixels. But when I read the extract from the manual now, it seems to be an overlay that shades the sprite pixel by pixel like a multiply layer in Photoshop would. My module is simpler in that it shades the whole sprite in the color of the pixel at character.x, character.y.

Quote from: tolworthy on Mon 28/04/2008 14:41:28Another idea that comes to mind (apart from the obvious one of animation) is to allow the offsetting of light maps according to a characters' position. And even scaling! Imagine a room where an overhead projector is turned on (using lightmaps). A character could walk between the projector and the wall and have the image shown on their face, offset and scaled in a dramatic way.

I actually already have this in a different module for my game (which also features projectors :)). I may consider merging the modules, but technically it's a different thing altogether from the tint lightmaps. These projection overlays also support animation and offset values.

QuoteHeck, they could even cast a shadow! But I suspect that would be a lot of work for one gimmick.

And another module still  ;D, though one currently so buggy that I probably won't release it publicly until it has shown its worth in my own game. (Example screenshot here (http://www.adventuregamestudio.co.uk/yabb/index.php?topic=8237.msg282828#msg282828))

I'll take all your other comments into consideration for the lightmap module. Thanks for your input! :)

Edit: Oh, as for the scale of the lightmap, it must have the correct screen dimensions but can be in any resolution (such as using 320x240 lightmaps in a 640x480 game).
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: Pumaman on Mon 28/04/2008 15:23:58
Quote from: GarageGothic on Mon 28/04/2008 11:23:46
I'm using the DrawingSurface.GetPixel function to retrieve the pixel at (character.x, character.y), then converting the color value to RGB and applying it as a Character.Tint. Would it help me in any way to convert the RGB values to HSV and then converting the S and V values to percentages (sat, lum) to use in the Character.Tint(are,g,b,sat,lum)? - I'm still unsure if it makes any sense to try to retrieve saturation from the lightmap, since I guess a less saturated color will already tint the character less noticably (so lowering the tint saturation would make the tint almost invisible).

I didn't quite understand, did you say that tinting already changes the shading of the sprite's pixels and not just their color value? If so, is there any way to counterbalance this through arithmetic when calculating the luminance value?

Getting the saturation and luminance from a light map pixel probably won't give you a decent result, because then you're effectively drawing three different components (colour, saturation and luminance) from the same source.

I would continue to take the RGB values from the light map, but you'd need to decide for yourself what you wanted to do for the saturation and luminance. Normally I'd leave luminance at 100, and also pass saturation as 100 if you want the sprites to be completely re-tinted to your light map colour. If you don't, then you need to decide how strongly you want the tint to be applied and use that for the saturation value.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Mon 28/04/2008 16:03:39
Quote from: Pumaman on Mon 28/04/2008 15:23:58Getting the saturation and luminance from a light map pixel probably won't give you a decent result, because then you're effectively drawing three different components (colour, saturation and luminance) from the same source.

Ah, thanks for clearing that up. I somehow suspected I would need separate maps for these values, but thought I might at least be able to retrieve brightness from the RGB map since AGS tinting seemed to be more about value than luminance - even very low RGB values can give light tint colors if balanced properly. I guess I will let this be up to the end users how it's implemented. I think the best solution would be to have a color map, a light map and a per-room saturation value, since you'd rarely need to change saturation at different spots in the room.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: Pumaman on Mon 28/04/2008 18:14:55
Actually, in a sense the saturation might be the thing you want to change the most.

Suppose you have a big blue light at one end of the room. The actual RGB tint could be fixed for the entire room (eg. 0, 0, 200) but then as you walked closer or further away from the light, the saturation would gradually vary to bathe the character in more/less blue light.

So in a sense it might be more useful to have a saturation map than a colour map, depending on what effect you're going for.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Mon 28/04/2008 18:43:07
Ah, good point indeed. I think my main issue with saturation maps is that while I have a pretty good idea of how to draw a color or light map, I wouldn't know where to start in drawing a realistic looking saturation map for a room. Currently I've implemented saturation based on the HSV S value, and it works pretty well with the saturation fading as RGB values near white. At the moment I'm wondering whether it shouldn't be the same at the other end of the spectrum, that saturation of colors lessens the less light there is.

I made a test room where the character is walking around a huge gradient like this:

(http://tbn0.google.com/images?q=tbn:GiRTkFWg-Ozl-M:http://ursimon.musichall.cz/files/progs/hls_color/hls_map2.jpg)

and I must say he adapts pretty well to the tonal range. Obviously there are values you cannot achieve with automatic luminance/saturation calculation, but it will take some experimenting to see what needs to change.

In addition, I think this module would benefit greatly if support of luminance values up to 200% was implemented for Character.Tint as it is for region lighting (to gradually "white out" the character). I've mentioned it before (http://www.adventuregamestudio.co.uk/yabb/index.php?topic=24852.0), but particularly now that I'm coding the module, I see all kinds of cool uses - for instance you could have a very smooth looking fog effect. 
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: Pumaman on Mon 28/04/2008 20:13:46
Would anyone else find support for luminance > 100% useful?
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: G on Thu 01/05/2008 10:13:15
Quote from: Pumaman on Mon 28/04/2008 20:13:46
Would anyone else find support for luminance > 100% useful?

I would find support for luminance AND saturation 100% useful. As I'm interested in changing thar saturation of the colors to get grey tones of characters and rooms without using aditional sprites.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Thu 01/05/2008 10:23:45
You mean for region tinting, right? Because if you set the R, G and B in Character.Tint to the same value, you WILL get grayscale characters already (at saturation 100, saturation works a bit differently in AGS than you're used to from e.g. Photoshop), and can use luminance at the same time. It's a pain that Regions don't support both luminance and color tint (one reason for my module). But it should be quite easy to write some code in repeatedly_execute_always which tints every character in the current room to their current region's luminance (only up to 100%) AND to grayscale.

  int index;
  while (index < Game.CharacterCount) {
    if (character[index].Room == player.Room) {
      Region *charregion = Region.GetAtRoomXY(character[index].x,  character[index].y);
      int lightlevel = charregion.LightLevel + 100;
      if (lightlevel > 100) lightlevel = 100; //because character.Tint doesn't support +100 values
      character[index].Tint(255, 255, 255, 100, lightlevel);
      }
    index++;
    }
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: G on Thu 01/05/2008 11:16:51
Quote from: GarageGothic on Thu 01/05/2008 10:23:45
You mean for region tinting, right? Because if you set the R, G and B in Character.Tint to the same value, you WILL get grayscale characters already (at saturation 100, saturation works a bit differently in AGS than you're used to from e.g. Photoshop). It's a pain that Regions don't support both luminance and color tint, but it should be quite easy to write some code in repeatedly_execute_always which tints every character in the current room to their current region's luminance (only up to 100%) AND to grayscale.

  int index;
  while (index < Game.CharacterCount) {
    if (character[index].Room == player.Room) {
      Region *charregion = Region.GetAtRoomXY(character[index].x,  character[index].y);
      int lightlevel = charregion.LightLevel + 100;
      if (lightlevel > 100) lightlevel = 100; //because character.Tint doesn't support +100 values
      character[index].Tint(255, 255, 255, 100, lightlevel);
      }
    index++;
    }


No, I'm afraid don't mean region tinting.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Thu 01/05/2008 11:18:38
What's the problem then? Character.Tint DOES support grayscale and luminance at the same time. Region tinting does not.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: G on Thu 01/05/2008 14:49:40
Let's say that I've tried several times to get a saturation transition, from a full-colored scene (not a character, object, the whole screen) to a greyscale look and resulted impossible.

If AGS works color with the HSV color system then there should be a simple way to control the saturation level of the sprites (from the original aspect to a greyscale look with all the 254 steps between, or maybe from 0% to 100% saturation), as far as I know about color theory. Maybe this could be added in the TintScreen function ar as another function (ScreenSaturation, or something else)
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: Pumaman on Thu 01/05/2008 20:24:34
In theory all you should need to do is keep calling Character.Tint with the same RGB values (eg. 200, 200, 200) but gradually increasing the saturation from 0 to 100.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Thu 01/05/2008 22:00:03
Pumaman is right about the method, but if you want to do it for the whole screen, of course it's not enough to just do it for the characters. If you take the code I posted before and then put a variable in place of the saturation setting, you've got the code for tinting characters grayscale. You just need to modify the saturation variable in-between loops. Then add a similar function for objects and work out how to tint the background efficiently. Instead of using DrawingSurface routines, which can be quite slow, I recommend making a DynamicSprite copy of the background, tinting it grayscale and then displaying it as a transparent object, changing it's transparency along with the saturation variable.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: G on Thu 01/05/2008 23:29:41
Sorry guys... but I got completely lost. I'm afraid my skills at scripting are not very sharp.

Anyway, I meant to say that implementing some kind of saturation control should be useful to give more depth to the use of color as a graphic resource while making a game.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: subspark on Thu 01/05/2008 23:32:00
Quotesince you'd rarely need to change saturation at different spots in the room
Yeah I would also find it useful for turning a color scene into a grayscale scene reminiscent of the 1950's.

The lightmap, shadow, and scale map is something I requested once as an AGS feature long before somebody chose to take it on as a plug-in. This all sounds too good to be true and if I was going to suggest anything it would be to combine your shadow, scale/offset, and lighting plug-in once they are all complete.

How exactly did you get those shadows to work as I was playing with the idea in my head and figured that you could warp or rotate the already flat/horizontal/diagonal (floor) shadow vertically for the walls based on a color code map. How exactly have you approached it? It looks amazing but complex.

The thread for my Z-Depth scale idea seems to have been erased due to server change or something. This was a long while ago. Back in 2.7x.

The idea is this:
To accompany a room background with predrawn lighting conditions like so:
(http://www.shuugouteki.net/paul/Images/room.jpg)

You have a lightmap which pays attention to walkable areas and matches the lighting of the background:
(http://www.shuugouteki.net/paul/Images/lighting-pass.jpg)

And in a similar fashion, you have a scale map (Z-Depth) which scales the character based on shade (luminance):
(http://www.shuugouteki.net/paul/Images/zscale-pass.jpg)

Do you think this is too much for one plug-in alone or would this, combined with your shadow plug-in, serve as the ultimate dream tool for anybody wanting to make super uber rooms for their game?

Cheers,
Paul.

Edit:
QuoteWould anyone else find support for luminance > 100% useful?
I have requested this before actually, CJ. I would find it astronomically useful. Incidentally, my game if full of super bright spotlights that nearly swallow the player and I don't really want to keep creating separate views for this.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Fri 02/05/2008 00:32:00
Oh, I could easily add the scale map thing. Not sure how many people will use it unless the editing/debug tools are super easy to use, so that's the most demanding task in implementing it.

As I said to tolworthy, I don't intend to merge the ShadowBox module with the LightMap module because it's currently too buggy. But since I need to rewrite parts of it to DrawingSurface code, I may rethink if there's better ways to do it than the current coordinate based wall setup functions.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: subspark on Fri 02/05/2008 02:25:52
QuoteNot sure how many people will use it unless the editing/debug tools are super easy to use
Well I imagined that people would simply create their z-depth maps in Photoshop. Not 'everything' has to be done inside AGS. Designing these depth maps is best left in the hands of a technical artist or an artistically motivated programmer anyway. As for the debug tools, youve got me there. :)

QuoteI don't intend to merge the ShadowBox module with the LightMap module yet
Fair enough. But after all the bugs are ironed out or after you have found a new method of going about shadows, do you plan to integrate the two features together?

Top work mate. Your nothing short of a genius to me.

Cheers,
Paul.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Fri 02/05/2008 13:25:46
Quote from: subspark on Fri 02/05/2008 02:25:52Well I imagined that people would simply create their z-depth maps in Photoshop. Not 'everything' has to be done inside AGS. Designing these depth maps is best left in the hands of a technical artist or an artistically motivated programmer anyway. As for the debug tools, youve got me there. :)

The problem with creating them in Photoshop is that you have little color/size reference unless you keep pasting your character in at different scales. With in-game tools, you could move your character around on-screen, set his size for a specific area and the game would provide you with the correct color for that scale. Once painted, you could see him walk around and make adjustments if something looks wrong. But of course you can use external tools if you wish.

QuoteFair enough. But after all the bugs are ironed out or after you have found a new method of going about shadows, do you plan to integrate the two features together?

I intend to keep them as separate modules, but they will be compatible with eachother so you can use both on the same screen.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: subspark on Fri 02/05/2008 14:41:32
QuoteWith in-game tools, you could move your character around on-screen
Thats a very good point but for the meantime I imagine its not too laborious to create them in Photoshop until an engine based dynamic editor has been created. Looking forward to that one.

QuoteI intend to keep them as separate modules, but they will be compatible with each other so you can use both on the same screen.
Thats marvelous. Thanks for the heads up.

Cheers,
Paul.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: lemmy101 on Sat 03/05/2008 17:51:40
We would love to have everything we need to convert from HSV to RGB and back.

i.e. for HSV you have a tint function that passes the hue parameter, but this is handled in AGS at the moment as a scaled RGB value that (I assume) is converted to the hue parameter internally.

For me, the ability to specify the hue itself, or a way to ascertain it from the passed rgb would be mega useful. Then we could convert our end to RGB colour spaces, and do light/colour maps, I assume,  in full RGB.

In one part, for e.g. we want characters to be coloured by a fire, depending on where they are stood, but also the light level of the character, and also simulating the siloette effect you get when an object is in front of a bright lightsource. this is easy to do in RGB, less so in HSV unless we do this conversion.

Since we find RGB the more useful colourspace for such things, we'd love to be able to edit the 360 degree hue, 100% saturation and 100% luminance so we can make this conversion back and forth.

Thanks!

lemmy

PS: GG here are two c# functions we have to convert between HSV (or HSL, same difference) and RGB, if you find them useful (HSV values being between 0 and 1).


                public static Color HSL_to_RGB(HSL hsl)
{
int Max, Mid, Min;
double q;

Max = Round(hsl.L * 255);
Min = Round((1.0 - hsl.S)*(hsl.L/1.0)*255);
q   = (double)(Max - Min)/255;

if ( hsl.H >= 0 && hsl.H <= (double)1/6 )
{
Mid = Round(((hsl.H - 0) * q) * 1530 + Min);
return Color.FromArgb(Max,Mid,Min);
}
else if ( hsl.H <= (double)1/3 )
{
Mid = Round(-((hsl.H - (double)1/6) * q) * 1530 + Max);
return Color.FromArgb(Mid,Max,Min);
}
else if ( hsl.H <= 0.5 )
{
Mid = Round(((hsl.H - (double)1/3) * q) * 1530 + Min);
return Color.FromArgb(Min,Max,Mid);
}
else if ( hsl.H <= (double)2/3 )
{
Mid = Round(-((hsl.H - 0.5) * q) * 1530 + Max);
return Color.FromArgb(Min,Mid,Max);
}
else if ( hsl.H <= (double)5/6 )
{
Mid = Round(((hsl.H - (double)2/3) * q) * 1530 + Min);
return Color.FromArgb(Mid,Min,Max);
}
else if ( hsl.H <= 1.0 )
{
Mid = Round(-((hsl.H - (double)5/6) * q) * 1530 + Max);
return Color.FromArgb(Max,Min,Mid);
}
else return Color.FromArgb(0,0,0);
}

                public static Color HSL_to_RGB(HSL hsl)
{
int Max, Mid, Min;
double q;

Max = Round(hsl.L * 255);
Min = Round((1.0 - hsl.S)*(hsl.L/1.0)*255);
q   = (double)(Max - Min)/255;

if ( hsl.H >= 0 && hsl.H <= (double)1/6 )
{
Mid = Round(((hsl.H - 0) * q) * 1530 + Min);
return Color.FromArgb(Max,Mid,Min);
}
else if ( hsl.H <= (double)1/3 )
{
Mid = Round(-((hsl.H - (double)1/6) * q) * 1530 + Max);
return Color.FromArgb(Mid,Max,Min);
}
else if ( hsl.H <= 0.5 )
{
Mid = Round(((hsl.H - (double)1/3) * q) * 1530 + Min);
return Color.FromArgb(Min,Max,Mid);
}
else if ( hsl.H <= (double)2/3 )
{
Mid = Round(-((hsl.H - 0.5) * q) * 1530 + Max);
return Color.FromArgb(Min,Mid,Max);
}
else if ( hsl.H <= (double)5/6 )
{
Mid = Round(((hsl.H - (double)2/3) * q) * 1530 + Min);
return Color.FromArgb(Mid,Min,Max);
}
else if ( hsl.H <= 1.0 )
{
Mid = Round(-((hsl.H - (double)5/6) * q) * 1530 + Max);
return Color.FromArgb(Max,Min,Mid);
}
else return Color.FromArgb(0,0,0);
}


Also nice idea for a plugin, GG, I don't think this is "too much" for a plugin at all, or I'd hope not :D will look forward to seeing it as it's something we're experimenting with in AGX.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Sat 03/05/2008 18:52:00
I should clarify that this is a module, not a plugin.

Thanks for the code, lemmy. I already have a bit of script that converts RGB values to the S and V part of HSV and then applies them as the Saturation and Luminance value in the tint. I didn't find any need to do the arithmetic for the hue, since I don't really need that value anywhere. But I'll try to add it in a way that makes sense, and also HSL.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: lemmy101 on Sat 03/05/2008 19:13:48
Quote from: GarageGothic on Sat 03/05/2008 18:52:00
I should clarify that this is a module, not a plugin.

Thanks for the code, lemmy. I already have a bit of script that converts RGB values to the S and V part of HSV and then applies them as the Saturation and Luminance value in the tint. I didn't find any need to do the arithmetic for the hue, since I don't really need that value anywhere. But I'll try to add it in a way that makes sense, and also HSL.

NP :)
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Thu 15/05/2008 23:56:13
DISCLAIMER: This is NOT a finished module, merely a proof-of-concept. It is not suitable for public consumption.

I talked to indie_boy in the AGS Stickam room, and he was having some problems with creating a smooth scaling effect using walkable areas. So I decided to whip up an alpha version of my LightMap module so he could see if the module's scaling-map function would be useful for him.

Basically it allows you to use bitmaps (stored as sprites) to specify character scaling and character tint. The color map tints the character the color of the pixel he's standing on in an RGB image. The scaling map scales him depending on a grayscale bitmap where pure black is 5%, 50% gray is 100% and pure white is 200%.

The map dimensions are in 320x240 coordinates. So for hi-res games the mask can maximum be half the background's resolution (there would be no point in having it larger since character coordinates are in 320x240 scale. At the moment, sprites that are too large will crash the game). However, you CAN use LOWER resolutions for the map to save disk space. The only rule is that it's dimensions must be proportional to your game resolution, so for an 800x600 game, the mask could be 400x300, 200x150 or even 100x75 - but the latter would only be recommended for very basic lighting.

Since the AGS color system only supports 64 different gray-scales, I had to add some tweaks to allow for the full 5-200% scaling range (previously the scaling jumped 6 points at a time). The module currently interpolates the scale from three (x,y) sets - the one from the previous frame, the current one and an estimation of the characters next position. It does create a bit of wobblyness when moving in-and-out of standing positions, so I'm trying to come up with alternate solutions.

The module is at the moment totally undocumented and does not confirm to AGS module guidelines. The only two functions available are:

LightMap.SetLightMap(int spriteid);
and
LightMap.SetScaleMap(int spriteid);

Typically you'll want to run these in on_event for eEventEnterRoomBeforeFadein, but to change a light-/scale-map during gameplay just call it again with a different sprite number. I haven't yet set up animation support but plan on it (so the player specifies a View instead of a sprite). You can also input 0 as spriteid and any light/-scale maps will be unloaded. The characters will be stuck with their current scale/color though.

*FILE LINK REMOVED DUE TO 2DADVENTURE SERVER PROBLEMS*

Edit: I should add that it's a good idea to paint your light/scale maps slightly larger than the walkable areas. Otherwise you míght see strange effects when moving near the edges.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: Snarky on Fri 16/05/2008 15:09:40
Instead of that complicated hack to interpolate between 64 levels of grayscale, wouldn't it be easier to use a wider spectrum of colors instead of just grayscale for the map, to provide more gradations? It should be relatively straightforward to automatically convert a 32-bit grayscale map to an 16-bit (or whatever) color map.

I'm thinking something like black->blue->cyan->white (i.e. first the B channel from min-max, then the G channel, then the R channel).

I've never played around with this functionality of AGS, so I'm just assuming that you can use the color channels this way.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Fri 16/05/2008 15:41:54
It should be possible, and it's definitely not a bad idea. But I see no user friendly way to implement it without a plugin or external converter - neither of which I'm able to code. While the concept COULD work for my in-game mask editor by drawing the mask to two separate surfaces (one for the actual mask, one for the grayscale image displayed to the player), the player would still only see the 64 grayscale levels while editing. You could possibly have a toggle key to show the full color image, but I doubt anyone would like to try editing that directly.

An alternate solution could be to fake more grayscale levels in the map editor by drawing some colors as semitransparent pixels (perhaps using a temporary DynamicSprite for the brush). But at the moment the idea makes my head hurt. I'll think about it some more.

I wonder if it would be possible to use the full RGB color spectrum if CJ implemented DrawingSurface.GetPixelRed, GetPixelBlue and GetPixelGreen values, or whether they still would be locked to the 16-bit range?
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: Snarky on Fri 16/05/2008 16:41:44
I guess I was thinking more that you'd create the lightmap in Photoshop, and use a macro to convert it to the right format, and then import it into AGS. I wasn't really concerned with the limitations of an in-game editor.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Fri 16/05/2008 16:47:07
Yes, a Photoshop macro I could probably set up and distribute along with the module. Seems like the best solution would just be to add a "bool UseRGB" parameter to the SetScaleMask(int spriteid) function, so that non-Photoshop users and those wanting to use the in-game editor will have to settle for 64 tones.

Edit: Hmmm, wouldn't the black->blue->cyan->white method only allow for 32 more levels? While it's better than 64 I'm not sure it's worth the hassle. If it was set up to accept mixed colors (e.g. 128 red, 64 blue, 0 green)  we could have a hell of a lot of tones (32*32*32), but to set up this process in a Photoshop macro is becoming quite complicated.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: Snarky on Fri 16/05/2008 19:45:33
If I remember 16-bit RGB correctly, this method would give 32+64+32=128 levels (5+6+5 bits, since the green channel has an extra bit of precision... assuming AGS provides that bit of data).

In order to use more of the color space, as you say, and assuming we only have access to 15 bits, the easiest thing in Photoshop might be to use two of the channels as described, to provide 64 levels, and use the third channel as a multiplier to give us 128, 256, 512, ... or as many levels as we would like (up to some 32 thousand). It does become a little bit more tedious to create the macro, but I think 256 levels would still be very doable, and that's all you need, right?

Alternatively, it would not be too much work to write a small standalone app that calculates and converts the pixel values directly, for a more intuitive mapping (e.g. just read the integer value of each pixel). That way, people wouldn't need to have Photoshop, either. It's really the best solution. If you'd like to give this method a go, I could put together a simple C# app tonight or tomorrow.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Fri 16/05/2008 19:56:05
You could be right about green having 1 more bit (5+6+5=16, yeah :)), but I'm not sure how AGS handles it - if it does, that accuracy should already be available in the RGB code from the colormap module. However, that still only brings us up to 128 levels out of the 200 (or 195) needed, so I think your last solution makes most sense.

It would be awesome if you could write a bitmap color converter app. Just supply me with the arithmetic for the color encoding and I'll get it set up in my module. Thanks, man!
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: Snarky on Sat 17/05/2008 03:15:44
Here's (http://home.comcast.net/~snarkibartfast/LightmapConverter.zip) a quick command-line app for converting between 32-bit grayscale and 16-bit color color-spectrum maps. Warning: If you mess around with the command-line arguments it's possible to overwrite your input file(s), so back them up first!

The arithmetic is pretty simple: Given a 32-bit source image, it looks at the first byte of each pixel (the R channel), and whatever that number is, it puts it into the lower byte (the B channel and the lower 3 bits of the G channel) of the corresponding 16-bit target image. (Since x86 is little-endian, the lower byte is the first byte of each 16-bit byte-pair.)

To convert from the color mapping back to a 0-255 "grayscale" level, for each pixel you just calculate:

G * 32 + B


Err... or you could just use the GetPixel value directly. That ought to work. (If it doesn't, try dividing it by 256.)

Also, I would recommend that instead of trying to scale the 0-255 value we have to the 5-200 range we want, leave it as is and just chop it off at the boundaries.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Sat 17/05/2008 05:29:32
Great work Snarky, it does scale pixel perfect now! I tried passing the GetPixel value directly, and to my amazement it worked (I understand now that's how you coded the application, but I was expecting some kind of conversion necessary). Way cool.

However, I'm not sure about the multiplier. While the dark end of the spectrum works fine (scaling down to zero), I get light values going up to 479! Even though I can script around this, it puzzles me. Shouldn't it be 255?
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: Snarky on Sat 17/05/2008 06:50:08
Glad it seems to be working. I'm able to read back a 0-255 range from the raw pixel data, so AGS must be doing something slightly different. I think I know why you're getting the result you're getting.

479 = 255 + 256 - 32

In other words, the maximum value of 9 bits with the 6th bit off. I would guess this has something to do with the green channel having 6 bits of precision instead of 5.
Desired             Actual AGS
interpretation:     interpretation:

Bits 87654321       9 87654321
Chan gggbbbbb       g gg-bbbbb
I think the easiest way to fix this is something like:


if(value>255)
  value = value - 224; // 224=256-32, i.e. b9-b6


No, wait... that's not right! That would almost certainly shuffle the bits around. Let's see...


value = (value / 64)*32 + (value % 32); // Do not simplify! Dividing by 64 is an important step here


I would certainly hope that one of these versions would work. Both should give you values in the 0-255 range, but only the right one will provide a smooth gradient without discontinuities.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Sat 17/05/2008 14:27:29
Yes, that last one seemed to do the trick. Works perfectly now. Thanks!

I think you're right that there's no need to alter the 0-255 range. Any "resolution gain" would be pure illusion since we already have the full range of scaling values available. And it's much more convenient for the user just to be able to put RGB(scale,scale,scale) as the color rather than having to figure out some arbitrary conversion system.

I'll keep working on the in-game editor. While it won't create smooth transitions, it's a good way for the player to prototype the base scale for each screen area while watching the character move around. It can then be saved to a .bmp/.pcx and opened in Photoshop to add smoothing and gradients.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: Snarky on Sat 17/05/2008 15:31:16
In case someone needs the details of the conversion, here's the relevant C# source code:


// This code, written by me, is public domain and can be used by anyone for any purpose.
// Parts of this code is adapted from a code example found at:
// http://msdn.microsoft.com/en-us/library/system.drawing.imaging.imagelockmode.aspx

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace LightmapConverter
{
// Converts between 16-bit color-spectrum encoded maps and 32-bit grayscale-encoded maps
// Because .NET converts 16-bit images to 32 bits internally, which would mess things up, we have to read and write the raw data ourselves
class Converter
{
private PixelFormat format16bit; // Whether we use 5 or 6 bits for the G channel (RGB:555 or RGB:565 format) in 16-bit

public Converter(PixelFormat format16bit)
{
if (format16bit != PixelFormat.Format16bppRgb555 && format16bit != PixelFormat.Format16bppRgb565)
throw new Exception("Illegal pixel format: Must be a 16-bit format");
else
this.format16bit = format16bit;
}

// Convert a 16-bit color-spectrum-encoded map to a 32-bit grayscale-encoded map
public Bitmap ConvertToGrayscale(Bitmap colorImage)
{
// First, copy the image into a byte-array so we can read the raw data directly:

// Lock the bitmap's bits. 
Rectangle rect = new Rectangle(0, 0, colorImage.Width, colorImage.Height);
System.Drawing.Imaging.BitmapData imageData =
colorImage.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
colorImage.PixelFormat);

// Get the address of the first line.
IntPtr colorImagePtr = imageData.Scan0;

// Declare an array to hold the bytes of the bitmap.
int arraySize = colorImage.Width*colorImage.Height*2; // 2 bytes per pixel
byte[] rawData = new byte[arraySize];

// Copy the RGB values into the array.
System.Runtime.InteropServices.Marshal.Copy(colorImagePtr, rawData, 0, arraySize);

// Unlock the bits.
colorImage.UnlockBits(imageData);

// Now, read the raw data and write it to our new bitmap
Bitmap grayscaleImage = new Bitmap(colorImage.Width, colorImage.Height, PixelFormat.Format32bppArgb);

int index = 0;
for (int y = 0; y < grayscaleImage.Height; y++)
{
for (int x = 0; x < grayscaleImage.Width; x++)
{
int value = rawData[index++];
index++; // We only use every other byte, 1 byte per 16-bit pixel

Color grayLevel = Color.FromArgb(value, value, value);
grayscaleImage.SetPixel(x, y, grayLevel);
}
}

return grayscaleImage;
}


// Convert a 32-bit grayscale-encoded map to a 16-bit color-spectrum-encoded map
public Bitmap ConvertToColor(Bitmap grayscaleImage)
{
// Create a byte array for the raw data
int arraySize = grayscaleImage.Width * grayscaleImage.Height * 2;
byte[] rawData = new byte[arraySize];

// Write the data into the array in the format we want:
//
//         LOW BYTE HIGH BYTE  LOW BYTE HIGH BYTE ...
// Bit:    87654321  87654321
// RGB555: gggbbbbb  -rrrrrgg
// RGB565: gggbbbbb  rrrrrggg
//
// Coding: vvvvvvvv  00000000
//
// Where v is the 8-bit grayscale value, and r,g,b are how the various bits map onto 16-bit color channels
// under typical formats (top bit unused in RGB:555) (cf. http://msdn.microsoft.com/en-us/library/ms788140(VS.85).aspx).
// (It doesn't particularly matter to us, though, as we ignore this RGB coding and just read/write the raw data.)
//
int index=0;
for (int y = 0; y < grayscaleImage.Height; y++)
{
for (int x = 0; x < grayscaleImage.Width; x++)
{
rawData[index++] = grayscaleImage.GetPixel(x, y).R; // If image is grayscale, R=G=B = v
index++; // We only use one byte of data per 16-bit pixel
}
}

// Create a new bitmap and copy our raw data into it
Bitmap colorImage = new Bitmap(grayscaleImage.Width, grayscaleImage.Height, format16bit);

Rectangle rect = new Rectangle(0, 0, colorImage.Width, colorImage.Height);
System.Drawing.Imaging.BitmapData imageData =
colorImage.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
colorImage.PixelFormat);
IntPtr colorImagePtr = imageData.Scan0;

System.Runtime.InteropServices.Marshal.Copy(rawData, 0, colorImagePtr, arraySize);

colorImage.UnlockBits(imageData);

return colorImage; // Don't save this as a PNG file, since .NET does not support 16-bit PNGs and will convert it to 32-bit!
}
}
}
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: subspark on Wed 21/05/2008 18:18:41
Brilliant work Snarky and GarageGothic. You guys certainly impress with your mathematical wizardry! I can't wait for the day when I simply create a smooth scale and light map in photoshop and load it into AGS for use with the finished module. Truly intuitive guys.

Keep up the good work.
Cheers,
Paul.
Title: Re: How to interpret luminance/saturation values from a lightmap
Post by: GarageGothic on Wed 21/05/2008 19:16:57
It should be quite soon. There's just an AGS bug holding be back from finishing the in-game toolset at the moment, so I hope it'll be fixed in the next beta.

I found a McGyver'ish way to circumvent the engine's 64-tone grayscale range and paint in 256 tonal values (I think, at least I've measured up to 252 unique grayscale values when indexing the palette of a screendump in Photoshop). You will still need to save the file to BMP and use Snarky's conversion app to make use of the full spectrum in-game, but it can be previewed while editing using scaling interpolation.

The current in-game editing tools are:

Pencil (line tool, used freehand or with single clicks)
Brush (paints a color, uses a pre-defined sprite as alpha mask - allows transparency, even when using alpha channel mask)
Flood fill (fill area or outline with a plain color)
Gradient flood fill (fill area or outline with gradient between two colors, either radial or linear)
Smoothing tool (Blurs edges between colors)
Eraser (erases to RGB(100,100,100) for grayscale or background color for color map)
Color picker (gets color beneath cursor)
Undo (skip back one of 10 undo levels)
Redo (skip forward again if you just pressed Undo)
Zoom function (shows area currently edited enlarged in a 64x64 window)
Change view (show lightmap transparent over background, shows walkable areas)
RGB sliders and color picking from tonal map (would a palette function be of use for anyone?)

Any wishes for further functionality? It should be easy to add a clone stamp, but I'm not sure how useful it would be.