Hue Shifting with Transforms

Recently I had the opportunity to fiddle with some image adjustment algorithms to show an approximate preview for a texture on a mesh which among others included a hue shift.

Of course shifting the hue is no big deal, there’s already of plethora of online resources that demonstrate how to change a RGB color to HSL and back which makes hue shifting as easy as incrementing a value, which is very nifty of course, however let’s assume you already have a setup that computes a matrix to adjust brightness, contrast and saturation which is sent to a shader, this is where things get interesting.

Now the obvious solution would have been to modify the shader, pass the hue value do the adjustment and be done with it, however that broke backwards compatibility, so this was not an option. Luckily, HSL isn’t just a formula, once you try to visualize RGB as a coordinate system instead of pixel values, you start to realize that a rotation on the (1,1,1) axis would essentially create a hue shift, HSL is only a convention to bring the Z (or blue in our case) axis to the (1,1,1) direction so you can make the rotation.

So let’s consider that Create*Rotation would make a rotation matrix on the specified axis with the specified angle and that MultiplyMatrix multiplies two matrices leaving the result in the last argument, then the following sequence will create a matrix adjustment for a hue shift:

CreateXRotation(xrot, PI / 4)
CreateYRotation(yrot, PI / 6)
CreateZRotation(hueShift, hue)
CreateYRotation(invyRot, -PI / 6)
CreateXRotation(invxRot, -PI / 4)
MultiplyMatrix(xrot, yrot, tohsl)
MultiplyMatrix(tohsl, hueShift, shiftedMat)
MultiplyMatrix(invyRot, invxRot, torgb)
MultiplyMatrix(shiftedMat, torgb, adjustment)

Which gives us an adjustment matrix that we can premultiply into the existing matrix, problem solved and we keep backwards compatibility.

One thing to be noted is that hue has to be in radians, the most intuitive course of action is to let users specify it in degrees then turn it into radians before creating the matrix, but that is just a minor detail.

And there you have it, an easy to use transform to implement hue shifting when working with HSL values is not an option

Defcamp Conclusions

It’s only natural that if I write an introductory post regarding an event, then I should also follow up with some conclusions, more so when the event itself was to say the least awe inspiring.

Now I know one or two things about security, it’s as much a hobby as it is a curiosity of mine, though I’m not the “I don’t know what that is but I know I’m gonna break it” type, I like to understand common pitfalls so that if hypothetically someone were to try and break my code at least they’d say it posed some sort of a challenge, so this conference was a bit of a head rush for me, quantity and quality playing equal roles.

However what happens in Defcamp, stays in Defcamp so I’m not going to go into a lot of detail regarding the contents of the presentations, if you want them, send them an email, I just want to point out that everyone there did the same things you’d do, they only saw them differently, so if that’s what you’re after, then you’re definitely going to want to participate next year.

As for me, count me in next year as well, I have a flag to catch.

Icon Misfortune

Around 60-70% of the tasks that you will receive as a programmer will be simple and very easy to do. Generally, you can suppose that the rest will be the same, the difference however is that they will generate bugs, and I’m not talking the kind that will turn out to be tasks themselves, rather about the ones that will spawn out of nowhere and persist for no apparent reason.

Let’s say for instance that you receive an icon set that needs to end up in a ribbon menu, the project used some other form of UI toolkit so you need to create a managed buffer, dump the raw image data in there and set that as a data source for the ribbon because in this case it’s better for everyone if small resources are embedded in a dll. And you do this and it works, only every icon looks normal except one. So each icon is 32×32 and they also get forcibly resized by the api to a 32×32 image when they get set and yet just one of them is 42×42.

Of course a quick debugging session revealed that the culprit was the DPI which is where the fun just started because of course everyone swears that unless otherwise specified WPF bitmaps are created with a default 96 dpi and that DPI settings only have a getter, so they are locked there and that’s where they will stay.
This is where you start to learn new things, remember the raw image data I was dumping in the stream? As it turns out, the PNG standard says that width and height aren’t enough to describe the dimensions of a picture, you should also save the DPI and to make everything perfect, if you go down to section 13.6 you see this little gem:

Non-square pixels can be represented (see pHYs Physical pixel dimensions), but viewers are not required to account for them; a viewer can present any PNG datastream as though its pixels are square.

Well guess what because of this tiny detail, every viewer I used showed the image as a normal 32×32 picture, except for that ribbon button.

Of course editing an image header to solve a little misunderstanding due to inconsistency isn’t really that much of a story in itself, but the way you get there usually is.