Killer Rhino.6794:

Hey all,

I hope the [How To] tag can become a developer-helping-developer thread which can show examples on certain APIs for the betterment of the dev community.

The inaugural thread will be on the Color API (since there’s been a lot of confusion with it), inspired by Cliff’s personal assistance in helping me get it working.

The API

Colors API is here: https://api.guildwars2.com/v1/colors.json

It has a root key of “colors”, and contained within that are keys of color IDs. Plucking out a single color dictionary in the color response looks like this:


691: {
    name: "Spring Dew"
    cloth: {
        brightness: 27
        contrast: 1.25
        hue: 190
        saturation: 0.3125
        lightness: 0.644531
    }
    leather: {
        brightness: 27
        contrast: 1.28906
        hue: 190
        saturation: 0.253906
        lightness: 0.664063
    }
    metal: {
        brightness: 27
        contrast: 1.28906
        hue: 190
        saturation: 0.234375
        lightness: 0.664063
    }
}

Materials

Default, cloth, leather, and metal are materials. The dye picker in the game uses the cloth material, so if you plan on doing something similar, use the cloth material (we’ll get to how to produce colors from these values in a second).

Color Components

Each material contains the following: brightness, contrast, hue, saturation, & lightness

brightness:

  • A negative or positive number (range unknown)

contrast:

  • A value between 0 – 2

hue:

  • A value between 0 – 360

saturation:

  • A value between 0 – 2
  • A value of 1.0 will mean “no change”

lightness:

  • A value between 0 – 2
  • A value of 1.0 will mean “no change”

The purpose of these components is to shift a reference (base) color. What does that mean? Starting with the base color, we’re going to use these values to perform calculations that will get us the actual color we want.

Base Color

The base color will start in the RGB colorspace. In whatever language, framework, or library you work in, get a color object in the RGB colorspace that has the values:

R:128, G:26, B:26.

The Color Algorithm

I just updated the API to show the base_rgb of colors. I also removed the non-dye colors to make it more uniform and reduce confusion (multiple “black” dyes, etc.).

I also created a javascript version of our internal color shifting algorithm here: http://jsfiddle.net/cliff/jQ8ga/ . This example code uses the sylvester javascript matrix library for math.

The way it works is pretty different than the processes previously described — it calculates a transformation matrix which is then applied to the color in one pass.

I also added pre-calculated RGB values to the color API for those who don’t need to deal with HSL transformations and just want an RGB value. It can also be used to test your color shifting algorithm against ours for correctness.

smiley.1438 has a site with the colors shown correctly: https://chillerlan.net/gw2color.php

smiley.1438:

Ok, i’ve thrown together an example in php:
https://gist.github.com/codemasher/fb407de5587fdbd3e7c5

conversion functions:
https://gist.github.com/codemasher/741f30e68c6e9c7921c7

Dr Ishmael.9685:

I’ve set this up in my Perl module, and I’m running into cases where the saturation transform results in a value greater than 1, which in turn results in negative RGB values. I know that hue can just wrap-around (i.e. if hue > 360 {hue = hue – 360} ), but what do you do for saturation?

Goddchen.4958:

Awesome, thanks so much.

A working Java implementation can be seen in my GitHub project:
https://github.com/Goddchen/GuildWars2-API-Explorer/blob/master/GuildWars2APIExplorer/src/main/java/de/goddchen/android/gw2/api/data/Color.java

smiley.1438:

I’ve set this up in my Perl module, and I’m running into cases where the saturation transform results in a value greater than 1, which in turn results in negative RGB values. I know that hue can just wrap-around (i.e. if hue > 360 {hue = hue – 360} ), but what do you do for saturation?

Do you have some specific examples so i can check that?

Think.8042:

I’ve set this up in my Perl module, and I’m running into cases where the saturation transform results in a value greater than 1, which in turn results in negative RGB values. I know that hue can just wrap-around (i.e. if hue > 360 {hue = hue – 360} ), but what do you do for saturation?

Do you have some specific examples so i can check that?

Try (144, Umber) or (341, Patina). There’s some more that have saturation > 1.
EDIT: (947, White) has lightness > 1 instead of saturation.

