jpeg编码学习

正点原子stm32教程提到过jpeg解码库libjpeg,但是没有提到jpeg编码,我也好奇jpeg编码怎么实现,用代码怎么生成jpeg文件的。所以最近学习了jpeg编码,在这里做记录。

参考文章

jpeg图片格式详解 https://blog.csdn.net/yun_hen/article/details/78135122
这篇文章比较详细的介绍了jpeg的文件结构以及每个段的内容的解析。
但是jpeg使用的游程编码和哈夫曼编码描述很少。

jpeg编码原理 https://zhuanlan.zhihu.com/p/600252083
jpeg中的范式哈夫曼编码 https://zhuanlan.zhihu.com/p/72044095
这两篇文章进行了补充。
但是依旧没有实际的代码实现jpeg编码。

下面这位给出了C语言的代码,
learn_jpeg https://github.com/xnvi/learn-jpeg-encode

使用devcpp编译代码,有字节对齐的问题,我修改后代码如下,
代码思路是从bmp文件读取RGB数据,然后进行jpeg压缩,对应的讲解和测试图片在作者的仓库都存在,clone仓库测试即可。

我阅读代码后,发现作者使用了YUV444的格式进行jpeg压缩,那压缩效果肯定不行,所以我又改成YUV420的数据格式,然后进行jpeg压缩。对比压缩后的文件大小,很明显。
压缩效果对比
test.bmp是原始的RGB的图片,
output_master.jpg是原作者的master分支代码的结果,使用YUV444进行jpeg编码。
output_YUV420.jpg是我修改后使用YUV420进行jpeg压缩的结果。
output_win11.jpg是使用win11自带的画图软件生成的jpeg图片,显示压缩率更高了。

使用YUV420进行jpeg压缩更符合我们学习jpeg压缩原理的目的,所以如下是我修改后使用YUV420压缩图片的代码。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>

#define DCT_SIZE 8
#define HUFF_TREE_DC_LEN 16
#define HUFF_TREE_AC_LEN 256
#define INPUT_BMP_FILE   "test.bmp"
#define OUTPUT_JPEG_FILE  "output_YUV420.jpeg"

FILE *fp_jpeg = NULL;

#pragma pack(push) //保存对齐状态
#pragma pack(1)//设定为4字节对齐
// 位图文件头

typedef struct  BMP_FILE_HEAD
{
    uint16_t bfType; // 文件类型,必须是0x424D,也就是字符BM
    uint32_t bfSize; // 文件大小,包含头
    uint16_t bfReserved1; // 保留字
    uint16_t bfReserved2; // 保留字
    uint32_t bfOffBits; // 文件头到实际的图像数据的偏移字节数
}BMP_FILE_HEAD;


// 位图信息头
typedef struct BMP_INFO_HEAD
{
    uint32_t biSize; // 这个结构体的长度,为40字节
    int32_t biWidth; // 图像的宽度
    int32_t biHeight; // 图像的长度
    uint16_t biPlanes; // 必须是1
    uint16_t biBitCount; // 表示颜色时要用到的位数,常用的值为 1(黑白二色图),4(16 色图),8(256 色),24(真彩色图)(新的.bmp 格式支持 32 位色,这里不做讨论)
    uint32_t biCompression; // 指定位图是否压缩,有效的值为 BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS(都是一些Windows定义好的常量,暂时只考虑BI_RGB不压缩的情况)
    uint32_t biSizeImage; // 指定实际的位图数据占用的字节数
    int32_t biXPelsPerMeter; // 指定目标设备的水平分辨率
    int32_t biYPelsPerMeter; // 指定目标设备的垂直分辨率
    int32_t biClrUsed; // 指定本图象实际用到的颜色数,如果该值为零,则用到的颜色数为 2 的 biBitCount 次方
    int32_t biClrImportant; // 指定本图象中重要的颜色数,如果该值为零,则认为所有的颜色都是重要的
}BMP_INFO_HEAD;
#pragma pack(pop)//恢复对齐状态

const uint8_t default_luma_table[] =
{
    16, 11, 10, 16,  24,  40,  51,  61,
    12, 12, 14, 19,  26,  58,  60,  55,
    14, 13, 16, 24,  40,  57,  69,  56,
    14, 17, 22, 29,  51,  87,  80,  62,
    18, 22, 37, 56,  68, 109, 103,  77,
    24, 35, 55, 64,  81, 104, 113,  92,
    49, 64, 78, 87, 103, 121, 120, 101,
    72, 92, 95, 98, 112, 100, 103,  99,
};

const uint8_t default_chroma_table[] =
{
    17, 18, 24, 47, 99, 99, 99, 99,
    18, 21, 26, 66, 99, 99, 99, 99,
    24, 26, 56, 99, 99, 99, 99, 99,
    47, 66, 99, 99, 99, 99, 99, 99,
    99, 99, 99, 99, 99, 99, 99, 99,
    99, 99, 99, 99, 99, 99, 99, 99,
    99, 99, 99, 99, 99, 99, 99, 99,
    99, 99, 99, 99, 99, 99, 99, 99,
};

const uint8_t zigzag_table[] =
{
    0,   1,  5,  6, 14, 15, 27, 28,
    2,   4,  7, 13, 16, 26, 29, 42,
    3,   8, 12, 17, 25, 30, 41, 43,
    9,  11, 18, 24, 31, 40, 44, 53,
    10, 19, 23, 32, 39, 45, 52, 54,
    20, 22, 33, 38, 46, 51, 55, 60,
    21, 34, 37, 47, 50, 56, 59, 61,
    35, 36, 48, 49, 57, 58, 62, 63,
};

const uint8_t default_ht_luma_dc_len[16] =
{
    0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0
};

const uint8_t default_ht_luma_dc[12] =
{
    0,1,2,3,4,5,6,7,8,9,10,11
};

const uint8_t default_ht_chroma_dc_len[16] =
{
    0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0
};

const uint8_t default_ht_chroma_dc[12] =
{
    0,1,2,3,4,5,6,7,8,9,10,11
};

const uint8_t default_ht_luma_ac_len[16] =
{
    0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d
};

const uint8_t default_ht_luma_ac[162] =
{
    0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
    0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0,
    0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28,
    0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
    0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
    0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
    0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
    0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5,
    0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2,
    0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
    0xF9, 0xFA
};

const uint8_t default_ht_chroma_ac_len[16] =
{
    0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77
};

const uint8_t default_ht_chroma_ac[162] =
{
    0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
    0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0,
    0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26,
    0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
    0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
    0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
    0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5,
    0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3,
    0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA,
    0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
    0xF9, 0xFA
};


