opencv freetype图片写中文

opencv freetype图片写中文

说明:直接使用freetype,不需要重新编译opencv

代码

// cvx_text.h

#ifndef CVX_TEXT_H
#define CVX_TEXT_H

#include <string>
#include <vector>
#include <map>
#include <opencv2/opencv.hpp>

// FreeType Headers
#include <ft2build.h>
#include FT_FREETYPE_H

class CvxText {
public:
    explicit CvxText(const char* font_path);
    virtual ~CvxText();

    void putText(cv::Mat& img, const std::string& text, cv::Point org,
                 cv::Scalar color, int font_size);
    /**
     * @brief 计算文本渲染后的大致包围框尺寸
     *
     * @param text 要计算的 UTF-8 字符串
     * @param font_size 字体大小(像素)
     * @param[out] w 存储计算出的宽度
     * @param[out] h 存储计算出的高度
     * @param[out] baseline 存储计算出的基线
     */
    void getTextSize(const std::string& text, int font_size, int* w, int* h, int* baseline);

private:
    CvxText(const CvxText&) = delete;
    CvxText& operator=(const CvxText&) = delete;

    FT_Face getFace(const char* font_path);
    void utf8_to_ucs4(const std::string& str, std::vector<long>& ucs4);

private:
    FT_Library m_library;
    std::map<std::string, FT_Face> m_faces;
};

#endif // CVX_TEXT_H
#include "osd/cvx_text.hpp"
#include <iostream>

CvxText::CvxText(const char* font_path) {
    if (FT_Init_FreeType(&m_library)) {
        throw std::runtime_error("错误: 无法初始化 FreeType 库");
    }
    // 预加载默认字体
    getFace(font_path); 
}

CvxText::~CvxText() {
    // 释放所有缓存的 FT_Face
    for (auto& pair : m_faces) {
        FT_Done_Face(pair.second);
    }
    // 释放 FreeType 库
    FT_Done_FreeType(m_library);
}

FT_Face CvxText::getFace(const char* font_path) {
    // 如果字体已经加载,直接返回
    if (m_faces.count(font_path)) {
        return m_faces[font_path];
    }

    // 否则,加载新字体
    FT_Face face;
    if (FT_New_Face(m_library, font_path, 0, &face)) {
        std::cerr << "错误: 无法从文件加载字体 " << font_path << std::endl;
        return nullptr;
    }

    // 缓存并返回
    m_faces[font_path] = face;
    return face;
}

void CvxText::putText(cv::Mat& img, const std::string& text, cv::Point org,
                      cv::Scalar color, int font_size)
{
    // 默认使用构造函数中加载的字体
    if (m_faces.empty()) {
        std::cerr << "错误: 没有加载任何字体" << std::endl;
        return;
    }
    FT_Face face = m_faces.begin()->second; // 获取第一个(默认)字体

    // 设置字体大小
    FT_Set_Pixel_Sizes(face, 0, font_size);
    FT_Select_Charmap(face, FT_ENCODING_UNICODE);

    // 将 UTF-8 解码为 Unicode
    std::vector<long> ucs4_codes;
    utf8_to_ucs4(text, ucs4_codes);

    FT_GlyphSlot slot = face->glyph;

    for (long code : ucs4_codes) {
        if (FT_Load_Char(face, code, FT_LOAD_RENDER)) {
            std::cerr << "警告: 无法加载字符码点 " << code << std::endl;
            continue;
        }

        FT_Bitmap& bitmap = slot->bitmap;
        int y_start = org.y - slot->bitmap_top;
        int x_start = org.x + slot->bitmap_left;

        for (unsigned int r = 0; r < bitmap.rows; ++r) {
            for (unsigned int c = 0; c < bitmap.width; ++c) {
                int y = y_start + r;
                int x = x_start + c;
                if (x >= 0 && x < img.cols && y >= 0 && y < img.rows) {
                    unsigned char alpha = bitmap.buffer[r * bitmap.pitch + c];
                    if (alpha == 0) continue;
                    
                    double alpha_norm = alpha / 255.0;
                    cv::Vec3b& dst_pixel = img.at<cv::Vec3b>(y, x);

                    for (int k = 0; k < 3; ++k) {
                        dst_pixel[k] = cv::saturate_cast<uchar>(
                            color.val[k] * alpha_norm + dst_pixel[k] * (1 - alpha_norm)
                        );
                    }
                }
            }
        }
        org.x += (slot->advance.x >> 6);
    }
}

void CvxText::getTextSize(const std::string& text, int font_size, int* w, int* h, int* baseline) {
    if (w) *w = 0;
    if (h) *h = 0;
    if (baseline) *baseline = 0;
    if (m_faces.empty()) return;

    FT_Face face = m_faces.begin()->second;
    FT_Set_Pixel_Sizes(face, 0, font_size);

    std::vector<long> ucs4_codes;
    utf8_to_ucs4(text, ucs4_codes);

    if (ucs4_codes.empty()) {
        return;
    }

    int pen_x = 0;
    int max_ascent = 0;  // 最高点(相对于基线)
    int max_descent = 0; // 最低点(相对于基线,为正值)

    for (size_t i = 0; i < ucs4_codes.size(); ++i) {
        long code = ucs4_codes[i];
        if (FT_Load_Char(face, code, FT_LOAD_DEFAULT)) {
            continue;
        }

        FT_GlyphSlot slot = face->glyph;

        int ascent = slot->bitmap_top;
        int descent = slot->bitmap.rows - slot->bitmap_top;

        if (ascent > max_ascent) max_ascent = ascent;
        if (descent > max_descent) max_descent = descent;

        // 计算最后一个字符的宽度
        if (i == ucs4_codes.size() - 1) {
             pen_x += slot->bitmap_left + slot->bitmap.width;
        } else {
             pen_x += (slot->advance.x >> 6);
        }
    }

    if (w) *w = pen_x;
    if (h) *h = max_ascent + max_descent;
    if (baseline) *baseline = 0.25 * (*h);
}

void CvxText::utf8_to_ucs4(const std::string& str, std::vector<long>& ucs4) {
    ucs4.clear();
    for (size_t i = 0; i < str.length(); ) {
        unsigned char c = str[i];
        long code = 0;
        int len = 0;
        if ((c & 0x80) == 0) { // 1-byte
            code = c;
            len = 1;
        } else if ((c & 0xE0) == 0xC0) { // 2-byte
            code = ((str[i] & 0x1F) << 6) | (str[i + 1] & 0x3F);
            len = 2;
        } else if ((c & 0xF0) == 0xE0) { // 3-byte
            code = ((str[i] & 0x0F) << 12) | ((str[i + 1] & 0x3F) << 6) | (str[i + 2] & 0x3F);
            len = 3;
        } else if ((c & 0xF8) == 0xF0) { // 4-byte
            code = ((str[i] & 0x07) << 18) | ((str[i + 1] & 0x3F) << 12) | ((str[i + 2] & 0x3F) << 6) | (str[i + 3] & 0x3F);
            len = 4;
        }
        ucs4.push_back(code);
        i += len;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。