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;	
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值