当前位置: 首页 > news >正文

【正点原子I.MX6U-MINI应用篇】5、嵌入式Linux在LCD上显示BMP、JPG、PNG图片

一、BMP图像介绍与显示

我们常用的图片格式有很多,一般最常用的有三种:JPEG(或 JPG)、PNG、BMP和GIF。其中 JPEG(或JPG)、PNG以及 BMP 都是静态图片,而 GIF 则可以实现动态图片。

BMP(全称 Bitmap)是Window操作系统中的标准图像文件格式,文件后缀名为.bmp,使用非常
广。它采用位映射存储格式,除了图像深度可选以外,图像数据没有进行任何压缩,因此,BMP 图像文件所占用的空间很大,但是没有失真、并且解析BMP图像简单

BMP文件的图像深度可选1bit、4bit、8bit、16bit、24bit 以及32bit,典型的BMP图像文件由四部分组成:

  • ①、BMP文件头(BMP file header),它包含BMP文件的格式、大小、位图数据的偏移量等信息;
  • ②、位图信息头(bitmap information),它包含位图信息头大小、图像的尺寸、图像大小、位平面数、压缩方式以及颜色索引等信息;
  • ③、调色板(color palette),这部分是可选的,如果使用索引来表示图像,调色板就是索引与其对应颜色的映射表;
  • ④、位图数据(bitmap data),也就是图像数据。

BMP图像各数据段说明

一般常见的图像都是以 16 位(R、G、B 三种颜色分别使用 5bit、6bit、5bit 来表示)、24 位(R、G、B 三种颜色都使用 8bit 来表示)色图像为主,我们称这样的图像为真彩色图像,真彩色图像是不需要调色板的,即位图信息头后面紧跟的就是位图数据了

对某些BMP位图文件说并非如此,譬如16色位图、256色位图,它们需要使用到调色板,具体调色板如何使用,我们不关心,本节我们将会以16 位色(RGB565)BMP 图像为例。

图片链接

可以看到该图片的分辨率为800*480,位深度为16bit,每个像素点使用16位表示,也就是RGB565。为了向大家介绍BMP文件结构,接下来使用十六进制查看工具将image.bmp文件打开,文件头部分的内容如下所示:

1.1 bmp文件头

Windows下为bmp文件头定义了如下结构体:

typedef struct tagBITMAPFILEHEADER
{
	UINT16 bfType;
	DWORD bfSize;
	UINT16 bfReserved1;
	UINT16 bfReserved2;
	DWORD bfOffBits;
} BITMAPFILEHEADER;

结构体中每一个成员说明如下:

bmp 文件头成员说明
从上面的描述信息,再来对照文件数据:

  • 00~01H :0x42、0x4D 对应的 ASCII 字符分别为为B、M,表示这是 Windows 所支持的位图格式,该字段必须是BM才是Windows位图文件。
  • 02~05H :对应于文件大小,0x000BB848=768072字节=750KB,与image.bmp文件大小是相符的。
  • 06~09H :保留字段。
  • 0A~0D :0x00000046=70,即从文件头部开始到位图数据需要偏移70个字节。

bmp文件头的大小固定为 14 个字节。

1.2 位图信息头

同样,Windows 下为位图信息头定义了如下结构体:

typedef struct tagBITMAPINFOHEADER 
{
	DWORD biSize;
	LONG biWidth;
	LONG biHeight;
	WORD biPlanes;
	WORD biBitCount;
	DWORD biCompression;
	DWORD biSizeImage;
	LONG biXPelsPerMeter;
	LONG biYPelsPerMeter;
	DWORD biClrUsed;
	DWORD biClrImportant;
} BITMAPINFOHEADER;

结构体中每一个成员说明如下:

 位图信息头成员说明从上面的描述信息,再来对照文件数据:

  • 0E~11H :0x00000038=56,这说明这个位图信息头的大小为56个字节。
  • 12~15H :0x00000320=800,图像宽度为 800 个像素,与文件属性一致。
  • 16~19H :0x000001E0=480,图像高度为 480 个像素,与文件属性一致;
  • 1A~1BH :0x0001=1,这个值总为 1。
  • 1C~1DH :0x0010=16,表示每个像素占16个bit。
  • 1E~21H :0x00000003=0,bit-fileds 方式。
  • 22~25H :0x000BB802=768002,图像的大小,注意图像的大小并不是BMP文件的大小,而是图像数据的大小。
  • 26~29H :0x00001274=4724,水平分辨率为4724像素/米。
  • 2A~2DH :0x00001274=4724,垂直分辨率为4724像素/米。
  • 2E~31H :0x00000000=0,本位图未使用调色板。
  • 32~35H :0x00000000=0。

