How to compare two images in rails3 - ruby-on-rails

How to compare images from my local directory with the downloaded image. Comparison showld be based on image content and size.
How to do this in ruby?

There are several ways to do this.
1) For my opinion the best way is using Rmagick signature(thanks to this useful manual):
require 'RMagick'
new_image = Magick::Image.read(new_photo)[0] ## new_photo = "/your/dir/file.jpg"
expected_image = Magick::Image.read(expected_photo)[0]
new_image.signature.should eql expected_image.signature
or
diff_img, diff_metric = img1[0].compare_channel( img2[0], Magick::MeanSquaredErrorMetric )
2) You also can use raster_graphics library (rosettacode.org):
require 'raster_graphics'
class RGBColour
# the difference between two colours
def -(a_colour)
(#red - a_colour.red).abs +
(#green - a_colour.green).abs +
(#blue - a_colour.blue).abs
end
end
class Pixmap
# the difference between two images
def -(a_pixmap)
if #width != a_pixmap.width or #height != a_pixmap.height
raise ArgumentError, "can't compare images with different sizes"
end
sum = 0
each_pixel {|x,y| sum += self[x,y] - a_pixmap[x,y]}
Float(sum) / (#width * #height * 255 * 3)
end
end
lenna50 = Pixmap.open_from_jpeg('Lenna50.jpg')
lenna100 = Pixmap.open_from_jpeg('Lenna100.jpg')
puts "difference: %.5f%%" % (100.0 * (lenna50 - lenna100))
#=>:
#difference: 1.62559%

Related

Rails Helper to display rating in stars

Using Rails 6. Here's a piece that I wrote just to display number of stars. Obviously I am disgusted by my own code. How would you refactor?
# application_helper.rb
module ApplicationHelper
def show_star_rating(rating)
zero_star_icon_name = "star"
full_star_icon_name = "star_fill"
half_star_icon_name = "star_lefthalf_fill"
total_stars = []
round_by_half = (rating * 2).round / 2.0
(round_by_half.to_i).times { total_stars << full_star_icon_name }
if round_by_half - round_by_half.to_i == 0.5
total_stars << half_star_icon_name
end
if total_stars.size != 5
(5 - total_stars.size).times { total_stars << zero_star_icon_name }
end
total_stars
end
end
# show.html.erb
<% show_star_rating(agent_review.rating).each do |star| %>
<i class="f7-icons"><%= star %></i>
<% end %>
You can make use of the Array.new, passing in the maximum number of stars you want to show, and defaulting all the stars to empty. Then, you can fill in the number of full stars you need. Then, finally, thanks to Numeric's divmod returning either 0 or 1 for the number of half stars you need, you make one more pass and fill in the number of half stars you need:
module StarHelper
EMPTY_STAR_ICON = 'star'.freeze
FULL_STAR_ICON = 'star_fill'.freeze
HALF_STAR_ICON = 'star_lefthalf_fill'.freeze
def full_and_half_star_count(rating)
(rating * 2).round.divmod(2)
end
def stars(rating, max_stars: 5)
full_stars, half_stars = full_and_half_star_count(rating)
Array.new(max_stars, EMPTY_STAR_ICON).
fill(FULL_STAR_ICON, 0, full_stars).
fill(HALF_STAR_ICON, full_stars, half_stars)
end
end
The way I would implement show_star_rating:
def show_star_rating(rating)
zero_star_icon_name = "star"
full_star_icon_name = "star_fill"
half_star_icon_name = "star_lefthalf_fill"
rating_round_point5 = (rating * 2).round / 2.0
(1..5).map do |i|
next(full_star_icon_name) if i <= rating_round_point5
next(half_star_icon_name) if rating_round_point5 + 0.5 == i
zero_star_icon_name
end
end

RMagick/Imagemagick text-processing too slow for Heroku

Japan has massive gift giving culture and every year we have to print out tons of those "Noshi"s. I made a simply rails program for adding text to a blank noshi image to add to our system (already built in rails).
For reference, basically I wanted to make an open version of this that dosen't have a watermark: www.noshi.jp
Here's what the controller looks like:
def create
#noshi = Noshi.new(noshi_params)
# Set up variables
ntype = #noshi.ntype
omote = #noshi.omotegaki
olength = omote.length
opsize = (168 - (olength * 12))
namae = #noshi.namae
namae2 = #noshi.namae2
# namae3 = #noshi.namae3
# namae4 = #noshi.namae4
# namae5 = #noshi.namae5
replacements = [ ["(株)", "㈱"], ["(有)", "㈲"] ]
replacements.each {|replacement| namae.gsub!(replacement[0], replacement[1])}
replacements.each {|replacement| namae2.gsub!(replacement[0], replacement[1])}
# replacements.each {|replacement| namae3.gsub!(replacement[0], replacement[1])}
# replacements.each {|replacement| namae4.gsub!(replacement[0], replacement[1])}
# replacements.each {|replacement| namae5.gsub!(replacement[0], replacement[1])}
names = []
names += [namae, namae2] # removed namae3, namae4, namae5 for the time being
longest = names.max_by(&:length)
nlength = longest.length
npsize = (144 - (nlength * 12))
i = 0
# Pull Noshi Type
noshi_img = MiniMagick::Image.open("#{ENV['GBUCKET_PREFIX']}noshi/noshi#{ntype}.jpg")
# Resize to A4 # 300dpi
noshi_img.resize "2480x3508"
# Iterate through each character
omote.each_char do |c|
# Open new blank/transparent noshi
chars = MiniMagick::Image.open("#{ENV['GBUCKET_PREFIX']}noshi/noshi_blank.png")
chars.resize "2480x3508"
# Draw Each Omotegaki Character
chars.combine_options do |d|
d.gravity 'North'
# Placement based on point size
plcmnt = ((opsize / 12 * 12) + (opsize * i * 1.2))
d.draw "text 0,#{plcmnt} '#{c}'"
d.font 'TakaoPMincho'
d.pointsize opsize
d.fill("#000000")
i += 1
end
# Composite each letter as iterated
noshi_img = noshi_img.composite(chars) do |comp|
comp.compose "Over" # OverCompositeOp
comp.geometry "+0+0" # copy second_image onto first_image from (0, 0)
end
end
# Iterator Reset
i = 0
# Draw Name Text (Line 1)
namae.each_char do |c|
# Iterate through each character
# Open new blank/transparent noshi
chars = MiniMagick::Image.open("#{ENV['GBUCKET_PREFIX']}noshi/noshi_blank.png")
# Resize to a square so it's easy to flip
chars.resize "2480x3508"
chars.combine_options do |d|
# Middle position for first line so set to 0
xplcmnt = (npsize / 12) * 0
yplcmnt = (625 - npsize) - (npsize * i)
d.gravity 'south'
# Placement based on point size, fix for katakana dash
# positive x is
if c == 'ー'
yplcmnt += 15
d.draw "text 0,#{yplcmnt} '|'"
d.pointsize (npsize * 0.85)
else
d.draw "text 0,#{yplcmnt} '#{c}'"
d.pointsize npsize
end
d.font 'TakaoPMincho'
d.fill("#000000")
i += 1
end
# Composite each letter as iterated
noshi_img = noshi_img.composite(chars) do |comp|
comp.compose "Over" # OverCompositeOp
comp.geometry "+0+0" # copy second_image onto first_image from (0, 0)
end
end
# Iterator Reset
i = 0
# Draw Name Text (Line 2)
namae2.each_char do |c|
# Iterate through each character
# Open new blank/transparent noshi
chars = MiniMagick::Image.open("#{ENV['GBUCKET_PREFIX']}noshi/noshi_blank.png")
# Resize to a square so it's easy to flip
chars.resize "2480x3508"
chars.combine_options do |d|
# Next position for second line so set by font size
xplcmnt = (npsize / 6) - npsize * 1.45
yplcmnt = (625 - (npsize * 2)) - (npsize * i)
d.gravity 'south'
# Placement based on point size, fix for katakana dash
if c == 'ー'
yplcmnt += 15
d.draw "text #{xplcmnt},#{yplcmnt} '|'"
d.pointsize (npsize * 0.85)
else
d.draw "text #{xplcmnt},#{yplcmnt} '#{c}'"
d.pointsize npsize
end
d.font 'TakaoPMincho'
d.fill("#000000")
i += 1
end
# Composite each letter as iterated
noshi_img = noshi_img.composite(chars) do |comp|
comp.compose "Over" # OverCompositeOp
comp.geometry "+0+0" # copy second_image onto first_image from (0, 0)
end
end
# Setup and save the file
noshi_img.format "png"
fname = "#{#noshi.omotegaki}_#{#noshi.namae}"
dkey = Time.now.strftime('%Y%m%d%H%M%S')
ext = '.png'
finlname = fname + dkey + ext
noshi_img.write finlname
#noshi.image = File.open(finlname)
File.delete(finlname) if File.exist?(finlname)
respond_to do |format|
if #noshi.save
format.html { redirect_to #noshi, notice: '熨斗が作成されました。' }
format.json { render :show, status: :created, location: #noshi }
else
format.html { render :new }
format.json { render json: #noshi.errors, status: :unprocessable_entity }
end
end end
How it works.
1. User pics a noshi background, selects a noshi header type (for お歳暮 or お祝い or whatever), and inputs a name
2. The app then takes a corresponding file from the gCloud for the noshi background.
3. The app takes each letter and calculates the font size and placement based on the number of total letters and lines.
4. It takes an empty image file and puts each letter onto it's own image and then merges all of them into a final image.
YES it is necessary to make a new image for each letter because as far as I can tell there is no (right-side-up) vertical text format for ImageMagick (a pretty crucial function for a large portion of the planet [China, Japan, Korea] so I find it pretty surprising that it's missing this).
This works fine in development and for our purposes I don't mind it being slow. However, on Heroku this returns an error if it takes over 30 seconds to process, even though the noshi is correctly created every time.
THE QUESTION:
I read that "scale" instead of "resize" may help, but looking at my code I feel like there has to be a more efficient way to do what I've done here. I tried using base images with smaller file sizes, this didn't help much.
Is there a more efficient way to do this?
If not, is there a way to send the user somewhere to wait while the noshi completes so it doesn't return an error every time?
UPDATE:
Just coming back to show the working Ruby on Rails controller I ended up with:
def create
#noshi = Noshi.new(noshi_params)
# Set up variables
ntype = #noshi.ntype
omote = #noshi.omotegaki
omote_length = omote.length
omote_point_size = (168 - (omote_length * 12))
#make an array with each of the name lines entered
name_array = Array.new
name_array << #noshi.namae
name_array << #noshi.namae2
name_array << #noshi.namae3
name_array << #noshi.namae4
name_array << #noshi.namae5
#replace multi-character prefixes with their single charcter versions
#replace katakana dash with capital I
#insert line breaks after each letter for Japanese vertical type
name_array.each do |namae|
replacements = [ ["(株)", "㈱"], ["(有)", "㈲"], ["ー", "|"] ]
replacements.each {|replacement| namae.gsub!(replacement[0], replacement[1])}
end
def add_line_breaks(string)
string.scan(/.{1}/).join("\n")
end
name_array.map!{ |namae| add_line_breaks(namae)}
#add line breaks after each character for the omote as well
omote = add_line_breaks(omote)
#find the longest string (important: after the character concatenation) in the name array to calculate the point size for the names section
name_array_max_length = (name_array.map { |namae| namae.length }).max
name_point_size = (204 - (name_array_max_length * 10))
#max omote size is 156, and the name needs to be an order smaller than that by default.
if name_point_size > 108
name_point_size = 108
end
# Pull Noshi Type
noshi_img = MiniMagick::Image.open("#{ENV['GBUCKET_PREFIX']}noshi/noshi#{ntype}.jpg")
# Resize to A4 # 300dpi
noshi_img.resize "2480x3508"
#create the overlay image
name_overlay = MiniMagick::Image.open("#{ENV['GBUCKET_PREFIX']}noshi/noshi_blank.png")
name_overlay.resize "2480x3508"
#first time for omote
name_overlay.combine_options do |image|
image.gravity 'North'
# Placement based on point size
omote_placement_y = (348 - (omote_length * (omote_point_size / 2)))
image.font 'TakaoPMincho'
image.pointsize omote_point_size
image.fill("#000000")
image.draw "text 0,#{omote_placement_y} '#{omote}'"
end
#count number of names in array, add a name for each time
name_array.count.times do |i|
name_overlay.combine_options do |image|
image.gravity 'North'
# Placement based on point size and iteration
name_placement_x = (0 - i * name_point_size)
name_placement_y = 1150 + ((i * name_point_size) - (name_point_size / 2))
image.font 'TakaoPMincho'
image.pointsize name_point_size
image.fill("#000000")
image.draw "text #{name_placement_x},#{name_placement_y} '#{name_array[i]}'"
end
end
noshi_img = noshi_img.composite(name_overlay) do |comp|
comp.compose "Over" #OverCompositeOp
comp.geometry "+0+0" #copy second_image onto first_image from (0, 0)
end
# Setup and save the file
noshi_img.format "png"
#name the file
fname = "#{#noshi.omotegaki}_#{#noshi.namae}"
dkey = Time.now.strftime('%Y%m%d%H%M%S')
ext = '.png'
final_name = fname + dkey + ext
#write a temporary version
noshi_img.write final_name
#write/stream the file to the uploader
#noshi.image = File.open(final_name)
#delete the original temporary
File.delete(final_name) if File.exist?(final_name)
respond_to do |format|
if #noshi.save
format.html { redirect_to #noshi, notice: '熨斗が作成されました。' }
format.json { render :show, status: :created, location: #noshi }
else
format.html { render :new }
format.json { render json: #noshi.errors, status: :unprocessable_entity }
end
end
end
YES it is necessary to make a new image for each letter because as far
as I can tell there is no (right-side-up) vertical text format for
ImageMagick
In ImageMagick command line, you can create a vertically aligned text string image by placing line feeds after each character.
convert -background white -fill black -pointsize 18 -font arial -gravity center label:"t\ne\ns\nt\ni\nn\ng" result.png
Does this help you? Or is that not practical?

Is it possible to add DataUrl Image in RBPDF?

I am making pdf file from DataUrl Image in ruby on rails.
I have selected RBPDF to produce pdf file in server side.
But in this code I have following error
#pdf.Image(object["src"] , object["left"], object["top"], object["width"], object["height"])
Here object["src"] is DataUrl Image.
RuntimeError (RBPDF error: Missing image file:
data:image/jpeg;base64,/9j/4REORXhp...
Is it impossible to add RBPDF image from DataUrl image?
Adding files dynamically is not effective I think.
You may monkey patch the origin method.
I use the data_uri gem to parse the image data.
require 'data_uri'
require 'rmagick'
module Rbpdf
alias_method :old_getimagesize, :getimagesize
# #param [String] date_url
def getimagesize(date_url)
if date_url.start_with? 'data:'
uri = URI::Data.new date_url
image_from_blob = Magick::Image.from_blob(uri.data)
origin_process_image(image_from_blob[0])
else
old_getimagesize date_url
end
end
# this method is extracted without comments from the origin implementation of getimagesize
def origin_process_image(image)
out = Hash.new
out[0] = image.columns
out[1] = image.rows
case image.mime_type
when "image/gif"
out[2] = "GIF"
when "image/jpeg"
out[2] = "JPEG"
when "image/png"
out[2] = "PNG"
when " image/vnd.wap.wbmp"
out[2] = "WBMP"
when "image/x-xpixmap"
out[2] = "XPM"
end
out[3] = "height=\"#{image.rows}\" width=\"#{image.columns}\""
out['mime'] = image.mime_type
case image.colorspace.to_s.downcase
when 'cmykcolorspace'
out['channels'] = 4
when 'rgbcolorspace', 'srgbcolorspace' # Mac OS X : sRGBColorspace
if image.image_type.to_s == 'GrayscaleType' and image.class_type.to_s == 'PseudoClass'
out['channels'] = 0
else
out['channels'] = 3
end
when 'graycolorspace'
out['channels'] = 0
end
out['bits'] = image.channel_depth
out
end
end

Optimizing finding and counting proper divisors in ruby

This code needs to run under 7000ms or it times out and I am trying to learn ruby so I am here to see if anyone has any ideas that could optimize this code. Or if you can just let me know which functions in this code take the most time so I can concentrate on the parts that will do the most good.
The questions to solve is that you have to tell if the number of divisors for any umber is odd or even.
For n=12 the divisors are [1,2,3,4,6,12] – 'even'
For n=4 the divisors are [1,2,4] – 'odd'
Any help is greatly appreciated,
Thanks.
def oddity(n)
div(n) % 2 == 0 ? (return 'even'): (return 'odd')
end
def div(num)
divs = []
(1..num).each{|x| if (num % x == 0) then divs << x end}
return divs.length
end
The key observation here is that you need only the number of divisors, rather than the divisors themselves. Thus, a fairly simple solution is to decompose the number to primes, and check how many combinations can we form.
require 'mathn'
def div(num)
num.prime_division.inject(1){ |prod, n| prod *= n[1] + 1 }
end
prime_division returns a list of pairs, where the first is the prime and the second is its exponent. E.g.:
12.prime_division
=> [[2, 2], [3, 1]]
We simply multiply the exponents, adding 1 to each, to account for the case where this prime wasn't taken.
Since performance is an issue, let's compare the OP's solution with #standelaune's and #dimid's.
require 'prime'
require 'fruity'
n = 100_000
m = 20
tst = m.times.map { rand(n) }
#=> [30505, 26103, 53968, 24108, 78302, 99141, 22816, 67504, 10149, 28406,
# 18294, 92203, 73157, 5444, 24928, 65154, 24850, 64219, 68310, 64951]
def op(num) # Alex
divs = []
(1..num).each { |x| if (num % x == 0) then divs << x end }
divs.length
end
def test_op(tst) # Alex
tst.each { |n| op(n) }
end
def pd(num) # divid
num.prime_division.inject(1){ |prod, n| prod *= n[1] + 1 }
end
def test_pd(tst) #divid
tst.each { |n| nfacs_even?(n) }
end
def div(num) # standelaune
oddity = false
(1..num).each{|x| if (num % x == 0) then oddity = !oddity end}
oddity ? "odd" : "even"
end
def test_div(tst) # standelaune
tst.each { |n| div(n) }
end
compare do
_test_op { test_op tst }
_test_div { test_div tst }
_test_pd { test_pd tst }
end
Running each test 16 times. Test will take about 56 seconds.
_test_pd is faster than _test_div by 480x ± 100.0
_test_div is similar to _test_op
I'm not suprised that divid's method smokes the others, as prime_division uses (an instance of) the default prime generator, Prime::Generator23, That generator is coded in C and is fast relative to other generators in Prime subclasses.
You could solve this by optimising your algorithm.
You don't have to check all numbers below the number you are examining. It is enough to split your number in to it´s prime components. Then it is a simple matter of combinatorics to determine how many possible divisors there are.
One way to get all prime components could be:
PRIME_SET = [2,3,5,7,11,13,17,19]
def factorize(n)
cut_off = Math.sqrt(n)
parts = []
PRIME_SET.each do |p|
return parts if p > cut_off
if n % p == 0
n = n/p
parts << p
redo
end
end
raise 'To large number for current PRIME_SET'
end
Then computing the number of possible can be done in a number of different ways and there are probably ways of doing it without even computing them. But here is a naive implementation.
def count_possible_divisors(factors)
divisors = Set.new
(1..factors.length-1).each do |i|
factors.combination(i).each do |comb|
divisors.add(comb.reduce(1, :*))
end
end
divisors.length + 2 # plus 2 for 1 and n
end
This should result in less work than what you are doing. But for large numbers this is a hard task to achieve.
If you want to stick with your algorithm, here is an optimization.
def div(num)
oddity = false
(1..num).each{|x| if (num % x == 0) then oddity = !oddity end}
oddity ? "odd" : "even"
end

Ruby Simulated Annealing Trouble

I'm trying to implement a simulated annealing on ruby based on a TSP in which i tried to solve (i converted this code from java). However it turns out the annealing is making my results worst! (PlayerPath gives me a path in which i'll do an Simulated annealing on - i got the path by carrying out a greedy algorithm 1). Can someone help me check on the code and see if i've got something wrong or is it just that simulated annealing doesnt always make things better?
#BEGIN ANNEALING
for i in 1..k
temp = 10000000
cooling = 0.003
if (playerPath[i].length > 2) # if path is larger than 2
bestPath = playerPath[i]
while temp > 1
newSolution = playerPath[i];
firstPosition = rand(newSolution.length)
secondPosition = rand(newSolution.length)
if(firstPosition == 0 || firstPosition == newSolution.length-1)
next
end
if(secondPosition == 0 || secondPosition == newSolution.length-1 )
next
end
# swap cities
tempStore = newSolution[firstPosition]
newSolution[firstPosition] = newSolution[secondPosition]
newSolution[secondPosition] = tempStore
# Tabulation
currentEnergy = calculate_distance(playerPath[i])
neighbourEnergy = calculate_distance(newSolution)
if(acceptanceProbability(currentEnergy,neighbourEnergy,temp) > rand)
playerPath[i] = newSolution
end
if(calculate_distance(playerPath[i])< calculate_distance(bestPath))
bestPath = playerPath[i];
end
temp *= (1-cooling);
end
end
end
#END ANNEALING
#acceptanceProbability
def acceptanceProbability(energy, newEnergy,temperature)
# If the new solution is better, accept it
if (newEnergy < energy)
return 1.0
end
# If the new solution is worse, calculate an acceptance probability
return Math.exp((energy - newEnergy) / temperature)
end

Resources