Direct2D in Rainmeter

Good day, Habr. Always wanted freedom in Rainmeter. The same type of skins, simple plugins - not that.



Today I will show you how to get full control over Rainmeter.



Let's climb into the Rainmeter sources and see how everything is drawn so beautifully there ...





We are interested in the Library / Meter.cpp file



Hidden text
bool Meter::Draw(Gfx::Canvas& canvas)
{
	if (IsHidden()) return false;

	canvas.SetAntiAliasing(m_AntiAlias);

	if (m_SolidColor.a != 0.0f || m_SolidColor2.a != 0.0f)
	{
		const FLOAT x = (FLOAT)GetX();
		const FLOAT y = (FLOAT)GetY();

		const D2D1_RECT_F r = D2D1::RectF(x, y, x + (FLOAT)m_W, y + (FLOAT)m_H);

		if (m_SolidColor.r == m_SolidColor2.r && m_SolidColor.g == m_SolidColor2.g && 
			m_SolidColor.b == m_SolidColor2.b && m_SolidColor.a == m_SolidColor2.a)
		{
			canvas.FillRectangle(r, m_SolidColor);
		}
		else
		{
			canvas.FillGradientRectangle(r, m_SolidColor, m_SolidColor2, (FLOAT)m_SolidAngle);
		}
	}

	if (m_SolidBevel != BEVELTYPE_NONE)
	{
		D2D1_COLOR_F lightColor = D2D1::ColorF(D2D1::ColorF::White);
		D2D1_COLOR_F darkColor = D2D1::ColorF(D2D1::ColorF::Black);
		
		if (m_SolidBevel == BEVELTYPE_DOWN)
		{
			lightColor = D2D1::ColorF(D2D1::ColorF::Black);
			darkColor = D2D1::ColorF(D2D1::ColorF::White);
		}

		// The bevel is drawn outside the meter
		const FLOAT x = (FLOAT)GetX();
		const FLOAT y = (FLOAT)GetY();
		const D2D1_RECT_F rect = D2D1::RectF(x - 2.0f, y - 2.0f, x + (FLOAT)m_W + 2.0f, y + (FLOAT)m_H + 2.0f);
		DrawBevel(canvas, rect, lightColor, darkColor);
	}

	return true;
}




Now let's find out what this Canvas is.



Common / Gfx / Canvas.cpp
/* Copyright (C) 2013 Rainmeter Project Developers
 *
 * This Source Code Form is subject to the terms of the GNU General Public
 * License; either version 2 of the License, or (at your option) any later
 * version. If a copy of the GPL was not distributed with this file, You can
 * obtain one at <https://www.gnu.org/licenses/gpl-2.0.html>. */

#include "StdAfx.h"
#include "Canvas.h"
#include "TextFormatD2D.h"
#include "D2DBitmap.h"
#include "RenderTexture.h"
#include "Util/D2DUtil.h"
#include "Util/DWriteFontCollectionLoader.h"
#include "../../Library/Util.h"
#include "../../Library/Logger.h"

