class CTioga2::Graphics::Styles::ColorMap

A mapping Z values -> color.

It can be a simple two-point gradient, but it can also be much more complex.

Basically, a ColorMap is a series of colors with an optional Z value (taken as the average of the ones around if missing) + a color for above and a color for below.

@todo For now, ColorMap relies on the intrisic tioga color map, but it would be interesting to implement that “by hand” for the case when a byte of resolution isn't enough (which are going to be rare, I think)

Attributes

above[RW]

Colors for points of Z value below and above the limit; nil for no specific value, :mask for masking them out

@todo These are currently not implemented.

below[RW]

Colors for points of Z value below and above the limit; nil for no specific value, :mask for masking them out

@todo These are currently not implemented.

colors[RW]

Corresponding colors

rgb[RW]

Whether the map is interpolated in the RGB (true) or HLS (false) color space. On by default.

It does not change anything with respect to how the colors are interpreted: whatever happens, the values are RGB.

symmetry_center[RW]

Whether or not the map should be considered symmetric around a given value.

values[RW]

Z values

Public Class Methods

from_text(str) click to toggle source

Creates a ColorMap from a text specification of the kind:

Red--Blue(1.0)--Green
The specification can optionally be surrounded by colors with

Green::Red–Blue::Orange

Means that Green are for colors below, Orange for above. These colors can also be “cut” or “mask”, meaning that the corresponding side isn't displayed.

