aboutsummaryrefslogtreecommitdiff
path: root/src/platform/graphics
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2022-05-15 13:56:40 +0800
committercrupest <crupest@outlook.com>2022-05-15 13:56:40 +0800
commit9e0c9d3499bc50c3534b4dc500d8b5d0b5f22752 (patch)
tree7342f6991771fa31b16fd6a5ed892ff6025f3d05 /src/platform/graphics
parent41de54bad2c0f857821fcc83f41af3334d068b6d (diff)
downloadcru-9e0c9d3499bc50c3534b4dc500d8b5d0b5f22752.tar.gz
cru-9e0c9d3499bc50c3534b4dc500d8b5d0b5f22752.tar.bz2
cru-9e0c9d3499bc50c3534b4dc500d8b5d0b5f22752.zip
...
Diffstat (limited to 'src/platform/graphics')
-rw-r--r--src/platform/graphics/direct2d/Brush.cpp17
-rw-r--r--src/platform/graphics/direct2d/CMakeLists.txt16
-rw-r--r--src/platform/graphics/direct2d/Factory.cpp99
-rw-r--r--src/platform/graphics/direct2d/Font.cpp34
-rw-r--r--src/platform/graphics/direct2d/Geometry.cpp125
-rw-r--r--src/platform/graphics/direct2d/Image.cpp45
-rw-r--r--src/platform/graphics/direct2d/ImageFactory.cpp159
-rw-r--r--src/platform/graphics/direct2d/Painter.cpp183
-rw-r--r--src/platform/graphics/direct2d/Resource.cpp16
-rw-r--r--src/platform/graphics/direct2d/TextLayout.cpp162
-rw-r--r--src/platform/graphics/direct2d/WindowPainter.cpp15
-rw-r--r--src/platform/graphics/direct2d/WindowRenderTarget.cpp81
12 files changed, 952 insertions, 0 deletions
diff --git a/src/platform/graphics/direct2d/Brush.cpp b/src/platform/graphics/direct2d/Brush.cpp
new file mode 100644
index 00000000..27b76202
--- /dev/null
+++ b/src/platform/graphics/direct2d/Brush.cpp
@@ -0,0 +1,17 @@
+#include "cru/platform/graphics/direct2d/Brush.h"
+
+#include "cru/platform/graphics/direct2d/ConvertUtil.h"
+#include "cru/platform/graphics/direct2d/Exception.h"
+#include "cru/platform/graphics/direct2d/Factory.h"
+
+namespace cru::platform::graphics::direct2d {
+D2DSolidColorBrush::D2DSolidColorBrush(DirectGraphicsFactory* factory)
+ : DirectGraphicsResource(factory) {
+ ThrowIfFailed(factory->GetDefaultD2D1DeviceContext()->CreateSolidColorBrush(
+ Convert(color_), &brush_));
+}
+
+void D2DSolidColorBrush::SetColor(const Color& color) {
+ brush_->SetColor(Convert(color));
+}
+} // namespace cru::platform::graphics::direct2d
diff --git a/src/platform/graphics/direct2d/CMakeLists.txt b/src/platform/graphics/direct2d/CMakeLists.txt
new file mode 100644
index 00000000..a9d5900b
--- /dev/null
+++ b/src/platform/graphics/direct2d/CMakeLists.txt
@@ -0,0 +1,16 @@
+add_library(CruPlatformGraphicsDirect2d SHARED
+ Brush.cpp
+ Font.cpp
+ Geometry.cpp
+ Image.cpp
+ ImageFactory.cpp
+ Factory.cpp
+ Painter.cpp
+ Resource.cpp
+ TextLayout.cpp
+ WindowPainter.cpp
+ WindowRenderTarget.cpp
+)
+target_link_libraries(CruPlatformGraphicsDirect2d PUBLIC D3D11 D2d1 DWrite)
+target_link_libraries(CruPlatformGraphicsDirect2d PUBLIC CruWinBase CruPlatformGraphics)
+target_compile_definitions(CruPlatformGraphicsDirect2d PRIVATE CRU_WIN_GRAPHICS_DIRECT_EXPORT_API)
diff --git a/src/platform/graphics/direct2d/Factory.cpp b/src/platform/graphics/direct2d/Factory.cpp
new file mode 100644
index 00000000..c35c53cf
--- /dev/null
+++ b/src/platform/graphics/direct2d/Factory.cpp
@@ -0,0 +1,99 @@
+#include "cru/platform/graphics/direct2d/Factory.h"
+
+#include "cru/common/log/Logger.h"
+#include "cru/platform/graphics/direct2d/Brush.h"
+#include "cru/platform/graphics/direct2d/Exception.h"
+#include "cru/platform/graphics/direct2d/Font.h"
+#include "cru/platform/graphics/direct2d/Geometry.h"
+#include "cru/platform/graphics/direct2d/ImageFactory.h"
+#include "cru/platform/graphics/direct2d/TextLayout.h"
+
+#include <cstdlib>
+#include <utility>
+
+namespace cru::platform::graphics::direct2d {
+
+DirectGraphicsFactory::DirectGraphicsFactory() {
+ // TODO: Detect repeated creation. Because I don't think we can create two d2d
+ // and dwrite factory so we need to prevent the "probably dangerous" behavior.
+
+ UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
+
+#ifdef CRU_DEBUG
+ creation_flags |= D3D11_CREATE_DEVICE_DEBUG;
+#endif
+
+ const D3D_FEATURE_LEVEL feature_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};
+
+ Microsoft::WRL::ComPtr<ID3D11DeviceContext> d3d11_device_context;
+
+ ThrowIfFailed(D3D11CreateDevice(
+ nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, creation_flags,
+ feature_levels, ARRAYSIZE(feature_levels), D3D11_SDK_VERSION,
+ &d3d11_device_, nullptr, &d3d11_device_context));
+
+ Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
+ ThrowIfFailed(d3d11_device_->QueryInterface(dxgi_device.GetAddressOf()));
+
+ ThrowIfFailed(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
+ IID_PPV_ARGS(&d2d1_factory_)));
+
+ ThrowIfFailed(d2d1_factory_->CreateDevice(dxgi_device.Get(), &d2d1_device_));
+
+ d2d1_device_context_ = CreateD2D1DeviceContext();
+
+ // Identify the physical adapter (GPU or card) this device is runs on.
+ Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
+ ThrowIfFailed(dxgi_device->GetAdapter(&dxgi_adapter));
+
+ // Get the factory object that created the DXGI device.
+ ThrowIfFailed(dxgi_adapter->GetParent(IID_PPV_ARGS(&dxgi_factory_)));
+
+ ThrowIfFailed(DWriteCreateFactory(
+ DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory),
+ reinterpret_cast<IUnknown**>(dwrite_factory_.GetAddressOf())));
+
+ ThrowIfFailed(dwrite_factory_->GetSystemFontCollection(
+ &dwrite_system_font_collection_));
+
+ image_factory_ = std::make_unique<WinImageFactory>(this);
+}
+
+DirectGraphicsFactory::~DirectGraphicsFactory() {}
+
+Microsoft::WRL::ComPtr<ID2D1DeviceContext1>
+DirectGraphicsFactory::CreateD2D1DeviceContext() {
+ Microsoft::WRL::ComPtr<ID2D1DeviceContext1> d2d1_device_context;
+ ThrowIfFailed(d2d1_device_->CreateDeviceContext(
+ D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &d2d1_device_context));
+ return d2d1_device_context;
+}
+
+std::unique_ptr<ISolidColorBrush>
+DirectGraphicsFactory::CreateSolidColorBrush() {
+ return std::make_unique<D2DSolidColorBrush>(this);
+}
+
+std::unique_ptr<IGeometryBuilder>
+DirectGraphicsFactory::CreateGeometryBuilder() {
+ return std::make_unique<D2DGeometryBuilder>(this);
+}
+
+std::unique_ptr<IFont> DirectGraphicsFactory::CreateFont(String font_family,
+ float font_size) {
+ return std::make_unique<DWriteFont>(this, std::move(font_family), font_size);
+}
+
+std::unique_ptr<ITextLayout> DirectGraphicsFactory::CreateTextLayout(
+ std::shared_ptr<IFont> font, String text) {
+ return std::make_unique<DWriteTextLayout>(this, std::move(font),
+ std::move(text));
+}
+
+IImageFactory* DirectGraphicsFactory::GetImageFactory() {
+ return image_factory_.get();
+}
+} // namespace cru::platform::graphics::direct2d
diff --git a/src/platform/graphics/direct2d/Font.cpp b/src/platform/graphics/direct2d/Font.cpp
new file mode 100644
index 00000000..afbc9049
--- /dev/null
+++ b/src/platform/graphics/direct2d/Font.cpp
@@ -0,0 +1,34 @@
+#include "cru/platform/graphics/direct2d/Font.h"
+
+#include "cru/common/Format.h"
+#include "cru/platform/graphics/direct2d/Exception.h"
+#include "cru/platform/graphics/direct2d/Factory.h"
+
+#include <array>
+#include <utility>
+
+namespace cru::platform::graphics::direct2d {
+DWriteFont::DWriteFont(DirectGraphicsFactory* factory, String font_family,
+ float font_size)
+ : DirectGraphicsResource(factory), font_family_(std::move(font_family)) {
+ // Get locale
+ std::array<wchar_t, LOCALE_NAME_MAX_LENGTH> buffer;
+ if (!::GetUserDefaultLocaleName(buffer.data(),
+ static_cast<int>(buffer.size())))
+ throw platform::win::Win32Error(
+ ::GetLastError(), u"Failed to get locale when create dwrite font.");
+
+ ThrowIfFailed(factory->GetDWriteFactory()->CreateTextFormat(
+ reinterpret_cast<const wchar_t*>(font_family_.c_str()), nullptr,
+ DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
+ DWRITE_FONT_STRETCH_NORMAL, font_size, buffer.data(), &text_format_));
+
+ ThrowIfFailed(text_format_->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING));
+ ThrowIfFailed(
+ text_format_->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR));
+}
+
+String DWriteFont::GetFontName() { return font_family_; }
+
+float DWriteFont::GetFontSize() { return text_format_->GetFontSize(); }
+} // namespace cru::platform::graphics::direct2d
diff --git a/src/platform/graphics/direct2d/Geometry.cpp b/src/platform/graphics/direct2d/Geometry.cpp
new file mode 100644
index 00000000..b84a901e
--- /dev/null
+++ b/src/platform/graphics/direct2d/Geometry.cpp
@@ -0,0 +1,125 @@
+#include "cru/platform/graphics/direct2d/Geometry.h"
+
+#include "cru/common/platform/win/Exception.h"
+#include "cru/platform/graphics/direct2d/ConvertUtil.h"
+#include "cru/platform/graphics/direct2d/Exception.h"
+#include "cru/platform/graphics/direct2d/Factory.h"
+
+#include <d2d1.h>
+#include <d2d1helper.h>
+
+namespace cru::platform::graphics::direct2d {
+D2DGeometryBuilder::D2DGeometryBuilder(DirectGraphicsFactory* factory)
+ : DirectGraphicsResource(factory) {
+ ThrowIfFailed(factory->GetD2D1Factory()->CreatePathGeometry(&geometry_));
+ ThrowIfFailed(geometry_->Open(&geometry_sink_));
+}
+
+void D2DGeometryBuilder::CheckValidation() {
+ if (!IsValid())
+ throw ReuseException(u"The geometry builder is already disposed.");
+}
+
+Point D2DGeometryBuilder::GetCurrentPosition() {
+ CheckValidation();
+ return current_position_;
+}
+
+void D2DGeometryBuilder::MoveTo(const Point& point) {
+ CheckValidation();
+ geometry_sink_->BeginFigure(Convert(point), D2D1_FIGURE_BEGIN_FILLED);
+ start_point_ = current_position_ = point;
+}
+
+void D2DGeometryBuilder::LineTo(const Point& point) {
+ CheckValidation();
+ geometry_sink_->AddLine(Convert(point));
+ current_position_ = point;
+}
+
+void D2DGeometryBuilder::CubicBezierTo(const Point& start_control_point,
+ const Point& end_control_point,
+ const Point& end_point) {
+ CheckValidation();
+ geometry_sink_->AddBezier(D2D1::BezierSegment(Convert(start_control_point),
+ Convert(end_control_point),
+ Convert(end_point)));
+ current_position_ = end_point;
+}
+
+void D2DGeometryBuilder::QuadraticBezierTo(const Point& control_point,
+ const Point& end_point) {
+ CheckValidation();
+ geometry_sink_->AddQuadraticBezier(
+ D2D1::QuadraticBezierSegment(Convert(control_point), Convert(end_point)));
+ current_position_ = end_point;
+}
+
+void D2DGeometryBuilder::ArcTo(const Point& radius, float angle,
+ bool is_large_arc, bool is_clockwise,
+ const Point& end_point) {
+ CheckValidation();
+ geometry_sink_->AddArc(D2D1::ArcSegment(
+ Convert(end_point), {radius.x, radius.y}, angle,
+ is_clockwise ? D2D1_SWEEP_DIRECTION_CLOCKWISE
+ : D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE,
+ is_large_arc ? D2D1_ARC_SIZE_LARGE : D2D1_ARC_SIZE_SMALL));
+ current_position_ = end_point;
+}
+
+void D2DGeometryBuilder::CloseFigure(bool close) {
+ CheckValidation();
+ geometry_sink_->EndFigure(close ? D2D1_FIGURE_END_CLOSED
+ : D2D1_FIGURE_END_OPEN);
+ current_position_ = start_point_;
+}
+
+std::unique_ptr<IGeometry> D2DGeometryBuilder::Build() {
+ CheckValidation();
+ ThrowIfFailed(geometry_sink_->Close());
+ geometry_sink_ = nullptr;
+ auto geometry =
+ std::make_unique<D2DGeometry>(GetDirectFactory(), std::move(geometry_));
+ geometry_ = nullptr;
+ return geometry;
+}
+
+D2DGeometry::D2DGeometry(DirectGraphicsFactory* factory,
+ Microsoft::WRL::ComPtr<ID2D1Geometry> geometry)
+ : DirectGraphicsResource(factory), geometry_(std::move(geometry)) {}
+
+bool D2DGeometry::FillContains(const Point& point) {
+ BOOL result;
+ ThrowIfFailed(geometry_->FillContainsPoint(
+ Convert(point), D2D1::Matrix3x2F::Identity(), &result));
+ return result != 0;
+}
+
+Rect D2DGeometry::GetBounds() {
+ D2D1_RECT_F bounds;
+ ThrowIfFailed(geometry_->GetBounds(D2D1::Matrix3x2F::Identity(), &bounds));
+ return Convert(bounds);
+}
+
+std::unique_ptr<IGeometry> D2DGeometry::Transform(const Matrix& matrix) {
+ Microsoft::WRL::ComPtr<ID2D1TransformedGeometry> d2d1_geometry;
+ ThrowIfFailed(GetDirectFactory()->GetD2D1Factory()->CreateTransformedGeometry(
+ geometry_.Get(), Convert(matrix), &d2d1_geometry));
+ return std::make_unique<D2DGeometry>(GetDirectFactory(),
+ std::move(d2d1_geometry));
+}
+
+std::unique_ptr<IGeometry> D2DGeometry::CreateStrokeGeometry(float width) {
+ Microsoft::WRL::ComPtr<ID2D1PathGeometry> d2d1_geometry;
+ ThrowIfFailed(
+ GetDirectFactory()->GetD2D1Factory()->CreatePathGeometry(&d2d1_geometry));
+ Microsoft::WRL::ComPtr<ID2D1GeometrySink> d2d1_geometry_sink;
+ ThrowIfFailed(d2d1_geometry->Open(&d2d1_geometry_sink));
+ ThrowIfFailed(
+ geometry_->Widen(width, nullptr, nullptr, d2d1_geometry_sink.Get()));
+ d2d1_geometry_sink->Close();
+
+ return std::make_unique<D2DGeometry>(GetDirectFactory(),
+ std::move(d2d1_geometry));
+}
+} // namespace cru::platform::graphics::direct2d
diff --git a/src/platform/graphics/direct2d/Image.cpp b/src/platform/graphics/direct2d/Image.cpp
new file mode 100644
index 00000000..78cccd6a
--- /dev/null
+++ b/src/platform/graphics/direct2d/Image.cpp
@@ -0,0 +1,45 @@
+#include "cru/platform/graphics/direct2d/Image.h"
+#include <d2d1_1.h>
+#include "cru/common/platform/win/Exception.h"
+#include "cru/platform/graphics/direct2d/ConvertUtil.h"
+#include "cru/platform/graphics/direct2d/Exception.h"
+#include "cru/platform/graphics/direct2d/Factory.h"
+#include "cru/platform/graphics/direct2d/Painter.h"
+
+namespace cru::platform::graphics::direct2d {
+Direct2DImage::Direct2DImage(DirectGraphicsFactory* graphics_factory,
+ Microsoft::WRL::ComPtr<ID2D1Bitmap1> d2d_bitmap)
+ : DirectGraphicsResource(graphics_factory),
+ d2d_bitmap_(std::move(d2d_bitmap)) {
+ Expects(d2d_bitmap_);
+}
+
+Direct2DImage::~Direct2DImage() {}
+
+float Direct2DImage::GetWidth() { return d2d_bitmap_->GetSize().width; }
+
+float Direct2DImage::GetHeight() { return d2d_bitmap_->GetSize().height; }
+
+std::unique_ptr<IImage> Direct2DImage::CreateWithRect(const Rect& rect) {
+ auto device_context = GetDirectFactory()->CreateD2D1DeviceContext();
+ Microsoft::WRL::ComPtr<ID2D1Bitmap1> bitmap;
+ ThrowIfFailed(device_context->CreateBitmap(
+ D2D1::SizeU(rect.width, rect.height), nullptr, 0,
+ D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_TARGET,
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,
+ D2D1_ALPHA_MODE_PREMULTIPLIED)),
+ &bitmap));
+ device_context->SetTarget(bitmap.Get());
+ device_context->BeginDraw();
+ device_context->DrawBitmap(d2d_bitmap_.Get(), Convert(rect));
+ ThrowIfFailed(device_context->EndDraw());
+ return std::make_unique<Direct2DImage>(GetDirectFactory(), std::move(bitmap));
+}
+
+std::unique_ptr<IPainter> Direct2DImage::CreatePainter() {
+ auto device_context = GetDirectFactory()->CreateD2D1DeviceContext();
+ device_context->SetTarget(d2d_bitmap_.Get());
+ return std::make_unique<D2DDeviceContextPainter>(device_context.Detach(),
+ true);
+}
+} // namespace cru::platform::graphics::direct2d
diff --git a/src/platform/graphics/direct2d/ImageFactory.cpp b/src/platform/graphics/direct2d/ImageFactory.cpp
new file mode 100644
index 00000000..7cbc0ad4
--- /dev/null
+++ b/src/platform/graphics/direct2d/ImageFactory.cpp
@@ -0,0 +1,159 @@
+#include "cru/platform/graphics/direct2d/ImageFactory.h"
+#include "cru/common/platform/win/Exception.h"
+#include "cru/common/platform/win/StreamConvert.h"
+#include "cru/platform/Check.h"
+#include "cru/platform/graphics/direct2d/Exception.h"
+#include "cru/platform/graphics/direct2d/Factory.h"
+#include "cru/platform/graphics/direct2d/Image.h"
+
+#include <d2d1_1.h>
+#include <d2d1_1helper.h>
+#include <wincodec.h>
+#include <wrl/client.h>
+
+namespace cru::platform::graphics::direct2d {
+WinImageFactory::WinImageFactory(DirectGraphicsFactory* graphics_factory)
+ : DirectGraphicsResource(graphics_factory) {
+ HRESULT hr =
+ CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(&wic_imaging_factory_));
+ ThrowIfFailed(hr);
+}
+
+WinImageFactory::~WinImageFactory() {}
+
+std::unique_ptr<IImage> WinImageFactory::DecodeFromStream(io::Stream* stream) {
+ auto graphics_factory = GetDirectFactory();
+
+ HRESULT hr;
+
+ Microsoft::WRL::ComPtr<IStream> com_stream(
+ platform::win::ConvertStreamToComStream(stream));
+
+ Microsoft::WRL::ComPtr<IWICBitmapDecoder> wic_bitmap_decoder;
+ hr = wic_imaging_factory_->CreateDecoderFromStream(
+ com_stream.Get(), NULL, WICDecodeMetadataCacheOnDemand,
+ &wic_bitmap_decoder);
+ ThrowIfFailed(hr);
+
+ Microsoft::WRL::ComPtr<IWICBitmapFrameDecode> wic_bitmap_frame_decode;
+ hr = wic_bitmap_decoder->GetFrame(0, &wic_bitmap_frame_decode);
+ ThrowIfFailed(hr);
+
+ auto d2d_context = graphics_factory->GetDefaultD2D1DeviceContext();
+
+ Microsoft::WRL::ComPtr<ID2D1Bitmap1> d2d_image;
+ d2d_context->CreateBitmapFromWicBitmap(
+ wic_bitmap_frame_decode.Get(),
+ D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_TARGET,
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,
+ D2D1_ALPHA_MODE_PREMULTIPLIED)),
+ &d2d_image);
+
+ return std::make_unique<Direct2DImage>(graphics_factory,
+ std::move(d2d_image));
+}
+
+GUID ConvertImageFormatToGUID(ImageFormat format) {
+ GUID format_guid;
+ switch (format) {
+ case ImageFormat::Png:
+ format_guid = GUID_ContainerFormatPng;
+ break;
+ case ImageFormat::Jpeg:
+ format_guid = GUID_ContainerFormatJpeg;
+ break;
+ case ImageFormat::Gif:
+ format_guid = GUID_ContainerFormatGif;
+ break;
+ default:
+ throw Exception(u"Unknown image format");
+ }
+ return format_guid;
+}
+
+void WinImageFactory::EncodeToStream(IImage* image, io::Stream* stream,
+ ImageFormat format, float quality) {
+ auto direct_image = CheckPlatform<Direct2DImage>(image, GetPlatformId());
+
+ Microsoft::WRL::ComPtr<IStream> com_stream(
+ platform::win::ConvertStreamToComStream(stream));
+
+ auto d2d_bitmap = direct_image->GetD2DBitmap();
+ auto size = d2d_bitmap->GetPixelSize();
+ FLOAT dpi_x, dpi_y;
+ d2d_bitmap->GetDpi(&dpi_x, &dpi_y);
+ auto pixel_format = d2d_bitmap->GetPixelFormat();
+ Ensures(pixel_format.format == DXGI_FORMAT_B8G8R8A8_UNORM);
+ Ensures(pixel_format.alphaMode == D2D1_ALPHA_MODE_PREMULTIPLIED);
+
+ Microsoft::WRL::ComPtr<ID2D1Bitmap1> cpu_bitmap;
+ ThrowIfFailed(GetDirectFactory()->GetDefaultD2D1DeviceContext()->CreateBitmap(
+ size, nullptr, 0,
+ D2D1::BitmapProperties1(
+ D2D1_BITMAP_OPTIONS_CANNOT_DRAW | D2D1_BITMAP_OPTIONS_CPU_READ,
+ pixel_format, dpi_x, dpi_y),
+ &cpu_bitmap));
+
+ ThrowIfFailed(cpu_bitmap->CopyFromBitmap(nullptr, d2d_bitmap.Get(), nullptr));
+
+ D2D1_MAPPED_RECT mapped_rect;
+ ThrowIfFailed(cpu_bitmap->Map(D2D1_MAP_OPTIONS_READ, &mapped_rect));
+
+ Microsoft::WRL::ComPtr<IWICBitmap> wic_bitmap;
+ ThrowIfFailed(wic_imaging_factory_->CreateBitmapFromMemory(
+ size.width, size.height, GUID_WICPixelFormat32bppPBGRA, mapped_rect.pitch,
+ mapped_rect.pitch * size.height, mapped_rect.bits, &wic_bitmap));
+
+ ThrowIfFailed(cpu_bitmap->Unmap());
+
+ Microsoft::WRL::ComPtr<IWICBitmapEncoder> wic_bitmap_encoder;
+
+ ThrowIfFailed(wic_imaging_factory_->CreateEncoder(
+ ConvertImageFormatToGUID(format), nullptr, &wic_bitmap_encoder));
+
+ ThrowIfFailed(wic_bitmap_encoder->Initialize(com_stream.Get(),
+ WICBitmapEncoderNoCache));
+
+ Microsoft::WRL::ComPtr<IWICBitmapFrameEncode> wic_bitmap_frame_encode;
+ Microsoft::WRL::ComPtr<IPropertyBag2> property_bag;
+ ThrowIfFailed(wic_bitmap_encoder->CreateNewFrame(&wic_bitmap_frame_encode,
+ &property_bag));
+
+ if (format == ImageFormat::Jpeg) {
+ PROPBAG2 option = {0};
+ option.pstrName = const_cast<wchar_t*>(L"ImageQuality");
+ VARIANT varValue;
+ VariantInit(&varValue);
+ varValue.vt = VT_R4;
+ varValue.fltVal = quality;
+ ThrowIfFailed(property_bag->Write(1, &option, &varValue));
+ }
+
+ ThrowIfFailed(wic_bitmap_frame_encode->Initialize(property_bag.Get()));
+ ThrowIfFailed(wic_bitmap_frame_encode->SetResolution(dpi_x, dpi_y));
+ ThrowIfFailed(wic_bitmap_frame_encode->WriteSource(wic_bitmap.Get(), NULL));
+ ThrowIfFailed(wic_bitmap_frame_encode->Commit());
+
+ ThrowIfFailed(wic_bitmap_encoder->Commit());
+}
+
+std::unique_ptr<IImage> WinImageFactory::CreateBitmap(int width, int height) {
+ if (width <= 0) throw Exception(u"Bitmap width must be greater than 0.");
+ if (height <= 0) throw Exception(u"Bitmap height must be greater than 0.");
+
+ auto graphics_factory = GetDirectFactory();
+
+ Microsoft::WRL::ComPtr<ID2D1Bitmap1> bitmap;
+
+ auto d2d_context = graphics_factory->GetDefaultD2D1DeviceContext();
+ ThrowIfFailed(d2d_context->CreateBitmap(
+ D2D1::SizeU(width, height), nullptr, 0,
+ D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_TARGET,
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,
+ D2D1_ALPHA_MODE_PREMULTIPLIED)),
+ &bitmap));
+
+ return std::make_unique<Direct2DImage>(graphics_factory, std::move(bitmap));
+}
+} // namespace cru::platform::graphics::direct2d
diff --git a/src/platform/graphics/direct2d/Painter.cpp b/src/platform/graphics/direct2d/Painter.cpp
new file mode 100644
index 00000000..dea3ba03
--- /dev/null
+++ b/src/platform/graphics/direct2d/Painter.cpp
@@ -0,0 +1,183 @@
+#include "cru/platform/graphics/direct2d/Painter.h"
+
+#include "cru/common/log/Logger.h"
+#include "cru/platform/Check.h"
+#include "cru/platform/graphics/direct2d/Brush.h"
+#include "cru/platform/graphics/direct2d/ConvertUtil.h"
+#include "cru/platform/graphics/direct2d/Exception.h"
+#include "cru/platform/graphics/direct2d/Geometry.h"
+#include "cru/platform/graphics/direct2d/Image.h"
+#include "cru/platform/graphics/direct2d/TextLayout.h"
+
+#include <type_traits>
+
+namespace cru::platform::graphics::direct2d {
+D2DDeviceContextPainter::D2DDeviceContextPainter(
+ ID2D1DeviceContext1* device_context, bool release) {
+ Expects(device_context);
+ device_context_ = device_context;
+ release_ = release;
+ device_context->BeginDraw();
+}
+
+D2DDeviceContextPainter::~D2DDeviceContextPainter() {
+ if (is_drawing_) {
+ CRU_LOG_INFO(u"You may forget to call EndDraw before destroying painter.");
+ }
+
+ if (release_) {
+ device_context_->Release();
+ device_context_ = nullptr;
+ }
+}
+
+platform::Matrix D2DDeviceContextPainter::GetTransform() {
+ CheckValidation();
+ D2D1_MATRIX_3X2_F m;
+ device_context_->GetTransform(&m);
+ return Convert(m);
+}
+
+void D2DDeviceContextPainter::SetTransform(const platform::Matrix& matrix) {
+ CheckValidation();
+ device_context_->SetTransform(Convert(matrix));
+}
+
+void D2DDeviceContextPainter::ConcatTransform(const Matrix& matrix) {
+ SetTransform(GetTransform() * matrix);
+}
+
+void D2DDeviceContextPainter::Clear(const Color& color) {
+ CheckValidation();
+ device_context_->Clear(Convert(color));
+}
+
+void D2DDeviceContextPainter::DrawLine(const Point& start, const Point& end,
+ IBrush* brush, float width) {
+ CheckValidation();
+ const auto b = CheckPlatform<ID2DBrush>(brush, GetPlatformId());
+ device_context_->DrawLine(Convert(start), Convert(end),
+ b->GetD2DBrushInterface(), width);
+}
+
+void D2DDeviceContextPainter::StrokeRectangle(const Rect& rectangle,
+ IBrush* brush, float width) {
+ CheckValidation();
+ const auto b = CheckPlatform<ID2DBrush>(brush, GetPlatformId());
+ device_context_->DrawRectangle(Convert(rectangle), b->GetD2DBrushInterface(),
+ width);
+}
+
+void D2DDeviceContextPainter::FillRectangle(const Rect& rectangle,
+ IBrush* brush) {
+ CheckValidation();
+ const auto b = CheckPlatform<ID2DBrush>(brush, GetPlatformId());
+ device_context_->FillRectangle(Convert(rectangle), b->GetD2DBrushInterface());
+}
+
+void D2DDeviceContextPainter::StrokeEllipse(const Rect& outline_rect,
+ IBrush* brush, float width) {
+ CheckValidation();
+ const auto b = CheckPlatform<ID2DBrush>(brush, GetPlatformId());
+ device_context_->DrawEllipse(
+ D2D1::Ellipse(Convert(outline_rect.GetCenter()),
+ outline_rect.width / 2.0f, outline_rect.height / 2.0f),
+ b->GetD2DBrushInterface(), width);
+}
+void D2DDeviceContextPainter::FillEllipse(const Rect& outline_rect,
+ IBrush* brush) {
+ CheckValidation();
+ const auto b = CheckPlatform<ID2DBrush>(brush, GetPlatformId());
+ device_context_->FillEllipse(
+ D2D1::Ellipse(Convert(outline_rect.GetCenter()),
+ outline_rect.width / 2.0f, outline_rect.height / 2.0f),
+ b->GetD2DBrushInterface());
+}
+
+void D2DDeviceContextPainter::StrokeGeometry(IGeometry* geometry, IBrush* brush,
+ float width) {
+ CheckValidation();
+ const auto g = CheckPlatform<D2DGeometry>(geometry, GetPlatformId());
+ const auto b = CheckPlatform<ID2DBrush>(brush, GetPlatformId());
+ device_context_->DrawGeometry(g->GetComInterface(), b->GetD2DBrushInterface(),
+ width);
+}
+
+void D2DDeviceContextPainter::FillGeometry(IGeometry* geometry, IBrush* brush) {
+ CheckValidation();
+ const auto g = CheckPlatform<D2DGeometry>(geometry, GetPlatformId());
+ const auto b = CheckPlatform<ID2DBrush>(brush, GetPlatformId());
+ device_context_->FillGeometry(g->GetComInterface(),
+ b->GetD2DBrushInterface());
+}
+
+void D2DDeviceContextPainter::DrawText(const Point& offset,
+ ITextLayout* text_layout,
+ IBrush* brush) {
+ CheckValidation();
+ const auto t = CheckPlatform<DWriteTextLayout>(text_layout, GetPlatformId());
+ const auto b = CheckPlatform<ID2DBrush>(brush, GetPlatformId());
+ device_context_->DrawTextLayout(Convert(offset), t->GetComInterface(),
+ b->GetD2DBrushInterface());
+}
+
+void D2DDeviceContextPainter::DrawImage(const Point& offset, IImage* image) {
+ CheckValidation();
+ const auto i = CheckPlatform<Direct2DImage>(image, GetPlatformId());
+
+ Microsoft::WRL::ComPtr<ID2D1DeviceContext> device_context;
+
+ device_context_->QueryInterface(device_context.GetAddressOf());
+ device_context->DrawImage(i->GetD2DBitmap().Get(), Convert(offset));
+}
+
+void D2DDeviceContextPainter::PushLayer(const Rect& bounds) {
+ CheckValidation();
+
+ Microsoft::WRL::ComPtr<ID2D1Layer> layer;
+ ThrowIfFailed(device_context_->CreateLayer(&layer));
+
+ device_context_->PushLayer(D2D1::LayerParameters(Convert(bounds)),
+ layer.Get());
+
+ layers_.push_back(std::move(layer));
+}
+
+void D2DDeviceContextPainter::PopLayer() {
+ device_context_->PopLayer();
+ layers_.pop_back();
+}
+
+void D2DDeviceContextPainter::PushState() {
+ Microsoft::WRL::ComPtr<ID2D1Factory> factory = nullptr;
+ device_context_->GetFactory(&factory);
+
+ Microsoft::WRL::ComPtr<ID2D1DrawingStateBlock> state_block;
+ factory->CreateDrawingStateBlock(&state_block);
+ device_context_->SaveDrawingState(state_block.Get());
+
+ drawing_state_stack_.push_back(std::move(state_block));
+}
+
+void D2DDeviceContextPainter::PopState() {
+ Expects(!drawing_state_stack_.empty());
+ auto drawing_state = drawing_state_stack_.back();
+ drawing_state_stack_.pop_back();
+ device_context_->RestoreDrawingState(drawing_state.Get());
+}
+
+void D2DDeviceContextPainter::EndDraw() {
+ if (is_drawing_) {
+ is_drawing_ = false;
+ ThrowIfFailed(device_context_->EndDraw());
+ DoEndDraw();
+ }
+}
+
+void D2DDeviceContextPainter::CheckValidation() {
+ if (!is_drawing_) {
+ throw cru::platform::ReuseException(
+ u"Can't do that on painter after end drawing.");
+ }
+}
+} // namespace cru::platform::graphics::direct2d
diff --git a/src/platform/graphics/direct2d/Resource.cpp b/src/platform/graphics/direct2d/Resource.cpp
new file mode 100644
index 00000000..b81aa69e
--- /dev/null
+++ b/src/platform/graphics/direct2d/Resource.cpp
@@ -0,0 +1,16 @@
+#include "cru/platform/graphics/direct2d/Resource.h"
+
+#include "cru/platform/graphics/direct2d/Factory.h"
+
+namespace cru::platform::graphics::direct2d {
+String DirectResource::kPlatformId = u"Windows Direct";
+
+DirectGraphicsResource::DirectGraphicsResource(DirectGraphicsFactory* factory)
+ : factory_(factory) {
+ Expects(factory);
+}
+
+IGraphicsFactory* DirectGraphicsResource::GetGraphicsFactory() {
+ return factory_;
+}
+} // namespace cru::platform::graphics::direct2d
diff --git a/src/platform/graphics/direct2d/TextLayout.cpp b/src/platform/graphics/direct2d/TextLayout.cpp
new file mode 100644
index 00000000..5bc392de
--- /dev/null
+++ b/src/platform/graphics/direct2d/TextLayout.cpp
@@ -0,0 +1,162 @@
+#include "cru/platform/graphics/direct2d/TextLayout.h"
+#include <dwrite.h>
+
+#include "cru/common/log/Logger.h"
+#include "cru/platform/Check.h"
+#include "cru/platform/graphics/direct2d/Exception.h"
+#include "cru/platform/graphics/direct2d/Factory.h"
+#include "cru/platform/graphics/direct2d/Font.h"
+
+#include <utility>
+
+namespace cru::platform::graphics::direct2d {
+DWriteTextLayout::DWriteTextLayout(DirectGraphicsFactory* factory,
+ std::shared_ptr<IFont> font, String text)
+ : DirectGraphicsResource(factory), text_(std::move(text)) {
+ Expects(font);
+ font_ = CheckPlatform<DWriteFont>(font, GetPlatformId());
+
+ ThrowIfFailed(factory->GetDWriteFactory()->CreateTextLayout(
+ reinterpret_cast<const wchar_t*>(text_.c_str()),
+ static_cast<UINT32>(text_.size()), font_->GetComInterface(), max_width_,
+ max_height_, &text_layout_));
+}
+
+DWriteTextLayout::~DWriteTextLayout() = default;
+
+String DWriteTextLayout::GetText() { return text_; }
+
+void DWriteTextLayout::SetText(String new_text) {
+ text_ = std::move(new_text);
+ ThrowIfFailed(GetDirectFactory()->GetDWriteFactory()->CreateTextLayout(
+ reinterpret_cast<const wchar_t*>(text_.c_str()),
+ static_cast<UINT32>(text_.size()), font_->GetComInterface(), max_width_,
+ max_height_, &text_layout_));
+}
+
+std::shared_ptr<IFont> DWriteTextLayout::GetFont() {
+ return std::dynamic_pointer_cast<IFont>(font_);
+}
+
+void DWriteTextLayout::SetFont(std::shared_ptr<IFont> font) {
+ font_ = CheckPlatform<DWriteFont>(font, GetPlatformId());
+ ThrowIfFailed(GetDirectFactory()->GetDWriteFactory()->CreateTextLayout(
+ reinterpret_cast<const wchar_t*>(text_.c_str()),
+ static_cast<UINT32>(text_.size()), font_->GetComInterface(), max_width_,
+ max_height_, &text_layout_));
+}
+
+void DWriteTextLayout::SetMaxWidth(float max_width) {
+ max_width_ = max_width;
+ ThrowIfFailed(text_layout_->SetMaxWidth(max_width_));
+}
+
+void DWriteTextLayout::SetMaxHeight(float max_height) {
+ max_height_ = max_height;
+ ThrowIfFailed(text_layout_->SetMaxHeight(max_height_));
+}
+
+bool DWriteTextLayout::IsEditMode() { return edit_mode_; }
+
+void DWriteTextLayout::SetEditMode(bool enable) {
+ edit_mode_ = enable;
+ // TODO: Implement this.
+}
+
+Index DWriteTextLayout::GetLineIndexFromCharIndex(Index char_index) {
+ if (char_index < 0 || char_index >= text_.size()) {
+ return -1;
+ }
+
+ auto line_index = 0;
+ for (Index i = 0; i < char_index; ++i) {
+ if (text_[i] == u'\n') {
+ line_index++;
+ }
+ }
+
+ return line_index;
+}
+
+float DWriteTextLayout::GetLineHeight(Index line_index) {
+ Index count = GetLineCount();
+ std::vector<DWRITE_LINE_METRICS> line_metrics(count);
+
+ UINT32 actual_line_count = 0;
+ text_layout_->GetLineMetrics(line_metrics.data(), static_cast<UINT32>(count),
+ &actual_line_count);
+ return line_metrics[line_index].height;
+}
+
+Index DWriteTextLayout::GetLineCount() {
+ UINT32 line_count = 0;
+ text_layout_->GetLineMetrics(nullptr, 0, &line_count);
+ return line_count;
+}
+
+Rect DWriteTextLayout::GetTextBounds(bool includingTrailingSpace) {
+ DWRITE_TEXT_METRICS metrics;
+ ThrowIfFailed(text_layout_->GetMetrics(&metrics));
+ return Rect{metrics.left, metrics.top,
+ includingTrailingSpace ? metrics.widthIncludingTrailingWhitespace
+ : metrics.width,
+ metrics.height};
+}
+
+std::vector<Rect> DWriteTextLayout::TextRangeRect(
+ const TextRange& text_range_arg) {
+ if (text_range_arg.count == 0) {
+ return {};
+ }
+ const auto text_range = text_range_arg.Normalize();
+
+ DWRITE_TEXT_METRICS text_metrics;
+ ThrowIfFailed(text_layout_->GetMetrics(&text_metrics));
+ const auto metrics_count =
+ text_metrics.lineCount * text_metrics.maxBidiReorderingDepth;
+
+ std::vector<DWRITE_HIT_TEST_METRICS> hit_test_metrics(metrics_count);
+ UINT32 actual_count;
+ ThrowIfFailed(text_layout_->HitTestTextRange(
+ static_cast<UINT32>(text_range.position),
+ static_cast<UINT32>(text_range.count), 0, 0, hit_test_metrics.data(),
+ metrics_count, &actual_count));
+
+ hit_test_metrics.erase(hit_test_metrics.cbegin() + actual_count,
+ hit_test_metrics.cend());
+
+ std::vector<Rect> result;
+ result.reserve(actual_count);
+
+ for (const auto& metrics : hit_test_metrics) {
+ result.push_back(
+ Rect{metrics.left, metrics.top, metrics.width, metrics.height});
+ }
+
+ return result;
+}
+
+Rect DWriteTextLayout::TextSinglePoint(Index position, bool trailing) {
+ DWRITE_HIT_TEST_METRICS metrics;
+ FLOAT left;
+ FLOAT top;
+ ThrowIfFailed(text_layout_->HitTestTextPosition(static_cast<UINT32>(position),
+ static_cast<BOOL>(trailing),
+ &left, &top, &metrics));
+ return Rect{left, top, 0, GetFont()->GetFontSize()};
+}
+
+TextHitTestResult DWriteTextLayout::HitTest(const Point& point) {
+ BOOL trailing;
+ BOOL inside;
+ DWRITE_HIT_TEST_METRICS metrics;
+
+ ThrowIfFailed(text_layout_->HitTestPoint(point.x, point.y, &trailing, &inside,
+ &metrics));
+
+ TextHitTestResult result;
+ result.position = metrics.textPosition;
+ result.trailing = trailing != 0;
+ return result;
+}
+} // namespace cru::platform::graphics::direct2d
diff --git a/src/platform/graphics/direct2d/WindowPainter.cpp b/src/platform/graphics/direct2d/WindowPainter.cpp
new file mode 100644
index 00000000..c950a2b6
--- /dev/null
+++ b/src/platform/graphics/direct2d/WindowPainter.cpp
@@ -0,0 +1,15 @@
+#include "cru/platform/graphics/direct2d/WindowPainter.h"
+
+#include "cru/platform/graphics/direct2d/Exception.h"
+#include "cru/platform/graphics/direct2d/Factory.h"
+#include "cru/platform/graphics/direct2d/WindowRenderTarget.h"
+
+namespace cru::platform::graphics::direct2d {
+D2DWindowPainter::D2DWindowPainter(D2DWindowRenderTarget* render_target)
+ : D2DDeviceContextPainter(render_target->GetD2D1DeviceContext()),
+ render_target_(render_target) {}
+
+D2DWindowPainter::~D2DWindowPainter() {}
+
+void D2DWindowPainter::DoEndDraw() { render_target_->Present(); }
+} // namespace cru::platform::graphics::direct2d
diff --git a/src/platform/graphics/direct2d/WindowRenderTarget.cpp b/src/platform/graphics/direct2d/WindowRenderTarget.cpp
new file mode 100644
index 00000000..0227f084
--- /dev/null
+++ b/src/platform/graphics/direct2d/WindowRenderTarget.cpp
@@ -0,0 +1,81 @@
+#include "cru/platform/graphics/direct2d/WindowRenderTarget.h"
+
+#include "cru/platform/graphics/direct2d/Exception.h"
+#include "cru/platform/graphics/direct2d/Factory.h"
+
+namespace cru::platform::graphics::direct2d {
+D2DWindowRenderTarget::D2DWindowRenderTarget(
+ gsl::not_null<DirectGraphicsFactory*> factory, HWND hwnd)
+ : factory_(factory), hwnd_(hwnd) {
+ const auto d3d11_device = factory->GetD3D11Device();
+ const auto dxgi_factory = factory->GetDxgiFactory();
+
+ d2d1_device_context_ = factory->CreateD2D1DeviceContext();
+ d2d1_device_context_->SetUnitMode(D2D1_UNIT_MODE_DIPS);
+
+ // Allocate a descriptor.
+ DXGI_SWAP_CHAIN_DESC1 swap_chain_desc;
+ swap_chain_desc.Width = 0; // use automatic sizing
+ swap_chain_desc.Height = 0;
+ swap_chain_desc.Format =
+ DXGI_FORMAT_B8G8R8A8_UNORM; // this is the most common swapchain format
+ swap_chain_desc.Stereo = false;
+ swap_chain_desc.SampleDesc.Count = 1; // don't use multi-sampling
+ swap_chain_desc.SampleDesc.Quality = 0;
+ swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ swap_chain_desc.BufferCount = 2; // use double buffering to enable flip
+ swap_chain_desc.Scaling = DXGI_SCALING_NONE;
+ swap_chain_desc.SwapEffect =
+ DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // all apps must use this SwapEffect
+ swap_chain_desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
+ swap_chain_desc.Flags = 0;
+
+ // Get the final swap chain for this window from the DXGI factory.
+ ThrowIfFailed(dxgi_factory->CreateSwapChainForHwnd(
+ d3d11_device, hwnd, &swap_chain_desc, nullptr, nullptr,
+ &dxgi_swap_chain_));
+
+ CreateTargetBitmap();
+}
+
+void D2DWindowRenderTarget::SetDpi(float x, float y) {
+ d2d1_device_context_->SetDpi(x, y);
+}
+
+void D2DWindowRenderTarget::ResizeBuffer(const int width, const int height) {
+ // In order to resize buffer, we need to untarget the buffer first.
+ d2d1_device_context_->SetTarget(nullptr);
+ target_bitmap_ = nullptr;
+ ThrowIfFailed(dxgi_swap_chain_->ResizeBuffers(0, width, height,
+ DXGI_FORMAT_UNKNOWN, 0));
+ CreateTargetBitmap();
+}
+
+void D2DWindowRenderTarget::Present() {
+ ThrowIfFailed(dxgi_swap_chain_->Present(1, 0));
+}
+
+void D2DWindowRenderTarget::CreateTargetBitmap() {
+ Expects(target_bitmap_ == nullptr); // target bitmap must not exist.
+
+ // Direct2D needs the dxgi version of the backbuffer surface pointer.
+ Microsoft::WRL::ComPtr<IDXGISurface> dxgi_back_buffer;
+ ThrowIfFailed(
+ dxgi_swap_chain_->GetBuffer(0, IID_PPV_ARGS(&dxgi_back_buffer)));
+
+ float dpi_x, dpi_y;
+ d2d1_device_context_->GetDpi(&dpi_x, &dpi_y);
+
+ auto bitmap_properties = D2D1::BitmapProperties1(
+ D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE),
+ dpi_x, dpi_y);
+
+ // Get a D2D surface from the DXGI back buffer to use as the D2D render
+ // target.
+ ThrowIfFailed(d2d1_device_context_->CreateBitmapFromDxgiSurface(
+ dxgi_back_buffer.Get(), &bitmap_properties, &target_bitmap_));
+
+ d2d1_device_context_->SetTarget(target_bitmap_.Get());
+}
+} // namespace cru::platform::graphics::direct2d