class EasyImgUtils

Public Class Methods

calc_resize(geometry, new_geometry) click to toggle source

e.g. calc_resize '1449x1932', '640x480' #=> 480x640 e.g. calc_resize '518x1024', '*518x500' #=> “518x1024” the asterisk denotes a guarantee the image will be resized to a minimum size on either dimension

e.g. calc_resize '1920x1088', '*518x*500' #=> “882x500” 2 asterisks denotes a guaranteed minumum size on both dimensions

# File lib/easyimg_utils.rb, line 103
def self.calc_resize(geometry, new_geometry)
  
  xy = geometry.split('x',2)
  xy2 = new_geometry.split('x',2)

  # find any locked geometry which guarantees the resize on either x or y
  locks = xy2.select {|x| x =~ /^\*/}        

  a = xy.map {|x| x[/\d+/].to_i}
  a2 = xy2.map {|x| x[/\d+/].to_i}
  
  i = if locks.length > 1 then

    a.index(a.min)
    
  elsif locks.any? 
    
    lock = locks.first
    a2.index(lock[1..-1].to_i)
    
  else      
    a.index(a.max)
  end

  factor = a2[i] / a[i].to_f

  s3 = a.map {|x| (x * factor).round}.join('x')    
  
end
new(img_in=nil, img_out=nil, out: img_out, working_dir: '/tmp', debug: false) click to toggle source
# File lib/easyimg_utils.rb, line 87
def initialize(img_in=nil, img_out=nil, out: img_out, 
               working_dir: '/tmp', debug: false)

  @file_in, @file_out, @working_dir = img_in, out, working_dir    
  @debug = debug

end

Public Instance Methods

add_img(filex=nil, x: 0, y: 0, quality: nil)
Alias for: composite
add_rectangle(a=[], quality: nil, color: 'green', stroke_width: 5, x1: 0, y1: 0, x2: 0, y2: 0) click to toggle source
# File lib/easyimg_utils.rb, line 133
def add_rectangle(a=[], quality: nil, color: 'green', stroke_width: 5, 
                  x1: 0, y1: 0, x2: 0, y2: 0)
  
  x1, y1, x2, y2 = *a if a
  read() do |img|
    gc = Magick::Draw.new
    gc.stroke('green')
    gc.stroke_width(5)
    gc.fill('transparent')
    gc.rectangle(x1, y1, x2, y2)
    gc.draw(img)
    write img, quality
  end
  
end
add_svg(svg_file) click to toggle source
# File lib/easyimg_utils.rb, line 149
def add_svg(svg_file)

  img = Magick::ImageList.new
  img.read(@file_in)
  
  img.read(svg_file) do
      self.format = 'SVG'
      self.background_color = 'transparent'
  end
  
  img.flatten_images.write @file_out
  
end
add_text(s='your text goes here', quality: nil) click to toggle source
# File lib/easyimg_utils.rb, line 163
def add_text(s='your text goes here', quality: nil)
  
  read() do |img|

    d = Draw.new
    img.annotate(d, 0,0,0,0, s) do
      d.gravity = Magick::SouthGravity
      d.pointsize = 26
      d.stroke = '#000000'
      d.fill = '#ffffff'
      d.font_weight = Magick::BoldWeight
    end
    
    write img, quality
    
  end
  
end
animate() click to toggle source
# File lib/easyimg_utils.rb, line 182
def animate()

  anim = Magick::ImageList.new
  
  read() {|img| anim << img }    
  anim.ticks_per_second = 100
  anim.delay = 20
  
  @file_out ? anim.write(@file_out) : anim.animate
  
end
best_viewport() click to toggle source

Used where images are perhaps cropped using CSS to a letterbox size image. Works best with portrait mode photos of selfies or natural

landscapes with a lot of sky

Returns the starting y pos as a percentage of the image using face detection and high contrast detection on the y-axis

