moviepy icon indicating copy to clipboard operation
moviepy copied to clipboard

TextClip produces low quality text

Open mondeja opened this issue 3 years ago • 12 comments

I think that the usage of ImageMagick to render texts is a bad option, but maybe I'm wrong and someone could explain me how to achieve good quality texts using TextClip. To illustrate this, I've done a comparison between gizeh vs PIL vs TextClip:

import math

import gizeh as gz  # pip install gizeh
import numpy as np
from moviepy import *
from PIL import Image, ImageFont, ImageDraw


SIZE = (300, 200)
BG_COLOR = (255, 90, 0)
DURATION = 2
FPS = 1

TEXT = "Text"
TEXT_SIZE = (200, 100)
TEXT_POSITION = (0, 0)
TEXT_ROTATION = 45
FONT_SIZE = 50
TEXT_COLOR_RGB = (74, 74, 74)
TEXT_COLOR_HEX = "#4a4a4a"
FONT = "Amiri-Bold"


def create_gizeh_demo():
    def create_gizeh_surface():
        surface = gz.Surface(
            width=SIZE[0],
            height=SIZE[1],
            bg_color=(BG_COLOR[0] / 255, BG_COLOR[1] / 255, BG_COLOR[2] / 255),
        )
        text = gz.text(
            TEXT,
            xy=(
                TEXT_SIZE[0] / 2 + TEXT_POSITION[0],
                TEXT_SIZE[1] / 2 + TEXT_POSITION[1],
            ),
            fill=(
                TEXT_COLOR_RGB[0] / 255,
                TEXT_COLOR_RGB[1] / 255,
                TEXT_COLOR_RGB[2] / 255,
            ),
            fontfamily=FONT.split("-")[0],  # only family
            fontsize=FONT_SIZE,
        )
        text = text.rotate(
            -math.radians(TEXT_ROTATION),  # Gizeh accepts radians and inversed
            center=(
                (TEXT_POSITION[0] + SIZE[0]) / 2,
                (TEXT_POSITION[1] + SIZE[1]) / 2,
            ),
        )
        text.draw(surface)
        return surface.get_npimage()

    surface = create_gizeh_surface()

    text_clip = VideoClip(lambda t: surface, duration=DURATION).with_fps(FPS)
    text_clip.size = SIZE
    return CompositeVideoClip([text_clip], size=SIZE).with_duration(DURATION)


def create_PIL_demo():
    img = Image.new("RGB", size=SIZE, color=BG_COLOR)
    draw = ImageDraw.Draw(img)
    font = ImageFont.truetype(f"~/.local/share/fonts/{FONT}.ttf", size=FONT_SIZE)
    draw.text((50, 50), TEXT, font=font, fill=TEXT_COLOR_RGB)
    img = img.rotate(
        TEXT_ROTATION,
        expand=False,
        fillcolor=BG_COLOR,
    )

    img = np.array(img)

    return VideoClip(lambda t: img, duration=DURATION).with_fps(FPS)


def create_TextClip_demo():
    bg_clip = ColorClip(size=SIZE, color=BG_COLOR, duration=DURATION).with_position(
        TEXT_POSITION
    )
    text_clip = (
        TextClip(
            TEXT,
            color=TEXT_COLOR_HEX,
            size=TEXT_SIZE,
            font=FONT,
            font_size=FONT_SIZE,
            stroke_color=TEXT_COLOR_HEX,
        )
        .with_position(TEXT_POSITION)
        .with_duration(DURATION)
    )
    text_clip = text_clip.rotate(TEXT_ROTATION, unit="deg", expand=True)
    return CompositeVideoClip([bg_clip, text_clip]).with_duration(DURATION)


final = concatenate_videoclips(
    [create_gizeh_demo(), create_PIL_demo(), create_TextClip_demo()]
)
final.preview()

Rendered result (MP4)

cairo_pil_imagemagick

The result is that Gizeh creates a text with good quality using Cairo (first scene), followed by PIL (second scene) and moviepy using ImageMagick creates a low quality text (third scene).

Specifications

  • Python Version: 3.8.5
  • Moviepy Version: master
  • Platform Name: Ubuntu
  • Platform Version: 20.04
  • ImageMagick Version: 6.9.10-23
  • PIL version: 8.1.0
  • cairocffi version: 1.2.0
  • gizeh version: 0.1.11

mondeja avatar Jan 20 '21 12:01 mondeja

I'm seeing now your implementation of TextClip using PIL @tburrows13. What do you think that is worth to make?

  • Implementation for PIL.
  • Implementation for Cairo.
  • Remove support for ImageMagick or not?
  • Support all 3 providers and create a provider argument to select between them?

mondeja avatar Jan 20 '21 12:01 mondeja

Indeed. The other alternative to Gizeh is just to use PIL directly. I was working on this before: https://github.com/tburrows13/moviepy/blob/redo-textclip/moviepy/video/NewTextClip.py.

With the following added to your demo (and adjusting the rest of the code to be compatible with ~v2.0.0.dev1 which this fork is currently based off):

def create_PILTextClip_demo():
    bg_clip = ColorClip(size=SIZE, color=BG_COLOR, duration=DURATION).set_position(
        TEXT_POSITION
    )
    text_clip = (
        NewTextClip(TEXT, color=TEXT_COLOR_HEX, size=TEXT_SIZE, fontsize=FONT_SIZE, bg_color=(0, 0, 0, 0))
        .set_position(TEXT_POSITION)
        .set_duration(DURATION)
    )
    text_clip = text_clip.rotate(TEXT_ROTATION, unit="deg", expand=True)
    return CompositeVideoClip([bg_clip, text_clip]).set_duration(DURATION)

