前言
之前实现了PPT的抖动算法,发现效果差强人意,能支持的分辨率也有限,因此开始倒腾一些高阶的抖动算法。通过查询资料,发现了两个有趣的抖动算法。一个是bayer抖动,一个是floyd-steinberg算法。bayer算法具体使用场景暂未查到,floyd-steinberg的应用场景相对较大,单色打印机就是使用此算法实现抖动。
算法实现逻辑
bayer算法
这个算法,本质上就是把数据分割成8 * 8的色块,色块内的值标准化到0~63后与bayer矩阵对应位置的数据比较,大于数据,则画黑点,小于,则清除点处的颜色。之所以这么操作,是因为在小范围内,颜色的变化是渐变的,个人理解,这属于比简单抖动算法稍微复杂点的抖动实现,避免了简单抖动带来的分辨率不得不降低的问题。至于这个矩阵的获取方法,大家可以自行网上搜索计算。
其具体实现逻辑如下:
void dither_bayer_m3(unsigned char *image) { uint16_t i = 0, j = 0; unsigned char M3_Bayer[8][8] = { {0, 32, 8, 40, 2, 34, 10, 42}, // {48, 16, 56, 24, 50, 18, 58, 26}, // {12, 44, 4, 36, 14, 46, 6, 38}, // {60, 28, 52, 20, 62, 30, 54, 22}, // {3, 35, 11, 43, 1, 33, 9, 41}, // {51, 19, 59, 27, 49, 17, 57, 25}, // {15, 47, 7, 39, 13, 45, 5, 37}, // {63, 31, 55, 23, 61, 29, 53, 21}, // }; for (i = 0; i < IMAGE_W - 1; i++) { for (j = 0; j < IMAGE_H - 1; j++) { if ((image[i * IMAGE_H + j] >> 2) > M3_Bayer[j & 7][i & 7]) image[i * IMAGE_H + j] = 255;//实际可以是1,这里设置成255,仅仅是为了和后面画图标准统一 else image[i * IMAGE_H + j] = 0; } } }
floyd-steinberg
相比较于bayer算法,floyd-steinberg算法的实现思路就更加巧妙了,他的实现思路是:既然一个像素无法显示灰阶,那几个像素的和是否可以显示?而这个实现逻辑的核心,就是差值传递,也就是,实际使用的是像素点的差值作为参数,更新周围的像素点的值,使所有的像素点的值尽量往255和0两边靠,最后以128为分界线决定是否画点。
其实现逻辑如下:
void dither_floyd_steinberg(uint8_t *image) { uint16_t i = 0, j = 0; uint8_t oldPixel, newPixel, errPixel; uint8_t add_flag = 0; for (i = 0; i < IMAGE_W - 1; i++) { for (j = 0; j < IMAGE_H - 1; j++) { oldPixel = image[i * IMAGE_H + j]; if (oldPixel > 128) { newPixel = 255; errPixel = 255 - oldPixel; add_flag = 0; } else { newPixel = 0; errPixel = oldPixel; add_flag = 1; } image[i * IMAGE_H + j] = newPixel; if (add_flag) { if (image[i * IMAGE_H + j + 1] > 255 - errPixel * 7 / 16) { image[i * IMAGE_H + j + 1] = 255; } else { image[i * IMAGE_H + j + 1] = image[i * IMAGE_H + j + 1] + errPixel * 7 / 16; } if (image[(i + 1) * IMAGE_H + j] > 255 - errPixel * 5 / 16) { image[(i + 1) * IMAGE_H + j] = 255; } else { image[(i + 1) * IMAGE_H + j] = image[(i + 1) * IMAGE_H + j] + errPixel * 5 / 16; } if (image[(i + 1) * IMAGE_H + j + 1] > 255 - errPixel * 1 / 16) { image[(i + 1) * IMAGE_H + j + 1] = 255; } else { image[(i + 1) * IMAGE_H + j + 1] = image[(i + 1) * IMAGE_H + j + 1] + errPixel * 1 / 16; } if (j > 0) { if (image[(i + 1) * IMAGE_H + j - 1] > 255 - errPixel * 3 / 16) { image[(i + 1) * IMAGE_H + j - 1] = 255; } else { image[(i + 1) * IMAGE_H + j - 1] = image[(i + 1) * IMAGE_H + j - 1] + errPixel * 3 / 16; } } } else { if (image[i * IMAGE_H + j + 1] < errPixel * 7 / 16) { image[i * IMAGE_H + j + 1] = 0; } else { image[i * IMAGE_H + j + 1] = image[i * IMAGE_H + j + 1] - errPixel * 7 / 16; } if (image[(i + 1) * IMAGE_H + j] < errPixel * 5 / 16) { image[(i + 1) * IMAGE_H + j] = 0; } else { image[(i + 1) * IMAGE_H + j] = image[(i + 1) * IMAGE_H + j] - errPixel * 5 / 16; } if (image[(i + 1) * IMAGE_H + j + 1] < errPixel * 1 / 16) { image[(i + 1) * IMAGE_H + j + 1] = 0; } else { image[(i + 1) * IMAGE_H + j + 1] = image[(i + 1) * IMAGE_H + j + 1] - errPixel * 1 / 16; } if (j > 0) { if (image[(i + 1) * IMAGE_H + j - 1] < errPixel * 3 / 16) { image[(i + 1) * IMAGE_H + j - 1] = 0; } else { image[(i + 1) * IMAGE_H + j - 1] = image[(i + 1) * IMAGE_H + j - 1] - errPixel * 3 / 16; } } } } } }
编码验证
main.c
int main(void) { system_init(); key_init(); epd_init(); DEV_Delay_ms(2000); show_picture(0); while (get_key() == 0) { continue; } show_picture(1); while (get_key() == 0) { continue; } show_picture(0); while (get_key() == 0) { continue; } show_picture(2); while (true) { } return 0; }
my_epd.c
void show_picture(char ditherEn) { unsigned char *pDitherImage; int idxX, idxY; char level; int x = 0, y = 0; pDitherImage = (unsigned char *)malloc(sizeof(gImage_raw)); My_memcpy(pDitherImage, (unsigned char *)gImage_raw, sizeof(gImage_raw)); if (ditherEn == 1) { dither_floyd_steinberg(pDitherImage); } else if (ditherEn == 2) { dither_bayer_m3(pDitherImage); } for (idxX = 0; idxX < IMAGE_W; idxX++) { for (idxY = 0; idxY < IMAGE_H; idxY++) { if (pDitherImage[idxX * IMAGE_H + idxY] / 128) draw_fb_point(x + idxX, y + idxY); else clear_fb_point(x + idxX, y + idxY); } } free(pDitherImage); updata_to_epd(DISPLAY_ALL); }
数据准备
既然分变率不再像简单算法那么受限,那显示可选的图片就多了。而图像效果验证最经典的图片莫过于IEEE女神了,因此这里也直接采用IEEE女神图建立数据,图片如下:
受限于文字量,无法粘贴,导出的数据就不提供了,大家可以按照简单抖动算法实现中的方法自行导出。
效果验证
原图
bayer算法
floyd-steinberg算法