jpeg格式是一种针对相片影像而广泛使用的一种失真压缩标准,其压缩技术十分先进,用有损压缩方式去除冗余的图像数据,在获得极高压缩率的同时,能展现十分丰富生动的图像,能用最少的磁盘空间得到较好的图像品质。由于其尺寸较小,能够较快地在网络上传输,因此在数码相机、网页等领域均广泛应用到jpeg图像格式。笔者此处就移植libjpeg开源库来应用jpeg作一个简单的介绍。
1. 代码准备
libjpeg源码,libjpeg是一个完全用c语言编写的库,包含了被广泛使用的jpeg编码、jpeg解码和其它的jpeg功能的实现,这个库由独立的jpeg工作组维护。请读者自行到官网下载最新的源码。
s3c2416启动代码工程,启动代码是s3c2416/50/51这系列arm9芯片在运行用户c代码main函数之前必须先运行的代码,启动代码支持sd、Nand启动,为用户设置系统时钟,初始化内存,自动识别启动设备并搬移代码到RAM,MMU映射,中断管理等,支持x、ymodem文件传输协议,代码可直接通过串口下载进RAM执行,用户只需专注于用c开发其它功能函数即可。关于启动代码以及启动代码的实现过程,笔者前面章节有非常详细的介绍。此处在MDK下开发,下载”MDK启动代码工程应用实例”中的启动代码源码。
用户代码,用c开发的所有功能代码,其中,用户代码入口为main()函数,在这里用libjpeg库实现在屏上显示jpeg图像以及屏幕截图保存成jpeg图片。
2. 标准io库函数
libjpeg由于其开源免费,对桌面操作系统均有良好的支持,因此在linux、windows操作系统下均常用libjpeg来编解码jpeg图片。libjpeg以及例程使用了标准io库函数,如文件操作fopen、fread、fwrite等;输入、输出、错误流函数printf、fprintf等。对于嵌入式开发来说,所有的io操作均是面对特定设备,编译器的标准c库是无法统一实现的,应由用户去实现。在arm编译器中,是使用一种半主机的模式,当用户使用了标准io库函数时,默认情况下,编译器是通过一组定义好的软中断来把io应用请求传送至运行调试器的主机,如用printf和scanf来使用主机的屏幕以及键盘,需要jtag的支持。一旦目标板脱离主机,需单独运行时,在半主机模式下是无法运行,因为io软中断请求无法得到处理。因此对于libjpeg移植,我们必须先对所使用到的io库函数进行重定向,如把输入、输出、错误流重定向到串口,把文件操作重定向到对sd卡的文件操作。此处我们在sd卡中采用的是fatfs文件系统,关于fatfs的移植,笔者在前面的章节有详细的介绍,此处不再详述。在MDK中,标准io库函数最终是通过系统调用接口来完成实质的io请求,我们必须在这些系统调用接口上重定向为目标板上特定设备的请求操作。对于linux操作系统,标准io库函数最终也是通过系统调用,从用户态转到内核态,通过系统api完成实质的io请求操作。在Retarget.c中我们实现所有的标准io库函数的重定向,该部分的代码如下:
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <rt_sys.h>
#include "ff.h"
#include "diskio.h"
#include "UART0.h"
#pragma import (__use_no_semihosting_swi)
#defineSTDIO 1
/* Standard IO device handles. */
#define STDIN 0x1
#define STDOUT 0x2
#define STDERR 0x3
/* Standard IO device name defines. */
const char __stdin_name[] = "STDIN";
const char __stdout_name[] = "STDOUT";
const char __stderr_name[] = "STDERR";
struct __FILE {int handle; /* Add whatever you need here */ };
#ifdef STDIO
/*-----------------------------------------------------------------------------
Write character to the Serial Port
*----------------------------------------------------------------------------*/
int sendchar(int c) {
if (c == '\n') {
Uart0_SendByte('\r');
}
Uart0_SendByte(c);
return (c);
}
/*-----------------------------------------------------------------------------
Read character from the Serial Port
*----------------------------------------------------------------------------*/
int getkey(void) {
int ch = Uart0_ReceiveByte();
if (ch < 0) {
return 0;
}
return ch;
}
#endif
/*---------------------------_ttywrch ---------------------------------------*/
void _ttywrch(int ch) {
#ifdef STDIO
sendchar (ch);
#endif
}
/*---------------------------_sys_open --------------------------------------*/
FILEHANDLE _sys_open (const char *name, int openmode) {
FRESULT Res;
FILEHANDLE fh;
BYTE Mode = FA_READ;
/* Register standard Input Output devices. */
if (strcmp(name, "STDIN") == 0) {
return (STDIN);
}
if (strcmp(name, "STDOUT") == 0) {
return (STDOUT);
}
if (strcmp(name, "STDERR") == 0) {
return (STDERR);
}
if (openmode & OPEN_W) {
Mode|= FA_WRITE | FA_OPEN_ALWAYS;
Mode&= ~FA_READ;
}
if (openmode & OPEN_PLUS) {
Mode|= FA_WRITE | FA_READ;
}
if (openmode & OPEN_A) {
Mode|= FA_OPEN_ALWAYS;
}
fh = (FILEHANDLE)malloc(sizeof(FIL));
if (fh == NULL) {
return -1;
}
Res = f_open((FIL *)fh, name, Mode);
if (Res == RES_OK) {
return fh;
} else {
free((FIL *)fh);
return -1;
}
}
/*---------------------------_sys_close -------------------------------------*/
int _sys_close(FILEHANDLE fh) {
FRESULT Res;
if (fh > 0 && fh < 4) {
return (0);
}
Res = f_close((FIL *)fh);
free((FIL *)fh);
if (Res == RES_OK) {
return 0;
}
return -1;
}
/*---------------------------_sys_write -------------------------------------*/
int _sys_write(FILEHANDLE fh, const uint8_t *buf, uint32_t len, int32_t mode) {
FRESULT Res;
UINT ByteWrite;
#ifdef STDIO
if (fh > 0 && fh < 4) {
if (fh == STDOUT) {
/* Standard Output device. */
for ( ; len; len--) {
sendchar (*buf++);
}
return (0);
} else {
return (-1);
}
}
#endif
#if!(_FS_READONLY)
Res = f_write((FIL *)fh, buf, len,&ByteWrite);
if (Res == RES_OK) {
if(ByteWrite < len) {
return (len-ByteWrite);
}
return 0;
}
#endif
return -1;
}
/*---------------------------_sys_read --------------------------------------*/
int _sys_read(FILEHANDLE fh, uint8_t *buf, uint32_t len, int32_t mode) {
FRESULT Res;
UINT ByteRead;
#ifdef STDIO
if (fh > 0 && fh < 4) {
if (fh == STDIN) {
/* Standard Input device. */
for ( ; len; len--) {
*buf++ = getkey ();
}
return (0);
} else {
return (-1);
}
}
#endif
Res = f_read((FIL *)fh, buf, len,&ByteRead);
if (Res == RES_OK) {
if(ByteRead < len) {
return (len-ByteRead);
}
return 0;
}
return -1;
}
/*---------------------------_sys_istty -------------------------------------*/
int _sys_istty(FILEHANDLE fh) {
if (fh > 0 && fh < 4) {
return (1);
}
return (0);
}
/*---------------------------_sys_seek --------------------------------------*/
int _sys_seek(FILEHANDLE fh, long pos) {
FRESULT Res;
if (fh > 0 && fh < 4) {
return (-1);
}
#if_FS_MINIMIZE < 3
Res = f_lseek((FIL *)fh, pos);
if (Res == RES_OK) {
return 0;
}
#endif
return (-1);
}
/*---------------------------_sys_ensure ------------------------------------*/
int _sys_ensure(FILEHANDLE fh) {
FRESULT Res;
if (fh > 0 && fh < 4) {
return (-1);
}
#if!(_FS_READONLY)
Res = f_sync((FIL *)fh);
if (Res == RES_OK) {
return 0;
}
#endif
return -1;
}
/*---------------------------_sys_flen --------------------------------------*/
long _sys_flen(FILEHANDLE fh) {
if (fh > 0 && fh < 4) {
return (0);
}
return f_tell((FIL *)fh);
}
/*---------------------------_sys_tmpnam ------------------------------------*/
int _sys_tmpnam(char *name, int sig, unsigned maxlen) {
return (1);
}
/*---------------------------_sys_command_string ----------------------------*/
char* _sys_command_string (char *cmd, int len) {
return (cmd);
}
/*---------------------------_sys_exit --------------------------------------*/
void _sys_exit(int return_code) {
/* Endless loop. */
while (1);
}
3. libjpeg移植
首先解压从官网下载的libjpeg源码,有很多的文件,其中以j命名开头的.h、.c文件均是源码文件,其它的有Makefile、shell脚本以及例程代码等,详细信息以及libjpeg的使用请参考源码包中的文档。把jpeg源码文件提取出加入到MDK工程,在libjpeg源码包中还包含读写bmp、gif等例程源码,可根据需要提取加入工程。此处由于重定向了标准io库函数,libjpeg内存分配也采用默认的c库malloc/free函数,因此,移植无需任何的修改,加入工程即可进行编译。
4. 显示jpeg图片
加入了文件系统fatfs,并对io库函数进行重定向以后,就可以用libjpeg库函数进行jpeg图片的编解码了。目前大量基于桌面操作系统的libjpeg应用例程均无需作太多的修改就可以应用到我们的嵌入式系统中。此处完全采用libjpeg源码包中的jpeg解压例程,但解压的像素数据输出到屏上进行显示,当然也可以输出到一块缓存中,再对缓存进行图像处理等。
struct my_error_mgr {
struct jpeg_error_mgr pub; /* "public" fields */
jmp_buf setjmp_buffer; /* for return to caller */
};
typedef struct my_error_mgr * my_error_ptr;
void my_error_exit (j_common_ptr cinfo)
{
/* cinfo->err really points to amy_error_mgr struct, so coerce pointer */
my_error_ptr myerr = (my_error_ptr)cinfo->err;
/* We could postpone this untilafter returning, if we chose. */
(*cinfo->err->output_message)(cinfo);
/* Return control to the setjmppoint */
longjmp(myerr->setjmp_buffer,1);
}
int32_t LCD_DisplayJpeg(char * filename, int x, int y)
{
struct jpeg_decompress_structcinfo;
struct my_error_mgr jerr;
FILE * infile; /* source file */
JSAMPARRAY buffer; /* Output row buffer */
JSAMPROW RowBuffer; /* a row buffer */
uint16_t *pBuffer;
uint8_t r, g, b;
int row_stride; /* physical row width in output buffer */
uint32_t i;
uint32_t DisplayWidth;
uint32_t DisplayHeight;
if (x<0 || x>(HSize-1) ||y<0 || y>(VSize-1)) {
printf("parameter error\n");
return -1;
}
if ((infile = fopen(filename,"rb")) == NULL) {
fprintf(stderr, "can't open%s\n", filename);
return 1;
}
/* Step 1: allocate and initializeJPEG decompression object */
cinfo.err =jpeg_std_error(&jerr.pub);
jerr.pub.error_exit =my_error_exit;
if (setjmp(jerr.setjmp_buffer)) {
jpeg_destroy_decompress(&cinfo);
fclose(infile);
return 2;
}
/* Now we can initialize the JPEGdecompression object. */
jpeg_create_decompress(&cinfo);
/* Step 2: specify data source (eg,a file) */
jpeg_stdio_src(&cinfo, infile);
/* Step 3: read file parameterswith jpeg_read_header() */
(void) jpeg_read_header(&cinfo,TRUE);
/* Step 4: set parameters fordecompression */
/* In this example, we don't needto change any of the defaults set by
* jpeg_read_header(), so we donothing here.
*/
/* Step 5: Start decompressor */
(void) jpeg_start_decompress(&cinfo);
if (HSize - x >cinfo.output_width) {
DisplayWidth = cinfo.output_width;
} else {
DisplayWidth = HSize - x;
}
if (VSize - y >cinfo.output_height) {
DisplayHeight =cinfo.output_height;
} else {
DisplayHeight = VSize - y;
}
cinfo.output_width = DisplayWidth;
cinfo.output_height =DisplayHeight;
row_stride = cinfo.output_width *cinfo.output_components;
buffer =(*cinfo.mem->alloc_sarray)
((j_common_ptr) &cinfo,JPOOL_IMAGE, row_stride, 1);
/* Step 6: while (scan lines remainto be read) */
/* jpeg_read_scanlines(...); */
while (cinfo.output_scanline <DisplayHeight) {
(void)jpeg_read_scanlines(&cinfo, buffer, 1);
pBuffer = (uint16_t*)GetFrameBuffer() + x + y*HSize;
RowBuffer = *buffer;
for (i=0; i<DisplayWidth; i++){
r = *RowBuffer++;
r >>= 3;
g = *RowBuffer++;
g >>= 2;
b = *RowBuffer++;
b >>= 3;
*pBuffer++=((uint16_t)r << 11)|((uint16_t)g << 5)|((uint16_t)b << 0);
}
y++;
}
/* Step 7: Finish decompression */
(void)jpeg_finish_decompress(&cinfo);
/* Step 8: Release JPEGdecompression object */
jpeg_destroy_decompress(&cinfo);
fclose(infile);
/* And we're done! */
return 0;
}
5. 屏幕截图
像素数据可以保存成jpeg图片文件,此处把屏缓存数据提取出进行jpeg编码,并在sd卡中保存成jpeg文件。
// (x0, y0)为截屏的左上角像素位置,(x1,y1)为右下角像素位置
int32_t LCD_CaptureScreen (char * filename, int x0, int y0, int x1, int y1,int quality)
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
/* More stuff */
FILE * outfile; /* target file */
JSAMPARRAY buffer; /* Output row buffer */
JSAMPROW RowBuffer; /* a row buffer */
int row_stride; /* physical row width in image buffer */
uint16_t *pBuffer;
uint32_t i;
int Width;
int Height;
if (x0<0 || x1>HSize ||y0<0 || y1>VSize) {
printf("parameter error\n");
return -1;
}
Width = x1 - x0;
Height = y1 - y0;
if (Width <= 0 && Height<= 0) {
printf("parameter error\n");
return -2;
}
/* Step 1: allocate and initializeJPEG compression object */
cinfo.err =jpeg_std_error(&jerr);
/* Now we can initialize the JPEGcompression object. */
jpeg_create_compress(&cinfo);
/* Step 2: specify data destination(eg, a file) */
/* Note: steps 2 and 3 can be donein either order. */
if ((outfile = fopen(filename,"wb")) == NULL) {
fprintf(stderr, "can't open%s\n", filename);
return 1;
}
jpeg_stdio_dest(&cinfo,outfile);
/* Step 3: set parameters forcompression */
cinfo.image_width = Width; /* image width and height, in pixels */
cinfo.image_height = Height;
cinfo.input_components = 3; /* # of color components per pixel */
cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo,quality, TRUE /* limit to baseline-JPEG values */);
/* Step 4: Start compressor */
jpeg_start_compress(&cinfo,TRUE);
/* Step 5: while (scan lines remainto be written) */
/* jpeg_write_scanlines(...); */
row_stride = Width * 3; /* JSAMPLEs per row in image_buffer */
buffer =(*cinfo.mem->alloc_sarray)
((j_common_ptr) &cinfo,JPOOL_IMAGE, row_stride, 1);
while (cinfo.next_scanline <Height) {
RowBuffer = *buffer;
pBuffer = (uint16_t*)GetFrameBuffer() + x0 + y0*HSize;
for (i=0; i<Width; i++) {
*RowBuffer++ = (*pBuffer &0xf800) >> 8;
*RowBuffer++ = (*pBuffer &0x7e0) >> 3;
*RowBuffer++ = (*pBuffer &0x1f) << 3;
pBuffer++;
}
(void)jpeg_write_scanlines(&cinfo, buffer, 1);
y0++;
}
/* Step 6: Finish compression */
jpeg_finish_compress(&cinfo);
/* After finish_compress, we canclose the output file. */
fclose(outfile);
/* Step 7: release JPEG compressionobject */
/* This is an important step sinceit will release a good deal of memory. */
jpeg_destroy_compress(&cinfo);
/* And we're done! */
return 0;
}
6. 测试实例
写好jpeg显示函数以及屏幕截图函数,就可以在main函数中编写调用测试代码,在代码中实现按”1”进行jpeg图片的显示,按”2”进行截屏保存成jpeg图片。
#include "s3c2416.h"
#include "ff.h"
#include "diskio.h"
#include "RTC.h"
#include "UART0.h"
#include "stdio.h"
#include "stdint.h"
#include "lcd_rgb.h"
extern int32_t LCD_DisplayJpeg(char * filename, int x, int y);
extern int32_t LCD_CaptureScreen (char * filename, int x0, int y0, int x1,int y1, int quality);
int main()
{
uint8_t Command;
FATFS fs;
RTC_Time Time = {
2014, 5, 22, 23, 00, 0, 5
};
RTC_Init(&Time);
Uart0_Init();
RTC_GetTime(&Time);
printf("Time: %4d/%02d/%02d%02d:%02d:%02d\n", Time.Year,
Time.Month, Time.Day,Time.Hour, Time.Min, Time.Sec);
LCD_RGB_Init();
f_mount(&fs, "" ,0);
LCD_ClearScreen(0x1f);
while (1) {
printf("1: displaytest.jpg\n");
printf("2: capture screenand save as a jpg file\n");
Command = Uart0_ReceiveByte();
if (Command == '1') {
if(LCD_DisplayJpeg("test.jpg", 20, 20)) {
printf("displaypicture failed\n");
}
} else if (Command == '2') {
if(LCD_CaptureScreen("screen.jpg", 0, 0, HSize, VSize, 80)) {
printf("capturescreen failed\n");
}
}
printf("\n");
}
}
7. 广告
以下内容可能引起您的不适和反感,心理承受能力差的读者请跳过本段。 本人代理广州斯道s3c2416开发板,负责s3c2416裸机方面的开发支持,有需要的童鞋可以联系本人^_^(qq: 1048272975,8位机、cortex-m0/m3、arm7/arm9均可免费支持)。
icool2416由广州斯道一流开发团队设计,主处理器采用三星的S3C2416,cpu主频标配400M,可以稳定支持到533M,内存采用64MB DDR2 K4T51163(266M),NAND flash采用256MB SLCK9F2G08U0B,板载10/100M有线DM9000网卡,专业声卡WM8960,标配7寸RGB屏,真实5点触摸电容屏,外扩usb主从接口、rs232、tf卡座等标准接口,供电部分采用专业Torex的DC/DC芯片,具有非常良好的功耗,是一款极具性价比的开发板。
8. 附录
Libjpeg_MDK.rar,MDK下libjpeg移植工程源码,包含屏显示jpeg图片以及截屏保存成jpeg文件源码。