I get

https://user-images.githubusercontent.com/22625968/105173962-b2fa7300-5b19-11eb-8412-ca5cefc346b1.mp4

Which admittedly looks bad.

Maybe that's just a bug in my implementation though? I've not worked on it for a while. Feel free to take over my effort, or start again from scratch with Gizeh.

The main advantage of getting rid of ImageMagick, besides the poor quality that you've demonstrated, is that it is tricky for users to get setup. Pillow is great because it is already a dependency. As long as Gizeh is pip-installable it shouldn't matter too much adding it as a dependency.

tburrows13 avatar Jan 20 '21 12:01 tburrows13

I'm seeing now your implementation of TextClip using PIL @tburrows13. What do you think that is worth to make?

  • Implementation for PIL.
  • Implementation for Cairo.
  • Remove support for ImageMagick or not?
  • Support all 3 providers and create a provider argument to select between them?

Yep, as I noted, I'd like to completely remove ImageMagick (it is only used here and as one of 3 optional gif renderers - I don't think it will be missed). This allows us to remove a lot of the complexity of installation.

Between PIL and Cairo/Gizeh, if PIL can get equivalent results, then it is preferable. If Gizeh is as easy to install as PIL is, then that would be fine as well.

tburrows13 avatar Jan 20 '21 12:01 tburrows13

Between PIL and Cairo/Gizeh, if PIL can get equivalent results, then it is preferable. If Gizeh is as easy to install as PIL is, then that would be fine as well.

Perfect :+1: I'm going to try to create the same results with PIL.

Yep, as I noted, I'd like to completely remove ImageMagick (it is only used here and as one of 3 optional gif renderers - I don't think it will be missed). This allows us to remove a lot of the complexity of installation.

I agree completely, that would be the most reasonable approach.

mondeja avatar Jan 20 '21 12:01 mondeja

+1 to remove ImageMagick (which was a 10-minute decision I took at the very beginning of the project and am not proud of).

I made a "Gizeh text clip for moviepy" some time ago here. It is much better than ImageMagick, but Cairo can be difficult to install on some machines.

PIL would be the best in terms of user installation experience. But can PIL access any user-installed font easily? Last time I checked (years ago...) it required the complete path to a font file.

Zulko avatar Jan 20 '21 12:01 Zulko

You can see that I changed some of the more niche parameters in the PIL TextClip to align with what got passed to PIL. I think that that is fine. It makes sense to align the TextClip parameters with whatever is being used to underly it.

tburrows13 avatar Jan 20 '21 12:01 tburrows13

Maybe that's just a bug in my implementation though? I've not worked on it for a while. Feel free to take over my effort, or start again from scratch with Gizeh.

I've updated the issue description with the complete example: Gizeh vs PIL vs TextClip. It seems that, as @Zulko said, Cairo render with best quality.

I made a "Gizeh text clip for moviepy" some time ago here. It is much better than ImageMagick, but Cairo can be difficult to install on some machines.

It seems that Pycairo provides a wheel with cairo 1.17.2 included for Windows, so I think that we can go with it. In other systems the installation is easy.

PIL would be the best in terms of user installation experience. But can PIL access any user-installed font easily? Last time I checked (years ago...) it required the complete path to a font file.

Matplotlib has a multiplatform system fonts discovering feature that we could reuse in the case of providing a PIL-based TextClip.

mondeja avatar Jan 20 '21 18:01 mondeja

I made a "Gizeh text clip for moviepy" some time ago here. It is much better than ImageMagick, but Cairo can be difficult to install on some machines.

I've integrated your example inside a TextClip version removing all the gizeh stuff and using pycairo directly (is the same API as cairocffi used by gizeh), and works like a charm but results in crazy unmaintanable code. Would you consider a pull to replace cairocffi dependency by pycairo in gizeh @Zulko? Keep in mind that the benefits are:

  • Removing the need of install CFFI (difficult in Windows platforms).
  • Adding standalone support for Windows thanks to prebuilt dll included in the pycairo Windows wheel.

With that I could create a TextClip Cairo based in your version using Gizeh maintaining a reasonable code base, adding gizeh as a direct dependency of moviepy.

mondeja avatar Jan 21 '21 00:01 mondeja

If you manage that I would vote for it (@tburrows13 what do you think?). Note that this would require some work to update the scripts that use the current ImageMagick-based text API, but that's allowed by the major version bump in v2.0.

Zulko avatar Jan 21 '21 01:01 Zulko

Yep. As long as it is pip-installable (which the current ImageMagick solution isn't), then I'm very happy. Breaking changes are fine, so presumably make the TextClip parameters fit as neatly around the underlying library possible.

tburrows13 avatar Jan 21 '21 20:01 tburrows13

@tburrows13 While trying your PIL-based TextClip you linked here, I found that the default bg_color="transparent" causes an error with PIL:

ValueError: unknown color specifier: 'transparent'

Otherwise it works fine 👍

michaelosthege avatar Aug 12 '21 12:08 michaelosthege

good solutions.

KehaoWu avatar Jun 16 '22 15:06 KehaoWu