# File lib/ctioga2/graphics/styles/colormap.rb, line 83
def self.from_text(str)
  str = str.dup
  hls = false
  re = /(natural|hls|wheel):?/i     # Not too bad ?
  if str =~ re
    str.sub!(re,'')
    hls = true
  end

  sym = nil

  re = /:(?:sym|around):(.*)$/i
  if str =~ re
    sym = $1.to_f
    str.sub!(re,'')
  end

  # We first try to see if it could be a color set ?
  colorsettype = Commands::CommandType.get_type('color-set')

  begin 
    set = colorsettype.string_to_type(str)
    cm = ColorMap.new([nil] * set.size, set)
    cm.rgb = ! hls
    cm.symmetry_center = sym
    return cm
  rescue Exception => e
    # This is not a color set
  end

  l = str.split(/::/)
  

  if l.size == 2        # This is the complex case
    if l[1] =~ /--/
      l.push('')
    else
      l.unshift('')
    end
  elsif l.size == 1
    l.push('')
    l.unshift('')
  end

  ## @todo More and more I find that this metabuilder thing is
  ## a little cumbersome, especially since I have an
  ## additional type system on top of this one.
  colortype = Commands::CommandType.get_type('color')

  
  # Now, we have three elements
  if l[0].size > 0
    if l[0] =~ /mask|cut/i
      below = :mask
    else
      below = colortype.string_to_type(l[0])
    end
  else
    below = nil
  end

  if l[2].size > 0
    if l[2] =~ /mask|cut/i
      above = :mask
    else
      above = colortype.string_to_type(l[2])
    end
  else
    above = nil
  end

  specs = l[1].split(/--/)

  values = []
  colors = []
  for s in specs
    if s =~ /([^(]+)\((.*)\)/
      values << $2.to_f
      colors << colortype.string_to_type($1)
    else
      values << nil
      colors << colortype.string_to_type(s)
    end
  end
  cm = ColorMap.new(values, colors)
  cm.above = above
  cm.below = below
  cm.rgb = ! hls
  cm.symmetry_center = sym
  return cm
end
new(values = [], colors = []) click to toggle source
# File lib/ctioga2/graphics/styles/colormap.rb, line 63
def initialize(values = [], colors = [])
  @values = values.dup
  @colors = colors.dup

  @symmetry_center = nil

  @rgb = true
end

Public Instance Methods

prepare_data_display(t, data, zmin, zmax) click to toggle source

Prepares the 'data', 'colormap' and 'value_mask' arguments to t.create_image based on the given data, and the min and max Z levels

@todo handle masking + in and out of range.

@todo I don't think this function is named properly.

# File lib/ctioga2/graphics/styles/colormap.rb, line 183
def prepare_data_display(t, data, zmin, zmax)
  # We correct zmin and zmax
  cmap, zmin, zmax = *self.to_colormap(t, zmin, zmax)
  
  data = t.create_image_data(data.reverse_rows,
                             'min_value' => zmin,
                             'max_value' => zmax)
  
  return { 'data' => data,
    'colormap' => cmap
  }
end
to_colormap(t, zmin, zmax) click to toggle source

Converts to a Tioga color_map

@todo That won't work when there are things inside/outside of the map.

# File lib/ctioga2/graphics/styles/colormap.rb, line 219
def to_colormap(t, zmin, zmax)

  # OK. Now, we have correct z values. We just need to scale
  # them between z_values[0] and z_values.last, to get a [0:1]
  # interval.
  zvs = z_values(zmin, zmax)
  p_values = zvs.dup
  p_values.sub!(p_values.first)
  p_values.div!(p_values.last)
  
  dict = {
    'points' => p_values
  }
  if @rgb
    dict['Rs'] = []
    dict['Gs'] = []
    dict['Bs'] = []
    for col in @colors
      dict['Rs'] << col[0]
      dict['Gs'] << col[1]
      dict['Bs'] << col[2]
    end
  else
    dict['Hs'] = []
    dict['Ls'] = []
    dict['Ss'] = []
    for col in @colors
      col = t.rgb_to_hls(col)
      dict['Hs'] << col[0]
      dict['Ls'] << col[1]
      dict['Ss'] << col[2]
    end
  end
  return [t.create_colormap(dict), zvs.first, zvs.last]
end
z_color(z, zmin, zmax) click to toggle source

Returns a color triplet corresponding to the given z value

@todo For now, the HSV parameter isn't honored.

# File lib/ctioga2/graphics/styles/colormap.rb, line 199
def z_color(z, zmin, zmax)
  zvs = z_values(zmin, zmax)
  
  idx = zvs.where_first_ge(z)
  if idx && idx > 0
    x = (zvs[idx] - z)/(zvs[idx] - zvs[idx-1])
    c = Utils::mix_objects(@colors[idx-1],@colors[idx], x)
    # p [c, idx, z, zmin, zmax]
    return c
  elsif idx == 0
    return @colors.first
  else
    return @colors.last
  end
end

Protected Instance Methods

z_values(zmin, zmax) click to toggle source

Returns a Dvector holding z values corresponding to each of the color.

@todo This function will be called very often and is not very efficient; there should be a way to cache the results, either implicitly using a realy cache or explicitly by “instantiating” the colormap for given values of zmin and zmax.

@todo This function doesn't ensure that the resulting z values are monotonic, which isn't quite that good.

# File lib/ctioga2/graphics/styles/colormap.rb, line 269
def z_values(zmin, zmax)
  # Real Z values.
  z_values = @values.dup

  # We tweak the zmin and zmax to be symmetric around the
  # symmetry_center should that be needed.
  if @symmetry_center
    dmin = (zmin - @symmetry_center).abs
    dmax = (zmax - @symmetry_center).abs
    
    dfin = [dmin, dmax].max
    zmin = @symmetry_center - dfin
    zmax = @symmetry_center + dfin
    
  end

  z_values[0] ||= zmin
  z_values[-1] ||= zmax

  # Now, we replace all the nil values by the correct position
  # (the middle or both around when only one _nil_ is found,
  # 1/3 2/3 for 2 consecutive _nil_ values, and so on).
  last_value = 0
  1.upto(z_values.size-1) do |i|
    if z_values[i]
      if last_value + 1 < i
        (last_value+1).upto(i - 1) do |j|
          frac = (j - last_value )/(i - last_value*1.0)
          z_values[j] = z_values[last_value] * (1 - frac) + 
            z_values[i] * frac
        end
      end
      last_value = i
    end
  end
  return Dobjects::Dvector[*z_values]
end