le_meme 0.0.8 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -0
- data/README.md +9 -9
- data/Rakefile +1 -1
- data/bin/console +14 -0
- data/bin/meme +68 -39
- data/le_meme.gemspec +9 -7
- data/lib/le_meme.rb +5 -179
- data/lib/le_meme/meme.rb +107 -0
- data/lib/le_meme/meme_lib.rb +49 -0
- data/lib/le_meme/version.rb +3 -2
- data/spec/le_meme/meme_spec.rb +30 -0
- data/spec/spec_helper.rb +1 -3
- metadata +40 -35
- data/spec/le_meme_spec.rb +0 -68
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 855301d19080a1db16d6df06321f7dbfcc8bb712
|
4
|
+
data.tar.gz: 0596203d86d7345eda25226571cec379b6b12ac0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0bdd71930c20ecd3b8aa3bea58e193db74e4eeddd69c2a3af25db7111e3e1538163b52919be9bc9c0a0dd95d7c0ce7014ca4353224fdc43fe6f707062366e891
|
7
|
+
data.tar.gz: de6f304bbaeca7733b128b49717b9e012af2d062b9a0d7d3d06760a04a0302f444846f58afcaba4a5967fa4dee60ebc87e66d4a4a6128093b23043763d2cf550
|
data/.rubocop.yml
ADDED
data/README.md
CHANGED
@@ -37,15 +37,20 @@ Should be straightforward enough for even the dankest memer.
|
|
37
37
|
### I want to make memes in my ruby application
|
38
38
|
Easy enough!
|
39
39
|
|
40
|
+
#### Using a template
|
40
41
|
```ruby
|
41
42
|
require 'le_meme'
|
43
|
+
LeMeme::MemeLib.new_with_default_memes.meme(template: 'maymay', top: 'Top text', bottom: 'Bottom Text').to_file
|
44
|
+
```
|
42
45
|
|
43
|
-
|
46
|
+
#### Using any old image
|
44
47
|
|
45
|
-
|
48
|
+
```ruby
|
49
|
+
require 'le_meme'
|
50
|
+
LeMeme::Meme.new('~/Desktop/to_be_memed.jpg', top: 'Top text', bottom: 'bottom text').to_file
|
46
51
|
```
|
47
52
|
|
48
|
-
See the [docs](http://www.rubydoc.info/gems/le_meme) for all the
|
53
|
+
See the [docs](http://www.rubydoc.info/gems/le_meme) for all the memetastic details
|
49
54
|
|
50
55
|
## Contributing
|
51
56
|
|
@@ -60,12 +65,7 @@ Because the world needs more dank memes!
|
|
60
65
|
|
61
66
|
Actually, because I wanted to take some time and clean up the core of [memebot](http://github.com/paradox460/memebot), and figured making the essential meme generation a gem was the best way to do it. Now I can spam my coworkers with memes in hipchat as well.
|
62
67
|
|
63
|
-
##
|
64
|
-
- [ ] Allow dynamic additon of memes to internal memecache
|
65
|
-
- [ ] Test `meme` binary
|
66
|
-
|
67
|
-
## License
|
68
|
-
|
68
|
+
## LICENSE
|
69
69
|
```
|
70
70
|
Copyright (c) 2015 Jeff Sandberg
|
71
71
|
|
data/Rakefile
CHANGED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'le_meme'
|
5
|
+
require 'pry'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
Pry.start
|
data/bin/meme
CHANGED
@@ -1,61 +1,90 @@
|
|
1
|
-
#!/
|
1
|
+
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'bundler/setup'
|
3
4
|
require 'le_meme'
|
4
|
-
require '
|
5
|
+
require 'optparse'
|
6
|
+
require 'pry'
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
8
|
+
options = {}
|
9
|
+
|
10
|
+
if ARGV.empty?
|
11
|
+
exit(0) unless $stdout.tty?
|
12
|
+
$stdout.puts 'Usage: meme [options] [top-text] [bottom-text]'
|
13
|
+
$stdout.puts ' run -h for help'
|
14
|
+
exit(0)
|
15
|
+
end
|
16
|
+
|
17
|
+
memelib = LeMeme::MemeLib.new_with_default_memes
|
18
|
+
|
19
|
+
OptionParser.new do |opts|
|
20
|
+
opts.banner = <<-HEREDOC
|
9
21
|
Usage: meme [options] [top-text] [bottom-text]
|
10
22
|
|
11
23
|
Top and bottom may be set by the last 2 unprefixed arguments, or by their respective flags.
|
12
24
|
If you don't specify a meme, either via image path or by template, we'll pick one for you.
|
13
25
|
HEREDOC
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
on
|
18
|
-
|
19
|
-
on 'p', 'path=', 'path to an image to use for the meme'
|
20
|
-
on 'w', 'watermark=', 'text to watermark the image'
|
21
|
-
on 'o', 'outpath=', 'path to save the final meme to'
|
22
|
-
on 'O', 'open', "Open the file in your system's default image viewer"
|
23
|
-
|
24
|
-
on 'templates', 'list all the meme templates' do
|
25
|
-
puts memegen.memes.keys.join("\n")
|
26
|
-
exit
|
26
|
+
|
27
|
+
opts.separator '---'
|
28
|
+
|
29
|
+
opts.on('-tTOP', '--top=TOP', 'top text') do |top|
|
30
|
+
options[:top] = top
|
27
31
|
end
|
28
|
-
on
|
29
|
-
|
30
|
-
|
32
|
+
opts.on('-bBOTTOM', '--bottom=BOTTOM', 'bottom text') do |bottom|
|
33
|
+
options[:bottom] = bottom
|
34
|
+
end
|
35
|
+
opts.on('-wWATERMARK', '--watermark=WATERMARK', 'watermark text') do |watermark|
|
36
|
+
options[:watermark] = watermark
|
31
37
|
end
|
32
|
-
end
|
33
|
-
if ARGV.empty?
|
34
|
-
$stdout.puts opts if $stdout.tty?
|
35
|
-
exit(0)
|
36
|
-
end
|
37
38
|
|
38
|
-
opts.
|
39
|
+
opts.separator ''
|
39
40
|
|
40
|
-
|
41
|
-
|
41
|
+
opts.on('-mTEMPLATE', '--meme=TEMPLATE', 'meme template you want to use. See --templates for list') do |template|
|
42
|
+
options[:template] = template
|
43
|
+
end
|
42
44
|
|
45
|
+
opts.on('-pPATH', '--path=PATH', 'path to image file to use') do |path|
|
46
|
+
options[:path] = path
|
47
|
+
end
|
48
|
+
opts.on('-oPATH', '--outpath=PATH', 'path to save generated meme') do |path|
|
49
|
+
options[:outpath] = path
|
50
|
+
end
|
51
|
+
opts.on('-O', '--open', 'open when finished') do |o|
|
52
|
+
options[:open] = o
|
53
|
+
end
|
43
54
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
55
|
+
opts.separator ''
|
56
|
+
|
57
|
+
opts.on('--templates', 'list all the meme templates') do
|
58
|
+
puts memelib.memes.keys.join("\n")
|
59
|
+
exit(0)
|
60
|
+
end
|
61
|
+
|
62
|
+
opts.on('-h', '--help', 'prints this help') do
|
63
|
+
puts opts
|
64
|
+
exit(0)
|
65
|
+
end
|
66
|
+
end.parse!
|
67
|
+
|
68
|
+
top = options[:top] || ARGV[0]
|
69
|
+
bottom = options[:bottom] || ARGV[1]
|
70
|
+
|
71
|
+
meme = if options[:path]
|
72
|
+
LeMeme::Meme.new(options[:path], top: top, bottom: bottom, watermark: options[:watermark])
|
73
|
+
else
|
74
|
+
template = options[:template].nil? ? nil : options[:template].strip
|
75
|
+
memelib.meme(template: template, top: top, bottom: bottom, watermark: options[:watermark])
|
76
|
+
end
|
77
|
+
|
78
|
+
output = meme.to_file(options[:outpath]).path
|
50
79
|
|
51
|
-
if
|
80
|
+
if options[:open]
|
52
81
|
case RbConfig::CONFIG['host_os']
|
53
82
|
when /mswin|mingw|cygwin/
|
54
|
-
`start #{output}`
|
83
|
+
`start '#{output}'`
|
55
84
|
when /darwin/
|
56
|
-
`open -F #{output}`
|
85
|
+
`open -F '#{output}'`
|
57
86
|
when /linux|bsd/
|
58
|
-
`xdg-open #{output}`
|
87
|
+
`xdg-open '#{output}'`
|
59
88
|
else
|
60
89
|
$stderr.puts "Sorry, don't know how to open on your system! (#{RbConfig::Config['host_os']})"
|
61
90
|
end
|
data/le_meme.gemspec
CHANGED
@@ -16,14 +16,16 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = [
|
19
|
+
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.
|
22
|
-
spec.add_development_dependency 'rake', '~> 10.0'
|
23
|
-
spec.add_development_dependency 'pry', '~> 0.10'
|
24
|
-
spec.add_development_dependency 'rspec', '~> 3.1'
|
21
|
+
spec.required_ruby_version = '>= 2.1'
|
25
22
|
|
26
|
-
spec.
|
23
|
+
spec.add_development_dependency 'bundler'
|
24
|
+
spec.add_development_dependency 'rake'
|
25
|
+
spec.add_development_dependency 'pry'
|
26
|
+
spec.add_development_dependency 'pry-byebug'
|
27
|
+
spec.add_development_dependency 'rspec'
|
28
|
+
|
29
|
+
spec.add_runtime_dependency 'rmagick', '~> 2.15'
|
27
30
|
spec.add_runtime_dependency 'word_wrapper', '~> 0.5.0'
|
28
|
-
spec.add_runtime_dependency 'slop', '~> 3.6'
|
29
31
|
end
|
data/lib/le_meme.rb
CHANGED
@@ -1,184 +1,10 @@
|
|
1
|
-
require 'le_meme/version'
|
2
1
|
require 'rmagick'
|
3
2
|
require 'word_wrapper'
|
4
|
-
require 'pathname'
|
5
|
-
|
6
|
-
# Create some dank memes
|
7
|
-
#
|
8
|
-
# @author Jeff Sandberg <paradox460@gmail.com>
|
9
|
-
class LeMeme
|
10
|
-
VALID_IMAGE_EXTENSIONS = /\.(jp[e]?g|png|gif)$/i
|
11
|
-
|
12
|
-
attr_reader :memes
|
13
|
-
|
14
|
-
# Create a new instance of LeMeme, for generating memes.
|
15
|
-
#
|
16
|
-
# @param [Array] Directories of images you want the gem to know about. Scrapes images in said directory
|
17
|
-
# @return [String] description of returned object
|
18
|
-
def initialize(*dirs)
|
19
|
-
@memes = {}
|
20
|
-
load_directory!
|
21
|
-
dirs.each do |dir|
|
22
|
-
load_directory!(dir)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# Create a meme with the given text
|
27
|
-
#
|
28
|
-
# @param path [String] The path to the image file you want to annotate.
|
29
|
-
# @param top [String] The text you want to appear on the top of the meme.
|
30
|
-
# @param bottom [String] The text you want to appear on the bottom of the meme.
|
31
|
-
# @param watermark [String] The watermark text. If nil it is omitted
|
32
|
-
# @param outpath [String] Where do you want to put the generated meme. Defaults to /tmp/meme-<timestamp>.<extension>
|
33
|
-
# @param nowrite [Boolean] Returns the meme as a blob (string) instead of writing to disk.
|
34
|
-
# @return [String] Either the generated meme object (if nowrite) or a path to the meme
|
35
|
-
def generate(path:, top: nil, bottom: nil, watermark: nil, outpath: nil, nowrite: false)
|
36
|
-
top = (top || '').upcase
|
37
|
-
bottom = (bottom || '').upcase
|
38
|
-
|
39
|
-
path = Pathname.new(path).realpath
|
40
|
-
|
41
|
-
canvas = Magick::ImageList.new(path)
|
42
|
-
|
43
|
-
caption_meme(top, Magick::NorthGravity, canvas) unless top.empty?
|
44
|
-
caption_meme(bottom, Magick::SouthGravity, canvas) unless bottom.empty?
|
45
|
-
|
46
|
-
# Draw the watermark
|
47
|
-
unless watermark.nil?
|
48
|
-
watermark_draw = Magick::Draw.new
|
49
|
-
watermark_draw.annotate(canvas, 0, 0, 0, 0, " #{watermark}") do
|
50
|
-
self.font = 'Helvetica'
|
51
|
-
self.fill = 'white'
|
52
|
-
self.text_antialias(false)
|
53
|
-
self.font_weight = 100
|
54
|
-
self.gravity = Magick::SouthEastGravity
|
55
|
-
self.pointsize = 10
|
56
|
-
self.undercolor = 'hsla(0,0,0,.5)'
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
output_path = outpath || "/tmp/meme-#{Time.now.to_i}#{path.extname}"
|
61
|
-
if nowrite
|
62
|
-
canvas.to_blob
|
63
|
-
else
|
64
|
-
canvas.write(output_path)
|
65
|
-
output_path
|
66
|
-
end
|
67
|
-
end
|
68
|
-
alias_method :meme, :generate
|
69
3
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
# @param name [String] Name of the template to use. See #memes
|
74
|
-
# @param top [String] The text you want to appear on the top of the meme.
|
75
|
-
# @param bottom [String] The text you want to appear on the bottom of the meme.
|
76
|
-
# @param watermark [String] The watermark text. If nil it is omitted
|
77
|
-
# @param outpath [String] Where do you want to put the generated meme. Defaults to /tmp/meme-<timestamp>.<extension>
|
78
|
-
# @param nowrite [Boolean] Returns the meme as a blob (string) instead of writing to disk.
|
79
|
-
# @return [String] Either the generated meme object (if nowrite) or a path to the meme
|
80
|
-
def fast_meme(name: nil, top: nil, bottom: nil, watermark: nil, outpath: nil, nowrite: false)
|
81
|
-
if name.nil?
|
82
|
-
path = @memes[@memes.keys.sample]
|
83
|
-
elsif @memes[name].nil?
|
84
|
-
fail ArgumentError, "#{name} is not a pre-loaded meme"
|
85
|
-
else
|
86
|
-
path = @memes[name]
|
87
|
-
end
|
88
|
-
generate(path: path, top: top, bottom: bottom, watermark: watermark, outpath: outpath, nowrite: nowrite)
|
89
|
-
end
|
90
|
-
alias_method :m, :fast_meme
|
91
|
-
|
92
|
-
private
|
93
|
-
|
94
|
-
# Load all images in a directory as memes.
|
95
|
-
# Last loaded directory overrides first, if there are name conflicts.
|
96
|
-
#
|
97
|
-
# @param directory [String] Directory to scrape.
|
98
|
-
def load_directory!(directory = nil)
|
99
|
-
directory = directory.nil? ? "#{File.join(File.dirname(File.expand_path(__FILE__)), '../memes')}/*.jpg" : "#{directory}/*"
|
100
|
-
paths = Dir.glob(directory).select do |path|
|
101
|
-
path =~ VALID_IMAGE_EXTENSIONS
|
102
|
-
end
|
103
|
-
@memes.merge!(paths.reduce({}) do |images, path|
|
104
|
-
path = Pathname.new(path).realpath
|
105
|
-
name = path.split.last.sub(VALID_IMAGE_EXTENSIONS, '').to_s
|
106
|
-
images.merge(name => path)
|
107
|
-
end)
|
108
|
-
end
|
109
|
-
|
110
|
-
# Caption a canvas
|
111
|
-
#
|
112
|
-
# @param text [String] The text to put on as a caption
|
113
|
-
# @param gravity [Grav] RMagick Gravity Constant
|
114
|
-
# @param canvas [Magick::ImageList] ImageList/Canvas to compose the text onto
|
115
|
-
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
116
|
-
def caption_meme(text, gravity, canvas)
|
117
|
-
text.gsub!('\\', '\\\\\\')
|
118
|
-
|
119
|
-
# 28 is the biggest pointsize memes dont look like shit on
|
120
|
-
min_pointsize = 28
|
121
|
-
max_pointsize = 128
|
122
|
-
current_pointsize = min_pointsize
|
123
|
-
current_stroke = current_pointsize / 30.0
|
124
|
-
|
125
|
-
max_width = canvas.columns - 20
|
126
|
-
max_height = (canvas.rows / 2) - 20
|
127
|
-
|
128
|
-
draw = Magick::Draw.new
|
129
|
-
draw.font = File.join(File.dirname(File.expand_path(__FILE__)), '..', 'fonts', 'Impact.ttf')
|
130
|
-
draw.font_weight = Magick::BoldWeight
|
131
|
-
metrics = nil
|
132
|
-
|
133
|
-
# Non-ragged word-wrap
|
134
|
-
text = word_wrap(text)
|
135
|
-
|
136
|
-
# Calculate out the largest pointsize that will fit
|
137
|
-
loop do
|
138
|
-
draw.pointsize = current_pointsize
|
139
|
-
last_metrics = metrics
|
140
|
-
metrics = draw.get_multiline_type_metrics(text)
|
141
|
-
|
142
|
-
if metrics.width + current_stroke > max_width ||
|
143
|
-
metrics.height + current_stroke > max_height ||
|
144
|
-
current_pointsize > max_pointsize
|
145
|
-
if current_pointsize > min_pointsize
|
146
|
-
current_pointsize -= 1
|
147
|
-
current_stroke = current_pointsize / 30.0
|
148
|
-
metrics = last_metrics
|
149
|
-
end
|
150
|
-
break
|
151
|
-
else
|
152
|
-
current_pointsize += 1
|
153
|
-
current_stroke = current_pointsize / 30.0
|
154
|
-
end
|
155
|
-
end
|
156
|
-
# rubocop:disable Style/RedundantSelf
|
157
|
-
draw.annotate(canvas, canvas.columns, canvas.rows - 10, 0, 0, text) do
|
158
|
-
self.interline_spacing = -(current_pointsize / 5)
|
159
|
-
self.stroke_antialias(true)
|
160
|
-
self.stroke = 'black'
|
161
|
-
self.fill = 'white'
|
162
|
-
self.gravity = gravity
|
163
|
-
self.stroke_width = current_stroke
|
164
|
-
self.pointsize = current_pointsize
|
165
|
-
end
|
166
|
-
# rubocop:enable Style/RedundantSelf
|
167
|
-
end
|
168
|
-
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
4
|
+
require 'le_meme/version'
|
5
|
+
require 'le_meme/meme'
|
6
|
+
require 'le_meme/meme_lib'
|
169
7
|
|
170
|
-
|
171
|
-
|
172
|
-
#
|
173
|
-
# @param text [String] The text string to word-wrap.
|
174
|
-
# @param col [Fixnum] The number of "columns" to wrap at
|
175
|
-
# @return [String] The text, with the
|
176
|
-
def word_wrap(text, col: 24)
|
177
|
-
text.strip.gsub(/\n\r/, '\s')
|
178
|
-
text = WordWrapper::MinimumRaggedness.new(col, text).wrap
|
179
|
-
text = text.split("\n").map do |line|
|
180
|
-
line.length > col ? line.gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}})/, "\\1\\3\n").strip : line
|
181
|
-
end * "\n"
|
182
|
-
text.strip
|
183
|
-
end
|
8
|
+
module LeMeme
|
9
|
+
IMAGE_EXTENSIONS = /\.(jp[e]?g|png|gif)$/i
|
184
10
|
end
|
data/lib/le_meme/meme.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
module LeMeme
|
2
|
+
# A single meme object
|
3
|
+
class Meme
|
4
|
+
attr_accessor(*%i(path top bottom watermark))
|
5
|
+
# @param [String, Pathanem] path Path to an image for the meme background
|
6
|
+
# @param [String] top: nil The text on the top of the meme
|
7
|
+
# @param [String] bottom: nil The text on the bottom of the meme
|
8
|
+
# @param [String] watermark: nil Watermark text
|
9
|
+
# @return [Meme] A new meme object
|
10
|
+
def initialize(path, top: nil, bottom: nil, watermark: nil)
|
11
|
+
@path = File.expand_path(path)
|
12
|
+
@top = top.to_s.upcase
|
13
|
+
@bottom = bottom.to_s.upcase
|
14
|
+
@watermark = watermark
|
15
|
+
@canvas = Magick::ImageList.new(@path)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Outputs the meme to the file system
|
19
|
+
#
|
20
|
+
# @param [String] path = nil Where to save the meme
|
21
|
+
# @return [File] File object representing the meme
|
22
|
+
def to_file(path = nil)
|
23
|
+
path = File.expand_path(path.nil? ? "#{ENV['TMPDIR']}meme-#{Time.now.to_i}.jpg" : path)
|
24
|
+
generate!
|
25
|
+
|
26
|
+
file = File.new(path, 'w+')
|
27
|
+
@canvas.write(path)
|
28
|
+
|
29
|
+
file
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get a binary string representing the meme
|
33
|
+
#
|
34
|
+
# @return [String]
|
35
|
+
def to_blob
|
36
|
+
generate!
|
37
|
+
|
38
|
+
@canvas.to_blob
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def generate!
|
44
|
+
return if @generated
|
45
|
+
@generated = true
|
46
|
+
caption(@top, Magick::NorthGravity) unless @top.empty?
|
47
|
+
caption(@bottom, Magick::SouthGravity) unless @bottom.empty?
|
48
|
+
watermark unless watermark.nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
# rubocop:disable Metrics/MethodLength
|
52
|
+
def caption(text, gravity)
|
53
|
+
text = word_wrap(text)
|
54
|
+
draw, pointsize = calculate_pointsize(text)
|
55
|
+
|
56
|
+
draw.annotate(@canvas, @canvas.columns, @canvas.rows - 10, 0, 0, text) do
|
57
|
+
self.interline_spacing = -(pointsize / 5)
|
58
|
+
stroke_antialias(true)
|
59
|
+
self.stroke = 'black'
|
60
|
+
self.fill = 'white'
|
61
|
+
self.gravity = gravity
|
62
|
+
self.stroke_width = pointsize / 30.0
|
63
|
+
self.pointsize = pointsize
|
64
|
+
end
|
65
|
+
end
|
66
|
+
# rubocop:enable Metrics/MethodLength
|
67
|
+
|
68
|
+
def watermark
|
69
|
+
draw = Magick::Draw.new
|
70
|
+
draw.annotate(@canvas, 0, 0, 0, 0, " #{@watermark}") do
|
71
|
+
self.fill = 'white'
|
72
|
+
text_antialias(false)
|
73
|
+
self.font_weight = 100
|
74
|
+
self.gravity = Magick::SouthEastGravity
|
75
|
+
self.pointsize = 10
|
76
|
+
self.undercolor = 'hsla(0,0,0,.5)'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# rubocop:disable Metrics/AbcSize
|
81
|
+
def calculate_pointsize(text, size_range: 28...128)
|
82
|
+
draw = Magick::Draw.new
|
83
|
+
draw.font = File.join(File.dirname(File.expand_path(__FILE__)), '..', '..', 'fonts', 'Impact.ttf')
|
84
|
+
draw.font_weight = Magick::BoldWeight
|
85
|
+
|
86
|
+
size = size_range.detect(-> { size_range.last }) do |pointsize|
|
87
|
+
draw.pointsize = pointsize + 1
|
88
|
+
current_stroke = pointsize / 30.0
|
89
|
+
|
90
|
+
metrics = draw.get_multiline_type_metrics(text)
|
91
|
+
|
92
|
+
metrics.width + current_stroke > @canvas.columns - 20 || metrics.height + current_stroke > (@canvas.rows / 2) - 20
|
93
|
+
end
|
94
|
+
[draw, size]
|
95
|
+
end
|
96
|
+
# rubocop:enable Metrics/AbcSize
|
97
|
+
|
98
|
+
def word_wrap(text, col: 24)
|
99
|
+
text.strip.gsub(/\n\r/, '\s')
|
100
|
+
text = WordWrapper::MinimumRaggedness.new(col, text).wrap
|
101
|
+
text = text.split("\n").map do |line|
|
102
|
+
line.length > col ? line.gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}})/, "\\1\\3\n").strip : line
|
103
|
+
end * "\n"
|
104
|
+
text.strip
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module LeMeme
|
2
|
+
# Utility for easily generating memes based off templates
|
3
|
+
class MemeLib
|
4
|
+
attr_reader :memes
|
5
|
+
# Creates a meme library
|
6
|
+
#
|
7
|
+
# @param [String] *dirs Directory glob patterns to meme templates
|
8
|
+
# @return [MemeLib]
|
9
|
+
def initialize(*dirs)
|
10
|
+
@memes = {}
|
11
|
+
dirs.each(&method(:load_directory!))
|
12
|
+
end
|
13
|
+
|
14
|
+
# Creates a meme library, preloaded with the included templates
|
15
|
+
#
|
16
|
+
# @return [MemeLib]
|
17
|
+
def self.new_with_default_memes
|
18
|
+
path = File.join(File.dirname(File.expand_path(__FILE__)), '..', '..', 'memes', '*')
|
19
|
+
new(path)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Loads a directory into the MemeLib, for template consumption
|
23
|
+
# Clobbers any existing templates
|
24
|
+
#
|
25
|
+
# @param [String] dir Directory glob pattern to meme templates
|
26
|
+
# @return [Hash] Hash of all templates and their filepaths
|
27
|
+
def load_directory!(dir)
|
28
|
+
paths = Dir.glob(dir).grep LeMeme::IMAGE_EXTENSIONS
|
29
|
+
@memes.merge!(paths.reduce({}) do |images, path|
|
30
|
+
path = File.expand_path(path)
|
31
|
+
name = path.split.last.sub(LeMeme::IMAGE_EXTENSIONS, '').to_s
|
32
|
+
images.merge(name => path)
|
33
|
+
end)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Create a meme from a template
|
37
|
+
#
|
38
|
+
# @param [String] template: nil The template to use. Omit for random template
|
39
|
+
# @param [String] top: nil
|
40
|
+
# @param [String] bottom: nil
|
41
|
+
# @param [String] watermark: nil
|
42
|
+
# @return [LeMeme::Meme]
|
43
|
+
def meme(template: nil, top: nil, bottom: nil, watermark: nil)
|
44
|
+
path = template.nil? ? @memes.values.sample : @memes[template]
|
45
|
+
|
46
|
+
Meme.new(path, top: top, bottom: bottom, watermark: watermark)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/le_meme/version.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe LeMeme::Meme do
|
4
|
+
subject { LeMeme::Meme.new('memes/maymay.jpg') }
|
5
|
+
describe :new do
|
6
|
+
context 'without text' do
|
7
|
+
it { is_expected.to be_a LeMeme::Meme }
|
8
|
+
it { expect(subject.path).to match(%r{memes/maymay.jpg$}) }
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'with text' do
|
12
|
+
subject do
|
13
|
+
LeMeme::Meme.new('memes/maymay.jpg', top: 'top text', bottom: 'bottom text')
|
14
|
+
end
|
15
|
+
|
16
|
+
it { expect(subject.top).to eql('TOP TEXT') }
|
17
|
+
it { expect(subject.bottom).to eql('BOTTOM TEXT') }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe :to_file do
|
22
|
+
it { expect(subject.to_file).to be_a(File) }
|
23
|
+
it { expect(subject.to_file.path).to match(/#{ENV['TMPDIR']}meme-\d+.jpg/) }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe :to_blob do
|
27
|
+
it { expect(subject.to_blob).to be_a(String) }
|
28
|
+
it { expect(subject.to_blob.size).to eql(43_685) }
|
29
|
+
end
|
30
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,131 +1,136 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: le_meme
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeff Sandberg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: pry
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '0
|
47
|
+
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '0
|
54
|
+
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: pry-byebug
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
76
|
-
type: :
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: rmagick
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
89
|
+
version: '2.15'
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
96
|
+
version: '2.15'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
98
|
+
name: word_wrapper
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
103
|
+
version: 0.5.0
|
104
104
|
type: :runtime
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
110
|
+
version: 0.5.0
|
111
111
|
description: A gem that generates memes. Can be used as a library or command-line
|
112
112
|
executable.
|
113
113
|
email:
|
114
114
|
- paradox460@gmail.com
|
115
115
|
executables:
|
116
|
+
- console
|
116
117
|
- meme
|
117
118
|
extensions: []
|
118
119
|
extra_rdoc_files: []
|
119
120
|
files:
|
120
121
|
- ".gitignore"
|
122
|
+
- ".rubocop.yml"
|
121
123
|
- Gemfile
|
122
124
|
- LICENSE.txt
|
123
125
|
- README.md
|
124
126
|
- Rakefile
|
127
|
+
- bin/console
|
125
128
|
- bin/meme
|
126
129
|
- fonts/Impact.ttf
|
127
130
|
- le_meme.gemspec
|
128
131
|
- lib/le_meme.rb
|
132
|
+
- lib/le_meme/meme.rb
|
133
|
+
- lib/le_meme/meme_lib.rb
|
129
134
|
- lib/le_meme/version.rb
|
130
135
|
- memes/10guy.jpg
|
131
136
|
- memes/aaduck.jpg
|
@@ -208,7 +213,7 @@ files:
|
|
208
213
|
- memes/wonka.jpg
|
209
214
|
- memes/yee.jpg
|
210
215
|
- memes/yuno.jpg
|
211
|
-
- spec/
|
216
|
+
- spec/le_meme/meme_spec.rb
|
212
217
|
- spec/spec_helper.rb
|
213
218
|
homepage: http://github.com/paradox460/le_meme
|
214
219
|
licenses:
|
@@ -222,7 +227,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
222
227
|
requirements:
|
223
228
|
- - ">="
|
224
229
|
- !ruby/object:Gem::Version
|
225
|
-
version: '
|
230
|
+
version: '2.1'
|
226
231
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
227
232
|
requirements:
|
228
233
|
- - ">="
|
@@ -230,10 +235,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
230
235
|
version: '0'
|
231
236
|
requirements: []
|
232
237
|
rubyforge_project:
|
233
|
-
rubygems_version: 2.
|
238
|
+
rubygems_version: 2.5.1
|
234
239
|
signing_key:
|
235
240
|
specification_version: 4
|
236
241
|
summary: Dank memes, in gem form
|
237
242
|
test_files:
|
238
|
-
- spec/
|
243
|
+
- spec/le_meme/meme_spec.rb
|
239
244
|
- spec/spec_helper.rb
|
data/spec/le_meme_spec.rb
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe LeMeme do
|
4
|
-
let(:meme) { LeMeme.new }
|
5
|
-
|
6
|
-
describe '#generate' do
|
7
|
-
let(:image) { File.join(File.dirname(File.expand_path(__FILE__)), '../memes/maymay.jpg') }
|
8
|
-
context 'without an image path' do
|
9
|
-
it 'should raise ArgumentError with "missing keyword: path"' do
|
10
|
-
expect { meme.generate }.to raise_error(ArgumentError, 'missing keyword: path')
|
11
|
-
end
|
12
|
-
end
|
13
|
-
context 'with an image path' do
|
14
|
-
x = ['text', nil] * 3
|
15
|
-
x.permutation(3).to_a.uniq.each do |top, bottom, watermark|
|
16
|
-
it "should generate a meme with top: '#{top}', bottom: '#{bottom}', and watermark: '#{watermark}'" do
|
17
|
-
expect_any_instance_of(Magick::ImageList).to receive(:write).and_return("/tmp/meme-#{Time.now.to_i}.jpg")
|
18
|
-
expect(meme.generate(path: image, top: top, bottom: bottom, watermark: watermark)).to match(%r{/tmp/meme-\d+.jpg})
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
context 'with an outpath' do
|
23
|
-
it 'should generate a meme at the specified outpath' do
|
24
|
-
expect_any_instance_of(Magick::ImageList).to receive(:write).and_return('test.jpg')
|
25
|
-
expect(meme.generate(path: image, top: 'top', bottom: 'bottom', outpath: 'test.jpg')).to eq('test.jpg')
|
26
|
-
end
|
27
|
-
end
|
28
|
-
context 'with nowrite set to true' do
|
29
|
-
it 'should output the meme as a blob string' do
|
30
|
-
mymeme = meme.generate(path: image, nowrite: true)
|
31
|
-
expect(mymeme.size).to eq(43618)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
describe '#meme' do
|
37
|
-
it 'should be an alias of #generate' do
|
38
|
-
expect(meme.method(:meme)).to eq(meme.method(:generate))
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
describe '#fast_meme' do
|
43
|
-
let(:template) { 'maymay' }
|
44
|
-
it 'should pass its params to generate_meme' do
|
45
|
-
expect(meme).to receive(:generate) do |args|
|
46
|
-
expect(args[:path]).to eq(Pathname.new('/Users/jeffsandberg/Developer/le_meme/memes/maymay.jpg'))
|
47
|
-
expect(args[:top]).to eq('top text')
|
48
|
-
expect(args[:bottom]).to eq('bottom text')
|
49
|
-
expect(args[:watermark]).to eq('watermark')
|
50
|
-
expect(args[:outpath]).to eq('outpath')
|
51
|
-
end
|
52
|
-
meme.fast_meme(name: template, top: 'top text', bottom: 'bottom text', watermark: 'watermark', outpath: 'outpath')
|
53
|
-
end
|
54
|
-
|
55
|
-
context 'without a specified template' do
|
56
|
-
it 'should generate a meme' do
|
57
|
-
expect_any_instance_of(Magick::ImageList).to receive(:write).and_return("/tmp/meme-#{Time.now.to_i}.jpg")
|
58
|
-
expect(meme.fast_meme).to match(%r{/tmp/meme-\d+.jpg})
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
describe '#m' do
|
64
|
-
it 'should be an alias of #fast_meme' do
|
65
|
-
expect(meme.method(:m)).to eq(meme.method(:fast_meme))
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|