拓冰建站拓冰建站
首页 / 资讯中心 / 正文

VS2008 C++ GDAL读取TIFF并显示到MFC的完整实现

简介面向C开发者及GIS入门者的VS2008平台GDAL库示例工程演示如何读取并显示TIFF遥感影像。从获取GDAL驱动器、打开数据集、读取栅格波段到像素数据绘制与资源关闭完整展现GDAL处理地理空间数据的基本流程适合需要快速上手GDAL或搭建影像显示原型的读者参考。资源包共45个文件约14.31MB包含h/cpp源码、sln/vcproj工程配置、res/ico等界面资源同时保留obj/pdb/exe等编译输出与可执行程序便于直接运行和对比学习。目前已有800人学习下载。该小程序还涉及色彩解释与ReadRaster读取等关键API可继续向图像裁剪、重采样、坐标转换等方向扩展附有ReadMe说明代码结构完整是结合C与GDAL开展GIS二次开发的良好起点。1. 项目概述与总体思路最近在维护一个老项目需要用VS2008 C GDAL把TIFF 影像显示到 MFC 界面上。这个需求在测绘、GIS、水利、海事这类行业里特别常见——新系统用 C# / Python 做二次开发很方便但大量已经跑了几年的遗留系统还是抱着 VC2008 不撒手新模块又必须嵌进老框架里于是 GDAL 就成了绕不开的选择。先说结论这件事本身不难GDAL 负责解算 TIFFGDI 负责把解算结果画到窗口上中间只需要处理好数据格式转换即可。但难点在于VS2008 太老GDAL 的版本选择、字符集配置、库文件引入方式、DIB 位图结构这些细节每一步都可能让新手卡住好几个小时。这篇文章我把完整流程、核心代码和自己的踩坑经历全部整理出来适合正在维护老系统、或者做毕业设计选了 VS2008 GDAL 方向的读者直接参考。需要提前说明的是我这里使用的 GDAL 是比较老的 1.x / 2.x 版本VS2008 环境下不建议上新版本原因后面会讲开发环境是 32 位 x86系统为 Windows。如果你的项目是 64 位思路一样只是库文件要替换成 64 位版本。2. 环境搭建VS2008 与 GDAL 的组合要点2.1 为什么 GDAL 版本不能盲目追新GDAL 一直在迭代3.x 版本早就支持了 VS2015 甚至更高版本的编译工具链。问题在于VS2008 对 C 标准的支持停留在 C03新版本的 GDAL 源码里大量使用 C11/14 的特性比如auto、nullptr、智能指针、std::unique_ptr等拿到 VS2008 里编译会直接报一堆语法错误而且错误信息非常不友好改起来等于重写。所以我的建议是用 GDAL 1.9.x / 1.10.x或者 2.0.x 早期版本。这些版本在 VS2008 下编译没有障碍API 也足够稳定。实测下来 1.10.1 这个版本最顺手绝大多数功能已经具备老系统用完全够。如果你只是显示 TIFF连影像金字塔、空间参考之类的功能都用不上1.x 完全能胜任。2.2 获取 GDAL 库的两种方式获取 GDAL 库有两种途径下载预编译的 Windows 版本比如从 GIS 工具站或者官方源下载的gdal-1.10.1-win32包里面包含头文件、gdal_i.lib导入库和gdal110.dll动态库。自己用源码编译。编译需要安装NMAKE环境进入 GDAL 源码目录后先修改nmake.opt里的编译配置在 VS2008 命令行窗口执行nmake /f makefile.vc大概要编译十几分钟。这种方式灵活性最高可以自定义去掉不需要的驱动但新手不推荐容易在依赖项上栽跟头。我使用的方式是第一种直接把预编译包解压里面会有bin、lib、include三个文件夹开发时只需要把路径指过去。2.3 VS2008 工程配置的完整步骤在 VS2008 中新建一个 MFC 对话框工程工程名比如TiffViewer然后做以下配置打开项目属性 - C/C - 常规 - 附加包含目录填入 GDAL 的include路径。打开项目属性 - 链接器 - 常规 - 附加库目录填入 GDAL 的lib路径。打开项目属性 - 链接器 - 输入 - 附加依赖项填入gdal_i.lib。注意字符集GDAL 1.x 的 C API 默认是 ANSI 编码VS2008 默认也兼容但推荐将项目字符集设为“使用多字节字符集”避免 UNICODE 和 ANSI 之间来回转换的麻烦。如果非要用 Unicode调用GDALOpen时得显式处理字符串转换。注意VS2008 的“解决方案配置”一定要选 x86或 Win32因为 32 位 GDAL 库无法链接到 64 位程序反之亦然。这个错误在链接阶段会提示unresolved external symbol很多人误以为是自己代码写错了其实是位数不匹配。配置完成后建议写在stdafx.h里统一引入 GDAL 头文件#include gdal_priv.h #include cpl_conv.h #pragma comment(lib, gdal_i.lib)#pragma comment可以省去每次手动设置附加依赖项的步骤但前提是lib路径已经在链接器的附加库目录里否则编译器找不到。3. 核心代码实现读取 TIFF 并显示到界面3.1 GDAL 初始化与打开 TIFF 文件GDAL 在使用前必须注册驱动这一步有很多人忘记写结果一调用GDALOpen就返回空指针然后误以为函数用错了其实是初始化没做。#include gdal_priv.h // 在程序初始化时调用一次 GDALAllRegister();打开 TIFF 文件的标准写法GDALDataset* poDataset NULL; poDataset (GDALDataset*)GDALOpen(D:\\test\\demo.tif, GA_ReadOnly); if (poDataset NULL) { AfxMessageBox(_T(打开 TIFF 文件失败)); return; } // 获取影像基本信息 int nWidth poDataset-GetRasterXSize(); // 列数即宽度 int nHeight poDataset-GetRasterYSize(); // 行数即高度 int nBandCount poDataset-GetRasterCount(); // 波段数这里有个细节GDALOpen的第一个参数是文件路径如果项目设置为多字节字符集可以直接传CString转出来的char*如果是 Unicode 字符集需要用CW2A宏做一次转换否则路径里包含中文时可能打不开。3.2 读取图像数据到内存缓冲区TIFF 影像通常分为三类8 位灰度图、24 位真彩色图RGB 三个波段、带调色板的索引色图。针对不同格式读取和显示的方式都不一样这里先讲最通用的 24 位真彩色图的读取流程。int nBands poDataset-GetRasterCount(); // 8 位灰度图波段数为 1 // 24 位真彩色图波段数为 3 // 还有可能是 16 位高程数据或 32 位浮点数据后面扩展部分再讲 GDALRasterBand* poBand NULL; BYTE* pData new BYTE[nWidth * nHeight * nBands]; for (int i 0; i nBands; i) { poBand poDataset-GetRasterBand(i 1); poBand-RasterIO(GF_Read, 0, 0, nWidth, nHeight, pData i * nWidth * nHeight, nWidth, nHeight, GDT_Byte, 0, 0); }RasterIO的参数很多但核心逻辑只有几个GF_Read表示读取0, 0, nWidth, nHeight表示整幅影像都读后面跟着的是输出缓冲区和目标尺寸GDT_Byte表示读取为 8 位无符号字节GDAL 会自动把源数据转换成这个类型。这正是 GDAL 最方便的地方——不用关心 TIFF 底层的压缩和存储格式它帮你全部处理好了。读取完后pData中的数据顺序是波段 1通常是 R占一整块波段 2G占一整块波段 3B占一整块也就是所谓的 BSQBand Sequential排列。而 Windows 显示时希望得到的是 BGR 交错排列所以显示前需要做一个重排。3.3 用 GDI 显示影像DIB 位图的核心原理Windows 下显示图像数据最直接的方式是使用 DIBDevice-Independent Bitmap设备无关位图。它本质上是一块内存里面按固定格式存放像素数据SetDIBitsToDevice或StretchDIBits可以直接把它绘制到窗口 DC 上。DIB 的像素格式由BITMAPINFO结构体描述其中biBitCount 32是最方便的形式——每个像素占 4 字节按 BGRA 顺序排列且每一行天然是 4 字节对齐不需要额外计算行填充。相比 24 位位图需要按行对齐的麻烦32 位省了很多事代价只是内存占用稍大一点显示当前窗口完全可忽略。显示代码BITMAPINFO bmi; memset(bmi, 0, sizeof(BITMAPINFO)); bmi.bmiHeader.biSize sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth nWidth; bmi.bmiHeader.biHeight -nHeight; // 负值表示自顶向下存储 bmi.bmiHeader.biPlanes 1; bmi.bmiHeader.biBitCount 32; bmi.bmiHeader.biCompression BI_RGB; // pDataBGRA 是重排后的数据长度为 nWidth * nHeight * 4 StretchDIBits(pDC-GetSafeHdc(), 0, 0, nDestWidth, nDestHeight, 0, 0, nWidth, nHeight, pDataBGRA, bmi, DIB_RGB_COLORS, SRCCOPY);biHeight设为负值很关键。Windows 里位图第 0 行在“数学坐标系”中是从底部开始的但图像数据习惯从顶部开始存储直接设负号可以让数据显示为正立不用再去翻转数据这是很多人容易踩坑的地方。3.4 三波段数据重排与显示从 GDAL 读出来的三波段数据是 BSQ 排列显示需要 BGR 交错排列。这个重排过程可以直接在循环里完成也可以调用CPLStridedCopy这类 GDAL 辅助函数但手写循环最简单直观BYTE* pDataBGRA new BYTE[nWidth * nHeight * 4]; BYTE* pR pData; // 波段1 BYTE* pG pData nWidth * nHeight; // 波段2 BYTE* pB pData 2 * nWidth * nHeight; // 波段3 int nIndex 0; for (int i 0; i nWidth * nHeight; i) { pDataBGRA[nIndex] pB[i]; // B pDataBGRA[nIndex] pG[i]; // G pDataBGRA[nIndex] pR[i]; // R pDataBGRA[nIndex] 0; // Alpha设为0即可 }注意 GDAL 的波段顺序是 R、G、B而 Windows 32 位 DIB 的像素顺序是 B、G、R、A两者的顺序恰好相反不做转换的话显示出来红蓝通道会互换画面颜色完全不对特别是红色地面会变成蓝色。这也是一个常见显示异常。4. 常见问题与排查实录4.1 打开 TIFF 失败GDALOpen 返回 NULL排除文件路径本身不存在的情况后最常见的两个原因没有调用GDALAllRegister()。GDAL 的驱动机制是注册制不注册驱动库就不知道如何解析 TIFF 文件。在程序最开始的位置调用一次或者在上一个文件打开前调用。动态库缺失。VS2008 编译的程序如果放在别的电脑上运行别忘了把gdal110.dll等 GDAL 动态库拷贝到 exe 同目录或系统 PATH 里。否则程序编译通过运行时报0xc0000135错误无法找到 DLL。4.2 显示出来是黑屏或者花屏黑屏通常代表数据读到了空值或全零问题多半出在RasterIO的参数上。比如 TIFF 是 16 位数据但代码里用了GDT_ByteGDAL 做类型转换时会截断超过 255 的数值全变成 255白色不是黑屏只有null值区域才会黑。花屏则大概率是BITMAPINFO设置不对检查biBitCount是否和实际数据匹配以及biHeight的符号是否让图像上下颠倒。还有一个容易忽略的点GDAL 对 TIFF 的统计值使用缩放比例。如果影像本身带GDAL_SCALE元数据比如高程影像直接读出来的值可能是百分比或灰度值而不是真实值需要调用GetScale()/GetOffset()做反算。普通卫星影像和数码相机输出的 TIFF 不受影响。4.3 大 TIFF 文件加载太慢或者内存不足大影像直接RasterIO读全图内存占用是宽 × 高 × 字节数。以一张 1GB 的 TIFF 为例如果读成 8 位灰度内存约 1GB如果读成 24 位 RGB就是 3GB很容易造成内存爆掉。更优的做法是分层显示窗口有多少像素就只读多少像素。RasterIO的nBufXSize和nBufYSize参数可以实现这个功能。比如窗口宽度只有 1024可以读出一个 1024 × 768 的数据块具体写法int nViewWidth 1024; // 目标缓冲区宽 int nViewHeight 768; // 目标缓冲区高 BYTE* pViewData new BYTE[nViewWidth * nViewHeight]; poBand-RasterIO(GF_Read, 0, 0, nWidth, nHeight, pViewData, nViewWidth, nViewHeight, GDT_Byte, 0, 0);GDAL 内置了影像金字塔和重采样算法会自动从原始数据中抽稀速度比读全图快很多。如果只需要看缩略图这种办法是首选。4.4 单波段灰度图的显示单波段灰度图的显示更简单不需要重排直接把灰度数据当作 8 位 DIB 显示即可bmi.bmiHeader.biBitCount 8; bmi.bmiHeader.biClrUsed 256; // 初始化调色板为灰度渐变 for (int i 0; i 256; i) { bmi.bmiColors[i].rgbRed i; bmi.bmiColors[i].rgbGreen i; bmi.bmiColors[i].rgbBlue i; } StretchDIBits(pDC-GetSafeHdc(), ...);如果图片显示出来偏色、完全模糊或者过暗多半是调色板没初始化。8 位 DIB 必须提供调色板否则系统用默认调色板显示完全不是原来的样子。4.5 把 GDAL 显示移植到 OpenGL / GDI有些项目不是用 GDI 而是 OpenGL 或 GDI 显示GDAL 读出来的数据接入方式也会不同。GDI 里可以用Bitmap(nWidth, nHeight, nWidth * 4, PixelFormat32bppRGB, pDataBGRA)直接把内存数据封装成 Bitmap不需要再用 DIB代码更简洁。OpenGL 则用glTexImage2D需要特别注意数据是 RGB 还是 BGR以及行对齐GL_UNPACK_ALIGNMENT的问题。核心思路是一样的GDAL 只负责从 TIFF 中提取原始像素屏幕上的呈现方式完全由你的显示框架决定。5. 容易忽略的实用经验与扩展方向5.1 坐标信息的读取与显示TIFF 作为地理空间数据往往带有地理参考信息比如经纬度范围、像素分辨率等。这些信息对很多行业应用非常关键比如鼠标移动时显示当前点对应的地理坐标。读取方法是double adfGeoTransform[6]; poDataset-GetGeoTransform(adfGeoTransform);adfGeoTransform是一个数组其中第 0 个和第 3 个元素分别是左上角点的 X一般对应经度或东向和 Y一般对应纬度或北向第 1 个和第 5 个元素是像素宽度和高度。结合鼠标位置用简单的线性公式就能算出地理坐标double geoX adfGeoTransform[0] pixelX * adfGeoTransform[1]; double geoY adfGeoTransform[3] pixelY * adfGeoTransform[5];这里要注意 Y 方向一般是负的代表影像行向下而坐标向北如果忽略符号算出来的纬度完全反了这是新人特别容易犯的错。5.2 影像拉伸显示的两种布局默认的显示方式把整幅影像缩放到客户区相当于从左上角到右下角“塞进”窗口。在实际项目中更多的情况是分块漫游类似地图浏览器的形式。实现上可以用StretchDIBits加上滚动条但更优雅的做法是把当前视口中心换算成影像像素坐标再调用RasterIO读取对应分块。这样做之后无论影像多大读取速度都稳定内存占用也不会有波动。还要考虑视口缩放时的重采样问题缩小显示时直接读大图GDAL 内部会做重采样细节虽然丢失但整体轮廓清晰放大显示时则按窗口尺寸计算需要读取的像素区域避免超出实际像素范围。5.3 对影像做简单的增强处理TIFF 卫星影像通常存在对比度不足的问题直接显示灰蒙蒙的。一个简单的线性拉伸就能显著改善效果读取整幅影像的直方图找到最小值和最大值然后做一个线性映射到 0-255。如果不想遍历全图可以设置一个百分比比如 2% 和 98%把两端异常值截掉这样显示效果更贴近人眼感知。实现上可以在RasterIO之后对缓冲区数据做一次映射用一个循环完成几毫秒就处理完了。这个增强处理后影像的视觉可读性会明显提升尤其适合做演示和成果汇报。6. 最终实操心得如果你从零开始做 VS2008 GDAL TIFF 显示按照“配置环境 - 测试 GDALOpen - 单波段灰度显示 - 三波段 RGB 显示”这条路线走最稳妥。先把最简单的 8 位灰度图跑通确认环境没问题再处理彩色图问题排查范围会小很多。直接一上来就啃 24 位真彩色一旦画面颜色不对你会分不清是环境问题还是数据排列问题。我在实际维护老系统过程中还有一个领悟VS2008 环境下不要放过任何一条编译器警告。GDAL 的库很成熟但老编译器能给出的提示有限很多潜在的错误直到运行时才暴露。比如char*与CString的隐式转换在 DEBUG 模式下可能崩得很隐蔽Release 下反而能跑但这种坑最好在平时就消灭掉。如果你后续考虑升级到新版本 GDAL或者切换到 64 位程序强烈建议先跑通一个最小 Demo 再看业务代码。环境切换带来的变量太多一口吃成胖子往往花费的时间远超预期。这就是我这次维护老系统的全部经验了希望对你有所帮助。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门