# File lib/easyimg_utils.rb, line 201
def best_viewport()
  
  percentage = 0
  
  read() do |img|

    found = faces()
    
    index = if found.any? then
      
      # find the top y
      box = found.max_by {|x, y, width, height| y}
      y, height = box.values_at 1, 3
      y - (height / 2)
      
    else
      
      y_maxcontrast(img)
      
    end
          
    percentage = (100 / (img.rows / index.to_f)).round
    
  end    
  
  return percentage
  
end
blur(x: 0, y: 0, w: 80, h: 80, strength: 8, quality: nil) click to toggle source
# File lib/easyimg_utils.rb, line 230
def blur(x: 0, y: 0, w: 80, h: 80, strength: 8, quality: nil)
  
  width, height = w, h
  
  read() do |img|
    
    region = img.dispatch(x, y, width, height, 'RGB')
    face_img = Magick::Image.constitute(width, height, "RGB", region)
    img.composite!(face_img.gaussian_blur(0, strength), x, y, 
                  Magick::OverCompositeOp)
    write img, quality
    
  end
  
end
brightness(quality: nil) click to toggle source
# File lib/easyimg_utils.rb, line 246
def brightness(quality: nil)
  read() do |img|
    img2 = imglevel(-Magick::QuantumRange * 0.25, Magick::QuantumRange * 1.25, 1.0)
    write img2, quality
  end
end
calc_resize(geometry) click to toggle source

calculates the new geometry after a resize

# File lib/easyimg_utils.rb, line 255
def calc_resize(geometry)    
  EasyImgUtils.calc_resize(info()[:geometry], geometry)
end
capture_screen(quality: nil) click to toggle source
# File lib/easyimg_utils.rb, line 259
def capture_screen(quality: nil)
  
  # defaults (silent=false, frame=false, descend=false,
  #           screen=false, borders=false)
  
  img = Magick::Image.capture(true, false, false, true, true) {
    self.filename = "root"
  }
  write img, quality
  
end
center_crop(w=0, h=0, width: w, height: h, quality: nil) click to toggle source
# File lib/easyimg_utils.rb, line 271
def center_crop(w=0, h=0, width: w, height: h, quality: nil)
  
  return unless w
  
  read() do |img|
    img.crop!(CenterGravity, width, height)
    write img, quality
  end
  
end
composite(filex=nil, x: 0, y: 0, quality: nil) click to toggle source
# File lib/easyimg_utils.rb, line 282
def composite(filex=nil, x: 0, y: 0, quality: nil)
  
  return unless filex
  
  read() do |img|
      
    imgx = Magick::ImageList.new(filex)

    # Change the white pixels in the sign to transparent.
    imgx = imgx.matte_replace(0,0)

    img2 = Magick::Draw.new
    img2.composite(x, y, 0, 0, imgx)
    img2.draw(img)    
    
    write img, quality
  end
  
end
Also aliased as: overlay, add_img
contrast(level=5) click to toggle source

contrast level 1 low -> 10 high

# File lib/easyimg_utils.rb, line 308
def contrast(level=5)
  
  neutral = 5
  
  return if level == neutral
      
  read() do |img|
          
    n = neutral - level
    sharpen  = n > 0 
    n.abs.times { img = img.contrast(sharpen) }
    
    write img, quality
  end
  
end
convert(quality: nil) click to toggle source
# File lib/easyimg_utils.rb, line 325
def convert(quality: nil)
  
  if File.extname(@file_in) == '.webp' then      
    
    # output_format options: pam, ppm, pgm, bmp, tiff or yuv
    ext = File.extname(@file_out)[1..-1].to_sym
    puts 'ext: ' + ext.inspect if @debug
    
    if ext == :jpg then
      
      file_out = @file_out.sub(/\.jpg$/,'.png')
      WebP.decode(@file_in, file_out, output_format: :png)
      img = read(file_out)
      write img, quality
      
    else
    
      WebP.decode(@file_in, @file_out, output_format: ext)
    end
    
  else
    img = read()
    write img, quality
  end
  
