基于STM32设计的数码相册

news2024/9/22 13:38:14

一、项目介绍

项目是基于STM32设计的数码相册,能够通过LCD显示屏解码显示主流的图片,支持bmp、jpg、gif等格式。用户可以通过按键或者触摸屏来切换图片,同时还可以旋转显示,并能够自适应居中显示,小尺寸图片居中显示,大尺寸图片自动缩小显示(超出屏幕范围)。图片从SD卡中获取。

image-20230618135436038

二、设计思路

2.1 硬件设计

本项目所需的主要硬件:

  • STM32F103ZET6
  • LCD屏幕
  • SD卡模块
  • 按键和触摸屏

2.2 软件设计

(1)解码图片

在STM32芯片中,解码图片需要将读取到的数据存入图形缓冲区中,以便进行图画显示。常用的解码算法有JPEG解码和BMP解码。

(2)图片显示

为了更好的实现图片旋转和缩放功能,在显示图片时需对其进行矩阵运算。通过左右翻转和上下翻转,可实现图片的旋转功能。通过计算图片与显示屏幕之间的比例关系并进行缩放,实现自适应居中和图片的缩放功能。

(3)SD卡

SD卡模块可通过SPI接口与STM32芯片进行通信,读取SD卡中的图片数据,实现对图片的加载和显示。

(4)按键和触摸屏

在使用过程中,用户可以通过按键和触摸屏对图片进行切换、旋转和缩放等操作。通过设置中断处理函数,响应用户的操作并及时更新显示屏幕上的图片。

2.3 图片播放流程图

image-20230618135212377

2.4 显示效果

image-20230618135436038

三、代码设计

3.1 主函数

#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "usart.h"
#include <string.h>
#include <stdio.h>
#include "sd.h" //SD卡
#include "ff.h" //文件系统
#include "bmp.h" //文件系统
#include "iic.h"
#include "at24c02.h"
#include "xpt2046.h"
#include "lcd.h"


FATFS fs;  // 用户定义的文件系统结构体
int main()
{
	DIR  dir_dp;
	FILINFO file_info;
	u32 sd_size;    //存放SD卡返回的容量
	BeepInit();		  //蜂鸣器初始化
	LedInit();      //LED灯初始化 
	UsartInit(USART1,72,115200);
	KeyInit();     //按键初始化
	IICInit();
	LcdInit();
	TOUCH_Init(); 
	//TOUCH_ADJUST(); //触摸屏校准
	
	printf("串口工作正常!\r\n");
	if(SDCardDeviceInit()) 
	{
	   printf("SD卡初始化失败!\r\n");
	}
	
	sd_size=GetSDCardSectorCount();	//检测SD卡大小,返回值右移11位得到以M为单位的容量
	printf("SD卡Sizeof:%d\r\n",sd_size>>11);
	
  f_mount(&fs,"0:",1);  // 注册文件系统工作区,驱动器号 0,初始化后其他函数可使用里面的参数
	LcdClear(0xFFFF);
	
	//f_mkdir("0:/目录创建测试!"); //测试OK
	//f_unlink("0:/123"); //删除目录,注意只能删除空目录
	//f_unlink("0:/1.bmp");//删除文件
	//printf("%d\r\n",Show_BMP("1.bmp"));
	
	if(f_opendir(&dir_dp,"0:/bmp")!=FR_OK)printf("目录打开失败!\r\n");
	
	//循环读取目录
	while(f_readdir(&dir_dp,&file_info)==FR_OK)
	{
			if(file_info.fname[0]==0)break;    //判断目录跳出条件,表示目录已经读取完毕
			if(strstr(file_info.fname,".bmp")) //过滤目录
			{
				 	printf("文件名称: %s,文件大小: %ld 字节\r\n",file_info.fname,file_info.fsize);
			}else 	printf("文件名称: %s,文件大小: %ld 字节\r\n",file_info.fname,file_info.fsize);
	}
	if(f_closedir(&dir_dp)!=FR_OK)printf("目录关闭失败!\r\n");
	while(1)
	{	
		 LED1=!LED1;
		 DelayMs(100);
	}
}

3.2 BMP图片解码

#include "bmp.h"
unsigned short RGB888ToRGB565(unsigned int n888Color)  
{  
    unsigned short n565Color = 0;  
  
    // 获取RGB单色,并截取高位  
    unsigned char cRed   = (n888Color & RGB888_RED)   >> 19;  
    unsigned char cGreen = (n888Color & RGB888_GREEN) >> 10;  
    unsigned char cBlue  = (n888Color & RGB888_BLUE)  >> 3;  
  
    // 连接  
    n565Color = (cRed << 11) + (cGreen << 5) + (cBlue << 0);  
    return n565Color;  
}  

unsigned int RGB565ToRGB888(unsigned short n565Color)  
{  
    unsigned int n888Color = 0;  
  
    // 获取RGB单色,并填充低位  
    unsigned char cRed   = (n565Color & RGB565_RED)    >> 8;  
    unsigned char cGreen = (n565Color & RGB565_GREEN)  >> 3;  
    unsigned char cBlue  = (n565Color & RGB565_BLUE)   << 3;  
  
    // 连接  
    n888Color = (cRed << 16) + (cGreen << 8) + (cBlue << 0);  
    return n888Color;  
}  



