因为要做DICOM的图像显示,我要从文件中读图像的像素数组(0-4095)到内存中,然后显示在界面上。
这个过程需要将Uint16的DICOM像素格式转成Uint8的Bitmap像素格式。也即是把0-4095的灰度值转成0-255的灰度值并转换成Bitmap。
我做了两个版本的函数,代码在最后。
其中的e:\1.txt是我处理过的DICOM文件,仅仅是把图像的宽和高放在文件首,然后后面跟着像素数据而已。
但是我的代码运行时我发现了一个神奇的现象:
C++的程序显示出来的图像非常难看,这个是我意料之中的,因为我仅仅是把像素数据除以16而已,如此简单的操作当然应该得到如此粗糙的结果。
可是C#的程序运行之后,图像显示得很正常!正常得有点不正常了……
所以我想请问一下大家:明明是同样的像素数据(我对比过内存的),为什么C#中显示出来的样子和C++中显示出来的样子有如此差别,难道C#在显示的时候做了些什么其它的工作么?又或者是我的代码的问题?又或者是Bitmap这个类的关系?总之很不理解,求真相。读取文件显示图像代码C#版如下:请不要在意我脑残的注释
           string fileName = @"e:\1.txt";         // 还是c#用着舒服啊,尤其是字符串,C++的那个能叫字符串?            // 用这个东西来读取文件数据
            FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);            byte[] bytes = new byte[4];            // 存放图像长和宽的数组            fs.Read(bytes, 0, 4);
            Int16 nWidth = (Int16)ToObject(bytes, typeof(Int16));   // 把维数为4的BYTE型数组转成Int16的数字
            fs.Read(bytes, 0, 4);
            Int16 nHeight = (Int16)ToObject(bytes, typeof(Int16));            Bitmap bmp = new Bitmap(nWidth, nHeight);            // 锁定图像对象的内存以进行读写,这个语句很常用。
            BitmapData data = bmp.LockBits(new Rectangle(0, 0, nWidth, nHeight), ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);            // 一个作为中转站的数组
            byte[] pixels = new byte[nWidth * nHeight*4];            fs.Read(pixels, 0, nWidth * nHeight * 4);                             // 读取文件内容            Marshal.Copy(pixels, 0, data.Scan0, nWidth * nHeight * 4); // 拷贝到图像对象的内存中。            bmp.UnlockBits(data);           // 显示图像,这个就没什么了。
            panel1.BackgroundImageLayout = ImageLayout.Stretch;
            panel1.BackgroundImage = bmp;以及C++版,同样请不要在意注释……
UINT16 nWidth;
UINT16 nHeight;CString fileName = "e:\\1.txt";
ifstream fileRead(fileName, ios::binary);                    // 输入二进制流BYTE* pData = (BYTE*)malloc(sizeof(UINT16)*4);    // 缓存fileRead.read((char*)pData, sizeof(UINT16)*4);         // 读取文件数据nWidth = *(((UINT16*)pData));                                    // 前8个字节是图像的宽和高
nHeight= *(((UINT16*)pData)+2);                                // 但是为什么这里要加2……free(pData);     // C++ 的一个大问题就是一定要随时随地释放内存……pData = (BYTE*)malloc(sizeof(BYTE)*nWidth*nHeight*4); // 像素数据的缓存fileRead.read((char*)pData, sizeof(BYTE)*nWidth*nHeight*4);   // 读取像素数据fileRead.close();     // 用完了要记得把输入流关闭,否则这个文件就再也打不开了……LPBITMAPINFOHEADER m_lpBMIH;    // 这个结构是C++用来显示图像的,常用。
// 下面是这个结构的一般初始化方式
m_lpBMIH = (LPBITMAPINFOHEADER) new char
                               [sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256]; // 分配内存
m_lpBMIH->biSize = sizeof(BITMAPINFOHEADER);      // 这个值用于标记所占用内存的长度,常见。
m_lpBMIH->biWidth = nWidth;                                       // 图像的宽度,不是扫描线宽度
m_lpBMIH->biHeight = nHeight;                                    // 图像的高度。
m_lpBMIH->biBitCount = 32;                                        // 像素格式,32表示RGBA,也可以取24、16等值
m_lpBMIH->biPlanes = 1;                                              // 不用理会,直接赋这个值就好。
m_lpBMIH->biCompression = BI_RGB;                         // 不用理会,直接赋这个值就好。
m_lpBMIH->biSizeImage = 0;                                        // 不用理会,直接赋这个值就好。 
m_lpBMIH->biXPelsPerMeter = 0;                                // 不用理会,直接赋这个值就好。
m_lpBMIH->biYPelsPerMeter = 0;                                // 不用理会,直接赋这个值就好。void* pPixelData = (void*)pData;    // 显示时要求使用VOID型的指针,这里转换一下看起来清楚一些// 得到窗口,C#里一个PANEL搞定的问题在C++里就这么麻烦……
CWnd* pWnd = this->GetDlgItem(IDC_PictureBox);   // 窗口指针。IDC_PictureBox就是某个控件的ID啦
CDC* pDc = pWnd->GetDC();                                   // 窗口的显示上下文(表问我这是什么……)
HDC hdc = pDc->GetSafeHdc();                               // 那个“上下文”的句柄(也表问我这是什么……)
CRect destRect;                                                        // 窗口对应的矩形
pWnd->GetWindowRect(&destRect);                        // 得到这个矩形……(麻烦啊……)
int destWidth = destRect.Width();                             // 窗口的宽度和高度
int destHeight = destRect.Height();
// 绘图函数,C#里只需要告诉控件:把图给我画出来,而C++就得手把手的告诉控件这图怎么画……
StretchDIBits( hdc,                                      // 就是那个“上下文”的句柄啦
   0, 0,destWidth, destHeight,                     // 画图的矩形范围,我们传给它坐标原点和窗口矩形的长与宽
   0, 0, nWidth, nHeight,                              // 数据的矩形范围,我们传给它坐标原点和图像的长与宽
   pPixelData,                                             // 像素数据的指针,这个是VOID*型的
   (LPBITMAPINFO)m_lpBMIH,                   // 在前面初始化过的绘图参数结构
   DIB_RGB_COLORS, SRCCOPY);          // 这两个值指示如何绘图,一般不用去变free(pData);           // 画完了图要把内存释放掉……

解决方案 »

  1.   

    如果大家在代码中发现了某些未声明的变量之类的,请谅解,我是直接从程序中Copy出来的,但是所有的变量应该都是正常值。
      

  2.   

    关键就在这:
    BitmapData data = bmp.LockBits(new Rectangle(0, 0, nWidth, nHeight), ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
      

  3.   

    你把两个图都按BITMAP保存出来看看..应该是一样的记忆中StretchDIBits 会改变图形的
      

  4.   

    BitmapData data = bmp.LockBits(new Rectangle(0, 0, nWidth, nHeight), ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb); 
    改成这个试试:
    BitmapData data = bmp.LockBits(new Rectangle(0, 0, nWidth, nHeight), ImageLockMode.ReadWrite, PixelFormat.Format16bppRgb); 
      

  5.   

    下面是C++和C#的代码显示出来的图片,C++的明显难看许多。
      

  6.   

    bitmap -> int [,,];
    int [,,] -> image;
      

  7.   


    可是我的问题是:C#的图是优化过的。
    其实我只是想在C++里也这样优化一下。
    然后如果能知道C#里是怎么优化的就再好不过了。