基于dcmtk的dicom工具 第十章 读取dicom文件图像数据并显示

文章目录


前言

本章介绍使用dcmtk解析dicom文件的方法。

  1. DcmFileFormat打开文件
  2. DcmDataset读取TAG,获取文字信息
  3. DicomImage读取图像数据,并生成显示用位图数据

程序界面还是使用简单的MFC对话框。界面左边的文件加载区,不再赘述,参考基于dcmtk的dicom工具 第二章 dicom文件tag读取与修改工具

效果如下:


一、程序中的类介绍

tree 复制代码
└── CDcmImageDlg  对话框界面类
		└── CDisplayer 图像显示窗口类,从CWnd派生
      			├── CDicomImage(CBaseImage) 数据接口类
      			│		└── DcmParser 解析器类
      			└── DrawParam 绘制参数类

1. 解析器类DcmParser

  1. Open函数,调用DcmFileFormat打开dicom文件
  2. GetTagValue, GetTagStringValue, GetTagIntValue, GetTagFloatValue,四个函数中调用DcmDataset类的findAndGetOFString函数读取Dicom Tag值
  3. CreateDIB函数,根据DcmFileFormat::getDataset()创建DicomImage对象处理图像数据,可设置窗宽窗位,负像、实现图像缩放、旋转、翻转等功能,最后调用DicomImage::createWindowsDIB生成可用于绘制的位图数据。
  4. Export函数,实现把dicom文件转换成其他格式的图像,如bmp、jpg、png、tiff、mp4等格式,此函数在后续章节中介绍。

2. 数据接口类CBaseImage, CDicomImage

  1. 父类CBaseImage定义接口,方便加载多种类型图像,如jpg, png, dicom
  2. CDicomImage类,从CBaseImage派生,包含一个DcmParser类对象,专门读取dicom文件,ParseFile函数解析文件,患者信息保存到PatientInfo、检查信息保存到StudyInfo、序列信息保存到SeriesInfo、图像信息保存到ImageInfo。CreateDIB函数 生成位图数据
  3. 后续如果要加载jpg、png,可以从CBaseImage派生一个CColorImage类来读取jpg、png等图像,并从与jpg、png图像关联的文本文件中加载检查信息、检查信息、检查信息、图像信息。本章不涉及此功能。

3. 图像显示窗口类Displayer

从MFC CWnd派生,包含CBaseImage类,调用CDicomImage类读取图像信息,位图数据,最后显示图像、文字。并接受鼠标消息,进行调窗、缩放、旋转等操作。

4. 绘制参数类DrawParam

记录Displayer类当前绘制状态,如图像大小,缩放系数、窗宽窗位、旋转角度等。

5. 界面对话框类CDcmImageDlg

包含Displayer类显示图像、文字。

包含DrawParam类记录当前绘制状态。

文件加载区加载dicom文件,操作按钮区添加各类功能

二、代码

1. DcmParser类

简要描述

重点 DcmParser类中Open函数,CreateDIB函数中已经处理了单帧图和多帧图

只需要在调用CreateDIB时,指定第六个参数frame即可获取某一帧的位图数据。

头文件DcmParser.h:

cpp 复制代码
#pragma once

class DcmParser
{
public:
	DcmParser();
	virtual ~DcmParser();

public:
	static void RegistryCodecs();
	BOOL Open(std::string dcmfile);
	BOOL Close();
	std::string GetFilePath() { return m_Path; }
	BOOL IsValid() { return m_bParserValid; }
	BOOL HasImage() { return m_hasImage; }
	int GetFrameCount() { return m_nFrameCount; }

	CString GetTagValue(unsigned short g, unsigned short e, unsigned long pos=0);
	std::string GetTagStringValue(unsigned short g, unsigned short e, unsigned long pos = 0);
	int GetTagIntValue(unsigned short g, unsigned short e, unsigned long pos = 0);
	float GetTagFloatValue(unsigned short g, unsigned short e, unsigned long pos = 0);

	void GetDefaultWindow(double& defWC, double& defWW);

	// 
	/** 根据参数生成位图数据
	@param pdib, out,返回位图数据指针,由调用者管理
	@param w, h, out,返回图像宽高
	@param wc, ww, in, 指定窗宽窗位
	@param frame, in, 指定帧数, 索引从0开始
	@param bneg, in, 指定是否负像
	*/
	BOOL CreateDIB(void*& pdib, int& w, int& h, double wc, double ww, int frame=0, bool bneg = false);

	bool Export(std::string dst, int format);
	int  SaveToTiff(DicomImage* di, std::string out, int frame);

	BOOL IsRGB();

private:
	DcmFileFormat m_dcmFile;
	DicomImage* m_pDcmImg;
	E_TransferSyntax m_newXfer;
	E_TransferSyntax m_orgXfer;
	std::string m_Path;
	BOOL   m_bParserValid;
	BOOL   m_hasImage;
	int    m_nFrameStart;
	int    m_nFrameCount;
	int    m_nLoadCount;

	OFString m_PhotometricOrig;

	double m_defWC;
	double m_defWW;
	DicomImage* createImage(int frame);
};

源文件DcmParser.cpp

cpp 复制代码
#include "pch.h"
#include "DcmParser.h"
#include "Utilities.h"
#include "tiffio.h"
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>

#define WIDTHBYTES(bits) ((DWORD)(((bits)+31) & (~31)) / 8)

DcmParser::DcmParser()
	: m_bParserValid(FALSE)
	, m_hasImage(FALSE)
	, m_pDcmImg(nullptr)
	, m_defWC(40.0)
	, m_defWW(400.0)
	, m_nFrameCount(1)
	, m_nFrameStart(0)
	, m_nLoadCount(1)
{
}


DcmParser::~DcmParser()
{
	Close();
}

void DcmParser::RegistryCodecs()
{
	DJDecoderRegistration::registerCodecs();
	DJLSDecoderRegistration::registerCodecs();
	DcmRLEDecoderRegistration::registerCodecs();

	DcmRLEEncoderRegistration::registerCodecs();
	DJEncoderRegistration::registerCodecs();
	DJLSEncoderRegistration::registerCodecs();

}

BOOL DcmParser::Open(std::string dcmfile)
{
	if (m_bParserValid)
		return TRUE;

	dcmAcceptUnexpectedImplicitEncoding.set(OFFalse);
	dcmPreferVRFromDataDictionary.set(OFFalse);

	OFCondition cond;
	cond = m_dcmFile.loadFile(dcmfile.c_str());
	
	if (cond.bad())
		return FALSE;

	DcmDataset* pDataset = m_dcmFile.getDataset();

	Sint32 nFrameCount;
	pDataset->findAndGetSint32(DCM_NumberOfFrames, nFrameCount);

	if (nFrameCount == 0) m_nFrameCount = 1;
	else m_nFrameCount = nFrameCount;

	m_orgXfer = m_newXfer = pDataset->getOriginalXfer();
	DcmXfer xfer(m_newXfer);

	if (xfer.usesEncapsulatedFormat() && m_nFrameCount==1)
	{
		m_newXfer = EXS_LittleEndianExplicit;
		cond = pDataset->chooseRepresentation((E_TransferSyntax)m_newXfer, NULL);
	}
		
	//OFString PhotometricOrig;
	pDataset->findAndGetOFString(DCM_PhotometricInterpretation, m_PhotometricOrig);


	OFCondition wwConf;
	wwConf = pDataset->findAndGetFloat64(DCM_WindowCenter, m_defWC);
	wwConf = pDataset->findAndGetFloat64(DCM_WindowWidth, m_defWW);

	if (wwConf.bad() && m_PhotometricOrig == "RGB")
	{
		m_defWC = 128;
		m_defWW = 256;
		wwConf = EC_Normal;
	}

	m_Path = dcmfile;
	m_bParserValid = TRUE;
	m_hasImage = TRUE;

	if (m_nFrameCount == 1) 
	{
		m_pDcmImg = new DicomImage(pDataset, m_newXfer);
	}
	else 
	{
		m_nLoadCount = m_nFrameCount > 5 ? 5 : m_nFrameCount;
		m_nFrameStart = 0;
		unsigned long flag = CIF_UsePartialAccessToPixelData;
		m_pDcmImg = new DicomImage(pDataset, m_orgXfer, flag, m_nFrameStart, m_nLoadCount);
	}

	if (m_pDcmImg->getStatus() != EIS_Normal)
		m_hasImage = FALSE;

	if (wwConf.bad() && m_nFrameCount > 1) {
		DcmItem *pPerFrameItem = NULL;
		OFString defWinCenter, defWinWidth;
		cond = pDataset->findAndGetSequenceItem(DCM_SharedFunctionalGroupsSequence, pPerFrameItem, 0);
		if (pPerFrameItem)
		{
			DcmItem* pPlanePositionItem = NULL, *pPlaneOrientationItem = NULL, *pVoiLutSeqItem = NULL, *pPixelValueTransformation = NULL,
				*pPixelMeasureSeqItem = NULL;

			cond = pPerFrameItem->findAndGetSequenceItem(DCM_FrameVOILUTSequence, pVoiLutSeqItem, 0);
			if (pVoiLutSeqItem)
			{
				cond = pVoiLutSeqItem->findAndGetOFString(DCM_WindowCenter, defWinCenter, 0);
				cond = pVoiLutSeqItem->findAndGetOFString(DCM_WindowWidth, defWinWidth, 0);
				m_defWC = atof(defWinCenter.c_str());
				m_defWW = atof(defWinWidth.c_str());
				wwConf = EC_Normal;
			}
		}
	}

	if (wwConf.bad() && m_pDcmImg->isMonochrome())
	{
		double min, max;
		m_pDcmImg->getMinMaxValues(min, max);
		m_defWC = (max - min + 1) / 2.0 + min;
		m_defWW = max - min + 1;
		wwConf = EC_Normal;
	}

	return m_bParserValid;
}


