The challenge of creating a decent meme generator script
A few weeks ago, I set about creating a new meme generator; partly for my own amusement, and partly because I was not satisifed with the alternatives available. They either used flash, were slow to load, had a terrible font face, or all of the above.
My objectives in creating a new meme generator were the following:
I'm proud to say that after three weeks of mostly trial and error, I have accomplished these basic objectives and much more (such as analytics for memes and easy meme-chaining). So that others don't have to reinvent the wheel, I'm releasing the basic source code for the meme generator. It's not as fully polished as the all-out JackMeme generator, but it should be more than enough to point people in the right direction.
In the beginning, there was GD...
I started out first by using the GD Library compiled with PHP, using imagettftext to write the text on top of the image. This produced something like the following:
Not bad, but not ideal either. For one, it's not centered. For another, there's no stroke! Fortunately, fixing the latter was pretty simple thanks to a function by John Ciacia: imagettfstroketext. Plugging his function in yielded this:
We got stroke! Not too shabby. But, still not centered. This proved somewhat more difficult to do, but what I ended up doing is using imagettfbox to draw the text and figure out its width based on that.
After achieving all the above, I was able to create the following:
Not bad. We have stroke, centered text, and I was able to draw a bottom line using imagettfbox again. To do that, I got the height of the bottom text using imagettfbox, then subtracted that from the total height of the image and plugged that value into the image using imagettfstroketext.
But, it still looked kind of shabby. In fact as I learned, it's pretty easy to create good looking text at large font sizes. It's much harder at small font sizes. Here's an example:
That looks pretty bad. The line spacing is uneven, the stroke isn't big enough, and the letters even appear slightly off. Am I still even using Impact for my font?
This is the problem a lot of meme generators run into, and they usually solve it by increasing the width of the stroke. That's what I did to at first:
In some ways, though, this actually looks worse than before. The text is somewhat more legible, but in a very loud sort of way. Instead of complementing the meme, it's contrasting with it. However, I was tired, and content to leave it be.
...and then there was Image Magick
But, I couldn't leave it be for long. It just did not look good, and my objective to create smooth text had not yet been met. So, I went back to the drawing board and decided to try out Image Magick compiled with PHP.
If you take a look at the source code, you'll notice that the code for IM is much longer than the code for GD, and this put me off at first. I didn't want to deal with it when what I had was already better than many meme generators. Nonetheless, I dove in and I'm glad I did.
Image Magick is much, much easier to work. It took me hours before I stumbled upon John Ciacia's stroke function for GD text, but for IM, you can do it out of the box. I was happy, I was ecstatic, and then I produced this:
Wow, that looks bad. That's worse than anything I ever did with GD.
Now, I wish I could point to all the resources that helped me in solving this problem, but unfortunately, I didn't bookmark them and now I can't find them. However, suffice to say I extensively combed Stack Overflow for the solution, the comments on php.net, and numerous Image Magick resources.
What I ended up doing was using this code:
$image = new Imagick($meme_img); // Draw the initial text $draw = new ImagickDraw(); $draw->setFont($font); $draw->setFontSize( 100 ); // Set to a large font to look good. We'll resize it later. $draw->setStrokeAntialias(true); $draw->setTextAntialias(true); $draw->setGravity(Imagick::GRAVITY_NORTH); $draw->setFillColor('#fff'); // Save text to image $textOnly = new Imagick(); $textOnly->newImage(3100,3100, "transparent"); $textOnly->annotateImage($draw, 21, 101, 0, $caption); // Create the stroke $draw->setFillColor('#000'); $draw->setStrokeColor('#000'); $draw->setStrokeWidth((.04 * $strokeSize)*100); // Save the stroke to an image $strokeImage = new Imagick(); $strokeImage->newImage(3100,3100, "transparent"); $strokeImage->annotateImage($draw, 20, 100, 0, $caption); // Combine the text and the stroke $strokeImage->compositeImage($textOnly, imagick::COMPOSITE_OVER, 0, 0, Imagick::CHANNEL_ALPHA ); $strokeImage->trimImage(0); // For this demo, we're resizing the generated text // to 300px width. Ideally, this is not what you would do. Instead, // you'd get the width of the text as it would appear with // a user inputted font size, then resize it to that width. $strokeImage->resizeImage(450,0, imagick::FILTER_CATROM, 0.9, false); $image->compositeImage($strokeImage, imagick::COMPOSITE_OVER, 15, 15, Imagick::CHANNEL_ALPHA ); // Destroy all the layers! $strokeImage->clear(); $strokeImage->destroy(); $textOnly->clear(); $textOnly->destroy(); $draw->clear(); $draw->destroy(); header('Content-type: image/jpeg'); echo $image;
So what's going on here? First, I draw the text using Draw, and save that to its own image. I then draw the stroke separately, and then combine the two using compositeImage. I then redraw that on top of the original image, and voila!
So now the text looks great, but again I'm stuck at square 1 because the text isn't centered. Fortunately, fixing this is just a simple matter of using imagettfbox again to draw the text, then get the width of the text based on that. You resize to that (instead of 450 as in the code above), and offset accordingly using compositeImage.
At last, here's the final result from JackMeme's current meme generator:
The text is clear and legible at all sizes, appears nice and smooth, and it just looks great overall. Even better, I was able to achieve this without using heavy text shadowing like Livememe does to trick the eye into ignoring jagged edges.
In fact, the cool thing about Image Magick is I'm now able to do even more with it than I could with GD. I can do image effects like blur and implode, change the text color very easily, and even allow people to add text on gifs:
Don't tell me the text on that gif doesn't look beautiful, because it does.
Unlike the JackMeme generator, which does some text shadowing, text-wrapping, font-sizing, and allows gifs, the demo does not. The demo is simply meant to be a very simple script to help point people in the right direction. It's the result of dozens upon dozens of hours of work, and my hope is that others who are out there creating meme generators will be able to use it as well.
In order to serve content as fast as possible, the JackMeme blog does not allow commenting. However, please refer to our Subreddit (linked in the header) for any comments or questions, or send an email to [email protected] .