2025-11-28 00:35:46 +09:00

478 lines
12 KiB
C++

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved
//
//----------------------------------------------------------------------------
#include "Common.h"
#include "RenderTest.h"
#include "TextHelpers.h"
#include "D2DRenderer.h"
namespace
{
DWRITE_MATRIX const g_identityTransform =
{
1, 0,
0, 1,
0, 0
};
D2D1_MATRIX_3X2_F const g_identityMatrix =
{
1, 0,
0, 1,
0, 0
};
}
IRenderer* CreateD2DRenderer(
HWND hwnd,
UINT width,
UINT height,
IDWriteTextFormat* textFormat,
wchar_t const* text
)
{
return new(std::nothrow) D2DRenderer(
hwnd,
width,
height,
textFormat,
text
);
}
D2DRenderer::D2DRenderer(
HWND hwnd,
UINT width,
UINT height,
IDWriteTextFormat* textFormat,
wchar_t const* text
) :
hwnd_(hwnd),
width_(width),
height_(height),
measuringMode_(DWRITE_MEASURING_MODE_NATURAL),
transform_(g_identityTransform),
text_(text),
textFormat_(SafeAcquire(textFormat)),
textLayout_(),
renderTarget_(),
bitmapRenderTarget_(),
renderingParams_(),
backBrush_(),
textBrush_(),
borderBrush_()
{
magnifier_.visible = false;
}
void D2DRenderer::SetFormat(IDWriteTextFormat* format)
{
SafeSet(&textFormat_, format);
FreeTextLayout();
}
void D2DRenderer::SetText(wchar_t const* text)
{
text_ = text;
FreeTextLayout();
}
void D2DRenderer::SetMeasuringMode(DWRITE_MEASURING_MODE measuringMode)
{
measuringMode_ = measuringMode;
FreeTextLayout();
}
void D2DRenderer::SetTransform(DWRITE_MATRIX const& transform)
{
transform_ = transform;
// GDI-compatible layouts depend on the DPI and transform.
if (measuringMode_ != DWRITE_MEASURING_MODE_NATURAL)
FreeTextLayout();
}
void D2DRenderer::SetMagnifier(MagnifierInfo const& magnifier)
{
magnifier_ = magnifier;
SafeRelease(&bitmapRenderTarget_);
}
void D2DRenderer::SetWindowSize(UINT width, UINT height)
{
width_ = width;
height_ = height;
if (renderTarget_ != NULL)
{
if (FAILED(renderTarget_->Resize(D2D1::SizeU(width, height))))
SafeRelease(&renderTarget_);
}
FreeDeviceDependentResources();
UpdateTextOrigin();
}
void D2DRenderer::SetMonitor(HMONITOR monitor)
{
IDWriteRenderingParams* renderingParams = NULL;
if (SUCCEEDED(g_dwriteFactory->CreateMonitorRenderingParams(monitor, &renderingParams)))
{
SafeAttach(&renderingParams_, SafeDetach(&renderingParams));
if (renderTarget_ != NULL)
{
renderTarget_->SetTextRenderingParams(renderingParams_);
}
if (bitmapRenderTarget_ != NULL)
{
bitmapRenderTarget_->SetTextRenderingParams(renderingParams_);
}
}
SafeRelease(&renderingParams);
}
HRESULT D2DRenderer::Draw(HDC hdc)
{
HRESULT hr = DrawInternal();
// If D2D returns D2DERR_RECREATE_TARGET, the client should recreate
// the render target and try rendering the entire frame again.
if (hr == D2DERR_RECREATE_TARGET)
{
FreeDeviceDependentResources();
hr = DrawInternal();
}
return hr;
}
HRESULT D2DRenderer::DrawInternal()
{
HRESULT hr = S_OK;
if (SUCCEEDED(hr))
{
hr = InitializeDeviceDependentResources();
}
if (SUCCEEDED(hr))
{
hr = InitializeTextLayout();
}
renderTarget_->BeginDraw();
renderTarget_->Clear(backColor_);
// Set the render target transform. D2D has its own 3x2 matrix type, but
// it has the same layout as DWRITE_MATRIX.
D2D1_MATRIX_3X2_F matrix;
memcpy(&matrix, &transform_, sizeof(matrix));
renderTarget_->SetTransform(matrix);
// Draw the text layout.
renderTarget_->DrawTextLayout(
textOrigin_,
textLayout_,
textBrush_
);
// Restore the original identity transform.
renderTarget_->SetTransform(g_identityMatrix);
// Draw the magnifier.
if (SUCCEEDED(hr))
{
hr = DrawMagnifier();
}
if (SUCCEEDED(hr))
{
hr = renderTarget_->EndDraw();
}
return hr;
}
// MakeRectF
// Converts left, top, right, bottom in pixels to a D2D rectangle in DIPs.
D2D1_RECT_F MakeRectF(int left, int top, int right, int bottom)
{
D2D1_RECT_F result;
result.left = PixelsToDipsX(left);
result.top = PixelsToDipsY(top);
result.right = PixelsToDipsX(right);
result.bottom = PixelsToDipsY(bottom);
return result;
}
HRESULT D2DRenderer::DrawMagnifier()
{
HRESULT hr = S_OK;
if (!magnifier_.visible)
return hr;
// Get the bounding rectangles, in DIPs, of the magnifier and the focus area.
D2D1_RECT_F magnifierRect = MakeRectF(
magnifier_.magnifierPos.x,
magnifier_.magnifierPos.y,
magnifier_.magnifierPos.x + magnifier_.magnifierSize.cx,
magnifier_.magnifierPos.y + magnifier_.magnifierSize.cy
);
int const focusWidth = magnifier_.magnifierSize.cx / magnifier_.scale;
int const focusHeight = magnifier_.magnifierSize.cy / magnifier_.scale;
D2D1_RECT_F focusRect = MakeRectF(
magnifier_.focusPos.x,
magnifier_.focusPos.y,
magnifier_.focusPos.x + focusWidth,
magnifier_.focusPos.y + focusHeight
);
// Branch depending on the magnifier type:
//
// * Vector means we scale up the text using a render transform. This is enabled only
// for natural layout because GDI-compatible layouts are not resolution-independent.
//
// * Pixel means we render at normal size to an intermediate bitmap and then scale up
// the pixels. This is the only other magnifier type implemented by the D2D renderer.
//
if (magnifier_.type == MagnifierInfo::Vector && measuringMode_ == DWRITE_MEASURING_MODE_NATURAL)
{
renderTarget_->FillRectangle(magnifierRect, backBrush_);
// Clip to the magnifier rectangle.
renderTarget_->PushAxisAlignedClip(magnifierRect, D2D1_ANTIALIAS_MODE_ALIASED);
// Create a transform that translates and scales the focus rect to the magnifier rect.
D2D1_MATRIX_3X2_F zoomTransform;;
zoomTransform._11 = transform_.m11 * magnifier_.scale;
zoomTransform._12 = transform_.m12 * magnifier_.scale;
zoomTransform._21 = transform_.m21 * magnifier_.scale;
zoomTransform._22 = transform_.m22 * magnifier_.scale;
zoomTransform._31 = ((transform_.dx - focusRect.left) * magnifier_.scale) + magnifierRect.left;
zoomTransform._32 = ((transform_.dy - focusRect.top) * magnifier_.scale) + magnifierRect.top;
// Redraw the text layout using the zoom transform.
renderTarget_->SetTransform(zoomTransform);
renderTarget_->DrawTextLayout(textOrigin_, textLayout_, textBrush_);
renderTarget_->SetTransform(g_identityMatrix);
renderTarget_->PopAxisAlignedClip();
}
else
{
// Create the bitmap render target if we haven't already.
if (bitmapRenderTarget_ == NULL)
{
hr = renderTarget_->CreateCompatibleRenderTarget(
D2D1::SizeF(PixelsToDipsX(focusWidth), PixelsToDipsY(focusHeight)),
D2D1::SizeU(focusWidth, focusHeight),
D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_IGNORE),
D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE,
&bitmapRenderTarget_
);
}
if (SUCCEEDED(hr))
{
if (renderingParams_ != NULL)
{
bitmapRenderTarget_->SetTextRenderingParams(renderingParams_);
}
D2D1_MATRIX_3X2_F bitmapTransform;
memcpy(&bitmapTransform, &transform_, sizeof(bitmapTransform));
bitmapTransform._31 -= focusRect.left;
bitmapTransform._32 -= focusRect.top;
bitmapRenderTarget_->BeginDraw();
bitmapRenderTarget_->Clear(backColor_);
bitmapRenderTarget_->SetTransform(bitmapTransform);
bitmapRenderTarget_->DrawTextLayout(textOrigin_, textLayout_, textBrush_);
hr = bitmapRenderTarget_->EndDraw();
}
ID2D1Bitmap* bitmap = NULL;
if (SUCCEEDED(hr))
{
hr = bitmapRenderTarget_->GetBitmap(&bitmap);
}
if (SUCCEEDED(hr))
{
renderTarget_->DrawBitmap(
bitmap,
magnifierRect,
1.0f,
D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR
);
}
SafeRelease(&bitmap);
}
renderTarget_->DrawRectangle(focusRect, borderBrush_, 2.0f);
renderTarget_->DrawRectangle(magnifierRect, borderBrush_, 2.0f);
return hr;
}
HRESULT D2DRenderer::InitializeDeviceDependentResources()
{
HRESULT hr = S_OK;
if (renderTarget_ != NULL)
return hr;
ID2D1HwndRenderTarget* renderTarget = NULL;
ID2D1SolidColorBrush* backBrush = NULL;
ID2D1SolidColorBrush* textBrush = NULL;
ID2D1SolidColorBrush* borderBrush = NULL;
D2D1_RENDER_TARGET_PROPERTIES renderTargetProperties = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
D2D1::PixelFormat(),
static_cast<float>(g_dpiX),
static_cast<float>(g_dpiY)
);
if (SUCCEEDED(hr))
{
hr = g_d2dFactory->CreateHwndRenderTarget(
renderTargetProperties,
D2D1::HwndRenderTargetProperties(hwnd_, D2D1::SizeU(width_, height_)),
&renderTarget
);
}
if (renderingParams_ != NULL)
{
renderTarget->SetTextRenderingParams(renderingParams_);
}
backColor_ = D2D1::ColorF(GetSysColor(COLOR_WINDOW));
if (SUCCEEDED(hr))
{
hr = renderTarget->CreateSolidColorBrush(
backColor_,
&backBrush
);
}
if (SUCCEEDED(hr))
{
hr = renderTarget->CreateSolidColorBrush(
D2D1::ColorF(GetSysColor(COLOR_WINDOWTEXT)),
&textBrush
);
}
if (SUCCEEDED(hr))
{
hr = renderTarget->CreateSolidColorBrush(
D2D1::ColorF(1.0f, 0, 0, 0.5f),
&borderBrush
);
}
// Commit changes.
SafeAttach(&renderTarget_, SafeDetach(&renderTarget));
SafeAttach(&backBrush_, SafeDetach(&backBrush));
SafeAttach(&textBrush_, SafeDetach(&textBrush));
SafeAttach(&borderBrush_, SafeDetach(&borderBrush));
return hr;
}
void D2DRenderer::FreeDeviceDependentResources()
{
SafeRelease(&renderTarget_);
SafeRelease(&bitmapRenderTarget_);
SafeRelease(&backBrush_);
SafeRelease(&textBrush_);
SafeRelease(&borderBrush_);
}
HRESULT D2DRenderer::InitializeTextLayout()
{
HRESULT hr = S_OK;
if (textLayout_ != NULL)
return hr;
if (measuringMode_ == DWRITE_MEASURING_MODE_NATURAL)
{
hr = g_dwriteFactory->CreateTextLayout(
text_,
lstrlenW(text_),
textFormat_,
g_formatWidth,
0, // max height
&textLayout_
);
}
else
{
BOOL useGdiNatural = (measuringMode_ == DWRITE_MEASURING_MODE_GDI_NATURAL);
hr = g_dwriteFactory->CreateGdiCompatibleTextLayout(
text_,
lstrlenW(text_),
textFormat_,
g_formatWidth,
0, // max height
g_dpiY / 96.0f, // pixels per DIP
&transform_,
useGdiNatural,
&textLayout_
);
}
if (SUCCEEDED(hr))
{
hr = UpdateTextOrigin();
}
return hr;
}
HRESULT D2DRenderer::UpdateTextOrigin()
{
HRESULT hr = S_OK;
if (textLayout_ == NULL)
return hr;
// Get the text layout size.
DWRITE_TEXT_METRICS metrics = {};
hr = textLayout_->GetMetrics(&metrics);
// Center the text.
textOrigin_ = D2D1::Point2F(
(PixelsToDipsX(width_) - metrics.width) * 0.5f,
(PixelsToDipsY(height_) - metrics.height) * 0.5f
);
return hr;
}
void D2DRenderer::FreeTextLayout()
{
SafeRelease(&textLayout_);
}