BOOL DcmParser::Close()
{
	OFCondition cond;
	cond = m_dcmFile.clear();
	
	if (m_pDcmImg)
	{
		delete m_pDcmImg;
		m_pDcmImg = nullptr;
	}

	m_bParserValid = FALSE;
	return cond.good() ? TRUE : FALSE;
}

CString DcmParser::GetTagValue(unsigned short g, unsigned short e, unsigned long pos/*=0*/)
{
	if (!m_bParserValid)
		return _T("");

	OFString val;

	DcmDataset* pDataset = m_dcmFile.getDataset();
	DcmMetaInfo* pMetaInfo = m_dcmFile.getMetaInfo();

	if (g == 0x0002)
		pMetaInfo->findAndGetOFString(DcmTagKey(g, e), val, pos);
	else
		pDataset->findAndGetOFString(DcmTagKey(g, e), val, pos);

	return val.c_str();
}

std::string DcmParser::GetTagStringValue(unsigned short g, unsigned short e, unsigned long pos /*= 0*/)
{
	if (!m_bParserValid)
		return "";

	OFString val;

	DcmDataset* pDataset = m_dcmFile.getDataset();
	DcmMetaInfo* pMetaInfo = m_dcmFile.getMetaInfo();

	if (g == 0x0002)
		pMetaInfo->findAndGetOFString(DcmTagKey(g, e), val, pos);
	else
		pDataset->findAndGetOFString(DcmTagKey(g, e), val, pos);

	return val.c_str();
}

int DcmParser::GetTagIntValue(unsigned short g, unsigned short e, unsigned long pos /*= 0*/)
{
	if (!m_bParserValid)
		return 0;

	OFString val;

	DcmDataset* pDataset = m_dcmFile.getDataset();
	DcmMetaInfo* pMetaInfo = m_dcmFile.getMetaInfo();

	if (g == 0x0002)
		pMetaInfo->findAndGetOFString(DcmTagKey(g, e), val, pos);
	else
		pDataset->findAndGetOFString(DcmTagKey(g, e), val, pos);

	if (!val.empty())
		return atoi(val.c_str());

	return 0;
}

float DcmParser::GetTagFloatValue(unsigned short g, unsigned short e, unsigned long pos /*= 0*/)
{
	if (!m_bParserValid)
		return 0.0;

	OFString val;

	DcmDataset* pDataset = m_dcmFile.getDataset();
	DcmMetaInfo* pMetaInfo = m_dcmFile.getMetaInfo();

	if (g == 0x0002)
		pMetaInfo->findAndGetOFString(DcmTagKey(g, e), val, pos);
	else
		pDataset->findAndGetOFString(DcmTagKey(g, e), val, pos);

	if (!val.empty())
		return atof(val.c_str());

	return 0.0;
}


void DcmParser::GetDefaultWindow(double& defWC, double& defWW)
{
	defWC = m_defWC;
	defWW = m_defWW;
}

BOOL DcmParser::CreateDIB(void*& pdib, int& w, int& h, double wc, double ww, int frame/* = 0*/, bool bneg/* = false*/)
{
	DcmDataset* pDataset = m_dcmFile.getDataset();

	int fend = m_nFrameStart + m_nLoadCount - 1;
	if (!(frame >= m_nFrameStart && frame <= fend)) 
	{
		m_nFrameStart = frame / m_nLoadCount * m_nLoadCount;
		delete m_pDcmImg;

		unsigned long flag = CIF_UsePartialAccessToPixelData;
		m_pDcmImg = new DicomImage(pDataset, m_orgXfer, flag, m_nFrameStart, m_nLoadCount);
	}

	m_pDcmImg->setWindow(wc, ww);
	DicomImage* di = m_pDcmImg;

	bool bReverse = false;
	OFString Presentation;
	pDataset->findAndGetOFString(DCM_PresentationLUTShape, Presentation);

	EP_Polarity p = di->getPolarity();
	ES_PresentationLut esp = di->getPresentationLutShape();
	EP_Interpretation epi = di->getPhotometricInterpretation();


	int size = 0;
	if (di)
	{
		if ((Presentation.compare("INVERSE") == 0 && di->getPhotometricInterpretation() == EPI_Monochrome2)
			|| (Presentation.compare("IDENTITY") == 0 && di->getPhotometricInterpretation() == EPI_Monochrome1))
			bReverse = true;

		// 负像
		if (bneg)
		{
			if (bReverse)
				di->setPolarity(EPP_Normal);
			else
				di->setPolarity(EPP_Reverse);
		}
		else
		{
			if (bReverse)
				di->setPolarity(EPP_Reverse);
			else
				di->setPolarity(EPP_Normal);
		}

		w = di->getWidth();
		h = di->getHeight();
		size = di->createWindowsDIB(pdib, 0, frame-m_nFrameStart, 24, 1)
	}

	return (size>0);
}

bool DcmParser::Export(std::string dst, int format)
{
	int result = 0;
	int nFrames = GetFrameCount();
	std::string sopInsUid = GetTagStringValue(0x0008, 0x0018);
	std::string serInsUid = GetTagStringValue(0x0020, 0x000e);
	std::string ext[] = { ".jpg", ".png", ".bmp", ".tif", ".jpg" };
	std::string serDir = dst + "\\" + serInsUid;
	MakePath(serDir);

	std::vector<std::string> aviFiles;
	int w, h;

	for (int i=0; i<nFrames; i++)
	{
		std::string fn = serDir + "\\" + sopInsUid + "_" + std::to_string(i) + ext[format];
		DicomImage *di = createImage(i);
		int frame = i - m_nFrameStart;

		if (i == 0) {
			w = di->getWidth();
			h = di->getHeight();
		}

		switch (format)
		{
		case 0:  // jpg
		case 4:
		{
			DiJPEGPlugin plugin;
			plugin.setQuality(OFstatic_cast(unsigned int, 100));
			plugin.setSampling(ESS_422);
			result = di->writePluginFormat(&plugin, fn.c_str(), frame);

			if (result && format==4) 
			{
				aviFiles.push_back(fn);
			}
		}
		break;
		case 1:  // png
		{
			DiPNGPlugin pngPlugin;
			pngPlugin.setInterlaceType(E_pngInterlaceAdam7);
			pngPlugin.setMetainfoType(E_pngFileMetainfo);
			result = di->writePluginFormat(&pngPlugin, fn.c_str(), frame);
		}
		break;
		case 2:  // bmp
			result = di->writeBMP(fn.c_str(), 0, frame);
			break;
		case 3:  // tiff
		{
			//std::string tivVer = DiTIFFPlugin::getLibraryVersionString();
			//CString msg = tivVer.c_str();
			//
			//OutputDebugString(msg + "\r\n");
			//DiTIFFPlugin tiffPlugin;
			//tiffPlugin.setCompressionType(E_tiffLZWCompression);
			////tiffPlugin.setCompressionType(E_tiffPackBitsCompression);
			//tiffPlugin.setLZWPredictor(E_tiffLZWPredictorDefault);
			//tiffPlugin.setRowsPerStrip(OFstatic_cast(unsigned long, 0));
			//result = di->writePluginFormat(&tiffPlugin, ofile, frame);

			result = SaveToTiff(di, fn, frame);

		}
		break;
		default:
			break;
		}
	}

	if (format == 4) {
		if (aviFiles.size() > 9) {
			std::string fn = serDir + "\\" + sopInsUid + ".mp4";
			cv::VideoWriter writer;
			cv::Mat cvImg;
			//writer = cv::VideoWriter(fn,
			//	cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), 5,
			//	cv::Size(w, h));

			writer = cv::VideoWriter(fn,
				cv::VideoWriter::fourcc('M', 'P', '4', 'V'), 5,
				cv::Size(w, h));

			for (int i = 0; i < aviFiles.size(); i++)
			{
				std::string jpgFn = aviFiles.at(i);
				cvImg = cv::imread(jpgFn);
				writer << cvImg;
				DeleteFile(jpgFn.c_str());
			}
		}
		else {
			result = 0;
		}
		
	}
	
	return result>0;
}