namespace Gfx {

UINT Canvas::c_Instances = 0;
D3D_FEATURE_LEVEL Canvas::c_FeatureLevel;
Microsoft::WRL::ComPtr<ID3D11Device> Canvas::c_D3DDevice;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> Canvas::c_D3DContext;
Microsoft::WRL::ComPtr<ID2D1Device> Canvas::c_D2DDevice;
Microsoft::WRL::ComPtr<IDXGIDevice1> Canvas::c_DxgiDevice;
Microsoft::WRL::ComPtr<ID2D1Factory1> Canvas::c_D2DFactory;
Microsoft::WRL::ComPtr<IDWriteFactory1> Canvas::c_DWFactory;
Microsoft::WRL::ComPtr<IWICImagingFactory> Canvas::c_WICFactory;

Canvas::Canvas() :
	m_W(0),
	m_H(0),
	m_MaxBitmapSize(0U),
	m_IsDrawing(false),
	m_EnableDrawAfterGdi(false),
	m_TextAntiAliasing(false),
	m_CanUseAxisAlignClip(true)
{
	Initialize(true);
}

Canvas::~Canvas()
{
	Finalize();
}

bool Canvas::LogComError(HRESULT hr)
{
	_com_error err(hr);
	LogErrorF(L"Error 0x%08x: %s", hr, err.ErrorMessage());
	return false;
}

bool Canvas::Initialize(bool hardwareAccelerated)
{
	++c_Instances;
	if (c_Instances == 1U)
	{
		// Required for Direct2D interopability.
		UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

#ifdef _DEBUG
		creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

		auto tryCreateContext = [&](D3D_DRIVER_TYPE driverType,
			const D3D_FEATURE_LEVEL* levels, UINT numLevels)
		{
			return D3D11CreateDevice(
				nullptr,
				driverType,
				nullptr,
				creationFlags,
				levels,
				numLevels,
				D3D11_SDK_VERSION,
				c_D3DDevice.GetAddressOf(),
				&c_FeatureLevel,
				c_D3DContext.GetAddressOf());
		};

		// D3D selects the best feature level automatically and sets it
		// to |c_FeatureLevel|. First, we try to use the hardware driver
		// and if that fails, we try the WARP rasterizer for cases
		// where there is no graphics card or other failures.
		const D3D_FEATURE_LEVEL levels[] = 
		{
			D3D_FEATURE_LEVEL_11_1,
			D3D_FEATURE_LEVEL_11_0,
			D3D_FEATURE_LEVEL_10_1,
			D3D_FEATURE_LEVEL_10_0,
			D3D_FEATURE_LEVEL_9_3,
			D3D_FEATURE_LEVEL_9_2,
			D3D_FEATURE_LEVEL_9_1
		};

		HRESULT hr = E_FAIL;
		if (hardwareAccelerated)
		{
			hr = tryCreateContext(D3D_DRIVER_TYPE_HARDWARE, levels, _countof(levels));
			if (hr == E_INVALIDARG)
			{
				hr = tryCreateContext(D3D_DRIVER_TYPE_HARDWARE, &levels[1], _countof(levels) - 1);
			}
		}

		if (FAILED(hr))
		{
			hr = tryCreateContext(D3D_DRIVER_TYPE_WARP, nullptr, 0U);
			if (FAILED(hr)) return false;
		}

		hr = c_D3DDevice.As(&c_DxgiDevice);
		if (FAILED(hr)) return false;

		D2D1_FACTORY_OPTIONS fo = {};
#ifdef _DEBUG
		fo.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif

		hr = D2D1CreateFactory(
			D2D1_FACTORY_TYPE_SINGLE_THREADED,
			fo,
			c_D2DFactory.GetAddressOf());
		if (FAILED(hr)) return false;

		hr = c_D2DFactory->CreateDevice(
			c_DxgiDevice.Get(),
			c_D2DDevice.GetAddressOf());
		if (FAILED(hr)) return false;

		hr = CoCreateInstance(
			CLSID_WICImagingFactory,
			nullptr,
			CLSCTX_INPROC_SERVER,
			IID_IWICImagingFactory,
			(LPVOID*)c_WICFactory.GetAddressOf());
		if (FAILED(hr)) return false;

		hr = DWriteCreateFactory(
			DWRITE_FACTORY_TYPE_SHARED,
			__uuidof(c_DWFactory),
			(IUnknown**)c_DWFactory.GetAddressOf());
		if (FAILED(hr)) return false;

		hr = c_DWFactory->RegisterFontCollectionLoader(Util::DWriteFontCollectionLoader::GetInstance());
		if (FAILED(hr)) return false;
	}

	return true;
}

void Canvas::Finalize()
{
	--c_Instances;
	if (c_Instances == 0U)
	{
		c_D3DDevice.Reset();
		c_D3DContext.Reset();
		c_D2DDevice.Reset();
		c_DxgiDevice.Reset();
		c_D2DFactory.Reset();
		c_WICFactory.Reset();

		if (c_DWFactory)
		{
			c_DWFactory->UnregisterFontCollectionLoader(Util::DWriteFontCollectionLoader::GetInstance());
			c_DWFactory.Reset();
		}
	}
}

bool Canvas::InitializeRenderTarget(HWND hwnd)
{
	DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 };
	swapChainDesc.Width = 1U;
	swapChainDesc.Height = 1U;
	swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
	swapChainDesc.Stereo = false;
	swapChainDesc.SampleDesc.Count = 1U;
	swapChainDesc.SampleDesc.Quality = 0U;
	swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	swapChainDesc.BufferCount = 2U;
	swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
	swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_GDI_COMPATIBLE;
	swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;

	Microsoft::WRL::ComPtr<IDXGIAdapter> dxgiAdapter;
	HRESULT hr = c_DxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf());
	if (FAILED(hr)) return LogComError(hr);

	// Ensure that DXGI does not queue more than one frame at a time.
	hr = c_DxgiDevice->SetMaximumFrameLatency(1U);
	if (FAILED(hr)) return LogComError(hr);

	Microsoft::WRL::ComPtr<IDXGIFactory2> dxgiFactory;
	hr = dxgiAdapter->GetParent(IID_PPV_ARGS(dxgiFactory.GetAddressOf()));
	if (FAILED(hr)) return LogComError(hr);

	hr = dxgiFactory->CreateSwapChainForHwnd(
		c_DxgiDevice.Get(),
		hwnd,
		&swapChainDesc,
		nullptr,
		nullptr,
		m_SwapChain.ReleaseAndGetAddressOf());
	if (FAILED(hr)) return LogComError(hr);

	hr = CreateRenderTarget();
	if (FAILED(hr)) return LogComError(hr);

	return CreateTargetBitmap(0U, 0U);
}

void Canvas::Resize(int w, int h)
{
	// Truncate the size of the skin if it's too big.
	if (w > (int)m_MaxBitmapSize) w = (int)m_MaxBitmapSize;
	if (h > (int)m_MaxBitmapSize) h = (int)m_MaxBitmapSize;

	m_W = w;
	m_H = h;

	// Check if target, targetbitmap, backbuffer, swap chain are valid?

	// Unmap all resources tied to the swap chain.
	m_Target->SetTarget(nullptr);
	m_TargetBitmap.Reset();
	m_BackBuffer.Reset();

	// Resize swap chain.
	HRESULT hr = m_SwapChain->ResizeBuffers(
		0U,
		(UINT)w,
		(UINT)h,
		DXGI_FORMAT_B8G8R8A8_UNORM,
		DXGI_SWAP_CHAIN_FLAG_GDI_COMPATIBLE);
	if (FAILED(hr)) return;

	CreateTargetBitmap((UINT32)w, (UINT32)h);
}

bool Canvas::BeginDraw()
{
	if (!m_Target)
	{
		HRESULT hr = CreateRenderTarget();
		if (FAILED(hr))
		{
			m_IsDrawing = false;
			return false;
		}

		// Recreate target bitmap
		Resize(m_W, m_H);
	}

	m_Target->BeginDraw();
	m_IsDrawing = true;
	return true;
}

void Canvas::EndDraw()
{
	HRESULT hr = m_Target->EndDraw();
	if (FAILED(hr))
	{
		m_Target.Reset();
	}

	m_IsDrawing = false;
}