end
crop(x: 0, y: 0, w: nil, h: nil, quality: nil) click to toggle source
# File lib/easyimg_utils.rb, line 352
def crop(x: 0, y: 0, w: nil, h: nil, quality: nil)
  
  return unless w
  
  read() do |img|
    img.crop!(x,y, width=w, height=h)
    write img, quality
  end
  
end
drawing(quality: nil)
Alias for: sketch
faces() click to toggle source
# File lib/easyimg_utils.rb, line 363
def faces()
  DetectFaces.new(@file_in).faces
end
fax_effect(threshold: 0.55, quality: nil) click to toggle source
# File lib/easyimg_utils.rb, line 367
def fax_effect(threshold: 0.55, quality: nil)

  read() do |img|
  
    # Use a threshold of 55% of MaxRGB.
    img = img.threshold(Magick::MaxRGB*threshold)      
    write img, quality
    
  end
  
end
feathered_around(quality: nil)
Alias for: vignette
grayscale(quality: nil)
Alias for: greyscale
greyscale(quality: nil) click to toggle source
# File lib/easyimg_utils.rb, line 379
def greyscale(quality: nil)
  
  read() do |img|
    img2 = img.quantize(256, GRAYColorspace)
    write img2, quality
  end
  
end
Also aliased as: grayscale
info() click to toggle source
# File lib/easyimg_utils.rb, line 390
def info()

  img = Magick::Image.ping( @file_in ).first
  {
    geometry: "%sx%s" % [img.columns, img.rows],
    mime_type: img.mime_type, format: img.format,
    quality: img.quality, filesize: img.filesize,
    filename: img.filename, created: img.properties
  }
  
end
make_thumbnail(width=125, height=125) click to toggle source
# File lib/easyimg_utils.rb, line 402
def make_thumbnail(width=125, height=125)
  
  read() do |img|
    img2 = img.thumbnail(width, height)
    write img2, quality
  end
  
end
Also aliased as: thumbnail
overlay(filex=nil, x: 0, y: 0, quality: nil)
Alias for: composite
resize(raw_geometry='320x240', quality: nil) click to toggle source

defines the maximum size of an image while maintaining aspect ratio

# File lib/easyimg_utils.rb, line 415
def resize(raw_geometry='320x240', quality: nil)
  
  geometry = calc_resize(raw_geometry
                           )
  read() do |preview|
  
    preview.change_geometry!(geometry) do |cols, rows, img|
      img.resize!(cols, rows)
    end
    
    write preview, quality
  end
  
end
rotate(degrees) click to toggle source
# File lib/easyimg_utils.rb, line 430
def rotate(degrees)
  
  read() do |img|
    img2 = img.rotate(degrees.to_i)
    write img2, quality
  end
  
end
rotate_180() click to toggle source
# File lib/easyimg_utils.rb, line 439
def rotate_180()
  
  read() do |img|
    img2 = img.rotate(180)
    write img2, quality
  end
  
end
rotate_left() click to toggle source
# File lib/easyimg_utils.rb, line 448
def rotate_left()
  
  read() do |img|
    img2 = img.rotate(-45)
    write img2, quality
  end
  
end
rotate_right() click to toggle source
# File lib/easyimg_utils.rb, line 457
def rotate_right()
  
  read() do |img|
    img2 = img.rotate(45)
    write img2, quality    
  end
  
end
scale(factor=0.75, quality: nil) click to toggle source

Scales an image by the given factor e.g. 0.75 is 75%

# File lib/easyimg_utils.rb, line 468
def scale(factor=0.75, quality: nil)
  
  read() do |img|
  
    img2 = img.scale(factor)      
    write img2, quality
    
  end
  
end
screencast(duration: 6, scale: 1, window: true) click to toggle source

Takes a screenshot every second to create an animated gif

# File lib/easyimg_utils.rb, line 481
def screencast(duration: 6, scale: 1, window: true)
  
  fileout = @file_out.sub(/\.\w+$/,'%d.png')
  
  puts 'fileout: ' + fileout if @debug
  
  x4ss = X4ss.new fileout, mouse: true, window: true
  mode = window ? :window : :screen
  sleep 2; x4ss.record duration: duration, mode: mode
  x4ss.save
  
  fileout2 = fileout.sub(/(?=%)/,'b')
  EasyImgUtils.new(fileout, fileout2).scale(scale) unless scale == 1
  EasyImgUtils.new(fileout2, @file_out).animate
  