int DcmParser::SaveToTiff(DicomImage* di, std::string dst, int frame)
{
	void *data = OFconst_cast(void *, di->getOutputData(8, frame, 0));
	if (data == nullptr) {
		return 0;
	}

	int cols = di->getWidth();
	int rows = di->getHeight();
	OFBool isMono = di->isMonochrome();
	short photometric = isMono ? PHOTOMETRIC_MINISBLACK : PHOTOMETRIC_RGB;
	short samplesperpixel = isMono ? 1 : 3;
	unsigned long bytesperrow = cols * samplesperpixel;

	long opt_rowsperstrip = OFstatic_cast(long, 0);
	if (opt_rowsperstrip <= 0) opt_rowsperstrip = 8192 / bytesperrow;
	if (opt_rowsperstrip == 0) opt_rowsperstrip++;

	TIFF* tif = TIFFOpen(dst.c_str(), "w");
	if (!tif) {
		return 0;
	}

	int ret = 0;
	ret = TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, cols);
	ret = TIFFSetField(tif, TIFFTAG_IMAGELENGTH, rows);
	ret = TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
	//ret = TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_LZW);
	ret = TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_PACKBITS);
	ret = TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photometric);
	ret = TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, samplesperpixel);
	ret = TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, opt_rowsperstrip);


	int OK = 1;
	unsigned long offset = 0;
	unsigned char *bytedata = OFstatic_cast(unsigned char *, data);
	for (Uint16 i = 0; (i < rows) && OK; i++)
	{
		if (TIFFWriteScanline(tif, bytedata + offset, i, 0) < 0)
			OK = 0;
		offset += bytesperrow;
	}

	TIFFFlushData(tif);
	TIFFClose(tif); 

	return OK;
}


DicomImage* DcmParser::createImage(int frame)
{
	DcmDataset* pDataset = m_dcmFile.getDataset();
	DicomImage* di = nullptr;

	int fend = m_nFrameStart + m_nLoadCount - 1;
	if (!(frame >= m_nFrameStart && frame <= fend))
	{
		m_nFrameStart = frame / m_nLoadCount * m_nLoadCount;
		unsigned long flag = CIF_UsePartialAccessToPixelData;
		if (m_pDcmImg) {
			delete m_pDcmImg;
		}
		m_pDcmImg = new DicomImage(pDataset, m_orgXfer, flag, m_nFrameStart, m_nLoadCount);
	}

	di = m_pDcmImg;

	di->setWindow(m_defWC, m_defWW);

	bool bReverse = false;
	OFString Presentation;
	pDataset->findAndGetOFString(DCM_PresentationLUTShape, Presentation);

	int size = 0;
	if (di)
	{
		if ((Presentation.compare("INVERSE") == 0 && di->getPhotometricInterpretation() == EPI_Monochrome2)
			|| (Presentation.compare("IDENTITY") == 0 && di->getPhotometricInterpretation() == EPI_Monochrome1))
			bReverse = true;

		if (bReverse)
			di->setPolarity(EPP_Reverse);
		else
			di->setPolarity(EPP_Normal);
	}

	return di;
}


BOOL DcmParser::IsRGB()
{
	return m_pDcmImg == NULL ? FALSE : m_pDcmImg->getPhotometricInterpretation() == EPI_RGB;
}


}

2. CBaseImage, CDicomImage类

简要描述

包装DcmParser类,从dicom文件中读取患者信息、检查信息、序列信息、图像信息,及由图像数据生成的位图数据。

头文件CBaseImage.h

cpp 复制代码
#pragma once
#include "DcmParser.h"

#include <string>
#include <vector>
#include <mutex>

enum IMAGETYPE
{
	IT_UNKNOWN,
	IT_DCM,
	IT_BMP,
	IT_JPG,
	IT_PNG,
	IT_GIF,
	IT_TIFF,
	IT_PDF,
	
};

struct PatientInfo
{
	CString patId;              // (0010,0020) LO Patient ID
	CString patName;            // (0010,0010) PN Patient's Name
	CString sex;                // (0010,0040) CS Patient's Sex
	CString birthday;           // (0010,0030) DA Patient's Birth Date
	CString studyAge;           // (0010,1010) AS Patient's Age
	float  patWeight;              // (0010,1030) DS Patient's Weight
};

struct StudyInfo 
{
	CString studyInsUid;        // (0020,000D) UI Study Instance UID
	CString studyDesc;          // (0008,1030) LO Study Description
	CString studyId;            // (0020,0010) SH Study ID
	CString accessionNumber;    // (0008,0050) SH Accession Number
	CString studyDate;          // (0008,0020) DA Study Date
	CString studyTime;          // (0008,0030) TM Study Time
	CString modality;           // (0008,0060) CS Modality
	CString manufacturer;       // (0008,0070) LO Manufacturer
	CString hospitalName;       // (0008,0080) LO Institution Name
	CString hospitalAddr;       // (0008,0081) ST Institution Address
	CString operatorName;       // (0008,1070) PN Operators' Name
	
};

struct SeriesInfo
{
	CString seriesInsUid;       // (0020,000E) UI Series Instance UID
	CString seriesDesc;         // (0008,103E) LO Series Description
	CString seriesDate;         // (0008,0021) DA Series Date
	CString seriesTime;         // (0008,0031) TM Series Time
	int    seriesNumber;           // (0020,0011) IS Series Number
	
	// CT
	CString   kvp;                 // (0018,0060) DS KVP
	CString   exposureTime;        // (0018,1150) IS Exposure Time
	CString   xRayTubeCurrent;     // (0018,1151) IS X-Ray Tube Current
	CString   exposure;            // (0018,1152) IS Exposure

	// MR
	CString  percentFOV;         // (0018,0094) DS  Percent Phase Field of View
	CString  TR;                 // (0018,0080) DS  Repetition Time;
	CString  TE;                 // (0018,0081) DS  Echo Time;
	CString  FS;                 // (0018,0087) DS  Magnetic Field Strength; 
	int    amiX;               // (0018,1310) US 0 Acquisition Matrix
	int    amiY;               // (0018,1310) US 1 Acquisition Matrix
	int    amjX;               // (0018,1310) US 2 Acquisition Matrix
	int    amjY;               // (0018,1310) US 3 Acquisition Matrix
	CString protocolName;       // (0018,1030) LO Protocol Name
	CString scanSequence;   // (0018,0020) CS Scanning Sequence

};

struct ImageInfo 
{
	float ipX;                 // (0020,0032) DS 0 Image Position(Patient)
	float ipY;                 // (0020,0032) DS 1 Image Position(Patient)
	float ipZ;                 // (0020,0032) DS 2 Image Position(Patient)
	float ioiX;                // (0020,0037) DS 0 Image Orientation(Patient)
	float ioiY;                // (0020,0037) DS 1 Image Orientation(Patient)
	float ioiZ;                // (0020,0037) DS 2 Image Orientation(Patient)
	float iojX;                // (0020,0037) DS 3 Image Orientation(Patient)
	float iojY;                // (0020,0037) DS 4 Image Orientation(Patient)
	float iojZ;                // (0020,0037) DS 5 Image Orientation(Patient)
	CString poX;            // (0020,0020) DS 0 Patient Orientation
	CString poY;            // (0020,0020) DS 1 Patient Orientation
	CString patPosition;    // (0018,5100) CS   Patient Position
                  
	CString sliceThickness;      // (0018,0050) DS Slice Thickness
	CString sliceLocation;       // (0020,1041) DS Slice Location
	int   samplePerPixel;      // (0028,0002) US Samples per Pixel
	float psX;                 // (0028,0030) DS 0 Pixel Spacing
	float psY;                 // (0028,0030) DS 1 Pixel Spacing
	int bitsAlloc;             // (0028,0100) US Bits Allocated
	int bitsStored;			   // (0028,0101) US Bits Stored
	int hightBit;			   // (0028,0102) US High Bit
	int pixelRep;              // (0028,0103) US Pixel Representation
	float defWinCenter;        // (0028,1050) DS Window Center
	float defWinWidth;         // (0028,1051) DS Window Width
	float rescaleIntercept;    // (0028,1052) DS Rescale Intercept
	float rescaleSlope;        // (0028,1053) DS Rescale Slope
	int height;                // (0028,0010) US Rows
	int width;                 // (0028,0011) US Columns
	int instanceNumber;        // (0020,0013) IS Instance Number

	CString sopInsUid;          // (0008,0018) UI SOP Instance UID
	CString xfer;               // (0002,0010) UI Transfer Syntax UID
	CString imageType;          // (0008,0008) CS Image Type
	CString charset;            // (0008,0005) CS Specific Character Set
	CString contentDate;        // (0008,0023) DA Content Date
	CString contentTime;        // (0008,0033) TM Content Time
	CString acquisitionDate;    // (0008,0022) DA Acquisition Date
	CString acquisitionTime;    // (0008,0032) TM Acquisition Time
};

class CBaseImage
{
public:
	CBaseImage();
	virtual ~CBaseImage();

public:
	PatientInfo& GetPatientInfo();
	StudyInfo&   GetStudyInfo();
	SeriesInfo&  GetSeriesInfo();
	ImageInfo&   GetImageInfo();

	virtual BOOL ParseFile(std::string fn)=0;
	virtual void Close() = 0;
	virtual BOOL ReParseFile() = 0;
	virtual void GetDefaultWindow(double& wc, double& ww) = 0;
	virtual BOOL CreateDIB(void*& pdib, int& w, int& h, int wc, int ww, int frame = 0, bool bneg = false) = 0;

	virtual std::string GetFileName() { return m_fileName; }
	virtual BOOL IsValid() { return m_bParserValid; }
	virtual int GetFrameCount() { return 1; }
	virtual bool Export(std::string dst, int format) {
		return false;
	}

	DcmParser* GetParser() { return &m_parser; }

protected:

	PatientInfo m_patInfo;
	StudyInfo   m_studyInfo;
	SeriesInfo  m_seriesInfo;
	ImageInfo   m_imageInfo;

