C#中使用FreeImage库加载Bmp、JPG、PNG、PCX、TGA、PSD等25种格式的图像(源码)。

  其实我一直都是喜欢自己去做图像格式的解码的(目前我自己解码的图像格式大概有15种),但是写本文主要原因是基于CSDN的这个帖子的:                             

http://bbs.csdn.net/topics/390510431 用pictureBox显示一个黑白8bit图像,如何消除颗粒感

      用于测试的原始的JPG图像: http://files.cnblogs.com/Imageshop/img01.rar

      这个帖子中,作者的需要加载一副灰度的8位的PG格式图像,但是利用.net的Bitmap类加载的图像会出现明显颗粒感,由于.net中的Bitmap类是基于GDI+操作的,因此我也是试着用我的Imageshop打开这幅图像(Imageshop内部也是用GDI+的API实现的),同样有颗粒感。因此,我们需要从其他的手段来解决这个问题。

              .net下加载的效果                                                         Photoshop打开的效果

      首先,我用了VS6.0中的Stdpicture对象来加载这幅图像,能得到正确的结果。然后用PS打开它,也能得到较好的效果,最后用微软的图片查看器,也是可以正确显示的。最后用mspaint(画图)工具打开,则出现了和在.net中一样的效果。

      因此,我们的第一理想方案是使用com里的Stdpicture来解决这个问题,其实在VB6.0下,一个LoadPicture函数就可以解决它,但是在C#下要使用它,需要很多API函数来处理,我自己试着搞了下,觉得过于繁琐,因此放弃了。

      因此,我把希望投向了比较有名的图像解码的软件FreeImage中,经过试验,发现FreeImage的解码是和PS一致的。

     我们先来看看百度对FreeImage的介绍:

      FreeImage是一款免费的、开源的、跨平台(Windows 、Linux 和Mac OS X )的,支持20 多种图像类型的(如BMP 、JPEG 、GIF 、PNG 、TIFF 等)图像处理库。其最大优点就是采用插件驱动型架构,具有快速、灵活、简单易用的特点,得到了广泛使用。

      FreeImage 的主要功能有多格式位图的读写;方便的位图信息获取;位深度转换;位图页面访问;基本的几何变换和点处理;通道合并与混合等。FreeImage 暂时不支持矢量图形和高级图像处理,位图绘制需要由用户来完成。

      FreeImage 中的所有函数都以FreeImage_ 开头,如图像文件的读写函数分别为FreeImage_Load 和FreeImage_Save 。FIBITMAP 数据结构保存着位图信息和像素数据,是FreeImage 的核心。

     由上述可见,FreeImage的侧重点偏向于图像的解码和编码,显示图像则需要用户自己负责,而这正是我们所需要的。

     为了能在.NET中使用FreeImage,我知道的有两种方式,一种是直接使用FreeImage 的Flat API,而这需要对使用的API函数进行声明。另外一种方式就是使用FreeImage 提供的FreeImageNET.dll中提供的类库(其实就是对FreeImage.dll中函数的封装)。 我这里把两种方式的实现都简单的描述下:

        public static Bitmap LoadImageFormFreeImage(string FileName)
        {
            Bitmap Bmp = null;
            FREE_IMAGE_FORMAT fif = FREE_IMAGE_FORMAT.FIF_UNKNOWN; ;
            fif = FreeImage_GetFileType(FileName, 0);
            if (fif == FREE_IMAGE_FORMAT.FIF_UNKNOWN)
            {
                fif = FreeImage_GetFIFFromFilename(FileName);
            }

            if ((fif != FREE_IMAGE_FORMAT.FIF_UNKNOWN) && (FreeImage_FIFSupportsReading(fif) != 0))
            {
                IntPtr Dib = FreeImage_Load(fif, FileName, 0);
                int Bpp = FreeImage_GetBPP(Dib);
                PixelFormat PF;
                int Width, Height, Stride;
                switch (Bpp)
                {
                    case 1:
                        PF = PixelFormat.Format1bppIndexed; break;
                    case 4:
                        PF = PixelFormat.Format4bppIndexed; break;
                    case 8:
                        PF = PixelFormat.Format8bppIndexed; break;
                    case 16:
                        PF = PixelFormat.Format16bppRgb555; break;
                    case 24:
                        PF = PixelFormat.Format24bppRgb; break;
                    case 32:
                        PF = PixelFormat.Format32bppArgb; break;
                    default:
                        FreeImage_Free(Dib);
                        return null;
                }
                Width = FreeImage_GetWidth(Dib);                        //  图像宽度
                Height = FreeImage_GetHeight(Dib);                      //  图像高度
                Stride = FreeImage_GetPitch(Dib);                       //  图像扫描行的大小,必然是4的整数倍

                /**  方案1:存在内存泄露
                    *  FreeImage_FlipVertical(Dib);                        // 因为FreeImage的认为的图像的起点在左下角,不进行翻转则图像的倒过来的
                    *  IntPtr Bits = FreeImage_GetBits(Dib);               // 得到图像数据在内存中的地址
                    *  Bmp = new Bitmap(Width, Height, Stride, PF, Bits);  // 实际上调用的GdipCreateBitmapFromScan0函数从内存创建位图
                **/


                //方案2:
                IntPtr Bits = FreeImage_GetBits(Dib);
                Bmp = new Bitmap(Width, Height, Stride, PF, Bits);
                Bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);         // 调用GDI+自己的旋转函数
                if (Bpp <= 8)
                {
                    ColorPalette Pal = Bmp.Palette;                     //  设置调色板
                    RGBQUAD* GdiPal = FreeImage_GetPalette(Dib);
                    int ClrUsed = FreeImage_GetColorsUsed(Dib);
                    for (int I = 0; I < ClrUsed; I++)
                    {
                        Pal.Entries[I] = Color.FromArgb(255, GdiPal[I].Red, GdiPal[I].Green, GdiPal[I].Blue);
                    }
                    Bmp.Palette = Pal;
                }
                FreeImage_Free(Dib);                                        // 使用方案2则可以立马释放
                return Bmp;
            }
            return null;
        }
    }

