AWTK实现描边字体

项目中需要用到描边字体,原本以为是个简单的需求,可是绕了一圈发现awtk中并没有简单的设置去实现这个,向官方提了issue,得到的回答居然也是不懂,无奈之下,只好自己探究,写自定义插件。

听老板说之前的产品描边字体是用freetype实现的,正好自己最近在学linux开发板的操作,于是用AI写了个freetype写描边字体的代码,在开发板上实验了下,效果还行。本质思想就是提取字体的glyph位图,在垂直和水平方向各偏移一个像素画一遍黑色字体,形成3x3共9个绘制起点,最后在中心的绘制起点开始画一遍白色字体。

image-20241030083211736

正好awtk有个font_get_glyph函数可以对标FT_Load_Char, 给出的文档中也有将字体转化为位图函数,免除了需要手动移植,适配freetype和兼容awtkAPI的麻烦。

static bitmap_t* bitmap_from_str(canvas_t* canvas, wchar_t* str, color_t tc) {
  glyph_t g;
  uint32_t x = 0;
  uint32_t y = 0;
  uint32_t i = 0;
  uint32_t w = 0;
  uint32_t h = 0;
  uint32_t ox = 0;
  font_vmetrics_t vm;
  uint32_t* p = NULL;
  uint32_t baseline = 0;
  bitmap_t* bitmap = NULL;
  uint32_t font_size = canvas->font_size;
  font_manager_t* fm = canvas->font_manager;
  font_t* font = font_manager_get_font(fm, canvas->font_name, font_size);
  return_value_if_fail(font != NULL, NULL);

  vm = font_get_vmetrics(font, font_size);
  h = vm.ascent - vm.descent;
  return_value_if_fail(h > 0, NULL);

  baseline = vm.ascent;
  for (i = 0; str[i]; i++) {
    return_value_if_fail(font_get_glyph(font, str[i], font_size, &g) == RET_OK, NULL);
    w += g.advance + 1;
  }

  bitmap = bitmap_create_ex(w, h, 0, BITMAP_FMT_RGBA8888);
  return_value_if_fail(bitmap != NULL, NULL);

  p = (uint32_t*)bitmap_lock_buffer_for_write(bitmap);

  memset(p, 0x00, w * h * 4);

  for (i = 0; str[i]; i++) {
    return_value_if_fail(font_get_glyph(font, str[i], font_size, &g) == RET_OK, NULL);

    for (y = 0; y < g.h; y++) {
      for (x = 0; x < g.w; x++) {
        int32_t dx = ox + g.x + x;
        int32_t dy = baseline + g.y + y;

        uint32_t* d = p +  dy * w + dx;
        const uint8_t* s = g.data + y * g.w + x;

        tc.rgba.a = *s;
        *d = tc.color;
      }
    }
    ox += g.advance + 1;
  }
  bitmap_unlock_buffer(bitmap);
  bitmap->flags |= BITMAP_FLAG_CHANGED;

  return bitmap;
}

但是同样的逻辑搬到awtk上,效果就很不理想,过渡的区域经常绘制不整,再查了一圈,发现位图绘制到画布这种做法,字体位图从中心到边缘的过渡区域a值是逐渐下降的。

image-20241030082657906

于是新加了个逻辑,用a值是不是255来区分是边缘还是字体,对于字体一律白色处理,边缘一律黑色处理,这下勉强能看,接近用freetype画的效果,但估计由于是通过字体的位图直接绘制的原因,清晰度很低,锯齿严重。

image-20241030083035924

写到这里差不多忙活了一天,原本打算到此为止时,忽然又想到,awtk的canvas_draw_text_in_rect貌似是矢量绘制,没有上面的清晰度和锯齿问题,重复绘制的思想也可以拿到这里,于是再试了一次,效果立竿见影!没想到苦苦追寻一天,最后解决问题的只有寥寥几行代码。

