// ImageItem.cpp #include "pch.h" #include "ImageItem.h" #include using std::min; // NOMINMAX 下で gdiplus.h が min/max を使うための保険 using std::max; #include #pragma comment(lib, "gdiplus.lib") IMPLEMENT_SERIAL(CImageItem, CPrintItem, 1) // コンストラクタ。既定は縦横比保持フィット(m_fit=1)。 CImageItem::CImageItem() : m_fit(1) { } // コピーコンストラクタ。画像バイト列はコピーするが、描画キャッシュ(m_cache)は // 複製せず必要時に再生成する。Clone() から利用される。 CImageItem::CImageItem(const CImageItem& other) : CPrintItem(other) , m_fit(other.m_fit) { m_bytes.Copy(other.m_bytes); // m_cache は複製しない(遅延再生成) } // 画像ファイルを読み込み、その生バイト列を m_bytes に取り込む(.otz 埋め込み用)。 // 読み込み妥当性の確認も兼ねて一度デコードする。失敗時は FALSE。 BOOL CImageItem::LoadFromFile(LPCTSTR path) { CFile file; if (!file.Open(path, CFile::modeRead | CFile::shareDenyWrite)) return FALSE; ULONGLONG len = file.GetLength(); if (len == 0 || len > (ULONGLONG)INT_MAX) return FALSE; m_bytes.SetSize((INT_PTR)len); file.Read(m_bytes.GetData(), (UINT)len); file.Close(); m_cache.reset(); // 妥当性確認 + 既定サイズ算出のため一度ロード CImage* img = EnsureImage(); if (img == nullptr) { m_bytes.RemoveAll(); return FALSE; } return TRUE; } // m_bytes から CImage を遅延生成して返す(描画キャッシュ)。 // 既に生成済みならそれを返す。バイト列が無効なら nullptr。 CImage* CImageItem::EnsureImage() { if (m_cache && !m_cache->IsNull()) return m_cache.get(); if (m_bytes.GetSize() == 0) return nullptr; HGLOBAL hMem = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)m_bytes.GetSize()); if (!hMem) return nullptr; void* p = ::GlobalLock(hMem); memcpy(p, m_bytes.GetData(), (size_t)m_bytes.GetSize()); ::GlobalUnlock(hMem); IStream* pStream = nullptr; if (FAILED(::CreateStreamOnHGlobal(hMem, TRUE, &pStream))) { ::GlobalFree(hMem); return nullptr; } auto img = std::make_shared(); HRESULT hr = img->Load(pStream); pStream->Release(); // fDeleteOnRelease=TRUE のため hMem も解放される if (FAILED(hr) || img->IsNull()) return nullptr; m_cache = img; return m_cache.get(); } BOOL CImageItem::GetPixelSize(int& w, int& h) { CImage* img = EnsureImage(); if (!img) return FALSE; w = img->GetWidth(); h = img->GetHeight(); return (w > 0 && h > 0); } BOOL CImageItem::GetResolutionDpi(double& dx, double& dy) { // EnsureImage() 成功中は atlimage が GDI+ を起動済みなので、 // ここでは GdiplusStartup を呼ばず Gdiplus::Bitmap で解像度だけ読む。 if (EnsureImage() == nullptr || m_bytes.GetSize() == 0) return FALSE; HGLOBAL hMem = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)m_bytes.GetSize()); if (!hMem) return FALSE; void* p = ::GlobalLock(hMem); memcpy(p, m_bytes.GetData(), (size_t)m_bytes.GetSize()); ::GlobalUnlock(hMem); IStream* pStream = nullptr; if (FAILED(::CreateStreamOnHGlobal(hMem, TRUE, &pStream))) { ::GlobalFree(hMem); return FALSE; } BOOL ok = FALSE; { Gdiplus::Bitmap bmp(pStream, FALSE); if (bmp.GetLastStatus() == Gdiplus::Ok) { Gdiplus::REAL hx = bmp.GetHorizontalResolution(); Gdiplus::REAL hy = bmp.GetVerticalResolution(); if (hx > 0 && hy > 0) { dx = hx; dy = hy; ok = TRUE; } } } pStream->Release(); // fDeleteOnRelease=TRUE のため hMem も解放 return ok; } // 画像を矩形内に描画する。m_fit=1 なら縦横比を保って収め、0 なら矩形いっぱいに // 引き伸ばす。画像が読めない場合は ×印付きのプレースホルダ枠を描く。 void CImageItem::Draw(CDC& dc, const CoordMap& map, bool selected) { CRect dr = map.MmToDev(m_rect); CImage* img = EnsureImage(); if (img) { CRect dest = dr; if (m_fit == 1 && img->GetWidth() > 0 && img->GetHeight() > 0) { // 縦横比保持で矩形内にフィット double sr = (double)img->GetWidth() / img->GetHeight(); double dRatio = (double)dr.Width() / max(1, dr.Height()); if (sr > dRatio) { int h = (int)(dr.Width() / sr + 0.5); dest = CRect(dr.left, dr.top, dr.right, dr.top + h); } else { int w = (int)(dr.Height() * sr + 0.5); dest = CRect(dr.left, dr.top, dr.left + w, dr.bottom); } } int oldMode = dc.SetStretchBltMode(HALFTONE); ::SetBrushOrgEx(dc.GetSafeHdc(), 0, 0, nullptr); img->StretchBlt(dc.GetSafeHdc(), dest, CRect(0, 0, img->GetWidth(), img->GetHeight()), SRCCOPY); dc.SetStretchBltMode(oldMode); } else { // 画像なし: プレースホルダ枠 + ×印 CBrush* pOldBrush = (CBrush*)dc.SelectStockObject(NULL_BRUSH); dc.Rectangle(dr); dc.MoveTo(dr.left, dr.top); dc.LineTo(dr.right, dr.bottom); dc.MoveTo(dr.right, dr.top); dc.LineTo(dr.left, dr.bottom); dc.SelectObject(pOldBrush); } if (selected) DrawSelection(dc, dr); } // 自身の複製を生成して返す(画像バイト列ごと複製)。 CPrintItem* CImageItem::Clone() const { return new CImageItem(*this); } // 永続化。基底(矩形)に続けてフィット方式と画像バイト列を読み書きする。 void CImageItem::Serialize(CArchive& ar) { CPrintItem::Serialize(ar); if (ar.IsStoring()) { ar << m_fit; m_bytes.Serialize(ar); } else { ar >> m_fit; m_bytes.Serialize(ar); m_cache.reset(); } }