回 帖 发 新 帖 刷新版面

主题:[原创]做了一个动态连接库(DLL),读取JPEG图像文件(附件已上传)

上次在网上看到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]

回复列表 (共11个回复)

沙发

[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]

板凳

[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 楼

很好,很强大!

4 楼

呃,帖子都放了一天了,现在才发现没有把附件传上来,失败啊。

5 楼

自在Windows平台下直接用GDI+可以读取所有Windows系统支持的图像,并获取像素矩阵。

6 楼

[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 楼

可以为内存中的原始矩阵建立一个BMPINFO,然后Create一个HBITMAP,然后Load这个Handle就可以了。

8 楼

本人也不喜欢WindowsAPI的那种风格,很别扭。看人家Borland封装Windows窗体多么彻底,那么好用,MFC是没法比。

C#的库还可以。而.Net的构架师就是从Borland挖过去的,只能说明一点:微软自己还没有培养出来一个真正懂得面向对象的大师。

9 楼

为什么不把你封装的 DLL 源代码也开放出来?
我打算在 WINCE 下使用的

10 楼

抱歉,发贴的时候没有公布源代码,现在好象已经找不到了。
读取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

我来回复

您尚未登录,请登录后再回复。点此登录或注册