Direct2D en Rainmeter

Buen día, Habr. Siempre quise libertad en Rainmeter. El mismo tipo de máscaras, complementos simples, no eso.



Hoy te mostraré cómo tener un control total sobre Rainmeter.



Subamos a las fuentes de Rainmeter y veamos cómo todo se dibuja tan bellamente allí ...





Estamos interesados ​​en el archivo Library / Meter.cpp



Texto oculto
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;
}




Ahora averigüemos qué es este Canvas.



Común / 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




Que necesitas:



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;
.
.
.
 


Agregue la estructura de contexto a Common / Gfx / Canvas.h:



Contexto
.
.
.
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();
	.
	.
	.
};




Común / 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;
}
.
.
.




Creemos un nuevo medidor - Canvas:



Biblioteca / 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




Biblioteca / 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()
{
	
}





Agreguemos la capacidad de usar el medidor:



Biblioteca / 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;
}




Intentemos dibujar algo:



Meter.ini
[Rainmeter]
Update=1000

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




Biblioteca / 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);
}




Funciona:



imagen



ahora escribamos una dll para renderizar:



Dibujar.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:



Biblioteca / 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);
}




También funciona:



imagen



agreguemos el código para seleccionar dlls por parte del usuario:



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


¿Por qué no hacer una explosión por separado?

Vamos a hacerlo.



Agreguemos Acción a la lista de explosiones.



Biblioteca / CommandHandler.h:



enum class Bang
{
	Action,
	.
	.
	.
}


Biblioteca / CommandHandler.cpp

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


Biblioteca / Skin.cpp:



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


Biblioteca / Medidor.h:



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


Biblioteca / 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


Tratamiento
Biblioteca / MeterCanvas.cpp:



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




imagen



Desde dll
Biblioteca / 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);
}


Dibujar.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);




Agreguemos también una acción de arranque:



Texto oculto
Dibujar.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);


Biblioteca / 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();
}




¡Poder casi ilimitado!



Pero falta algo ...



¿Dónde está el procesamiento de clics en el teclado?



Y aquí está ella:







Pero no funciona.

No hay problema.



Vaya a rainmeter-master \ x32-Debug \ Plugins

También necesitamos los archivos .pdb y .ilk



Descargar.





Compilamos, lanzamos a complementos.



Eso es todo.

Tu puedes hacer cualquier cosa.



Como beneficio adicional, un ejemplo: fuentes de



video