之前我们讲解了如何使用RGB LCD显示点,线,面,以及矩形这些图形,还有字母等,以及可能在编写实验代码会遇到的问题,那么这一节我们就讲解如何使用IMX6UL在RGB LCD中显示图片
首先画点函数是画图函数的基础,所以首先要编写出画点函数,但是在上一节例程中已经讲解了如何编写画点函数,所以这里就不过多的赘述,将上一节的代码放在这里
inline void lcd_drawpoint(unsigned short x,unsigned short y,unsigned int color)
{
*(unsigned int*)((unsigned int)tftlcd_dev.framebuffer +
tftlcd_dev.pixsize * (tftlcd_dev.width * y+x))=color;
}
画图函数一共有两种实现方法,第一种是通过数组检索的方式,第二种便是通过指针缩索引的方式,首先我们先从数组索引的方法来开始讲解画图函数,首先找到一张图片,来确定他的分辨率,分辨率一般和LCD屏幕大小,要相同 ,如果你上传的图片分辨率大小不一样,那就可以使用相关的软件进入裁剪,例如PS
博主这里选取了一张长屏的图片,所以是宽600,高1024的形式竖着放在LCD显示屏上,那么将这张图片放到专门的图片取模软件中,目前常用的图片取模软件为Img2Lcd
界面如上,首先就是先导图片,然后之前讲解过,我们的屏幕其实是ARGB也就是支持透明通道,这里可以选择32位真彩色,也可以选择24位真彩色,因为图片的取模软件其实是不会将A这个透明通道加入进去,它的A那一位都是为0,所以取24位真彩色的大小也是可以的,在旁边设置好过后就点击上方的重新载入,然后点击保存
然后就会生成一个字模数组的.c文件,它的数组大小是[2457600],表示每一个元素都是unsigned char的形式,也就是占用一个字节(8位)的内存空间,整个数组相当于要占用2457600个unsigned char,也就是2457600字节(B),2457600字节约等于2400kb,也就是2.3MB,1kb=1024b
其次就是这个2457600是如何计算出来的,屏幕的宽度为1024,高度为600,每个像素点为32位也就是四个字节(因为是ARGB,如果是RGB就是3个字节),8位等于一个字节,1024*600*4=2457600
· 得到了一个图片的字模大小后,那么我们就需要将如此大的字模文件传递到我们的Vscode中,这里直接将文件的名字修改一下,变成picture.h然后通过FTP的文件传输方式下。将文件传输到我们的LCD_API函数中,这样才可以通过画点函数来对图的显示进行比较
字模被导入过过后,就开始编写我们的函数,这里先讲解一下数组索引的方式和指针索引方式之间的区别,首先就是数组索引
使用数组索引的时候,编译器通过基地址加上偏移量来找到数组的元素。比如在二维数组(y,x)的情况下,每次访问数组时,都需要进行乘法运算,然后加上偏移量来获得准确的内存位置。这些计算在每次循环中重复进行,对于高分辨率更大的数组的同时,效率较低
指针索引是直接通过指针的递增来访问下一个元素,就不需要每次计算基地址与偏移量。指针递增只需要简单的加法操作,相比数组索引效率会更高
那么先编写数组索引的函数
extern const unsigned char gImage_1[2457600]
void display_image()
{
int screen_width = 1024;
int screen_height = 600;
int y=0; int x=0;
for(y = 0; y < 600; y++)
{
for(x = 0; x < 1024; x++)
{
}
}
}
因为我们将字模的文件导入进入文件夹了,所以这里就告诉程序这里有一个叫做gImage_1(我们字模数组的名字)的数组在其他地方定义,这个数组有2457600个元素,每个元素都是一个无符号字符
定义一个void display_image(),函数没有返回值也不需要传入参数
下一步就是设置屏幕的分辨率,也就是宽1024像素,高600像素
然后创造两个循环的初始变量,用来定义到屏幕上的每个像素点
开始编写循环,循环的目的就是为了让程序按照顺序准确的找到每一个像素点,所以写了两个for,for是外层循环,意思就是首先先定义到第一排,然后进入到内层循环x,然后一直执行内存循环,也就是到1023,那么寻找像素点的方式就是先画完第一排的像素点,然后再开始绘画第二排
知道如何找到每一个像素点过后,下一步就是计算像素点在数组的索引,图像数组是按行顺序存储的,意味着第一行所有的像素值首先存储,然后再是第二行,代码如下
extern const unsigned char gImage_1[2457600]
void display_image()
{
int screen_width = 1024;
int screen_height = 600;
int y=0; int x=0;
for(y = 0; y < 600; y++)
{
for(x = 0; x < 1024; x++)
{
int index = (y* screen_width + x)*4
}
}
}
首先定义一个int大小的变量index,因为这里将像素点对应的图像数组计算出来,就将值存储在index中,char为一个字节,而int为四个字节,刚好对应ARGB
那么索引是如何计算出来的,这里我们先随便举一个例子,刚开始第一个像素点为(0,0),那么关于数组的索引就是0,那么就从0开始,往后取四个字节,也就是gImage_1[0],gImage_1[1],gImage_1[2],gImage_1[3],对应的就是下图四位的值
那么往后推,因为要先画完一排,所以就是先增加x的值,一个绘画的像素点就是(1,0),对应的数组索引就是gImage_1[4],gImage_1[5],gImage_1[6],gImage_1[7],这样类推
存储在index中的时候,我们按照BGRA的顺序访问数组中的四个连续字节,分别代表蓝色,绿色,红色,透明色,那么我们将这些颜色从数组中提取出来,然后再重新存储到一个变量中,然后方便我们对这些进行重新的组合或者排列,所以代码如下
extern const unsigned char gImage_1[2457600]
void display_image()
{
int screen_width = 1024;
int screen_height = 600;
int y=0; int x=0;
for(y = 0; y < 600; y++)
{
for(x = 0; x < 1024; x++)
{
int index = (y* screen_width + x)*4;
unsigned char a = gImage[index+4];
unsigned char r = gImage[index+3];
unsigned char g = gImage[index+2];
unsigned char b = gImage[index+1];
}
}
}
将颜色提取出来过后,就是重新整理这些颜色,因为我们屏幕是ARGB所以这里我们的排列方式也选择ARGB,代码如下
extern const unsigned char gImage_1[2457600]
void display_image()
{
int screen_width = 1024;
int screen_height = 600;
int y=0; int x=0;
for(y = 0; y < 600; y++)
{
for(x = 0; x < 1024; x++)
{
int index = (y* screen_width + x)*4;
unsigned char a = gImage[index+4];
unsigned char r = gImage[index+3];
unsigned char g = gImage[index+2];
unsigned char b = gImage[index+1];
uint32_t color = (a << 24)|(r << 16)|(g << 8)|(b << 0);
}
}
}
最后一步就是调用我们的画点函数,需要将我们每个画好的像素点显示在屏幕上
extern const unsigned char gImage_1[2457600]
void display_image()
{
int screen_width = 1024;
int screen_height = 600;
int y=0; int x=0;
for(y = 0; y < 600; y++)
{
for(x = 0; x < 1024; x++)
{
int index = (y* screen_width + x)*4;
unsigned char a = gImage[index+4];
unsigned char r = gImage[index+3];
unsigned char g = gImage[index+2];
unsigned char b = gImage[index+1];
uint32_t color = (a << 24)|(r << 16)|(g << 8)|(b << 0);
lcd_drawpoint(x , y ,color);
}
}
}
那么通过数组索引的函数就是如上,最后将函数编写到主函数当中就可以看到效果了
下面我们就换一种思路,开始使用指针索引的方式来编写代码
void display_image(unsigned char*image)
{
}
首先这个函数要传递参数,因为上面数组检索的方式确实不方便,为了能够指定传入的图片,那么就需要一个能够指向数组的指针
unsigned char* image,指向的是一个unsigned char类型的数据,image指向一个unsigned char 类型的数组的数组或者内存块,这个数组或内存块代表了一个图像的数据,也就是指向我们任意指定的图像数组的地址
void display_image(unsigned char*image)
{
int screen_width = 1024;
int screen_height = 600;
int y=0; int x=0;
for(y=0 ; y<screen_height; y++)
{
for(x=0 ; x<screen_width; x++)
{
}
}
}
首先基本的原理还是和数组索引一样,需要能够将每个像素点都涵盖到,现在需要如何对于数组里面的图像数据进行访问
void display_image(unsigned char*image)
{
int screen_width = 1024;
int screen_height = 600;
int y=0; int x=0;
unsigned char* ptr =image;
for(y=0 ; y<screen_height; y++)
{
for(x=0 ; x<screen_width; x++)
{
}
}
}
在函数内部,声明了一个新的unsigned char 类型的指针ptr,ptr是一个赋值操作,它将image参数的值(即图像数据的地址)赋给ptr。意味着ptr现在也指向图像数据的开始位置
这里我们讲解一下函数参数与内部声明的指针的关联,image是函数的参数,也是一个指针,指向传递给函数的图像数据的第一个字节。
ptr是在函数内部声明的局部指针变量,它的初始化为与image参数相同的地址
由于ptr被初始化为image的值,它们现在都指向同一块内存区域,即图像数据的开始位置。这意味着通过ptr进行的任何操作都会影响到image参数所指向的数据
void display_image(unsigned char*image)
{
int screen_width = 1024;
int screen_height = 600;
int y=0; int x=0;
unsigned char* ptr =image;
for(y=0 ; y<screen_height; y++)
{
for(x=0 ; x<screen_width; x++)
{
unsigned char a = *(ptr + 3);
unsigned char r = *(ptr + 2);
unsigned char g = *(ptr + 1);
unsigned char b = *(ptr);
ptr += 4;
}
}
}
首先还是需要提出出数据中的ARGB的值,定义四个unsigned char变量a,r,g,b,然后*()的意思是解引用,它的作用是获取指针所指向的内存地址中的值,具体来说,当我们使用*ptr的时候,我们是在请求计算机访问ptr变量所存储的地址,并取出存储在该地址中的数据
以第一个变量a来举例,这行代码从指针ptr当前指向的位置向后移动3个字节的位置,并解引用该位置以获取存储在哪里的unsigned char值。这个值代表像素的透明度,原本指针ptr指向的是像素数据的蓝色分量部分,向后移动3个字节就会跳过蓝色、绿色和红色分量,最终指向透明度
那么下面的四行颜色的分区也是同样的道理,就不过多赘述了
下一步即是,在获取了一个像素的所有颜色分量后,为了移动到下一个像素开始的位置,需要跳过当前像素的所有字节,因为像素占用四个字节,所有ptr需要增加四个字节,这样就会指向下一个像素的蓝色分量
所以ptr +=4的作用就是将指针ptr从当前像素的末尾(也就是alpha分量的位置),移动到下一个像素的开hi,就可以指向下一个像素的数据
不加*是因为,这里目的本身就是为了移动指针本身,而不是访问或解引用指针所指向的内存位置。这行代码只是简单地将指针ptr地值增加4,使其指向下一个像素开始地位置
void display_image(unsigned char* image)
{
int screen_width = 1024;
int screen_height = 600;
int y=0; int x=0;
unsigned char* ptr = image;
for(y=0;y<screen_height;y++)
{
for(x=0;x<screen_width;x++)
{
unsigned char a = *(ptr + 3);
unsigned char r = *(ptr + 2);
unsigned char g = *(ptr + 1);
unsigned char b = *(ptr);
ptr+=3;
uint32_t color = (a << 24) | (r<<16) | (g<<8) | (b<<0);
lcd_drawpoint(x,y,color);
}
}
}
下一步就是重新分类二点像素颜色,按照ARGB地要求重新排列,后面进行地操作和数组索引是一样地,原理也是相同,唯一不同的就是访问颜色数据的当时的不同,所以后面的内容就不过多赘述了,
#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_led.h"
#include "bsp_beep.h"
#include "bsp_key.h"
#include "bsp_int.h"
#include "bsp_uart.h"
#include "stdio.h"
#include "bsp_lcd.h"
#include "bsp_lcdapi.h"
#include "image.h"
int main(void)
{
unsigned char state = OFF;
int_init(); /* 初始化中断(一定要最先调用!) */
imx6u_clkinit(); /* 初始化系统时钟 */
delay_init(); /* 初始化延时 */
clk_enable(); /* 使能所有的时钟 */
led_init(); /* 初始化led */
beep_init(); /* 初始化beep */
uart_init(); /* 初始化串口,波特率115200 */
lcd_init(); /* 初始化LCD */
while(1)
{
display_image(gImage_1);
state = !state;
led_switch(LED0,state);
delayms(1000);
}
return 0;
}
最后在主函数使用使用这个编写好的函数就可以了,记得编写好函数后,要在对应的.函数中声明一下,最后实验效果和上图一样,那么这一讲的教程到这里就结束了
如果遇到问题,欢迎指出或者与博主进行讨论