~ubuntu-branches/ubuntu/oneiric/ctioga2/oneiric

« back to all changes in this revision

Viewing changes to lib/ctioga2/graphics/styles/colormap.rb

  • Committer: Bazaar Package Importer
  • Author(s): Vincent Fourmond
  • Date: 2011-01-24 21:36:06 UTC
  • Revision ID: james.westby@ubuntu.com-20110124213606-9ettx0ugl83z0bzp
Tags: upstream-0.1
ImportĀ upstreamĀ versionĀ 0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# colormap.rb: a way to map values to colors
 
2
# copyright (c) 2009 by Vincent Fourmond
 
3
  
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation; either version 2 of the License, or
 
7
# (at your option) any later version.
 
8
  
 
9
# This program is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU General Public License for more details (in the COPYING file).
 
13
 
 
14
require 'ctioga2/utils'
 
15
require 'ctioga2/log'
 
16
 
 
17
# This module contains all the classes used by ctioga
 
18
module CTioga2
 
19
 
 
20
  Version::register_svn_info('$Revision: 199 $', '$Date: 2010-11-30 00:48:26 +0100 (Tue, 30 Nov 2010) $')
 
21
 
 
22
  module Graphics
 
23
 
 
24
    module Styles
 
25
 
 
26
 
 
27
      # A mapping Z values -> color.
 
28
      #
 
29
      # It can be a simple two-point gradient, but it can also be much
 
30
      # more complex.
 
31
      #
 
32
      # Basically, a ColorMap is a series of colors with an optional Z
 
33
      # value (taken as the average of the ones around if missing) + a
 
34
      # color for above and a color for below.
 
35
      #
 
36
      # @todo For now, ColorMap relies on the intrisic tioga color
 
37
      # map, but it would be interesting to implement that "by hand"
 
38
      # for the case when a byte of resolution isn't enough (which are
 
39
      # going to be rare, I think)
 
40
      class ColorMap
 
41
 
 
42
        # Z values
 
43
        attr_accessor :values
 
44
 
 
45
        # Corresponding colors
 
46
        attr_accessor :colors
 
47
 
 
48
        # Colors for points of Z value below and above the limit;
 
49
        # _nil_ for no specific value, :mask for masking them out
 
50
        #
 
51
        # @todo These are currently not implemented.
 
52
        attr_accessor :below, :above
 
53
 
 
54
        # Whether the map follows RGB (true) or HLS (false). On by
 
55
        # default.
 
56
        #
 
57
        # It does not change anything with respect to how the colors
 
58
        # are interpreted: whatever happens, the values are RGB.
 
59
        attr_accessor :rgb
 
60
 
 
61
        def initialize(values = [], colors = [])
 
62
          @values = values.dup
 
63
          @colors = colors.dup
 
64
 
 
65
          @rgb = true
 
66
        end
 
67
        
 
68
        # Creates a ColorMap from a text specification of the kind:
 
69
        # 
 
70
        #  Red--Blue(1.0)--Green
 
71
        #  
 
72
        # The specification can optionally be surrounded by colors with ::
 
73
        # 
 
74
        #  Green::Red--Blue::Orange
 
75
        #  
 
76
        # Means that Green are for colors below, Orange for
 
77
        # above. These colors can also be "cut" or "mask", meaning
 
78
        # that the corresponding side isn't displayed.
 
79
        def self.from_text(str)
 
80
          str = str.dup
 
81
          hls = false
 
82
          re = /natural:?/i     # Not too bad ?
 
83
          if str =~ re
 
84
            str.sub!(re,'')
 
85
            hls = true
 
86
          end
 
87
 
 
88
          l = str.split(/::/)
 
89
          
 
90
 
 
91
          if l.size == 2        # This is the complex case
 
92
            if l[1] =~ /--/
 
93
              l.push('')
 
94
            else
 
95
              l.unshift('')
 
96
            end
 
97
          elsif l.size == 1
 
98
            l.push('')
 
99
            l.unshift('')
 
100
          end
 
101
 
 
102
          ## @todo More and more I find that this metabuilder thing is
 
103
          ## a little cumbersome, especially since I have an
 
104
          ## additional type system on top of this one.
 
105
          colortype = Commands::CommandType.get_type('color')
 
106
 
 
107
          
 
108
          # Now, we have three elements
 
109
          if l[0].size > 0
 
110
            if l[0] =~ /mask|cut/i
 
111
              below = :mask
 
112
            else
 
113
              below = colortype.string_to_type(l[0])
 
114
            end
 
115
          else
 
116
            below = nil
 
117
          end
 
118
 
 
119
          if l[2].size > 0
 
120
            if l[2] =~ /mask|cut/i
 
121
              above = :mask
 
122
            else
 
123
              above = colortype.string_to_type(l[2])
 
124
            end
 
125
          else
 
126
            above = nil
 
127
          end
 
128
 
 
129
          specs = l[1].split(/--/)
 
130
 
 
131
          values = []
 
132
          colors = []
 
133
          for s in specs
 