只有压缩方式选项被设置为bit-fileds(0x3)时,位图信息头的大小才会等于56字节,否则,为40字节。

1.3 调色板

调色板是单色、16 色、256 色位图图像文件所持有的,如果是 16 位、24 位以及 32 位位图文件,则 BMP文件组成部分中不包含调色板,关于调色板这里不过多介绍,有兴趣可以自己去了解。

1.4 位图数据

位图数据其实就是图像的数据,对于24位位图,使用3个字节数据来表示一个像素点的颜色,对于16位位图,使用2个字节数据来表示一个像素点的颜色,同理,32 位位图则使用4个字节来描述。
BMP位图分为正向的位图和倒向的位图,主要区别在于图像数据存储的排列方式,前面已经给大家解释的比较清楚了,如下如所示(左边对应的是正向位图,右边对应的则是倒向位图):

 所以正向位图先存储图像的第一行数据,从左到右依次存放,接着存放第二行,依次这样;而倒向位图,则先存储图像的最后一行(倒数第一行)数据,也是从左到右依次存放,接着倒数二行,依次这样。

1.5 源码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/mman.h>

/**** BMP文件头数据结构 ****/
typedef struct {
    unsigned char type[2];      //文件类型
    unsigned int size;          //文件大小
    unsigned short reserved1;   //保留字段1
    unsigned short reserved2;   //保留字段2
    unsigned int offset;        //到位图数据的偏移量
} __attribute__ ((packed)) bmp_file_header;

/**** 位图信息头数据结构 ****/
typedef struct {
    unsigned int size;          //位图信息头大小
    int width;                  //图像宽度
    int height;                 //图像高度
    unsigned short planes;      //位面数
    unsigned short bpp;         //像素深度 
    unsigned int compression;   //压缩方式
    unsigned int image_size;    //图像大小
    int x_pels_per_meter;       //像素/米
    int y_pels_per_meter;       //像素/米 
    unsigned int clr_used;
    unsigned int clr_omportant;
} __attribute__ ((packed)) bmp_info_header;

/**** 静态全局变量 ****/
static int width;                       //LCD X分辨率
static int height;                      //LCD Y分辨率
static unsigned short *screen_base = NULL; //映射后的显存基地址
static unsigned long line_length;       //LCD一行的长度(字节为单位)

/********************************************************************
 * 函数名称: show_bmp_image
 * 功能描述: 在LCD上显示指定的BMP图片
 * 输入参数: 文件路径
 * 返 回 值: 成功返回0, 失败返回-1
 ********************************************************************/
