meme_captain 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,55 @@
1
+ Ruby gem to create meme images (images with text added at the top and bottom).
2
+
3
+ Runs locally and has no web dependencies.
4
+
5
+ Works with animated gifs.
6
+
7
+ ```ruby
8
+ require 'open-uri'
9
+
10
+ require 'meme_captain'
11
+
12
+ open('http://memecaptain.com/troll_face.jpg', 'rb') do |f|
13
+ i = MemeCaptain.meme(f, 'test', '1 2 3')
14
+ i.display
15
+ i.write('out.jpg')
16
+ end
17
+ ```
18
+
19
+ Also includes a Sinatra app that exposes the API over HTTP which is currently
20
+ running http://memecaptain.com/
21
+
22
+ You can use the memecaptain.com API if you prefer it to using the gem.
23
+
24
+ Simplest API:
25
+
26
+ ```
27
+ http://memecaptain.com/i?u=<url encoded source image url>&tt=<url encoded top text>&tb=<url encoded bottom text>
28
+ ```
29
+
30
+ Example:
31
+
32
+ ```
33
+ http://memecaptain.com/i?u=http%3A%2F%2Fmemecaptain.com%2Fyao_ming.jpg&tt=sure+i%27ll+test&tb=the+api
34
+ ```
35
+
36
+ ![Sure I'll test the API](http://memecaptain.com/i?u=http%3A%2F%2Fmemecaptain.com%2Fyao_ming.jpg&tt=sure+i%27ll+test&tb=the+api)
37
+
38
+ If you want better error messages, use this which will return JSON:
39
+
40
+ ```
41
+ http://memecaptain.com/g?u=<url encoded source image url>&tt=<url encoded top text>&tb=<url encoded bottom text>
42
+ ```
43
+
44
+ Example:
45
+
46
+ ```
47
+ http://memecaptain.com/g?u=http%3A%2F%2Fmemecaptain.com%2Fyao_ming.jpg&tt=sure+i%27ll+test&tb=the+api
48
+ ```
49
+
50
+ ```json
51
+ {
52
+ permUrl: "http://memecaptain.com/i?u=http%3A%2F%2Fmemecaptain.com%2Fyao_ming.jpg&tt=sure+i%27ll+test&tb=the+api"
53
+ tempUrl: "http://memecaptain.com/tmp/de55f7a78c6559d4a24ef3e72e2de89992b82695.jpeg"
54
+ }
55
+ ```
@@ -0,0 +1,45 @@
1
+ module MemeCaptain
2
+
3
+ class Caption < String
4
+
5
+ def initialize(s='')
6
+ super s.to_s
7
+ end
8
+
9
+ # Return the contents of the string quoted for ImageMagick annotate.
10
+ def annotate_quote
11
+ Caption.new(gsub('\\', '\\\\\\').gsub('%', '\%'))
12
+ end
13
+
14
+ # Whether the string contains any non-whitespace.
15
+ def drawable?
16
+ match(/[^\s]/) ? true : false
17
+ end
18
+
19
+ # Wrap the string of into num_lines lines.
20
+ def wrap(num_lines)
21
+ cleaned = gsub(/\s+/, ' ').strip
22
+
23
+ chars_per_line = cleaned.size / num_lines.to_f
24
+
25
+ lines = []
26
+ cleaned.split.each do |word|
27
+ if lines.empty?
28
+ lines << word
29
+ else
30
+ if (lines[-1].size + 1 + word.size) <= chars_per_line or
31
+ lines.size >= num_lines
32
+ lines[-1] << ' ' unless lines[-1].empty?
33
+ lines[-1] << word
34
+ else
35
+ lines << word
36
+ end
37
+ end
38
+ end
39
+
40
+ Caption.new(lines.join("\n"))
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,33 @@
1
+ module MemeCaptain
2
+
3
+ # For comparing different caption line break and pointsize choices.
4
+ class CaptionChoice
5
+ include Comparable
6
+
7
+ def initialize(fits, pointsize, text)
8
+ @fits = fits
9
+ @pointsize = pointsize
10
+ @text = text
11
+ end
12
+
13
+ def num_lines
14
+ text.count("\n") + 1
15
+ end
16
+
17
+ def fits_i
18
+ fits ? 1: 0
19
+ end
20
+
21
+ def <=>(other)
22
+ [fits_i, pointsize, fits ? -num_lines : num_lines] <=>
23
+ [other.fits_i, other.pointsize,
24
+ other.fits ? -other.num_lines : other.num_lines]
25
+ end
26
+
27
+ attr_accessor :fits
28
+ attr_accessor :pointsize
29
+ attr_accessor :text
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,35 @@
1
+ module MemeCaptain
2
+
3
+ # Mix-in for Magick::Draw
4
+ module Draw
5
+
6
+ # Calculate the largest pointsize for text that will be in a width x
7
+ # height box.
8
+ #
9
+ # Return [pointsize, fits] where pointsize is the largest pointsize and
10
+ # fits is true if that pointsize will fit in the box.
11
+ def calc_pointsize(width, height, text, min_pointsize)
12
+ current_pointsize = min_pointsize
13
+
14
+ fits = false
15
+
16
+ loop {
17
+ self.pointsize = current_pointsize
18
+ metrics = get_multiline_type_metrics(text)
19
+ if metrics.width > width or metrics.height > height
20
+ if current_pointsize > min_pointsize
21
+ current_pointsize -= 1
22
+ fits = true
23
+ end
24
+ break
25
+ else
26
+ current_pointsize += 1
27
+ end
28
+ }
29
+
30
+ [current_pointsize, fits]
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -46,6 +46,9 @@ module MemeCaptain
46
46
  # Put data in the cache and return its path.
47
47
  def put(id, data)
48
48
  mime_type = MemeCaptain.mime_type(data)
49
+ unless mime_type
50
+ raise 'Data loaded from source image url is not an image'
51
+ end
49
52
  file_path = id_path(id, ".#{mime_type.extensions[0]}")
50
53
 
51
54
  open(file_path, 'w') do |f|
@@ -6,7 +6,7 @@ module MemeCaptain
6
6
 
7
7
  # Create a meme image.
8
8
  # Input can be an IO object or a blob of data.
9
- def meme(input, line1, line2, options={})
9
+ def meme(input, top_text, bottom_text, options={})
10
10
  img = Magick::ImageList.new
11
11
  if input.respond_to?(:read)
12
12
  img.from_blob(input.read)
@@ -14,34 +14,53 @@ module MemeCaptain
14
14
  img.from_blob(input)
15
15
  end
16
16
 
17
- options = {
18
- :density => 144,
19
- :fill => 'white',
20
- :font => 'Impact-Regular',
21
- :gravity => Magick::CenterGravity,
22
- :size => "#{img.page.width * 1.8}x#{img.page.height / 2}",
23
- :stroke => 'black',
24
- :stroke_width => 2,
25
- :background_color => 'none',
26
- }.merge(options)
27
-
28
- line1_caption = Magick::Image.read("caption:#{line1.to_s.upcase}") {
29
- options.each { |k,v| self.send("#{k}=", v) }
30
- }
31
- line1_caption[0].resize!(0.5)
17
+ max_lines = 16
18
+ super_sample = 2.0
32
19
 
33
- line2_caption = Magick::Image.read("caption:#{line2.to_s.upcase}") {
34
- options.each { |k,v| self.send("#{k}=", v) }
35
- }
36
- line2_caption[0].resize!(0.5)
20
+ min_pointsize = 12 * super_sample
21
+ text_width = img.page.width * 0.9 * super_sample
22
+ text_height = img.page.height / 4.0 * super_sample
37
23
 
38
- text_layer = Magick::Image.new(img.page.width, img.page.height) {
24
+ text_layer = Magick::Image.new(
25
+ img.page.width * super_sample, img.page.height * super_sample) {
39
26
  self.background_color = 'none'
27
+ self.density = 144
40
28
  }
41
- text_layer.composite!(line1_caption[0], Magick::NorthGravity,
42
- Magick::OverCompositeOp)
43
- text_layer.composite!(line2_caption[0], Magick::SouthGravity,
44
- Magick::OverCompositeOp)
29
+
30
+ draw = Magick::Draw.new.extend(Draw)
31
+
32
+ draw.fill = 'white'
33
+ draw.font = 'Impact'
34
+
35
+ [
36
+ [Caption.new(top_text), Magick::NorthGravity],
37
+ [Caption.new(bottom_text), Magick::SouthGravity],
38
+ ].select { |x| x[0].drawable? }.each do |caption, gravity|
39
+ wrap_tries = (1..max_lines).map { |num_lines|
40
+ caption.wrap(num_lines).upcase.annotate_quote
41
+ }.uniq
42
+
43
+ choices = wrap_tries.map do |wrap_try|
44
+ pointsize, fits = draw.calc_pointsize(
45
+ text_width, text_height, wrap_try, min_pointsize)
46
+
47
+ CaptionChoice.new(fits, pointsize, wrap_try)
48
+ end
49
+
50
+ choice = choices.max
51
+
52
+ draw.gravity = gravity
53
+ draw.pointsize = choice.pointsize
54
+
55
+ draw.stroke = 'black'
56
+ draw.stroke_width = 8
57
+ draw.annotate text_layer, 0, 0, 0, 0, choice.text
58
+
59
+ draw.stroke = 'none'
60
+ draw.annotate text_layer, 0, 0, 0, 0, choice.text
61
+ end
62
+
63
+ text_layer.resize!(1 / super_sample)
45
64
 
46
65
  img.each do |frame|
47
66
  frame.composite!(text_layer, -frame.page.x, -frame.page.y,
@@ -13,6 +13,8 @@ module MemeCaptain
13
13
 
14
14
  ImageExts = %w{.jpeg .gif .png}
15
15
 
16
+ set :root, File.join(File.dirname(__FILE__), '..', '..')
17
+
16
18
  get '/' do
17
19
  @u = params[:u]
18
20
  @tt= params[:tt]
@@ -32,6 +34,9 @@ module MemeCaptain
32
34
  curl = Curl::Easy.perform(params[:u]) do |c|
33
35
  c.useragent = 'Meme Captain http://memecaptain.com/'
34
36
  end
37
+ unless curl.response_code == 200
38
+ raise "Error loading source image url #{params[:u]}"
39
+ end
35
40
  curl.body_str
36
41
  }
37
42
 
@@ -49,21 +54,23 @@ module MemeCaptain
49
54
  end
50
55
 
51
56
  get '/g' do
52
- content_type :json
53
-
54
- processed_cache_path = gen(params)
55
-
56
- temp_url = URI(request.url)
57
- temp_url.path = processed_cache_path.sub('public', '')
58
- temp_url.query = nil
59
-
60
- perm_url = URI(request.url)
61
- perm_url.path = '/i'
62
-
63
- {
64
- 'tempUrl' => temp_url.to_s,
65
- 'permUrl' => perm_url.to_s,
66
- }.to_json
57
+ begin
58
+ processed_cache_path = gen(params)
59
+
60
+ temp_url = URI(request.url)
61
+ temp_url.path = processed_cache_path.sub('public', '')
62
+ temp_url.query = nil
63
+
64
+ perm_url = URI(request.url)
65
+ perm_url.path = '/i'
66
+
67
+ [200, { 'Content-Type' => 'application/json' }, {
68
+ 'tempUrl' => temp_url.to_s,
69
+ 'permUrl' => perm_url.to_s,
70
+ }.to_json]
71
+ rescue => error
72
+ [500, { 'Content-Type' => 'text/plain' }, error.to_s]
73
+ end
67
74
  end
68
75
 
69
76
  get '/i' do
@@ -0,0 +1,3 @@
1
+ module MemeCaptain
2
+ VERSION = '0.0.7'
3
+ end
data/lib/meme_captain.rb CHANGED
@@ -1,5 +1,9 @@
1
+ require 'meme_captain/caption'
2
+ require 'meme_captain/caption_choice'
3
+ require 'meme_captain/draw'
1
4
  require 'meme_captain/file_body'
5
+ require 'meme_captain/filesystem_cache'
2
6
  require 'meme_captain/meme'
3
7
  require 'meme_captain/mime_type'
4
8
  require 'meme_captain/server'
5
- require 'meme_captain/filesystem_cache'
9
+ require 'meme_captain/version'
data/meme_captain.gemspec CHANGED
@@ -2,9 +2,12 @@
2
2
 
3
3
  $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
4
4
 
5
+ require 'meme_captain/version'
6
+
5
7
  Gem::Specification.new do |s|
6
8
  s.name = 'meme_captain'
7
9
  s.version = '0.0.6'
10
+ s.version = MemeCaptain::VERSION
8
11
  s.summary = 'create meme images'
9
12
  s.description = s.summary
10
13
  s.homepage = 'https://github.com/mmb/meme_captain'
data/public/1.gif ADDED
Binary file
data/public/thumbs.jpg ADDED
Binary file
@@ -0,0 +1,20 @@
1
+ require 'RMagick'
2
+
3
+ # make a combined thumbnail image from a list of images for use in CSS sprites
4
+
5
+ puts 'images = ['
6
+
7
+ thumbs = Magick::ImageList.new
8
+
9
+ ARGV.sort.each do |file|
10
+ image = Magick::ImageList.new(file)
11
+ image.resize_to_fit!(0, 50)
12
+ image.each do |frame|
13
+ thumbs.push frame
14
+ puts " ['#{File.basename(file)}', #{frame.columns}],"
15
+ end
16
+ end
17
+
18
+ puts ']'
19
+
20
+ thumbs.append(false).write('thumbs.jpg')
data/views/index.erb CHANGED
@@ -1,16 +1,28 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en">
2
+ <html lan="en" xmlns:fb="https://www.facebook.com/2008/fbml">
3
3
 
4
4
  <head>
5
5
  <title>Meme Captain</title>
6
6
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
7
+ <style type="text/css">
8
+ #sourceImages {
9
+ background-color : #eee;
10
+ border-top : 1px solid #ccc;
11
+ border-bottom : 1px solid #ccc;
12
+ padding : 0em 1em 0.5em 1em;
13
+ margin : 1em 0em 1em 0em;
14
+ }
15
+
16
+ img.thumb {
17
+ background-image : url('thumbs.jpg');
18
+ height : 50px;
19
+ }
20
+ </style>
7
21
  </head>
8
22
 
9
23
  <body>
10
24
 
11
- <p><a href="/">Meme Captain</a></p>
12
-
13
- <p>Add text to images from the internet.</p>
25
+ <p><a href="/">Meme Captain</a> - add text to images from the internet</p>
14
26
 
15
27
  <div id="img"></div>
16
28
 
@@ -42,45 +54,183 @@
42
54
 
43
55
  </form>
44
56
 
45
- <img src="http://memecaptain.com/bear_grylls_thumb.jpg" onClick="$('#u').val('http://memecaptain.com/bear_grylls.jpg'); return false;" />
46
-
47
- <img src="http://memecaptain.com/most_interesting_thumb.jpg" onClick="$('#u').val('http://memecaptain.com/most_interesting.jpg'); return false;" />
48
-
49
- <img src="http://memecaptain.com/philosoraptor_thumb.jpg" onClick="$('#u').val('http://memecaptain.com/philosoraptor.jpg'); return false;" />
50
-
51
- <img src="http://memecaptain.com/scumbag_steve_thumb.jpg" onClick="$('#u').val('http://memecaptain.com/scumbag_steve.jpg'); return false;" />
52
-
53
- <img src="http://memecaptain.com/town_crier_thumb.jpg" onClick="$('#u').val('http://memecaptain.com/town_crier.jpg'); return false;" />
57
+ <div id="sourceImages">
58
+
59
+ <p>Locally hosted source images or search Bing for source images (click thumbnail to use):</p>
60
+
61
+ <%
62
+
63
+ images = [
64
+ ['all_the_things.jpg', 67],
65
+ ['bear_grylls.jpg', 45],
66
+ ['business_cat.jpg', 50],
67
+ ['courage_wolf.jpg', 50],
68
+ ['dwight_schrute.jpg', 73],
69
+ ['fry.png', 67],
70
+ ['good_guy_greg.jpg', 51],
71
+ ['grandma.jpg', 68],
72
+ ['insanity_wolf.jpg', 50],
73
+ ['internet_husband.jpg', 45],
74
+ ['joseph_ducreux.jpg', 38],
75
+ ['me_gusta.png', 50],
76
+ ['most_interesting.jpg', 40],
77
+ ['ok.png', 50],
78
+ ['philosoraptor.jpg', 50],
79
+ ['rage.png', 50],
80
+ ['sap.jpg', 50],
81
+ ['scumbag_steve.jpg', 50],
82
+ ['seriously.png', 50],
83
+ ['slowpoke.jpg', 50],
84
+ ['success_kid.jpg', 50],
85
+ ['town_crier.jpg', 75],
86
+ ['troll_face.jpg', 55],
87
+ ['trolldad.png', 50],
88
+ ['trolldad_dancing.png', 50],
89
+ ['tyler_durden.jpg', 50],
90
+ ['xzibit.jpg', 77],
91
+ ['y_u_no.jpg', 67],
92
+ ['yao_ming.jpg', 42],
93
+ ]
94
+
95
+ pos = 0
96
+ images.each do |name, offset|
97
+ %>
98
+ <img src="1.gif" class="thumb" style="width : <%= offset %>px; background-position : <%= pos %>px 0px;" onClick="$('#u').val('http://memecaptain.com/<%= name %>'); return false;" />
99
+ <%
100
+ pos -= offset
101
+ end
102
+ %>
103
+
104
+ <div id="bingImageResults"></div>
54
105
 
55
- <img src="http://memecaptain.com/troll_face_thumb.jpg" onClick="$('#u').val('http://memecaptain.com/troll_face.jpg'); return false;" />
56
-
57
- <img src="http://memecaptain.com/yao_ming_thumb.jpg" onClick="$('#u').val('http://memecaptain.com/yao_ming.jpg'); return false;" />
106
+ <form action="" method="get">
107
+ <input type="text" id="bingSearch" />
108
+ <input type="button" id="bingButton" value="Bing Image Search" />
109
+ </form>
58
110
 
59
- <img src="http://memecaptain.com/y_u_no_thumb.jpg" onClick="$('#u').val('http://memecaptain.com/y_u_no.jpg'); return false;" />
111
+ </div>
60
112
 
61
113
  <p>by Matthew M. Boedicker <a href="mailto:matthewm@boedicker.org">matthewm@boedicker.org</a></p>
62
114
 
63
- <p><a href="https://github.com/mmb/meme_captain">source code</a></p>
115
+ <p><a href="https://github.com/mmb/meme_captain">source code and API</a></p>
64
116
 
65
117
  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js"></script>
66
118
  <script>
119
+ function showBingImages(resp) {
120
+ var div = $('#bingImageResults'),
121
+ searchResponse = resp.SearchResponse,
122
+ image = searchResponse.Image;
123
+
124
+ div.empty();
125
+
126
+ if (image.Total > 0) {
127
+ $.each(image.Results, function (i, img) {
128
+ div.append($('<img />').attr('src', img.Thumbnail.Url).click(
129
+ function () { $('#u').val(img.MediaUrl); }
130
+ ));
131
+ });
132
+ } else {
133
+ div.append($('<p />').append(
134
+ 'No results for "' + searchResponse.Query.SearchTerms + '".'));
135
+ }
136
+ }
137
+
138
+ function bingImageSearch() {
139
+ var bingSearch = $('#bingSearch'),
140
+ bingSearchVal = bingSearch.val();
141
+
142
+ if (bingSearchVal.match(/[^\s]/)) {
143
+ bingSearch.val('');
144
+
145
+ $.ajax({
146
+ type : 'GET',
147
+ url : 'http://api.bing.net/json.aspx',
148
+ data : {
149
+ AppId : 'A120380275E87F0071F163210211F0592D0E964C',
150
+ Query : bingSearchVal + ' imagesize:Medium',
151
+ Sources : 'Image',
152
+ Version : '2.0',
153
+ Market : 'en-us',
154
+ 'Image.Count' : 5,
155
+ 'Image.Offset' : 0,
156
+ JsonType : 'callback'
157
+ },
158
+ dataType : 'jsonp',
159
+ jsonpCallback : 'showBingImages',
160
+ jsonp : 'JsonCallback'
161
+ });
162
+ }
163
+ }
164
+
67
165
  $(function () {
166
+ var bingSearch,
167
+ bingSearchVal,
168
+ imgDiv;
169
+
68
170
  if (window.location.search.match(/u=[^&$]/)) {
69
- var imgDiv = $('#img');
171
+ $('#tt').focus();
172
+
173
+ imgDiv = $('#img');
70
174
 
71
175
  imgDiv.append($('<p />').append('Creating image ...'));
72
176
 
73
- $.get('/g' + window.location.search, function(data) {
177
+ $.get('/g' + window.location.search, function (data) {
74
178
  var img = $('<img />').attr('src', data.tempUrl),
75
179
  tempLink = $('<a />').attr('href', data.tempUrl).append(data.tempUrl),
76
- permLink = $('<a />').attr('href', data.permUrl).append(data.permUrl);
180
+ permLink = $('<a />').attr('href', data.permUrl).append(data.permUrl),
181
+ tweetLink = $('<a />').attr({
182
+ href : 'http://twitter.com/share',
183
+ 'class' : 'twitter-share-button',
184
+ 'data-count' : 'none',
185
+ 'data-text' : ' ',
186
+ 'data-url' : data.permUrl
187
+ }).append('Tweet');
77
188
 
78
189
  imgDiv.empty().append(
79
190
  img).append(
80
191
  $('<p />').append('Temporary image url: ').append(tempLink)).append(
81
- $('<p />').append('Permanent image url: ').append(permLink));
192
+ $('<p />').append('Permanent image url: ').append(permLink)).append(
193
+ $('<p />').append('Temporary url not guaranteed to work forever. Permanent url should work as long as source image exists.')).append(
194
+ $('<p />').append(tweetLink).append($('<script />').attr('src',
195
+ 'http://platform.twitter.com/widgets.js')));
196
+
197
+ // Facebook like
198
+ imgDiv.append(
199
+ $('<div />').attr('id', 'fb-root')).append(
200
+ $('<fb:like />').attr({
201
+ href : data.tempUrl,
202
+ send : 'true',
203
+ width : '450',
204
+ show_faces : 'true',
205
+ font : ''
206
+ }));
207
+
208
+ window.fbAsyncInit = function () {
209
+ FB.init({
210
+ appId : '108445492580525',
211
+ status : true,
212
+ cookie : true,
213
+ xfbml : true
214
+ });
215
+ };
216
+
217
+ imgDiv.append($('<script />').attr({
218
+ src : 'http://connect.facebook.net/en_US/all.js'}));
219
+ }).error(function (j) {
220
+ imgDiv.empty().append($('<p />').text(j.responseText));
82
221
  });
222
+ } else {
223
+ $('#u').focus();
83
224
  }
225
+
226
+ $('#bingSearch').keypress(function (event) {
227
+ if (event.which == 13) {
228
+ event.preventDefault();
229
+ bingImageSearch();
230
+ }
231
+ });
232
+
233
+ $('#bingButton').click(bingImageSearch);
84
234
  });
85
235
  </script>
86
236
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: meme_captain
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 17
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 6
10
- version: 0.0.6
9
+ - 7
10
+ version: 0.0.7
11
11
  platform: ruby
12
12
  authors:
13
13
  - Matthew M. Boedicker
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-05-15 00:00:00 -04:00
18
+ date: 2011-11-08 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -114,17 +114,24 @@ extra_rdoc_files: []
114
114
  files:
115
115
  - .gitignore
116
116
  - COPYING
117
- - README.textile
117
+ - README.md
118
118
  - config.ru
119
119
  - img_cache/source/.gitignore
120
120
  - lib/meme_captain.rb
121
+ - lib/meme_captain/caption.rb
122
+ - lib/meme_captain/caption_choice.rb
123
+ - lib/meme_captain/draw.rb
121
124
  - lib/meme_captain/file_body.rb
122
125
  - lib/meme_captain/filesystem_cache.rb
123
126
  - lib/meme_captain/meme.rb
124
127
  - lib/meme_captain/mime_type.rb
125
128
  - lib/meme_captain/server.rb
129
+ - lib/meme_captain/version.rb
126
130
  - meme_captain.gemspec
131
+ - public/1.gif
132
+ - public/thumbs.jpg
127
133
  - public/tmp/.gitignore
134
+ - script/thumb_sprites.rb
128
135
  - views/index.erb
129
136
  has_rdoc: true
130
137
  homepage: https://github.com/mmb/meme_captain
data/README.textile DELETED
@@ -1,20 +0,0 @@
1
- Ruby gem to create meme images (images with text added at the top and bottom).
2
-
3
- Also includes Sinatra app that exposes API over HTTP which is currently
4
- running "http://memecaptain.com/":http://memecaptain.com/
5
-
6
- Works with animated gifs.
7
-
8
- <pre>
9
- <code>
10
- require 'meme_captain'
11
-
12
- require 'open-uri'
13
-
14
- open('http://image_from_web_or_local_file.jpg', 'rb') do |f|
15
- i = MemeCaptain.meme(f, 'test', '1 2 3')
16
- i.display
17
- i.write('out.jpg')
18
- end
19
- </code>
20
- </pre>