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


前言

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

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

程序界面还是使用简单的MFC对话框。界面左边的文件加载区,不再赘述,参考基于dcmtk的dicom工具 第二章 dicom文件tag读取与修改工具
效果如下:
在这里插入图片描述


一、程序中的类介绍

└── 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:

#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

#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

#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

#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

#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

#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

#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

// 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

#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

// 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

// 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;
        }

posted @ 2026-01-28 21:53  clnchanpin  阅读(2)  评论(0)    收藏  举报