HDC Canvas::GetDC()
{
	if (m_IsDrawing)
	{
		m_EnableDrawAfterGdi = true;
		m_IsDrawing = false;
		EndDraw();
	}

	HDC hdc;
	HRESULT hr = m_BackBuffer->GetDC(FALSE, &hdc);
	if (FAILED(hr)) return nullptr;

	return hdc;
}

void Canvas::ReleaseDC()
{
	m_BackBuffer->ReleaseDC(nullptr);

	if (m_EnableDrawAfterGdi)
	{
		m_EnableDrawAfterGdi = false;
		m_IsDrawing = true;
		BeginDraw();
	}
}

bool Canvas::IsTransparentPixel(int x, int y)
{
	if (!(x >= 0 && y >= 0 && x < m_W && y < m_H)) return false;

	auto pixel = GetPixel(GetDC(), x, y);
	ReleaseDC();

	return (pixel & 0xFF000000) == 0;
}

void Canvas::GetTransform(D2D1_MATRIX_3X2_F* matrix)
{
	if (m_Target)
	{
		m_Target->GetTransform(matrix);
	}
}

void Canvas::SetTransform(const D2D1_MATRIX_3X2_F& matrix)
{
	m_Target->SetTransform(matrix);

	m_CanUseAxisAlignClip =
		(matrix.m11 ==  1.0f && matrix.m12 ==  0.0f && matrix.m21 ==  0.0f && matrix.m22 ==  1.0f) ||	// Angle: 0
		(matrix.m11 ==  0.0f && matrix.m12 ==  1.0f && matrix.m21 == -1.0f && matrix.m22 ==  0.0f) ||	// Angle: 90
		(matrix.m11 == -1.0f && matrix.m12 ==  0.0f && matrix.m21 ==  0.0f && matrix.m22 == -1.0f) ||	// Angle: 180
		(matrix.m11 ==  0.0f && matrix.m12 == -1.0f && matrix.m21 ==  1.0f && matrix.m22 ==  0.0f);		// Angle: 270
}

void Canvas::ResetTransform()
{
	m_Target->SetTransform(D2D1::Matrix3x2F::Identity());
}

bool Canvas::SetTarget(RenderTexture* texture)
{
	auto bitmap = texture->GetBitmap();
	if (bitmap->m_Segments.size() == 0) return false;

	auto image = bitmap->m_Segments[0].GetBitmap();
	m_Target->SetTarget(image);
	return true;
}

void Canvas::ResetTarget()
{
	m_Target->SetTarget(m_TargetBitmap.Get());
}
void Canvas::SetAntiAliasing(bool enable)
{
	m_Target->SetAntialiasMode(enable ? D2D1_ANTIALIAS_MODE_PER_PRIMITIVE : D2D1_ANTIALIAS_MODE_ALIASED);
}

void Canvas::SetTextAntiAliasing(bool enable)
{
	// TODO: Add support for D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE?
	m_TextAntiAliasing = enable;
	m_Target->SetTextAntialiasMode(enable ? D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE : D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
}

void Canvas::Clear(const D2D1_COLOR_F& color)
{
	if (!m_Target) return;

	m_Target->Clear(color);
}
void Canvas::DrawTextW(const std::wstring& srcStr, const TextFormat& format, const D2D1_RECT_F& rect,
	const D2D1_COLOR_F& color, bool applyInlineFormatting)
{
	Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> solidBrush;
	HRESULT hr = m_Target->CreateSolidColorBrush(color, solidBrush.GetAddressOf());
	if (FAILED(hr)) return;

	TextFormatD2D& formatD2D = (TextFormatD2D&)format;

	static std::wstring str;
	str = srcStr;
	formatD2D.ApplyInlineCase(str);

	if (!formatD2D.CreateLayout(
		m_Target.Get(),
		str,
		rect.right - rect.left,
		rect.bottom - rect.top,
		!m_AccurateText && m_TextAntiAliasing)) return;

	D2D1_POINT_2F drawPosition;
	drawPosition.x = [&]()
	{
		if (!m_AccurateText)
		{
			const float xOffset = formatD2D.m_TextFormat->GetFontSize() / 6.0f;
			switch (formatD2D.GetHorizontalAlignment())
			{
			case HorizontalAlignment::Left: return rect.left + xOffset;
			case HorizontalAlignment::Right: return rect.left - xOffset;
			}
		}

		return rect.left;
	} ();

	drawPosition.y = [&]()
	{
		// GDI+ compatibility.
		float yPos = rect.top - formatD2D.m_LineGap;
		switch (formatD2D.GetVerticalAlignment())
		{
		case VerticalAlignment::Bottom: yPos -= formatD2D.m_ExtraHeight; break;
		case VerticalAlignment::Center: yPos -= formatD2D.m_ExtraHeight / 2.0f; break;
		}

		return yPos;
	} ();

	if (formatD2D.m_Trimming)
	{
		D2D1_RECT_F clipRect = rect;

		if (m_CanUseAxisAlignClip)
		{
			m_Target->PushAxisAlignedClip(clipRect, D2D1_ANTIALIAS_MODE_ALIASED);
		}
		else
		{
			const D2D1_LAYER_PARAMETERS1 layerParams =
				D2D1::LayerParameters1(clipRect, nullptr, D2D1_ANTIALIAS_MODE_ALIASED);
			m_Target->PushLayer(layerParams, nullptr);
		}
	}

	// When different "effects" are used with inline coloring options, we need to
	// remove the previous inline coloring, then reapply them (if needed) - instead
	// of destroying/recreating the text layout.
	UINT32 strLen = (UINT32)str.length();
	formatD2D.ResetInlineColoring(solidBrush.Get(), strLen);
	if (applyInlineFormatting)
	{
		formatD2D.ApplyInlineColoring(m_Target.Get(), &drawPosition);

		// Draw any 'shadow' effects
		formatD2D.ApplyInlineShadow(m_Target.Get(), solidBrush.Get(), strLen, drawPosition);
	}

	m_Target->DrawTextLayout(drawPosition, formatD2D.m_TextLayout.Get(), solidBrush.Get());

	if (applyInlineFormatting)
	{
		// Inline gradients require the drawing position, so in case that position
		// changes, we need a way to reset it after drawing time so on the next
		// iteration it will know the correct position.
		formatD2D.ResetGradientPosition(&drawPosition);
	}

	if (formatD2D.m_Trimming)
	{
		if (m_CanUseAxisAlignClip)
		{
			m_Target->PopAxisAlignedClip();
		}
		else
		{
			m_Target->PopLayer();
		}
	}
}
bool Canvas::MeasureTextW(const std::wstring& str, const TextFormat& format, D2D1_SIZE_F& size)
{
	TextFormatD2D& formatD2D = (TextFormatD2D&)format;

	static std::wstring formatStr;
	formatStr = str;
	formatD2D.ApplyInlineCase(formatStr);

	const DWRITE_TEXT_METRICS metrics = formatD2D.GetMetrics(formatStr, !m_AccurateText);
	size.width = metrics.width;
	size.height = metrics.height;
	return true;
}

bool Canvas::MeasureTextLinesW(const std::wstring& str, const TextFormat& format, D2D1_SIZE_F& size, UINT32& lines)
{
	TextFormatD2D& formatD2D = (TextFormatD2D&)format;
	formatD2D.m_TextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP);

	static std::wstring formatStr;
	formatStr = str;
	formatD2D.ApplyInlineCase(formatStr);

	const DWRITE_TEXT_METRICS metrics = formatD2D.GetMetrics(formatStr, !m_AccurateText, size.width);
	size.width = metrics.width;
	size.height = metrics.height;
	lines = metrics.lineCount;

	if (size.height > 0.0f)
	{
		// GDI+ draws multi-line text even though the last line may be clipped slightly at the
		// bottom. This is a workaround to emulate that behaviour.
		size.height += 1.0f;
	}
	else
	{
		// GDI+ compatibility: Zero height text has no visible lines.
		lines = 0U;
	}
	return true;
}