134
            if s =~ /([^(]+)\((.*)\)/
 
135
              values << $2.to_f
 
136
              colors << colortype.string_to_type($1)
 
137
            else
 
138
              values << nil
 
139
              colors << colortype.string_to_type(s)
 
140
            end
 
141
          end
 
142
          cm = ColorMap.new(values, colors)
 
143
          cm.above = above
 
144
          cm.below = below
 
145
          cm.rgb = ! hls
 
146
          return cm
 
147
        end
 
148
 
 
149
 
 
150
        # Prepares the 'data', 'colormap' and 'value_mask' arguments
 
151
        # to t.create_image based on the given data, and the min and
 
152
        # max Z levels
 
153
        #
 
154
        # @todo handle masking + in and out of range.
 
155
        #
 
156
        # @todo I don't think this function is named properly.
 
157
        def prepare_data_display(t, data, zmin, zmax)
 
158
          # We correct zmin and zmax
 
159
          cmap, zmin, zmax = *self.to_colormap(t, zmin, zmax)
 
160
          
 
161
          data = t.create_image_data(data.reverse_rows,
 
162
                                     'min_value' => zmin,
 
163
                                     'max_value' => zmax)
 
164
          
 
165
          return { 'data' => data,
 
166
            'colormap' => cmap
 
167
          }
 
168
        end
 
169
 
 
170
        # Returns a color triplet corresponding to the given z value
 
171
        #
 
172
        # @todo For now, the HSV parameter isn't honored.
 
173
        def z_color(z, zmin, zmax)
 
174
          zvs = z_values(zmin, zmax)
 
175
          
 
176
          idx = zvs.where_first_ge(z)
 
177
          if idx && idx > 0
 
178
            x = (zvs[idx] - z)/(zvs[idx] - zvs[idx-1])
 
179
            c = Utils::mix_objects(@colors[idx-1],@colors[idx], x)
 
180
            # p [c, idx, z, zmin, zmax]
 
181
            return c
 
182
          elsif idx == 0
 
183
            return @colors.first
 
184
          else
 
185
            return @colors.last
 
186
          end
 
187
        end
 
188
 
 
189
        # Converts to a Tioga color_map
 
190
        #
 
191
        # @todo That won't work when there are things inside/outside
 
192
        # of the map.
 
193
        def to_colormap(t, zmin, zmax)
 
194
 
 
195
          # OK. Now, we have correct z values. We just need to scale
 
196
          # them between z_values[0] and z_values.last, to get a [0:1]
 
197
          # interval.
 
198
          zvs = z_values(zmin, zmax)
 
199
          p_values = zvs.dup
 
200
          p_values.sub!(p_values.first)
 
201
          p_values.div!(p_values.last)
 
202
          
 
203
          dict = {
 
204
            'points' => p_values
 
205
          }
 
206
          if @rgb
 
207
            dict['Rs'] = []
 
208
            dict['Gs'] = []
 
209
            dict['Bs'] = []
 
210
            for col in @colors
 
211
              dict['Rs'] << col[0]
 
212
              dict['Gs'] << col[1]
 
213
              dict['Bs'] << col[2]
 
214
            end
 
215
          else
 
216
            dict['Hs'] = []
 
217
            dict['Ls'] = []
 
218
            dict['Ss'] = []
 
219
            for col in @colors
 
220
              col = t.rgb_to_hls(col)
 
221
              dict['Hs'] << col[0]
 
222
              dict['Ls'] << col[1]
 
223
              dict['Ss'] << col[2]
 
224
            end
 
225
          end
 
226
          return [t.create_colormap(dict), zvs.first, zvs.last]
 
227
        end
 
228
 
 
229
        protected
 
230
 
 
231
        # Returns a Dvector holding z values corresponding to each of
 
232
        # the color.
 
233
        #
 
234
        # @todo This function will be called very often and is not
 
235
        # very efficient; there should be a way to cache the results,
 
236
        # either implicitly using a realy cache or explicitly by
 
237
        # "instantiating" the colormap for given values of zmin and
 
238
        # zmax.
 
239
        #
 
240
        # @todo This function doesn't ensure that the resulting z
 
241
        # values are monotonic, which isn't quite that good.
 
242
        def z_values(zmin, zmax)
 
243
          # Real Z values.
 
244
          z_values = @values.dup
 
245
          z_values[0] ||= zmin
 
246
          z_values[-1] ||= zmax
 
247
 
 
248
          # Now, we replace all the nil values by the correct position
 
249
          # (the middle or both around when only one _nil_ is found,
 
250
          # 1/3 2/3 for 2 consecutive _nil_ values, and so on).
 
251
          last_value = 0
 
252
          1.upto(z_values.size-1) do |i|
 
253
            if z_values[i]
 
254
              if last_value + 1 < i
 
255
                (last_value+1).upto(i - 1) do |j|
 
256
                  frac = (j - last_value)/(i - last_value + 1.0)
 
257
                  p [last_value, j, i, frac]
 
258
                  z_values[j] = z_values[last_value] * frac + 
 
259
                    z_values[i] * (1 - frac)
 
260
                end
 
261
              end
 
262
              last_value = i
 
263
            end
 
264
          end
 
265
          return Dobjects::Dvector[*z_values]
 
266
        end
 
267
 
 
268
      end
 
269
 
 
270
    end
 
271
  end
 
272
end