Moturdrn.2837:

I’m making use of the PHP examples provided by smiley to change the colour of individual pixels of each image (background, emblem part 1, emblem part 2.)

Some of these seem to be working okay (e.g. Hot Pink) however White and Abyss seem to be somewhat hit or miss.

Code: http://pastebin.com/PiFs0Tje

Using the above code, the example below should have the background coloured Abyss, and the octopus White. However the colour seems quite off. Anyone have any ideas?

Killer Rhino.6794:

Not sure how much help this is, but:

I normalize my base color’s values, and my hue value, between 0 – 1. Part of the reason is that the iOS libraries expect that range. The base color can be represented in HSL as either (0 deg, 66%, 30%), or (360 deg, 66%, 30%). I rely on the later, so that when it comes to my hue shift calculation becomes (360 deg * hue / 360), or rather just hue.

Also, I observed that most (perhaps all) colors that have cloth, leather and metal material values represent dyes. For instance, there are many “Black” colors, but only one “Abyss” color. Furthermore, only one of the “Black” colors has material values for cloth, leather, and metal. I suspect that is our “Black” dye.

smiley.1438:

Thats where the error in my example is. I assumed that the S/L values are already normalized to 0-1, i didn’t check that before. (Hot pink has already a l>1, so i should have noticed, but it was like 4am when i wrote that…)

zwei.9073:

Thanks!

I used it to create simple color sheer, here is code:

https://code.google.com/p/gw2api/source/browse/trunk/src/cz/zweistein/gw2/app/color/ColorSheet.java

And here is result:

https://gw2api.googlecode.com/svn/trunk/colorsheet.html

(you might want to save it as html file on local drive and then open in broswer)

Killer Rhino.6794:

Thats where the error in my example is. I assumed that the S/L values are already normalized to 0-1, i didn’t check that before. (Hot pink has already a l>1, so i should have noticed, but it was like 4am when i wrote that…)