/*
函数功能:实现截图功能
参    数:
				char filename:文件名称
返 回 值:0表示成功,1表示失败
*/
u8 C_BMP(const char *filename,u32 Width,u32 Height)
{
	FIL  file; // 用户定义的文件系统结构体
	u8   res;  // 保存文件操作的返回值
	BITMAPFILEHEADER BmpHead; //保存图片文件头的信息
  BITMAPINFOHEADER BmpInfo; //图片参数信息
	char *p;
	u32 cnt,c_32;
	int x,y;
	u16 c_16; //存放16位的颜色
	
	/*1. 创建一张BMP图片*/
	res = f_open(&file,filename, FA_OPEN_ALWAYS | FA_WRITE);
	if(res!=0)return 1;
	
	/*2. 创建BMP的图片头参数*/
	memset(&BmpHead,0,sizeof(BITMAPFILEHEADER)); //将指定空间赋值为指定的值
	p=(char*)&BmpHead.bfType; //填充BMP图片的类型
	*p='B';
	*(p+1)='M';

	//BmpHead.bfType=0x4d42;//'B''M'   //0x4d42
	BmpHead.bfSize=Width*Height*3+54;  //图片的总大小
	BmpHead.bfOffBits=54;              //图片数据的偏移量
  res =f_write(&file,&BmpHead,sizeof(BITMAPFILEHEADER),&cnt);
	if(res!=0)return 1;
	
	/*3. 创建BMP图片的参数*/
	memset(&BmpInfo,0,sizeof(BITMAPINFOHEADER));
	BmpInfo.biSize=sizeof(BITMAPINFOHEADER); //当前结构体大小
	BmpInfo.biWidth=Width;
	BmpInfo.biHeight=Height;
	BmpInfo.biPlanes=1;
	BmpInfo.biBitCount=24;
	res =f_write(&file,&BmpInfo,sizeof(BITMAPINFOHEADER),&cnt);
	if(res!=0)return 1;
		
	/*4. 读取LCD屏的颜色数据,用于创建BMP图片*/
	for(y=Height-1;y>=0;y--)
	{
		  for(x=0;x<Width;x++)
		  {
			    c_16=LcdReadPoint(x,y); //读取LCD屏上一个点的颜色
				  c_32=RGB565ToRGB888(c_16); //颜色的转换
				  res =f_write(&file,&c_32,3,&cnt);
				  if(res!=0)return 1;
			}
	}
	
	/*5. 关闭文件*/
	f_close(&file);
}


/*
函数功能:BMP图片显示功能
参    数:
				char filename:文件名称
返 回 值:0表示成功,1表示失败
*/
u8 Show_BMP(const char *filename)
{
	FIL  file; // 用户定义的文件系统结构体
	u8   res;  // 保存文件操作的返回值
	BITMAPFILEHEADER BmpHead; //保存图片文件头的信息
  BITMAPINFOHEADER BmpInfo; //图片参数信息
	char *p;
	u32 cnt,c_24;
	int x,y;
	u16 c_16; //存放16位的颜色
	
	/*1. 打开一张BMP图片*/
	res = f_open(&file,filename,FA_READ);
	if(res!=0)return 1;
	
	/*2. 读取BMP的图片头参数*/
  res =f_read(&file,&BmpHead,sizeof(BITMAPFILEHEADER),&cnt);
	if(res!=0)return 1;
	
	/*3. 读取BMP图片的参数*/
	res =f_read(&file,&BmpInfo,sizeof(BITMAPINFOHEADER),&cnt);
	if(res!=0)return 1;
	
	/*4.显示BMP图片*/
	f_lseek(&file,BmpHead.bfOffBits); //移动到RGB数据的存放位置
	
	//后期的优化:读取一行的数据,再显示一行。
   for(y=0;y<BmpInfo.biHeight;y++)
	 {
			for(x=0;x<BmpInfo.biWidth;x++)
		 {
			    res =f_read(&file,&c_24,3,&cnt);
					if(res!=0)return 1;
			    c_16=RGB888ToRGB565(c_24); //转换颜色
			    LcdDrawPoint(x,y,c_16);
		 }
	 }
	/*5. 关闭文件*/
	f_close(&file);
}


3.3 jpeg图片解码

#include "piclib.h"
#include "nt35310_lcd.h"
_pic_info picinfo;	 	//图片信息
_pic_phy pic_phy;		  //图片显示物理接口	

/*
函数功能: 划横线函数,需要自己实现
*/
void Picture_DrawLine(u16 x0,u16 y0,u16 len,u16 color)
{
	NT35310_Fill(x0,y0,x0+len-1,y0,color);	
}

/*
函数功能: 矩形填充颜色
函数参数:
		x,y:起始坐标
		width,height:宽度和高度。
		color:颜色数组
*/
void Picture_FillColor(u16 x,u16 y,u16 width,u16 height,u16 *color)
{  
	NT35310_DrawRectangle(x,y,x+width-1,y+height-1,*color);	
}

/*
函数功能: 画图初始化,在画图之前,必须先调用此函数
函数参数: 指定画点/读点
*/
void Picture_DisplayInit(void)
{
	pic_phy.draw_point=NT35310_DrawPoint;    //画点函数实现
	pic_phy.fill=NT35310_Fill;					     //填充函数实现,仅GIF需要
	pic_phy.draw_hline=Picture_DrawLine;  	 //画线函数实现,仅GIF需要
	pic_phy.fillcolor=Picture_FillColor;  	 //颜色填充函数实现,仅TJPGD需要 
	picinfo.lcdwidth=Lcd_Width;							 //得到LCD的宽度像素
	picinfo.lcdheight=Lcd_Height;						 //得到LCD的高度像素

	picinfo.ImgWidth=0;	//初始化宽度为0
	picinfo.ImgHeight=0;//初始化高度为0
	picinfo.Div_Fac=0;	//初始化缩放系数为0
	picinfo.S_Height=0;	//初始化设定的高度为0
	picinfo.S_Width=0;	//初始化设定的宽度为0
	picinfo.S_XOFF=0;	  //初始化x轴的偏移量为0
	picinfo.S_YOFF=0;	  //初始化y轴的偏移量为0
	picinfo.staticx=0;	//初始化当前显示到的x坐标为0
	picinfo.staticy=0;	//初始化当前显示到的y坐标为0
}


