Robert Klemme
2/1/2008 10:56:00 AM
2008/1/31, Leif Eriksen <leif.eriksen@bigpond.com>:
> Some background - bear with me
>
> So I'm writing my first little library (module) in ruby, for a
> play-project in rails.
>
> The library is for color management related functions, and its first
> method is to
> provide a color 'gradient', which I use for images where the colors smoothly
> change from a to b in as many steps as I require
>
> for example, to get from html rgb color #010101 to #444444 in 5 'steps',
> we would do
>
> ColorManagement.gradient(#010101, #444444, 5) # will used named params
> in version 2!
>
> and it would return
>
> ["#010101", "#0e0e0e", "#1b1b1b", "#282828", "#353535", "#444444"]
>
> The method supports leading '0x' chars as well, and prepends the leading
> chars (if any) to the
> entries in the result array.
>
> And this is all cool, until I did this
>
> start = '#010101'
> finish = '#444444'
> ColorGradient.gradient(start, finish , 5)
> pp start "#{start}"
> pp "finish #{finish }"
>
> and I see this
>
> "start 010101"
> "finish 444444"
>
> The '#''s are gone. Internally the library is stripping these off to make
> splitting into RGB channels easier, and it does the stripping like this
>
> def self.gradient(hex_start="000000", hex_end="FFFFFF", steps=256)
> [hex_start,hex_end].each do |hex|
> hex.sub!(/^(0[xX]|#)/) do |match|
> match = '' # remove any leading # or Ox
>
> So the strings that are passed in are permanently munged by the sub! .
> OK I get that, and to solve it I did this
>
> def self.gradient(param_start="000000", param_end="FFFFFF", steps=256)
> #make local copies of parameters
> hex_start = String.new(param_start)
> hex_end = String.new(param_end)
>
> [hex_start,hex_end].each do |hex|
> hex.sub!(/^(0[xX]|#)/) do |match|
> ...
>
> So in effect I copy the parameter strings into local vars, to avoid
> munging what the user passes me and annoying them.
>
> But is this the ruby way ? Is this the idiomatic way to avoid
> side-effects on objects passed in as parameters ?
I'd say, this is generally considered bad OO. A better solution would
be to create a class Color that provides methods to do all the
conversions. E.g.
Color = Struct.new :red, :green, :blue do
# will accept either
# a single string with hex number
# a single fixnum (0x000000 - 0xFFFFFF, larger values are truncated)
# three fixnums
# three strings
def initialize(*a)
case a.length
when 3
self.red,
self.green,
self.blue = a.map {|v| arg2bin(v) % 0x100}
when 1
tmp = arg2bin a.first
raise ArgumentError, "Negative value" if tmp < 0
self.red = (tmp >> 16) % 0x100
self.green = (tmp >> 8) % 0x100
self.blue = tmp % 0x100
else
raise ArgumentError,
"Need either a single string with hex number, a single
Fixnum or three separate Fixnums or hex Strings"
end
end
def to_hex
sprintf "%02x%02x%02x", red, green, blue
end
def to_s
sprintf "#%02x%02x%02x", red, green, blue
end
def &(color)
Color.new red & color.red,
green & color.green,
blue & color.blue
end
def |(color)
Color.new red | color.red,
green | color.green,
blue | color.blue
end
private
def arg2bin(a)
case a
when Fixnum
a
when String
a.to_i 16
end
end
end
This is just a sample implementation. You might as well change
internal representation to a single Fixnum etc. Then add methods as
you need them.
Kind regards
robert
--
use.inject do |as, often| as.you_can - without end