FreeTypeを使う 新しいページはコチラ
提供: yonewiki
(→サンプル2. 公式サイト版 のQtConsolApp) |
(→サンプル2. 公式サイト版 のQtConsolApp) |
||
679行: | 679行: | ||
もうちょっとあともういくつか公式のサンプルがあるので理解をすすめてみたいと思います。 | もうちょっとあともういくつか公式のサンプルがあるので理解をすすめてみたいと思います。 | ||
− | + | ||
+ | === ''' サンプル3.公式サイトのErik Mollerさん作成 Qt版 ''' === | ||
+ | エリックさんが作ったBという文字をアウトライン装飾ありで、文字の色とアウトラインの色を指定してレンダリングするというものです。ここまで手をかけておきながら、Bだけの固定のサンプルになっているところは、なかなか尖ったサンプルです。縁取りはこうやるということが伝わればいいわけで、色々な形をみれるという汎用性はバサッと切り捨て。カッコいい。 | ||
+ | |||
+ | |||
+ | 出力がTGAファイルなのもイカす。TGAはTruVision社が作った。Graphic形式です。ちょっと改造しないとVisual Studioではヘッダー部分が壊れてしまう感じでした。具体的にいうと、ヘッダーで使われているunsigned int の uint16 だと4バイトになってしまいます。2バイトでなければならない情報がすべて4バイトになって位置連れをおこしてしまいます。 | ||
+ | |||
+ | |||
+ | 出力された画像はirfanviewとかで見るのがいいんじゃないかな。それだと面倒だと感じる人もいるかもしれないので、bitmapも出力するようにTGAを出力する次の行に追加して変更しておきました。 | ||
+ | |||
+ | |||
+ | <Syntaxhighlight2 lang="cpp" line="1"> | ||
+ | #include <QtCore/QCoreApplication> | ||
+ | |||
+ | #include <QObject> | ||
+ | |||
+ | #include <QString> | ||
+ | #include <QFile> | ||
+ | #include <QTextStream> | ||
+ | #include <QImage> | ||
+ | #include <QColor> | ||
+ | #include <QSize> | ||
+ | |||
+ | #pragma execution_character_set("utf-8") | ||
+ | |||
+ | #include <ft2build.h> | ||
+ | #include FT_FREETYPE_H | ||
+ | #include FT_STROKER_H | ||
+ | |||
+ | #include <vector> | ||
+ | #include <fstream> | ||
+ | #include <iostream> | ||
+ | |||
+ | #ifdef _MSC_VER | ||
+ | #define MIN __min | ||
+ | #define MAX __max | ||
+ | #else | ||
+ | #define MIN std::min | ||
+ | #define MAX std::max | ||
+ | #endif | ||
+ | |||
+ | typedef unsigned char uint8; | ||
+ | typedef unsigned uint16; | ||
+ | typedef unsigned int uint32; | ||
+ | |||
+ | #if (('1234' >> 24) == '1') | ||
+ | //0x31 0x32 0x33 0x34 >> 24 = 0x00 0x00 0x00 0x31 '1' | ||
+ | // or | ||
+ | //0x34 0x33 0x32 0x31 >> 24 = 0x00 0x00 0x00 0x34 '4' | ||
+ | //BIG ENDIAN方式なら下の動きなのif文は否定。 | ||
+ | #elif (('4321' >> 24) == '1') | ||
+ | #define BIG_ENDIAN | ||
+ | #else | ||
+ | #error "Couldn't determine the endianness!" | ||
+ | #endif | ||
+ | |||
+ | #define WIDTH 640 | ||
+ | #define HEIGHT 480 | ||
+ | #define LEFT 32 | ||
+ | #define TOP 32 | ||
+ | |||
+ | static unsigned char canvas[HEIGHT][WIDTH]; | ||
+ | |||
+ | |||
+ | union Pixel32 { | ||
+ | Pixel32() : integer(0) {} | ||
+ | Pixel32(uint8 bi, uint8 gi, uint ri, uint ai = 255) { | ||
+ | b = bi; | ||
+ | g = gi; | ||
+ | r = ri; | ||
+ | a = ai; | ||
+ | } uint32 integer; | ||
+ | struct{ | ||
+ | #ifdef BIG_ENDIAN | ||
+ | uint8 a, r, g, b; | ||
+ | #else | ||
+ | uint8 b, g, r, a; | ||
+ | #endif | ||
+ | }; | ||
+ | }; | ||
+ | |||
+ | struct Vec2 { | ||
+ | Vec2() {} | ||
+ | Vec2(float a, float b) : x(a), y(b) {} | ||
+ | float x, y; | ||
+ | }; | ||
+ | |||
+ | struct Rect { | ||
+ | Rect() {} | ||
+ | Rect(float left, float top, float right, float bottom) : xmin(left), xmax(right), ymin(top), ymax(bottom){} | ||
+ | |||
+ | void Include(const Vec2& r) { | ||
+ | xmin = MIN(xmin, r.x); | ||
+ | ymin = MIN(ymin, r.y); | ||
+ | xmax = MAX(xmax, r.x); | ||
+ | ymax = MAX(ymax, r.y); | ||
+ | } | ||
+ | float Width() const { return xmax - xmin + 1; } | ||
+ | float Height() const { return ymax - ymin + 1; } | ||
+ | float xmin, xmax, ymin, ymax; | ||
+ | }; | ||
+ | |||
+ | #if defined(_MSC_VER) || defined(__GNUC__) | ||
+ | #pragma pack(push, 1) | ||
+ | #pragma pack(1) | ||
+ | #endif | ||
+ | |||
+ | struct TGAHeader | ||
+ | { | ||
+ | uint8 idLength, paletteType, imageType; | ||
+ | unsigned short firstPaletteEntry, numPaletteEntries; | ||
+ | // uint16 firstPaletteEntry, numPaletteEntries; | ||
+ | uint8 paletteBits; | ||
+ | unsigned short x, y, width, height; | ||
+ | // uint16 x, y, width, height; | ||
+ | uint8 depth, descriptor; | ||
+ | }; | ||
+ | |||
+ | #if defined(_MSC_VER) || defined(__GNUC__) | ||
+ | #pragma pack(pop) | ||
+ | #endif | ||
+ | |||
+ | bool WriteTGA(const std::string& filename, const Pixel32* pxl, uint16 width, uint16 height) { | ||
+ | std::ofstream file(filename.c_str(), std::ios::binary); | ||
+ | if (file) { | ||
+ | TGAHeader header; | ||
+ | memset(&header, 0, sizeof(TGAHeader)); | ||
+ | header.idLength = 0; | ||
+ | header.paletteType = 0; | ||
+ | header.imageType = 2; | ||
+ | |||
+ | header.firstPaletteEntry = 0; | ||
+ | header.numPaletteEntries = 0; | ||
+ | |||
+ | header.paletteBits = 0; | ||
+ | |||
+ | header.x = 0; | ||
+ | header.y = 0; | ||
+ | header.width = width; | ||
+ | header.height = height; | ||
+ | |||
+ | header.depth = 32; | ||
+ | header.descriptor = 0x20; | ||
+ | |||
+ | file.write((const char*)&header, sizeof(TGAHeader)); | ||
+ | file.write((const char*)pxl, sizeof(Pixel32) * width * height); | ||
+ | |||
+ | return true; | ||
+ | } | ||
+ | return false; | ||
+ | } | ||
+ | |||
+ | struct Span{ | ||
+ | Span() {} | ||
+ | Span(int _x, int _y, int _width, int _coverage) : x(_x), y(_y), width(_width), coverage(_coverage) {} | ||
+ | |||
+ | int x, y, width, coverage; | ||
+ | }; | ||
+ | |||
+ | typedef std::vector<Span> Spans; | ||
+ | |||
+ | void RasterCallback(const int y, const int count, const FT_Span* const spans, void* const user) { | ||
+ | Spans* sptr = (Spans*)user; | ||
+ | for (int i = 0; i < count; i++) { | ||
+ | sptr->push_back(Span(spans[i].x, y, spans[i].len, spans[i].coverage)); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void RenderSpans(FT_Library& library, FT_Outline* const outline, Spans* spans) { | ||
+ | FT_Raster_Params params; | ||
+ | memset(¶ms, 0, sizeof(params)); | ||
+ | params.flags = FT_RASTER_FLAG_AA | FT_RASTER_FLAG_DIRECT; | ||
+ | params.gray_spans = RasterCallback; | ||
+ | params.user = spans; | ||
+ | FT_Outline_Render(library, outline, ¶ms); | ||
+ | } | ||
+ | |||
+ | void WriteBMP(const std::string& filename, const Pixel32* pxl, uint16 width, uint16 height) { | ||
+ | QSize QSize_xy(width, height); | ||
+ | QString QString_ImageFileName = filename.data(); | ||
+ | QImage QImage_Canvas(QSize_xy, QImage::Format_ARGB32_Premultiplied); | ||
+ | int i = 0; | ||
+ | |||
+ | for (int h = 0; h < height; ++h) { | ||
+ | for (int w = 0; w < width; ++w) { | ||
+ | |||
+ | Pixel32 dst = pxl[(int)(width * h + w)]; | ||
+ | QImage_Canvas.setPixel(w, h, qRgba(dst.r, dst.g, dst.b, dst.a)); | ||
+ | |||
+ | } | ||
+ | } | ||
+ | QImage_Canvas.save(QString_ImageFileName, "BMP"); | ||
+ | } | ||
+ | |||
+ | void WriteGlyphAsTGA(FT_Library& library, const std::string& fileName, wchar_t ch, FT_Face& face, int size, | ||
+ | const Pixel32& fontCol, const Pixel32 outlineCol, float outlineWidth) { | ||
+ | if (FT_Set_Char_Size(face, size << 6, size << 6, 90, 90) == 0){ | ||
+ | FT_UInt gindex = FT_Get_Char_Index(face, ch); | ||
+ | if (FT_Load_Glyph(face, gindex, FT_LOAD_NO_BITMAP) == 0 ){ | ||
+ | if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) { | ||
+ | Spans spans; | ||
+ | RenderSpans(library, &face->glyph->outline, &spans); | ||
+ | |||
+ | Spans outlineSpans; | ||
+ | |||
+ | FT_Stroker stroker; | ||
+ | FT_Stroker_New(library, &stroker); | ||
+ | FT_Stroker_Set(stroker, (int)(outlineWidth * 64), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); | ||
+ | FT_Glyph glyph; | ||
+ | |||
+ | if (FT_Get_Glyph(face->glyph, &glyph) == 0) { | ||
+ | FT_Glyph_StrokeBorder(&glyph, stroker, 0, 1); | ||
+ | if (glyph->format == FT_GLYPH_FORMAT_OUTLINE) { | ||
+ | FT_Outline* o = &reinterpret_cast<FT_OutlineGlyph>(glyph)->outline; | ||
+ | RenderSpans(library, o, &outlineSpans); | ||
+ | } | ||
+ | FT_Stroker_Done(stroker); | ||
+ | FT_Done_Glyph(glyph); | ||
+ | |||
+ | if (!spans.empty()) { | ||
+ | Rect rect(spans.front().x, spans.front().y, spans.front().x, spans.front().y); | ||
+ | for (Spans::iterator s = spans.begin(); s != spans.end(); ++s) { | ||
+ | rect.Include(Vec2(s->x, s->y)); | ||
+ | rect.Include(Vec2(s->x + s->width - 1, s->y)); | ||
+ | } | ||
+ | for (Spans::iterator s = outlineSpans.begin(); s != outlineSpans.end(); ++s) { | ||
+ | rect.Include(Vec2(s->x, s->y)); | ||
+ | rect.Include(Vec2(s->x + s->width - 1, s->y)); | ||
+ | } | ||
+ | #if 0 | ||
+ | float bearingX = face->glyph->metrics.horiBearingX >> 6; | ||
+ | float bearingY = face->glyph->metrics.horiBearingY >> 6; | ||
+ | float advance = face->glyph->advance.x >> 6; | ||
+ | #endif | ||
+ | int imgWidth = rect.Width(), imgHeight = rect.Height(), imgSize = imgWidth * imgHeight; | ||
+ | |||
+ | Pixel32* pxl = new Pixel32[imgSize]; | ||
+ | memset(pxl, 0, sizeof(Pixel32) * imgSize); | ||
+ | |||
+ | for (Spans::iterator s = outlineSpans.begin(); s != outlineSpans.end(); ++s) { | ||
+ | for (int w = 0; w < s->width; ++w) { | ||
+ | pxl[(int)((imgHeight - 1 - (s->y - rect.ymin)) * imgWidth + s->x - rect.xmin + w)] = | ||
+ | Pixel32(outlineCol.r, outlineCol.g, outlineCol.b, s->coverage); | ||
+ | } | ||
+ | } | ||
+ | for (Spans::iterator s = spans.begin(); s != spans.end(); ++s) { | ||
+ | for (int w = 0; w < s->width; ++w) { | ||
+ | Pixel32& dst = pxl[(int)((imgHeight - 1 - (s->y - rect.ymin)) * imgWidth + s->x - rect.xmin + w)]; | ||
+ | Pixel32 src = Pixel32(fontCol.r, fontCol.g, fontCol.b, s->coverage); | ||
+ | dst.r = (int)(dst.r + ((src.r - dst.r) * src.a) / 255.0f); | ||
+ | dst.g = (int)(dst.g + ((src.g - dst.g) * src.a) / 255.0f); | ||
+ | dst.b = (int)(dst.b + ((src.b - dst.b) * src.a) / 255.0f); | ||
+ | dst.a = MIN(255, dst.a + src.a); | ||
+ | } | ||
+ | } | ||
+ | WriteTGA(fileName, pxl, imgWidth, imgHeight); | ||
+ | WriteBMP("C:/FreeTypeStep03Test.bmp", pxl, imgWidth, imgHeight); | ||
+ | delete[] pxl; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | static void draw_bitmap(FT_Bitmap* bitmap, FT_Int x, FT_Int y) { | ||
+ | FT_Int i, j, p, q; | ||
+ | FT_Int x_max = x + bitmap->width; | ||
+ | FT_Int y_max = y + bitmap->rows; | ||
+ | |||
+ | for (i = x, p = 0; i < x_max; i++, p++) { | ||
+ | for (j = y, q = 0; j < y_max; j++, q++) { | ||
+ | if (i < 0 || j < 0 || i >= WIDTH || j >= HEIGHT) { | ||
+ | continue; | ||
+ | } | ||
+ | |||
+ | canvas[j][i] |= bitmap->buffer[q * bitmap->width + p]; | ||
+ | |||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | int main(int argc, char *argv[]) | ||
+ | { | ||
+ | QCoreApplication a(argc, argv); | ||
+ | |||
+ | // if (argc == 3) { | ||
+ | if (argc == 3) { | ||
+ | std::cerr << "Render letter `B' of given font as a TGA image.\n"; | ||
+ | std::cerr << "\n"; | ||
+ | std::cerr << "usage: example2 <font> TGA-file\n"; | ||
+ | // return 1; | ||
+ | |||
+ | } | ||
+ | |||
+ | QString QString_FileName = "C:/FreeTypeStep03Test.txt"; | ||
+ | QString QString_ImageFileName = "C:/FreeTypeStep03Test.bmp"; | ||
+ | QString filename = "C:\\Windows\\Fonts\\BIZ-UDGothicR.ttc"; | ||
+ | QString QString_TgaFileName = "C:/FreeTypeStep03Test.tga"; | ||
+ | QFile QFile_Text(QString_FileName); | ||
+ | |||
+ | QString QString_ImageRowMonoBuffer = ""; | ||
+ | |||
+ | QSize QSize_xy(WIDTH, HEIGHT); | ||
+ | QImage QImage_Canvas(QSize_xy, QImage::Format_ARGB32_Premultiplied);//空イメージ | ||
+ | |||
+ | FT_Library library; | ||
+ | FT_Init_FreeType(&library); | ||
+ | FT_Face face; | ||
+ | |||
+ | //std::ifstream fontFile(argv[1], std::ios::binary); | ||
+ | std::ifstream fontFile(filename.toUtf8(), std::ios::binary); | ||
+ | if (fontFile) { | ||
+ | fontFile.seekg(0, std::ios::end); | ||
+ | std::fstream::pos_type fontFileSize = fontFile.tellg(); | ||
+ | fontFile.seekg(0); | ||
+ | unsigned char* fontBuffer = new unsigned char[fontFileSize]; | ||
+ | fontFile.read((char*)fontBuffer, fontFileSize); | ||
+ | |||
+ | FT_New_Memory_Face(library, fontBuffer, fontFileSize, 0, &face); | ||
+ | |||
+ | WriteGlyphAsTGA(library, "C:/FreeTypeStep03Test.tga"/*QString_TgaFileName.constData()*/, L'B', face, 100, Pixel32(255, 90, 30), Pixel32(255, 255, 255), 3.0f); | ||
+ | |||
+ | delete[] fontBuffer; | ||
+ | } | ||
+ | return a.exec(); | ||
+ | } | ||
+ | |||
+ | 出力結果 | ||
+ | |||
+ | [[ファイル:FreeTypeStep03Test.jpg|||none|実行結果]] | ||
[[フォント TrueType 構造解析]]に戻る。 | [[フォント TrueType 構造解析]]に戻る。 |