	BOOL        m_bParserValid;
	std::string	m_fileName;
	DcmParser   m_parser;

};

源文件CBaseImage.cpp

cpp 复制代码
#include "pch.h"
#include "CBaseImage.h"

CBaseImage::CBaseImage()
	: m_bParserValid(FALSE)
{
}


CBaseImage::~CBaseImage()
{
}

PatientInfo& CBaseImage::GetPatientInfo()
{
	return m_patInfo;
}

StudyInfo& CBaseImage::GetStudyInfo()
{
	return m_studyInfo;
}

SeriesInfo& CBaseImage::GetSeriesInfo()
{
	return m_seriesInfo;
}

ImageInfo& CBaseImage::GetImageInfo()
{
	return m_imageInfo;
}

头文件CDicomImage.h

cpp 复制代码
#pragma once
#include "CBaseImage.h"

class CDicomImage : public CBaseImage
{
public:
	CDicomImage();
	virtual ~CDicomImage();

	mutable std::mutex m_mtx;
	mutable std::mutex m_parsermtx;
public:
	BOOL ParseFile(std::string fn);

	void GetDefaultWindow(double& wc, double& ww);
	BOOL CreateDIB(void*& pdib, int& w, int& h, int wc, int ww, int frame=0, bool bneg = false, bool bov = true);

	virtual int GetFrameCount();
	virtual void Close();
	virtual BOOL ReParseFile();
	bool Export(std::string dst, int format);

};

源文件CDicomImage.cpp

cpp 复制代码
#include "pch.h"
#include "CDicomImage.h"
#include "Utilities.h"


CDicomImage::CDicomImage()
{
}


CDicomImage::~CDicomImage()
{
}

BOOL CDicomImage::ParseFile(std::string fn)
{
	std::lock_guard<std::mutex> lg(m_mtx);
	if (!m_parser.Open(fn))
		return FALSE;

	CString charset = m_parser.GetTagValue(0x0008, 0x0005);

	BOOL needTransToGbk = FALSE;
	if (charset == _T("ISO_IR 192"))
		needTransToGbk = TRUE;

	CString tmp;
	std::string strVal;

	// Patient
	m_patInfo.patId = m_parser.GetTagValue(0x0010, 0x0020);
	strVal = m_parser.GetTagStringValue(0x0010, 0x0010);
	if (needTransToGbk)
		m_patInfo.patName = UTF8toA(strVal).c_str();
	else
		m_patInfo.patName = strVal.c_str();
		
	m_patInfo.sex = m_parser.GetTagValue(0x0010, 0x0040);
	tmp = m_parser.GetTagValue(0x0010, 0x0030);
	if (tmp.GetLength() == 8)
	{
		m_patInfo.birthday = tmp.Left(4);
		m_patInfo.birthday += _T("-");
		m_patInfo.birthday += tmp.Mid(4, 2);
		m_patInfo.birthday += _T("-");
		m_patInfo.birthday += tmp.Mid(6);
	}
	else
		m_patInfo.birthday = tmp;

	m_patInfo.studyAge = m_parser.GetTagValue(0x0010, 0x1010);
	m_patInfo.patWeight = m_parser.GetTagFloatValue(0x0010, 0x1030);

	// Study
	m_studyInfo.modality = m_parser.GetTagValue(0x0008, 0x0060);
	m_studyInfo.studyInsUid = m_parser.GetTagValue(0x0020, 0x000D);
	strVal = m_parser.GetTagStringValue(0x0008, 0x1030);
	if (needTransToGbk)
		m_studyInfo.studyDesc = UTF8toA(strVal).c_str();
	else
		m_studyInfo.studyDesc = strVal.c_str();

	m_studyInfo.studyId = m_parser.GetTagValue(0x0020, 0x0010);
	m_studyInfo.accessionNumber = m_parser.GetTagValue(0x0008, 0x0050);

	tmp = m_parser.GetTagValue(0x0008, 0x0020);
	if (tmp.GetLength() == 8)
	{
		m_studyInfo.studyDate = tmp.Left(4);
		m_studyInfo.studyDate += _T("-");
		m_studyInfo.studyDate += tmp.Mid(4, 2);
		m_studyInfo.studyDate += _T("-");
		m_studyInfo.studyDate += tmp.Mid(6);
	}
	else
		m_studyInfo.studyDate = tmp;

	tmp = m_parser.GetTagValue(0x0008, 0x0030);
	if (tmp.GetLength() >= 6)
	{
		m_studyInfo.studyTime = tmp.Left(2);
		m_studyInfo.studyTime += _T(":");
		m_studyInfo.studyTime += tmp.Mid(2, 2);
		m_studyInfo.studyTime += _T(":");
		m_studyInfo.studyTime += tmp.Mid(4);
	}
	else
		m_studyInfo.studyTime = tmp;
	
	strVal = m_parser.GetTagStringValue(0x0008, 0x0070);
	if (needTransToGbk)
		m_studyInfo.manufacturer = UTF8toA(strVal).c_str();
	else
		m_studyInfo.manufacturer = strVal.c_str();

	strVal = m_parser.GetTagStringValue(0x0008, 0x0080);
	if (needTransToGbk)
		m_studyInfo.hospitalName = UTF8toA(strVal).c_str();
	else
		m_studyInfo.hospitalName = strVal.c_str();
	
	strVal = m_parser.GetTagStringValue(0x0008, 0x0081);
	if (needTransToGbk)
		m_studyInfo.hospitalAddr = UTF8toA(strVal).c_str();
	else
		m_studyInfo.hospitalAddr = strVal.c_str();

	strVal = m_parser.GetTagStringValue(0x0008, 0x1070);
	if (needTransToGbk)
		m_studyInfo.operatorName = UTF8toA(strVal).c_str();
	else
		m_studyInfo.operatorName = strVal.c_str();

	// Series
	m_seriesInfo.seriesInsUid = m_parser.GetTagValue(0x0020, 0x000E);
	strVal = m_parser.GetTagStringValue(0x0008, 0x103E);
	if (needTransToGbk)
		m_seriesInfo.seriesDesc = UTF8toA(strVal).c_str();
	else
		m_seriesInfo.seriesDesc = strVal.c_str();

	tmp = m_parser.GetTagValue(0x0008, 0x0021);
	if (tmp.GetLength() == 8)
	{
		m_seriesInfo.seriesDate = tmp.Left(4);
		m_seriesInfo.seriesDate += _T("-");
		m_seriesInfo.seriesDate += tmp.Mid(4, 2);
		m_seriesInfo.seriesDate += _T("-");
		m_seriesInfo.seriesDate += tmp.Mid(6);
	}
	else
		m_seriesInfo.seriesDate = tmp;

	tmp = m_parser.GetTagValue(0x0008, 0x0031);
	if (tmp.GetLength() >= 6)
	{
		m_seriesInfo.seriesTime = tmp.Left(2);
		m_seriesInfo.seriesTime += _T(":");
		m_seriesInfo.seriesTime += tmp.Mid(2, 2);
		m_seriesInfo.seriesTime += _T(":");
		m_seriesInfo.seriesTime += tmp.Mid(4);
	}
	else
		m_seriesInfo.seriesTime = tmp;

	m_seriesInfo.seriesNumber = m_parser.GetTagIntValue(0x0020, 0x0011);

	// CT
	m_seriesInfo.kvp = m_parser.GetTagValue(0x0018, 0x0060);
	m_seriesInfo.exposureTime = m_parser.GetTagValue(0x0018, 0x1150);
	m_seriesInfo.xRayTubeCurrent = m_parser.GetTagValue(0x0018, 0x1151);
	m_seriesInfo.exposure = m_parser.GetTagValue(0x0018, 0x1152);

	// MR
	m_seriesInfo.percentFOV = m_parser.GetTagValue(0x0018, 0x0094);
	m_seriesInfo.TR = m_parser.GetTagValue(0x0018, 0x0080);
	m_seriesInfo.TE = m_parser.GetTagValue(0x0018, 0x0081);
	m_seriesInfo.FS = m_parser.GetTagValue(0x0018, 0x0087);
	m_seriesInfo.amiX = m_parser.GetTagIntValue(0x0018, 0x1310, 0);
	m_seriesInfo.amiY = m_parser.GetTagIntValue(0x0018, 0x1310, 1);
	m_seriesInfo.amjX = m_parser.GetTagIntValue(0x0018, 0x1310, 2);
	m_seriesInfo.amjY = m_parser.GetTagIntValue(0x0018, 0x1310, 3);
	m_seriesInfo.protocolName = m_parser.GetTagValue(0x0018, 0x1030);
	m_seriesInfo.scanSequence = m_parser.GetTagValue(0x0018, 0x0020);

	// Image
	m_imageInfo.ipX = m_parser.GetTagFloatValue(0x0020, 0x0032, 0);
	m_imageInfo.ipY = m_parser.GetTagFloatValue(0x0020, 0x0032, 1);
	m_imageInfo.ipZ = m_parser.GetTagFloatValue(0x0020, 0x0032, 2);
	m_imageInfo.ioiX = m_parser.GetTagFloatValue(0x0020, 0x0037, 0);
	m_imageInfo.ioiY = m_parser.GetTagFloatValue(0x0020, 0x0037, 1);
	m_imageInfo.ioiZ = m_parser.GetTagFloatValue(0x0020, 0x0037, 2);
	m_imageInfo.iojX = m_parser.GetTagFloatValue(0x0020, 0x0037, 3);
	m_imageInfo.iojY = m_parser.GetTagFloatValue(0x0020, 0x0037, 4);
	m_imageInfo.iojZ = m_parser.GetTagFloatValue(0x0020, 0x0037, 5);
	m_imageInfo.poX = m_parser.GetTagValue(0x0020, 0x0020, 0);
	m_imageInfo.poY = m_parser.GetTagValue(0x0020, 0x0020, 1);
	m_imageInfo.patPosition = m_parser.GetTagValue(0x0018, 0x5100);
	m_imageInfo.sliceThickness = m_parser.GetTagValue(0x0018, 0x0050);
	m_imageInfo.sliceLocation = m_parser.GetTagValue(0x0020, 0x1041);
	m_imageInfo.samplePerPixel = m_parser.GetTagIntValue(0x0028, 0x0002);

	m_imageInfo.psX = m_parser.GetTagFloatValue(0x0018, 0x1164, 0);
	m_imageInfo.psY = m_parser.GetTagFloatValue(0x0018, 0x1164, 1);
	if (m_imageInfo.psX == 0.0 || m_imageInfo.psY == 0.0) {
		m_imageInfo.psX = m_parser.GetTagFloatValue(0x0028, 0x0030, 0);
		m_imageInfo.psY = m_parser.GetTagFloatValue(0x0028, 0x0030, 1);
	}

	m_imageInfo.bitsAlloc = m_parser.GetTagIntValue(0x0028, 0x0100);
	m_imageInfo.bitsStored = m_parser.GetTagIntValue(0x0028, 0x0101);
	m_imageInfo.hightBit = m_parser.GetTagIntValue(0x0028, 0x0102);
	m_imageInfo.pixelRep = m_parser.GetTagIntValue(0x0028, 0x0103);
	m_imageInfo.defWinCenter = m_parser.GetTagFloatValue(0x0028, 0x1050);
	m_imageInfo.defWinWidth = m_parser.GetTagFloatValue(0x0028, 0x1051);
	if (m_imageInfo.defWinCenter==0.0 && m_imageInfo.defWinWidth==0.0)
	{

	}
	m_imageInfo.rescaleIntercept = m_parser.GetTagFloatValue(0x0028, 0x1052);
	m_imageInfo.rescaleSlope = m_parser.GetTagFloatValue(0x0028, 0x1053);
	m_imageInfo.height = m_parser.GetTagIntValue(0x0028, 0x0010);
	m_imageInfo.width = m_parser.GetTagIntValue(0x0028, 0x0011);
	m_imageInfo.instanceNumber = m_parser.GetTagIntValue(0x0020, 0x0013);

	m_imageInfo.sopInsUid = m_parser.GetTagValue(0x0008, 0x0018);
	m_imageInfo.xfer = m_parser.GetTagValue(0x0002, 0x0010);
	m_imageInfo.imageType = m_parser.GetTagValue(0x0008, 0x0008);
	m_imageInfo.charset = charset;

	tmp = m_parser.GetTagValue(0x0008, 0x0023);
	if (tmp.GetLength() == 8)
	{
		m_imageInfo.contentDate = tmp.Left(4);
		m_imageInfo.contentDate += _T("-");
		m_imageInfo.contentDate += tmp.Mid(4, 2);
		m_imageInfo.contentDate += _T("-");
		m_imageInfo.contentDate += tmp.Mid(6);
	}
	else
		m_imageInfo.contentDate = tmp;

	tmp = m_parser.GetTagValue(0x0008, 0x0033);
	if (tmp.GetLength() >= 6)
	{
		m_imageInfo.contentTime = tmp.Left(2);
		m_imageInfo.contentTime += _T(":");
		m_imageInfo.contentTime += tmp.Mid(2, 2);
		m_imageInfo.contentTime += _T(":");
		m_imageInfo.contentTime += tmp.Mid(4);
	}
	else
		m_imageInfo.contentTime = tmp;

	tmp = m_parser.GetTagValue(0x0008, 0x0022);
	if (tmp.GetLength() == 8)
	{
		m_imageInfo.acquisitionDate = tmp.Left(4);
		m_imageInfo.acquisitionDate += _T("-");
		m_imageInfo.acquisitionDate += tmp.Mid(4, 2);
		m_imageInfo.acquisitionDate += _T("-");
		m_imageInfo.acquisitionDate += tmp.Mid(6);
	}
	else
		m_imageInfo.acquisitionDate = tmp;

	tmp = m_parser.GetTagValue(0x0008, 0x0032);
	if (tmp.GetLength() >= 6)
	{
		m_imageInfo.acquisitionTime = tmp.Left(2);
		m_imageInfo.acquisitionTime += _T(":");
		m_imageInfo.acquisitionTime += tmp.Mid(2, 2);
		m_imageInfo.acquisitionTime += _T(":");
		m_imageInfo.acquisitionTime += tmp.Mid(4);
	}
	else
		m_imageInfo.acquisitionTime = tmp;

	if (m_seriesInfo.seriesDate.IsEmpty())
		m_seriesInfo.seriesDate = m_imageInfo.acquisitionDate;

	if (m_seriesInfo.seriesTime.IsEmpty())
		m_seriesInfo.seriesTime = m_imageInfo.acquisitionTime;

	m_bParserValid = m_parser.HasImage();
	m_fileName = fn;

	Close();

	return TRUE;
}