void Canvas::DrawBitmap(const D2DBitmap* bitmap, const D2D1_RECT_F& dstRect, const D2D1_RECT_F& srcRect)
{
	auto& segments = bitmap->m_Segments;
	for (auto seg : segments)
	{
		const auto rSeg = seg.GetRect();
		D2D1_RECT_F rSrc = (rSeg.left < rSeg.right && rSeg.top < rSeg.bottom) ?
			D2D1::RectF(
				max(rSeg.left, srcRect.left),
				max(rSeg.top, srcRect.top),
				min(rSeg.right + rSeg.left, srcRect.right),
				min(rSeg.bottom + rSeg.top, srcRect.bottom)) :
			D2D1::RectF();
		if (rSrc.left == rSrc.right || rSrc.top == rSrc.bottom) continue;

		const D2D1_RECT_F rDst = D2D1::RectF(
			(rSrc.left   - srcRect.left) / (srcRect.right  - srcRect.left) * (dstRect.right  - dstRect.left) + dstRect.left,
			(rSrc.top    - srcRect.top)  / (srcRect.bottom - srcRect.top)  * (dstRect.bottom - dstRect.top)  + dstRect.top,
			(rSrc.right  - srcRect.left) / (srcRect.right  - srcRect.left) * (dstRect.right  - dstRect.left) + dstRect.left,
			(rSrc.bottom - srcRect.top)  / (srcRect.bottom - srcRect.top)  * (dstRect.bottom - dstRect.top)  + dstRect.top);

		while (rSrc.top >= m_MaxBitmapSize)
		{
			rSrc.bottom -= m_MaxBitmapSize;
			rSrc.top -= m_MaxBitmapSize;
		}

		while (rSrc.left >= m_MaxBitmapSize)
		{
			rSrc.right -= m_MaxBitmapSize;
			rSrc.left -= m_MaxBitmapSize;
		}

		m_Target->DrawBitmap(seg.GetBitmap(), rDst, 1.0f, D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC, &rSrc);
	}
}

void Canvas::DrawTiledBitmap(const D2DBitmap* bitmap, const D2D1_RECT_F& dstRect, const D2D1_RECT_F& srcRect)
{
	const FLOAT width = (FLOAT)bitmap->m_Width;
	const FLOAT height = (FLOAT)bitmap->m_Height;

	FLOAT x = dstRect.left;
	FLOAT y = dstRect.top;

	while (y < dstRect.bottom)
	{
		const FLOAT w = (dstRect.right - x) > width ? width : (dstRect.right - x);
		const FLOAT h = (dstRect.bottom - y) > height ? height : (dstRect.bottom - y);

		const auto dst = D2D1::RectF(x, y, x + w, y + h);
		const auto src = D2D1::RectF(0.0f, 0.0f, w, h);
		DrawBitmap(bitmap, dst, src);

		x += width;
		if (x >= dstRect.right && y < dstRect.bottom)
		{
			x = dstRect.left;
			y += height;
		}
	}
}

