본문 바로가기
파이썬

FreeTypeFont object has no attribute 'measure_text'

by 진심블로그 2024. 3. 17.

안녕하세요. 파이신입니다.

 

오늘은 이미지에 텍스트를 넣을 때 발생할 수 있는 에러에 대해 알아봅니다.

 

- 본 글은 파이썬 중급자 이상이 읽어 보기에 적합합니다.

 

이미지 처리를 하면서 글자를 삽입하는 일은 자주 있는 일입니다.

 

특히 자동화 부분이나, 스크래핑/크롤링을 하면서 화면을 캡처한 후 프로그래머의 메모를

 

직관적으로 남겨 놓는 경우에서는 더욱 유용합니다.

 

시간이 지난 후에 글자로 된 설명을 따로 읽어 보는 것보다 말 그대로 직관적이고

 

편하기 때문입니다.

 

이미지에 글자를 넣으려면, 글자의 크기와 위치에 대해 신경을 쓸 수밖에 없습니다.

 

아무 데나 넣을 수는 없으니까요.

 

일반적으로 글자의 크기와 위치를 넣을 때 사용하는 함수는,

ImageFont클래스의 measure_text 메서드입니다.

 

평소에 사용하는 폰트를 넣고 init을 하고 나면 다음과 같은 에러를 만납니다.

 

 

 

 

이런 코드를 사용하면 그렇죠.

 

font = ImageFont.truetype(r'c:\windows\fonts\gulim.ttc', fontSize)

 

 

measure_text() 메서드에서만 에러가 나오지, 일반적으로 이미지에 글자를 넣을 때는

전혀 문제가 없습니다.

 

폰트 자체도 문제가 없고요.

 

그럼 이 에러는 왜 나오는 걸까요?

 

Pillow 버전 9.0 이후에서부터는 FTF에 대한 지원이 없어졌기 때문입니다.

 

지금 최신 버전이 10.2이니 지금 버전에서는 동작을 하지 않습니다.

 

과거 가상환경에서 작업했던 코드들을 최신환경에서 가져와 실행시키면

이런 문제가 자주 발생합니다.

 

그렇다고 가상환경을 매번 바꾸어 주기도 뭐 하니, 이전 코드를 재생산/재활용

할 경우에는 최신버전으로 리빌드 해주는 것이 최선입니다.

 

뭐, 번거롭기는 하지만, 나중에 또 사용할 일이 있을 때를 대비해 준비해 주는 것이

좋습니다.

 

textWidth, textHeight = font.measure_text(text)

 

 

자, 위의 코드가 동작을 안 하니, 지원되는 코드로 바꾸어 봅니다.

 

font = ImageFont.truetype(r'c:\windows\fonts\gulim.ttc', fontSize)
bbox = font.getbbox(text)
textWidth = bbox[2] - bbox[0]
textHeight = bbox[3] - bbox[1]    
print(textWidth, textHeight)

 

위의 코드는 이전 버전에서부터 지원하는 내용이고 현재도 지원합니다.

 

코드가 깔끔해 보이지는 않지만, 자주 사용하는 메서드가 아닌 이상,

이렇게 작성하는 것이 좋을 수도 있습니다.

 

텍스트의 넓이는 사실, 폰트크기에 텍스트 길이를 곱한 것과 크게 차이가 없습니다.

 

높이는 폰트 크기랑 비슷하죠.

 

어떻게 보면 굳이 이렇게 가져올 필요가 없을 수 있습니다.

 

아주 정교하게 텍스트를 원하는 위치에 넣는 경우가 아니라면 그렇습니다.

 

그래서 이런 방법을 사용한 것보다 텍스트의 길이에 따라 폰트크기와 텍스트의

위치를 동적으로 조절하는 방법을 사용하는 것이 효율적입니다.

 

다만, 짧은 텍스트를 이미지의 특정 위치에 정교하게 넣을 때는 위 방법과의

조합이 필요할 수 있습니다.

 

font = ImageFont.truetype(r'c:\windows\fonts\gulim.ttc', fontSize)    
    
textPositionX = 50    
textPositionY = Image.height - fontSize * (lineBreak-1) + fontSize/3

 

위와 같은 코드를 작성하면, 이미지의 중간에 원하는 텍스트를 길이와 상관없이

넣을 수 있습니다.

 

lineBreak = int(round(Image.height/fontSize))

 

 

이미지 크기를 폰트의 크기로 나누어서 몇 줄의 텍스트를 넣을지 정합니다.

 

fontSize = 5000/textLength

 

 

폰트의 크기는 이미지의 크기에 따라 동적으로 변합니다.

 

여기에선 코드의 가독성을 위해 5000으로 지정했습니다.

 

1080x1080 이미지의 경우 200 크기의 폰트가 25자 들어갑니다.

 

계산해 보면 답이 나오죠?

 

다른 크기의 이미지들은 동적으로 계산해서 변수로 넣어 주면 그만입니다.

 

텍스트는 줄 넘김을 해야 하니,

 

text = add_newline_every_n_characters(text, lineBreak)

 

 

함수는 다음과 같습니다.

 

def add_newline_every_n_characters(input_str, n=30):
    result = ''
    for i in range(0, len(input_str), n):
        result += input_str[i:i + n] + '\n'
    return result

 

 

원하는 글자 수가 나올 때마다 줄 넘김을 한 텍스트를 반환합니다.

 

위에서는 lineBreak를 전달했는데, 예시로 말한 이미지에서는 폰트크기 200일 경우,

한 줄에 5 글자일 테니, 5가 전달되겠네요.

 

5글자마다 한 줄씩 띄워서 이미지의 가운데에 위치하게 됩니다.

 

사용하는 폰트에 따라 위치가 달라질 수 있다는 점은 알아 두어야 합니다.

 

draw = ImageDraw.Draw(Image)
draw.text((textPositionX, textPositionY), text, font=font, fill=fontColor)

 

 

이제 위와 같이 텍스트를 넣으면 끝입니다.

 

여담인데, 메서드 이름들을 보면 잘 지었다는 생각이 듭니다.

 

텍스트인데 draw로 표현하는 것이 적절하다고 보입니다.

 

실제로 Image 클래스의 안을 들여다보면 텍스트를 쓰는 것이 아니라

 

그림을 그리듯이 그립니다.

 

이걸 오버라이드 하면, 클래스 안에서 희한한 효과들도 만들어 낼 수 있습니다.

 

시간 남을 때 한번 오버라이드해서 테스트해보세요.

 

재밌습니다.