I'm not sure if this is the most efficient way to handle it, but I needed a way to dynamically modify my gosu colors and this is the module I came up with. It works in its current state so feel free to use it if you find it useful :)
If you: Include Colored into your ruby objects, you can then initiate a gosu color to them, and then modify it using these module methods.
I mainly needed it for dynamic alpha, but it wasn't any different to make it work for the color channels either.
Examples:
Object.alpha += 1 # Increases the alpha by 1
Object.alpha = "ff" # Set alpha channel to ff, 255 (Opaque)
Let me know if you find it useful, if there are ways to optimize, if there are any bugs, etc etc. :)
module Colored
attr_accessor :color
class Position
Alpha = 0
Red = 1
Green = 2
Blue = 3
end
def alpha
return unpack_color[Position::Alpha].to_i(16)
end
def red
return unpack_color[Position::Red].to_i(16)
end
def green
return unpack_color[Position::Green].to_i(16)
end
def blue
return unpack_color[Position::Blue].to_i(16)
end
def alpha=(value)
change_value(Position::Alpha, value)
end
def red=(value)
change_value(Position::Red, value)
end
def green=(value)
change_value(Position::Green, value)
end
def blue=(value)
change_value(Position::Blue, value)
end
private
def change_value(position, value)
if value.is_a?(String)
value = value.to_i(16)
end
if value.is_a?(Integer)
if (0..255).include? value
split_color = unpack_color
split_color[position] = value.to_s(16)
update_color(repack_color(split_color))
end
end
end
def unpack_color
return ("%08x" % @color).scan(/[0-9a-fA-F][0-9a-fA-F]/)
end
def repack_color(color)
return "0x" << color[0] << color[1] << color[2] << color[3]
end
def update_color(color)
@color = color.to_i(16)
end
end
The first thing I see is that you are passing ff as a string to Gosu::Color#alpha. Why not just do
Object.alpha = 0xff
instead? Then there's no need to convert the string to an integer on input. Also, 0xff == 255, so that should simplify things.
Also, why does #to_i take a parameter? At the very least, the parameter should default to 16. I would assume that most of the time someone would want to call #to_i they would be using #to_i(16). Just look at how many times you did so yourself.
That's my criticism for the time being.
I'm not passing "ff" myself, but it's there as a fallback to process a hex string. Personally I pass integers between 0-255 but other people may want to pass hex strings? If you pass 0xff, ruby will automatically turn it into 255 which is an Integer, and gets processed as such. The reason I have this is for versatility depending on how people want to use it in their own application. Ruby's integers default to base 10, hence the to_i(16). It's taking a string of hex code "ff" and it needs to convert it from base 16 to base 10.
irb(main):067:0> "ff".to_i
=> 0
irb(main):070:0> "ff".to_i(16)
=> 255
The problem I was having initially is that Gosu::Color has a color value of 0xAARRGGBB opposed to individual values for Alpha, Red, Green and Blue. 0xAA, 0xRR, 0xGG, 0xBB. So using what was available to me I process this compounded value as a string, split it, modify it, repack it, and then change the original value.
I'm in the process of revising this completely without any string manipulation, and will post when done :)
I would just keep it simple using a Gosu::Color object and delegation:
require 'forwardable'
module Colored
extend Forwardable
attr_accessor :color
def_delegators :@color, :red, :blue, :green, :alpha, :red=, :blue=, :green=, :alpha=
end
Job done!
I'm not really sure how to implement what you posted -- after looking into forwardable I wasn't sure how to make it meet my needs. I'm trying to keep an objects color value separate from a gosu object -- maybe this isn't the correct approach but my UI_Elements don't know anything about gosu, and I was trying to keep it as abstract as possible. For this reason each UI_Element has a @color ranging from 0x0 to 0xffffffff
After much mucking around today, hairs lost and hours of frustration later -- this is my new version of the module without string manipulation:
module Colored
class Position
Alpha = 0
Red = 1
Green = 2
Blue = 3
end
def color=(value)
if (0x0..0xffffffff).include? value
breakdown_color(value)
end
end
def alpha=(value)
change_value(Position::Alpha, value)
end
def red=(value)
change_value(Position::Red, value)
end
def green=(value)
change_value(Position::Green, value)
end
def blue=(value)
change_value(Position::Blue, value)
end
def color
return @color
end
def alpha
return @color_breakdown[Position::Alpha]
end
def red
return @color_breakdown[Position::Red]
end
def green
return @color_breakdown[Position::Green]
end
def blue
return @color_breakdown[Position::Blue]
end
private
def breakdown_color(value)
@color = value
remainder = value
alpha = 0
red = 0
green = 0
blue = 0
divmod = Array.new(1)
begin
case remainder
when (0x0..0xff)
blue = remainder
divmod[1] = 0
when (0x100..0xffff)
divmod = remainder.divmod(0x100)
green = divmod[0]
when (0x10000..0xffffff)
divmod = remainder.divmod(0x10000)
red = divmod[0]
when (0x1000000..0xffffffff)
divmod = remainder.divmod(0x1000000)
alpha = divmod[0]
end
remainder = divmod[1]
end until remainder == 0
@color_breakdown = Array[alpha, red, green, blue]
end
def change_value(position, value)
if (0x0..0xff).include? value
@color_breakdown[position] = value
update_color
end
end
def update_color
@color = (self.alpha * 0x1000000) +
(self.red * 0x10000) +
(self.green * 0x100) +
(self.blue)
end
end
What I gave was literally all you needed to do to have equivalent functionality to what you had written. You'd just need to ensure you did self.color = Color.rgba(123, 123, 13, 0)
or something in the constructor and you'd be able to access #color and directly affect #alpha, etc.
That said, I've never seen a reason to use the integer color in any of my games or libraries, since Gosu::Color is just so much more versatile and Rubified than a C-friendly integer (really, most of what Colored does is mimic the already existing Color class).
As an aside, since #divmod is just a way to get #div and #modulo at once, you can use:
red = remainder.div(0x10000)
instead of:
divmod = remainder.divmod(0x10000)
red = divmod[0]
I appreciate your replies and will continue to try and understand your point of view on this. Thanks :)
I didn't think Gosu::Color had a way to set values on ARGB, rather just read from them so I began working on this. But had I understood what you meant I probably could have saved a lot of trouble!
That would make sense, if it wasn't clear that Color had setters.
I appreciate it is hard to drop 50 lines of code you've worked hard on to use 5 lines of code that do the same thing. I maybe should properly explained how they worked rather than just plonking them down. I still find it hard to let go of code I've written and even after years of working in Ruby I learn something new every week that makes some part of my own work redundant!
I got it working with the code supplied, although now it's time for me to break everything down and learn its usefulness =) The one thing I was hoping to avoid was making a reference to Gosu from within my child classes, but that might not be possible/feasible. Thanks for your help!
with a simple addition to the code you supplied, I can now keep Gosu::Color out of my UI_Image
As before, UI_Image takes a 0xaarrbbgg integer on initialization, sets @color to it, but now also makes a call to init_color from the Colored module. Kind of messy? But it works =x
require 'forwardable'
module Colored
extend Forwardable
attr_accessor :color
def_delegators :@color, :red, :blue, :green, :alpha, :red=, :blue=, :green=, :alpha=
def init_color
self.color = Gosu::Color.argb(@color)
end
end
Ah, I thought you meant you wanted to keep Color out of your module, rather than to keep it hidden from the end-user.
Might I suggest:
attr_reader :color # Getter
def color=(color) # Setter
@color = color.is_a?(Gosu::Color) ? color.dup : Gosu::Color.argb(color)
end
By PhusionDev
Date 2012-03-07 13:54
Edited 2012-03-07 13:59
ah yes just looking at this.. it looks perfect for what I want :) many thanks! I think initially I was trying this but @color = value in my UI_Image wasn't calling Colored#color=(value) but I think it was a matter of scope at the time.
and I just found out why it didn't initially work when I tried it yesterday was because I was using @color = color in my UI_Image but I needed to use self.color = color to trigger off Colored#color=(value)
Well this was a great learning experience either way I look at it :D
Yes, needing to use self.value= is a bit annoying, and often trips me up. Glad you learned something new :)
Loading...