typedef struct
{
    uint8_t raw;
    uint8_t code_length;
    uint16_t code_word;
} coefficients;

#pragma pack(1)//设定为4字节对齐
// 参考JFIF标准 ITU-T T.871
typedef struct 
{
    uint16_t len;
    uint8_t  identifier[5];
    uint16_t version;
    uint8_t  units;
    uint16_t Hdensity;
    uint16_t Vdensity;
    uint8_t  HthumbnailA;
    uint8_t  VthumbnailA;
} jfif_header;

typedef struct 
{
    uint8_t id; // 颜色分量ID
    uint8_t sampling_factors; // 水平/垂直采样因子,高4位水平采样因子,低4位垂直采样因子
    uint8_t qt_table_id; // 量化表的ID
} color_component;

typedef struct 
{
    uint8_t         precision; // 图像的采样精度,常用8位
    uint16_t        height; // 图像高度
    uint16_t        width; // 图像宽度
    uint8_t         component_num; // 角色分量数,YUV一共3个分量
    color_component component[3]; // 颜色分量
} frame_header;

#pragma pack()//设定为4字节对齐

int32_t read_bmp_to_rgb888(char *path, uint8_t **data, int32_t *width, int32_t *height);
void rgb888_dump_bmp(char *path, uint8_t *data, int32_t width, int32_t height);
void rgb888_dump_ppm(char *path, uint8_t *data, int32_t width, int32_t height);

int32_t read_block(const uint8_t *rgb, uint8_t *block, int32_t w, int32_t h, int32_t x, int32_t y);
// int32_t block_rgb_to_yuv444(const uint8_t *block, uint8_t *y, uint8_t *u, uint8_t *v);
// int32_t block_dct(const uint8_t *in, double *out);
int32_t block_rgb_to_yuv444(const uint8_t *block, int8_t *y, int8_t *u, int8_t *v);
int32_t block_dct(const int8_t *in, double *out);
int32_t block_quantization(const uint8_t *table, const double *in, int32_t *out);
int32_t block_zigzag(int32_t *in, int32_t *out);
int32_t block_encode(int32_t *in, int32_t *last_dc, coefficients *dc, coefficients *ac);
int32_t huffman_encode();
int32_t make_qt_table(int32_t qt, const uint8_t *in, uint8_t *out);
int32_t make_huffman_tree(uint8_t *code_length, uint8_t *raw_val, int32_t out_len, coefficients *out);

double ck(int32_t k);
int32_t get_val_bit_and_code(int32_t value, uint16_t *bit_num, uint16_t *code);

uint16_t sw16(uint16_t dat);
int32_t jpeg_write_file(uint8_t *data, int32_t len);
int32_t jpeg_write_bits(uint32_t data, int32_t len, int32_t flush);
int32_t jpeg_write_u8(uint8_t data);
int32_t jpeg_write_u16(uint16_t data);
int32_t jpeg_write_u32(uint32_t data);

int32_t jpeg_write_file(uint8_t *data, int32_t len)
{
    return fwrite(data, 1, len, fp_jpeg);
}

int32_t jpeg_write_bits(uint32_t data, int32_t len, int32_t flush)
{
    static uint32_t bit_ptr = 0; // 与平时阅读习惯相反,最高位计为0,最低位计为31
    static uint32_t bitbuf = 0x00000000;
    uint8_t w = 0x00;

    bitbuf |= data << (32 - bit_ptr - len);
    bit_ptr += len;

    while (bit_ptr >= 8)
    {
        w = (uint8_t)((bitbuf & 0xFF000000) >> 24);
        jpeg_write_u8(w);
        if (w == 0xFF)
        {
            jpeg_write_u8(0x00);
        }
        bitbuf <<= 8;
        bit_ptr -= 8;
    }

    if (flush)
    {
        w = (uint8_t)((bitbuf & 0xFF000000) >> 24);
        jpeg_write_u8(w);
    }
    return 0;
}

int32_t jpeg_write_u8(uint8_t data)
{
    uint8_t wd = data;
    return fwrite(&wd, 1, sizeof(uint8_t), fp_jpeg);
}

int32_t jpeg_write_u16(uint16_t data)
{
    uint16_t wd = data;
    return fwrite(&wd, 1, sizeof(uint16_t), fp_jpeg);
}

int32_t jpeg_write_u32(uint32_t data)
{
    uint32_t wd = data;
    return fwrite(&wd, 1, sizeof(uint32_t), fp_jpeg);
}

uint16_t sw16(uint16_t dat)
{
    uint16_t lo = (dat & 0x00ff);
    uint16_t hi = ((dat & 0xff00) >> 8);
    return (uint16_t)((lo << 8) | hi);
}

double ck(int32_t k)
{
    if (k == 0)
    {
        return sqrt(1.0 / DCT_SIZE);
    }
    else
    {
        return sqrt(2.0 / DCT_SIZE);
    }
}

// int32_t block_dct(const uint8_t *in, double *out)
int32_t block_dct(const int8_t *in, double *out)
{
    int32_t i, j, n, m;
    double sum = 0.0;

    for (m = 0; m < DCT_SIZE; m++)
    {
        for (n = 0; n < DCT_SIZE; n++)
        {
            for (i = 0; i < DCT_SIZE; i++)
            {
                for (j = 0; j < DCT_SIZE; j++)
                {
                    // sum += in[i][j] * cos((2 * j + 1) * n * M_PI / (2 * DCT_SIZE)) * cos((2 * i + 1) * m * M_PI / (2 * DCT_SIZE));
                    sum += in[i * DCT_SIZE + j] * cos((2 * j + 1) * n * M_PI / (2 * DCT_SIZE)) * cos((2 * i + 1) * m * M_PI / (2 * DCT_SIZE));
                }
            }
            // out[m][n] = sum * ck(m) * ck(n);
            out[m * DCT_SIZE + n] =  sum * ck(m) * ck(n);
            sum = 0.0;
        }
    }

    return 0;
}

