Files
AveryDennisonUtil/ImageItem.cpp
T
yoshikazu 19d1c39370 Initial commit: OtozaiPrinter (AveryDennisonUtil) ラベル/レシート レイアウト設計・印刷 MFC アプリ
- 用紙設計キャンバス(1/100mm 基準)・テキスト/画像/矩形/直線アイテム
- コピー/複製/Zオーダー/整列/グリッドスナップ、原紙(下絵)読込と印刷可否
- GDI 直接印刷、.otz 保存/再読込(版数つき)
- MFC/CRT 静的リンクで VS2022 IDE から F5 デバッグ可
2026-06-05 17:27:29 +09:00

200 lines
6.0 KiB
C++

// ImageItem.cpp
#include "pch.h"
#include "ImageItem.h"
#include <algorithm>
using std::min; // NOMINMAX 下で gdiplus.h が min/max を使うための保険
using std::max;
#include <gdiplus.h>
#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<CImage>();
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();
}
}