module MemeCaptain

Constants

VERSION

Public Instance Methods

meme(input, text_poss, options={}) click to toggle source

Create a meme image.

Input can be an IO object or a blob of data. text_poss is an enumerable of TextPos objects containing text, position and style options.

Options:

super_sample - work this many times larger before shrinking
# File lib/meme_captain/meme.rb, line 14
def meme(input, text_poss, options={})
  img = Magick::ImageList.new
  if input.respond_to?(:read)
    img.from_blob(input.read)
  else
    img.from_blob(input)
  end

  img.auto_orient!

  super_sample = options[:super_sample] || 1.0

  text_layer = Magick::Image.new(
    img.page.width * super_sample, img.page.height * super_sample) {
    self.background_color = 'none'
  }

  text_poss.each do |text_pos|
    caption = Caption.new(text_pos.text)

    if caption.drawable?
      wrap_tries = (1..text_pos.max_lines).map { |num_lines|
        caption.wrap(num_lines).upcase.annotate_quote
      }.uniq

      text_x = (text_pos.x.is_a?(Float) ?
        img.page.width * text_pos.x : text_pos.x) * super_sample

      text_y = (text_pos.y.is_a?(Float) ?
        img.page.height * text_pos.y : text_pos.y) * super_sample

      text_width = (text_pos.width.is_a?(Float) ?
        img.page.width * text_pos.width : text_pos.width) * super_sample

      text_height = (text_pos.height.is_a?(Float) ?
        img.page.height * text_pos.height : text_pos.height) * super_sample

      min_pointsize = text_pos.min_pointsize * super_sample

      draw = Magick::Draw.new.extend(Draw)

      text_pos.draw_options.each do |k,v|
        # options that need to be scaled by super sample
        if [
          :stroke_width
          ].include?(k)
          v *= super_sample
        end
        draw.send("#{k}=", v)
      end

      choices = wrap_tries.map do |wrap_try|
        pointsize, metrics = draw.calc_pointsize(text_width, text_height,
          wrap_try, min_pointsize)

        CaptionChoice.new(pointsize, metrics, wrap_try, text_width,
          text_height)
      end

      choice = choices.max

      draw.pointsize = choice.pointsize

      draw.annotate text_layer, text_width, text_height, text_x, text_y,
        choice.text

      text_layer.virtual_pixel_method = Magick::TransparentVirtualPixelMethod
      text_layer = text_layer.blur_channel(text_pos.draw_options[:stroke_width] / 2.0,
                                           text_pos.draw_options[:stroke_width] / 4.0, Magick::OpacityChannel)

      draw.stroke = 'none'

      draw.annotate text_layer, text_width, text_height, text_x, text_y,
        choice.text
    end
  end

  if super_sample != 1
    text_layer.resize!(1.0 / super_sample)
    text_layer = text_layer.unsharp_mask
  end

  img.each do |frame|
    frame.composite!(text_layer, -frame.page.x, -frame.page.y,
      Magick::OverCompositeOp)
    frame.strip!
  end

  text_layer.destroy!

  img
end
meme_top_bottom(input, top_text, bottom_text, options={}) click to toggle source

Shortcut to generate a typical meme with text at the top and bottom.

# File lib/meme_captain/meme.rb, line 108
def meme_top_bottom(input, top_text, bottom_text, options={})
  meme(input, [
    TextPos.new(top_text, 0.05, 0, 0.9, 0.25, options),
    TextPos.new(bottom_text, 0.05, 0.75, 0.9, 0.25, options)
    ])
end
memebg(size, colors, num_rays, &block) click to toggle source

Public: Generate a pie slice meme background.

size - The side length in pixels of the generated image. colors - An Array of color strings (any values that RMagick accepts). num_rays - The Fixnum of rays to create. block - An optiona block passed to Draw.new for specifying additional

draw options

Examples

memebg(400, %w{red orange yellow green blue indigo violet}, 20) {
  # draw options
  # self.stroke = 'white'
}.display

Returns a Magick::Image of the meme background.

# File lib/meme_captain/memebg.rb, line 23
def memebg(size, colors, num_rays, &block)
  # make circle 5% too big to avoid empty space at corners from rounding
  # errors
  circle_radius = Math.sqrt(2 * ((size / 2.0) ** 2)) * 1.05

  side_len = 2 * circle_radius

  start_x = side_len
  start_y = circle_radius

  center = "#{circle_radius},#{circle_radius}"

  img = Magick::Image.new(side_len, side_len)

  color_cycle = colors.cycle

  (1..num_rays).each do |ray_index|
    ray_radius = 2 * Math::PI / num_rays * ray_index

    end_x = circle_radius + (Math.cos(ray_radius) * circle_radius)
    end_y = circle_radius - (Math.sin(ray_radius) * circle_radius)

    svg = "M#{center} L#{start_x},#{start_y} A#{center} 0 0,0 #{end_x},#{end_y} z"

    draw = Magick::Draw.new {
      instance_eval(&block)  if block_given?
      self.fill = color_cycle.next
    }

    draw.path svg
    draw.draw img

    start_x = end_x
    start_y = end_y
  end

  img.crop! Magick::CenterGravity, size, size
end