上述代码中,我们对方案1为什么存在内存泄露做一定的说明。

      方案1中,Bmp = new Bitmap(Width, Height, Stride, PF, Bits)这条语句实际上调用了GDI+的函数GdipCreateBitmapFromScan0从内存创建位图,通过此种方式创建的位图并没有新分配一块内存给创建的位图,而是和Bits对应的内存绑定的。您可以用如下的代码验证这一点:

 BitmapData  BmpData = Bmp.LockBits(new Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.ReadWrite, Bmp.PixelFormat);
 if (BmpData.Scan0 == Bits ) 
        MessageBox.Show ("通过GDI+创建的图像和FreeImage的DIB对象公用同一内存.")
 Bmp.UnlockBits(BmpData);

正是由于这个原因的存在,如果采用方案1,我们不能在创建GDI+的位图后立马释放FreeImage的创建的DIB对象,即不能调用FreeImage_Free(Dib);如果调用了,对应的 Bmp对象实际上是个空对象了。

  这样的话也许可能没有关系,我们只要在适当的地方调用Bmp.Dispose,不就可以了吗,你可以做个试验,使用这段代码,然后不断的打开新图像,你会发现程序占用的内存会不断的增加,而没有释放。因此,我们看看MSDN对GdipCreateBitmapFromScan0这个函数是怎么解释的,特别是最后一个参数:

scan0 [in]

Type: BYTE*

Pointer to an array of bytes that contains the pixel data. The caller is responsible for allocating and freeing the block of memory pointed to by this parameter. 

      上述文字表示用户需要对分配的内存进行释放,也就是说Dispose方法无法释放该部分内存。

     有了上述的问题,我们转而使用方案2,方案2使用了一句Bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);这个语句会创建一副新的位图,也就是说进行旋转后的图像已经不再同FreeImage使用同一块内存了。那么此时就可以放心的释放掉FreeImage的DIB对象了。

      本以为RotateFlip函数会降低效率,测试表面微软对这个函数的执行效率还是很高的,其实这个函数的函数完全可以借助于CopyMemory函数来高速实现。