int32_t read_block(const uint8_t *rgb, uint8_t *block, int32_t w, int32_t h, int32_t x, int32_t y)
{
    // 长宽不足8可以填充,也可以复制边缘像素
    int32_t dx, dy;
    int32_t rgb_offset, block_offset;

    for (dy = 0; dy < DCT_SIZE * 2; dy++)
    {
        for (dx = 0; dx < DCT_SIZE * 2; dx++)
        {
            rgb_offset = (((y + dy) * w) + (x + dx)) * 3;
            block_offset = (dy * DCT_SIZE * 2 + dx) * 3;
            // printf("b %d \n", block_offset);

            if (x + dx >= w)
            {
                block[block_offset + 0] = block[block_offset - 3 + 0];
                block[block_offset + 1] = block[block_offset - 3 + 1];
                block[block_offset + 2] = block[block_offset - 3 + 2];
                continue;
            }
            if (y + dy >= h)
            {
                block[block_offset + 0] = block[block_offset - 3 * DCT_SIZE + 0];
                block[block_offset + 1] = block[block_offset - 3 * DCT_SIZE + 1];
                block[block_offset + 2] = block[block_offset - 3 * DCT_SIZE + 2];
                continue;
            }
            block[block_offset + 0] = rgb[rgb_offset + 0];
            block[block_offset + 1] = rgb[rgb_offset + 1];
            block[block_offset + 2] = rgb[rgb_offset + 2];
        }
    }

    return 0;
}

// YUV有BT601、BT709、BT2020
// jpeg 使用的yuv公式 https://en.wikipedia.org/wiki/YCbCr
// 参考JFIF标准 ITU-T T.871
int32_t block_rgb_to_yuv444(const uint8_t *block, int8_t *y, int8_t *u, int8_t *v)
{
    uint8_t r, g, b;
    int32_t dx, dy, index;
    int ori_x, ori_y;   //像素点在入参的block中的位置
    int32_t rgb_offset, block_offset, YU_offset;
    int     mcu_offset[] = {0, 64*1, 64*2, 64*3}; 
    int     y_offset[] = {0, 0, 8, 8};
    int     x_offset[] = {0, 8, 0, 8};
    int     new_Y_offset = 0, new_uv_offset = 0;    //在输出的数组中,YUV的offset
    float luma, cb, cr;

    for(index = 0; index < 4; index++)
    {
        for (dy = 0; dy < DCT_SIZE; dy++)
        {
            for (dx = 0; dx < DCT_SIZE; dx++)
            {
                ori_y = dy + y_offset[index];
                ori_x = dx + x_offset[index];
                block_offset = (ori_y * DCT_SIZE * 2 + ori_x);
                new_Y_offset = (dy * DCT_SIZE + dx);
                new_uv_offset = ori_y / 2 * DCT_SIZE + ori_x / 2;
                

                r = block[block_offset * 3 + 0];
                g = block[block_offset * 3 + 1];
                b = block[block_offset * 3 + 2];
                // luma = 0.299f   * r + 0.587f  * g + 0.114f  * b;
                // cb   = -0.1687f * r - 0.3313f * g + 0.5f    * b + 128.0f;
                // cr   = 0.5f     * r - 0.4187f * g - 0.0813f * b + 128.0f;
                // y[block_offset] = (uint8_t)luma;
                // u[block_offset] = (uint8_t)cb;
                // v[block_offset] = (uint8_t)cr;
                luma = 0.299f   * r + 0.587f  * g + 0.114f  * b - 128;
                cb   = -0.1687f * r - 0.3313f * g + 0.5f    * b;
                cr   = 0.5f     * r - 0.4187f * g - 0.0813f * b;
                y[new_Y_offset + mcu_offset[index]] = (int8_t)round(luma);
                u[new_uv_offset] = (int8_t)round(cb);
                v[new_uv_offset] = (int8_t)round(cr);
            }
        }
    }
    return 0;
}

int32_t make_qt_table(int32_t qt, const uint8_t *in, uint8_t *out)
{
    int32_t dx, dy;
    int32_t offset;
    float alpha;
    float tmp;

    if (qt < 50)
    {
        alpha = 50.0f / qt;
    }
    else
    {
        alpha = 2.0f - qt / 50.0f;
    }

    for (dy = 0; dy < DCT_SIZE; dy++)
    {
        for (dx = 0; dx < DCT_SIZE; dx++)
        {
            offset = dy * DCT_SIZE + dx;
            tmp = in[offset] * alpha;
            tmp = tmp < 1 ? 1 : tmp;
            tmp = tmp > 255 ? 255 : tmp;
            out[offset] = (uint8_t)tmp;
        }
    }
    return 0;
}

int32_t make_huffman_tree(uint8_t *code_length, uint8_t *raw_val, int32_t out_len, coefficients *out)
{
    int32_t i, j;
    uint32_t code = 0;
    int32_t code_bits = 1;
    int32_t count = 0;

    for(i = 0; i < 16; i++)
    {
        for (j = 0; j < code_length[i]; j++)
        {
            // 生成
            // out[count].code_word = code;
            // out[count].code_length = code_bits;
            // out[count].raw = raw_val[count];

            // 生成并排序
            out[raw_val[count]].raw = raw_val[count];
            out[raw_val[count]].code_word = code;
            out[raw_val[count]].code_length = code_bits;
            // printf("num %d, raw %#02x, code %#02x, len %d \n", count, raw_val[count], code, code_bits);

            count += 1;
            code += 1;
        }
        code_bits += 1;
        code <<= 1;
    }

    // for (i = 0; i < out_len; i++)
    // {
    // 	printf("num %d, raw %#02x, code %#02x, len %d \n", i, out[i].raw, out[i].code_word, out[i].code_length);
    // }

    return 0;
}

int32_t block_quantization(const uint8_t *table, const double *in, int32_t *out)
{
    int32_t dx, dy;
    int32_t offset;

    for (dy = 0; dy < DCT_SIZE; dy++)
    {
        for (dx = 0; dx < DCT_SIZE; dx++)
        {
            offset = dy * DCT_SIZE + dx;
            out[offset] = round(in[offset] / table[offset]);
        }
    }
    return 0;
}

int32_t block_zigzag(int32_t *in, int32_t *out)
{
    int32_t i;
    for (i = 0; i < DCT_SIZE * DCT_SIZE; i++)
    {
        // out[i] = in[zigzag_table[i]];
        out[zigzag_table[i]] = in[i];
    }
}