end
sketch(quality: nil) click to toggle source
# File lib/easyimg_utils.rb, line 498
def sketch(quality: nil)
  
  read() do |img|

    # Convert to grayscale
    sketch = img.quantize(256, Magick::GRAYColorspace)

    # Apply histogram equalization
    sketch = sketch.equalize

    sketch = sketch.sketch(0, 10, 135)
    img = img.dissolve(sketch, 0.75, 0.25)

    write img, quality
    
  end
  
end
Also aliased as: drawing
thumbnail(width=125, height=125)
Alias for: make_thumbnail
view(show: true) click to toggle source
# File lib/easyimg_utils.rb, line 519
def view(show: true)
  
  return unless @file_out
  command = `feh #{@file_out}`
  run command, show
  
end
vignette(quality: nil) click to toggle source
# File lib/easyimg_utils.rb, line 527
def vignette(quality: nil)
  
  read() do |img|
    
    img2 = img.vignette
    write img2, quality
    
  end
  
end
Also aliased as: feathered_around

Private Instance Methods

read(file=@file_in) { |from_blob.first| ... } click to toggle source
# File lib/easyimg_utils.rb, line 542
def read(file=@file_in)
  
  files = if file =~ /%d/ then

    regfilepath = file.sub('%d','(\d+)')
    globfilepath = file.sub('%d','*')
    
    a = Dir[globfilepath]
    puts 'a: '  + a.inspect if @debug
    a.sort_by {|x| Regexp.new(regfilepath).match(x).captures[0].to_i }      

  else
    [file]
  end     
  
  files.each do |filex|
    
    @filex_in = filex
    data, type = RXFHelper.read(filex)
    yield(Magick::Image.from_blob(data).first)      
    
  end    

end
run(command, show=false) click to toggle source
# File lib/easyimg_utils.rb, line 567
def run(command, show=false)

  if show then 
    command
  else
    puts "Using ->" + command
    system command
  end

end
write(img, quality=nil) click to toggle source
# File lib/easyimg_utils.rb, line 578
def write(img, quality=nil)
  
  return img.to_blob unless @file_out
  
  file_out = if @file_out =~ /%d/ then
    regpath = @file_in.sub('%d','(\d+)')
    n = @filex_in[/#{regpath}/,1]
    @file_out.sub('%d',n)
  else
    @file_out
  end
  
  if file_out =~ /^dfs:/ then
    
    outfile = File.join(@working_dir, File.basename(file_out))
    
    img.write outfile do 
      self.quality = quality.to_i if quality
    end

    FileX.cp outfile, file_out
    
  else
    
    img.write file_out do 
      self.quality = quality.to_i if quality
    end
    
  end
end
y_maxcontrast(img) click to toggle source

returns the y index of the pixel containing the most contrast using the y-axis center of the image

# File lib/easyimg_utils.rb, line 612
def y_maxcontrast(img)

  rows, cols = img.rows, img.columns
  center = (cols / 2).round
  pixels = img.get_pixels(center,0,1,rows)
  
  rgb = []
  px = pixels[0]
  rgb = [px.red, px.green, px.blue].map { |v| 255*(v/65535.0) }

  a = pixels[1..-1].map do |pixel,i|

    c = [pixel.red, pixel.green, pixel.blue].map { |v| 255*(v/65535.0) }
    rgb[0] - c[0]
    rgb.map.with_index {|x,i| (x - c[i]).abs.to_i}

  end

  a2 = a.map(&:sum)
  puts 'a2: ' + a2.inspect if @debug
  #a2.index(a2.max) + 1
  # attempt to detect the 1st occurrence of contrast
  r = a2.detect {|x| x > 600}
  # otherwise select any occurrence of high contrast
  r = a2.max unless r
  a2.index(r) + 1
  
end