Anonymous user
Raster graphics operations/Ruby: Difference between revisions
→The Code
Thundergnat (talk | contribs) m (Avoid counting non tasks as task entries) |
|||
Line 3:
Collecting all the Ruby code from [[:Category:Raster graphics operations]], so one can invoke: <code>require 'raster_graphics'</code>
Uses the [https://github.com/wvanbergen/chunky_png ChunkyPNG] pure-Ruby PNG library. Important to note won't work with
`frozen_string_literal: true` uses mutable String operations.
<lang ruby>###########################################################################
# frozen_string_literal: false
# Represents an RGB[http://en.wikipedia.org/wiki/Rgb] colour. ▼
###########################################################################
class RGBColour
# Red, green and blue values must fall in the range 0..255.
def initialize(red, green, blue)
ok = [red, green, blue].inject(true) { |ok, c| ok &= c.between?(0, 255) }
▲ raise ArgumentError, "invalid RGB parameters: #{[red, green, blue].inspect}"
@blue = blue
end
attr_reader :red, :green, :blue
# Return the list of [red, green, blue] values.
Line 44 ⟶ 49:
#
def <=>(a_colour)
end
Line 53 ⟶ 58:
#
def luminosity
Integer(0.2126 * @red + 0.7152 * @green + 0.0722 * @blue)
end
Line 70 ⟶ 75:
# method.
def self.mandel_colour(i)
end
RED = RGBColour.new(255, 0, 0)
GREEN = RGBColour.new(0, 255, 0)
BLUE = RGBColour.new(0, 0, 255)
YELLOW = RGBColour.new(255, 255, 0)
BLACK = RGBColour.new(0, 0, 0)
WHITE = RGBColour.new(255, 255, 255)
end
Line 95 ⟶ 100:
def fill(colour)
@data = Array.new(@width) { Array.new(@height, colour) }
end
def validate_pixel(x, y)
unless x.between?(0, @width - 1)
raise ArgumentError, "requested pixel (#{x}, #{y}) is outside dimensions of this bitmap"
end
Line 105 ⟶ 110:
###############################################
def [](x, y)
validate_pixel(x, y)
@data[x][y]
end
def []=(x, y, colour)
validate_pixel(x, y)
@data[x][y] = colour
end
def each_pixel
if block_given?
@height.times { |y| @width.times { |x| yield x, y } }
else
to_enum(:each_pixel)
Line 127 ⟶ 132:
###############################################
# write to file/stream
PIXMAP_FORMATS = %w[
PIXMAP_BINARY_FORMATS = [
def write_ppm(ios, format =
end▼
▲ ios.puts format, "#{@width} #{@height}", "255"
ios.binmode if PIXMAP_BINARY_FORMATS.include?(format)
each_pixel do |x, y|
case format
when
when
end
end
end
def save(filename, opts = { format:
File.open(filename, 'w') do |f|
write_ppm(f, opts[:format])
end
end
def print(opts = { format:
write_ppm($stdout, opts[:format])
end
def save_as_jpeg(filename, quality = 75)
# using the ImageMagick convert tool
begin
▲ pipe = IO.popen("convert ppm:- -quality #{quality} jpg:#{filename}", 'w')
rescue StandardError
▲ write_ppm(pipe)
▲ rescue SystemCallError => e
▲ warn "problem writing data to 'convert' utility -- does it exist in your $PATH?"
▲ ensure
▲ pipe.close rescue false
end
end
Line 169 ⟶ 176:
def save_as_png(filename)
require 'chunky_png'
stream = StringIO.new(
each_pixel { |x, y| stream << self[x, y].values.pack(
stream.seek(0)
ChunkyPNG::Canvas.extend(ChunkyPNG::Canvas::StreamImporting)
Line 181 ⟶ 188:
def self.read_ppm(ios)
format = ios.gets.chomp
width, height = ios.gets.chomp.split.map
max_colour = ios.gets.chomp
if
ios.close
raise StandardError, "file '#{filename}' does not start with the expected header"
Line 193 ⟶ 199:
ios.binmode if PIXMAP_BINARY_FORMATS.include?(format)
bitmap =
bitmap.each_pixel do |x, y|
# read 3 bytes
red, green, blue = case format
when 'P3' then ios.gets.chomp.split
when 'P6' then ios.read(3).unpack('C3')
end
bitmap[x, y] = RGBColour.new(red, green, blue)
end
ios.close
Line 211 ⟶ 217:
def self.open_from_jpeg(filename)
raise ArgumentError, "#{filename} does not exists or is not readable." unless File.readable?(filename)
begin
pipe = IO.popen("convert jpg:#{filename} ppm:-", 'r')
Line 220 ⟶ 225:
warn "problem reading data from 'convert' utility -- does it exist in your $PATH?"
ensure
pipe.close
rescue StandardError
false
▲ end
end
end
Line 228 ⟶ 237:
def to_grayscale
gray = self.class.new(@width, @height)
each_pixel do |x, y|
gray[x, y] = self[x, y].to_grayscale
end
gray
Line 250 ⟶ 259:
# create the black and white image
bw = self.class.new(@width, @height)
each_pixel do |x, y|
bw[x, y] = self[x, y].luminosity < median ? RGBColour::BLACK : RGBColour::WHITE
end
bw
Line 265 ⟶ 274:
validate_pixel(p2.x, p2.y)
x1
x2 = p2.x
y2 = p2.y
steep = (y2 - y1).abs > (x2 - x1).abs
if steep
Line 282 ⟶ 293:
error = deltax / 2
ystep = y1 < y2 ? 1 : -1
y = y1
x1.upto(x2) do |x|
pixel = steep ? [y, x] : [x, y]
self[*pixel] = colour
error -= deltay
if error
y += ystep
error += deltax
Line 297 ⟶ 308:
###############################################
def draw_line_antialised(p1, p2, colour)
x1
x2 = p2.x
y2 = p2.y
steep = (y2 - y1).abs > (x2 - x1).abs
if steep
Line 312 ⟶ 325:
deltay = (y2 - y1).abs
gradient = 1.0 * deltay / deltax
# handle the first endpoint
xend = x1.round
Line 322 ⟶ 335:
put_colour(xpxl1, ypxl1 + 1, colour, steep, yend.fpart * xgap)
itery = yend + gradient
# handle the second endpoint
xend = x2.round
Line 331 ⟶ 344:
put_colour(xpxl2, ypxl2, colour, steep, yend.rfpart * xgap)
put_colour(xpxl2, ypxl2 + 1, colour, steep, yend.fpart * xgap)
# in between
(xpxl1 + 1).upto(xpxl2 - 1).each do |x|
put_colour(x, itery.truncate, colour, steep, itery.rfpart)
put_colour(x, itery.truncate + 1, colour, steep, itery.fpart)
end
end
Line 346 ⟶ 359:
def anti_alias(new, old, ratio)
blended = new.values.zip(old.values).map { |n, o| (n * ratio + o * (1.0 - ratio)).round }
RGBColour.new(*blended)
end
Line 353 ⟶ 366:
def draw_circle(pixel, radius, colour)
validate_pixel(pixel.x, pixel.y)
self[pixel.x, pixel.y + radius] = colour
self[pixel.x, pixel.y - radius] = colour
self[pixel.x + radius, pixel.y] = colour
self[pixel.x - radius, pixel.y] = colour
f = 1 - radius
ddF_x = 1
Line 391 ⟶ 404:
until queue.empty?
p = queue.dequeue
east =
draw_line(west, east,
while q.x <= east.x
%i[north south].each do
n =
queue.enqueue(n) if self[n.x, n.y] == current_colour
▲ end
q = neighbour(q, :east)▼
end
end
end
Line 426 ⟶ 439:
###############################################
def median_filter(radius = 3)
radius += 1 if radius.even?
filtered = self.class.new(@width, @height)
$stdout.puts "processing #{@height} rows"
Line 441 ⟶ 451:
(x - radius).upto(x + radius).each do |win_x|
(y - radius).upto(y + radius).each do |win_y|
win_x = 0 if win_x
win_y = 0 if win_y
win_x = @width - 1 if win_x >= @width
win_y = @height - 1 if win_y >= @height
window << self[win_x, win_y]
end
Line 462 ⟶ 472:
def magnify(factor)
bigger = self.class.new(@width * factor, @height * factor)
each_pixel do |x, y|
colour = self[x, y]
(x *
(y *
bigger[xx, yy] = colour
end
end
Line 476 ⟶ 486:
def histogram
histogram = Hash.new(0)
each_pixel do |x, y|
histogram[self[x, y].luminosity] += 1
end
histogram
end
Line 485 ⟶ 495:
def draw_bezier_curve(points, colour)
# ensure the points are increasing along the x-axis
points = points.sort_by { |p| [p.x, p.y] }
xmin = points[0].x
xmax = points[-1].x
increment = 2
prev = points[0]
((xmin + increment)
t = 1.0 * (x - xmin) / (xmax - xmin)
p = Pixel[x, bezier(t, points).round]
Line 502 ⟶ 512:
n = points.length - 1
points.each_with_index.inject(0.0) do |sum, (point, i)|
sum += n.choose(i) * (1 - t)**(n - i) * t**i * point.y
end
end
###############################################
def self.mandelbrot(width, height)
mandel = Pixmap.new(width, height)
pb = ProgressBar.new(width) if $DEBUG
width.times do |x|
height.times do |y|
x_ish = Float(x - width * 11 / 15) / (width / 3)
y_ish = Float(y - height / 2) / (height * 3 / 10)
mandel[x, y] = RGBColour.mandel_colour(mandel_iters(x_ish, y_ish))
end
pb.update(x) if $DEBUG
Line 522 ⟶ 532:
end
def self.mandel_iters(cx, cy)
x = y = 0.0
count = 0
while (Math.hypot(x, y) < 2)
x, y = (x**2 - y**2 + cx), (2 * x * y + cy)
count += 1
end
count
end
###############################################
Line 549 ⟶ 559:
# Applies a convolution kernel to produce a single pixel in the destination
def apply_kernel(x, y, kernel, newimg)
x0 = [0, x - 1].max
y0 = [0, y - 1].max
x1 = x
y1 = y
x2 = [@width - 1, x + 1].min
y2 = [@height - 1, y + 1].min
r = g = b = 0.0
[x0, x1, x2].zip(kernel).each do |xx, kcol|
[y0, y1, y2].zip(kcol).each do |yy, k|
r += k * self[xx, yy].r
g += k * self[xx, yy].g
b += k * self[xx, yy].b
end
newimg[x, y] = RGBColour.new(luma(r), luma(g), luma(b))
end
# Function for clamping values to those that we can use with colors
def luma(value)
if value
0
elsif value > 255
Line 578 ⟶ 588:
end
end
###########################################################################
Line 588 ⟶ 597:
@progress_pos = 0
@progress_view = 68
$stdout.print "[#{'-' * @progress_view}]\r["
end
def update(n)
new_pos = n * @progress_view / @progress_max
if new_pos > @progress_pos
@progress_pos = new_pos
$stdout.print '='
end
Line 604 ⟶ 613:
end
class Queue # < Array
end
class Numeric
def fpart
self -
end
def rfpart
1.0 -
end
end
Line 620 ⟶ 630:
class Integer
def choose(k)
end
def factorial
(2
end
end</lang>
|