Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

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