int32_t block_encode(int32_t *in, int32_t *last_dc, coefficients *dc, coefficients *ac)
{
    int32_t i;
    int32_t dc_delta = 0;
    uint16_t bit_num, code;
    int32_t last_not_zero_cnt = 0;
    int32_t zero_cnt = 0;
    uint8_t run_size = 0;
    
    // 直流
    dc_delta = in[0] - *last_dc;
    *last_dc = in[0];
    if (dc_delta != 0)
    {
        get_val_bit_and_code(dc_delta, &bit_num, &code);
        jpeg_write_bits(dc[bit_num].code_word, dc[bit_num].code_length, 0);
        jpeg_write_bits(code, bit_num, 0);
    }
    else
    {
        jpeg_write_bits(dc[0].code_word, dc[0].code_length, 0);
    }

    // 交流
    for (i = 63; i > 0; i--)
    {
        if (in[i] != 0)
        {
            last_not_zero_cnt = i;
            break;
        }
    }

    for (i = 1; i <= last_not_zero_cnt; i++)
    {
        zero_cnt = 0;
        while (in[i] == 0)
        {
            zero_cnt++;
            i++;
            if (zero_cnt == 16)
            {
                jpeg_write_bits(ac[0xF0].code_word, ac[0xF0].code_length, 0);
                zero_cnt = 0;
            }
        }

        get_val_bit_and_code(in[i], &bit_num, &code);
        run_size = zero_cnt << 4 | bit_num;
        jpeg_write_bits(ac[run_size].code_word, ac[run_size].code_length, 0);
        jpeg_write_bits(code, bit_num, 0);
    }
    if (last_not_zero_cnt != 63)
    {
        jpeg_write_bits(ac[0].code_word, ac[0].code_length, 0);
    }
}

int32_t get_val_bit_and_code(int32_t value, uint16_t *bit_num, uint16_t *code)
{
    // 计算方法:正数不变,负数则计算它绝对值的反码
    // 位数为这个数的绝对值的位数

    // 优化方法,利用补码特性
    int32_t abs_val = value;
    if ( value < 0 )
    {
        abs_val = -abs_val;
        value -= 1;
    }
    *bit_num = 1;
    while (abs_val >>= 1)
    {
        *bit_num += 1;
    }
    *code = (uint16_t)(value & ((1 << *bit_num) - 1));

#if 0
    // 原始方法
    int32_t tmp_val = value;
    int32_t abs_val = value;
    if (value < 0)
    {
        abs_val = -value;
        tmp_val = ~abs_val;
    }
    *bit_num = 1;
    while (abs_val >>= 1)
    {
        *bit_num += 1;
    }
    *code = (uint16_t)(tmp_val & ((1 << *bit_num) - 1));
#endif

    return 0;
}

int32_t read_bmp_to_rgb888(char *path, uint8_t **data, int32_t *width, int32_t *height)
{
    FILE *img_fp = NULL;
    BMP_FILE_HEAD BFH;
    BMP_INFO_HEAD BIH;
    int32_t file_size = 0;
    int32_t ret = 0;
    int32_t i = 0, j = 0;
    int32_t h = 0, v = 0;
    uint32_t offset = 0;
    uint32_t line_size = 0;
    uint8_t *bmp_data = NULL;
    uint8_t *rgb888_data = NULL;

    img_fp = fopen(path, "rb");
    if (img_fp == NULL)
    {
        printf("can not open %s\n", path);
        *data = NULL;
        *width = 0;
        *height = 0;
        return 1;
    }

    fseek(img_fp, 0, SEEK_END);
    file_size = ftell(img_fp);
    fseek(img_fp, 0, SEEK_SET);

    if (file_size < 54)
    {
        printf("file %s size error\n", path);
        *data = NULL;
        *width = 0;
        *height = 0;
        goto end;
    }

    fseek(img_fp, 0, SEEK_SET);
    fread(&BFH, sizeof(BFH), 1, img_fp); // 读取BMP文件头
    fread(&BIH, sizeof(BIH), 1, img_fp); // 读取BMP信息头,40字节,直接用结构体读

    printf("sizeof(BFH) = %d\n", sizeof(BFH));
    printf("sizeof(BIH) = %d\n", sizeof(BIH));

    printf("\nBMP file head\n");
    printf("bfType = %x\n", BFH.bfType);
    printf("bfSize = %d\n", BFH.bfSize);
    printf("bfReserved1 = %d\n", BFH.bfReserved1);
    printf("bfReserved2 = %d\n", BFH.bfReserved2);
    printf("bfOffBits = %d\n", BFH.bfOffBits);

    printf("\nBMP info head\n");
    printf("biSize = %d\n", BIH.biSize);
    printf("biWidth = %d\n", BIH.biWidth);
    printf("biHeight = %d\n", BIH.biHeight);
    printf("biPlanes = %d\n", BIH.biPlanes);
    printf("biBitCount = %d\n", BIH.biBitCount);
    printf("biCompression = %d\n", BIH.biCompression);
    printf("biSizeImage = %d\n", BIH.biSizeImage);
    printf("biXPelsPerMeter = %d\n", BIH.biXPelsPerMeter);
    printf("biYPelsPerMeter = %d\n", BIH.biYPelsPerMeter);
    printf("biClrUsed = %d\n", BIH.biClrUsed);
    printf("biClrImportant = %d\n", BIH.biClrImportant);
    
    // if((BFH.bfType != 0x424D) || (BIH.biClrImportant != 0))
    if((BFH.bfType != 0x4D42))
    {
        printf("\nnot bmp file\n");
        goto end;
    }

    if (BIH.biBitCount != 24 || ((BIH.biClrImportant != 0) && (BIH.biClrImportant != 16777216)))
    {
        printf("\nnot 24 bit bmp file\n");
        goto end;
    }

    bmp_data = (unsigned char *)malloc(BIH.biSizeImage);
    if (bmp_data == NULL)
    {
        printf("malloc bmp_buf error\n");
        *data = NULL;
        *width = 0;
        *height = 0;
        goto end;
    }
    fseek(img_fp, BFH.bfOffBits, SEEK_SET);
    // ret = fread(bmp_data, BIH.biSizeImage, 1, img_fp);
    // if (ret != 1) {
    ret = fread(bmp_data, 1, BIH.biSizeImage, img_fp);
    if (ret != BIH.biSizeImage)
    {
        printf("read bmp file error\n");
        *data = NULL;
        *width = 0;
        *height = 0;
        goto end;
    }
    fclose(img_fp);

    rgb888_data = (unsigned char *)malloc(BIH.biWidth * BIH.biHeight * 3);
    if (rgb888_data == NULL)
    {
        printf("malloc rgb_buf error\n");
        *data = NULL;
        *width = 0;
        *height = 0;
        return 1;
    }

    h = BIH.biWidth;
    v = BIH.biHeight;
    line_size = ((h * 3 + 3) >> 2) << 2; // 行4字节对齐

    for (i = 0; i < v; i++)
    {
        for (j = 0; j < h; j++)
        {
            offset = (v - i - 1) * line_size + j * 3;

            rgb888_data[i * h * 3 + j * 3]     = bmp_data[offset + 2];
            rgb888_data[i * h * 3 + j * 3 + 1] = bmp_data[offset + 1];
            rgb888_data[i * h * 3 + j * 3 + 2] = bmp_data[offset];
        }
    }

    *width = BIH.biWidth;
    *height = BIH.biHeight;
    *data = rgb888_data;
    free(bmp_data);

end:
    fclose(img_fp);
    return 1;
}