/*
函数功能: 初始化智能画点
说明: 内部调用
*/
void Picture_PointInit(void)
{
	float temp,temp1;	   
	temp=(float)picinfo.S_Width/picinfo.ImgWidth;
	temp1=(float)picinfo.S_Height/picinfo.ImgHeight;						 
	if(temp<temp1)temp1=temp;//取较小的那个	 
	if(temp1>1)temp1=1;	  
	//使图片处于所给区域的中间
	picinfo.S_XOFF+=(picinfo.S_Width-temp1*picinfo.ImgWidth)/2;
	picinfo.S_YOFF+=(picinfo.S_Height-temp1*picinfo.ImgHeight)/2;
	temp1*=8192;//扩大8192倍	 
	picinfo.Div_Fac=temp1;
	picinfo.staticx=0xffff;
	picinfo.staticy=0xffff;//放到一个不可能的值上面			 										    
}  

/*
函数功能: 判断这个像素是否可以显示
函数参数:
			(x,y) :像素原始坐标
			chg   :功能变量.
返回值:0,不需要显示.1,需要显示
*/
u8 Picture_is_Pixel(u16 x,u16 y,u8 chg)
{				  
	if(x!=picinfo.staticx||y!=picinfo.staticy)
	{
		if(chg==1)
		{
			picinfo.staticx=x;
			picinfo.staticy=y;
		} 
		return 1;
	}else return 0;
}

extern u8 jpg_decode(const u8 *filename);

/*
函数功能: 绘制图片
函数参数:
				FileName:要显示的图片文件  BMP/JPG/JPEG/GIF
				x,y,width,height:坐标及显示区域尺寸
				fast:使能jpeg/jpg小图片(图片尺寸小于等于液晶分辨率)快速解码,0,不使能;1,使能.
				函数说明: 图片在开始和结束的坐标点范围内显示
*/
u8 Picture_DisplayJPG(const u8 *filename,u16 x,u16 y,u16 width,u16 height,u8 fast)
{	
	u8	res;//返回值
	
	//显示的图片高度、宽度
	picinfo.S_Height=height;
	picinfo.S_Width=width;

	//显示的开始坐标点
	picinfo.S_YOFF=y;
	picinfo.S_XOFF=x;
	
	//解码JPG/JPEG	  	 
  res=jpg_decode(filename);				//解码JPG/JPEG	  	  										   
	return res;
}

3.4 gif图片解码

#include "piclib.h"
#include <stm32f10x.h>
#include "gif.h"	 
#include "ff.h"	
#include "delay.h"	    
#include <string.h>

const u16 _aMaskTbl[16] =
{
	0x0000, 0x0001, 0x0003, 0x0007,
	0x000f, 0x001f, 0x003f, 0x007f,
	0x00ff, 0x01ff, 0x03ff, 0x07ff,
	0x0fff, 0x1fff, 0x3fff, 0x7fff,
};	  
const u8 _aInterlaceOffset[]={8,8,4,2};
const u8 _aInterlaceYPos  []={0,4,2,1};
 
u8 gifdecoding=0;//标记GIF正在解码.

//检测GIF头
//返回值:0,是GIF89a/87a;非零,非GIF89a/87a
u8 gif_check_head(FIL *file)
{
	u8 gifversion[6];
	u32 readed;
	u8 res;
	res=f_read(file,gifversion,6,(UINT*)&readed);
	if(res)return 1;	   
	if((gifversion[0]!='G')||(gifversion[1]!='I')||(gifversion[2]!='F')||
	(gifversion[3]!='8')||((gifversion[4]!='7')&&(gifversion[4]!='9'))||
	(gifversion[5]!='a'))return 2;
	else return 0;	
}

//将RGB888转为RGB565
//ctb:RGB888颜色数组首地址.
//返回值:RGB565颜色.
u16 gif_getrgb565(u8 *ctb) 
{
	u16 r,g,b;
	r=(ctb[0]>>3)&0X1F;
	g=(ctb[1]>>2)&0X3F;
	b=(ctb[2]>>3)&0X1F;
	return b+(g<<5)+(r<<11);
}