void CDicomImage::GetDefaultWindow(double& wc, double& ww)
{
	m_parser.GetDefaultWindow(wc, ww);
}

BOOL CDicomImage::CreateDIB(void*& pdib, int& w, int& h, int wc, int ww, int frame/* = 0*/, bool bneg/* = false*/)
{
	if (pdib)
	{
		delete[]pdib;
		pdib = NULL;
	}

	if (!m_bParserValid)
		ReParseFile();

	return m_parser.CreateDIB(pdib, w, h, wc, ww, frame, bneg);
}


int CDicomImage::GetFrameCount()
{
	return m_parser.GetFrameCount();
}

void CDicomImage::Close()
{
	std::lock_guard<std::mutex> lg(m_parsermtx);
	if (m_parser.GetFrameCount() > 1)
		return;

	m_parser.Close();
	m_nImgSize = 0;
	m_bParserValid = FALSE;
}

BOOL CDicomImage::ReParseFile()
{
	std::lock_guard<std::mutex> lg(m_parsermtx);
	if (m_bParserValid)
		return TRUE;

	if (m_fileName.empty())
		return FALSE;

	if (!m_parser.Open(m_fileName))
		return FALSE;

	m_nImgSize = m_parser.GetImageSize();
	//m_bParserValid = m_parser.HasImage();
	m_bParserValid = m_parser.IsValid();

	return m_bParserValid;
}

bool CDicomImage::Export(std::string dst, int format)
{	
	if (!IsValid()) ReParseFile();
	return m_parser.Export(dst, format);
}

3. Displayer类

简要描述

从CWnd派生的窗口类,包含CBaseImage读取dicom文件信息、生成位图数据,包含DrawParam保存绘制参数。响应WM_PAINT消息,调用DrawImage绘制图像, 调用DrawText绘制文字

后续章节再添加鼠标滚轮消息实现滚动多帧图,鼠标按下消息、鼠标移动消息实现图像缩放、图像移动、调窗等功能

DrawImage 中使用Gdiplus::Graphics DrawImage绘制图像

DrawText 中使用Gdiplus::Graphics MeasureString获取文字区域, DrawString绘制文字

头文件Displayer.h

cpp 复制代码
#pragma once

#include "DrawParam.h"

// Displayer
class CBaseImage;

class Displayer : public CWnd
{
	DECLARE_DYNAMIC(Displayer)

public:
	Displayer();
	Displayer(CWnd* pParent);
	virtual ~Displayer();

	bool LoadFile(const char* fn);
	void SetImage(CBaseImage* pImg);
	void PrevFrame();
	void NextFrame();
	int  GetFrameCount();

	// format = 0 jpg, 1 png, 2 tiff
	bool Export(std::string dst, int format);

protected:
	DECLARE_MESSAGE_MAP()

	afx_msg void OnPaint();
	afx_msg void OnSize(UINT nType, int cx, int cy);

	void FitToView();
	void DrawImage(Gdiplus::Graphics& gs, void* pDib, CRect rcDraw, DrawParam& param);
	void DrawText(Gdiplus::Graphics& gs);

private:
	CBaseImage*	 m_pImage;
	void*		 m_pDib;
	DrawParam	 m_drawParam;
	
};

源文件Displayer.cpp

cpp 复制代码
// Displayer.cpp: 实现文件
//

#include "pch.h"
#include "DcmImage.h"
#include "Displayer.h"
#include "CDicomImage.h"
#include "Utilities.h"


// Displayer

IMPLEMENT_DYNAMIC(Displayer, CWnd)

Displayer::Displayer()
	: m_pImage(nullptr)
	, m_pDib(nullptr)
{
	
}

Displayer::Displayer(CWnd* pParent/*=nullptr*/)
	: m_pImage(nullptr)
	, m_pDib(nullptr)
{

}

Displayer::~Displayer()
{
}


BEGIN_MESSAGE_MAP(Displayer, CWnd)
	ON_WM_PAINT()
	ON_WM_SIZE()
