/** * @file XTPSvgImage.cpp * * @copyright * (c) 1998-2025 Codejock Software, All Rights Reserved. * * This source file is the property of Codejock Software and must not be * redistributed by any means without the explicit written permission of * Codejock Software. * * The use of this source code is governed by the terms and conditions specified * in the Toolkit Pro license agreement. Codejock Software grants you, as a * single software developer, the limited right to use this software on one * computer only. * * Contact Information: * support@codejock.com * http://www.codejock.com * */ #include "stdafx.h" #include "Common/XTPTypeId.h" #include "Common/XTPCasting.h" #include "Common/XTPFramework.h" #include "Common/XTPSystemHelpers.h" #include "Common/XTPSynchro.h" #include "Common/XTPApplication.h" #include "Common/XTPSingleton.h" #include "Common/XTPGdiObjects.h" #include "Common/XTPDrawHelpers.h" #include "GraphicLibrary/GdiPlus/XTPGdiPlus.h" using namespace Gdiplus; using namespace Gdiplus::DllExports; #define USE_GDIP #include "GraphicLibrary/XTPSvgImage.h" #include "Common/Base/Diagnostic/XTPDisableAdvancedWarnings.h" #define NANOSVG_ALL_COLOR_KEYWORDS #define NANOSVG_IMPLEMENTATION #define NANOSVGRAST_IMPLEMENTATION #include "GraphicLibrary/nanosvg/nanosvg.h" #include "GraphicLibrary/nanosvg/nanosvgrast.h" #include "Common/Base/Diagnostic/XTPEnableAdvancedWarnings.h" #include "Common/Base/Diagnostic/XTPDisableNoisyWarnings.h" #ifndef AC_SRC_ALPHA # define AC_SRC_ALPHA 0x01 #endif #ifdef _DEBUG # define new DEBUG_NEW # undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif AFX_INLINE const char* ToNSVGUnits(CXTPSvgImage::Units nUnits) { switch (nUnits) { case CXTPSvgImage::unitPX: return "px"; case CXTPSvgImage::unitPT: return "pt"; case CXTPSvgImage::unitPC: return "pc"; case CXTPSvgImage::unitMM: return "mm"; case CXTPSvgImage::unitCM: return "cm"; case CXTPSvgImage::unitIN: return "in"; case CXTPSvgImage::unitPercent: return "%"; case CXTPSvgImage::unitEM: return "em"; case CXTPSvgImage::unitEX: return "ex"; } return ""; // assumes NSVG_UNITS_USER } ////////////////////////////////////////////////////////////////////////// // CXTPSvgImage CXTPSvgImage::CXTPSvgImage() : m_bLoaded(FALSE) , m_bAlpha(FALSE) , m_bRasterized(FALSE) , m_pSvg(NULL) , m_pRast(NULL) , m_pBits(NULL) { } //----------------------------------------------------------------------------- CXTPSvgImage::~CXTPSvgImage() { Reset(); } //----------------------------------------------------------------------------- BOOL CXTPSvgImage::Load(const void* pData, SIZE_T cbSize, Units nUnits /*= unitPX*/) { _ASSERTE(NULL != pData); _ASSERTE(0 < cbSize); if (NULL == pData || 0 == cbSize) return FALSE; CArray buffer; buffer.SetSize(XTPToIntPtrChecked(cbSize + 1)); char* pSvgData = &buffer[0]; memcpy(pSvgData, pData, cbSize); buffer.SetAt(XTPToIntPtrChecked(cbSize - 1), '\0'); return LoadXml(pSvgData, nUnits); } //----------------------------------------------------------------------------- BOOL CXTPSvgImage::Load(IStream* pStream, Units nUnits /*= unitPX*/) { _ASSERTE(NULL != pStream); if (NULL == pStream) return FALSE; STATSTG st = { 0 }; if (FAILED(pStream->Stat(&st, STATFLAG_NONAME))) return FALSE; if (0 != st.cbSize.HighPart) return FALSE; // too large CArray buffer; buffer.SetSize(XTPToIntPtrChecked(st.cbSize.LowPart + 1)); char* pSvgData = &buffer[0]; ULONG cbRead = 0; if (FAILED(pStream->Read(pSvgData, st.cbSize.LowPart, &cbRead))) return FALSE; if (0 == cbRead) return FALSE; return LoadXml(pSvgData, nUnits); } //----------------------------------------------------------------------------- BOOL CXTPSvgImage::Load(CFile* pFile, Units nUnits /*= unitPX*/) { ASSERT_VALID(pFile); if (NULL == pFile) return FALSE; ULARGE_INTEGER ullSize; ullSize.QuadPart = pFile->GetLength(); if (0 != ullSize.HighPart) return FALSE; CArray buffer; buffer.SetSize(XTPToIntPtrChecked(ullSize.LowPart + 1)); char* pSvgData = &buffer[0]; ULONG cbRead = pFile->Read(pSvgData, ullSize.LowPart); if (0 == cbRead) return FALSE; return LoadXml(pSvgData, nUnits); } //----------------------------------------------------------------------------- BOOL CXTPSvgImage::Load(LPCTSTR lpFilePath, Units nUnits /*= unitPX*/) { _ASSERTE(NULL != lpFilePath); if (NULL == lpFilePath) return FALSE; CFile file; if (!file.Open(lpFilePath, CFile::modeRead | CFile::shareDenyWrite | CFile::typeBinary)) return FALSE; return Load(&file, nUnits); } //----------------------------------------------------------------------------- BOOL CXTPSvgImage::Load(HMODULE hModule, HRSRC hRes, Units nUnits /*= unitPX*/) { HGLOBAL hGlob = ::LoadResource(hModule, hRes); if (NULL == hGlob) return FALSE; DWORD dwSize = SizeofResource(hModule, hRes); if (0 == dwSize) return FALSE; LPVOID pData = ::LockResource(hGlob); if (NULL == pData) return FALSE; return Load(pData, dwSize, nUnits); } //----------------------------------------------------------------------------- BOOL CXTPSvgImage::IsAlpha() const { if (!m_bRasterized) const_cast(this)->Rasterize(); return m_bAlpha; } //----------------------------------------------------------------------------- const CBitmap* CXTPSvgImage::GetBitmap() const { if (!m_bRasterized) { _ASSERTE(NULL == m_bmp.m_hObject); const_cast(this)->Rasterize(); } return const_cast(this)->AccessBitmap(); } //----------------------------------------------------------------------------- void CXTPSvgImage::SetSize(CSize size) { if (!m_bLoaded) return; if (size == m_size) return; m_size = size; Unrasterize(); } //----------------------------------------------------------------------------- void CXTPSvgImage::Draw(HDC hDC, CPoint pt, BOOL bPreserveAlpha /*= TRUE*/) { if (!m_bLoaded) return; if (!m_bRasterized && !Rasterize()) return; CBitmap* pBmp = AccessBitmap(); if (NULL == pBmp) return; CDC memDC; memDC.Attach(::CreateCompatibleDC(hDC)); CBitmap* pOldBmp = memDC.SelectObject(pBmp); if (m_bAlpha && bPreserveAlpha && NULL != XTPDrawHelpers()->m_pfnAlphaBlend) { BLENDFUNCTION bf = { 0 }; bf.BlendOp = AC_SRC_OVER; bf.SourceConstantAlpha = 0xff; bf.AlphaFormat = AC_SRC_ALPHA; XTPDrawHelpers()->m_pfnAlphaBlend(hDC, pt.x, pt.y, m_size.cx, m_size.cy, memDC.m_hDC, 0, 0, m_size.cx, m_size.cy, bf); } else { ::BitBlt(hDC, pt.x, pt.y, m_size.cx, m_size.cy, memDC.m_hDC, 0, 0, SRCCOPY); } memDC.SelectObject(pOldBmp); memDC.DeleteDC(); } //----------------------------------------------------------------------------- void CXTPSvgImage::Draw(HDC hDC, CRect rc, BOOL bPreserveAlpha /*= TRUE*/) { if (!m_bLoaded) return; SetSize(rc.Size()); Draw(hDC, rc.TopLeft(), bPreserveAlpha); } //----------------------------------------------------------------------------- SIZE_T CXTPSvgImage::DirectDraw(PVOID pBits, SIZE_T cbSize, CRect rc) { _ASSERTE(NULL != pBits); if (NULL == pBits) return 0; if (!m_bLoaded) return 0; SetSize(rc.Size()); if (!m_bRasterized && !Rasterize()) return 0; SIZE_T cbSizeNeeded = XTPToSizeT(rc.Width() * rc.Height() * 4); if (cbSize < cbSizeNeeded) { _ASSERTE(!"RGBA bitmap data size size is not sufficient"); return 0; } _ASSERTE(NULL != m_pBits); memcpy(pBits, m_pBits, cbSizeNeeded); return cbSizeNeeded; } //----------------------------------------------------------------------------- BOOL CXTPSvgImage::LoadXml(const char* pData, Units nUnits /*= unitPX*/) { _ASSERTE(NULL != pData); Reset(); m_pSvg = nsvgParse(const_cast(pData), ToNSVGUnits(nUnits), 96.f); if (NULL == m_pSvg) return FALSE; m_size.cx = static_cast(m_pSvg->width); m_size.cy = static_cast(m_pSvg->height); m_bLoaded = TRUE; return TRUE; } //----------------------------------------------------------------------------- BOOL CXTPSvgImage::Rasterize() { _ASSERTE(m_bLoaded); _ASSERTE(!m_bRasterized); _ASSERTE(NULL == m_pBits); if (NULL == m_pRast) { m_pRast = nsvgCreateRasterizer(); if (NULL == m_pRast) return FALSE; } SIZE_T cbSize = XTPToSizeT(m_size.cx * m_size.cy * 4); m_pBits = malloc(cbSize); if (NULL == m_pBits) return FALSE; memset(m_pBits, 0, cbSize); float wm = static_cast(m_size.cx) / m_pSvg->width; float hm = static_cast(m_size.cy) / m_pSvg->height; float scale = (hm < wm ? hm : wm); // Currently all SVG files are assumed to have alpha channel so alpha pre-multiplication is // needed. m_bAlpha = TRUE; m_pRast->noUnpremultiplyAlpha = 1; nsvgRasterize(m_pRast, m_pSvg, 0, 0, scale, reinterpret_cast(m_pBits), m_size.cx, m_size.cy, m_size.cx * 4, 0); m_bRasterized = TRUE; return TRUE; } //----------------------------------------------------------------------------- void CXTPSvgImage::Unrasterize() { if (NULL != m_bmp.m_hObject) m_bmp.DeleteObject(); if (NULL != m_pBits) { free(m_pBits); m_pBits = NULL; } m_bRasterized = FALSE; } //----------------------------------------------------------------------------- void CXTPSvgImage::Reset() { Unrasterize(); if (NULL != m_pSvg) { nsvgDelete(m_pSvg); m_pSvg = NULL; } if (NULL != m_pRast) { nsvgDeleteRasterizer(m_pRast); m_pRast = NULL; } m_bLoaded = FALSE; m_bAlpha = FALSE; m_size = CSize(); } //----------------------------------------------------------------------------- CBitmap* CXTPSvgImage::AccessBitmap() { if (!m_bRasterized && !Rasterize()) return NULL; if (NULL != m_bmp.m_hObject) return &m_bmp; BITMAPV4HEADER bmi = { 0 }; bmi.bV4Size = sizeof(BITMAPV4HEADER); bmi.bV4BitCount = 32; bmi.bV4Width = m_size.cx; bmi.bV4Height = -m_size.cy; bmi.bV4Planes = 1; bmi.bV4AlphaMask = 0xff000000; bmi.bV4RedMask = 0x00ff0000; bmi.bV4GreenMask = 0x0000ff00; bmi.bV4BlueMask = 0x000000ff; bmi.bV4V4Compression = BI_BITFIELDS; HDC hDC = ::CreateCompatibleDC(NULL); PVOID pBmpBits = NULL; m_bmp.Attach(CreateDIBSection(hDC, reinterpret_cast(&bmi), DIB_RGB_COLORS, &pBmpBits, NULL, 0)); ::DeleteDC(hDC); if (NULL == m_bmp.m_hObject) return NULL; if (NULL == pBmpBits) { m_bmp.DeleteObject(); return NULL; } memcpy(pBmpBits, m_pBits, XTPToSizeT(m_size.cx * m_size.cy * 4)); return &m_bmp; } #include "Common/Base/Diagnostic/XTPEnableNoisyWarnings.h"