//读取颜色表
//file:文件;
//gif:gif信息;
//num:tbl大小.
//返回值:0,OK;其他,失败;
u8 gif_readcolortbl(FIL *file,gif89a * gif,u16 num)
{
	u8 rgb[3];
	u16 t;
	u8 res;
	u32 readed;
	for(t=0;t<num;t++)
	{
		res=f_read(file,rgb,3,(UINT*)&readed);
		if(res)return 1;//读错误
		gif->colortbl[t]=gif_getrgb565(rgb);
	}
	return 0;
} 
//得到逻辑屏幕描述,图像尺寸等
//file:文件;
//gif:gif信息;
//返回值:0,OK;其他,失败;
u8 gif_getinfo(FIL *file,gif89a * gif)
{
	u32 readed;	 
	u8 res;   
	res=f_read(file,(u8*)&gif->gifLSD,7,(UINT*)&readed);
	if(res)return 1;
	if(gif->gifLSD.flag&0x80)//存在全局颜色表
	{
		gif->numcolors=2<<(gif->gifLSD.flag&0x07);//得到颜色表大小
		if(gif_readcolortbl(file,gif,gif->numcolors))return 1;//读错误	
	}	   
	return 0;
}
//保存全局颜色表	 
//gif:gif信息;
void gif_savegctbl(gif89a* gif)
{
	u16 i=0;
	for(i=0;i<256;i++)gif->bkpcolortbl[i]=gif->colortbl[i];//保存全局颜色.
}
//恢复全局颜色表	 
//gif:gif信息;
void gif_recovergctbl(gif89a* gif)
{
	u16 i=0;
	for(i=0;i<256;i++)gif->colortbl[i]=gif->bkpcolortbl[i];//恢复全局颜色.
}

//初始化LZW相关参数	   
//gif:gif信息;
//codesize:lzw码长度
void gif_initlzw(gif89a* gif,u8 codesize) 
{
 	memset((u8 *)gif->lzw, 0, sizeof(LZW_INFO));
	gif->lzw->SetCodeSize  = codesize;
	gif->lzw->CodeSize     = codesize + 1;
	gif->lzw->ClearCode    = (1 << codesize);
	gif->lzw->EndCode      = (1 << codesize) + 1;
	gif->lzw->MaxCode      = (1 << codesize) + 2;
	gif->lzw->MaxCodeSize  = (1 << codesize) << 1;
	gif->lzw->ReturnClear  = 1;
	gif->lzw->LastByte     = 2;
	gif->lzw->sp           = gif->lzw->aDecompBuffer;
}

//读取一个数据块
//gfile:gif文件;
//buf:数据缓存区
//maxnum:最大读写数据限制
u16 gif_getdatablock(FIL *gfile,u8 *buf,u16 maxnum) 
{
	u8 cnt;
	u32 readed;
	u32 fpos;
	f_read(gfile,&cnt,1,(UINT*)&readed);//得到LZW长度			 
	if(cnt) 
	{
		if (buf)//需要读取 
		{
			if(cnt>maxnum)
			{
				fpos=f_tell(gfile);
				f_lseek(gfile,fpos+cnt);//跳过
				return cnt;//直接不读
			}
			f_read(gfile,buf,cnt,(UINT*)&readed);//得到LZW长度	
		}else 	//直接跳过
		{
			fpos=f_tell(gfile);
			f_lseek(gfile,fpos+cnt);//跳过
		}
	}
	return cnt;
}

//ReadExtension		 
//Purpose:
//Reads an extension block. One extension block can consist of several data blocks.
//If an unknown extension block occures, the routine failes.
//返回值:0,成功;
// 		 其他,失败
u8 gif_readextension(FIL *gfile,gif89a* gif, int *pTransIndex,u8 *pDisposal)
{
	u8 temp;
	u32 readed;	 
	u8 buf[4];  
	f_read(gfile,&temp,1,(UINT*)&readed);//得到长度		 
	switch(temp)
	{
		case GIF_PLAINTEXT:
		case GIF_APPLICATION:
		case GIF_COMMENT:
			while(gif_getdatablock(gfile,0,256)>0);			//获取数据块
			return 0;
		case GIF_GRAPHICCTL://图形控制扩展块
			if(gif_getdatablock(gfile,buf,4)!=4)return 1;	//图形控制扩展块的长度必须为4 
 		 	gif->delay=(buf[2]<<8)|buf[1];					//得到延时 
			*pDisposal=(buf[0]>>2)&0x7; 	    			//得到处理方法
			if((buf[0]&0x1)!=0)*pTransIndex=buf[3];			//透明色表 
			f_read(gfile,&temp,1,(UINT*)&readed);	 		//得到LZW长度	
 			if(temp!=0)return 1;							//读取数据块结束符错误.
			return 0;
	}
	return 1;//错误的数据
}

//从LZW缓存中得到下一个LZW码,每个码包含12位
//返回值:<0,错误.
//		 其他,正常.
int gif_getnextcode(FIL *gfile,gif89a* gif) 
{
	int i,j,End;
	long Result;
	if(gif->lzw->ReturnClear)
	{
		//The first code should be a clearcode.
		gif->lzw->ReturnClear=0;
		return gif->lzw->ClearCode;
	}
	End=gif->lzw->CurBit+gif->lzw->CodeSize;
	if(End>=gif->lzw->LastBit)
	{
		int Count;
		if(gif->lzw->GetDone)return-1;//Error 
		gif->lzw->aBuffer[0]=gif->lzw->aBuffer[gif->lzw->LastByte-2];
		gif->lzw->aBuffer[1]=gif->lzw->aBuffer[gif->lzw->LastByte-1];
		if((Count=gif_getdatablock(gfile,&gif->lzw->aBuffer[2],300))==0)gif->lzw->GetDone=1;
		if(Count<0)return -1;//Error 
		gif->lzw->LastByte=2+Count;
		gif->lzw->CurBit=(gif->lzw->CurBit-gif->lzw->LastBit)+16;
		gif->lzw->LastBit=(2+Count)*8;
		End=gif->lzw->CurBit+gif->lzw->CodeSize;
	}
	j=End>>3;
	i=gif->lzw->CurBit>>3;
	if(i==j)Result=(long)gif->lzw->aBuffer[i];
	else if(i+1==j)Result=(long)gif->lzw->aBuffer[i]|((long)gif->lzw->aBuffer[i+1]<<8);
	else Result=(long)gif->lzw->aBuffer[i]|((long)gif->lzw->aBuffer[i+1]<<8)|((long)gif->lzw->aBuffer[i+2]<<16);
	Result=(Result>>(gif->lzw->CurBit&0x7))&_aMaskTbl[gif->lzw->CodeSize];
	gif->lzw->CurBit+=gif->lzw->CodeSize;
	return(int)Result;
}