虽然还有点小瑕疵,比如字号比较小的情况边框对比度不太均衡,但对于当前的需求也够用了。

static ret_t border_text_on_paint_self(widget_t* widget, canvas_t* c) {
  border_text_t* border_text = BORDER_TEXT(widget);
  style_t *style = widget->astyle;
  int font_size = style_get_int(style, STYLE_ID_FONT_SIZE, 25);
  const char *font_name = style_get_str(style, STYLE_ID_FONT_NAME, "default");
  color_t text_color = color_init(255, 255, 255, 255);  
  color_t border_color = color_init(0, 0, 0, 255);
  
  canvas_set_text_align(c, ALIGN_H_CENTER, ALIGN_V_MIDDLE);
  
  canvas_set_font(c, font_name, font_size);
  canvas_set_text_color(c, border_color);
  for(int j = 0; j < 3; j++){
    for(int i = 0; i < 3; i++){
      rect_t r = rect_init(i, j, widget->w, widget->h);
      canvas_draw_text_in_rect(c, widget->text.str, widget->text.size, &r);
    }
  }

  canvas_set_text_color(c, text_color);
  rect_t r = rect_init(1, 1, widget->w, widget->h);
  canvas_draw_text_in_rect(c, widget->text.str, widget->text.size, &r);
  return RET_OK;
}

image-20241030210347816

自定义控件已开源,希望同样用这个平台开发的朋友不要再遭受相同的罪:

https://gitee.com/tracker647/awtk-widget-border-text

参考:

通义灵码

https://blog.codingnow.com/2013/09/edge_font.html

附原版freetype代码:

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <wchar.h>
#include <sys/ioctl.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H

int fd_fb;
struct fb_var_screeninfo var;	/* Current var */
int screen_size;
unsigned char *fbmem;
unsigned int line_width;
unsigned int pixel_width;


/**********************************************************************
 * 函数名称: lcd_put_pixel
 * 功能描述: 在LCD指定位置上输出指定颜色(描点)
 * 输入参数: x坐标,y坐标,颜色
 * 输出参数: 无
 * 返 回 值: 会
 * 修改日期        版本号     修改人	      修改内容
 * -----------------------------------------------
 * 2020/05/12	     V1.0	  zh(angenao)	      创建
 ***********************************************************************/ 
void lcd_put_pixel(int x, int y, unsigned int color)
{
	unsigned char *pen_8 = fbmem+y*line_width+x*pixel_width;
	unsigned short *pen_16;	
	unsigned int *pen_32;	

	unsigned int red, green, blue;	

	pen_16 = (unsigned short *)pen_8;
	pen_32 = (unsigned int *)pen_8;
	
	switch (var.bits_per_pixel)
	{
		case 8:
		{
			*pen_8 = color;
			break;
		}
		case 16:
		{
			/* 565 */
			red   = (color >> 16) & 0xff;
			green = (color >> 8) & 0xff;
			blue  = (color >> 0) & 0xff;
			color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
			*pen_16 = color;
			break;
		}
		case 32:
		{
			*pen_32 = color;
			break;
		}
		default:
		{
			printf("can't surport %dbpp\n", var.bits_per_pixel);
			break;
		}
	}
}


void draw_bitmap(FT_Bitmap* bitmap, FT_Int x, FT_Int y, unsigned int color)
{
    FT_Int i, j, p, q;
    FT_Int x_max = x + bitmap->width;
    FT_Int y_max = y + bitmap->rows;

    for (j = y, q = 0; j < y_max; j++, q++) {
        for (i = x, p = 0; i < x_max; i++, p++) {
            if (i < 0 || j < 0 || i >= var.xres || j >= var.yres) continue;

            if (bitmap->buffer[q * bitmap->width + p] != 0) {
                lcd_put_pixel(i, j, color);
            }
        }
    }
}