Darn it, I’m not doing a very good job explaining this (and it’s 8AM, what’s my excuse

I had it wrong when I said S & L are between 0 – 1. And for that matter, brightness is not a value between 0 – 255. Use the values that come in S & L as-is in our HSL shifts. I’ll correct the guide now.

Killer Rhino.6794:

Thanks!

I used it to create simple color sheer, here is code:

https://code.google.com/p/gw2api/source/browse/trunk/src/cz/zweistein/gw2/app/color/ColorSheet.java

And here is result:

https://gw2api.googlecode.com/svn/trunk/colorsheet.html

(you might want to save it as html file on local drive and then open in broswer)

Zwei, in your color shift code, try changing:

hsl0 = (float) (hsl0 * 360 + color.getHue()) / 360;

to:

hsl0 = (float) (color.getHue() / 360);

I took a screenshot of your output and mine. Notice the “Celestial” and “Starry Night”, in particular. The colors on the right should be what to expect.

smiley.1438:

I had it wrong when I said S & L are between 0 – 1 (saturation is; lightness is not).

Actually, they seem both to be between 0 and 2 (or at least >1) :

7: {
	name: "Ocean",
	cloth: {
		brightness: -21,
		contrast: 1,
		hue: 188,
		saturation: 1.32813, <--
		lightness: 0.976563
	},
	leather: {
		brightness: -18,
		contrast: 1,
		hue: 188,
		saturation: 1.13281, <--
		lightness: 1.05469
	},
	metal: {
		brightness: -18,
		contrast: 1,
		hue: 188,
		saturation: 0.78125,
		lightness: 1.01563
	}
}

Killer Rhino.6794:

Ah, yes. Cliff even specifically pointed that out to me in our conversations. I’ll update it again. Thanks, smiley!

Dr Ishmael.9685:

It’s not that the API’s saturation/lightness shift values are > 1, most of the time that’s okay because the calculated S/L that they get applied to are small enough that the result is still < 1. Also, I only see this problem on S, never on L (i.e. L * lightness is always < 1).

Color 592 “Cherry” has “saturation”=1.17188, however, the cloth and leather materials are okay; only on metal does this output an S > 1.

zeeZ.5713:

I had it wrong when I said S & L are between 0 – 1 (saturation is; lightness is not).

Actually, they seem both to be between 0 and 2 (or at least >1) :

7: {
	name: "Ocean",
	cloth: {
		brightness: -21,
		contrast: 1,
		hue: 188,
		saturation: 1.32813, &lt;--
		lightness: 0.976563
	},
	leather: {
		brightness: -18,
		contrast: 1,
		hue: 188,
		saturation: 1.13281, &lt;--
		lightness: 1.05469
	},
	metal: {
		brightness: -18,
		contrast: 1,
		hue: 188,
		saturation: 0.78125,
		lightness: 1.01563
	}
}

Just to make that clear, they’re not raw values you take but factors you multiply the current values with.

How much bigger than 1 is the product? If it’s not much I’d just cut it off.

Think.8042:

I’ve seen this happening up to a resulting RGB value of 111,-32,-8 on #122 (Ruby, metal).
Changing it up to 111,0,0 doesn’t completely kill the color, but it’s still something to consider.

You might also want to take a look at colors #144, 145, 146, they have these negative values on all materials.

Killer Rhino.6794:

Saturation & Lightness values are ranged between 0 – 2. A value of 1 means “no change”.

So sorry for the confusion, but the feedback is super helpful.

Edit: I’ve updated the guide to be clearer.

Dr Ishmael.9685:

Doh, sorry, I’ve tracked my issue backwards now – it’s when I apply the contrast transform, and it gives me negative values for G and B.

Color 482 “Midnight Fire” for material “cloth” has “brightness”=-38;“contrast”=1. Applying that to the base 128,26,26 produces 90,-12,-12. I’m obviously missing something here, but I’m not sure what it is.

Killer Rhino.6794:

Dr Ishmael, Let’s take a look at an example:


name: "Midnight Fire"
    cloth: {
        brightness: -38
        contrast: 1
        hue: 25
        saturation: 0.273438
        lightness: 1.01563
    }

We start with our baseColor: (R:128, G:26, B:26).

Dealing with just “cloth”, for now, apply the brightness:
(128-38, 26-38, 26-38) = (90, -12, -12).

Now apply the contrast:
(90 – 128) * 1 + 128 = 90
(-12 – 128) * 1 + 128 = -12
(-12 – 128) * 1 + 128 = -12

Now convert RGB to HSL
(Not shown: I will normalize the RGB values between 0 – 1. Also, the conversion function I use will output HSL normalized values between 0 – 1)
R:90 -> H:1 (or 360 deg, if you so prefer)
G:-12 -> S:1 (or 100%, if you so prefer)
B:-12 -> L:0.176471 (or 17.6471%, if you so prefer)

Apply the HSL shifts from the response with the “RGB converted-to- HSL” values:
Hue = 1 * (25 / 360)
Saturation = 1 * 0.273438
Lightness = 0.176471 * 1.01563

So at this point the HSL values I have are:
hsl_Hue 0.0694444
hsl_Sat 0.273438
hsl_Light 0.179229

Finally converting back to RGB should produce:
R:0.228237
G:0.171061
B:0.130221

Think.8042:

What are you normalizing? The RGB input? The calculation results? And how are you doing the normalization considering the fact that RGB is only defined on positive values.

Converting RGB 90, -12, -12 results in HSL 1 / 1.30 / 0.15 (rounded) for me.

Killer Rhino.6794:

What are you normalizing? The RGB input? The calculation results? And how are you doing the normalization considering the fact that RGB is only defined on positive values.

Converting RGB 90, -12, -12 results in HSL 1 / 1.30 / 0.15 (rounded) for me.

Look here: http://www.workwithcolor.com/color-converter-01.htm

Notice that
R:90
G:0
B:0
(as you pointed out, -12 clamps to 0 in RGB)

converts to

H:360 deg (0 deg & 360 deg are the same)
S:100%
L:18%

If we normalize the HSL values to be within 0 – 1:

H: 1 (from 360 deg / 360 deg)
S: 1 (from 100% / 100%)
L: 0.18 (from 18%)

Now look at the values in my example:

Now convert RGB to HSL (The library I use normalizes the values between 0 – 1)
R:90 -> H:1 (or 360 deg, if you so prefer)
G:-12 -> S:1 (or 100%, if you so prefer)
B:-12 -> L:0.176471

They’re pretty close. Did that answer your question?

Dr Ishmael.9685:

Rhino, basically what you’re saying is that I should “correct” any negative RGB value to 0 before converting to HSL? Just want clear confirmation of this before I go put it in my code.

Killer Rhino.6794:

Rhino, basically what you’re saying is that I should “correct” any negative RGB value to 0 before converting to HSL? Just want clear confirmation of this before I go put it in my code.

Yes, you should clamp RGB values to the range of 0 – 255. What language are you writing in, btw?

zwei.9073:

Thanks!

I used it to create simple color sheer, here is code:

https://code.google.com/p/gw2api/source/browse/trunk/src/cz/zweistein/gw2/app/color/ColorSheet.java

And here is result:

https://gw2api.googlecode.com/svn/trunk/colorsheet.html

(you might want to save it as html file on local drive and then open in broswer)

Zwei, in your color shift code, try changing:

hsl0 = (float) (hsl0 * 360 + color.getHue()) / 360;

to:

hsl0 = (float) (color.getHue() / 360);

I took a screenshot of your output and mine. Notice the “Celestial” and “Starry Night”, in particular. The colors on the right should be what to expect.

Fixed, thanks :-)

