Сегодня я расскажу как получить полную власть над Rainmeter’ом.
Залезем в исходники Rainmeter’а и посмотрим, как же там всё так красиво рисуется…
Нас интересует файл Library/Meter.cpp
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; }
Теперь узнаем что же такое этот Canvas
/* 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
То что надо:
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; . . . Ещё всякое
Добавим структуру Context в Common/Gfx/Canvas.h:
. . . 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(); . . . };
. . . 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; } . . .
Создадим новый meter — Canvas:
#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
#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() { }
Добавим возможность использования meter’а:
#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; }
Попробуем что-то нарисовать:
[Rainmeter] Update=1000 [Text] Meter=CANVAS W=100 H=100
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); }
Работает:

Теперь напишем dll для отрисовки:
#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);
#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; };
#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()); }
Компилируем Draw.dll:
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); }
Тоже работает:

Допишем код, для выбора dll’ки пользователем:
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
Почему бы не сделать отдельный bang?
Сделаем.
Добавим Action в список bang’ов.
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
. . . void MeterCanvas::Action(std::wstring arg) { LogDebug(arg.c_str()); }

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);
Также добавим действие при загрузке:
#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(); }
Почти безграничная власть!
Но чего-то не хватает…
Где же обработка нажатий на клаву?
А вот она:
Но оно не работает.
Не беда.
Заходим в rainmeter-master\x32-Debug\Plugins
Нам нужны еще файлы .pdb и .ilk
Качаем.
Компилируем, кидаем к плагинам.
Вот и всё.
Вы можете вообще всё.
В качестве бонуса — пример:
ссылка на оригинал статьи https://habr.com/ru/post/512510/
Добавить комментарий