Skip to content
Newer
Older
100644 290 lines (227 sloc) 9.14 KB
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
1 # This routine needs the color_histogram method
2 # from either ImageMagick 6.0.0 or GraphicsMagick 1.1
3 # Specify an image filename as an argument.
224c390 Remove shebang line
rmagick authored
4
5 require 'RMagick'
6
7 module Magick
8 class Image
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
9
cc1e285 Rewritten and much expanded.
rmagick authored
10 private
224c390 Remove shebang line
rmagick authored
11 HISTOGRAM_COLS = 256
12 HISTOGRAM_ROWS = 200
cc1e285 Rewritten and much expanded.
rmagick authored
13 AIR_FACTOR = 1.025
224c390 Remove shebang line
rmagick authored
14
15 # Given a channel name and the frequency data for the
cc1e285 Rewritten and much expanded.
rmagick authored
16 # channel, draw a histogram for the specified channel.
17 def channel_histogram(color, label, freqs, scale, fg, bg)
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
18
cc1e285 Rewritten and much expanded.
rmagick authored
19 # Make a blank image to draw on. Calls AllocateImage.
224c390 Remove shebang line
rmagick authored
20 histogram = Image.new(HISTOGRAM_COLS, HISTOGRAM_ROWS) {
21 self.background_color = bg
22 self.border_color = fg
23 }
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
24
224c390 Remove shebang line
rmagick authored
25 gc = Draw.new
26 gc.stroke(color)
27 gc.stroke_width(1)
cc1e285 Rewritten and much expanded.
rmagick authored
28 gc.affine(1, 0, 0, -scale, 0, HISTOGRAM_ROWS)
224c390 Remove shebang line
rmagick authored
29
30 HISTOGRAM_COLS.times do |x|
cc1e285 Rewritten and much expanded.
rmagick authored
31 gc.line(x, 0, x, freqs[x])
224c390 Remove shebang line
rmagick authored
32 end
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
33
cc1e285 Rewritten and much expanded.
rmagick authored
34 gc.draw(histogram)
35 histogram['Label'] = label
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
36
cc1e285 Rewritten and much expanded.
rmagick authored
37 return histogram
38 end
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
39
cc1e285 Rewritten and much expanded.
rmagick authored
40 # The alpha frequencies are shown as white dots.
41 def alpha_histogram(freqs, scale, fg, bg)
42 histogram = Image.new(HISTOGRAM_COLS, HISTOGRAM_ROWS) {
43 self.background_color = bg
44 self.border_color = fg
45 }
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
46
cc1e285 Rewritten and much expanded.
rmagick authored
47 gc = Draw.new
48 gc.affine(1, 0, 0, -scale, 0, HISTOGRAM_ROWS)
49 gc.fill('white')
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
50
cc1e285 Rewritten and much expanded.
rmagick authored
51 HISTOGRAM_COLS.times do |x|
52 gc.point(x, freqs[x])
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
53 end
54
224c390 Remove shebang line
rmagick authored
55 gc.draw(histogram)
cc1e285 Rewritten and much expanded.
rmagick authored
56 histogram['Label'] = 'Alpha'
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
57
224c390 Remove shebang line
rmagick authored
58 return histogram
59 end
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
60
61 # The RGB histogram must be drawn a pixel at a time. Of course
62 # we could've drawn all the histograms this way, but where's
63 # the fun in that?
64 def rgb_histogram(red, green, blue, scale, fg, bg)
65 histogram = Image.new(HISTOGRAM_COLS, HISTOGRAM_ROWS) {
66 self.background_color = bg
67 self.border_color = fg
68 }
69
70 HISTOGRAM_COLS.times do |x|
71 column = histogram.get_pixels(x, 0, 1, HISTOGRAM_ROWS-1)
72 column.each_with_index do |p, n|
73 p.red = MaxRGB if n >= HISTOGRAM_ROWS - (red[x] * scale)
74 p.green = MaxRGB if n >= HISTOGRAM_ROWS - (green[x] * scale)
75 p.blue = MaxRGB if n >= HISTOGRAM_ROWS - (blue[x] * scale)
76 end
77 histogram.store_pixels(x, 0, 1, HISTOGRAM_ROWS-1, column)
78 end
79
80 histogram['Label'] = 'RGB'
81 return histogram
82 end
83
cc1e285 Rewritten and much expanded.
rmagick authored
84 # Use AnnotateImage to write the stats.
85 def info_text(fg, bg)
86 klass = class_type == DirectClass ? "DirectClass" : "PsuedoClass"
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
87
cc1e285 Rewritten and much expanded.
rmagick authored
88 text = <<-END_TEXT
89 Format: #{format}
90 Geometry: #{columns}x#{rows}
91 Class: #{klass}
92 Depth: #{depth} bits-per-pixel component
93 Colors: #{number_colors}
94 END_TEXT
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
95
cc1e285 Rewritten and much expanded.
rmagick authored
96 info = Image.new(HISTOGRAM_COLS, HISTOGRAM_ROWS) {
97 self.background_color = bg
98 self.border_color = fg
99 }
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
100
cc1e285 Rewritten and much expanded.
rmagick authored
101 gc = Draw.new
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
102
cc1e285 Rewritten and much expanded.
rmagick authored
103 gc.annotate(info, 0, 0, 0, 0, text) {
104 self.stroke = 'transparent'
105 self.fill = fg
106 self.gravity = CenterGravity
107 }
108 info['Label'] = 'Info'
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
109
cc1e285 Rewritten and much expanded.
rmagick authored
110 return info
111 end
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
112
cc1e285 Rewritten and much expanded.
rmagick authored
113 # The intensity histogram is drawn as a gradient. Draw the histogram using
114 # white lines, then call TransparentImage to make the white lines transparent,
115 # then composite the histogram over a gradient background.
116 def intensity_histogram(freqs, scale, fg, bg)
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
117
cc1e285 Rewritten and much expanded.
rmagick authored
118 histogram = channel_histogram('white', 'Intensity', freqs, scale, fg, bg)
119 histogram = histogram.transparent('white')
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
120
cc1e285 Rewritten and much expanded.
rmagick authored
121 gradient = (Image.read("gradient:#ffff80-#ff9000") { self.size="#{HISTOGRAM_COLS}x#{HISTOGRAM_ROWS}" }).first
122 histogram = gradient.composite(histogram, CenterGravity, OverCompositeOp)
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
123
cc1e285 Rewritten and much expanded.
rmagick authored
124 histogram['Label'] = 'Intensity'
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
125
cc1e285 Rewritten and much expanded.
rmagick authored
126 return histogram
127 end
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
128
cc1e285 Rewritten and much expanded.
rmagick authored
129 # Returns a value between 0 and 255. Same as the PixelIntensity macro.
130 def pixel_intensity(pixel)
131 (306*pixel.red + 601*pixel.green + 117*pixel.blue)/1024
132 end
224c390 Remove shebang line
rmagick authored
133
cc1e285 Rewritten and much expanded.
rmagick authored
134 # Make the color histogram. Quantize the image to 256 colors if necessary.
135 def color_freq(fg, bg)
136 img = number_colors > 256 ? quantize(256) : self
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
137
cc1e285 Rewritten and much expanded.
rmagick authored
138 histogram = Image.new(HISTOGRAM_COLS, HISTOGRAM_ROWS) {
139 self.background_color = bg
140 self.border_color = fg
141 }
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
142
25bd366 Be helpful: write a descriptive message to stdout when invoked.
rmagick authored
143 begin
144 hist = img.color_histogram
145 pixels = hist.keys.sort_by {|pixel| hist[pixel] }
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
146
25bd366 Be helpful: write a descriptive message to stdout when invoked.
rmagick authored
147 scale = HISTOGRAM_ROWS / (hist.values.max*AIR_FACTOR)
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
148
25bd366 Be helpful: write a descriptive message to stdout when invoked.
rmagick authored
149 gc = Draw.new
150 gc.affine(1, 0, 0, -scale, 0, HISTOGRAM_ROWS)
151 width = 256.0/img.number_colors
152 gc.stroke_width(width.to_i)
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
153
25bd366 Be helpful: write a descriptive message to stdout when invoked.
rmagick authored
154 start = 256 - img.number_colors
155 pixels.each { |pixel|
156 gc.stroke(pixel.to_color)
157 gc.line(start, 0, start, hist[pixel])
158 start += width
159 }
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
160
25bd366 Be helpful: write a descriptive message to stdout when invoked.
rmagick authored
161 gc.draw(histogram)
162 rescue NotImplementedError
163 $stderr.puts "The color_histogram method is not supported by this version "+
164 "of ImageMagick/GraphicsMagick"
165 end
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
166
167 histogram['Label'] = 'Color Frequency'
168 return histogram
cc1e285 Rewritten and much expanded.
rmagick authored
169 end
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
170
171 public
cc1e285 Rewritten and much expanded.
rmagick authored
172 # Create the histogram montage.
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
173 def histogram(fg='white', bg='black')
174
224c390 Remove shebang line
rmagick authored
175 red = Array.new(HISTOGRAM_COLS, 0)
176 green = Array.new(HISTOGRAM_COLS, 0)
177 blue = Array.new(HISTOGRAM_COLS, 0)
cc1e285 Rewritten and much expanded.
rmagick authored
178 alpha = Array.new(HISTOGRAM_COLS, 0)
179 int = Array.new(HISTOGRAM_COLS, 0)
224c390 Remove shebang line
rmagick authored
180
181 rows.times do |row|
182 pixels = get_pixels(0, row, columns, 1)
183 pixels.each do |pixel|
cc1e285 Rewritten and much expanded.
rmagick authored
184 red[pixel.red] = red[pixel.red].succ
185 green[pixel.green] = green[pixel.green].succ
186 blue[pixel.blue] = blue[pixel.blue].succ
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
187
cc1e285 Rewritten and much expanded.
rmagick authored
188 # Only count opacity channel if some pixels are not opaque.
189 if !opaque?
190 alpha[pixel.opacity] = alpha[pixel.opacity].succ
191 end
192 v = pixel_intensity(pixel)
193 int[v] = int[v].succ
224c390 Remove shebang line
rmagick authored
194 end
195 end
196
cc1e285 Rewritten and much expanded.
rmagick authored
197 # Scale to chart size. When computing the scale, add some "air" between
198 # the max frequency and the top of the histogram. This makes a prettier chart.
199 # The RGBA and intensity histograms are all drawn to the same scale.
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
200 max = [red.max, green.max, blue.max, alpha.max, int.max].max
cc1e285 Rewritten and much expanded.
rmagick authored
201 scale = HISTOGRAM_ROWS / (max*AIR_FACTOR)
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
202
224c390 Remove shebang line
rmagick authored
203 charts = ImageList.new
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
204
cc1e285 Rewritten and much expanded.
rmagick authored
205 # Add the thumbnail.
206 thumb = copy
207 thumb['Label'] = File.basename(filename)
208 charts << thumb
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
209
210 # Add the channel histograms
cc1e285 Rewritten and much expanded.
rmagick authored
211 charts << channel_histogram('red', 'Red', red, scale, fg, bg)
212 charts << channel_histogram('rgb(0,255,0)', 'Green', green, scale, fg, bg)
213 charts << channel_histogram('blue', 'Blue', blue, scale, fg, bg)
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
214
cc1e285 Rewritten and much expanded.
rmagick authored
215 # Add Alpha channel or image stats
216 if !opaque?
217 charts << alpha_histogram(alpha, scale, fg, bg)
218 else
219 charts << info_text(fg, bg)
220 end
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
221
222 charts << rgb_histogram(red, green, blue, scale, fg, bg)
223
cc1e285 Rewritten and much expanded.
rmagick authored
224 # Add the intensity histogram.
225 charts << intensity_histogram(int, scale, fg, bg)
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
226
cc1e285 Rewritten and much expanded.
rmagick authored
227 # Add the color frequency histogram.
228 charts << color_freq(fg, bg)
224c390 Remove shebang line
rmagick authored
229
230 # Make a montage.
231 histogram = charts.montage {
232 self.background_color = bg
cc1e285 Rewritten and much expanded.
rmagick authored
233 self.stroke = 'transparent'
234 self.fill = fg
224c390 Remove shebang line
rmagick authored
235 self.border_width = 1
cc1e285 Rewritten and much expanded.
rmagick authored
236 self.tile = "4x2"
237 self.geometry = "#{HISTOGRAM_COLS}x#{HISTOGRAM_ROWS}+10+10"
224c390 Remove shebang line
rmagick authored
238 }
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
239
224c390 Remove shebang line
rmagick authored
240 return histogram
241 end
242 end
243 end
244
25bd366 Be helpful: write a descriptive message to stdout when invoked.
rmagick authored
245 puts <<END_INFO
246
247 This example shows how to get pixel-level access to an image.
248 Usage: histogram.rb <image-filename>
249
250 END_INFO
cc1e285 Rewritten and much expanded.
rmagick authored
251
252 # Get filename from command line.
25bd366 Be helpful: write a descriptive message to stdout when invoked.
rmagick authored
253 if !ARGV[0] then
e2ae5fe Avoid using PlusCompositeOp, which doesn't produce the desired
rmagick authored
254 puts "No filename argument. Defaulting to Flower_Hat.jpg"
255 filename = '../doc/ex/images/Flower_Hat.jpg'
25bd366 Be helpful: write a descriptive message to stdout when invoked.
rmagick authored
256 else
257 filename = ARGV[0]
224c390 Remove shebang line
rmagick authored
258 end
259
cc1e285 Rewritten and much expanded.
rmagick authored
260 # Only process first frame if multi-frame image
25bd366 Be helpful: write a descriptive message to stdout when invoked.
rmagick authored
261 image = Magick::Image.read(filename)
224c390 Remove shebang line
rmagick authored
262 if image.length > 1
263 puts "Charting 1st image"
264 end
265 image = image.first
266
cc1e285 Rewritten and much expanded.
rmagick authored
267 # Give the user something to look at while we're working.
25bd366 Be helpful: write a descriptive message to stdout when invoked.
rmagick authored
268 name = File.basename(filename).sub(/\..*?$/,'')
224c390 Remove shebang line
rmagick authored
269 $defout.sync = true
cc1e285 Rewritten and much expanded.
rmagick authored
270 printf "Creating #{name}_Histogram.miff"
224c390 Remove shebang line
rmagick authored
271
272 timer = Thread.new do
273 loop do
274 sleep(1)
275 printf "."
276 end
277 end
278
cc1e285 Rewritten and much expanded.
rmagick authored
279 # Generate the histograms
224c390 Remove shebang line
rmagick authored
280 histogram = image.histogram
281
cc1e285 Rewritten and much expanded.
rmagick authored
282 # Write output file
283 histogram.compression = Magick::ZipCompression
284 histogram.write("./#{name}_Histogram.miff")
285
224c390 Remove shebang line
rmagick authored
286 Thread.kill(timer)
287 puts "Done!"
288 exit
cc1e285 Rewritten and much expanded.
rmagick authored
289
Something went wrong with that request. Please try again.