# Displaying images with text

**URL:** <https://discourse.julialang.org/t/displaying-images-with-text/102694>\
**Category:** Signal and Image Processing\
**Tags:** question, images\
**Created:** [August 10, 2023, 8:27pm UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694 "2023-08-10T20:27:58Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![cpfiffer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cpfiffer/32/208747_2.png) [@cpfiffer](https://discourse.julialang.org/u/cpfiffer)\
**Post date:** [August 10, 2023, 8:27pm UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/1 "2023-08-10T20:27:58Z")

</div>

I’m working on adapting the fast.ai [fastbook](https://github.com/fastai/fastbook) with a few other people. You can find our work slowly happening via our [website](https://www.deep-learning.club) or by the Quarto book [here](https://book.deep-learning.club).

As part of this, I want to build a little tool to view images with the class printed right above them. To do this, I need to do the following:

1. Build a white pane that is slightly larger than the underlying image.
2. Paste the underying image on top of the white pane.
3. Add text to the top of the white pane.

Anyone have any ideas on how to go about this? I looked at most of the functions in Images.jl and its sattelite packages but I was not able to find any way to compose images or to add text. Happy to be pointed to something if I missed it!

---

<div class="post-metadata">

**Author:** ![stillyslalom](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stillyslalom/32/45687_2.png) [@stillyslalom](https://discourse.julialang.org/u/stillyslalom)\
**Post date:** [August 10, 2023, 10:30pm UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/2 "2023-08-10T22:30:50Z")

</div>

Once you start to wade into composing and annotating images, it’s time to bail out to a plotting library. Makie makes this sort of thing pretty easy:

```julia
julia> f = Figure()

julia> for idx in CartesianIndices(imgs)
           ax = Axis(f[idx.I...], aspect=DataAspect(), 
               title="Letter $(rand('a':'z'))")
           image!(ax, imgs[idx])
           hidedecorations!(ax)
       end

```

 ![image](https://global.discourse-cdn.com/julialang/original/3X/5/7/573e191c2fdce0187fffe8a6c85ba288355b2c0d.png)

---

<div class="post-metadata">

**Author:** ![joa-quim](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/joa-quim/32/227_2.png) [@joa-quim](https://discourse.julialang.org/u/joa-quim)\
**Post date:** [August 10, 2023, 10:32pm UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/3 "2023-08-10T22:32:01Z")

</div>

Should be easy with GMT. The “image” module I think.

---

<div class="post-metadata">

**Author:** ![mgkuhn](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mgkuhn/32/6276_2.png) [@mgkuhn](https://discourse.julialang.org/u/mgkuhn)\
**Post date:** [August 11, 2023, 8:34pm UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/4 "2023-08-11T20:34:55Z")

</div>

If you want to do it without a plotting library, you can simply copy parts of your image matrix around to compose your result.

You can render the text in Cairo, but when doing so I had to overcome a few pitfalls (e.g. for compatibility with Cairo, I just learnt you need to use the `Images.RGB24` color type, and documentation for Cairo.jl seems largely absent).

```julia
# Suround a bitmap image with a white margin and add text there

using Images
using Cairo
using TestImages

img = testimage("airplaneF16")
margin = 40
title = "U.S. Air Force F-16"

# prepare a white canvas (image size plus margin)
canvas = fill(RGB24(1.0,1.0,1.0), size(img) .+ 2*margin)

# copy image centered onto canvas
canvas[margin+1:margin+size(img,1),
       margin+1:margin+size(img,2)] = img

# prepare another canvas for rendering the text into top margin
# (because Cairo draws with X and Y axis fliped relative to Images)
header = fill(RGB24(1.0,1.0,1.0), (size(canvas,2), margin))
c = CairoImageSurface(header)
cr = CairoContext(c)

# render the text
select_font_face(cr, "Sans", Cairo.FONT_SLANT_NORMAL,
                 Cairo.FONT_WEIGHT_NORMAL)
set_font_size(cr, 20.0)
move_to(cr, (size(header, 1) - textwidth(cr, title)) / 2,
        (margin + textheight(cr, title)) / 2)
set_source_rgb(cr,0,0,0) # black
show_text(cr, title)

# copy the x/y flipped top margin back onto main canvas
canvas[axes(header')...] = header'

# Images.save seems to have a bug when receiving Matrix{RGB24}, so convert
Images.save("test.png", RGB{N0f8}.(canvas))

```

---

<div class="post-metadata">

**Author:** ![cormullion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cormullion/32/49131_2.png) [@cormullion](https://discourse.julialang.org/u/cormullion)\
**Post date:** [August 12, 2023, 8:57am UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/5 "2023-08-12T08:57:18Z")

</div>

Nice code, Markus.

But, it did remind me of why I found Cairo.lj a bit awkward to write in. All those underscores! 🙂

 ![Screenshot 2023-08-12 at 09.49.53](https://global.discourse-cdn.com/julialang/original/3X/e/1/e1e4b9379b49b52be37c49d12103a07557b3fe7d.jpeg)

> **Code (for Quarto docs)**
>
> ```julia
> using Images, TestImages
> import Luxor as 🪲
> 
> function imagecaption(img, caption = "";
> margin = 80,
> corner_radius = 30,
> fsize = 16)
> w, h = size(img)
> d = 🪲.Drawing(w + 2margin, h + 2margin, :png)
> 🪲.origin()
> 🪲.sethue("papayawhip")
> 🪲.box(Point(0, 0), w + 2margin, h + 2margin, corner_radius, :fill)
> 🪲.placeimage(img, centered = true)
> 🪲.sethue("black")
> 🪲.fontsize(fsize)
> 🪲.fontface("BricolageGrotesque-ExtraBold")
> 🪲.label(caption, :S, midpoint(boxtopleft(), boxtopright()), 
> offset = margin / 2)
> 🪲.finish()
> return d
> end
> 
> img = testimage("cameraman")
> w, h = size(img)
> display(imagecaption(img, "I'm Mandy the mandrill", fsize=30))
> 
> ```

---

<div class="post-metadata">

**Author:** ![Ashwani\_Rathee](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ashwani_rathee/32/49551_2.png) [@Ashwani\_Rathee](https://discourse.julialang.org/u/Ashwani_Rathee)\
**Post date:** [August 12, 2023, 9:18am UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/6 "2023-08-12T09:18:02Z")

</div>

Okay, I like the ideas from @stillyslalom, @mgkuhn and @cormullion. But I think there is a gap to fill here from image related dataset visualization pov.

- Makie solution: Makie is a great high level solution but its heavy like quite heavy
- Cairo+Luxor: Very flexible and powerful but quite low level for workflow needed by @cpfiffer

I think the solution is likely to eventually come in form of FreeTypeAbstraction.jl(for text render) being part of ImageDraw.jl, to support dataset visualization and such at a slight higher level in form of package extension if MosaicViews.jl is available. Very light and just what we need in a dynamic workflow in my opinion. It could also come from ImageView.jl but right now I think it’s bit heavy.

I would have wanted it in MosaicViews.jl itself but it’s supposed to play role for general mosaic of matrix viewer: [Feature Request:Labels on the images · Issue #25 · JuliaArrays/MosaicViews.jl · GitHub](https://github.com/JuliaArrays/MosaicViews.jl/issues/25)

---

<div class="post-metadata">

**Author:** ![stemann](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stemann/32/4030_2.png) [@stemann](https://discourse.julialang.org/u/stemann)\
**Post date:** [August 12, 2023, 9:29am UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/7 "2023-08-12T09:29:59Z")

</div>

I second the idea of a Makie or (better due to being lighter?) Cairo/Luxor solution.

I would like a solution to be usable both from a Pluto notebook (easily interpolatable into e.g. HypertextLiteral’s), in a web application (output some web component I can send off as a response from an Oxygen/HTTP method handler) - or just standalone - render something in an SVG, a PDF or (rendered) in a PNG. Nice if it also works in something building on GTK/QML.

I was thinking of this in relation to an [ImageAnnotations.jl](https://github.com/IHPSystems/ImageAnnotations.jl)-based framework - to support both image classification visualisations, object detection visualisations etc. (as extension packages to ImageAnnotations.jl for MakieViews or LuxorViews etc.): Cf. [https://julialang.zulipchat.com/#narrow/stream/390029-image-processing/topic/DL.20based.20tools/near/383544112](https://julialang.zulipchat.com/#narrow/stream/390029-image-processing/topic/DL.20based.20tools/near/383544112)

… but ImageDraw also seems sufficiently low on dependencies to be an excellent place for implementation. Package extensions for ImageDraw sounds just right - but it would be nice to be able to just stick it an ImageAnnotations [`AnnotatedImage`](https://github.com/IHPSystems/ImageAnnotations.jl/blob/master/src/annotated_image.jl) and get a view back.

---

<div class="post-metadata">

**Author:** ![cormullion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cormullion/32/49131_2.png) [@cormullion](https://discourse.julialang.org/u/cormullion)\
**Post date:** [August 12, 2023, 9:40am UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/8 "2023-08-12T09:40:59Z")

</div>

ImageDraw.jl will need some enhancement to support PDF and SVG text.

---

<div class="post-metadata">

**Author:** ![Ashwani\_Rathee](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ashwani_rathee/32/49551_2.png) [@Ashwani\_Rathee](https://discourse.julialang.org/u/Ashwani_Rathee)\
**Post date:** [August 12, 2023, 9:45am UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/9 "2023-08-12T09:45:39Z")

</div>

Why would ImageDraw.jl + MosaicViews.jl need to support PDF,SVG in data science context 🙂 unless going for publication grade perhaps?

In publication grade case as I see it:

- Makie.jl provides better structure for drawing and the actual plots
- Luxor.jl provides way more flexibility

---

<div class="post-metadata">

**Author:** ![stemann](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stemann/32/4030_2.png) [@stemann](https://discourse.julialang.org/u/stemann)\
**Post date:** [August 12, 2023, 9:50am UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/10 "2023-08-12T09:50:45Z")

</div>

I have no idea what would be needed to get SVG/PDF output (but I would assume Cairo would be helpful in that regard).

I may have gone a bit too far with my wishes (to include SVG and PDF) 🙂

---

<div class="post-metadata">

**Author:** ![Ashwani\_Rathee](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ashwani_rathee/32/49551_2.png) [@Ashwani\_Rathee](https://discourse.julialang.org/u/Ashwani_Rathee)\
**Post date:** [August 12, 2023, 9:53am UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/11 "2023-08-12T09:53:43Z")

</div>

I wish Makie was lighter and had Luxor level flexibility 😁

---

<div class="post-metadata">

**Author:** ![stemann](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stemann/32/4030_2.png) [@stemann](https://discourse.julialang.org/u/stemann)\
**Post date:** [August 12, 2023, 9:57am UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/12 "2023-08-12T09:57:11Z")

</div>

> [@stemann](#):
>
> … it would be nice to be able to just stick it (ImageDraw, edit.) an ImageAnnotations [`AnnotatedImage`](https://github.com/IHPSystems/ImageAnnotations.jl/blob/master/src/annotated_image.jl) and get a view back.

Do note however that `AnnotatedImage` is not up to speed yet - at all: It needs to be transformed into something like `AnnotatedImage{TArray<:AbstractArray{T,N}}` to support actual image data - both eager- and lazily-loaded data (and not just a path to some image file as it is currently). Cf. [Slack thread on lazy loading of images](https://julialang.slack.com/archives/CB1R90P8R/p1684156559679039) from May 2023.

---

<div class="post-metadata">

**Author:** ![cormullion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cormullion/32/49131_2.png) [@cormullion](https://discourse.julialang.org/u/cormullion)\
**Post date:** [August 12, 2023, 10:02am UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/13 "2023-08-12T10:02:13Z")

</div>

> [@Ashwani\_Rathee](#):
>
> I wish Makie was lighter and had Luxor level flexibility

I think Makie can do everything you want - and if you’re alreadymaking plots with it, you really don’t need anything else. It does take a while to install, though…

---

<div class="post-metadata">

**Author:** ![stemann](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stemann/32/4030_2.png) [@stemann](https://discourse.julialang.org/u/stemann)\
**Post date:** [August 12, 2023, 10:10am UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/14 "2023-08-12T10:10:33Z")

</div>

I guess it would be enough for an ImageDraw Makie-extension to depend on MakieCore - to just get a plot recipe back?

(I have yet (how is that possible?! 😱 😆) to have the pleasure of actually using Makie … and Plots … and Luxor.)

---

<div class="post-metadata">

**Author:** ![Ashwani\_Rathee](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ashwani_rathee/32/49551_2.png) [@Ashwani\_Rathee](https://discourse.julialang.org/u/Ashwani_Rathee)\
**Post date:** [August 12, 2023, 10:14am UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/15 "2023-08-12T10:14:25Z")

</div>

Yes, that’s also an option but it could endup becoming something that’s difficult and seem unneccessary to maintain for a few nice features

---

<div class="post-metadata">

**Author:** ![stemann](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stemann/32/4030_2.png) [@stemann](https://discourse.julialang.org/u/stemann)\
**Post date:** [August 12, 2023, 10:49am UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/16 "2023-08-12T10:49:38Z")

</div>

OK. Difficult to maintain in which way?

(still completely in the dark wrt. Makie etc.)

---

<div class="post-metadata">

**Author:** ![joa-quim](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/joa-quim/32/227_2.png) [@joa-quim](https://discourse.julialang.org/u/joa-quim)\
**Post date:** [August 12, 2023, 11:35pm UTC](https://discourse.julialang.org/t/displaying-images-with-text/102694/17 "2023-08-12T23:35:22Z")

</div>

No plotting package is light, and GMT is no exception but it’s so simple to do it

```julia
using GMT

I = gmtread("stefan_rgba.png");
imshow(I, title="Eu Touco Muito", frame=:none)

```

 ![touco](https://global.discourse-cdn.com/julialang/original/3X/2/1/2127cff154b6d1af6cec41b96d77ebae548e1d23.png)