END_MESSAGE_MAP()

// Displayer 消息处理程序

void Displayer::OnPaint()
{
	CPaintDC dc(this); // device context for painting
					   // TODO: 在此处添加消息处理程序代码
					   // 不为绘图消息调用 CWnd::OnPaint()

	// 1. 获取客户区大小
	CRect rect;
	GetClientRect(&rect);
	int nWidth = rect.Width();
	int nHeight = rect.Height();

	// 2. 创建内存 DC 和临时位图(双缓冲载体)
	CDC memDC;
	memDC.CreateCompatibleDC(&dc); // 创建与屏幕 DC 兼容的内存 DC
	CBitmap bmp;
	bmp.CreateCompatibleBitmap(&dc, nWidth, nHeight); // 创建与客户区等大的位图
	CBitmap* pOldBmp = memDC.SelectObject(&bmp); // 将位图选入内存 DC

	memDC.FillSolidRect(rect, RGB(0, 0, 0)); 

	// 4. 使用 GDI+ 在内存 DC 上绘制图像
	Graphics gs(memDC.GetSafeHdc()); // 关联内存 DC 到 GDI+
	DrawImage(gs, m_pDib, rect, m_drawParam);

	// 5. 将内存 DC 内容一次性复制到屏幕 DC(避免闪烁)
	dc.BitBlt(0, 0, nWidth, nHeight, &memDC, 0, 0, SRCCOPY);

	// 6. 清理资源
	memDC.SelectObject(pOldBmp); 
}


void Displayer::OnSize(UINT nType, int cx, int cy)
{
	CWnd::OnSize(nType, cx, cy);

	// TODO: 在此处添加消息处理程序代码
	if (m_pImage) {
		FitToView();
	}
}

bool Displayer::LoadFile(const char* fn)
{
	CDicomImage* pImg = new CDicomImage();
	if (!pImg->ParseFile(fn)) {
		delete pImg;
		pImg = nullptr;
		return false;
	}

	SetImage(pImg);

	return true;
}


void Displayer::SetImage(CBaseImage* pImg)
{
	if (m_pImage) {
		delete m_pImage;
		m_pImage = nullptr;
	}

	m_pImage = pImg;

	if (m_pDib)
	{
		delete[] m_pDib;
		m_pDib = nullptr;
	}

	m_drawParam.Clear();
	m_pImage->GetDefaultWindow(m_drawParam.winCenter, m_drawParam.winWidth);

	m_pImage->CreateDIB(m_pDib, m_drawParam.width, m_drawParam.height, m_drawParam.winCenter,
		m_drawParam.winWidth, m_drawParam.nFrame, m_drawParam.bNagtive, true);

	FitToView();
}

void Displayer::PrevFrame()
{
	int nFrame = m_drawParam.nFrame - 1;
	if (nFrame < 0) {
		return;
	}

	m_drawParam.nFrame = nFrame;

	if (m_pDib)
	{
		delete[] m_pDib;
		m_pDib = nullptr;
	}

	m_pImage->CreateDIB(m_pDib, m_drawParam.width, m_drawParam.height, m_drawParam.winCenter,
		m_drawParam.winWidth, m_drawParam.nFrame, m_drawParam.bNagtive, true);

	FitToView();
}

void Displayer::NextFrame()
{
	int nFrameCount = m_pImage->GetFrameCount();

	int nFrame = m_drawParam.nFrame + 1;
	if (nFrame > nFrameCount - 1) {
		return;
	}

	m_drawParam.nFrame = nFrame;

	if (m_pDib)
	{
		delete[] m_pDib;
		m_pDib = nullptr;
	}

	m_pImage->CreateDIB(m_pDib, m_drawParam.width, m_drawParam.height, m_drawParam.winCenter,
		m_drawParam.winWidth, m_drawParam.nFrame, m_drawParam.bNagtive, true);

	FitToView();
}

int Displayer::GetFrameCount()
{
	if (m_pImage == nullptr)
		return 0;

	return m_pImage->GetFrameCount();
}

void Displayer::FitToView()
{
	if (m_pImage == nullptr)
		return;

	CRect rc;
	GetClientRect(&rc);

	int w = rc.Width();
	int h = rc.Height();
	int imgW = m_drawParam.width;
	int imgH = m_drawParam.height;

	float fx = w * 0.9 / imgW;
	float fy = h * 0.9 / imgH;
	float f = min(fx, fy);
	if (f < 0.02) f = 0.02;
	m_drawParam.zoom = f;
	Invalidate();
}


void Displayer::DrawImage(Gdiplus::Graphics& gs, void* pDib, CRect rcDraw, DrawParam& param)
{
	Gdiplus::Bitmap* img = nullptr;
	if (pDib)
	{
		BITMAPINFO bmi;
		ZeroMemory(&bmi, sizeof(BITMAPINFO));
		bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
		bmi.bmiHeader.biWidth = param.width;
		bmi.bmiHeader.biHeight = param.height;
		bmi.bmiHeader.biPlanes = 1;
		bmi.bmiHeader.biBitCount = 24;
		bmi.bmiHeader.biCompression = BI_RGB;
		bmi.bmiHeader.biClrImportant = 0;
		bmi.bmiHeader.biClrUsed = 0;

		img = new Gdiplus::Bitmap(&bmi, pDib);

	}

	// 图像
	if (img)
	{
		Rect rcDest;
		rcDest.X = rcDest.Y = 0;
		rcDest.Width = param.width;
		rcDest.Height = param.height;
		Matrix* mtx = param.GetMatrix(rcDraw);

		gs.SetTransform(mtx);
		gs.DrawImage(img, rcDest);
		gs.ResetTransform();

		DrawText(gs);

		delete img;
		img = nullptr;
	}
}

void Displayer::DrawText(Gdiplus::Graphics& gs)
{
	CRect rc;
	GetClientRect(&rc);

	Gdiplus::Font font(L"微软雅黑", 12);
	SolidBrush brush(Color(255, 255, 255));
	Gdiplus::PointF pt(2, 2);
	Gdiplus::RectF bound;
	int txtH = 18;

	// 左上角
	CString txt;
	txt = m_pImage->GetPatientInfo().patName;
	if (m_pImage->GetPatientInfo().sex != _T("")) {
		txt += +_T(" / ") + m_pImage->GetPatientInfo().sex;
	}

	if (m_pImage->GetPatientInfo().studyAge != _T("")) {
		txt += +_T(" / ") + m_pImage->GetPatientInfo().studyAge;
	}
		
	gs.DrawString(AtoW(txt).c_str(), -1, &font, pt, &brush);

	if (m_pImage->GetStudyInfo().studyId != _T("")) {
		pt.Y += txtH;
		txt = m_pImage->GetStudyInfo().studyId;
		gs.DrawString(AtoW(txt).c_str(), -1, &font, pt, &brush);
	}


	// 右上角
	pt.Y = 2;
	txt.Format(_T("WL: %.f WW: %.f"), m_drawParam.winCenter, m_drawParam.winWidth);
	gs.MeasureString(AtoW(txt).c_str(), -1, &font, PointF(0, 0), &bound);
	pt.X = rc.right - bound.Width + 2;
	gs.DrawString(AtoW(txt).c_str(), -1, &font, pt, &brush);

	int nFrames = m_pImage->GetFrameCount();
	if (nFrames > 1) {
		txt.Format(_T("fr: %d/%d"), m_drawParam.nFrame + 1, nFrames);
		gs.MeasureString(AtoW(txt).c_str(), -1, &font, PointF(0, 0), &bound);
		pt.Y += txtH;
		pt.X = rc.right - bound.Width - 2;
		gs.DrawString(AtoW(txt).c_str(), -1, &font, pt, &brush);
	}

	// 左下角
	pt.X = 2;
	pt.Y = rc.bottom - 4;

	if (m_pImage->GetStudyInfo().hospitalName != _T("")) {
		pt.Y -= txtH;
		txt = m_pImage->GetStudyInfo().hospitalName;
		gs.DrawString(AtoW(txt).c_str(), -1, &font, pt, &brush);
	}
	
	if (m_pImage->GetSeriesInfo().seriesDate != _T("")) {
		pt.Y -= txtH;
		txt = m_pImage->GetSeriesInfo().seriesDate;
		if (m_pImage->GetSeriesInfo().seriesTime != _T("")) {
			txt += _T(" ") + m_pImage->GetSeriesInfo().seriesTime;
		}
		gs.DrawString(AtoW(txt).c_str(), -1, &font, pt, &brush);
	}

	if (m_pImage->GetSeriesInfo().seriesDesc != _T("")) {
		pt.Y -= txtH;
		txt = m_pImage->GetSeriesInfo().seriesDesc;
		
		gs.DrawString(AtoW(txt).c_str(), -1, &font, pt, &brush);
	}

	if (m_pImage->GetStudyInfo().modality != _T("")) {
		pt.Y -= txtH;
		txt = m_pImage->GetStudyInfo().modality;
		gs.DrawString(AtoW(txt).c_str(), -1, &font, pt, &brush);
	}

	// 右下角
	pt.Y = rc.bottom - 4;

	txt = _T("");
	CString tmp;
	tmp = m_pImage->GetImageInfo().sliceThickness;
	if (!tmp.IsEmpty()) {
		txt = _T("T:") + tmp;
	}

	tmp = m_pImage->GetImageInfo().sliceLocation;
	if (!tmp.IsEmpty()) {
		if (!txt.IsEmpty()) txt += _T(" ");
		double location = atof(tmp);
		tmp.Format(_T("%.1f"), location);
		txt += _T("L:") + tmp;
	}

	gs.MeasureString(AtoW(txt).c_str(), -1, &font, PointF(0, 0), &bound);
	pt.Y -= txtH;
	pt.X = rc.right - bound.Width - 2;
	gs.DrawString(AtoW(txt).c_str(), -1, &font, pt, &brush);

	txt.Format(_T("Zoom: %.2f"), m_drawParam.zoom);
	gs.MeasureString(AtoW(txt).c_str(), -1, &font, PointF(0, 0), &bound);
	pt.Y -= txtH;
	pt.X = rc.right - bound.Width - 2;
	gs.DrawString(AtoW(txt).c_str(), -1, &font, pt, &brush);
}