//得到LZW的下一个码
//返回值:<0,错误(-1,不成功;-2,读到结束符了)
//		 >=0,OK.(LZW的第一个码)
int gif_getnextbyte(FIL *gfile,gif89a* gif) 
{
	int i,Code,Incode;
	while((Code=gif_getnextcode(gfile,gif))>=0)
	{
		if(Code==gif->lzw->ClearCode)
		{
			//Corrupt GIFs can make this happen  
			if(gif->lzw->ClearCode>=(1<<MAX_NUM_LWZ_BITS))return -1;//Error 
			//Clear the tables 
			memset((u8*)gif->lzw->aCode,0,sizeof(gif->lzw->aCode));
			for(i=0;i<gif->lzw->ClearCode;++i)gif->lzw->aPrefix[i]=i;
			//Calculate the'special codes' independence of the initial code size
			//and initialize the stack pointer 
			gif->lzw->CodeSize=gif->lzw->SetCodeSize+1;
			gif->lzw->MaxCodeSize=gif->lzw->ClearCode<<1;
			gif->lzw->MaxCode=gif->lzw->ClearCode+2;
			gif->lzw->sp=gif->lzw->aDecompBuffer;
			//Read the first code from the stack after clear ingand initializing*/
			do
			{
				gif->lzw->FirstCode=gif_getnextcode(gfile,gif);
			}while(gif->lzw->FirstCode==gif->lzw->ClearCode);
			gif->lzw->OldCode=gif->lzw->FirstCode;
			return gif->lzw->FirstCode;
		}
		if(Code==gif->lzw->EndCode)return -2;//End code
		Incode=Code;
		if(Code>=gif->lzw->MaxCode)
		{
			*(gif->lzw->sp)++=gif->lzw->FirstCode;
			Code=gif->lzw->OldCode;
		}
		while(Code>=gif->lzw->ClearCode)
		{
			*(gif->lzw->sp)++=gif->lzw->aPrefix[Code];
			if(Code==gif->lzw->aCode[Code])return Code;
			if((gif->lzw->sp-gif->lzw->aDecompBuffer)>=sizeof(gif->lzw->aDecompBuffer))return Code;
			Code=gif->lzw->aCode[Code];
		}
		*(gif->lzw->sp)++=gif->lzw->FirstCode=gif->lzw->aPrefix[Code];
		if((Code=gif->lzw->MaxCode)<(1<<MAX_NUM_LWZ_BITS))
		{
			gif->lzw->aCode[Code]=gif->lzw->OldCode;
			gif->lzw->aPrefix[Code]=gif->lzw->FirstCode;
			++gif->lzw->MaxCode;
			if((gif->lzw->MaxCode>=gif->lzw->MaxCodeSize)&&(gif->lzw->MaxCodeSize<(1<<MAX_NUM_LWZ_BITS)))
			{
				gif->lzw->MaxCodeSize<<=1;
				++gif->lzw->CodeSize;
			}
		}
		gif->lzw->OldCode=Incode;
		if(gif->lzw->sp>gif->lzw->aDecompBuffer)return *--(gif->lzw->sp);
	}
	return Code;
}
//DispGIFImage		 
//Purpose:
//   This routine draws a GIF image from the current pointer which should point to a
//   valid GIF data block. The size of the desired image is given in the image descriptor.
//Return value:
//  0 if succeed
//  1 if not succeed
//Parameters:
//  pDescriptor  - Points to a IMAGE_DESCRIPTOR structure, which contains infos about size, colors and interlacing.
//  x0, y0       - Obvious.
//  Transparency - Color index which should be treated as transparent.
//  Disposal     - Contains the disposal method of the previous image. If Disposal == 2, the transparent pixels
//                 of the image are rendered with the background color.
u8 gif_dispimage(FIL *gfile,gif89a* gif,u16 x0,u16 y0,int Transparency, u8 Disposal) 
{
	u32 readed;	   
  u8 lzwlen;
	int Index,OldIndex,XPos,YPos,YCnt,Pass,Interlace,XEnd;
	int Width,Height,Cnt,ColorIndex;
	u16 bkcolor;
	u16 *pTrans;

	Width=gif->gifISD.width;
	Height=gif->gifISD.height;
	XEnd=Width+x0-1;
	bkcolor=gif->colortbl[gif->gifLSD.bkcindex];
	pTrans=(u16*)gif->colortbl;
	f_read(gfile,&lzwlen,1,(UINT*)&readed);//得到LZW长度	 
	gif_initlzw(gif,lzwlen);//Initialize the LZW stack with the LZW code size 
	Interlace=gif->gifISD.flag&0x40;//是否交织编码
	for(YCnt=0,YPos=y0,Pass=0;YCnt<Height;YCnt++)
	{
		Cnt=0;
		OldIndex=-1;
		for(XPos=x0;XPos<=XEnd;XPos++)
		{
			if(gif->lzw->sp>gif->lzw->aDecompBuffer)Index=*--(gif->lzw->sp);
		    else Index=gif_getnextbyte(gfile,gif);	   
			if(Index==-2)return 0;//Endcode     
			if((Index<0)||(Index>=gif->numcolors))
			{
				//IfIndex out of legal range stop decompressing
				return 1;//Error
			}
			//If current index equals old index increment counter
			if((Index==OldIndex)&&(XPos<=XEnd))Cnt++;
	 		else
			{
				if(Cnt)
				{
					if(OldIndex!=Transparency)
					{									    
						pic_phy.draw_hline(XPos-Cnt-1,YPos,Cnt+1,*(pTrans+OldIndex));
					}else if(Disposal==2)
					{
						pic_phy.draw_hline(XPos-Cnt-1,YPos,Cnt+1,bkcolor);
					}
					Cnt=0;
				}else
				{
					if(OldIndex>=0)
					{
						if(OldIndex!=Transparency)pic_phy.draw_point(XPos-1,YPos,*(pTrans+OldIndex));
					 	else if(Disposal==2)pic_phy.draw_point(XPos-1,YPos,bkcolor); 
					}
				}
			}
			OldIndex=Index;
		}
		if((OldIndex!=Transparency)||(Disposal==2))
		{
			if(OldIndex!=Transparency)ColorIndex=*(pTrans+OldIndex);
		  else ColorIndex=bkcolor;
	 		if(Cnt)
			{
				pic_phy.draw_hline(XPos-Cnt-1,YPos,Cnt+1,ColorIndex);
			}else pic_phy.draw_point(XEnd,YPos,ColorIndex);	 
		}
		//Adjust YPos if image is interlaced 
		if(Interlace)//交织编码
		{
			YPos+=_aInterlaceOffset[Pass];
			if((YPos-y0)>=Height)
			{
				++Pass;
				YPos=_aInterlaceYPos[Pass]+y0;
			}
		}else YPos++;	    
	}
	return 0;
}