void Canvas::DrawMaskedBitmap(const D2DBitmap* bitmap, const D2DBitmap* maskBitmap, const D2D1_RECT_F& dstRect,
	const D2D1_RECT_F& srcRect, const D2D1_RECT_F& srcRect2)
{
	if (!bitmap || !maskBitmap) return;

	// Create bitmap brush from original |bitmap|.
	Microsoft::WRL::ComPtr<ID2D1BitmapBrush1> brush;
	D2D1_BITMAP_BRUSH_PROPERTIES1 propertiesXClampYClamp = D2D1::BitmapBrushProperties1(
		D2D1_EXTEND_MODE_CLAMP,
		D2D1_EXTEND_MODE_CLAMP,
		D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC);

	const FLOAT width = (FLOAT)bitmap->m_Width;
	const FLOAT height = (FLOAT)bitmap->m_Height;

	auto getRectSubRegion = [&width, &height](const D2D1_RECT_F& r1, const D2D1_RECT_F& r2) -> D2D1_RECT_F
	{
		return D2D1::RectF(
			r1.left / width * r2.right + r2.left,
			r1.top / height * r2.bottom + r2.top,
			(r1.right - r1.left) / width * r2.right,
			(r1.bottom - r1.top) / height * r2.bottom);
	};

	for (auto bseg : bitmap->m_Segments)
	{
		const auto rSeg = bseg.GetRect();
		const auto rDst = getRectSubRegion(rSeg, dstRect);
		const auto rSrc = getRectSubRegion(rSeg, srcRect);

		FLOAT s2Width = srcRect2.right - srcRect2.left;
		FLOAT s2Height = srcRect2.bottom - srcRect2.top;

		// "Move" and "scale" the |bitmap| to match the destination.
		D2D1_MATRIX_3X2_F translateMask = D2D1::Matrix3x2F::Translation(-srcRect2.left, -srcRect2.top);
		D2D1_MATRIX_3X2_F translate = D2D1::Matrix3x2F::Translation(rDst.left, rDst.top);
		D2D1_MATRIX_3X2_F scale = D2D1::Matrix3x2F::Scale(
			D2D1::SizeF((rDst.right - rDst.left) / s2Width, (rDst.bottom - rDst.top) / s2Height));
		D2D1_BRUSH_PROPERTIES brushProps = D2D1::BrushProperties(1.0f, translateMask * scale * translate);

		HRESULT hr = m_Target->CreateBitmapBrush(
			bseg.GetBitmap(),
			propertiesXClampYClamp,
			brushProps,
			brush.ReleaseAndGetAddressOf());
		if (FAILED(hr)) return;

		const auto aaMode = m_Target->GetAntialiasMode();
		m_Target->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); // required

		for (auto mseg : maskBitmap->m_Segments)
		{
			const auto rmSeg = mseg.GetRect();
			const auto rmDst = getRectSubRegion(rmSeg, dstRect);
			const auto rmSrc = getRectSubRegion(rmSeg, srcRect);

			// If no overlap, don't draw
			if ((rmDst.left < (rDst.left + rDst.right) &&
				(rmDst.right + rmDst.left) > rDst.left &&
				rmDst.top > (rmDst.top + rmDst.bottom) &&
				(rmDst.top + rmDst.bottom) < rmDst.top)) continue;

			m_Target->FillOpacityMask(
				mseg.GetBitmap(),
				brush.Get(),
				D2D1_OPACITY_MASK_CONTENT_GRAPHICS,
				&rDst,
				&rSrc);
		}

		m_Target->SetAntialiasMode(aaMode);
	}
}

void Canvas::FillRectangle(const D2D1_RECT_F& rect, const D2D1_COLOR_F& color)
{
	Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> solidBrush;
	HRESULT hr = m_Target->CreateSolidColorBrush(color, solidBrush.GetAddressOf());
	if (SUCCEEDED(hr))
	{
		m_Target->FillRectangle(rect, solidBrush.Get());
	}
}

void Canvas::FillGradientRectangle(const D2D1_RECT_F& rect, const D2D1_COLOR_F& color1, const D2D1_COLOR_F& color2, const FLOAT& angle)
{
	// D2D requires 2 points to draw the gradient along where GDI+ just requires a rectangle. To
	// mimic GDI+ for SolidColor2, we have to find and swap the starting and ending points of where
	// the gradient touches edge of the bounding rectangle. Normally we would offset the ending
	// point by 180, but we do this on starting point for SolidColor2. This differs from the other
	// D2D gradient options below:
	//  Gfx::TextInlineFormat_GradientColor::BuildGradientBrushes
	//  Gfx::Shape::CreateLinearGradient
	D2D1_POINT_2F start = Util::FindEdgePoint(angle + 180.0f, rect.left, rect.top, rect.right, rect.bottom);
	D2D1_POINT_2F end = Util::FindEdgePoint(angle, rect.left, rect.top, rect.right, rect.bottom);

	Microsoft::WRL::ComPtr<ID2D1GradientStopCollection> pGradientStops;

	D2D1_GRADIENT_STOP gradientStops[2];
	gradientStops[0].color = color1;
	gradientStops[0].position = 0.0f;
	gradientStops[1].color = color2;
	gradientStops[1].position = 1.0f;

	HRESULT hr = m_Target->CreateGradientStopCollection(
		gradientStops,
		2U,
		D2D1_GAMMA_2_2,
		D2D1_EXTEND_MODE_CLAMP,
		pGradientStops.GetAddressOf());
	if (FAILED(hr)) return;

	Microsoft::WRL::ComPtr<ID2D1LinearGradientBrush> brush;
	hr = m_Target->CreateLinearGradientBrush(
		D2D1::LinearGradientBrushProperties(start, end),
		pGradientStops.Get(),
		brush.GetAddressOf());
	if (FAILED(hr)) return;

	m_Target->FillRectangle(rect, brush.Get());
}

void Canvas::DrawLine(const D2D1_COLOR_F& color, FLOAT x1, FLOAT y1, FLOAT x2, FLOAT y2, FLOAT strokeWidth)
{
	Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> solidBrush;
	HRESULT hr = m_Target->CreateSolidColorBrush(color, solidBrush.GetAddressOf());
	if (FAILED(hr)) return;

	m_Target->DrawLine(D2D1::Point2F(x1, y1), D2D1::Point2F(x2, y2), solidBrush.Get(), strokeWidth);
}