Dr Ishmael.9685:

I’m writing in Perl. My module is on GitHub.

Hmm… Correcting up to 0 solves Midnight Fire, but I’m still getting problems with S * saturation > 1. Let’s go back to color 592 Cherry on metal that I mentioned before.

name: "Cherry"
    metal: {
        brightness: -5
        contrast: 1.13281
        hue: 356
        saturation: 1.17188
        lightness: 1.05469
    }

Base color: (R:128, G:26, B:26)

Apply brightness: (R:123, G:21, B:21)

Apply contrast: (R:122.33595, G:6.78933, B:6.78933) <— completely sane, no negative values

Convert to HSL: (H:0, S:0.89484, L:0.25319) <— matches the workwithcolor.com converter

Apply HSL transform: (H:0.98889, S:1.04865, L:0.26703) <— S > 1 ???

smiley.1438:

I’m still unsure if these are right, but: https://chillerlan.net/gw2color.php

Killer Rhino.6794:

I’m still unsure if these are right, but: https://chillerlan.net/gw2color.php

Nailed it!

Edit: added the link to the How-To guide for reference

Think.8042:

I’m still unsure if these are right, but: https://chillerlan.net/gw2color.php

Nailed it!

Really? Hot Purple, #526
→ rgb(308,-11,308)

Same problem as Ish mentioned above, even with clamping, the HSL shift will create values outside of HSL, and subsequently RGB.

Killer Rhino.6794:

I’m still unsure if these are right, but: https://chillerlan.net/gw2color.php

Nailed it!

Really? Hot Purple, #526
-> rgb(308,-11,308)

Same problem as Ish mentioned above, even with clamping, the HSL shift will create values outside of HSL, and subsequently RGB.

Have you tried carrying this through the whole algorithm? What do you get?

smiley.1438:

I’m still unsure if these are right, but: https://chillerlan.net/gw2color.php

Nailed it!

Edit: added the link to the How-To guide for reference

I’ve updated my gist to reflect the changes: https://gist.github.com/codemasher/fb407de5587fdbd3e7c5

€: i’ve forgot to “correct” the final RGB values earlier.

Think.8042:

I get the same colors that are already visible, because Opera will silently clamp negative or excessive RGB values to [0..255], but is this really the way to go?

Killer Rhino.6794:

I get the same colors that are already visible, because Opera will silently clamp negative or excessive RGB values to [0..255], but is this really the way to go?

Sure. The easiest way to know if you’re doing it right: Does #947 White result in R:255 G:255 B:255?

Think.8042:

Yeah, it does. The colors look like they’re calculated correctly, but I was wondering if clamping to the RGB space is really the way this is intended to be done.

Dr Ishmael.9685:

947 White actually gives L > 1. I’m not sure how I didn’t notice this before.

name: "White"
    default: {
        brightness: 52
        contrast: 1.85938
        hue: 185
        saturation: 0
        lightness: 1.99219
    }

Base color: (R:128, G:26, B:26)

Apply brightness: (R:180, G:78, B:78)

Apply contrast: (R:224.68776, G:35.031, B:35.031) <— completely sane, no negative values

Convert to HSL: (H:0, S:0.75777, L:0.50925) <— matches the workwithcolor.com converter

Apply HSL transform: (H:0, S:0.51389, L:1.01453) <— L > 1 ???

Think.8042:

I posted that earlier up there but the solution should work the same. At least for me it does.

Dr Ishmael.9685:

It still seems weird to me to “clamp” results to a given range “just because”, but I have very little experience with colorspace transforms like this.

So it’s perfectly alright to correct the result of a transformation (like applying contrast to RGB or a saturation-shift to HSL) if said result is outside the allowed range? If G < 0, just kick it up to 0? If S > 1, just smack it back down to 1?

Think.8042:

This is exactly my concern, too ^^

Killer Rhino.6794:

It still seems weird to me to “clamp” results to a given range “just because”, but I have very little experience with colorspace transforms like this.

So it’s perfectly alright to correct the result of a transformation (like applying contrast to RGB or a saturation-shift to HSL) if said result is outside the allowed range? If G < 0, just kick it up to 0? If S > 1, just smack it back down to 1?

Assuming “0.0 is 0” & “1.0 is 255”, an excerpt from Apple’s official iOS color documentation reads:

Discussion
Values below 0.0 are interpreted as 0.0, and values above 1.0 are interpreted as 1.0.

This is a restriction for any color library. There’s no way to represent an RGB color outside the range of 0 to 255. It’s not that strange.

Ruhrpottpatriot.7293:

Yep, that’s probably it. Colors only have a defined range. in RGB you simply cannot go beyond 255 and below zero. So any values above or below the max/min part should be interpreted as their respective max/min values.

I think that the values above or below the normed values come from mathematical conversion. WE don’t know if ANet uses the same way to storage the color values in the game as they give us via the api.

BruderTack.8429:

I build a php script to get all colors, but I’m not sure if I did everything right.
Maybe we can compare some RGB values (cloth)?

#2: 70, 69, 71
#129: 95, 115, 48
#443: 187, 186, 185
#629: 95, 10, 15
#1154: 252, 161, 15

smiley.1438:

I’ve already posted an example, have a look over here: https://chillerlan.net/gw2color.php
(i’ve added title-attributes, so that you can see the RGB values when you hover over a color)

BruderTack.8429:

Ah yes, I saw the link in OPs post too late. I compared the values and they are identical, fine.

zeeZ.5713:

I stumbled across those CSS3 filters:

	-webkit-filter: brightness(1.5) contrast(1.5) hue-rotate(180deg) saturate(2);

Now if only there was a lightness filter :P

Dr Ishmael.9685:

Okay, final question (I think). Looking at Smiley’s Gist code (post #2 in this thread), he’s not correcting the S/L values before converting HSL back to RGB, and instead just correcting the final RGB values. If I follow that method, I can produce an exact match of his HTML page.

However, that doesn’t make sense. If S > 1 is outside the HSL colorspace, wouldn’t that result in an invalid conversion back to RGB? Shouldn’t we be correcting S and L to 1 before the final RGB conversion?

Example: color 1220 Shamrock (default). Smiley’s result is (0, 255, 0), but if I correct S/L first, I get (0, 220, 37).

smiley.1438:

*Notice: i don’t claim that these results are correct, they only match Killer Rhino’s results. I’m still waiting for Cliff to clear things up and tell us if we’re on the right track.

Dr Ishmael.9685:

Yeah, some official confirmation would be nice at this point.

From the looks of Rhino’s code, and the documentation he quoted, it looks like iOS handles all of these corrections internally and silently. I’m going to go with correcting S/L before the final RGB conversion.

Killer Rhino.6794:

Yeah, some official confirmation would be nice at this point.

From the looks of Rhino’s code, and the documentation he quoted, it looks like iOS handles all of these corrections internally and silently. I’m going to go with correcting S/L before the final RGB conversion.

Not just iOS, the color functions are defined universally. I would check some source material out on the net if you need more affirmative confirmation. Regardless, you shouldn’t need to change any of the values returned by the APis

Edit: take a look again at the detailed example I posted a few responses up.

Cliff Spradlin.3512:

The colors you were seeing invalid saturation values for were colors that had a different base_rgb than 128,26,26 (skin, hair, eye, etc. colors).

I just updated the API to show the base_rgb of colors. I also removed the non-dye colors to make it more uniform and reduce confusion (multiple “black” dyes, etc.).

I also created a javascript version of our internal color shifting algorithm here: http://jsfiddle.net/cliff/jQ8ga/ . This example code uses the sylvester javascript matrix library for math.

The way it works is pretty different than the processes previously described — it calculates a transformation matrix which is then applied to the color in one pass.

I also added pre-calculated RGB values to the color API for those who don’t need to deal with HSL transformations and just want an RGB value. It can also be used to test your color shifting algorithm against ours for correctness.

Killer Rhino.6794:

The colors you were seeing invalid saturation values for were colors that had a different base_rgb than 128,26,26 (skin, hair, eye, etc. colors).

I just updated the API to show the base_rgb of colors. I also removed the non-dye colors to make it more uniform and reduce confusion (multiple “black” dyes, etc.).

I also created a javascript version of our internal color shifting algorithm here: http://jsfiddle.net/cliff/jQ8ga/ . This example code uses the sylvester javascript matrix library for math.

The way it works is pretty different than the processes previously described — it calculates a transformation matrix which is then applied to the color in one pass.

I also added pre-calculated RGB values to the color API for those who don’t need to deal with HSL transformations and just want an RGB value. It can also be used to test your color shifting algorithm against ours for correctness.

Thanks, Cliff. I updated my library with the new response fields and pushed them up. I’ll update the How-To.

Dr Ishmael.9685:

I also created a javascript version of our internal color shifting algorithm here: http://jsfiddle.net/cliff/jQ8ga/ . This example code uses the sylvester javascript matrix library for math.

The way it works is pretty different than the processes previously described — it calculates a transformation matrix which is then applied to the color in one pass.

I was able to convert this to Perl easily. The great thing is that Perl has “native” support for matrices, i.e. just create an array of arrays. Coupled with a subroutine for matrix multiplication from my Perl books, and I’m able to reproduce all of the pre-calculated RGB values. Thanks, Cliff!

Goddchen.4958:

precalculated RGB values: thanks you so much Cliff

zwei.9073:

precalculated RGB values: thanks you so much Cliff

I definitelly seccond this thanks!

Think.8042:

Yep, converting this new algorithm to PHP was not too hard, also using arrays for vectors and some algebra functions. Thanks for the precalculated values, too.

=)

smiley.1438:

Yep, converting this new algorithm to PHP was not too hard, also using arrays for vectors and some algebra functions. Thanks for the precalculated values, too.

=)

Mind to share? I’m still getting incorrect values with this:
https://gist.github.com/codemasher/b869faa7603e1934c28d

(my brain melts >.<)

Dr Ishmael.9685:

Smiley, I think you just need to change your bgrVector (line 82) from a single-row matrix to a single-column matrix. Then swap the order in the matrix multiplication, so it matches Cliff’s order.

smiley.1438:

Ah, thanks Dr Ishmael, that helped!

Updated and cleaned up that gist, you may take it as php example then:
https://gist.github.com/codemasher/b869faa7603e1934c28d

Also updated my color list: https://chillerlan.net/gw2color.php

Anyway, theres still the other conversion approach - i’d still like to know where the flaw is in there…

Think.8042:

Sorry, I somehow thought I posted the Gist link here. Since it’s already done now and looks pretty much exactly like mine, I won’t bother posting an identical copy, so people can focus a little more on what’s working.

Primal Zed.9714:

What about the difference between highlight, base, and shadow of each dye? The Flame & Frost dyes obviously have a greater difference in these values than most dyes do. How is this determined?