IShellFolderクラスのGetUIObjectOfを使って、ファイルのサムネイル画像を取得する方法です。エクスプローラーで縮小版が表示されるような画像をファイルから抜き出すことができます。
ファイルの縮小版
例えば、エクスプローラーの表示方法を「特大アイコン」に設定した場合に、次のようになります。(ファイルのプロパティ設定にも関係します)
上の画像は、パワーポイントのファイル(拡張子.pptx)を表示していますが、WordやExcel、PDFなどエクスプローラーでファイルのサムネイルが表示されるようであれば、他のファイルでも取得は可能です。
MFCアプリの作成
VisualStudioを起動して、「ファイル|プロジェクト」を選択でMFCのダイアログベースのアプリを新規作成します。
GDI+の利用
画像ファイルの生成に、GDIではなくGDI+を使うように定義を行います。
ダイアログのソースファイルの先頭部分に追加を行います。
#include <Gdiplus.h>
using namespace Gdiplus;
サムネイル取得の実装
サンプルなので、適当にイベントを作ります。ダイアログリソースに「ボタン」を追加して、イベントハンドラーを追加します。
追加したイベントハンドラーに次のコードを追加します。
IShellFolderを作成して、GetUIObjectOfメソッドからサムネイル画像へのポインタを取得しています。
[OBJ_CLEANUP]ラベル以降は、各オブジェクトの後始末をしています。
void CMFCApplication1Dlg::OnBnClickedButton1()
{
// TODO: ここにコントロール通知ハンドラー コードを追加します。
LONG nImageWidth = 320; //画像の横幅
LONG nImageHeight = 320; //画像の高さ
USES_CONVERSION;
HRESULT hResult = S_OK;
CString sFilePath = "ファイルの場所";
CString sOutputFilePath = "画像を出力する場所";
CString sFileName = sFilePath.Right(sFilePath.GetLength() - nIndex - 1);
CString sPathName = sFilePath.Left(nIndex + 1);
IMalloc* malloc = NULL;
IShellFolder* pDesktop = NULL;
IShellFolder* pSub = NULL;
IExtractImage* pIExtract = NULL;
LPITEMIDLIST pidl = NULL;
CSize size(nImageWidth, nImageHeight);
HRESULT hr;
hr = SHGetMalloc(&malloc);
if (hr != S_OK)
{
goto OBJ_CLEANUP;
}
hr = SHGetDesktopFolder(&pDesktop);
if (hr != S_OK)
{
goto OBJ_CLEANUP;
}
hr = pDesktop->ParseDisplayName(NULL, NULL, A2OLE(sPathName), NULL, &pidl, NULL);
if (hr != S_OK)
{
goto OBJ_CLEANUP;
}
hr = pDesktop->BindToObject(pidl, NULL, IID_IShellFolder, (void**)&pSub);
if (hr != S_OK)
{
goto OBJ_CLEANUP;
}
hr = pSub->ParseDisplayName(NULL, NULL, A2OLE(sFileName), NULL, &pidl, NULL);
if (hr != S_OK)
{
goto OBJ_CLEANUP;
}
hr = pSub->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST*)&pidl, IID_IExtractImage, NULL, (void**)&pIExtract);
HBITMAP hBitmap = GetImage(pIExtract, size);
if (hBitmap == NULL)
{
goto OBJ_CLEANUP;
}
if (WriteImageFile(sOutputFilePath, hBitmap) == false)
{
goto OBJ_CLEANUP;
}
OBJ_CLEANUP:
if (hBitmap != NULL)
{
::DeleteObject(hBitmap);
}
if (pidl != NULL)
{
malloc->Free(pidl);
pidl = NULL;
}
if (pIExtract != NULL)
{
pIExtract->Release();
pIExtract = NULL;
}
if (pSub != NULL)
{
pSub->Release();
pSub = NULL;
}
if (pDesktop != NULL)
{
pDesktop->Release();
pDesktop = NULL;
}
if (malloc != NULL)
{
malloc->Release();
malloc = NULL;
}
}
インラインで構わないので、実際にイメージを取得する関数を追加します。
IShellFolderから取得された画像ファイルのポインターから、ビットマップ画像へのハンドルが戻ります。
HBITMAP GetImage(IExtractImage* spExtractImage, const CSize& size)
{
DWORD dwDepth = 32;
if (spExtractImage == NULL)
{
return NULL;
}
HRESULT hr = S_OK;
OLECHAR wszPathBuffer[MAX_PATH];
DWORD dwPriority = 1;
DWORD dwFlags;
dwFlags = IEIFLAG_SCREEN;
dwFlags |= IEIFLAG_QUALITY;
dwFlags |= IEIFLAG_REFRESH;
hr = spExtractImage->GetLocation(wszPathBuffer, MAX_PATH, &dwPriority, &size, dwDepth, &dwFlags);
if (hr != E_PENDING)
{
if (hr != NOERROR)
{
return NULL;
}
}
HBITMAP hBitmap = NULL;
hr = spExtractImage->Extract(&hBitmap);
if (FAILED(hr))
{
return NULL;
}
return hBitmap;
}
最後に画像で出力する関数を追加します。チョット長い処理になっていますが、引数 sOutputFileName の拡張子によって出力できるようにしています。
bool WriteImageFile(CString sOutputFileName, HANDLE hImage)
{
bool bResult = true;
// GDI+ の初期化
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
USES_CONVERSION;
CWnd* pWnd = AfxGetMainWnd();
CWindowDC dc(pWnd);
CPalette pal;
if (dc.GetDeviceCaps(RASTERCAPS) & RC_PALETTE) {
UINT size = sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * 256);
LOGPALETTE *lp = (LOGPALETTE*) new BYTE[size];
lp->palVersion = 0x300;
lp->palNumEntries = GetSystemPaletteEntries(dc, 0, 255, lp->palPalEntry);
pal.CreatePalette(lp);
dc.SelectPalette(&pal, FALSE);
dc.RealizePalette();
delete[] lp;
}
Image* pImageOrg = Bitmap::FromHBITMAP((HBITMAP)hImage, (HPALETTE)pal.GetSafeHandle());
RectF rcf;
Unit unit = UnitPixel;
pImageOrg->GetBounds(&rcf, &unit);
int nWidth = (long)rcf.Width;
int nHeight = (long)rcf.Height;
Bitmap* pBitmap = new Bitmap(nWidth, nHeight);
Graphics* pGraphics = Graphics::FromImage(pBitmap);
Color color(0xFF, 0xFF, 0xFF);
Brush* pBrush = new SolidBrush(color);
pGraphics->FillRectangle(pBrush, 0, 0, (int)nWidth, (int)nHeight);
Gdiplus::Rect rectDist(0, 0, (int)nWidth, (int)nHeight);
pGraphics->DrawImage(
pImageOrg,
rectDist,
0, 0, pImageOrg->GetWidth(), pImageOrg->GetHeight(),
UnitPixel);
CLSID encoderClsid;
int nExtIndex = sOutputFileName.ReverseFind('.');
if (nExtIndex <= 0)
{
bResult = false;
}
CString sExtName = sOutputFileName.Right(sOutputFileName.GetLength() - nExtIndex - 1);
sExtName.MakeLower();
if (sExtName == "tif")
{
sExtName = "tiff";
}
else if (sExtName == "jpg")
{
csExtName = "jpeg";
}
CString sEncode;
sEncode.Format("image/%s", sExtName);
if (GetEncoderClsid(A2W(sEncode), &encoderClsid) == -1)
{
bResult = false;
}
if (bResult)
{
pBitmap->Save(A2W(sOutputFileName), &encoderClsid);
}
delete pBrush;
delete pGraphics;
delete pBitmap;
delete pImageOrg;
GdiplusShutdown(gdiplusToken);
return bResult;
}
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0;
UINT size = 0;
ImageCodecInfo* pImageCodecInfo = NULL;
GetImageEncodersSize(&num, &size);
if (size == 0)
{
return -1;
}
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL)
{
return -1;
}
GetImageEncoders(num, size, pImageCodecInfo);
for (UINT j = 0; j < num; ++j)
{
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j;
}
}
free(pImageCodecInfo);
return -1;
}
ビルド後に実行すると、指定したフォルダ内のファイルのサムネイル画像が画像ファイルとして保存されます。
すべての実装コード
ダイアログのソースファイルに追加した、すべてのコードは、こんな感じになります。
// MFCApplication1Dlg.cpp : 実装ファイル
//
#include "stdafx.h"
#include "MFCApplication1.h"
#include "MFCApplication1Dlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
//GDI+
#include <Gdiplus.h>
using namespace Gdiplus;
#ifdef _DEBUG
#define MFC_NEW DEBUG_NEW
#else
#define MFC_NEW 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()
// CMFCApplication1Dlg ダイアログ
CMFCApplication1Dlg::CMFCApplication1Dlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_MFCAPPLICATION1_DIALOG, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CMFCApplication1Dlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON1, &CMFCApplication1Dlg::OnBnClickedButton1)
END_MESSAGE_MAP()
// CMFCApplication1Dlg メッセージ ハンドラー
BOOL CMFCApplication1Dlg::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);
}
}
// このダイアログのアイコンを設定します。アプリケーションのメイン ウィンドウがダイアログでない場合、
// Framework は、この設定を自動的に行います。
SetIcon(m_hIcon, TRUE); // 大きいアイコンの設定
SetIcon(m_hIcon, FALSE); // 小さいアイコンの設定
// TODO: 初期化をここに追加します。
return TRUE; // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
}
void CMFCApplication1Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}
// ダイアログに最小化ボタンを追加する場合、アイコンを描画するための
// 下のコードが必要です。ドキュメント/ビュー モデルを使う MFC アプリケーションの場合、
// これは、Framework によって自動的に設定されます。
void CMFCApplication1Dlg::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 CMFCApplication1Dlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void CMFCApplication1Dlg::OnBnClickedButton1()
{
// TODO: ここにコントロール通知ハンドラー コードを追加します。
LONG nImageWidth = 320; //画像の横幅
LONG nImageHeight = 320; //画像の高さ
USES_CONVERSION;
HRESULT hResult = S_OK;
CString sFilePath = "ファイルの場所";
CString sOutputFilePath = "画像を出力する場所";
CString sFileName = sFilePath.Right(sFilePath.GetLength() - nIndex - 1);
CString sPathName = sFilePath.Left(nIndex + 1);
IMalloc* malloc = NULL;
IShellFolder* pDesktop = NULL;
IShellFolder* pSub = NULL;
IExtractImage* pIExtract = NULL;
LPITEMIDLIST pidl = NULL;
CSize size(nImageWidth, nImageHeight);
HRESULT hr;
hr = SHGetMalloc(&malloc);
if (hr != S_OK)
{
goto OBJ_CLEANUP;
}
hr = SHGetDesktopFolder(&pDesktop);
if (hr != S_OK)
{
goto OBJ_CLEANUP;
}
hr = pDesktop->ParseDisplayName(NULL, NULL, A2OLE(sPathName), NULL, &pidl, NULL);
if (hr != S_OK)
{
goto OBJ_CLEANUP;
}
hr = pDesktop->BindToObject(pidl, NULL, IID_IShellFolder, (void**)&pSub);
if (hr != S_OK)
{
goto OBJ_CLEANUP;
}
hr = pSub->ParseDisplayName(NULL, NULL, A2OLE(sFileName), NULL, &pidl, NULL);
if (hr != S_OK)
{
goto OBJ_CLEANUP;
}
hr = pSub->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST*)&pidl, IID_IExtractImage, NULL, (void**)&pIExtract);
HBITMAP hBitmap = GetImage(pIExtract, size);
if (hBitmap == NULL)
{
goto OBJ_CLEANUP;
}
if (WriteImageFile(sOutputFilePath, hBitmap) == false)
{
goto OBJ_CLEANUP;
}
OBJ_CLEANUP:
if (hBitmap != NULL)
{
::DeleteObject(hBitmap);
}
if (pidl != NULL)
{
malloc->Free(pidl);
pidl = NULL;
}
if (pIExtract != NULL)
{
pIExtract->Release();
pIExtract = NULL;
}
if (pSub != NULL)
{
pSub->Release();
pSub = NULL;
}
if (pDesktop != NULL)
{
pDesktop->Release();
pDesktop = NULL;
}
if (malloc != NULL)
{
malloc->Release();
malloc = NULL;
}
}
HBITMAP GetImage(IExtractImage* spExtractImage, const CSize& size)
{
DWORD dwDepth = 32;
if (spExtractImage == NULL)
{
return NULL;
}
HRESULT hr = S_OK;
OLECHAR wszPathBuffer[MAX_PATH];
DWORD dwPriority = 1;
DWORD dwFlags;
dwFlags = IEIFLAG_SCREEN;
dwFlags |= IEIFLAG_QUALITY;
dwFlags |= IEIFLAG_REFRESH;
hr = spExtractImage->GetLocation(wszPathBuffer, MAX_PATH, &dwPriority, &size, dwDepth, &dwFlags);
if (hr != E_PENDING)
{
if (hr != NOERROR)
{
return NULL;
}
}
HBITMAP hBitmap = NULL;
hr = spExtractImage->Extract(&hBitmap);
if (FAILED(hr))
{
return NULL;
}
return hBitmap;
}
bool WriteImageFile(CString sOutputFileName, HANDLE hImage)
{
bool bResult = true;
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
USES_CONVERSION;
CWnd* pWnd = AfxGetMainWnd();
CWindowDC dc(pWnd);
CPalette pal;
if (dc.GetDeviceCaps(RASTERCAPS) & RC_PALETTE) {
UINT size = sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * 256);
LOGPALETTE *lp = (LOGPALETTE*) new BYTE[size];
lp->palVersion = 0x300;
lp->palNumEntries = GetSystemPaletteEntries(dc, 0, 255, lp->palPalEntry);
pal.CreatePalette(lp);
dc.SelectPalette(&pal, FALSE);
dc.RealizePalette();
delete[] lp;
}
Image* pImageOrg = Bitmap::FromHBITMAP((HBITMAP)hImage, (HPALETTE)pal.GetSafeHandle());
RectF rcf;
Unit unit = UnitPixel;
pImageOrg->GetBounds(&rcf, &unit);
int nWidth = (long)rcf.Width;
int nHeight = (long)rcf.Height;
Bitmap* pBitmap = new Bitmap(nWidth, nHeight);
Graphics* pGraphics = Graphics::FromImage(pBitmap);
Color color(0xFF, 0xFF, 0xFF);
Brush* pBrush = new SolidBrush(color);
pGraphics->FillRectangle(pBrush, 0, 0, (int)nWidth, (int)nHeight);
Gdiplus::Rect rectDist(0, 0, (int)nWidth, (int)nHeight);
pGraphics->DrawImage(
pImageOrg,
rectDist,
0, 0, pImageOrg->GetWidth(), pImageOrg->GetHeight(),
UnitPixel);
CLSID encoderClsid;
int nExtIndex = sOutputFileName.ReverseFind('.');
if (nExtIndex <= 0)
{
bResult = false;
}
CString sExtName = sOutputFileName.Right(sOutputFileName.GetLength() - nExtIndex - 1);
sExtName.MakeLower();
if (sExtName == "tif")
sExtName = "tiff";
else if (sExtName == "jpg")
sExtName = "jpeg";
CString sEncode;
sEncode.Format("image/%s", sExtName);
if (GetEncoderClsid(A2W(sEncode), &encoderClsid) == -1)
{
bResult = false;
}
if (bResult)
pBitmap->Save(A2W(sOutputFileName), &encoderClsid);
delete pBrush;
delete pGraphics;
delete pBitmap;
delete pImageOrg;
GdiplusShutdown(gdiplusToken);
return bResult;
}
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0;
UINT size = 0;
ImageCodecInfo* pImageCodecInfo = NULL;
GetImageEncodersSize(&num, &size);
if (size == 0)
{
return -1;
}
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL)
{
return -1;
}
GetImageEncoders(num, size, pImageCodecInfo);
for (UINT j = 0; j < num; ++j)
{
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j;
}
}
free(pImageCodecInfo);
return -1; // 失敗 - 要求した種類のエンコーダが存在しなかった
}
まとめ
高解像度なイメージは望めませんが、エクスプローラーで表示される縮小版レベルの画像ファイルであれば、IShellFolderクラスで取得することが可能です。PNGやJPEGなどの画像ファイルやOfficeファイルの他にも、PDFファイルなどサードパーティ製のアプリ(Adobe Readerなど)がセットアップされている環境であれば、取得することが可能になります。
スポンサーリンク
最後までご覧いただき、ありがとうございます。