void Canvas::DrawGeometry(Shape& shape, int xPos, int yPos)
{
	D2D1_MATRIX_3X2_F worldTransform;
	m_Target->GetTransform(&worldTransform);
	m_Target->SetTransform(
		shape.GetShapeMatrix() *
		D2D1::Matrix3x2F::Translation((FLOAT)xPos, (FLOAT)yPos) *
		worldTransform);

	auto fill = shape.GetFillBrush(m_Target.Get());
	if (fill)
	{
		m_Target->FillGeometry(shape.m_Shape.Get(), fill.Get());
	}

	auto stroke = shape.GetStrokeFillBrush(m_Target.Get());
	if (stroke)
	{
		m_Target->DrawGeometry(
			shape.m_Shape.Get(),
			stroke.Get(),
			shape.m_StrokeWidth,
			shape.m_StrokeStyle.Get());
	}

	m_Target->SetTransform(worldTransform);
}

HRESULT Canvas::CreateRenderTarget()
{
	HRESULT hr = E_FAIL;
	if (c_D2DDevice)
	{
		c_D2DDevice->ClearResources();

		hr = c_D2DDevice->CreateDeviceContext(
			D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS,
			m_Target.ReleaseAndGetAddressOf());
		if (FAILED(hr))
		{
			hr = c_D2DDevice->CreateDeviceContext(
				D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
				m_Target.ReleaseAndGetAddressOf());
		}
	}

	// Hardware accelerated targets have a hard limit to the size of bitmaps they can support.
	// The size will depend on the D3D feature level of the driver used. The WARP software
	// renderer has a limit of 16MP (16*1024*1024 = 16777216).

	// https://docs.microsoft.com/en-us/windows/desktop/direct3d11/overviews-direct3d-11-devices-downlevel-intro#overview-for-each-feature-level
	// Max Texture Dimension
	// D3D_FEATURE_LEVEL_11_1 = 16348
	// D3D_FEATURE_LEVEL_11_0 = 16348
	// D3D_FEATURE_LEVEL_10_1 = 8192
	// D3D_FEATURE_LEVEL_10_0 = 8192
	// D3D_FEATURE_LEVEL_9_3  = 4096
	// D3D_FEATURE_LEVEL_9_2  = 2048
	// D3D_FEATURE_LEVEL_9_1  = 2048

	if (SUCCEEDED(hr))
	{
		m_MaxBitmapSize = m_Target->GetMaximumBitmapSize();
	}

	return hr;
}

bool Canvas::CreateTargetBitmap(UINT32 width, UINT32 height)
{
	HRESULT hr = m_SwapChain->GetBuffer(0U, IID_PPV_ARGS(m_BackBuffer.GetAddressOf()));
	if (FAILED(hr)) return LogComError(hr);

	D2D1_BITMAP_PROPERTIES1 bProps = D2D1::BitmapProperties1(
		D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
		D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED));

	hr = m_Target->CreateBitmapFromDxgiSurface(
		m_BackBuffer.Get(),
		&bProps,
		m_TargetBitmap.GetAddressOf());
	if (FAILED(hr)) return LogComError(hr);

	m_Target->SetTarget(m_TargetBitmap.Get());
	return true;
}

}  // namespace Gfx




What you need:



Microsoft::WRL::ComPtr<ID3D11Device> Canvas::c_D3DDevice;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> Canvas::c_D3DContext;
Microsoft::WRL::ComPtr<ID2D1Device> Canvas::c_D2DDevice;
Microsoft::WRL::ComPtr<IDXGIDevice1> Canvas::c_DxgiDevice;
Microsoft::WRL::ComPtr<ID2D1Factory1> Canvas::c_D2DFactory;
Microsoft::WRL::ComPtr<IDWriteFactory1> Canvas::c_DWFactory;
Microsoft::WRL::ComPtr<IWICImagingFactory> Canvas::c_WICFactory;
.
.
.
 


Add the Context structure to Common / Gfx / Canvas.h:



Context
.
.
.
class Canvas
{
public:

	struct Context
	{
		Microsoft::WRL::ComPtr<ID3D11Device> D3DDevice;
		Microsoft::WRL::ComPtr<ID3D11DeviceContext> D3DContext;
		Microsoft::WRL::ComPtr<ID2D1Device> D2DDevice;
		Microsoft::WRL::ComPtr<IDXGIDevice1> DxgiDevice;
		Microsoft::WRL::ComPtr<ID2D1Factory1> D2DFactory;
		Microsoft::WRL::ComPtr<IDWriteFactory1> DWFactory;
		Microsoft::WRL::ComPtr<IWICImagingFactory> WICFactory;
		Microsoft::WRL::ComPtr<ID2D1DeviceContext> Target;
		Microsoft::WRL::ComPtr<IDXGISwapChain1> SwapChain;
		Microsoft::WRL::ComPtr<IDXGISurface1> BackBuffer;
		Microsoft::WRL::ComPtr<ID2D1Bitmap1> TargetBitmap;
		int W;
		int H;
		UINT32 MaxBitmapSize;
		bool IsDrawing;
		bool EnableDrawAfterGdi;
		bool AccurateText;
		bool TextAntiAliasing;
		bool CanUseAxisAlignClip;
	};

	Context GetContext();
	.
	.
	.
};