/*
函数功能: 恢复成背景色
函数参数:
					x,y:坐标
					gif:gif信息.
					pimge:图像描述块信息
*/
void gif_clear2bkcolor(u16 x,u16 y,gif89a* gif,ImageScreenDescriptor pimge)
{
	u16 x0,y0,x1,y1;
	u16 color=gif->colortbl[gif->gifLSD.bkcindex];
	if(pimge.width==0||pimge.height==0)return;//直接不用清除了,原来没有图像!!
	if(gif->gifISD.yoff>pimge.yoff)
	{
   		x0=x+pimge.xoff;
		y0=y+pimge.yoff;
		x1=x+pimge.xoff+pimge.width-1;;
		y1=y+gif->gifISD.yoff-1;
		if(x0<x1&&y0<y1&&x1<320&&y1<320)pic_phy.fill(x0,y0,x1,y1,color); //设定xy,的范围不能太大.
	}
	if(gif->gifISD.xoff>pimge.xoff)
	{
   		x0=x+pimge.xoff;
		y0=y+pimge.yoff;
		x1=x+gif->gifISD.xoff-1;;
		y1=y+pimge.yoff+pimge.height-1;
		if(x0<x1&&y0<y1&&x1<320&&y1<320)pic_phy.fill(x0,y0,x1,y1,color);
	}
	if((gif->gifISD.yoff+gif->gifISD.height)<(pimge.yoff+pimge.height))
	{
   		x0=x+pimge.xoff;
		y0=y+gif->gifISD.yoff+gif->gifISD.height-1;
		x1=x+pimge.xoff+pimge.width-1;;
		y1=y+pimge.yoff+pimge.height-1;
		if(x0<x1&&y0<y1&&x1<320&&y1<320)pic_phy.fill(x0,y0,x1,y1,color);
	}
 	if((gif->gifISD.xoff+gif->gifISD.width)<(pimge.xoff+pimge.width))
	{
   		x0=x+gif->gifISD.xoff+gif->gifISD.width-1;
		y0=y+pimge.yoff;
		x1=x+pimge.xoff+pimge.width-1;;
		y1=y+pimge.yoff+pimge.height-1;
		if(x0<x1&&y0<y1&&x1<320&&y1<320)pic_phy.fill(x0,y0,x1,y1,color);
	}   
}


/*
函数功能: 画GIF图像的一帧
函数参数:
				gfile:gif文件
				x0,y0:开始显示的坐标
*/
u8 gif_drawimage(FIL *gfile,gif89a* gif,u16 x0,u16 y0)
{		  
	u32 readed;
	u8 res,temp;    
	u16 numcolors;
	ImageScreenDescriptor previmg;

	u8 Disposal;
	int TransIndex;
	u8 Introducer;
	TransIndex=-1;				  
	do
	{
		res=f_read(gfile,&Introducer,1,(UINT*)&readed);//读取一个字节
		if(res)return 1;   
		switch(Introducer)
		{		 
			case GIF_INTRO_IMAGE://图像描述
				previmg.xoff=gif->gifISD.xoff;
 				previmg.yoff=gif->gifISD.yoff;
				previmg.width=gif->gifISD.width;
				previmg.height=gif->gifISD.height;

				res=f_read(gfile,(u8*)&gif->gifISD,9,(UINT*)&readed);//读取一个字节
				if(res)return 1;			 
				if(gif->gifISD.flag&0x80)//存在局部颜色表
				{							  
					gif_savegctbl(gif);//保存全局颜色表
					numcolors=2<<(gif->gifISD.flag&0X07);//得到局部颜色表大小
					if(gif_readcolortbl(gfile,gif,numcolors))return 1;//读错误	
				}
				if(Disposal==2)gif_clear2bkcolor(x0,y0,gif,previmg); 
				gif_dispimage(gfile,gif,x0+gif->gifISD.xoff,y0+gif->gifISD.yoff,TransIndex,Disposal);
 				while(1)
				{
					f_read(gfile,&temp,1,(UINT*)&readed);//读取一个字节
					if(temp==0)break;
					readed=f_tell(gfile);//还存在块.	
					if(f_lseek(gfile,readed+temp))break;//继续向后偏移	 
			  }
				if(temp!=0)return 1;//Error 
				return 0;
			case GIF_INTRO_TERMINATOR://得到结束符了
				return 2;								//代表图像解码完成了.
			case GIF_INTRO_EXTENSION:
				res=gif_readextension(gfile,gif,&TransIndex,&Disposal);//读取图像扩展块消息
				if(res)return 1;
	 			break;
			default:
				return 1;
		}
	}while(Introducer!=GIF_INTRO_TERMINATOR);//读到结束符了
	return 0;
}