当图像的位深小于8时,需要获取调色板的数据。但是我对认为上述获取调色板的FreeImage_GetPalette函数存在内存泄露,无法释放这些RGBQUAD*分配的内存的。FreeImage应该考虑使用类似于GDI+中获取调色板数据那种方式。

  使用FreeImageNET.dll中提供的类库,则编写代码更为方便,推荐使用第二种方式,朋友们可以参考附件。

  实际上FreeImage还有很多强大的功能,比如色深转换、充分利用它洗看图软件,格式批处理那是很快捷方便的。

  附件中的拖动图像的方式我认为也是值得作为大家学习的。

http://files.cnblogs.com/Imageshop/FreeImage.rar

 http://files.cnblogs.com/Imageshop/FreeImageApi.rar

 ***************************作者: laviewpbt   时间: 2013.7.7   联系QQ:  33184777  转载请保留本行信息*************************

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小灰灰

cocos2dx-v3.4 2048(四):游戏逻辑的设计与实现

前言 ---- 2048的游戏逻辑比较简单,向四个方向移动单元格,若相邻的单元格数字相同,则合并成一个新的单元格,且数字为之前的两倍;若不同,则移动到目的方向上...

2616
来自专栏偏前端工程师的驿站

PLT:说说Evaluation strategy

Brief                                 在学习方法/函数时,我们总会接触到 按值传值 和 引用传值 两个概念。像C#是按值传...

1916
来自专栏刘望舒

Android绘制优化(一)绘制性能分析

前言 一个优秀的应用不仅仅是要有吸引人的功能和交互,同时在性能上也有很高的要求。运行Android系统的手机,虽然配置在不断的提升,但仍旧无法和PC相比,无法做...

1805
来自专栏SeanCheney的专栏

《Pandas Cookbook》第05章 布尔索引1. 计算布尔值统计信息2. 构建多个布尔条件3. 用布尔索引过滤4. 用标签索引代替布尔索引5. 用唯一和有序索引选取6. 观察股价7. 翻译SQ

第01章 Pandas基础 第02章 DataFrame运算 第03章 数据分析入门 第04章 选取数据子集 第05章 布尔索引 第06章 索引对齐 ...

522
来自专栏NetCore

Fluent NHibernate RC 1.0 --升级内容

Fluent NHiberante(FNT) RC 1.0 已经在上个星期发布了,其中很多东西被废弃,有些方法改进,还有一些命名更贴切,虽说不是很完美,但已经做...

1955
来自专栏叁金大数据

C#中Image , Bitmap 和 BitmapData

先说Image,Image 就是个图像,不能实例化,提供了位图和源文件操作的函数。本篇文章他就是来打酱油的,这里提供一个Bitmap转成BitmapSource...

572
来自专栏FD的专栏

Brainfuck JIT Compiler in Rust

我们都知道,对于解释型的语言实现来说,性能是大家关注的焦点。比如,这位 Tondbal ik Ni 曾经还说过:

933
来自专栏JadePeng的技术博客

HTML5录音控件

最近的项目又需要用到录音,年前有过调研,再次翻出来使用,这里做一个记录。 HTML5提供了录音支持,因此可以方便使用HTML5来录音,来实现录音、语音识别等功能...

4135
来自专栏wOw的Android小站

[Android] 使用MediaProjection截屏

Android5.0以上提供了MediaProjection,方便截屏录屏等功能。

611
来自专栏salesforce零基础学习

salesforce 零基础学习(六十四)页面初始化时实现DML操作

有的时候我们往往会遇到此种类似的需求:用户在访问某个详细的记录时,需要记录一下什么时候哪个用户访问过此页面,也就是说进入此页面时,需要插入一条记录到表中,表有用...

1978

扫码关注云+社区