1.前言
我所使用的点阵字库是由一个bit控制一个像素,也就是说,只有高亮和不亮(背景色)两种,然后项目中运用了关于固定点阵字库缩放的算法,主要用了几种方法,效果达不到很完美,主要有,等比例放大,邻插值算法缩放,对三次插值算法,后面为了防止缩小时造成部分笔画丢失,我还用一个超采样技术减少缩放过程中笔画的丢失。
2.等比例放大算法
这个没什么好说的,就是按照固定倍数去增加,比如16*16,变成32*32的,就是把每一个像素点长和宽都扩大两倍,就是由原先的1个像素点生成2*2的块,然后拼接起来。
/*********************************************************************/
/************** 只能按照比例扩 ***************/
/*********************************************************************/
// 函数:将16x16的点阵扩大到16的整数倍
void upscaleMatrix(uint8_t *original_matrix, uint8_t *new_matrix, int zoomFactor) {
// 计算新的点阵宽度和高度
int new_width = ORIGINAL_WIDTH * zoomFactor;
int new_height = ORIGINAL_WIDTH * zoomFactor;
// 计算原始点阵和新点阵每行的字节数
int original_bytes_per_row = (ORIGINAL_WIDTH + 7) / 8;
int new_bytes_per_row = (new_width + 7) / 8;
// 初始化新点阵矩阵
memset(new_matrix, 0, new_height * new_bytes_per_row);
// 遍历原始点阵的每个像素点
for (int y = 0; y < ORIGINAL_WIDTH; y++) {
for (int x = 0; x < ORIGINAL_WIDTH; x++) {
// 计算原始点阵中点的值
int original_index = y * original_bytes_per_row + (x / 8);
int original_bit = x % 8;
uint8_t pixel = (original_matrix[original_index] >> (7 - original_bit)) & 1;
// 将点复制到新点阵的相应位置,每个点复制zoomFactor * zoomFactor次
for (int i = 0; i < zoomFactor; i++) {
for (int j = 0; j < zoomFactor; j++) {
// 计算新点阵的字节索引和位索引
int new_index = (y * zoomFactor + i) * new_bytes_per_row + (x * zoomFactor + j) / 8;
int new_bit = (x * zoomFactor + j) % 8;
// 将原始像素点复制到新位置
new_matrix[new_index] |= (pixel << (7 - new_bit));
}
}
}
}
}
优点:不失真,不会说扩大后认不出来这是什么字
缺点:锯齿化很严重,我始终觉得这个由点阵字库无法避免,除非增加像素的复杂度,让几个bit控制一个像素位,但很复杂,运算量也很大。这种算法只能固定比例的缩放,如果时特殊需求18*18之类的,就不行了。
3.邻插值算法
这种算法怎么说呐,算是运算量来说最小的一种算法,只是简单的逻辑运算,通过某些规则/规范/约束,获取这些多出坐标点的像素值。具体的话去网上搜,一大堆例子,这里直接上代码;
/***********************************************************************/
/******** 邻插值算法 任意比例扩大 *******/
/**********************************************************************/
void nearestNeighborInterpolationBitMap(const uint8_t *src, uint8_t *dst,
int srcWidth, int srcHeight,
int dstWidth, int dstHeight) {
// 计算缩放比例
double scaleX = (double)srcWidth / dstWidth;
double scaleY = (double)srcHeight / dstHeight;
// 目标图像的每一行可能不是一个字节的整数倍,因此预先计算每行的字节数
int srcBytesPerRow = (srcWidth + 7) / 8;
int dstBytesPerRow = (dstWidth + 7) / 8;
// 初始化目标图像的内存
memset(dst, 0, dstHeight * dstBytesPerRow);
// 遍历目标图像的每个像素点
for (int dstY = 0; dstY < dstHeight; ++dstY) {
for (int dstX = 0; dstX < dstWidth; ++dstX) {
// 使用最近邻插值计算源坐标
int srcX = (int)(dstX * scaleX);
int srcY = (int)(dstY * scaleY);
// 确保源坐标在有效范围内
srcX = srcX >= 0 ? (srcX < srcWidth ? srcX : srcWidth - 1) : 0;
srcY = srcY >= 0 ? (srcY < srcHeight ? srcY : srcHeight - 1) : 0;
// 计算源像素和目标像素的字节索引及位位置
int srcByteIndex = srcY * srcBytesPerRow + (srcX / 8);
int srcBitIndex = 7 - (srcX % 8);
int dstByteIndex = dstY * dstBytesPerRow + (dstX / 8);
int dstBitIndex = 7 - (dstX % 8);
// 如果源像素为1,则复制到目标像素
if (src[srcByteIndex] & (1 << srcBitIndex)) {
dst[dstByteIndex] |= (1 << dstBitIndex);
}
}
}
}
优点:可以任意大小的字库,任意大小的缩放;
缺点:缩小时有很严重的失真。
为了减少失真效果我又加了超采样技术进去,使其对比邻近的3*3的块,从而决定舍不舍去像素。减少了失真,但却显得跟加粗了一样,怪怪的。
void bilinearInterpolationBitMap(const uint8_t *src, uint8_t *dst,
int srcWidth, int srcHeight,
int dstWidth, int dstHeight) {
// 计算缩放比例
double scaleX = (double)srcWidth / dstWidth;
double scaleY = (double)srcHeight / dstHeight;
// 初始化目标图像的内存
memset(dst, 0, dstHeight * ((dstWidth + 7) / 8));
// 遍历目标图像的每个像素点
for (int dstY = 0; dstY < dstHeight; ++dstY) {
for (int dstX = 0; dstX < dstWidth; ++dstX) {
// 计算目标像素在源图像中的对应坐标
double srcX = dstX * scaleX;
double srcY = dstY * scaleY;
// 计算四个最近邻像素的坐标
int x0 = (int)srcX;
int y0 = (int)srcY;
int x1 = (x0 < srcWidth - 1) ? x0 + 1 : x0;
int y1 = (y0 < srcHeight - 1) ? y0 + 1 : y0;
// 计算权重
double dx = srcX - x0;
double dy = srcY - y0;
double w00 = (1.0 - dx) * (1.0 - dy);
double w10 = dx * (1.0 - dy);
double w01 = (1.0 - dx) * dy;
double w11 = dx * dy;
// 计算四个最近邻像素的字节索引和位位置
int byteIndex00 = y0 * ((srcWidth + 7) / 8) + (x0 / 8);
int bitIndex00 = 7 - (x0 % 8);
int byteIndex10 = y1 * ((srcWidth + 7) / 8) + (x1 / 8);
int bitIndex10 = 7 - (x1 % 8);
int byteIndex01 = (y0 + 1) * ((srcWidth + 7) / 8) + (x0 / 8);
int bitIndex01 = 7 - (x0 % 8);
int byteIndex11 = (y1 + 1) * ((srcWidth + 7) / 8) + (x1 / 8);
int bitIndex11 = 7 - (x1 % 8);
// 计算双线性插值结果
int bit00 = src[byteIndex00] & (1 << bitIndex00) ? 1 : 0;
int bit10 = src[byteIndex10] & (1 << bitIndex10) ? 1 : 0;
int bit01 = src[byteIndex01] & (1 << bitIndex01) ? 1 : 0;
int bit11 = src[byteIndex11] & (1 << bitIndex11) ? 1 : 0;
int result = (bit00 * w00 + bit10 * w10 + bit01 * w01 + bit11 * w11) > 0.5 ? 1 : 0;
// 设置目标像素
int dstByteIndex = dstY * ((dstWidth + 7) / 8) + (dstX / 8);
int dstBitIndex = 7 - (dstX % 8);
dst[dstByteIndex] &= ~(1 << dstBitIndex); // Clear the bit first
dst[dstByteIndex] |= result << dstBitIndex; // Set the bit
}
}
}
4.对三次插值算法
我以为这种会是表达效果最好的一个,但是因为我们是点阵数据,它在处理衔接度比较高的图像时效果很好,但是对于图库并没有效果那么明显,至于具体原理我也没看太懂,稀里糊涂自己做的。
/*******************************************************************************************/
/******** 双三次插值算法 任意比例扩大 **********/
/*******************************************************************************************/
// 双三次插值核
float cubicKernel(float x) {
return (x * x * x - 4 * x * x + 6 * x) * (1.0f / 6.0f);
}
// 计算一个像素值
uint8_t getPixel(const uint8_t *src, int width, int height, int x, int y) {
int bitIndex = (y * width + x) % 8;
int byteIndex = (y * width + x) / 8;
return (src[byteIndex] >> (7 - bitIndex)) & 1;
}
// 设置一个像素值
void setPixel(uint8_t *dst, int width, int height, int x, int y, uint8_t value) {
int bitIndex = (y * width + x) % 8;
int byteIndex = (y * width + x) / 8;
if (value) {
dst[byteIndex] |= (1 << (7 - bitIndex));
} else {
dst[byteIndex] &= ~(1 << (7 - bitIndex));
}
}
// 双三次插值
uint8_t bicubicInterpolate(const uint8_t *src, int width, int height, float x, float y) {
int x0 = (int)(x);
int y0 = (int)(y);
x -= x0;
y -= y0;
uint8_t value = 0;
for (int i = 0; i < 4; ++i) {
float dy = cubicKernel(i - 1 - y);
for (int j = 0; j < 4; ++j) {
float dx = cubicKernel(j - 1 - x);
// 使用边界条件检查
if (x0 + j - 1 >= 0 && x0 + j - 1 < width && y0 + i - 1 >= 0 && y0 + i - 1 < height) {
value += getPixel(src, width, height, x0 + j - 1, y0 + i - 1) * dx * dy;
}
}
}
return value > 0.5f ? 1 : 0;
}
void bicubicResize(const uint8_t *src, uint8_t *dst, int srcWidth, int srcHeight, int dstWidth, int dstHeight) {
float scaleX = (float)srcWidth / dstWidth;
float scaleY = (float)srcHeight / dstHeight;
// 初始化目标图像为0(即全白)
memset(dst, 0xFF, ((dstWidth + 7) / 8) * dstHeight);
for (int dstY = 0; dstY < dstHeight; ++dstY) {
for (int dstX = 0; dstX < dstWidth; ++dstX) {
float srcX = scaleX * dstX;
float srcY = scaleY * dstY;
uint8_t interpolatedValue = bicubicInterpolate(src, srcWidth, srcHeight, srcX, srcY);
setPixel(dst, dstWidth, dstHeight, dstX, dstY, interpolatedValue);
}
}
}
这些代码拿过去都可以直接用的,我把他们的接口除了函数名,其他封装的都一样了,然后呐这些参数依次为:原始点阵数据的数组首地址,生成后的数组首地址,int srcWidth, int srcHeight, int dstWidth, int dstHeight,依次为原始点阵长宽,生成后的长宽。
如果对你有帮助,麻烦点个赞 谢谢。