bool Displayer::Export(std::string dst, int format)
{
	if (m_pImage == nullptr) return false;
	return m_pImage->Export(dst, format);
	
}

BOOL Displayer::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	if (!m_pImage) return true;
	int nFrames = m_pImage->GetFrameCount();
	if (nFrames > 1) {
		if (zDelta < 0) {
			NextFrame();
		}
		else {
			PrevFrame();
		}
	}

	return CWnd::OnMouseWheel(nFlags, zDelta, pt);
}

4. DrawParam类

简要描述

保存绘制参数,包含以下参数:

  • width,height 图像宽高
  • winCenter,winWidth 当前窗值
  • zoom 当前缩放系数
  • nOffsetX, nOffsetY 图像移动偏移
  • nFrame 当前帧数
  • bNagtive 是否负像
  • flipRotate 旋转翻转
  • matrix Gdiplus::Matrix变换矩阵

头文件DrawParam.h

cpp 复制代码
#pragma once

#include "DcmImage.h"
#include <string>
#include <vector>

class DrawParam
{
public:
	int width;
	int height;
	double winCenter;
	double winWidth;
	float zoom;
	int   nOffsetX;
	int   nOffsetY;
	int   nFrame;
	bool  bNagtive;
	Gdiplus::Matrix matrix;

	void Clear() {
		zoom = 1.0;
		nOffsetX = 0;
		nOffsetY = 0;
		nFrame = 0;
		bNagtive = false;
	}

	struct Trans
	{
		enum
		{
			ROTATE,
			FLIPH,
			FLIPV,
		};

		int TransType;
		int Angle;
	};

	std::vector<Trans> flipRotate;

	DrawParam()
	{
		zoom = 1.0;
		nOffsetX = 0;
		nOffsetY = 0;
		nFrame = 0;
		bNagtive = false;
	}

	DrawParam& operator=(const DrawParam& ref) {
		this->zoom = ref.zoom;
		this->height = ref.height;
		this->width = ref.width;
		this->winCenter = ref.winCenter;
		this->winWidth = ref.winWidth;
		this->nFrame = ref.nFrame;
		this->nOffsetX = ref.nOffsetX;
		this->nOffsetY = ref.nOffsetY;
		this->flipRotate = ref.flipRotate;
		this->nFrame = ref.nFrame;
		this->bNagtive = ref.bNagtive;

		return *this;
	}

	void Reset()
	{
		zoom = 1.0;
		nOffsetX = 0;
		nOffsetY = 0;
		nFrame = 0;
		bNagtive = false;
		flipRotate.clear();
	}

	void AddTrans(int type, int degree = 0)
	{
		Trans op;
		op.TransType = type;
		op.Angle = degree;
		if (flipRotate.empty())
		{
			flipRotate.push_back(op);
		}
		else
		{
			Trans& last = flipRotate.at(flipRotate.size() - 1);
			if (last.TransType != type)
			{
				flipRotate.push_back(op);
			}
			else
			{
				if (type == Trans::FLIPH || type == Trans::FLIPV)
				{
					flipRotate.pop_back();
				}
				else if (type == Trans::ROTATE)
				{
					last.Angle += degree;
					last.Angle %= 360;
					if (last.Angle == 0)
					{
						flipRotate.pop_back();
					}

				}
			}
		}
	}

	void ClearTrans()
	{
		flipRotate.clear();
	}

	Gdiplus::Matrix* GetMatrix(RECT rcDraw) {
		int rcW = rcDraw.right - rcDraw.left;
		int rcH = rcDraw.bottom - rcDraw.top;
		int dw = width * zoom;
		int dh = height * zoom;

		int dx = (rcW - dw) / 2 + rcDraw.left;
		int dy = (rcH - dh) / 2 + rcDraw.top;
		int cx = width / 2;
		int cy = height / 2;

		int reverse = 0;

		matrix.Reset();

		matrix.Translate(nOffsetX + dx, nOffsetY + dy);
		matrix.Scale(zoom, zoom);


		for (size_t i = 0; i < flipRotate.size(); i++)
		{
			Trans& op = flipRotate.at(i);
			if (op.TransType == Trans::FLIPH) {
				if (reverse) {
					matrix.Translate(0, cy);
					matrix.Scale(1, -1);
					matrix.Translate(0, -cy);
					reverse = 0;
				}
				else {
					matrix.Translate(cx, 0);
					matrix.Scale(-1, 1);
					matrix.Translate(-cx, 0);
				}
			}
			else if (op.TransType == Trans::FLIPV) {
				if (reverse) {
					matrix.Translate(cx, 0);
					matrix.Scale(-1, 1);
					matrix.Translate(-cx, 0);
					reverse = 0;
				}
				else {
					matrix.Translate(0, cy);
					matrix.Scale(1, -1);
					matrix.Translate(0, -cy);
				}
			}
			else if (op.TransType == Trans::ROTATE)
			{
				reverse = (op.Angle / 90) % 2;
				matrix.RotateAt(op.Angle, Gdiplus::PointF(cx, cy));
			}
		}

		return &matrix;
	}
};

5. CDcmImageDlg类

简要描述

用户界面,包含文件加载区,图像显示区,操作按钮区

其中图像显示区为CStatic控件,并添加变量关联到Displayer类。需要把CStatic控件的Notify属性设置为true,以使其能响应鼠标消息。

头文件DcmImageDlg.h

cpp 复制代码
// DcmImageDlg.h: 头文件
//

#pragma once

#include "Displayer.h"


// CDcmImageDlg 对话框
class CDcmImageDlg : public CDialogEx
{
// 构造
public:
	CDcmImageDlg(CWnd* pParent = nullptr);	// 标准构造函数

// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_DCMIMAGE_DIALOG };
#endif

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV 支持


// 实现
protected:
	HICON m_hIcon;

	// 生成的消息映射函数
	virtual BOOL OnInitDialog();
	afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
	afx_msg void OnPaint();
	afx_msg void OnSize(UINT nType, int cx, int cy);
	afx_msg HCURSOR OnQueryDragIcon();

	afx_msg void OnBnClickedButtonFile();
	afx_msg void OnBnClickedButtonDir();
	afx_msg void OnBnClickedButtonClearfile();
	afx_msg void OnSelchangeListDcm();
	afx_msg void OnBnClickedButtonPrev();
	afx_msg void OnBnClickedButtonNext();

	DECLARE_MESSAGE_MAP()
	
private:
	void Arrange();
	void UpdateWidth(LPCTSTR lpszItem);
	void EnumFileInDir(CString dir, std::vector<CString>& vec, LPCTSTR ext = NULL);
	CString SelectFolder();

	CListBox m_list;
	Displayer m_disp;

	CString m_lastDir;
	std::vector<CString> m_vecDcmFile;
	int  m_nListWidth;
	int  m_nDefWidth;
	
};

源文件DcmImageDlg.cpp

cpp 复制代码
// DcmImageDlg.cpp: 实现文件
//

#include "pch.h"
#include "framework.h"
#include "DcmImage.h"
#include "DcmImageDlg.h"
#include "afxdialogex.h"
#include "Utilities.h"
#include "DcmParser.h"

#include <shlobj.h>  // 包含SHBrowseForFolder等声明
#pragma comment(lib, "shell32.lib")  // 链接shell32库

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// 用于应用程序"关于"菜单项的 CAboutDlg 对话框

class CAboutDlg : public CDialogEx
{
public:
	CAboutDlg();

// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_ABOUTBOX };
#endif

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

// 实现
protected:
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CDcmImageDlg 对话框

CDcmImageDlg::CDcmImageDlg(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_DCMIMAGE_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CDcmImageDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_LIST_DCM, m_list);
	DDX_Control(pDX, IDC_STATIC_DISPLAY, m_disp);
}

BEGIN_MESSAGE_MAP(CDcmImageDlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BUTTON_FILE, &CDcmImageDlg::OnBnClickedButtonFile)
	ON_BN_CLICKED(IDC_BUTTON_DIR, &CDcmImageDlg::OnBnClickedButtonDir)
	ON_BN_CLICKED(IDC_BUTTON_CLEARFILE, &CDcmImageDlg::OnBnClickedButtonClearfile)
	ON_LBN_SELCHANGE(IDC_LIST_DCM, &CDcmImageDlg::OnSelchangeListDcm)
	ON_BN_CLICKED(IDC_BUTTON_PREV, &CDcmImageDlg::OnBnClickedButtonPrev)
	ON_BN_CLICKED(IDC_BUTTON_NEXT, &CDcmImageDlg::OnBnClickedButtonNext)
	ON_WM_SIZE()
