GIMP: A filter for fake 3D text effect

Introduction

I was editing some pictures, and I found out that I had to apply a certain effect to text multiple times.

While making this effect manually wasn't particularily difficult, always doing it that way was very tiring and bothersome.

Thankfully, GIMP can be extended so that users can write their own tools and filters. In this article, I will explain what I did to create a filter that would apply this effect automatically.

Since it's about creating a filter, this article is more programming-oriented, unlike the typical GIMP tutorial that can be found on the web.

The effect

As the article's title suggest, the desired effect was to make some text, added with the Text tool, look like it was tridimensional.

However, it's not just 3D text, but a specific perspective was needed. So I'm not sure if “3D text” is correct. Maybe “outset text”?

Anyway, the desired result is the following:

Result example

To create this effect, these are the steps I followed:

  1. Change the text color (in my case white)
  2. Use “Alpha to selection” on the text layer
  3. Create a new layer below the text and select it
  4. Grow the selection by 1
  5. Fill the selection with the shadow color (in my case black)
  6. Remove the selection and duplicate the shadow layer
  7. Move the second layer 1 pixel towards the bottom right corner
  8. Merge the two shadow layers

With this method the text would have a border and a shadow to give the fake 3D effect.

The filter

Now that the necessary steps have been listed, all that's needed is to convert them to a filter.

As such, it's time to write some script-fu.

First, the usual boilerplate to register the script:

(script-fu-register
 "vanni-fake-3d-text-effect"
 _"Fake 3D _Text"
 _"Gives the text a fake 3D effect.\
The effect will be applied to the selected text layer and will\
create a new layer with the result."
 "Alessio Vanni <vannilla@firemail.cc>"
 "Alessio Vanni"
 "September 3, 2018"
 "RGBA, GRAYA"
 SF-IMAGE "Image" 0
 SF-DRAWABLE "Drawable" 0
 SF-COLOR _"Text color" '(255 255 255)
 SF-COLOR _"Shadow color" '(0 0 0))

 (script-fu-menu-register "vanni-fake-3d-text-effect"
                          "<Image>/Filters/Alpha to Logo")
      

The GIMP manual has a fairly good explanation about these two functions, so I'll just explain some details: to begin with, this filter requires some transparency, so with RGBA and GRAYA the menu entry for this filter will be enabled only if the picture is not indexed and has an alpha layer available.

After that, since this is a filter, and not a tool, it must require an image and the selected layer, so SF-IMAGE and SF-DRAWABLE are used.

Lastly, I placed the filter inside the “Alpha to Logo” submenu because honestly I'm not really sure where to put it otherwise.

The vanni-fake-3d-text-effect function looks like this:

(define (vanni-fake-3d-text-effect image layer fore back)
  (cond ((zero? (car (gimp-item-is-text-layer layer)))
         (gimp-message _"Layer is not a text layer!"))
        (else
         (gimp-image-undo-group-start image)
         (apply-fake-3d image layer fore back)
         (gimp-image-undo-group-end image)
         (gimp-display-flush))))
      

Since the filter works only on text, it first checks if the selected layer is indeed a text layer. If not, it shows a message and terminates.

With gimp-image-undo-group-start and gimp-image-undo-group-end, the function will make it so that after applying the filter, it's possible to undo it in a single step, instead of having to undo all the changes done by the filter's workings.

These two function are essentially required when developing a filter.

gimp-display-flush makes sure that the changes are displayed (there can be cases when it doesn't happen, for one reason or another), and apply-fake-3d actually does the work.

Since this last function does many thing, I will explain it snippet by snippet, instead of writing it fully.

The signature isn't special:

	(define (apply-fake-3d image layer fore back)
      

After that, the function sets up the working environment:

(gimp-context-push)
(gimp-context-set-defaults)
(gimp-context-set-foreground fore)
(gimp-context-set-background back)
(gimp-image-select-item image CHANNEL-OP-ADD text-layer)
      

The filter uses functions that depend on some settings that can be changed by the user. To avoid strange results, these changes (the “context”) should be reset to the default values. However, before that, the user-defined context should be preserved. This is done with gimp-context-push. gimp-context-set-defaults will reset the context as desired.

With gimp-context-set-foreground and gimp-context-set-background, the function will set up the colors used to apply the filter.

Lastly, the function will select the text, in the same way as “Alpha to selection”.

Now, some variables are defined:

(let* ((text-text (car (gimp-text-layer-get-text layer)))
       (text-off (gimp-drawable-offsets layer))
       (fore-layer (car (gimp-layer-new image
                                        (+ (car (gimp-drawable-width layer)) 3)
                                        (+ (car (gimp-drawable-height layer)) 3)
                                        (car (gimp-drawable-type layer))
                                        text-text
                                        100
                                        NORMAL-MODE)))
       (back-layer1 (car (gimp-layer-copy fore-layer TRUE)))
       (back-layer2 (car (gimp-layer-copy fore-layer TRUE))))
      

Right now the important ones are fore-layer, back-layer1 and back-layer2. These are the layers that will be used by the filter to apply the effect.

The three layers are slightly bigger than the text layer. This is to make sure the filter has enough room.

Now that there are layers, the next step is obviously to add them to the image:

(gimp-image-insert-layer image back-layer2 0 -1)
(gimp-image-insert-layer image back-layer1 0 -1)
(gimp-image-insert-layer image fore-layer 0 -1)
(gimp-drawable-fill fore-layer TRANSPARENT-FILL)
(gimp-drawable-fill back-layer1 TRANSPARENT-FILL)
(gimp-drawable-fill back-layer2 TRANSPARENT-FILL)
      

The layers are also initialized with transparency.

Adding the layers to the image means that it's possible to edit them:

(gimp-layer-translate fore-layer (car text-off) (cadr text-off))
(gimp-layer-translate back-layer1 (car text-off) (cadr text-off))
(gimp-layer-translate back-layer2 (car text-off) (cadr text-off))
      

When layers are added, they are placed at position (0, 0), but no one guarantees that the text is there too.

Since the selection is also where the text is, if the filter were to apply the effect as-is, nothing would show up.

Thus, the three layers are first translated to the same position as the text. The purpose of the text-off variable was exactly to hold the position of the text layer.

With this part:

(gimp-edit-fill fore-layer FOREGROUND-FILL)
(gimp-selection-grow image 1)
(gimp-edit-fill back-layer1 BACKGROUND-FILL)
(gimp-edit-fill back-layer2 BACKGROUND-FILL)
(gimp-layer-translate back-layer2 1 1))
      

The function simply adds the colors to the layers. fore-layer will have the text colored with the color specifed by the “Text color” option, while the other two will be filled with the color defined by the “Shadow color” option.

The last gimp-layer-translate will move the layer towards the bottom-right corner.

The effect is complete, but I personally think that leaving the layers like this is poor form, so let's merge them in a single one:

(let ((new '()))
  (set! new (gimp-image-merge-down image fore-layer EXPAND-AS-NECESSARY))
  (set! new (gimp-image-merge-down image (car new) EXPAND-AS-NECESSARY))
  (gimp-item-set-name (car new) (string-append text-text " 3D")))
      

This way, the layers will be merged, and the resulting layer's name will reflect that the selected text layer is now tridimensional.

Before closing the function definition, there is some clean-up to do:

(gimp-item-set-visible layer FALSE)
(gimp-selection-none image)
(gimp-context-pop)
      

First, the text layer is made invisble. Since the effect has been applied, it's not needed anymore, but the user might want to reuse it for something else.

Then, selection is canceled. It's not really required, but I think it's nice.

Finally, the user-defined context that was saved using gimp-context-push is restored, using gimp-context-pop.

Now that it's complete, all that's needed is to place the file with this script in the appropriate folder.