static int show_bmp_image(const char *path)
{
    bmp_file_header file_h;
    bmp_info_header info_h;
    unsigned short *line_buf = NULL;    //行缓冲区
    unsigned long line_bytes;   //BMP图像一行的字节的大小
    unsigned int min_h, min_bytes;
    int fd = -1;
    int j;

    /* 打开文件 */
    if (0 > (fd = open(path, O_RDONLY))) {
        perror("open error");
        return -1;
    }

    /* 读取BMP文件头 */
    if (sizeof(bmp_file_header) !=
        read(fd, &file_h, sizeof(bmp_file_header))) {
        perror("read error");
        close(fd);
        return -1;
    }

    if (0 != memcmp(file_h.type, "BM", 2)) {
        fprintf(stderr, "it's not a BMP file\n");
        close(fd);
        return -1;
    }

    /* 读取位图信息头 */
    if (sizeof(bmp_info_header) !=
        read(fd, &info_h, sizeof(bmp_info_header))) {
        perror("read error");
        close(fd);
        return -1;
    }

    /* 打印信息 */
    printf("文件大小: %d\n"
         "位图数据的偏移量: %d\n"
         "位图信息头大小: %d\n"
         "图像分辨率: %d*%d\n"
         "像素深度: %d\n", file_h.size, file_h.offset,
         info_h.size, info_h.width, info_h.height,
         info_h.bpp);

    /* 将文件读写位置移动到图像数据开始处 */
    if (-1 == lseek(fd, file_h.offset, SEEK_SET)) {
        perror("lseek error");
        close(fd);
        return -1;
    }

    /* 申请一个buf、暂存bmp图像的一行数据 */
    line_bytes = info_h.width * info_h.bpp / 8;
    line_buf = malloc(line_bytes);
    if (NULL == line_buf) {
        fprintf(stderr, "malloc error\n");
        close(fd);
        return -1;
    }

    if (line_length > line_bytes)
        min_bytes = line_bytes;
    else
        min_bytes = line_length;

    /**** 读取图像数据显示到LCD ****/
    /*******************************************
     * 为了软件处理上方便,这个示例代码便不去做兼容性设计了
     * 如果你想做兼容, 可能需要判断传入的BMP图像是565还是888
     * 如何判断呢?文档里边说的很清楚了
     * 我们默认传入的bmp图像是RGB565格式
     *******************************************/
    if (0 < info_h.height) {//倒向位图
        if (info_h.height > height) {
            min_h = height;
            lseek(fd, (info_h.height - height) * line_bytes, SEEK_CUR);
            screen_base += width * (height - 1);    //定位到屏幕左下角位置
        }
        else {
            min_h = info_h.height;
            screen_base += width * (info_h.height - 1); //定位到....不知怎么描述 懂的人自然懂!
        }

        for (j = min_h; j > 0; screen_base -= width, j--) {
            read(fd, line_buf, line_bytes); //读取出图像数据
            memcpy(screen_base, line_buf, min_bytes);//刷入LCD显存
        }
    }
    else {  //正向位图
        int temp = 0 - info_h.height;   //负数转成正数
        if (temp > height)
            min_h = height;
        else
            min_h = temp;

        for (j = 0; j < min_h; j++, screen_base += width) {
            read(fd, line_buf, line_bytes);
            memcpy(screen_base, line_buf, min_bytes);
        }
    }

    /* 关闭文件、函数返回 */
    close(fd);
    free(line_buf);
    return 0;
}

int main(int argc, char *argv[])
{
    struct fb_fix_screeninfo fb_fix;
    struct fb_var_screeninfo fb_var;
    unsigned int screen_size;
    int fd;

    /* 传参校验 */
    if (2 != argc) {
        fprintf(stderr, "usage: %s <bmp_file>\n", argv[0]);
        exit(-1);
    }

    /* 打开framebuffer设备 */
    if (0 > (fd = open("/dev/fb0", O_RDWR))) {
        perror("open error");
        exit(EXIT_FAILURE);
    }

    /* 获取参数信息 */
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);

    screen_size = fb_fix.line_length * fb_var.yres;
    line_length = fb_fix.line_length;
    width = fb_var.xres;
    height = fb_var.yres;

    /* 将显示缓冲区映射到进程地址空间 */
    screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == (void *)screen_base) {
        perror("mmap error");
        close(fd);
        exit(EXIT_FAILURE);
    }

    /* 显示BMP图片 */
    memset(screen_base, 0xFF, screen_size);
    show_bmp_image(argv[1]);

    /* 退出 */
    munmap(screen_base, screen_size);  //取消映射
    close(fd);  //关闭文件
    exit(EXIT_SUCCESS);    //退出进程
}

1.6 编译程序

我们要想给ARM板编译出程序,需要使用交叉编译工具链,交叉编译的工具链我们已经安装过了,详细请看【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.x.pdf 的第4.3小节。我是用的是arm-linux-gnueabihf交叉编译工具链。使用arm-linux-gnueabihf-gcc -v可以查看交叉编译工具链的版本号。

然后就可以使用下面命令编译出可以在ARM板子上运行的可执行文件了。

arm-linux-gnueabihf-gcc -o bmp_show bmp_show.c
  • 1、arm表示这是编译arm架构代码的编译器。
  • 2、linux表示运行在linux环境下。
  • 3、gnueabihf表示嵌入式二进制接口。
  • 4、gcc表示是gcc工具。