END_MESSAGE_MAP()


// CDcmImageDlg 消息处理程序

BOOL CDcmImageDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 将"关于..."菜单项添加到系统菜单中。

	// IDM_ABOUTBOX 必须在系统命令范围内。
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != nullptr)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
	//  执行此操作
	SetIcon(m_hIcon, TRUE);			// 设置大图标
	SetIcon(m_hIcon, FALSE);		// 设置小图标

	// TODO: 在此添加额外的初始化代码
	DcmParser::RegistryCodecs();
	
	m_nListWidth = m_nDefWidth = m_list.GetHorizontalExtent();
	
	Arrange();

	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

void CDcmImageDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialogEx::OnSysCommand(nID, lParam);
	}
}

// 如果向对话框添加最小化按钮,则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序,
//  这将由框架自动完成。

void CDcmImageDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // 用于绘制的设备上下文

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// 使图标在工作区矩形中居中
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// 绘制图标
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CDcmImageDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}


void CDcmImageDlg::OnSize(UINT nType, int cx, int cy)
{
	CDialogEx::OnSize(nType, cx, cy);

	// TODO: 在此处添加消息处理程序代码
	if (::IsWindow(m_list.m_hWnd)) {
		Arrange();
	}
}


void CDcmImageDlg::OnBnClickedButtonFile()
{
	CString strFilter;
	strFilter = _T("DCM file(*.dcm;*.dic)|*.dcm;*.dic|All files(*.*)|*.*||");
	CFileDialog dlg(TRUE, NULL, NULL,
		OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_ALLOWMULTISELECT,
		strFilter, NULL);

	TCHAR *pszFile = new TCHAR[MAX_PATH * 500];
	memset(pszFile, 0, sizeof(TCHAR)*MAX_PATH * 500);
	dlg.m_ofn.lpstrFile = pszFile;
	dlg.m_ofn.nMaxFile = MAX_PATH * 500;
	dlg.m_ofn.lpstrFile[0] = '\0';
	
	if (dlg.DoModal() == IDOK)
	{
		CString strFile;
		POSITION pos = dlg.GetStartPosition();
		while (pos != NULL)
		{
			strFile = dlg.GetNextPathName(pos);
			m_vecDcmFile.push_back(strFile);
			m_list.AddString(strFile);
			UpdateWidth(strFile);
		}

		CString txt;
		txt.Format(_T("DICOM文件  数量: %d"), m_list.GetCount());
		GetDlgItem(IDC_STATIC_FILE)->SetWindowText(txt);
	}

	delete[] pszFile;
}


void CDcmImageDlg::OnBnClickedButtonDir()
{
	CFolderPickerDialog dlg(m_lastDir);
	if (dlg.DoModal() == IDOK)
	{
		CString dir = dlg.GetPathName();
		m_lastDir = dir;

		if (m_lastDir.Right(1) != _T("\\"))
			m_lastDir += _T("\\");

		EnumFileInDir(m_lastDir, m_vecDcmFile, _T(".dcm"));
		for (int i = 0; i < m_vecDcmFile.size(); i++)
		{
			m_list.AddString(m_vecDcmFile.at(i));
			UpdateWidth(m_vecDcmFile.at(i));
		}

		CString txt;
		txt.Format(_T("DICOM文件  数量: %d"), m_list.GetCount());
		GetDlgItem(IDC_STATIC_FILE)->SetWindowText(txt);
	}
}


void CDcmImageDlg::OnBnClickedButtonClearfile()
{
	m_list.ResetContent();
	m_list.SetHorizontalExtent(m_nDefWidth);
	m_nListWidth = m_nDefWidth;
	m_vecDcmFile.clear();

	CString txt;
	txt.Format(_T("DICOM文件  数量: 0"));
	GetDlgItem(IDC_STATIC_FILE)->SetWindowText(txt);
}

void CDcmImageDlg::Arrange()
{
	CRect rc;
	GetClientRect(&rc);

	m_list.MoveWindow(10, 70, 300, rc.bottom - 80);
	m_disp.MoveWindow(320, 10, rc.right - 320 - 110, rc.bottom - 20);

	CRect rcBtn;
	GetDlgItem(IDC_BUTTON_PREV)->GetWindowRect(&rcBtn);
	int w = rcBtn.Width();
	int h = rcBtn.Height();

	int btnTop = 50;
	GetDlgItem(IDC_BUTTON_PREV)->MoveWindow(rc.right - w - 10, btnTop, w, h);
	GetDlgItem(IDC_BUTTON_NEXT)->MoveWindow(rc.right - w - 10, btnTop + h + 8, w, h);
}

void CDcmImageDlg::UpdateWidth(LPCTSTR lpszItem)
{
	CFont *pFont = m_list.GetFont();
	CClientDC dc(this);
	dc.SelectObject(pFont);
	CSize sz = dc.GetTextExtent(lpszItem, _tcslen(lpszItem));
	sz.cx += (3 * ::GetSystemMetrics(SM_CXBORDER));
	if (sz.cx > m_nListWidth)
	{
		m_nListWidth = sz.cx + 10;
		m_list.SetHorizontalExtent(m_nListWidth);
	}
}

void CDcmImageDlg::EnumFileInDir(CString dir, std::vector<CString>& vec, LPCTSTR ext /*= NULL*/)
{
	CFileFind finder;

	if (dir[dir.GetLength() - 1] != _T('\\')) dir += _T("\\");

	if (finder.FindFile(dir + _T("*.*")))
	{
		BOOL bFind = FALSE;
		do
		{
			bFind = finder.FindNextFile();

			if (finder.IsDots())
				continue;
			else if (finder.IsDirectory())
			{
				CString strDir = finder.GetFilePath();
				strDir += _T("\\");
				EnumFileInDir(strDir, vec, ext);
			}
			else
			{
				CString fnext;
				CString fn = finder.GetFileName();
				int pos = fn.ReverseFind(_T('.'));
				if (pos != -1)
				{
					CString strExt = ext;
					fnext = fn.Mid(pos);
					if (fnext.CompareNoCase(strExt) == 0)
						vec.push_back(finder.GetFilePath());
				}
			}

		} while (bFind);
	}
}


void CDcmImageDlg::OnSelchangeListDcm()
{
	// TODO: 在此添加控件通知处理程序代码
	CString str;
	int idx = m_list.GetCurSel();
	m_list.GetText(idx, str);
	m_disp.LoadFile(str);

}


void CDcmImageDlg::OnBnClickedButtonPrev()
{
	// TODO: 在此添加控件通知处理程序代码
	int idx = m_list.GetCurSel();
	int nCount = m_list.GetCount();
	if (nCount == 0) return;

	if (idx < 0) idx = 1;
	if (idx == 0) {
		return;
	}

	idx--;
	m_list.SetCurSel(idx);
	OnSelchangeListDcm();
}


void CDcmImageDlg::OnBnClickedButtonNext()
{
	int idx = m_list.GetCurSel();
	int nCount = m_list.GetCount();
	if (nCount == 0) return;

	if (idx < 0) idx = 0;
	if (idx == nCount - 1) {
		return;
	}

	idx++;
	m_list.SetCurSel(idx);
	OnSelchangeListDcm();
}

CString CDcmImageDlg::SelectFolder()
{
	CString strFolder;
	BROWSEINFO bi = { 0 };
	bi.lpszTitle = _T("请选择文件夹");  // 对话框标题

	// 设置初始目录(可选)
	// TCHAR szInitDir[MAX_PATH] = _T("C:\\");
	// bi.lpfn = BrowseCallbackProc;  // 回调函数用于设置初始目录
	// bi.lParam = (LPARAM)szInitDir;

	// 显示文件夹选择对话框
	LPITEMIDLIST pidl = SHBrowseForFolder(&bi);
	if (pidl != NULL)
	{
		// 将选择的文件夹路径转换为字符串
		TCHAR szPath[MAX_PATH];
		if (SHGetPathFromIDList(pidl, szPath))
		{
			strFolder = szPath;
		}
		// 释放内存
		CoTaskMemFree(pidl);
	}
	return strFolder;
}

相关推荐
lsnm7 小时前
C++新手项目-JsonRPC框架
开发语言·c++·1024程序员节
给大佬递杯卡布奇诺8 小时前
FFmpeg 基本数据结构 AVPacket分析
数据结构·c++·ffmpeg·音视频
南方的狮子先生8 小时前
【数据结构】从线性表到排序算法详解
开发语言·数据结构·c++·算法·排序算法·1024程序员节
程序猿编码8 小时前
Linux 文件变动监控工具:原理、设计与实用指南(C/C++代码实现)
linux·c语言·c++·深度学习·inotify
好好学习啊天天向上9 小时前
多维c++ vector, vector<pair<int,int>>, vector<vector<pair<int,int>>>示例
开发语言·c++·算法
我狸才不是赔钱货9 小时前
CUDA:通往大规模并行计算的桥梁
c++·人工智能·pytorch
winds~10 小时前
【GUI】本地电脑弹出远程服务器的软件GUI界面
运维·服务器·c++
杨筱毅12 小时前
【穿越Effective C++】条款8:别让异常逃离析构函数——C++异常安全的关键支柱
c++·effective c++