9
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

freetype2 で文字列描画 (実践編)

Last updated at Posted at 2017-08-23

はじめに

日本語描画が必要だったので、freetypeを使用し実践的なコード組んでみた。
OpenCV 3.2 でFreetype描画がサポートされたようだが、痒い所に手が届かなそう(CalcRectが無いとか)だったので、結局実装してみた。

_72D0559s.jpg

Freetype のAPI referenceなど

API reference
https://www.freetype.org/freetype2/docs/reference/ft2-index.html

Tutorial
https://www.freetype.org/freetype2/docs/tutorial/step1.html
https://www.freetype.org/freetype2/docs/tutorial/step2.html

Sample
https://www.freetype.org/freetype2/docs/tutorial/example1.c
https://www.freetype.org/freetype2/docs/tutorial/example2.cpp

作成コード

全体はgitで。
https://github.com/blue777/NanoPi-NEO/blob/master/common/img_font.h

クラス

クラス名:ImageFont

メソッド 概要
ImageFont( const char* filename, uint32_t height, bool isFitHeight=true ) コンストラクタ
CalcRect( int& left, int& top, int& right, int& bottom, const char* str ) 文字列の描画サイズ計算
DrawTextGRAY( int x, int y, const char* str, uint8_t color, uint8_t * image, int stride, int cx, int cy ) Grayバッファへの文字列描画
DrawTextBGRA( int x, int y, const char* str, uint32_t color, uint8_t * image, int stride, int cx, int cy ) BGRAバッファへの文字列描画(α値指定描画対応)

コンストラクタ

基本的な初期化を行う。
指定した高さからはみ出て描画(baseline管理になるので、gjpqyなど基準線より下の部分)されるので、はみ出ないように調整する機能も付けている。

メンバ変数
	FT_Library		m_piLibrary;
	FT_Face			m_piFace;
	int				m_nBaseline;
ImageFont()
	ImageFont( const char* filename, uint32_t height, bool isFitHeight=true )
	{
		FT_Error			error;
		FT_Size_RequestRec	tReqSize;

		// initialize member var.
		m_nBaseline		= height;
		m_piLibrary		= NULL;
		m_piFace		= NULL;

		error	= FT_Init_FreeType( &m_piLibrary );
		if( 0 != error )
		{
			throw	"ERROR: FT_Init_FreeType()";
		}

		error	= FT_New_Face( m_piLibrary, filename, 0, &m_piFace );
		if( 0 != error )
		{
			throw	"ERROR: FT_New_Face";
		}
		
	    tReqSize.type			= FT_SIZE_REQUEST_TYPE_NOMINAL;
	    tReqSize.width			= 0;
	    tReqSize.height			= (height << 6);
	    tReqSize.horiResolution	= 0;
	    tReqSize.vertResolution	= 0;

		if( isFitHeight )
		{
			//	b. Scaled Global Metrics
			int		yMax	= m_piFace->bbox.yMax;
			int		yMin	= m_piFace->bbox.yMin;

			m_nBaseline		= height * yMax / (yMax - yMin);

			tReqSize.type	= FT_SIZE_REQUEST_TYPE_BBOX;
		    tReqSize.height	= (height << 6);
		}

		error	= FT_Request_Size( m_piFace, &tReqSize );
		if( 0 != error )
		{
			throw	"ERROR: FT_Request_Size()";
		}
	}

BGRA描画部

Freetypeに渡す際には、UNICODE32で1文字ずつ渡し描画する必要がある。

colorの上位8bitはα値で、文字列の半透明描画も出来るようにしてみている。
なお、BGRAは、OpenCVのCV_8UC4互換である。