这样编译出来的 led程序才可以在ARM板子上运行。执行file bmp_show命令就可以看出hello是32位LSB的ELF格式文件,目标机架构为ARM,说明这个交叉编译正常,可执行文件可以在ARM板上执行。

1.7 上传程序到开发板执行

开发板启动后通过nfs挂载Ubuntu目录的方式,将相应的文件拷贝到开发板上。简单来说,就是通过NFS在开发板上通过网络直接访问ubuntu虚拟机上的文件,并且就相当于自己本地的文件一样。

开发板想访问/home/zhiguoxin/myproject/alientek_app_development_source这个目录中的文件,就要把/home/zhiguoxin/myproject/alientek_app_development_source挂载到开发板的mnt目录,这样就可以通过nfs来访问/home/zhiguoxin/myproject/alientek_app_development_source了。

因为我的代码都放在/home/zhiguoxin/myproject/alientek_app_development_source这个目录下,所以我们将这个目录作为NFS共享文件夹。设置方法参考移植SQLite3、OpenCV到RV1126开发板上开发人脸识别项目第一章。

Ubuntu IP为192.168.10.100,然后一般都是挂载在开发板的mnt目录下,这个目录是专门用来给我们作为临时挂载的目录。

文件系统目录简介

然后使用MobaXterm软件通过SSH访问开发板。

ubuntu ip:192.168.10.100
windows ip:192.168.10.200
开发板ip:192.168.10.50

在开发板上执行以下命令:

mount -t nfs -o nolock,vers=3 192.168.10.100:/home/zhiguoxin/myproject/alientek_app_development_source /mnt

就将开饭的mnt目录挂载在ubuntu的/home/zhiguoxin/myproject/alientek_app_development_source目录下了。这样我们就可以在Ubuntu下修改文件,然后可以直接在开发板上执行可执行文件了。当然我这里的/home/zhiguoxin/myproject/windows之间是一个共享目录,我也可以直接在windows上面修改文件,然后ubuntu和开发板直接进行文件同步了。

执行应用程序

./bmp_show xiaoya.bmp

 在开发板上的显示效果

1.8 如何得到16位色RGB565格式 BMP图像?

在Windows下我们转换得到的BMP位图通常是24位色的RGB888格式图像,那如何得到RGB565格式 BMP位图呢?当然这个方法很多,这里笔者向大家介绍一种方法就是通过 Photoshop软件来得到 RGB565格式的 BMP 位图。

首先,找一张图片,图片格式无所谓,只要Photoshop软件能打开即可;确定图片之后,我们启动Photoshop软件,并且使用Photoshop软件打开这张图片,打开之后点击菜单栏中的文件—>存储为,接着出现如下界面:


二、jpeg图像介绍与显示

我们常用的图片格式有很多,一般最常用的有三种:JPEG(或 JPG)、PNG、BMP。BMP 图像虽然没有失真、并且解析简单,但是由于图像数据没有进行任何压缩,因此,BMP 图像文件所占用的存储空间很大,不适合存储在磁盘设备中。

而 JPEG(或 JPG)、PNG 则是经过压缩处理的图像格式,将图像数据进行压缩编码,大大降低了图像文件的大小,适合存储在磁盘设备中,所以很常用。

2.1 JPEG 简介

JPEG(Joint Photographic Experts Group)是由国际标准组织为静态图像所建立的第一个国际数字图像压缩标准,也是至今一直在使用的、应用最广的图像压缩标准

JPEG由于可以提供有损压缩,因此压缩比可以达到其他传统压缩算法无法比拟的程度;JPEG虽然是有损压缩,但这个损失的部分是人的视觉不容易察觉到的部分,它充分利用了人眼对计算机色彩中的高频信息部分不敏感的特点,来大大节省了需要处理的数据信息。JPEG 压缩文件通常以.jpg 或.jpeg 作为文件后缀名。

2.2 libjpeg简介

JPEG 压缩标准使用了一套压缩算法对原始图像数据进行了压缩得到.jpg 或.jpeg 图像文件,如果想要在LCD 上显示.jpg 或.jpeg 图像文件,则需要对其进行解压缩、以得到图像的原始数据,譬如RGB 数据。

