Initial commit: OtozaiPrinter (AveryDennisonUtil) ラベル/レシート レイアウト設計・印刷 MFC アプリ
- 用紙設計キャンバス(1/100mm 基準)・テキスト/画像/矩形/直線アイテム - コピー/複製/Zオーダー/整列/グリッドスナップ、原紙(下絵)読込と印刷可否 - GDI 直接印刷、.otz 保存/再読込(版数つき) - MFC/CRT 静的リンクで VS2022 IDE から F5 デバッグ可
This commit is contained in:
+199
@@ -0,0 +1,199 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user