void rgb888_dump_bmp(char *path, uint8_t *data, int32_t width, int32_t height)
{
    BMP_FILE_HEAD bfh;
    BMP_INFO_HEAD bih;
    FILE *fp;
    uint8_t line_buf[2048 * 3];
    int32_t line_size;
    int32_t i, j;

    if (width > 2048)
    {
        printf("width larger than 2048\n");
        return;
    }

    fp = fopen(path, "wb");
    if(fp == NULL)
    {
        printf("dump file %s error \n", path);
        return;
    }

    memset(&bfh, 0, sizeof(BMP_FILE_HEAD));
    memset(&bih, 0, sizeof(BMP_INFO_HEAD));
    memset(line_buf, 0, sizeof(line_buf));

    line_size = ((width * 3 + 3) >> 2) << 2;

    bfh.bfType = 0x4D42;
    bfh.bfSize = 54 + line_size * height;
    bfh.bfOffBits = 54;

    bih.biSize = 40;
    bih.biWidth = width;
    bih.biHeight = height;
    bih.biPlanes = 1;
    bih.biBitCount = 24;
    bih.biCompression = 0;
    bih.biSizeImage = line_size * height;
    bih.biXPelsPerMeter = 4724;
    bih.biYPelsPerMeter = 4724;
    bih.biClrUsed = 0;
    bih.biClrImportant = 0;

    fwrite(&bfh, sizeof(BMP_FILE_HEAD), 1, fp);
    fwrite(&bih, sizeof(BMP_INFO_HEAD), 1, fp);
    for(i = 0; i < height; i++)
    {
        for (j = 0; j < width; j++)
        {
            line_buf[j * 3] = data[(height - i - 1) * width * 3 + j * 3 + 2];
            line_buf[j * 3 + 1] = data[(height - i - 1) * width * 3 + j * 3 + 1];
            line_buf[j * 3 + 2] = data[(height - i - 1) * width * 3 + j * 3];
        }
        fwrite(line_buf, 1, line_size, fp);
    }

    fclose(fp);
}

void rgb888_dump_ppm(char *path, uint8_t *data, int32_t width, int32_t height)
{
    FILE *fp;
    char ppm_head[128];
    int32_t ppm_head_len;

    fp = fopen(path, "wb");
    if(fp == NULL)
    {
        printf("dump file %s error \n", path);
        return;
    }

    memset(ppm_head, 0, sizeof(ppm_head));
    sprintf(ppm_head, "P6 %d %d 255 ", width, height);
    ppm_head_len = strlen(ppm_head);
    fwrite(ppm_head, 1, ppm_head_len, fp);

    fwrite(data, 1, height * width * 3, fp);

    fclose(fp);
}


void print_block_double(double *in)
{
    int32_t i, j;
    printf("\n");
    for (i = 0; i < DCT_SIZE; i++)
    {
        for (j = 0; j < DCT_SIZE; j++)
        {
            printf("%8.3f ", in[i * DCT_SIZE + j]);
        }
        printf("\n");
    }
    printf("\n");
}

void print_block_u8(uint8_t *in)
{
    int32_t i, j;
    printf("\n");
    for (i = 0; i < DCT_SIZE; i++)
    {
        for (j = 0; j < DCT_SIZE; j++)
        {
            printf("%3d ", in[i * DCT_SIZE + j]);
        }
        printf("\n");
    }
    printf("\n");
}

void print_block_i8(int8_t *in)
{
    int32_t i, j;
    printf("\n");
    for (i = 0; i < DCT_SIZE; i++)
    {
        for (j = 0; j < DCT_SIZE; j++)
        {
            printf("%3d ", in[i * DCT_SIZE + j]);
        }
        printf("\n");
    }
    printf("\n");
}

void print_block_i32(int32_t *in)
{
    int32_t i, j;
    printf("\n");
    for (i = 0; i < DCT_SIZE; i++)
    {
        for (j = 0; j < DCT_SIZE; j++)
        {
            printf("%3d ", in[i * DCT_SIZE + j]);
        }
        printf("\n");
    }
    printf("\n");
}

void print_block_u8s3(uint8_t *in)
{
    int32_t i, j;
    printf("\n");
    for (i = 0; i < DCT_SIZE; i++)
    {
        for (j = 0; j < DCT_SIZE; j++)
        {
            printf("%3d ",  in[(i * DCT_SIZE + j) * 3 + 0]);
            printf("%3d ",  in[(i * DCT_SIZE + j) * 3 + 1]);
            printf("%3d  ", in[(i * DCT_SIZE + j) * 3 + 2]);
        }
        printf("\n");
    }
    printf("\n");
}