void draw_bitmap_with_border(FT_Bitmap* bitmap, FT_Int char_x, FT_Int char_y, int border_size, unsigned int text_color, unsigned int border_color)
{
    // 绘制边框
    for (int dx = -border_size; dx <= border_size; ++dx) {
        for (int dy = -border_size; dy <= border_size; ++dy) {
            if (dx == 0 && dy == 0) continue; // 跳过中心点

            for (int j = 0; j < bitmap->rows; ++j) {
                for (int i = 0; i < bitmap->width; ++i) {
                    if (bitmap->buffer[j * bitmap->width + i] != 0) {
                        int px = char_x + i + dx;
                        int py = char_y + j + dy;

                        if (px >= 0 && px < var.xres && py >= 0 && py < var.yres) {
                            lcd_put_pixel(px, py, border_color);
                        }
                    }
                }
            }
        }
    }

    // 绘制文字
    for (int j = 0; j < bitmap->rows; ++j) {
        for (int i = 0; i < bitmap->width; ++i) {
            if (bitmap->buffer[j * bitmap->width + i] != 0) {
                int px = char_x + i;
                int py = char_y + j;

                if (px >= 0 && px < var.xres && py >= 0 && py < var.yres) {
                    lcd_put_pixel(px, py, text_color);
                }
            }
        }
    }
}


void draw_text_with_border(FT_Face face, const wchar_t* text, int x, int y, int border_size, unsigned int text_color, unsigned int border_color)
{
    FT_GlyphSlot slot = face->glyph;
    int pen_x = x;  // 初始笔位置
    int pen_y = y;

    for (const wchar_t* p = text; *p; ++p)
    {
        if (FT_Load_Char(face, *p, FT_LOAD_RENDER))
        {
            continue;  // 如果加载失败,跳过这个字符
        }

        // 计算当前字符的位置
        int char_x = pen_x + slot->bitmap_left;
        int char_y = pen_y - slot->bitmap_top;

        // 绘制带有边框的字符
        draw_bitmap_with_border(&slot->bitmap, char_x, char_y, border_size, text_color, border_color);

        // 更新笔位置到下一个字符
        pen_x += slot->advance.x >> 6;  // 字符间距通常是 advance 的值除以 64
    }
}

int main(int argc, char **argv)
{
	wchar_t *chinese_str = L"Hello World!";

	FT_Library	  library;
	FT_Face 	  face;
	int error;
    FT_Vector     pen;
	FT_GlyphSlot  slot;
	int font_size = 24;
	const char *filepathname = NULL;
	int lcd_x = 0, lcd_y = 0; 

	if (argc < 5)
	{
		printf("Usage : %s <font_file> [font_size] <lcd_x> <lcd_y>\n", argv[0]);
		return -1;
	}

	if (argc == 5){
		filepathname = argv[1];
		font_size = strtoul(argv[2], NULL, 0);
		lcd_x = strtoul(argv[3], NULL, 0);
		lcd_y = strtoul(argv[4], NULL, 0);
	}
		
	fd_fb = open("/dev/fb0", O_RDWR);
	if (fd_fb < 0)
	{
		printf("can't open /dev/fb0\n");
		return -1;
	}

	if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
	{
		printf("can't get var\n");
		return -1;
	}

	line_width  = var.xres * var.bits_per_pixel / 8;
	pixel_width = var.bits_per_pixel / 8;
	screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
	fbmem = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
	if (fbmem == (unsigned char *)-1)
	{
		printf("can't mmap\n");
		return -1;
	}

	/* 清屏: 全部设为白色 */
	memset(fbmem, 0xcc, screen_size);

	/* 显示矢量字体 */
	error = FT_Init_FreeType( &library );			   /* initialize library */
	/* error handling omitted */
	
	error = FT_New_Face( library, filepathname, 0, &face ); /* create face object */
	/* error handling omitted */	
	slot = face->glyph;

	FT_Set_Pixel_Sizes(face, font_size, 0);

	/* 确定座标:
	 */
	//pen.x = 0;
	//pen.y = 0;

    /* set transformation */
    //FT_Set_Transform( face, 0, &pen);

    /* load glyph image into the slot (erase previous one) */
    error = FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );
	if (error)
	{
		printf("FT_Load_Char error\n");
		return -1;
	}
	
    // draw_bitmap( &slot->bitmap,
    //              var.xres/2,
    //              var.yres/2, 0x00000000);

	// 使用白色字体和黑色边框
    unsigned int white = 0x00FFFFFF;  // 假设32位颜色模式下白色是0xFFFFFFFF
    unsigned int black = 0x00000000;  // 黑色是0x00000000
    int border_size = 1;              // 边框宽度

   	// 绘制带边框的文字
    draw_text_with_border(face, chinese_str, lcd_x, lcd_y, border_size, white, black);
	return 0;	
}


