Hue Shift (like photoshop) Effect
Blitz3D Forums/Blitz3D Programming/Hue Shift (like photoshop) Effect
| ||
Hey everybody... this is a really weird thing, but I'm hoping that someone can help me out with it. Kinda hard to explain, but I'll try. I'm trying to accomplish a "hue shift", like in photoshop, but in realtime on an image. Doing read / write pixelfast would not be fast enough. You can see what i'm trying to do if you take an image in photoshop, go to image > adjust > Hue / Saturation and move the hue slider. On a rainbow-image, the image will appear to move right to left, as green becomes blue, red becomes orange, etc. Here's my approach. It's ALMOST WORKING. but not quite. I'm splitting my image into 3 grayscale channels, R, G, & B. Then placing them on top of each other additively, so they combine to form the image. Next, I'm doing an entitycolor on each of them to change how much red green and blue is in them. But it's not quite right. I'm attaching a zip file so you can see what i'm talking about. right now the rainbow kind of moves left to right, but it's not smooth. Any ideas?? [a] http://www.prayercore.com/roland/hueshift.zip [/a] thanks! roland |
| ||
man, couldn't get the URL to work... what's the syntax? |
| ||
slash goes the other direction like this / [edit] nevermind i forgot its "a" not url. the link |
| ||
Thanks mayaman! |
| ||
You may have to play with additive blending since EntityColor is only Subtractive. Working with both may allow to get any color. At the other hand, to alter the Hue you should convert RGB to HSB in the first place, then you only need to edit the Hue value, or Saturation or Brightness. Tho, convertion is slow: http://www.blitzbasic.com/codearcs/codearcs.php?code=1380 http://www.blitzbasic.com/codearcs/codearcs.php?code=2072 |
| ||
Hey jfk, thanks for the reply! I'll look at using the HSV conversion, but I'm pretty sure this will be too slow to do in realtime. I'll let you know how it works out, though. As to the other way, I'm not sure I know what you mean by saying that EntityColor is only subtractive... What I'm doing right now is I've split my image into 3 different textures which correspond to the greyscale R, G, and B channels out of photoshop. Then I am doing entitycolor on each of these individually and combining them additively. Does that make sense? Download the zip and check out the code if you get a sec and you'll see what i mean. Do you have any suggestions for why this isn't working correctly? I feel like i've tried every possible way of shifting their colors and still can't get it to work right. The only thing I can think at this point is that not having the Saturation and Brightness separate from the color is preventing the hue shift from being continuous. But what's weird is that the shift on a single color seems to work perfectly, and the shift on all three works correctly at some points, just not all. I really appreciate your help and hope we can sort this out. It would be an awesome realtime effect in the project that i'm working on! cheers, roland |
| ||
hmm... well, i integrated the HSL into the code and made it calculate the way to color each sprite using the hue instead, and it is better. But the colors still get blown out when they are cycled instead of staying at the correct luminance. Strange. This is definitely simpler and closer to working right, but it's not quite there. any ideas? Graphics3D 640,480,0,2 pivot = CreatePivot() cam = CreateCamera(pivot) PositionEntity cam,0,0,30 PointEntity cam,pivot AmbientLight 0,0,0 Global result_r#, result_g#, result_b# Global result_h#, result_s#, result_l# Type col Field image Field red Field green Field blue Field state Field h# Field s# Field l# End Type Function makecol(file$,red,green,blue,state) c.col = New col c\image = LoadSprite(file) ScaleSprite c\image,20,20 EntityFX c\image,1 EntityBlend c\image,3 c\red = red c\green = green c\blue = blue c\state = state rgb_to_hsl(c\red,c\green,c\blue) c\h = result_h c\s = result_s c\l = result_l End Function makecol("red.jpg",255,0,0,1) makecol("green.jpg",0,255,0,3) makecol("blue.jpg",0,0,255,5) speed = 5 While Not KeyHit(1) For c.col = Each col hsl_to_rgb(c\h,c\s,c\l) c\red = result_r c\green = result_g c\blue = result_b If c\h < 360 c\h = c\h+speed Else c\h = 0 EndIf EntityColor c\image,c\red,c\green,c\blue Next RenderWorld() Flip Wend End ; RGB/HSL function return values. ; ; Converts an RGB color to HSL. ; ; Params: ; ir,ig,ib - Color's red, green and blue components (0-255). ; ; Returns: ; The converted color's HSL values via the following globals: ; result_h - Hue component (0-360). ; result_s - Saturation component (0-1). ; result_l - Luminance component (0-1). ; Function rgb_to_hsl(ir%, ig%, ib%) ; Scale RGB down to unit range (0-1). r# = ir/255.0 g# = ig/255.0 b# = ib/255.0 If r > g max_is = 0 max_color# = r min_color# = g Else max_is = 1 max_color# = g min_color# = r EndIf If b > max_color max_is = 2 max_color = b ElseIf b < min_color min_color = b EndIf ; Luminance. result_l = (max_color + min_color) / 2 If max_color = min_color ; Color is grey. result_s = 0 ;result_h = 0 Else delta# = (max_color - min_color) ; Saturation. If result_l < 0.5 result_s = delta / (max_color + min_color) Else result_s = delta / (2.0 - max_color - min_color) EndIf ; Hue. Select max_is Case 0 ; Red. result_h = (g - b) / delta Case 1 ; Green. result_h = 2.0 + (b - r) / delta Case 2 ; Blue. result_h = 4.0 + (r - g) / delta End Select result_h = result_h * 60.0 If result_h < 0 Then result_h = result_h + 360.0 EndIf End Function ; ; Converts an HSL color to RGB. ; ; Params: ; h,s,l - Color's hue(0-360), luminance(0-1) and saturation(0-1) components. ; ; Returns: ; The converted color's RGB values via the following globals: ; result_r - Hue component (0-255). ; result_g - Saturation component (0-255). ; result_b - Luminance component (0-255). ; Function hsl_to_rgb(h#, s#, l#) If s = 0 result_r# = l result_g# = l result_b# = l Else If l < 0.5 temp2# = l * (1.0 + s) Else temp2# = (l + s) - (l * s) EndIf temp1# = 2.0 * l - temp2 h = h / 360.0 rtemp3# = h + 1.0 / 3.0 If rtemp3 < 0 rtemp3 = rtemp3 + 1.0 ElseIf rtemp3 > 1 rtemp3 = rtemp3 - 1.0 EndIf gtemp3# = h If gtemp3 < 0 gtemp3 = gtemp3 + 1.0 ElseIf gtemp3 > 1 gtemp3 = gtemp3 - 1.0 EndIf btemp3# = h - 1.0 / 3.0 If btemp3 < 0 btemp3 = btemp3 + 1.0 ElseIf btemp3 > 1 btemp3 = btemp3 - 1.0 EndIf ; Set red. If (6.0 * rtemp3) < 1 result_r# = temp1 + (temp2 - temp1) * 6.0 * rtemp3 ElseIf (2.0 * rtemp3) < 1 result_r# = temp2 ElseIf (3.0 * rtemp3) < 2 result_r# = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - rtemp3) * 6.0 Else result_r# = temp1 EndIf ; Set green. If (6.0 * gtemp3) < 1 result_g# = temp1 + (temp2 - temp1) * 6.0 * gtemp3 ElseIf (2.0 * gtemp3) < 1 result_g# = temp2 ElseIf (3.0 * gtemp3) < 2 result_g# = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - gtemp3) * 6.0 Else result_g# = temp1 EndIf ; Set blue. If (6.0 * btemp3) < 1 result_b# = temp1 + (temp2 - temp1) * 6.0 * btemp3 ElseIf (2.0 * btemp3) < 1 result_b# = temp2 ElseIf (3.0 * btemp3) < 2 result_b# = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - btemp3) * 6.0 Else result_b# = temp1 EndIf EndIf ; Scale RGB back to 0-255 range. ; Remove this if you want to keep RGB in the range 0-1! result_r = result_r * 255 result_g = result_g * 255 result_b = result_b * 255 |
| ||
Hm... Your code starts with: A*(255,0,0) + B*(0,255,0) + C*(0,0,255) Then the way you increment and decrement stuff, when you're halfway through one state it should be: A*(127,0,127) + B*(127,127,0) + C*(0,127,127) So if your initial image was red, then A would be (255, 255, 255), and B and C would be (0,0,0). So: (255, 255, 255)*(127,0,127) + (0,0,0)*(127,127,0) + (0,0,0)*(0,127,127) = (127,0,127) There's your problem. You need to increment the green until it reaches 255 and THEN start to decrement the red until it is 0, leaving the green at 255, not increment one while decrementing the other simulaneously. The total value of the three channels for each grayscale image should oscillate between 255 and 510. But due to the way your code works, and a bug you have somewhere, it doesn't do that. Try printing this: Text 0,0,c\red+c\green+c\blue It starts a 255, then jumps to 260 suddenly and stays there/ That's obviously wrong, even if 255 was the right value to always have. |
| ||
Hey Sswift, thanks for jumping in! I'm afraid I'd already tried that approach and found that it didn't work either (though I suspect it's <more> right than the one that i posted). In fact, it looks identical to the HSL version that I posted based on jfk's suggestion. I still don't understand what I can do to eliminate the intermediate stages where the colors add together to be too bright. Please let me know if you have any other ideas! thanks again, roland here's a version of it based on that approach. It adds to the next color while the previous color is at 255, then decrements the previous color, etc. A 6-step approach instead of 3. Graphics3D 640,480,0,2 pivot = CreatePivot() cam = CreateCamera(pivot) PositionEntity cam,0,0,30 PointEntity cam,pivot Type col Field image Field red Field green Field blue Field state End Type Function makecol(file$,red,green,blue,state) c.col = New col c\image = LoadSprite(file) ScaleSprite c\image,20,20 EntityFX c\image,1 EntityBlend c\image,3 c\red = red c\green = green c\blue = blue c\state = state EntityColor c\image,c\red,c\green,c\blue End Function makecol("red.jpg",255,0,0,1) makecol("green.jpg",0,255,0,3) makecol("blue.jpg",0,0,255,5) speed = 10 While Not KeyHit(1) For c.col= Each col Select c\state Case 1 c\green = c\green+speed If c\green>255 Then c\state = 2 Case 2 c\red = c\red-speed If c\red < 0 Then c\state = 3 Case 3 c\blue = c\blue+speed If c\blue > 255 Then c\state = 4 Case 4 c\green = c\green-speed If c\green < 0 Then c\state = 5 Case 5 c\red = c\red+speed If c\red > 255 Then c\state = 6 Case 6 c\blue = c\blue -speed If c\blue < 0 Then c\state = 1 End Select EntityColor c\image,c\red,c\green,c\blue If c\red < 0 Then c\red = 0 If c\green < 0 Then c\green = 0 If c\blue < 0 Then c\blue = 0 Next RenderWorld() c.col = First col Text 0,0,c\red Text 0,14,c\green Text 0,28,c\blue Text 0,50,c\red+c\green+c\blue Flip Wend |
| ||
I noticed you're still going up to 260 on your color channels, so I modified your code to not only make sure it doesn't go over 255, but so that it goes slower so you can see what's going on, and so it displays the original image and waits for you to hit a key before starting so you can get a better idea of where the bright spots are occuring. And it looks like the bright spots are occuring where your secondary colors, magenta, yellow, and cyan are, which is not surprising. Hm... Well, I know what's happening, but I don't know how to solve it. Let's say the color you're looking at in the image is yellow. You get yellow where both the red and green images are white. Now you start sliding the hue. The red layer starts becoming yellow, the green layer starts becoming cyan, and the blue layer starts becoming magenta. Yellow and cyan together contain all three primary colors, so you end up with white wherever your original image had both red and greem or green and blue, or blue and red. |
| ||
Yeah, that's exactly what's happening, sswift. You put it into words nicely... I'm stumped as to how to fix it as well. It makes me wonder why this behavior isn't seen in photoshop when you adjust the Hue of an image. It seems like there must be a bound on how high the additive values of a pixel can be based on its initial luminosity. So if you were evaluating the color of just a single pixel at a time you could prevent it from becoming too bright based on that initial measurement. Here, we don't have the ability to do that because we're modifying the entire image all the time. I wonder if it's not possible to fix it without doing read / write pixels on the whole image. if you have any other ideas, please let me know. I'm really motivated to get this working, but feel pretty much stumped right now. thanks again for your help! roland |
| ||
Well this behavior isn't seen in photoshop because they can do stuff like convert from RGB to HSV and back. So they can adjust the hue, without affecting the saturation or the value components of the image. If you could find some way to split an image into hue saturation and value channels, and alter the hue channel as desired, and then recombine them, then maybe you could do what you're trying to do. Value is easy enough, that's a black and white version of your image. Saturation... That one's not so easy to define. You can have dark pixels which are less saturated and light pixels which are less saturated. Maybe if you laid down the hue layer first, then added a saturation layer to that where white is least saturated, and then applied the value on top of that with multiply? The hue layer would be pixels that are full brightness hues, so (255,0,0)..(255,255,0)..(0,255,0) and so on. But how do you then adjust the hue layer? Once again you seem to have the same problem with either white showing up, or ending up with half-bright primary colors. I played around with photoshop a bit to see if I could figure out a way to get the effect you want with the color channels like you have now, but I failed there. I used the channel mixer to create a version of the image that looks like a hue adjustment of -120. So I moved the red channel to the green, the green to the blue, and the blue to the red. I had that on a layer over the original. I could then experiment with layer blending styles to try to find a way to blend between the two that would create a result that looks like a hue adjustment of only -60. The closest I got was to set the modified layer to 50% transparency, and normal blend with the original. The result was the right hues, and the right brightness level, but they were desaturated by 50%. Nothing else I tried worked. I tried cutting the brightness of both layers by half and adding them and that didn't look right at all. Desaturated and super contrasty. And simply adding them didn't work of course. Nice saturation, but everythng overbrightened. There was no point in playing around with most of the blending modes offered, since the only ones you have to play with in Blitz are multiply, add, and alpha. So those are the ones I tried to get to do the job. But no dice. |