int main(int argc, const char **argv)
{
    int32_t w = 0, h = 0;
    int32_t x = 0, y = 0;
    int32_t last_y = 0, last_u = 0, last_v = 0;
    int32_t qt = 100;
    int32_t i = 0;

    uint8_t *rgb_data;
    uint8_t rgb_block[DCT_SIZE * DCT_SIZE * 3 * 4];  // 16x16的MCU
    // uint8_t y_block[DCT_SIZE * DCT_SIZE];
    // uint8_t u_block[DCT_SIZE * DCT_SIZE];
    // uint8_t v_block[DCT_SIZE * DCT_SIZE];
    int8_t y_block[DCT_SIZE * DCT_SIZE * 4];
    int8_t u_block[DCT_SIZE * DCT_SIZE];
    int8_t v_block[DCT_SIZE * DCT_SIZE];
    double dct_block[DCT_SIZE * DCT_SIZE];
    int32_t qt_block[DCT_SIZE * DCT_SIZE];
    int32_t zigzag_block[DCT_SIZE * DCT_SIZE];
    uint8_t luma_table[DCT_SIZE * DCT_SIZE];
    uint8_t chroma_table[DCT_SIZE * DCT_SIZE];
    uint8_t qt_table_tmp[DCT_SIZE * DCT_SIZE];

    jfif_header jfif;
    frame_header frame;

    coefficients huff_tree_luma_dc[HUFF_TREE_DC_LEN];
    coefficients huff_tree_chroma_dc[HUFF_TREE_DC_LEN];
    coefficients huff_tree_luma_ac[HUFF_TREE_AC_LEN];
    coefficients huff_tree_chroma_ac[HUFF_TREE_AC_LEN];
    memset(huff_tree_luma_dc,   0, sizeof(huff_tree_luma_dc));
    memset(huff_tree_chroma_dc, 0, sizeof(huff_tree_chroma_dc));
    memset(huff_tree_luma_ac,   0, sizeof(huff_tree_luma_ac));
    memset(huff_tree_chroma_ac, 0, sizeof(huff_tree_chroma_ac));

    // 生成huffman编码树
    make_huffman_tree((uint8_t *)default_ht_luma_dc_len,   (uint8_t *)default_ht_luma_dc,   HUFF_TREE_DC_LEN, huff_tree_luma_dc);
    make_huffman_tree((uint8_t *)default_ht_chroma_dc_len, (uint8_t *)default_ht_chroma_dc, HUFF_TREE_DC_LEN, huff_tree_chroma_dc);
    make_huffman_tree((uint8_t *)default_ht_luma_ac_len,   (uint8_t *)default_ht_luma_ac,   HUFF_TREE_AC_LEN, huff_tree_luma_ac);
    make_huffman_tree((uint8_t *)default_ht_chroma_ac_len, (uint8_t *)default_ht_chroma_ac, HUFF_TREE_AC_LEN, huff_tree_chroma_ac);

    // 生成量化参数表
    make_qt_table(qt, default_luma_table, luma_table);
    make_qt_table(qt, default_chroma_table, chroma_table);
    // print_block_u8(luma_table);
    // print_block_u8(chroma_table);

    read_bmp_to_rgb888(INPUT_BMP_FILE, &rgb_data, &w, &h);
    // rgb888_dump_ppm("out.ppm", rgb_data, w, h);
    fp_jpeg = fopen(OUTPUT_JPEG_FILE, "wb");

    // 写文件头
    jpeg_write_u16(sw16(0xFFD8));

    // 写JFIF
    jfif.len = sw16(sizeof(jfif_header));
    strcpy(jfif.identifier, "JFIF");
    jfif.version = sw16(0x0102);
    jfif.units = 0x01; // 0x00-unspecified 0x01-dots_per_inch 0x02-dots_per_cm
    jfif.Hdensity = sw16(96);
    jfif.Vdensity = sw16(96);
    jfif.HthumbnailA = sw16(0);
    jfif.VthumbnailA = sw16(0);
    jpeg_write_u16(sw16(0xFFE0));
    printf("sizeof(jfif_header) = %d\n", sizeof(jfif_header));
    jpeg_write_file((uint8_t *)&jfif, sizeof(jfif_header));

    // 写量化表
    // 亮度量化表
    jpeg_write_u16(sw16(0xFFDB));
    jpeg_write_u16(sw16(2+1+64));
    jpeg_write_u8(0x00); // AAAABBBB(bin), AAAA = 精度(0:8位,1:16位) BBBB = 量化表ID(最多4个)
    for (i = 0; i < DCT_SIZE * DCT_SIZE; i++)
    {
        qt_table_tmp[zigzag_table[i]] = luma_table[i];
    }
    jpeg_write_file(qt_table_tmp, sizeof(qt_table_tmp));
    // 色度量化表
    jpeg_write_u16(sw16(0xFFDB));
    jpeg_write_u16(sw16(2+1+64));
    jpeg_write_u8(0x01); // AAAABBBB(bin), AAAA = 精度(0:8位,1:16位) BBBB = 量化表ID(最多4个)
    for (i = 0; i < DCT_SIZE * DCT_SIZE; i++)
    {
        qt_table_tmp[zigzag_table[i]] = chroma_table[i];
    }
    jpeg_write_file(qt_table_tmp, sizeof(qt_table_tmp));

    // 写图像开始标记
    jpeg_write_u16(sw16(0xFFC0));
    jpeg_write_u16(sw16(2+sizeof(frame_header)));
    frame.precision = 8;
    frame.height = sw16(h);
    frame.width = sw16(w);
    frame.component_num = 3;
    // 这里采用最简单的YUV444格式,所以这三个分量的采样因子都是0x11
    // 假设采用YUV420格式,这三个分量的采样因子分别是0x22,0x11,0x11
    // 亮度分量
    frame.component[0].id = 1;
    frame.component[0].sampling_factors = 0x22;
    frame.component[0].qt_table_id = 0;
    // 色度分量
    frame.component[1].id = 2;
    frame.component[1].sampling_factors = 0x11;
    frame.component[1].qt_table_id = 1;
    // 色度分量
    frame.component[2].id = 3;
    frame.component[2].sampling_factors = 0x11;
    frame.component[2].qt_table_id = 1;
    jpeg_write_file((uint8_t *)&frame, sizeof(frame_header));

    // 写huffman表,AC、DC表的ID分别从0开始累加
    // 亮度DC表
    jpeg_write_u16(sw16(0xFFC4));
    jpeg_write_u16(sw16(2+1+16+sizeof(default_ht_luma_dc)));
    jpeg_write_u8(0x00); // AAAABBBB(bin), AAAA = 类型(0:DC,1:AC) BBBB = 表ID
    jpeg_write_file((uint8_t *)default_ht_luma_dc_len, sizeof(default_ht_luma_dc_len));
    jpeg_write_file((uint8_t *)default_ht_luma_dc, sizeof(default_ht_luma_dc));
    // 亮度AC表
    jpeg_write_u16(sw16(0xFFC4));
    jpeg_write_u16(sw16(2+1+16+sizeof(default_ht_luma_ac)));
    jpeg_write_u8(0x10); // AAAABBBB(bin), AAAA = 类型(0:DC,1:AC) BBBB = 表ID
    jpeg_write_file((uint8_t *)default_ht_luma_ac_len, sizeof(default_ht_luma_ac_len));
    jpeg_write_file((uint8_t *)default_ht_luma_ac, sizeof(default_ht_luma_ac));
    // 色度DC表
    jpeg_write_u16(sw16(0xFFC4));
    jpeg_write_u16(sw16(2+1+16+sizeof(default_ht_chroma_dc)));
    jpeg_write_u8(0x01); // AAAABBBB(bin), AAAA = 类型(0:DC,1:AC) BBBB = 表ID
    jpeg_write_file((uint8_t *)default_ht_chroma_dc_len, sizeof(default_ht_chroma_dc_len));
    jpeg_write_file((uint8_t *)default_ht_chroma_dc, sizeof(default_ht_chroma_dc));
    // 色度AC表
    jpeg_write_u16(sw16(0xFFC4));
    jpeg_write_u16(sw16(2+1+16+sizeof(default_ht_chroma_ac)));
    jpeg_write_u8(0x11); // AAAABBBB(bin), AAAA = 类型(0:DC,1:AC) BBBB = 表ID
    jpeg_write_file((uint8_t *)default_ht_chroma_ac_len, sizeof(default_ht_chroma_ac_len));
    jpeg_write_file((uint8_t *)default_ht_chroma_ac, sizeof(default_ht_chroma_ac));

    // 写扫描开始标记
    jpeg_write_u16(sw16(0xFFDA));
    jpeg_write_u16(sw16(2+1+3*2+3));
    jpeg_write_u8(0x03); // 颜色分量数目 1-灰度图 3-YUV 4-CMYK
    // 颜色分量信息,有几种颜色就要几次
    jpeg_write_u8(0x01); // 颜色分量ID
    jpeg_write_u8(0x00); // 高4位直流分量huffman表ID,低4位交流分量huffman表ID
    jpeg_write_u8(0x02); // 颜色分量ID
    jpeg_write_u8(0x11); // 高4位直流分量huffman表ID,低4位交流分量huffman表ID
    jpeg_write_u8(0x03); // 颜色分量ID
    jpeg_write_u8(0x11); // 高4位直流分量huffman表ID,低4位交流分量huffman表ID
    jpeg_write_u8(0x00);// 谱选择开始:1个字节,固定值0x00
    jpeg_write_u8(0x3F);// 谱选择结束:1个字节,固定值0x3F
    jpeg_write_u8(0x00);// 谱选择:1个字节,固定值0x00

    // 测试用,打印某一块的数据
    // if (0)
    // {
    //     read_block(rgb_data, rgb_block, w, h, 0, 0);
    //     print_block_u8s3(rgb_block);

    //     block_rgb_to_yuv444(rgb_block, y_block, u_block, v_block);
    //     print_block_i8(y_block);
    //     print_block_i8(u_block);
    //     print_block_i8(v_block);

    //     block_dct(y_block, dct_block);
    //     block_quantization(luma_table, dct_block, qt_block);
    //     block_zigzag(qt_block, zigzag_block);
    //     print_block_double(dct_block);
    //     print_block_i32(qt_block);
    //     print_block_i32(zigzag_block);

    //     block_dct(u_block, dct_block);
    //     block_quantization(chroma_table, dct_block, qt_block);
    //     block_zigzag(qt_block, zigzag_block);
    //     print_block_u8(u_block);
    //     print_block_double(dct_block);
    //     print_block_i32(zigzag_block);

    //     block_dct(v_block, dct_block);
    //     block_quantization(chroma_table, dct_block, qt_block);
    //     block_zigzag(qt_block, zigzag_block);
    //     print_block_u8(v_block);
    //     print_block_double(dct_block);
    //     print_block_i32(zigzag_block);
    // }
    int index = 0;
    // 写入真正的图像数据
    /* 一次处理16x16的图像块,使用YUV420作为数据源,
       那么会存在4个8x8Y分量矩阵,1个U分量矩阵,1个V分量矩阵
       他们的排列顺序是左上Y,右上Y,左下Y,右下Y,U分量,Y分量
      */
    for(y = 0; y < h; y += DCT_SIZE * 2)
    {
        for(x = 0; x < w; x += DCT_SIZE * 2)
        {
            // printf("\ncoordinate %d %d\n", x, y);

            read_block(rgb_data, rgb_block, w, h, x, y);
            // print_block_u8s3(rgb_block);

            block_rgb_to_yuv444(rgb_block, y_block, u_block, v_block);
            // printf("yuv block\n");
            // print_block_i8(y_block);
            // print_block_i8(u_block);
            // print_block_i8(v_block);

            // printf("y block\n");

            for(index = 0 ; index < 4; index++)
            {
                 block_dct(y_block + 64 * index, dct_block);
                // print_block_double(dct_block);
                block_quantization(luma_table, dct_block, qt_block);
                // print_block_i32(qt_block);
                block_zigzag(qt_block, zigzag_block);
                // print_block_i32(zigzag_block);
                block_encode(zigzag_block, &last_y, huff_tree_luma_dc, huff_tree_luma_ac);
            }
           

            // printf("u block\n");
            block_dct(u_block, dct_block);
            // print_block_double(dct_block);
            block_quantization(chroma_table, dct_block, qt_block);
            // print_block_i32(qt_block);
            block_zigzag(qt_block, zigzag_block);
            // print_block_i32(zigzag_block);
            block_encode(zigzag_block, &last_u, huff_tree_chroma_dc, huff_tree_chroma_ac);

            // printf("v block\n");
            block_dct(v_block, dct_block);
            // print_block_double(dct_block);
            block_quantization(chroma_table, dct_block, qt_block);
            // print_block_i32(qt_block);
            block_zigzag(qt_block, zigzag_block);
            // print_block_i32(zigzag_block);
            block_encode(zigzag_block, &last_v, huff_tree_chroma_dc, huff_tree_chroma_ac);
        }

        printf("process:y[%d]/h[%d] = %f per\n", y, h, (float)y/h);
    }

    // 清除缓存
    jpeg_write_bits(0, 0, 1);

    // 写入结束标记
    jpeg_write_u16(sw16(0xFFD9));

    free(rgb_data);
    fclose(fp_jpeg);

    return 0;
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/669832.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【嵌入式DIY实例】-OLED显示网络时钟

OLED显示网络时钟 文章目录 OLED显示网络时钟1、硬件准备与接线2、代码实现在上一个ESP8266 NodeMCU文章中,我们用DS3231 RTC芯片和SSD1306 OLED制作了一个简单的实时时钟,时间和日期显示在SSD1306屏幕上,并且可以通过两个按钮进行设置。 在本中,我们将使用ESP 8266 NodeMC…

SpringBoot 多模块 多环境 项目 单元测试

环境描述 假设项目中有以下三个yml文件&#xff1a; application.ymlapplication-dev.ymlapplication-prod.yml 假设项目各Module之间依赖关系如下&#xff1a; 其中&#xff0c;D依赖C&#xff0c;C依赖B&#xff0c;B依赖A&#xff0c;D对外提供最终的访问接口 现在要想采…

glpi 安装与使用

1、环境介绍 操作系统&#xff1a;龙蜥os 8.9 nginx&#xff1a;1.26.1 php&#xff1a;8.2.19 mysql&#xff1a;MarinaDB 10.3.9 glpi&#xff1a;10.0.6 fusioninventory&#xff1a;fusioninventory-10.0.61.1 2、安装epel源 dnf install epel-release -y dnf install htt…

洗地机什么牌子好?洗地机前十名排行榜

现代吸拖扫一体洗地机不仅高效&#xff0c;还具有智能化设计&#xff0c;使清洁变得轻松。它强大的吸尘功能能够轻松应对灰尘和碎屑&#xff0c;不论是硬质地面还是地毯&#xff0c;都能提供理想的清洁效果。配合拖地功能&#xff0c;通过内置水箱和智能拖布&#xff0c;能彻底…

纵向导航栏使用navbar-nav-scroll溢出截断问题

项目场景&#xff1a; 组件&#xff1a;Bootstrap-4.6.2、JQuery 3.7.1 测试浏览器&#xff1a;Firefox126.0.1、Microsoft Edge125.0.2535.67 IDE&#xff1a;eclipes2024-03.R 在编写CRM的工作台主页面时&#xff0c;由于该页面使用的是较旧的技术&#xff0c;所以打算使用…

virtualbox识别windows上usb设备

当你插入 USB 时&#xff0c;你的宿主操作系统可以轻松访问它并使用其中的文件。如果需要VirtualBox 的虚拟机也能访问物理机的 USB设备&#xff0c;需要安装安装扩展包管理器。 第一步&#xff1a; 要安装 VirtualBox 扩展包&#xff0c;只需访问 VirtualBox 官方下载页面&a…

OpenCV学习 基础图像操作(十七):泛洪与分水岭算法

原理 泛洪填充算法和分水岭算法是图像处理中的两种重要算法&#xff0c;主要用于区域分割&#xff0c;但它们的原理和应用场景有所不同&#xff0c;但是他们的基础思想都是基于区域迭代实现的区域之间的划分。 泛洪算法 泛洪填充算法&#xff08;Flood Fill&#xff09;是一…

seaborn和matplotlib显示两条曲线图例

总结&#xff0c;添加label和plt.legend&#xff0c;以下由chatgpt生成 在使用 Seaborn 的 kdeplot&#xff08;核密度估计图&#xff09;时&#xff0c;显示图例也是一个常见需求&#xff0c;尤其是当你想比较多个不同分布的数据时。下面我将提供一个示例&#xff0c;说明如何…

实战还原AI驱动的网络攻击:如何构建SecOps智能自动化防线

随着AI技术的迅猛发展&#xff0c;网络攻击手段日益多样化和高度自动化&#xff0c;给企业和个人带来了巨大网络安全挑战。在此背景下&#xff0c;为企业提供全面高效安全保障的Fortinet SecOps解决方案应运而生。在Fortinet 2024网安攻防“Demo季”第二期直播中&#xff0c;Fo…

【GD32】从零开始学GD32单片机高级篇——SDIO外设详解(GD32F470ZGT6)

目录 简介总线拓扑总线操作“无响应” 和 “无数据” 操作多块读写操作数据流读写操作 总线协议命令响应R1/R1b (普通命令响应)R2 (CID, CSD 寄存器)R3 (OCR 寄存器)R4 (Fast IO)R4b&#xff08;Fast IO&#xff09;R5 (中断请求)R5b&#xff08;中断请求&#xff09;R6 (发布的…

代码随想录算法训练营第36期DAY46

DAY46 完全背包 在闫氏DP法里学过&#xff1a;第i个物品选k个&#xff0c;纸质直至不能选&#xff0c;k从0开始取。就有递推式了。 代码随想录的视频也看了。 518零钱兑换ii 注意与 目标和 那题区分开。 完全背包问题&#xff0c;正向遍历背包容量&#xff0c;就能实现“多次…

【项目经理】什么是领导

引言&#xff1a;        项目管理是一个不断发展的领域&#xff0c;它要求项目经理不断学习、适应和创新。通过本系列文章&#xff0c;我们希望能够帮助每一位项目经理提升自己的专业能力&#xff0c;成为引领项目成功的舵手。 持续更新。。。。。。。。。。。。。。。…

Pycharm使用时的红色波浪线报错——形如‘break‘ outside loop

背景&#xff1a; 我在一个方法中&#xff0c;写了一个if判断&#xff0c;写了一个break&#xff0c;期望终止这个函数&#xff0c;编辑器出现报错 形如下图 视频版问题教程&#xff1a; Pycharm下出现波浪线报错&#xff0c;形如break outside loop 过程&#xff1a; 很奇…

echarts 图表不显示的问题

是这样的&#xff0c;点击详情&#xff0c;再点击统计&#xff0c;切换的时候就不会显示echarts图表&#xff0c;刚开始使用的是next Tick&#xff0c;没有使用定时器&#xff0c;后来加上了定时器就实现了如下所示&#xff1a; 代码是如下 const chartContainer ref(null); …

线程思维导图

列出线程所有知识的框架结构&#xff0c;帮助理解线程相关知识&#xff0c;有更好的知识体系 Java相关进阶知识 多线程相关知识&#xff0c;超详细&#xff0c;易懂

NextJs 渲染篇 - 什么是CSR、SSR、SSG、ISR 和服务端/客户端组件

NextJs 渲染篇 - 什么是CSR、SSR、SSG、ISR 和服务端/客户端组件 前言一. 什么是CSR、SSR、SSG、ISR1.1 CSR 客户端渲染1.2 SSR 服务端渲染1.3 SSG 静态站点生成① 没有数据请求的页面② 页面内容需要请求数据③ 页面路径需要获取数据 1.4 ISR 增量静态再生1.5 四种渲染方式的对…

案例实践 | 基于长安链的首钢供应链金融科技服务平台

案例名称-首钢供应链金融科技服务平台 ■ 建设单位 首惠产业金融服务集团有限公司 ■ 用户群体 核心企业、资金方&#xff08;多为银行&#xff09;等合作方 ■ 应用成效 三大业务场景&#xff0c;共计关联29个业务节点&#xff0c;覆盖京票项目全部关键业务 案例背景…

CSS选择器的常见用法

大家好&#xff0c;本期博客整理了前端语言 CSS 中选择器的入门级常见用法&#xff0c;希望能对大家有所帮助 CSS 选择器的主要功能就是选中⻚⾯指定的标签元素&#xff0c;选中了元素&#xff0c;才可以设置元素的属性。 那么&#xff0c;css选择器有哪几种呢&#xff1f; 以…

win10修改conda环境和缓存默认路径

win10修改conda环境和缓存默认路径 conda环境和缓存的默认路径&#xff08;envs directories 和 package cache&#xff09;不一定要默认存储在用户目录&#xff0c;我们可以将他们设置到盈余空间稍大的其他目录来缓解这种空间压力&#xff0c;只要保证不同用户之间的设置不同…

OrangePi AIpro 变身 Android 打包机

主板基本信息介绍 OrangePi AIpro&#xff0c;是香橙派联合华为精心打造&#xff0c;建设人工智能新生态而设计的一款开发板&#xff0c;这次为大家分享下我上手的这款 OrangePi AIpro 8GB&#xff08;算力达8TOPS&#xff09; 的一些小小的经验。 基本参数如下&#xff1a; …