AWTK开发手册-AWTK开发实践指南-中文手册.pdf AWTK = Toolkit AnyWhere 随着手机、智能手表等便携式设备的普及,用户对 GUI 的要求越来越高,嵌入式系统对高性能、高可靠性、低功耗、美观炫酷的 GUI 的需求也越来越迫切,ZLG开源 GUI 引擎 AWTK 应运而生。AWTK 全称为 Toolkit AnyWhere,是 ZLG 倾心打造的一套基于 C 语言开发的 GUI 框架。旨在为用户提供一个功能强大、高效可靠、简单易用、可轻松做出炫酷效果的 GUI 引擎,并支持跨平台同步开发,一次编程,终生使用。 最终目标: 支持开发嵌入式软件。 支持开发Linux应用程序。 支持开发MacOS应用程序。 支持开发Windows应用程序。 支持开发Android应用程序。 支持开发iOS应用程序。 支持开发2D游戏。 其主要特色有: 小巧。在精简配置下,不依赖第三方软件包,仅需要32K RAM + 256K FLASH即可开发一些简单的图形应用程序。 高效。采用脏矩形裁剪算法,每次只绘制和更新变化的部分,极大提高运行效率和能源利用率。 稳定。通过良好的架构设计和编程风格、单元测试、动态(valgrind)检查和Code Review保证其运行的稳定性。 丰富的GUI组件。提供窗口、对话框和各种常用的组件(用户可以配置自己需要的组件,降低对运行环境的要求)。 支持多种字体格式。内置位图字体(并提供转换工具),也可以使用stb_truetype或freetype加载ttf字体。 支持多种图片格式。内置位图图片(并提供转换工具),也可以使用stb_image加载png/jpg等格式的图片。 紧凑的二进制界面描述格式。可以手工编辑的XML格式的界面描述文件,也可以使用Qt Designer设计界面,然后转换成紧凑的二进制界面描述格式,提高运行效率,减小内存开销。 支持主题并采用紧凑的二进制格式。开发时使用XML格式描述主题,然后转换成紧凑的二进制格式,提高运行效率,减小内存开销。 支持裸系统,无需OS和文件系统。字体、图片、主题和界面描述数据都编译到代码中,以常量数据的形式存放,运行时无需加载到内存。 内置nanovg实现高质量的矢量动画,并支持SVG矢量图。 支持窗口动画、控件动画、滑动动画和高清LCD等现代GUI常见特性。 支持国际化(Unicode、字符串翻译和输入法等)。 可移植。支持移植到各种RTOS和嵌入式Linux系统,并通过SDL在各种流行的PC/手机系统上运行。 脚本化。从API注释中提取API的描述信息,通过这些信息可以自动生成各种脚本的绑定代码。 支持硬件2D加速(目前支持STM32的DMA2D和NXP的PXP)和GPU加速(OpenGL/OpenGLES/DirectX/Metal),充分挖掘硬件潜能。 丰富的文档和示例代码。 采用LGPL协议开源发布,在商业软件中使用时无需付费。 目前核心功能已经完成,内部开始在实际项目中使用了,欢迎有兴趣的朋友评估和尝试,期待您的反馈。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值