/*
函数功能: 退出当前解码
*/
void gif_quit(void)
{
	gifdecoding=0;
}


/*
函数功能: 解码一个gif文件
函数参数:
			filename:带路径的gif文件名字
			x,y,width,height:显示坐标及区域大小.
*/
u8 gif_decode(const u8 *filename,u16 x,u16 y,u16 width,u16 height)
{
	u8 res=0;
	u16 dtime=0;//解码延时
	gif89a *mygif89a;
	FIL *gfile;
	gfile=(FIL*)SRAM_Malloc(sizeof(FIL));
	if(gfile==NULL)res=PIC_MEM_ERR;//申请内存失败 
	mygif89a=(gif89a*)SRAM_Malloc(sizeof(gif89a));
	if(mygif89a==NULL)res=PIC_MEM_ERR;//申请内存失败    
	mygif89a->lzw=(LZW_INFO*)SRAM_Malloc(sizeof(LZW_INFO));
	if(mygif89a->lzw==NULL)res=PIC_MEM_ERR;//申请内存失败 
	if(res==0)//OK
	{
		res=f_open(gfile,(TCHAR *)filename,FA_READ);
		if(res==0)//打开文件ok
		{
			if(gif_check_head(gfile))res=PIC_FORMAT_ERR;
			if(gif_getinfo(gfile,mygif89a))res=PIC_FORMAT_ERR;
			if(mygif89a->gifLSD.width>width||mygif89a->gifLSD.height>height)res=PIC_SIZE_ERR;//尺寸太大.
			else
			{
				x=(width-mygif89a->gifLSD.width)/2+x;
				y=(height-mygif89a->gifLSD.height)/2+y;
			}
			gifdecoding=1;
			while(gifdecoding&&res==0)//解码循环
			{	 
				res=gif_drawimage(gfile,mygif89a,x,y);//显示一张图片
				if(mygif89a->gifISD.flag&0x80)gif_recovergctbl(mygif89a);//恢复全局颜色表
				if(mygif89a->delay)dtime=mygif89a->delay;
				else dtime=10;//默认延时
				while(dtime--&&gifdecoding)delay_ms(10);//延迟
				if(res==2)
				{
					res=0;
					break;
				}
			}
		}
		f_close(gfile);
	}
	SRAM_Free(gfile);
	SRAM_Free(mygif89a->lzw);
	SRAM_Free(mygif89a); 
	return res;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

复习之kickstart无人职守安装脚本

一、kickstart简介 kickstart是红帽发行版中的一种安装方式&#xff0c;它通过以配置文件的方式来记录linux系统安装的各项参数和想要安装的软件。只要配置正确&#xff0c;整个安装过程中无需人工交互参与&#xff0c;达到无人值守安装的目的。 二、kickstar文件的生成 进入/…

销售易和管易云接口打通对接实战

销售易和管易云接口打通对接实战 来源系统:销售易 销售易CRM支持企业从营销、销售到服务的全流程自动化业务场景&#xff0c;创新性地利用AI、大数据、物联网等新型互联网技术打造双中台型CRM&#xff1b;既能帮助B2B企业连接外部经销商、服务商、产品以及最终用户&#xff0c;…

提升稳定性与动态响应,深入探究PID串级多闭环控制的应用价值

引言&#xff1a; PID&#xff08;比例-积分-微分&#xff09;控制作为自动控制系统中常用的控制算法&#xff0c;可以通过对系统的反馈进行调整&#xff0c;实现目标状态的稳定控制。而PID串级多闭环控制是在基本PID控制的基础上&#xff0c;引入多个PID控制器&#xff0c;形成…

某coin数据加密接口分析

新建项目&#xff0c;然后添加frida代码提示 frida 代码提示安装--vscode / node npm i types/frida-gum 任务 : sign 和 data&#xff0c;止于mobilekey是设备号&#xff0c;测试可以随机 sign 加密在 native 层 动态调试配置: 把ida 的 dbsgv 文件下的 android_server 复…

SFL218、SFL214、SFL216、SFL218B双喷嘴挡板两级电液伺服阀

SFZ141直接驱动式伺服阀 SFL317电反馈三级伺服阀 SFL316电反馈三级伺服阀 SFL218A双喷嘴挡板两级电液伺服阀 SFL218双喷嘴挡板两级电液伺服阀 SFL214双喷嘴挡板两级电液伺服阀 SFL216双喷嘴挡板两级电液伺服阀 SFL218B双喷嘴挡板两级电液伺服阀 SFL212B双喷嘴挡板两级电…

HTSA101伺服流量阀放大器

电液伺服阀放大器HTSA101特点&#xff1a; 可用拨码方式选择比例、积分(PI)控制前面板有电源、阀电流和继电器指示灯可开关选择阀电流的输出电流范围可选输出电流或者电压信号来匹配伺服阀或者比例阀采用标准 DIN rail 规格带有颤振信号、颤振信号的幅值和频率可调标准的DIN 导…

Day05-作业(SpringBootWeb请求响应)

作业1&#xff1a;联网创建SpringBoot工程&#xff0c;完成如下需求 测试接口数据&#xff0c;提取码&#xff1a;5555&#xff08;将上述json文件,下载并导入postman&#xff09;https://pan.baidu.com/s/1rwUfKTCgncB_xxarzOUpfA 需求&#xff1a; springboot的版本选择 2…

ALLEGRO之View

本文主要介绍ALLEGRO中的View菜单。 &#xff08;1&#xff09;Zoom By Points&#xff1a;按照选型区域放大&#xff1b; &#xff08;2&#xff09;Zoom Fit&#xff1a;适合窗口放大&#xff1b; &#xff08;3&#xff09;Zoom In&#xff1a;放大&#xff1b; &#xf…

Java---Shiro框架

第一章 入门概述 1.1 什么是shiro Apache Shiro 是一个功能强大且易于使用的 Java 安全(权限)框架。Shiro 可以完成:认证、授权、加密、会话管理、与 Web 集成、缓存 等。借助 Shiro 您可以快速轻地保护任何应用程序——从最小的移动应用程序到最大的 Web 和企业应用程序。 …

释放三年版本:Aspose.Total For NET [21.7/22.7/23.7]

请各位对号入座&#xff0c;选择自己需求范围&#xff0c;你懂的&#xff0c;你懂的&#xff0c;你懂的 Aspose.Total for .NET is the most complete package of all .NET File Format Automation APIs offered by Aspose. It empowers developers to create, edit, render, …

日撸java_day54-55

文章目录 第 54 、55 天: 基于 M-distance 的推荐代码运行截图 第 54 、55 天: 基于 M-distance 的推荐 1.M-distance, 就是根据平均分来计算两个用户 (或项目) 之间的距离. 2.邻居不用 k 控制. 距离小于 radius (即 ϵ ) 的都是邻居. 使用 M-distance 时, 这种方式效果更好. …

tinkerCAD案例:28. Build a Mobile Amplifier 构建移动放大器(3)

tinkerCAD案例&#xff1a;28. Build a Mobile Amplifier 构建移动放大器(3) 原文 step 1 “爵士乐”放大器 Lesson Overview: 课程概述&#xff1a; Now we’re going to decorate our design! 现在我们要装饰我们的设计&#xff01; step 2 In this step we will ref…

纯CSS实现手风琴效果(常用样式)

【效果图】&#xff1a; 【html代码】&#xff1a; <div class"rowd"><ul class"fold_wrap"><li><a href"#"><div class"pic_auto pic_auto1 trans"></div><div class"adv_intro flex&…

qt子进程和父进程读写数据通信

进程A&#xff08;例如主程序&#xff09;创建了一个QProcess B&#xff0c;这个B就称为A的子进程&#xff0c;而A称为B的父进程。 这也称为进程间通信&#xff0c;有多种方式&#xff1a; TCP/IPLocal Server/Socket共享内存D-Bus &#xff08;Unix库&#xff09;QProcess会…

Java版本企业电子招投标采购系统源码+功能模块功能描述+数字化采购管理 采购招投标

功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&#xff0c;为外部…

Android复习(Android基础-四大组件)—— Activity

Activity作为四大组件之首&#xff0c;是使用最为频繁的一种组件&#xff0c;中文直接翻译为"活动"&#xff0c;不过如果被翻译为"界面"会更好理解。正常情况&#xff0c;除了Window&#xff0c;Dialog和Toast &#xff0c; 我们能见到的界面只有Activity。…

【phaser微信抖音小游戏开发003】游戏状态state场景规划

经过目录优化后的执行结果&#xff1a; 经历过上001&#xff0c;002的规划&#xff0c;我们虽然实现了helloworld .但略显有些繁杂&#xff0c;我们将做以下的修改。修改后的目录和文件结构如图。 game.js//小游戏的重要文件&#xff0c;从这个开始。 main.js 游戏的初始化&a…

集合框架、多线程、IO流

目录 集合框架 Java迭代器&#xff08;Iterator&#xff09; Java集合类 Collection派生 Map接口派生&#xff1a; Java集合List ArrayList Vector LinkedList Java集合Set HashSet LinkedHashSet TreeSet Java集合Queue&#xff08;队列&#xff09; PriorityQue…

AP5101 高压线性恒流电源驱动 输入 24-36V 输出3串18V LED线性恒流驱动方案

1,输入 24V-36V 输出3串18V 直亮 参考BOM 表如下 2,输入 24V-36V 输出3串18V 直亮 参考线路图 如下​ 3&#xff0c;产品描述 AP5101B 是一款高压线性 LED 恒流芯片&#xff0c;外围简单、内置功率管&#xff0c;适用于6- 60V 输入的高精度降压 LED 恒流驱动芯片。最大…

cloudstack之advanced network

cloudstack网络模式的介绍&#xff0c;可参考【cloudstack之basic network】 一、添加资源 访问UI&#xff0c;默认端口为8080&#xff0c;默认用户民和密码是admin/password。点击【continue with installation】。修改默认密码选择zone type&#xff1a;core 选择advanced模…