Common / Canvas.cpp
.
.
.
Canvas::Context Canvas::GetContext()
{
	Context context;
	context.D2DDevice = c_D2DDevice;
	context.D2DFactory = c_D2DFactory;
	context.D3DContext = c_D3DContext;
	context.D3DDevice = c_D3DDevice;
	context.DWFactory = c_DWFactory;
	context.WICFactory = c_WICFactory;
	context.DxgiDevice = c_DxgiDevice;
	context.AccurateText = m_AccurateText;
	context.BackBuffer = m_BackBuffer;
	context.CanUseAxisAlignClip = m_CanUseAxisAlignClip;
	context.EnableDrawAfterGdi = m_EnableDrawAfterGdi;
	context.H = m_H;
	context.IsDrawing = m_IsDrawing;
	context.MaxBitmapSize = m_MaxBitmapSize;
	context.SwapChain = m_SwapChain;
	context.Target = m_Target;
	context.TargetBitmap = m_TargetBitmap;
	context.TextAntiAliasing = m_TextAntiAliasing;
	context.W = m_W;
	return context;
}
.
.
.




Let's create a new meter - Canvas:



Library / MeterCanvas.h
#ifndef __METERCANVAS_H__
#define __METERCANVAS_H__

#include "Meter.h"

class MeterCanvas : public Meter
{
public:
	MeterCanvas(Skin* skin, const WCHAR* name);

	UINT GetTypeID() override { return TypeID<MeterCanvas>(); }

	virtual bool Update();
	virtual bool Draw(Gfx::Canvas& canvas);

	bool HitTest(int x, int y);

	~MeterCanvas();
};
#endif




Library / MeterCanvas.cpp
#include "StdAfx.h"
#include "MeterCanvas.h"
#include "Logger.h"
#include "windows.h"
#include <iostream>
#include "../Common/Gfx/Canvas.h"
#include "../Common/Gfx/Util/D2DUtil.h"
MeterCanvas::MeterCanvas(Skin* skin, const WCHAR* name) : Meter(skin, name)
{
	Meter::Initialize();
}

bool MeterCanvas::Update()
{
	return Meter::Update();
}

bool MeterCanvas::Draw(Gfx::Canvas& canvas)
{
	return Meter::Draw(canvas);
}
bool MeterCanvas::HitTest(int x, int y)
{
	return Meter::HitTest(x, y);
}

MeterCanvas::~MeterCanvas()
{
	
}





Let's add the ability to use meter:



Library / Meter.cpp
#include "MeterCanvas.h"
.
.
.
Meter* Meter::Create(const WCHAR* meter, Skin* skin, const WCHAR* name)
{
	if (_wcsicmp(L"STRING", meter) == 0)
	{
		return new MeterString(skin, name);
	}
	else if (_wcsicmp(L"IMAGE", meter) == 0)
	{
		return new MeterImage(skin, name);
	}
	else if (_wcsicmp(L"HISTOGRAM", meter) == 0)
	{
		return new MeterHistogram(skin, name);
	}
	else if (_wcsicmp(L"BAR", meter) == 0)
	{
		return new MeterBar(skin, name);
	}
	else if (_wcsicmp(L"BITMAP", meter) == 0)
	{
		return new MeterBitmap(skin, name);
	}
	else if (_wcsicmp(L"LINE", meter) == 0)
	{
		return new MeterLine(skin, name);
	}
	else if (_wcsicmp(L"ROUNDLINE", meter) == 0)
	{
		return new MeterRoundLine(skin, name);
	}
	else if (_wcsicmp(L"ROTATOR", meter) == 0)
	{
		return new MeterRotator(skin, name);
	}
	else if (_wcsicmp(L"BUTTON", meter) == 0)
	{
		return new MeterButton(skin, name);
	}
	else if (_wcsicmp(L"SHAPE", meter) == 0)
	{
		return new MeterShape(skin, name);
	}
	else if (_wcsicmp(L"CANVAS", meter) == 0)
	{
		return new MeterCanvas(skin, name);
	}

	LogErrorF(skin, L"Meter=%s is not valid in [%s]", meter, name);

	return nullptr;
}




Let's try to draw something:



Meter.ini
[Rainmeter]
Update=1000

[Text]
Meter=CANVAS
W=100
H=100




Library / MeterCanvas.cpp
bool MeterCanvas::Draw(Gfx::Canvas& canvas)
{
	Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> brush;
	auto target = canvas.GetContext().Target;
	target->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Red), brush.GetAddressOf());
	target->FillRectangle(D2D1::RectF(100, 100), brush.Get());
	return Meter::Draw(canvas);
}




It works:



image



Now let's write a dll for rendering:



Draw.h
#include <d2d1_1.h>
#include <wrl\client.h>
#include "Context.h"
#pragma once
#ifdef LIBRARY_EXPORTS
#define EXPORT_PLUGIN
#else
#define EXPORT_PLUGIN __declspec(dllexport)
#endif
extern "C" EXPORT_PLUGIN void Draw(Context context);




Context.h
#pragma once
#include <string>
#include <d2d1_1.h>
#include <dwrite_1.h>
#include <wincodec.h>
#include <wrl/client.h>
#include <d3d11.h>
#include <DXGI1_2.h>
struct Context
{
	Microsoft::WRL::ComPtr<ID3D11Device> D3DDevice;
	Microsoft::WRL::ComPtr<ID3D11DeviceContext> D3DContext;
	Microsoft::WRL::ComPtr<ID2D1Device> D2DDevice;
	Microsoft::WRL::ComPtr<IDXGIDevice1> DxgiDevice;
	Microsoft::WRL::ComPtr<ID2D1Factory1> D2DFactory;
	Microsoft::WRL::ComPtr<IDWriteFactory1> DWFactory;
	Microsoft::WRL::ComPtr<IWICImagingFactory> WICFactory;
	Microsoft::WRL::ComPtr<ID2D1DeviceContext> Target;
	Microsoft::WRL::ComPtr<IDXGISwapChain1> SwapChain;
	Microsoft::WRL::ComPtr<IDXGISurface1> BackBuffer;
	Microsoft::WRL::ComPtr<ID2D1Bitmap1> TargetBitmap;
	int W;
	int H;
	UINT32 MaxBitmapSize;
	bool IsDrawing;
	bool EnableDrawAfterGdi;
	bool AccurateText;
	bool TextAntiAliasing;
	bool CanUseAxisAlignClip;
};