既然压缩过程使用了算法,那对.jpg 或.jpeg 图像文件进行解压同样也需要算法来处理,这里并不会教大家如何编写解压算法,这些算法的实现也是很复杂的,我不会,自然教不了大家!但是,我们可以使用别人写好的库、调用别人写好的库函数来解压.jpg 或.jpeg 图像文件—libjpeg 库。

libjpeg是一个完全用C语言编写的函数库,包含了JPEG解码(解压缩)、JPEG 编码(创建压缩)和
其他的 JPEG 功能的实现。可以使用 libjpeg 库对.jpg 或.jpeg 压缩文件进行解压或者生成.jpg 或.jpeg 压缩文件。

2.3 libjpeg移植

libjpeg是一个开源 C 语言库,我们获取到它的源代码。

下载源码包

首先,打开http://www.ijg.org/files/链接地址,如下所示

libjpeg下载链接页面
目前最新的一个版本是v9e,对应的年份为2022年,这里我们选择一个适中的版本,笔者以v9b 为例,对应的文件名为jpegsrc.v9b.tar.gz,点击该文件即可下载。我把源码下在完成后放在共享文件夹下:

其实开发板出厂系统中已经移植了libjpeg库,但是版本太旧了!所以这里我们选择重新移植

编译源码

将 jpegsrc.v9b.tar.gz 压缩包文件放到共享文件夹下,如下所示:
 执行命令解压

tar -vxzf jpegsrc.v9b.tar.gz

解压成功之后会生成 jpeg-9b 文件夹,也就是 libjpeg 源码文件夹。
 编译之前,在家目录下的tools文件夹中创建一个名为jpeg的文件夹,该目录作为libjpeg库的安装目录:

mkdir jpeg

回到共享文件夹目录下,进入到libjpeg源码目录jpeg-9b中,该目录下包含的内容如下所示:

接下来对 libjpeg 源码进行交叉编译,跟编译 tslib 时步骤一样,包含三个步骤:

  • 配置工程;
  • 编译工程;
  • 安装;

一套流程下来非常地快!没有任何难点。在此之前,先对交叉编译工具的环境进行初始化,使用 source执行交叉编译工具安装目录下的environment-setup-cortexa7hf-neon-poky-linux-gnueabi脚本文件(如果已经
初始化过了,那就不用再进行初始化了):

source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi

执行下面这条命令对 libjpeg 工程进行配置:

./configure --host=arm-poky-linux-gnueabi --prefix=/home/zhiguoxin/linux/tool/jpeg

大家可以执行./configure --help查看它的配置选项以及含义,--host选项用于指定交叉编译得到的库文件是运行在哪个平台,通常将--host设置为交叉编译器名称的前缀,譬如arm-poky-linux-gnueabi-gcc前缀就是arm-poky-linux-gnueabi--prefix选项则用于指定库文件的安装路径,将/home/zhiguoxin/linux/tool目录作为libjpeg的安装目录。

配置工程
接着执行 make 命令编译工程:

make

编译工程
编译完成之后,执行命令安装 libjpeg:

make install

安装工程

命令执行完成之后,我们的 libjpeg 也就安装成功了!

安装目录下的文件夹介绍

进入到 libjpeg 安装目录:

移植到开发板

开发板出厂系统已经移植了libjpeg库,前面给大家提到过,只是移植的版本太低了,所以这里不打算使用出厂系统移植的libjpeg库,而使用交叉编译好的libjpeg库。

进入到libjpeg安装目录下,将bin目录下的所有测试工具拷贝到开发板Linux系统/usr/bin目录;将 lib目录下的所有库文件拷贝到开发板 Linux 系统/usr/lib目录。

拷贝lib目录下的库文件时,需要注意符号链接的问题,不能破坏原有的符号链接;可以将lib目录下
的所有文件打包成压缩包的形式,譬如进入到lib目录,执行命令:

tar -czf lib.tar.gz ./*

将 lib 目录下的所有文件打包再将lib.tar.gz压缩文件拷贝到开发板Linux的用户家目录下,在解压之前,将开发板出厂系统中已经移植的libjpeg库删除,执行命令:

rm -rf /usr/lib/libjpeg.*

删除出厂系统原有的 libjpeg 库

Tips:注意!当出厂系统原有的libjpeg库被删除后,将会导致开发板下次启动后,出厂系统的 Qt GUI应用程序会出现一些问题,原本显示图片的位置变成了空白,显示不出来了!原因在于 Qt 程序处理图片(对jpeg 图片解码)时,它的底层使用到了 ibjpeg 库,而现在我们将出厂系统原有的 libjpeg 给删除了,自然就会导致Qt GUI应用程序中图片显示不出来(无法对 jpeg 图片进行解码)!

接着我们将lib.tar.gz压缩文件解压到开发板Linux系统/usr/lib目录下。我们可以使用scp命令将/home/zhiguoxin/linux/jpeg/lib目录下的lib.tar.gz发送到发开板中。

scp lib.tar.gz root@192.168.10.50:/home/root


注意:有的时候发下面这种错误,拷贝失败,可以关闭命令窗口,在重新打开一次就好了。


然后lib.tar.gz就拷贝到开发板了,然后使用下面命令将其解压至/usr/lib 目录下

tar -xzf lib.tar.gz -C /usr/lib
tar -xmzf lib.tar.gz -C /usr/lib

解压成功之后,接着执行libjpeg提供的测试工具,看看我们移植成功没:

djpeg --help
${CC} -o show_jpeg_image show_jpeg_image.c -I /home/zhiguoxin/linux/tool/jpeg/include -L /home/zhiguoxin/linux/tool/jpeg/lib -ljpeg

相关文章:

  • 四非到保研厦大,我们还有多少路要走----技术人的保研之路
  • 美团Leaf分布式ID源码启动部署
  • 归一化小程序
  • 走过岁月我才发现——云IDE真方便(Python3.8环境测试)
  • SpringBoot核心技术 之 基础入门
  • Linux下编译工具:gcc/g++ の最全使用教程
  • 【计算机视觉】imutils的基本使用
  • Vue--nextTick--作用/用法/原理
  • 自动化测试项目学习笔记(五):Pytest结合allure生成测试报告以及重构项目
  • 计算机网络习题答案
  • js中的‘==‘和‘===‘
  • 一起来部署项目-采购一台云服务器
  • 【老生谈算法】matlab实现抽样定理算法源码——抽样定理
  • [从0开始机器学习]4.线性回归 正规方程
  • RayVentory以改进IT的分析,RayVentory原始数据之间轻松切换
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • IndexedDB
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • js数组之filter
  • log4j2输出到kafka
  • OSS Web直传 (文件图片)
  • pdf文件如何在线转换为jpg图片
  • rabbitmq延迟消息示例
  • STAR法则
  • TypeScript迭代器
  • Vue组件定义
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 第2章 网络文档
  • 关于extract.autodesk.io的一些说明
  • 设计模式(12)迭代器模式(讲解+应用)
  • 译米田引理
  • 最简单的无缝轮播
  • ionic异常记录
  • Java性能优化之JVM GC(垃圾回收机制)
  • (2)nginx 安装、启停
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (附源码)spring boot基于Java的电影院售票与管理系统毕业设计 011449
  • (附源码)springboot 智能停车场系统 毕业设计065415
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (入门自用)--C++--抽象类--多态原理--虚表--1020
  • (四)TensorRT | 基于 GPU 端的 Python 推理
  • .NET Core 通过 Ef Core 操作 Mysql
  • .NET Entity FrameWork 总结 ,在项目中用处个人感觉不大。适合初级用用,不涉及到与数据库通信。
  • .Net MVC + EF搭建学生管理系统
  • .Net Redis的秒杀Dome和异步执行
  • .NET/C# 的字符串暂存池
  • .Net+SQL Server企业应用性能优化笔记4——精确查找瓶颈
  • .net实现头像缩放截取功能 -----转载自accp教程网
  • .sdf和.msp文件读取
  • /etc/X11/xorg.conf 文件被误改后进不了图形化界面
  • [ vulhub漏洞复现篇 ] Apache Flink目录遍历(CVE-2020-17519)
  • [ 蓝桥杯Web真题 ]-Markdown 文档解析
  • [<MySQL优化总结>]
  • [120_移动开发Android]008_android开发之Pull操作xml文件
  • [4.9福建四校联考]