In this post I am going to show you:
- How to add text to an image with RMagick
- How to overlay one image on top of another using “composite” in RMagick
- How to solve the “text wrap” problem so that the text does not run of the edge of the image
The situation: Let’s pretend that we are developing a “cooking” website.
- Each time a famous chef posts a recipe, we want to let the world know.
- So, we generate an image to post on our Instagram automatically
- The image will have the chef’s DP as well as the name of the recipe he/she just created.
- Also, so that the name is always readable, we will add a gradient overlay to the image.
Step 1: Install The RMagick Gem (And The Underlying ImageMagick Library)
You may or may not need this section. But, for the sake of completeness, I have gathered all the information in one place.
If you feel that the information below is out of date in some way, you can check with the official RMagick Github Repo here.
Before you install RMagick, you need to install ImageMagick. RMagick is a “wrapper” library that helps you use ImageMagick in Ruby.
As the official docs puts it..
RMagick is an interface between the Ruby programming language and the ImageMagick image processing library.
Ubuntu users, you can run:
sudo apt-get install libmagickwand-dev
Centos users, you can run:
sudo yum install gcc ImageMagick-devel make which
Arch Linux users, you can run:
pacman -Syy pkg-config imagemagick
Alpine Linux users, you can run:
apk add pkgconfig imagemagick imagemagick-dev imagemagick-libs
macOS users, you can run:
brew install pkg-config imagemagick
- Install latest Ruby+Devkit package which you can get from RubyInstaller for Windows.
- You might need to configure
PATHenvironment variable to where the compiler is located. (Ex:
ImageMagick-7.XXXX-Q16-x64-static.exe) binary from Windows Binary Release, or you can download ImageMagick 6 from Windows Binary Release.
- Install ImageMagick. You need to turn on checkboxes
Add application directory to your system pathand
Install development headers and librarries for C and C++in an installer for RMagick.
Installing The RMagick Gem
Add to your Gemfile:
Installing via RubyGems
gem install rmagick
Step 2: RMagick Read – How To Load An Image (A Few Different Ways Based On Your Needs)
Option 1: Loading An Image From A File
Let’s look at some code..
Warning: Windows Users Vs Mac & Linux Users
The code on line 5 works on Mac and Linux. It will open up the file referenced on line 4 in a new window. This is great for quickly looking at the output your script is producing.
This does not work on Windows. So, you will have to write the output to a new file like so..
This will create a file called “output.jpg” in the current folder. You can open up that file to see what’s going on.
What Is The Deal With The “.first”?
You might have noticed, on line 4 above, we pass the path of the image, but then we call “.first” on the result. What’s the deal with that?
Well, you see, RMagic has the ability to read “multi-layer images”. For example GIF files.
The RMagick official documentation says that:
Note: Because an image file can contain multiple images or multiple image layers, read always returns an array containing 0 or more elements, one element for each image or image layer in the file.
So, Let’s Use That & Split A GIF Into It’s Frames
Here is a GIF of a globe I took from Wikipedia.
You can use this little script, to split this GIF into all the component frames..
Below is a screenshot of the folder where I saved all these frames..
Option 2: Loading A Remote Image (From A URL)
RMagick does not seem to directly support the reading of images from a URL. But, you can use “open-ui” from the Ruby Standard Library in combination with loading image “from_blob” class method as follows:
Option 3: Generating A New Image Via RMagick
Lastly, you can generate an image with a gradient using a string like so..
The above script generates a gradient PNG with a size of 200px by 200px.
Now, you are probably wondering where “gradient:rgba(0,0,0,0.7)-rgba(0,0,0,0.0)” comes from and what else is possible. Well, this comes from the ImageMagick documentation on gradients.
In fact, you can generate an image with all sorts of crazy and amazing things. Here is the complete documentation of “Canvas Creation” from ImageMagick.
Step 3: RMagick Composite – How To Overlay One Image On Top Of Another
So, our goal in this section is going to be to take our recipe image, overlay it with a gradient and finally put the chef’s DP on top of it.
Let’s look at the code for doing this, and then we will break it down..
The output of this above script is as follows:
A lot of the code we have seen before. So, let’s just focus on the new bits:
recipie_image = recipie_image.composite( chef_image, 30, 30, OverCompositeOp)
Once we have an “Image” object in a variable, we can overlay another Image object on top of it, using the “composite” method. Here is the official documentation of the method.
The parameters of the “composite” method are as follows:
- First Param: Image to overlay (in our case chef_image)
- Second and Third Param: The x- and y-offset of the composited image, measured from the upper-left corner of the image. (in our case 30, 30)
- Third Param: A CompositeOperator (this one is going to take some more explanation. We will go into that below)
What Is A CompositeOperator? How To Choose A Value For That? What Are The Options?
When you put one image on top of another, you can decide how the pixels of the two images interact with each other.
If you want to visually understand this, you can have a look at this video. It shows Photoshop allows you to use various “Blending Modes”. It’s a great short explanation.
If you decide you want to actually play with all this and you don’t have Photoshop you can do so with a great open source version of Photoshop that runs within your browser called Photopea.
Okay, now that you understand what the CompositeOperator is all about. Let’s look at how you can select various options.
And if you need to know more about what each of these mean, you can check the ImageMagick docs about the same here.
So, coming back to the line..
recipie_image = recipie_image.composite( chef_image, 30, 30, OverCompositeOp)
The “OverCompositeOp” is just the simple “Over” blending mode where one image is just simply put “Over” the other image. No mixing of pixels.
Other Lines Of Interest: Scaling Down The Image With RMagic
chef_image = chef_image.scale(0.5)
This line is easy enough to understand. Here are the docs for the “scale” method.
Other Lines Of Interest: Getting Image Dimensions With RMagick
width, height = recipie_image.columns, recipie_image.rows
Basically, every image has a “columns” and a “rows” method. You can use that to get the width and height of the image.
Note (There is a faster way to get the dimensions): If all you need to do is get the width and height of the image, there is a more efficient way. It’s called “ping”.
The official docs say: It omits the pixel data. Only the attributes are stored. This method is faster than the “read” method and uses less memory.
Step 3: RMagick Annotate – How To Write Text On The Image
Let’s look at some code to generate an image with text on it in a custom font.
The result of the above script is:
I have used a fancy cursive font from Google fonts, just to show that it’s easy to use any font you want. You just need a TTF file.
As you can see, there is a big problem with this image. The text is not visible. It is larger than the image and has been cut. We will fix this problem in the next section.
First, let us properly understand the code.
Introducing The “Draw” Class In RMagick
Draw is the class used in RMagick to draw shapes and text to an image.
Think of it this way: We are creating a new “Drawing”. Since this is ruby code, the drawing is an object. We can set various properties on the object. And when we are ready we finally use the “annotate” method to stick the drawing onto an image.
Here is a snippet of the drawing code in action:
The “annotate” method
Here is a link to the official documentation of the method.
The method takes in the following parameters: (img, width, height, x, y, text)
- img: the image on which we need to put the text
- width: width of the rectangle within which the text is positioned
- height: height of the rectangle within which the text is positioned
- x: the “left” of the rectangle in which the text is positioned (top left being 0,0)
- y: the “top” of the rectangle in which the text is positioned (top left being 0,0)
- text: the text to be written on the image
As you can see, it seems like RMagick draws a virtual rectangle using the width, height, x and y. Inside of this virtual rectangle the text is placed.
NOTE: RMagick creates a virtual box but DOES NOT DO ANY WORD WRAPPING ON ITS OWN! We have to implement that ourselves. See the next section for details.
Gravity: What Does That Mean?
You might have noticed the line.
text.gravity = NorthWestGravity
You see, in the “virtual box” that RMagic creates, the text is pulled by the “gravity” setting. Here is the diagram from the RMagick docs.
Whatever you set the gravity setting to, the text gets pulled in that direction within the box as shown in the image above. Here is the list of the possible gravity values from the docs.
Possible gravity settings:
- ForgetGravity: Don’t use gravity.
- NorthWestGravity: Position object at top-left of region
- NorthGravity: Position object at top-center of region
- NorthEastGravity: Position object at top-right of region
- WestGravity: Position object at left-center of region
- CenterGravity: Position object at center of region
- EastGravity: Position object at right-center of region
- SouthWestGravity: Position object at left-bottom of region
- SouthGravity: Position object at bottom-center of region
- SouthEastGravity: Position object at bottom-right of region
As you can see, the NorthWestGravity is used in the code example shared above.
Step 4: Correcting The Text Wrap Problem With A Helper Class
Below is the code for the class..
You can use the class in the following way..
I have written the code in pretty self explanatory manner:
The variable names tell you what is needed in that position. Basically it’s all the things that are needed to figure out the size of the text.
Finally, you can call the method: “get_text_with_line_breaks” and get the text with line breaks like so: “Hey this is\nsome text!”
Finally, Let’s Complete Our Project..
Here is the final script. If you were with me so far, the final script should be easy to follow..
And the final result is..