Draw.cpp
#include "pch.h"
#include "Draw.h"

#pragma comment(lib,"d2d1.lib")
EXPORT_PLUGIN void Draw(Context context)
{
	Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> brush;
	auto target = context.Target;
	target->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Red), brush.GetAddressOf());
	target->FillRectangle(D2D1::RectF(100, 100), brush.Get());
}




Compile Draw.dll:



Library / MeterCanvas.cpp
HMODULE hlib;
void (*draw)(Gfx::Canvas::Context context);
MeterCanvas::MeterCanvas(Skin* skin, const WCHAR* name) : Meter(skin, name)
{
	hlib = LoadLibrary(L"Draw.dll");
	(FARPROC&)draw = GetProcAddress(hlib, "Draw");
	Meter::Initialize();
}
bool MeterCanvas::Draw(Gfx::Canvas& canvas)
{
	if (draw != nullptr)
		draw(canvas.GetContext());
	return Meter::Draw(canvas);
}
MeterCanvas::~MeterCanvas()
{
	FreeLibrary(hlib);
}




It also works:



image



Let's add the code to select dlls by the user:



std::wstring dll;
.
.
.
MeterCanvas::MeterCanvas(Skin* skin, const WCHAR* name) : Meter(skin, name)
{
	dll = skin->GetParser().GetValue(name, L"Dll", L"");

	hlib = LoadLibrary(dll.c_str());

	(FARPROC&)draw = GetProcAddress(hlib, "Draw");

	Meter::Initialize();
}




Meter.ini

[Rainmeter]
Update=1000

[Text]
Meter=CANVAS
Dll=Draw.dll
W=100
H=100


Why not make a separate bang?

Let's do it.



Let's add Action to the list of bangs.



Library / CommandHandler.h:



enum class Bang
{
	Action,
	.
	.
	.
}


Library / CommandHandler.cpp

const BangInfo s_Bangs[] =
{
	{Bang::Action, L"Action", 1},
	.
	.
	.
}


Library / Skin.cpp:



void Skin::DoBang(Bang bang, const std::vector<std::wstring>& args)
{
	switch (bang)
	{
	case Bang::Action:
		GetMeters()[0]->Action(args[0]);
		break;
	.
	.
	.
}


Library / Meter.h:



class __declspec(novtable) Meter : public Section
{
public:
	virtual void Action(std::wstring arg) {};
	.
	.
	.
}


Library / MeterCanvas.h:



class MeterCanvas : public Meter
{
public:
	virtual void Action(std::wstring arg) {};
	.
	.
	.
}


Meter.ini:



[Rainmeter]
Update=1000

[Text]
Meter=CANVAS
Dll=Draw.dll
W=100
H=100
LeftMouseDownAction=!Action Test


Treatment
Library / MeterCanvas.cpp:



.
.
.
void MeterCanvas::Action(std::wstring arg)
{
	LogDebug(arg.c_str());
}




image



From dll
Library / MeterCanvas.cpp

void (*action)(std::wstring arg);
.
.
.
MeterCanvas::MeterCanvas(Skin* skin, const WCHAR* name) : Meter(skin, name)
{
	hlib = LoadLibrary(L"Draw.dll");
	(FARPROC&)draw = GetProcAddress(hlib, "Draw");
	(FARPROC&)action = GetProcAddress(hlib, "Action");
	Meter::Initialize();
}
void MeterCanvas::Action(std::wstring arg)
{
	if (action != nullptr)
		action(arg);
}


Draw.h

#include <d2d1_1.h>
#include <wrl\client.h>
#include <math.h>
#include "Context.h"
#pragma once
#ifdef LIBRARY_EXPORTS
#define EXPORT_PLUGIN
#else
#define EXPORT_PLUGIN __declspec(dllexport)
#endif
extern "C" EXPORT_PLUGIN void Draw(Context context);

extern "C" EXPORT_PLUGIN void Action(std::wstring arg);




Let's also add a boot action:



Hidden text
Draw.h

#include <d2d1_1.h>
#include <wrl\client.h>
#include <math.h>
#include "Context.h"
#pragma once
#ifdef LIBRARY_EXPORTS
#define EXPORT_PLUGIN
#else
#define EXPORT_PLUGIN __declspec(dllexport)
#endif
extern "C" EXPORT_PLUGIN void Init(Context context);

extern "C" EXPORT_PLUGIN void Draw(Context context);

extern "C" EXPORT_PLUGIN void Action(std::wstring arg);


Library / MeterCanvas.cpp

void (*init)(Gfx::Canvas::Context context);
.
.
.
MeterCanvas::MeterCanvas(Skin* skin, const WCHAR* name) : Meter(skin, name)
{
	dll = skin->GetParser().GetValue(name, L"Dll", L"");

	hlib = LoadLibrary(dll.c_str());

	(FARPROC&)draw = GetProcAddress(hlib, "Draw");

	(FARPROC&)action = GetProcAddress(hlib, "Action");

	(FARPROC&)init = GetProcAddress(hlib, "Init");                         

	init(skin->GetCanvas().GetContext());

	Meter::Initialize();
}




Almost unlimited power!



But something is missing ...



Where is the processing of clicks on the keyboard?



And here she is:







But it doesn't work.

No problem.



Go to rainmeter-master \ x32-Debug \ Plugins

We also need .pdb and .ilk files



Download.





We compile, throw to plugins.



That's all.

You can do anything.



As a bonus - an example:



Video



Source