VS2008 C++ GDAL读取TIFF并显示到MFC的完整实现
2026/9/9 17:21:09 网站建设 项目流程

简介:面向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++ + GDALTIFF 影像显示到 MFC 界面上。这个需求在测绘、GIS、水利、海事这类行业里特别常见——新系统用 C# / Python 做二次开发很方便,但大量已经跑了几年的遗留系统还是抱着 VC2008 不撒手,新模块又必须嵌进老框架里,于是 GDAL 就成了绕不开的选择。

先说结论:这件事本身不难,GDAL 负责解算 TIFF,GDI 负责把解算结果画到窗口上,中间只需要处理好数据格式转换即可。但难点在于: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++ 标准的支持停留在 C++03,新版本的 GDAL 源码里大量使用 C++11/14 的特性,比如autonullptr、智能指针、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 库有两种途径:

  1. 下载预编译的 Windows 版本(比如从 GIS 工具站或者官方源下载的gdal-1.10.1-win32包),里面包含头文件、gdal_i.lib导入库和gdal110.dll动态库。
  2. 自己用源码编译。编译需要安装NMAKE环境,进入 GDAL 源码目录后先修改nmake.opt里的编译配置,在 VS2008 命令行窗口执行nmake /f makefile.vc,大概要编译十几分钟。这种方式灵活性最高,可以自定义去掉不需要的驱动,但新手不推荐,容易在依赖项上栽跟头。

我使用的方式是第一种:直接把预编译包解压,里面会有binlibinclude三个文件夹,开发时只需要把路径指过去。

2.3 VS2008 工程配置的完整步骤

在 VS2008 中新建一个 MFC 对话框工程(工程名比如TiffViewer),然后做以下配置:

  1. 打开项目属性 -> C/C++ -> 常规 -> 附加包含目录,填入 GDAL 的include路径。
  2. 打开项目属性 -> 链接器 -> 常规 -> 附加库目录,填入 GDAL 的lib路径。
  3. 打开项目属性 -> 链接器 -> 输入 -> 附加依赖项,填入gdal_i.lib
  4. 注意字符集: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)占一整块,波段 2(G)占一整块,波段 3(B)占一整块,也就是所谓的 BSQ(Band Sequential)排列。而 Windows 显示时希望得到的是 BGR 交错排列,所以显示前需要做一个重排。

3.3 用 GDI 显示影像:DIB 位图的核心原理

Windows 下显示图像数据最直接的方式是使用 DIB(Device-Independent Bitmap,设备无关位图)。它本质上是一块内存,里面按固定格式存放像素数据,SetDIBitsToDeviceStretchDIBits可以直接把它绘制到窗口 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

排除文件路径本身不存在的情况后,最常见的两个原因:

  1. 没有调用GDALAllRegister()。GDAL 的驱动机制是注册制,不注册驱动,库就不知道如何解析 TIFF 文件。在程序最开始的位置调用一次,或者在上一个文件打开前调用。
  2. 动态库缺失。VS2008 编译的程序如果放在别的电脑上运行,别忘了把gdal110.dll等 GDAL 动态库拷贝到 exe 同目录或系统 PATH 里。否则程序编译通过,运行时报0xc0000135错误(无法找到 DLL)。

4.2 显示出来是黑屏或者花屏

黑屏通常代表数据读到了空值或全零,问题多半出在RasterIO的参数上。比如 TIFF 是 16 位数据,但代码里用了GDT_Byte,GDAL 做类型转换时会截断,超过 255 的数值全变成 255(白色),不是黑屏;只有null值区域才会黑。花屏则大概率是BITMAPINFO设置不对,检查biBitCount是否和实际数据匹配,以及biHeight的符号是否让图像上下颠倒。

还有一个容易忽略的点:GDAL 对 TIFF 的统计值使用缩放比例。如果影像本身带GDAL_SCALE元数据,比如高程影像,直接读出来的值可能是百分比或灰度值而不是真实值,需要调用GetScale()/GetOffset()做反算。普通卫星影像和数码相机输出的 TIFF 不受影响。

4.3 大 TIFF 文件加载太慢或者内存不足

大影像直接RasterIO读全图,内存占用是宽 × 高 × 字节数。以一张 1GB 的 TIFF 为例,如果读成 8 位灰度,内存约 1GB,如果读成 24 位 RGB,就是 3GB,很容易造成内存爆掉。更优的做法是分层显示:窗口有多少像素,就只读多少像素。

RasterIOnBufXSizenBufYSize参数可以实现这个功能。比如窗口宽度只有 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 再看业务代码。环境切换带来的变量太多,一口吃成胖子往往花费的时间远超预期。这就是我这次维护老系统的全部经验了,希望对你有所帮助。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询