- 用紙設計キャンバス(1/100mm 基準)・テキスト/画像/矩形/直線アイテム - コピー/複製/Zオーダー/整列/グリッドスナップ、原紙(下絵)読込と印刷可否 - GDI 直接印刷、.otz 保存/再読込(版数つき) - MFC/CRT 静的リンクで VS2022 IDE から F5 デバッグ可
200 lines
6.0 KiB
C++
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();
|
|
}
|
|
}
|