固定点阵字库缩放

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,依次为原始点阵长宽,生成后的长宽。

        如果对你有帮助,麻烦点个赞 谢谢。

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值