主题:[原创]做了一个动态连接库(DLL),读取JPEG图像文件(附件已上传)
eastcowboy
[专家分:25370] 发布于 2008-04-15 16:45:00
上次在网上看到IJG,是一个用C语言写的、跨平台的、操作JPEG的开源函数库。但是这个库比较庞大,编译后有数百k。对于普通的应用来说,通常只希望读取JPEG图象,而不会保存JPEG图象,因此很多功能也用不上。
最近我把IJG封装了一下,做成了自己的DLL,现在发出来给大家分享。提供了两种风格的接口,C语言可以直接调用函数接口,C++则推荐使用封装后的类。大家看看这个封装是否合理?希望各位能提出好的建议[em2]。
头文件、编译好的DLL和LIB文件在附件里,另外还附带了测试图片和测试用的源代码(利用OpenGL的glDrawPixels把像素绘制到屏幕上)。
单独头文件贴出,其中有大量注释,就是对各函数、类的说明了。(因字数限制,分为两部分)
[code=c]#ifndef _jpeg_H_
#define _jpeg_H_
#ifdef __cplusplus
extern "C" {
#endif
//+===========================================================================+
//| |
//| C风格的函数接口 |
//| |
//+===========================================================================+
// enum tagErrorCode
//
// 错误代码
typedef enum tagErrorCode{
EC_NO_ERROR = 0, // 正常
EC_UNKNOWN_ERROR, // 未知错误
EC_OUT_OF_MEMORY, // 内存不足
EC_BAD_ALIGN, // 错误的字节对齐数
EC_BAD_FILE, // 文件打开失败
EC_BAD_FORCE_X, // 强制设定宽度引起的失败
EC_BAD_FORCE_Y // 强制设定高度引起的失败
} ErrorCode;
// enum tagJpegImageStatusName
//
// 状态名称
typedef enum tagJpegImageStatusName {
JISN_RESERVED = 0, // 零是保留的名字
JISN_LAST_ERROR, // 错误代码,为零表示没有错误
JISN_ALIGN_BYTES, // 按字节对齐(1表示不对齐)
JISN_IS_BUTTON_TO_TOP, // 像素垂直保存方向(非零表示从上到下)
JISN_FORCE_X, // 强制指定的宽度(零表示不强制指定)
JISN_FORCE_Y // 强制指定的高度(零表示不强制指定)
} JpegImageStatusName;
// struct JpegImage
//
// 保存JPEG数据
typedef struct tagJpegImage {
int sizeX; // 图象宽度(水平方向像素数量)
int sizeY; // 图象高度(垂直方向像素数量)
int components; // 图象的颜色分量数,例如:
// RGB有红、绿、蓝三个分量
// RGBA有红、绿、蓝、alpha四个分量
int bytesInLine; // 每行像素数据所占的字节数
// 这个值至少是sizeX * components,但是可能更多
// 参见JpegImage_SetAlign函数
int isButtonToTop; // 像素的垂直保存方式
// 参见JpegImage_SetButtonToTop函数
const void* bytes; // 指向像素数据的指针
} JpegImage;
// function JpegImage_GetLastError
//
// 获得最后一次的出错信息
// 如果JpegImage_LoadXXX系列函数没有成功的读取到JPEG图象,则会设置一个错误信息
// 通过本函数可以获得该错误信息的描述文本
//
// 参数:没有
// 返回:一个错误信息文本
//
// 说明:文本数据放在静态存储区,不要调用free或delete来释放
//
const char* JpegImage_GetLastError();
// function JpegImage_SetAlignBytes
//
// 设置字节对齐
// 比如某图象宽度为30,使用RGB格式,因此一行像素总共需要30*3=90字节,
// 但是如果设置为四字节对齐,则一行像素使用92字节(92是4的倍数),
// 最后两字节闲置不使用
// 例如将JPEG转化为BMP,BMP要求四字节对齐,这里设置字节对齐后不必另行编写代码
// 注意本函数只能设置为单字节、双字节、四字节、八字节对齐
// 若设置为单字节对齐,实际上与不使用字节对齐是等效的
//
// 参数:int alignBytes -- 对齐的字节数,必须是1, 2, 4, 8这四者之一
// 返回:若成功返回非零值,若失败返回零
// 如果返回零,可以使用JpegImage_GetLastError取得错误信息
//
// 说明:如果没有使用本函数设置字节对齐,则默认为四字节对齐
//
int JpegImage_SetAlignBytes(int alignBytes);
// function JpegImage_SetButtonToTop
//
// 设置像素的垂直保存方式
// 有两种保存方式可供选择
// 从上到下的保存,
// 首先保存最上一行像素,然后依次往下,直到最下一行
// 从下到上的保存,
// 首先保存最下一行像素,然后依次往上,直到最上一行
//
// 参数:int isButtonToTop -- 如果为非零值,表示从下到上的保存
// 否则表示从上到下的保存
// 返回:没有
//
// 说明:如果没有使用本函数设置垂直保存方式,则默认为从上到下的保存
// 无论哪种方式,每一行像素都是从左到右保存的
//
void JpegImage_SetButtonToTop(int isButtonToTop);
// function JpegImage_SetForceSize
//
// 强制性的设定JPEG图象的大小
// 如果JPEG文件头中没有包含正确的高度、宽度信息,则可以使用本函数强制设定
//
// 参数:int forceX -- 强制性的设定图象宽度
// 如果这个值为零或者负数,则表示不强制性的设定
// int forceY -- 强制性的设定图象高度
// 如果这个值为零或者负数,则表示不强制性的设定
//
// 说明:某些情况下,多个JPEG图象共用一个文件头,只是文件体不同
// (参见暴雪公司游戏《魔兽争霸3》中的BLP文件格式)
// 如果这些JPEG图象本身的大小是各不相同的,文件头无法一一指定各自的大小
// 此时可以通过本函数强制设定
//
// 注意:为了确保不出现运行时错误(Runtime Error),规定
// 当强制性设定的大小大于图象本身的大小时,读取直接失败,
// 而不是妄图读取大量的数据,导致下标越界
//
void JpegImage_SetForceSize(int forceX, int forceY);
// function JpegImage_LoadFromFile
//
// 从文件读取一幅JPEG图象
//
// 参数:const char* filename -- 文件的名字
// 返回:若读取成功,返回图象数据的指针
// 当图象数据不再需要时,可以通过JpegImage_Unload函数销毁图象数据
// 若读取失败,返回NULL指针
// 此时可以使用JpegImage_GetLastError取得错误信息
//
JpegImage* JpegImage_LoadFromFile(const char* filename);
// function JpegImage_LoadFromMemory
//
// 从内存中读取一幅JPEG图象
//
// 参数:const void* data -- 指向JPEG数据的指针
// size_t size -- JPEG数据的长度(按字节计算)
// 返回:若读取成功,返回图象数据的指针
// 当图象数据不再需要时,可以通过JpegImage_Unload函数销毁图象数据
// 若读取失败,返回NULL指针
// 此时可以使用JpegImage_GetLastError取得错误信息
//
// 说明:如果是从网络或其它位置获得JPEG数据,而不是从文件获得,则可以使用本函数
//
// 本函数相当于
// JpegImage_LoadFromMemorySegment(1, &data, &size);
// 参见JpegImage_LoadFromMemorySegment
//
JpegImage* JpegImage_LoadFromMemory(const void* data, size_t size);
// function JpegImage_LoadFromMemorySegment
//
// 从内存中读取一幅JPEG图象
// 其中JPEG图象的数据并不是连续存放,而是被分为多段
//
// 参数:size_t iSegments -- 数据的段数
// const void* segmentData -- 一个数组
// 数组的第i个元素是指向第i段数据的指针
// size_t segmentSize -- 一个数组
// 数组的第i个元素是第i段数据的长度
// 返回:若读取成功,返回图象数据的指针
// 当图象数据不再需要时,可以通过JpegImage_Unload函数销毁图象数据
// 若读取失败,返回NULL指针
// 此时可以使用JpegImage_GetLastError取得错误信息
//
// 说明:某些情况下,多个JPEG图象共用一个文件头,只是文件体不同
// (参见暴雪公司游戏《魔兽争霸3》中的BLP文件格式)
// 这时一个JPEG图象被分为文件头和文件体两段,可以使用本函数来读取
// 例如:pHeader, iHeaderSize, pBody, iBodySize分别表示了
// 指向文件头数据的指针、文件头数据长度、
// 指向文件体数据的指针、文件体数据长度
// 则以下代码可以读取这个JPEG图象
// const void* segmentData[] = {pHeader, pBody};
// const size_t segmentSize[] = {iHeaderSize, iBodySize};
// JpegImage* pJpegImage = JpegImage_LoadFromMemorySegment(
// 2, segmentData, segmentSize);
//
JpegImage* JpegImage_LoadFromMemorySegment(
size_t iSegments,
const void* segmentData[],
const size_t size[]);
// function JpegImage_Unload
//
// 销毁JPEG图象数据
//
// 参数:JpegImage* pJpegImage -- 指向JPEG图象数据的指针
// 返回:没有
//
// 说明:仅当JPEG图象数据不再使用时,才应该进行销毁
// 销毁后的图象数据不再能使用
void JpegImage_Unload(JpegImage* pJpegImage);
// function JpegImage_GetStatusInteger
//
// 查询当前的状态
//
// 参数:JpegImageStatusName statusName -- 要取得的状态的名字
// 返回:指定的状态的值
// 如果指定的不是一个状态,则默认返回0
//
int JpegImage_GetStatusInteger(JpegImageStatusName statusName);[/code]
最后更新于:2008-04-16 17:43:00
回复列表 (共11个回复)
沙发
eastcowboy [专家分:25370] 发布于 2008-04-15 16:46:00
[code=c]#ifdef __cplusplus
//+===========================================================================+
//| |
//| C++风格的类接口 |
//| |
//| 如果使用C++语言而不是C语言,请使用下面的类,而不是直接使用上面的C语言函数 |
//| |
//| 类接口的特点: |
//| 1. 在构造函数中读取数据,在析构函数中销毁数据 |
//| 这样做的好处是即使C++程序运行出现异常时,仍然可以自动销毁数据 |
//| 2. 利用异常来报告错误,而不是使用GetLastError |
//| 这样做的好处是可以简化代码,减少软件测试工作量,线程安全 |
//+===========================================================================+
#include <exception>
// class CJpegException
//
// 异常类,从std::exception继承
// 当使用C风格的函数遇到错误时,由CJpegImage类抛出CJpegException异常
//
// 因为从std::exception继承,可以通过what函数来了解异常的具体信息
//
// 参见JpegImage_GetLastError函数
//
class CJpegException : public std::exception {
public:
CJpegException() {
m_message = JpegImage_GetLastError();
}
const char* what() const {
return m_message;
}
private:
const char* m_message;
};
// class CJpegImage
//
// JPEG图象类,在构造函数中读取JPEG数据,并在析构函数中销毁之
// 本类其实是对前面C风格的函数接口的封装
//
// 在调用前面C风格的任何函数时如果遇到错误,
// 本类并不会返回错误代码,而是使用异常来报告错误
//
// CJpegImage类的对象是不允许被复制的
// 拷贝构造函数和operator=都被声明为private
//
class CJpegImage {
public:
// 从文件读取JPEG图象
// 参见JpegImage_LoadFromFile函数
CJpegImage(const char* filename)
: m_pJpegImage(JpegImage_LoadFromFile(filename)) {
checkLoadError();
}
// 从内存读取JPEG图象
// 参见JpegImage_LoadFromMemory函数
CJpegImage(const void* data, size_t size)
: m_pJpegImage(JpegImage_LoadFromMemory(data, size)) {
checkLoadError();
}
// 从内存读取JPEG图象,内存的数据被分成许多段
// 参见JpegImage_LoadFromMemorySegment函数
CJpegImage(size_t segments,
const void* segmentData[], const size_t segmentSize[])
: m_pJpegImage(JpegImage_LoadFromMemorySegment(
segments, segmentData, segmentSize)){
checkLoadError();
}
// 析构函数,销毁图象数据
// 参见JpegImage_Unload函数
~CJpegImage() {
JpegImage_Unload(m_pJpegImage);
}
// 设置字节对齐
// 参见JpegImage_SetAlignBytes函数
static void setAlignBytes(int alignBytes) {
if( !JpegImage_SetAlignBytes(alignBytes) )
throw CJpegException();
}
// 设置像素的垂直保存方式
// 参见JpegImage_SetButtonToTop函数
static void setButtonToTop(int isButtonToTop) {
JpegImage_SetButtonToTop(isButtonToTop);
}
// 强制性的设定JPEG图象的大小
// 参见JpegImage_SetForceSize函数
static void setForceSize(int forceX, int forceY) {
JpegImage_SetForceSize(forceX, forceY);
}
[/code]
板凳
eastcowboy [专家分:25370] 发布于 2008-04-15 16:47:00
[code=c]
// 查询当前的状态
// 参见JpegImage_GetStatusInteger函数
static int getStatusInteger(JpegImageStatusName statusName) {
return JpegImage_GetStatusInteger(statusName);
}
// 图象宽度(水平方向像素数量)
int sizeX() const {
return m_pJpegImage->sizeX;
}
// 图象高度(垂直方向像素数量)
int sizeY() const {
return m_pJpegImage->sizeY;
}
// 图象的颜色分量数,例如:
// RGB有红、绿、蓝三个分量
// RGBA有红、绿、蓝、alpha四个分量
int components() const {
return m_pJpegImage->components;
}
// 每行像素数据所占的字节数
// 这个值至少是sizeX * components,但是可能更多
// 参见JpegImage_SetAlign函数
int bytesInLine() const {
return m_pJpegImage->bytesInLine;
}
// 像素的垂直保存方式
// 参见JpegImage_SetButtonToTop函数
int isButtonToTop() const {
return m_pJpegImage->isButtonToTop;
}
// 指向像素数据的指针
const void* bytes() const {
return m_pJpegImage->bytes;
}
private:
JpegImage* m_pJpegImage;
CJpegImage(const CJpegImage& copy);
CJpegImage& operator=(const CJpegImage& copy);
void checkLoadError() {
if( !m_pJpegImage )
throw CJpegException();
}
};
}
#endif
#endif
[/code]
3 楼
sarrow [专家分:35660] 发布于 2008-04-15 17:39:00
很好,很强大!
4 楼
eastcowboy [专家分:25370] 发布于 2008-04-16 17:44:00
呃,帖子都放了一天了,现在才发现没有把附件传上来,失败啊。
5 楼
SonicLing [专家分:6260] 发布于 2008-04-17 10:00:00
自在Windows平台下直接用GDI+可以读取所有Windows系统支持的图像,并获取像素矩阵。
6 楼
eastcowboy [专家分:25370] 发布于 2008-04-17 13:12:00
[quote]Windows平台下直接用GDI+可以读取所有Windows系统支持的图像,并获取像素矩阵[/quote]
这个还没留意过。不知道是不是LoadImage函数?据我所知这个函数只能从文件读取或者从资源读取,不知道能不能从内存中读取?
我包装的这个东东是可以从内存中读取的。而且因为IJG是逐行解码,可以设定图象像素是否倒置,以及每行像素如何对齐,这比“先读取再倒置”“先读取再对齐”要快上一点。
包装的最初目的除了读取JPEG格式的文件外,还包括读取暴雪公司在《魔兽争霸3》中所使用的BLP图象格式,这是JPEG格式的变体,数据是分段保存的(分为文件头、文件体两部分),dll中也提供对应的接口。
还有一点就是我本人不太喜欢Windows API的那一套风格。要用API的功能,我总是喜欢先封装再使用。如果代码中到处都是HWND, HDC, HBITMAP,让我觉得不舒服。
我喜欢把销毁资源的代码全部都写到析构函数中。如果代码中到处都是DeleteObject, DestroyDC, ReleaseDC,这也是我所不喜欢的。C++不像C语言,它在运行的时候可能产生异常,因此函数并不总是遇到return才返回的,要正确的销毁资源,就应该用析构函数。
7 楼
SonicLing [专家分:6260] 发布于 2008-04-17 13:18:00
可以为内存中的原始矩阵建立一个BMPINFO,然后Create一个HBITMAP,然后Load这个Handle就可以了。
8 楼
SonicLing [专家分:6260] 发布于 2008-04-17 13:22:00
本人也不喜欢WindowsAPI的那种风格,很别扭。看人家Borland封装Windows窗体多么彻底,那么好用,MFC是没法比。
C#的库还可以。而.Net的构架师就是从Borland挖过去的,只能说明一点:微软自己还没有培养出来一个真正懂得面向对象的大师。
9 楼
carlward [专家分:0] 发布于 2009-12-30 22:33:00
为什么不把你封装的 DLL 源代码也开放出来?
我打算在 WINCE 下使用的
10 楼
eastcowboy [专家分:25370] 发布于 2009-12-31 03:15:00
抱歉,发贴的时候没有公布源代码,现在好象已经找不到了。
读取JPG图片的话,我现在知道的有以下几种选择:
(1) 使用IJL,即Intel JPEG Library。这个库是Intel公司做的,以前比较流行,网上资料也不少。不过现在Intel已经不再维护它了,因此有点过时。
(2) 使用IJG(一般说libjpeg也是它)。前面所发的dll就是用的这个库。它是C语言编写的,可以在Dos/Windows/Unix等系统中使用。可以在官方网站下载这个库的源代码:http://www.ijg.org/。可以自己用源代码编译,这样的好处很明显——不管是Windows还是WinCE还是DOS,只要编译,就好了。VC的编译方法为:解压后先把jconfig.vc改名为jconfig.h,把makefile.vc改名为makefile,然后启动命令行,转到该目录,输入“nmake”即可完成编译,得到“libjpeg.lib”。如果不喜欢命令行,可以在VC(或者你用EVC)建立一个工程,把需要的代码全都加到这个工程里面,编译即可。
(3) 使用CxImage。采用面向对象的方式来操作图象。高版本的MFC有一个类叫做CImage,而它叫作CxImage,名字相似,使用方法也相似。如果熟悉MFC的话,相信很快就熟悉了。它支持多种图片格式,包括BMP,TGA等。它默认配置为不支持JPG,但是也可以修改配置,以便支持JPG。不过在内部其实是调用了IJG来操作JPG的。如果你是下载的源代码,那在编译时就需要修改配置以便支持JPG,并且在编译时需要IJG库。如果下载的是已经编译好的dll,那就很简单了。
(4) DevIL,以前也叫OpenIL。这个库是我比较喜欢的,在网上也可以找到源代码和编译好的dll。它在使用方面分为三个部分(因此也有三个独立的dll):DevIL部分,负责图片的基本处理,包括图片的读取、保存、像素/色深/格式转换等。ILU部分,负责图片的变换,例如颜色变换、各种滤镜等。ILUT部分,负责处理图片与其它库的接口,例如把图片加载为HBITMAP,或者加载到OpenGL,或者DirectX等。它内部在读取JPG图片时也是使用IJG库。
基本就是这样了。如果还有什么疑问,欢迎与我联系:eastcowboy2002@163.com
我来回复