DrawTextBGRA()

	int DrawTextBGRA( int x, int y, const char* str, uint32_t color, uint8_t * image, int stride, int cx, int cy )
	{
		std::u32string	u32str	= std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t>().from_bytes(str);

		return	DrawTextBGRA( x, y, u32str, color, image, stride, cx, cy );
	}
	
	int DrawTextBGRA( int x, int y, const std::u32string& u32str, uint32_t color, uint8_t * image, int stride, int cx, int cy )
	{
		FT_Error		error;
		FT_GlyphSlot	slot	= m_piFace->glyph;
		FT_Matrix		matrix	= { 1 << 16, 0, 0, 1 << 16 };
		FT_Vector		pen		= { 0, 0 };
		uint32_t		alpha	= (color >> 24) + (color >> 31);

		for( size_t i = 0; i < u32str.size(); i++ )
		{
			switch( u32str[i] )
			{
			case '\r':
				break;
				
			case '\t':
				pen.x	+= m_piFace->size->metrics.max_advance * 4;
				pen.x	-= pen.x % (m_piFace->size->metrics.max_advance * 4);
				break;

			case '\n':
				pen.x	= 0;
				pen.y	-= m_piFace->size->metrics.height;
				break;

			default:
				FT_Set_Transform( m_piFace, &matrix, &pen );
	
				error	= FT_Load_Char( m_piFace, u32str[i], FT_LOAD_RENDER );
				if( 0 == error )
				{
					int	bmp_cy	= slot->bitmap.rows;
					int	pos_y	= y + m_nBaseline - slot->bitmap_top;
					int	rs		= 0 <= pos_y ? 0 : -pos_y;
					int	re		= (pos_y + bmp_cy) <= cy ? bmp_cy : (cy - pos_y);
	
					int	bmp_cx	= slot->bitmap.width;
					int	pos_x	= x + slot->bitmap_left;
					int	cs		= 0 <= pos_x ? 0 : -pos_x;
					int	ce		= (pos_x + bmp_cx) <= cx ? bmp_cx : (cx - pos_x);
	
					for( int r = rs; r < re; r++ )
					{
						uint8_t *	src_line	= &slot->bitmap.buffer[ bmp_cx * r ];
						uint8_t *	dst_line	= &image[ stride * (pos_y + r) + (pos_x * 4) ];
						int 		c			= cs;
	
						for( ; c < ce; c++ )
						{
							int32_t	a	= src_line[c+0];
							
							if( 0 < a )
							{
								uint8_t*	clr	= (uint8_t*)&color;
								int32_t		d0	= dst_line[c*4+0];
								int32_t		d1	= dst_line[c*4+1];
								int32_t		d2	= dst_line[c*4+2];
								int32_t		d3	= dst_line[c*4+3];
		
								a	+= a >> 7;
								a	*= alpha;
		
								dst_line[c*4+0] = (uint8_t)( ((d0 << 16) + (clr[0] - d0) * a) >> 16 );
								dst_line[c*4+1] = (uint8_t)( ((d1 << 16) + (clr[1] - d1) * a) >> 16 );
								dst_line[c*4+2] = (uint8_t)( ((d2 << 16) + (clr[2] - d2) * a) >> 16 );
								dst_line[c*4+3] = (uint8_t)( ((d3 << 16) + (clr[3] - d3) * a) >> 16 );
							}
						}
					}
	
					pen.x	+= slot->advance.x;
					pen.y	+= slot->advance.y;
				}
				break;
			}
		}
		
		return	0;
	}

呼び出しサンプル

最初写真は、この実行結果になります。
UTF-8をUNICODE32に変換して、Freetypeでbitmap化して、αブレンドしながら描画し、OLEDに表示しています。

各種ディスプレイの接続などは、NanoPi-NEOと5種のLCDと2種のOLED にて。

コンパイルコマンド例:

g++ -O3 -std=c++11 Sample.cpp -o Sample.o `freetype-config --cflags` `freetype-config --libs`

実装サンプル

Sample

#include "common/img_font.h"
#include "common/display_ssd1306_i2c.h"
#include "common/display_st7735_spi.h"
#include "common/display_ili9225_spi.h"
#include "common/display_ili9328_spi.h"
#include "common/display_ili9341_spi.h"

#define	FONT_PATH	"/usr/share/fonts/truetype/takao-gothic/TakaoPGothic.ttf"

int	main()
{
	const int	buf_width	= 320;
	const int	buf_height	= 240;
	const int	buf_stride	= buf_width * 4;
	uint8_t		buf[buf_width*buf_height*4];

	std::vector<DisplayIF*>	display;

//	display.push_back( new Display_ILI9225_spi(270) );
//	display.push_back( new Display_ILI9341_spi_TM24(270) );
//	display.push_back( new Display_ILI9341_spi_TM22(270) );
//	display.push_back( new Display_ILI9341_spi(270) );
//	display.push_back( new Display_ILI9328_spi_TM22(270) );
//	display.push_back( new Display_ST7735_spi(270) );
	display.push_back( new Display_SSD1306_i2c(180) );

	for( auto it : display )
	{
		it->Init();
		it->DispOn();
		it->DispClear();
	}

	ImageFont	iFont1( FONT_PATH, 32 );	
	ImageFont	iFont2( FONT_PATH, 64 );
	const char* pszText1	= u8"こんにちわ";
	const char* pszText2	= u8"世界";
	int			cx, cy;

	memset( buf, 0, sizeof(buf) );
	iFont1.DrawTextBGRA(  0, 0, pszText1, 0xFFFFFFFF, buf, buf_stride, buf_width, buf_height );
	iFont2.DrawTextBGRA( 16, 8, pszText2, 0xA000FF00, buf, buf_stride, buf_width, buf_height );

	for( auto it : display )
	{
		it->WriteImageBGRA( 0, 0, buf, buf_stride, buf_width, buf_height );
	}

	FILE*	fp	= fopen("image.bin", "wb");
	if( fp != NULL )
	{
		fwrite( buf, buf_stride, buf_height, fp );
		fclose( fp );
	}

	getchar();
	return	0;
}

ファイル保存した結果は、以下のようになる。

image.PNG

おわかりいただけただろうか?
世界がαブレンド描画されている。

9
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?