bool Renderable::BuildBitmap(Displayer* displayer, DanmakuConfig* config) { if (!HasTextLayout()) return false; ComPtr<ID2D1Factory1> d2dFactory = displayer->GetD2DFactory(); if (nullptr == d2dFactory) return false; ComPtr<ID2D1Bitmap1> bmp = displayer->CreateBitmap( static_cast<uint32_t>(mDanmaku->mTextWidth), static_cast<uint32_t>(mDanmaku->mTextHeight) ); if (bmp == nullptr) return false; float strokeWidth = 1.5f * config->FontScaleFactor; strokeWidth *= displayer->GetDpiY() / 96.0f; ComPtr<ID2D1DeviceContext> deviceContext = displayer->AcquireDeviceContext(bmp); if (deviceContext == nullptr) return false; deviceContext->BeginDraw(); deviceContext->Clear(); deviceContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); ComPtr<ID2D1SolidColorBrush> brush; deviceContext->CreateSolidColorBrush(D2D1::ColorF(mDanmaku->mTextColor), &brush); ComPtr<ID2D1SolidColorBrush> outlineBrush; deviceContext->CreateSolidColorBrush(D2D1::ColorF(mDanmaku->mTextShadowColor, 1.0f), &outlineBrush); switch (config->DanmakuStyle) { case kOutline: { ComPtr<OutlineTextRenderer> textRenderer(new OutlineTextRenderer(d2dFactory, deviceContext, outlineBrush, strokeWidth, brush)); mTextLayout->Draw(deviceContext.Get(), textRenderer.Get(), 0.0f, 0.0f); break; } case kProjection: { deviceContext->DrawTextLayout(D2D1::Point2F(1.0f, 1.0f), mTextLayout.Get(), outlineBrush.Get()); deviceContext->DrawTextLayout(D2D1::Point2F(0.0f, 0.0f), mTextLayout.Get(), brush.Get()); break; } } HRESULT hr = deviceContext->EndDraw(); displayer->ReleaseDeviceContext(deviceContext); if (FAILED(hr)) return false; mBitmap = bmp; mBitmapValidFlag = config->BitmapValidFlag; return true; }
// Renders one frame. void DirectXLatencyRenderer::Render() { ComPtr<ID2D1DeviceContext1> deviceContext = m_deviceResources->GetD2DDeviceContext(); deviceContext->BeginDraw(); // Rotate the rendered scene based on the current orientation of the device. deviceContext->SetTransform(m_deviceResources->GetOrientationTransform2D()); // Draw the circle at its current position. deviceContext->FillEllipse( m_ellipse, m_brush.Get() ); // We ignore D2DERR_RECREATE_TARGET here. This error indicates that the device // is lost. It will be handled during the next call to Present. HRESULT hr = deviceContext->EndDraw(); if (hr != D2DERR_RECREATE_TARGET) { DX::ThrowIfFailed(hr); } }
virtual bool get_bitmap(unicode_char chnum, bitmap_argb32 &bitmap, std::int32_t &width, std::int32_t &xoffs, std::int32_t &yoffs) override { const int MEM_ALIGN_CONST = 31; const int BITMAP_PAD = 50; HRESULT result; UINT cbData; BYTE* pixels = nullptr; ComPtr<ID2D1BitmapRenderTarget> target; ComPtr<ID2D1SolidColorBrush> pWhiteBrush; ComPtr<IWICBitmap> wicBitmap; ComPtr<IWICBitmapLock> lock; ComPtr<IDWriteFontFace> face; HR_RET0(m_font->CreateFontFace(face.GetAddressOf())); // get the GDI metrics DWRITE_FONT_METRICS gdi_metrics; HR_RET0(face->GetGdiCompatibleMetrics( m_fontEmHeightInDips, 1.0f, nullptr, &gdi_metrics)); FontDimensionFactory fdf(gdi_metrics.designUnitsPerEm, m_fontEmHeightInDips); UINT32 tempChar = chnum; UINT16 glyphIndex; HR_RET0(face->GetGlyphIndicesW(&tempChar, 1, &glyphIndex)); // get the width of this character DWRITE_GLYPH_METRICS glyph_metrics = { 0 }; HR_RET0(face->GetGdiCompatibleGlyphMetrics( m_fontEmHeightInDips, 1.0f, nullptr, FALSE, &glyphIndex, 1, &glyph_metrics)); // The height is the ascent added to the descent // By definition, the Em is equal to Cell Height minus Internal Leading (topSide bearing). //auto cellheight = fdf.FromDesignUnit(gdi_metrics.ascent + gdi_metrics.descent + gdi_metrics.); auto ascent = fdf.FromDesignUnit(gdi_metrics.ascent); auto descent = fdf.FromDesignUnit(gdi_metrics.descent); auto charHeight = ascent + descent; auto abc = fdf.CreateAbcWidths( glyph_metrics.advanceWidth, glyph_metrics.leftSideBearing, glyph_metrics.rightSideBearing); width = abc.abcA().Dips() + abc.abcB().Dips() + abc.abcC().Dips(); // determine desired bitmap size int bmwidth = (BITMAP_PAD + abc.abcA().Dips() + abc.abcB().Dips() + abc.abcC().Dips() + BITMAP_PAD + MEM_ALIGN_CONST) & ~MEM_ALIGN_CONST; int bmheight = BITMAP_PAD + charHeight.Dips() + BITMAP_PAD; // GUID_WICPixelFormat8bppAlpha is 8 bits per pixel const REFWICPixelFormatGUID source_bitmap_wic_format = GUID_WICPixelFormat8bppAlpha; const DXGI_FORMAT source_bitmap_dxgi_format = DXGI_FORMAT_A8_UNORM; const D2D1_ALPHA_MODE source_bitmap_d2d_alpha_mode = D2D1_ALPHA_MODE_STRAIGHT; // describe the bitmap we want HR_RET0(m_wicFactory->CreateBitmap( bmwidth, bmheight, source_bitmap_wic_format, WICBitmapCacheOnLoad, wicBitmap.GetAddressOf())); D2D1_RENDER_TARGET_PROPERTIES targetProps; targetProps = D2D1::RenderTargetProperties(); targetProps.pixelFormat = D2D1::PixelFormat(source_bitmap_dxgi_format, source_bitmap_d2d_alpha_mode); // create a DIB to render to HR_RET0(this->m_d2dfactory->CreateWicBitmapRenderTarget( wicBitmap.Get(), &targetProps, reinterpret_cast<ID2D1RenderTarget**>(target.GetAddressOf()))); target->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED); // Create our brush HR_RET0(target->CreateSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), pWhiteBrush.GetAddressOf())); // Signal the start of the frame target->BeginDraw(); // clear the bitmap // In the alpha mask, it will look like 0x00 per pixel target->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f)); // now draw the character DWRITE_GLYPH_RUN run = { nullptr }; DWRITE_GLYPH_OFFSET offsets; offsets.advanceOffset = 0; offsets.ascenderOffset = 0; float advanceWidth = abc.advanceWidth().Dips(); run.fontEmSize = m_fontEmHeightInDips; run.fontFace = face.Get(); run.glyphCount = 1; run.glyphIndices = &glyphIndex; run.glyphAdvances = &advanceWidth; run.glyphOffsets = &offsets; auto baseline_origin = D2D1::Point2F(BITMAP_PAD + abc.abcA().Dips() + 1, BITMAP_PAD + ascent.Dips()); target->DrawGlyphRun( baseline_origin, &run, pWhiteBrush.Get(), DWRITE_MEASURING_MODE_GDI_CLASSIC); HR_RET0(target->EndDraw()); #ifdef DWRITE_DEBUGGING // Save to file for debugging SaveBitmap(wicBitmap.Get(), GUID_WICPixelFormatBlackWhite, L"C:\\temp\\ddraw_step1.bmp"); #endif // characters are expected to be full-height rectangle actbounds; actbounds.min_y = BITMAP_PAD; actbounds.max_y = BITMAP_PAD + charHeight.Dips() - 1; // Lock the bitmap and get the data pointer WICRect rect = { 0, 0, bmwidth, bmheight }; HR_RET0(wicBitmap->Lock(&rect, WICBitmapLockRead, lock.GetAddressOf())); HR_RET0(lock->GetDataPointer(&cbData, static_cast<BYTE**>(&pixels))); // determine the actual left of the character for (actbounds.min_x = 0; actbounds.min_x < bmwidth; actbounds.min_x++) { BYTE *offs = pixels + actbounds.min_x; UINT8 summary = 0; for (int y = 0; y < bmheight; y++) summary |= offs[y * bmwidth]; if (summary != 0) { break; } } // determine the actual right of the character // Start from the right edge, and move in until we find a pixel for (actbounds.max_x = bmwidth - 1; actbounds.max_x >= 0; actbounds.max_x--) { BYTE *offs = pixels + actbounds.max_x; UINT8 summary = 0; // Go through the entire column and build a summary for (int y = 0; y < bmheight; y++) summary |= offs[y * bmwidth]; if (summary != 0) { break; } } // allocate a new bitmap if (actbounds.max_x >= actbounds.min_x && actbounds.max_y >= actbounds.min_y) { bitmap.allocate(actbounds.max_x + 1 - actbounds.min_x, actbounds.max_y + 1 - actbounds.min_y); // copy the bits into it for (int y = 0; y < bitmap.height(); y++) { UINT32 *dstrow = &bitmap.pix32(y); UINT8 *srcrow = &pixels[(y + actbounds.min_y) * bmwidth]; for (int x = 0; x < bitmap.width(); x++) { int effx = x + actbounds.min_x; dstrow[x] = rgb_t(srcrow[effx], 0xff, 0xff, 0xff); } } // set the final offset values xoffs = actbounds.min_x - (BITMAP_PAD + abc.abcA().Dips()); yoffs = actbounds.max_y - (BITMAP_PAD + ascent.Dips()); #ifdef DWRITE_DEBUGGING SaveBitmap2(bitmap, L"C:\\temp\\dwrite_final.bmp"); #endif } BOOL success = bitmap.valid(); #ifdef DWRITE_DEBUGGING osd_printf_debug( "dwr: %s, c'%S' w%i x%i y%i asc%i dsc%i a%ib%ic%i\n", success ? "Success" : "Error", (WCHAR*)&chnum, width, xoffs, yoffs, ascent.Dips(), descent.Dips(), abc.abcA().Dips(), abc.abcB().Dips(), abc.abcC().Dips()); #endif return success; }
// // Saves the current image // HRESULT SimpleImage::Save(__in IShellItem *saveAsItem) { ComPtr<IWICImagingFactory> wicFactory; ComPtr<ID2D1Factory> d2dFactory; ComPtr<IWICBitmap> wicBitmap; ComPtr<ID2D1RenderTarget> wicRenderTarget; // Clear backup information from previous save m_imageInfo.backupFileName.clear(); // Don't save if there are no image operations applied to this image unless the user specifed 'Save As' if (m_imageOperations.empty() && nullptr == saveAsItem) { return S_OK; } HRESULT hr = Direct2DUtility::GetWICFactory(&wicFactory); if (SUCCEEDED(hr)) { hr = Direct2DUtility::GetD2DFactory(&d2dFactory); } // Get the original bitmap rectangle in terms of the current crop D2D1_RECT_F originalBitmapRect = D2D1::RectF(0, 0, Direct2DUtility::GetRectWidth(m_clipRect), Direct2DUtility::GetRectHeight(m_clipRect)); // Adjust height and width based on current orientation and clipping rectangle float width = m_isHorizontal ? Direct2DUtility::GetRectWidth(m_clipRect) : Direct2DUtility::GetRectHeight(m_clipRect); float height = m_isHorizontal ? Direct2DUtility::GetRectHeight(m_clipRect) : Direct2DUtility::GetRectWidth(m_clipRect); if (SUCCEEDED(hr)) { // Create WIC bitmap for rendering hr = wicFactory->CreateBitmap( static_cast<unsigned int>(width), static_cast<unsigned int>(height), GUID_WICPixelFormat32bppBGR, WICBitmapCacheOnLoad, &wicBitmap); } if (SUCCEEDED(hr)) { hr = d2dFactory->CreateWicBitmapRenderTarget(wicBitmap, D2D1::RenderTargetProperties(), &wicRenderTarget); } if (SUCCEEDED(hr)) { // Replace current bitmap with one that's compatible with the WIC render target if (m_bitmap) { m_bitmap = nullptr; } hr = Direct2DUtility::LoadBitmapFromFile(wicRenderTarget, m_imageInfo.fileName.c_str(), 0, 0, &m_bitmap); } if (SUCCEEDED(hr)) { // When rotating images make sure that the point around which rotation occurs lines // up with the center of the rotated render target if (false == m_isHorizontal) { float offsetX; float offsetY; if (width > height) { offsetX = (width - height) / 2; offsetY = -offsetX; } else { offsetY = (height - width) / 2; offsetX = - offsetY; } D2D1_MATRIX_3X2_F translation = D2D1::Matrix3x2F::Translation(offsetX, offsetY); wicRenderTarget->SetTransform(translation); } // Update current render target to point to WIC render target m_currentRenderTarget = wicRenderTarget; // Draw updated image to WIC render target wicRenderTarget->BeginDraw(); DrawImage(originalBitmapRect, m_clipRect, true); wicRenderTarget->EndDraw(); } if (SUCCEEDED(hr) && !m_imageOperations.empty()) { // Create copy of original image unless the user is simply using 'Save As' std::wstring backupPath(m_imageInfo.fileName); backupPath.insert(backupPath.find_last_of('\\'), L"\\AnnotatorBackup"); std::wstring backupDirectory(backupPath.substr(0, backupPath.find_last_of('\\'))); // Create backup directory if needed if (false == ::CreateDirectoryW(backupDirectory.c_str(), nullptr)) { hr = (GetLastError() == ERROR_ALREADY_EXISTS) ? S_OK : E_FAIL; } if (SUCCEEDED(hr)) { // Do not copy if the backup file already exists if (false == ::CopyFile(m_imageInfo.fileName.c_str(), backupPath.c_str(), true)) { hr = (GetLastError() == ERROR_FILE_EXISTS) ? S_OK : E_FAIL; } else { // Capture name of backup file m_imageInfo.backupFileName.assign(backupPath); } } } if (SUCCEEDED(hr)) { if (nullptr == saveAsItem) { // Save updated file hr = Direct2DUtility::SaveBitmapToFile(wicBitmap, m_imageInfo.fileName.c_str()); } else { // Save updated file as the specifed shell item wchar_t * saveAsFileName; hr = saveAsItem->GetDisplayName(SIGDN_FILESYSPATH, &saveAsFileName); if (SUCCEEDED(hr)) { hr = Direct2DUtility::SaveBitmapToFile(wicBitmap, m_imageInfo.fileName.c_str(), saveAsFileName); ::CoTaskMemFree(saveAsFileName); } } } if (SUCCEEDED(hr)) { // Force image to reload hr = DiscardResources(); m_isHorizontal = true; if (nullptr == saveAsItem) { // Clear all undo operations m_imageOperations.clear(); // Empty redo stack while (!m_redoStack.empty()) { m_redoStack.pop(); } } } return hr; }
// Renders one frame. This method draws a small ruler at the correct physical size. // The distance ticks on the ruler should be accurate, regardless of any user or // OS DPI settings. void PhysicalDpiRenderer::Render() { ComPtr<ID2D1DeviceContext1> deviceContext = m_deviceResources->GetD2DDeviceContext(); // Retrieve the physical DPI (raw DPI) of the current display. This is the actual pixel density // reported by the display. These values do not change based on any user or OS settings. DisplayInformation^ displayInfo = DisplayInformation::GetForCurrentView(); float rawDpiX = displayInfo->RawDpiX; float rawDpiY = displayInfo->RawDpiY; // Save the device context's current DPI so that it can be restored later. float savedDpiX; float savedDpiY; deviceContext->GetDpi(&savedDpiX, &savedDpiY); // Set the device context's DPI to the physical DPI. Note that the DisplayInformation object // returns 0 for RawDpiX and RawDpiY if the monitor does not report a DPI or if the monitor // is being run in Duplicate mode. Passing 0 values to SetDpi specifies the factory-read system // DPI. deviceContext->SetDpi(rawDpiX, rawDpiY); // Compute the physical size (in DIPs) of the current display from its pixel size. The physical // size is useful when laying out elements based on physical DPI. Size pixelSize = m_deviceResources->GetOutputSize(); Size physicalSize( (pixelSize.Width / displayInfo->RawDpiX) * 96.0f, (pixelSize.Height / displayInfo->RawDpiY) * 96.0f ); deviceContext->BeginDraw(); // Set the transform to the physical (raw) orientation transform, so that content rendered // according to the physical DPI is rotated and translated appropriately. deviceContext->SetTransform(m_deviceResources->GetRawOrientationTransform2D()); // Draw a horizontal ruler: a rectangle five inches wide and half an inch tall. By // using the physical DPI, this ruler will have this size regardless of any user or // OS DPI settings. float xPos = ConvertInchesToDips(1.0f); float yPos = ConvertInchesToDips(1.0f); float width = ConvertInchesToDips(5.0f); float height = ConvertInchesToDips(0.5f); deviceContext->FillRectangle( D2D1::RectF(xPos, yPos, xPos + width, yPos + height), m_goldBrush.Get() ); // Draw ticks to denote the one-inch intervals on the ruler. for (float tick = 1.0f; tick <= 4.0f; tick += 1.0f) { deviceContext->DrawLine( D2D1::Point2F(xPos + ConvertInchesToDips(tick), yPos), D2D1::Point2F(xPos + ConvertInchesToDips(tick), yPos + ConvertInchesToDips(0.2f)), m_blackBrush.Get() ); } // Draw ticks to denote the half-inch intervals on the ruler. for (float tick = 0.5f; tick <= 4.5f; tick += 1.0f) { deviceContext->DrawLine( D2D1::Point2F(xPos + ConvertInchesToDips(tick), yPos), D2D1::Point2F(xPos + ConvertInchesToDips(tick), yPos + ConvertInchesToDips(0.1f)), m_blackBrush.Get() ); } // Draw ticks to denote the quarter-inch intervals on the ruler. for (float tick = 0.25f; tick <= 4.75f; tick += 0.5f) { deviceContext->DrawLine( D2D1::Point2F(xPos + ConvertInchesToDips(tick), yPos), D2D1::Point2F(xPos + ConvertInchesToDips(tick), yPos + ConvertInchesToDips(0.05f)), m_blackBrush.Get() ); } // Draw a vertical ruler: a rectangle 3 inches tall and half an inch wide. By // using the physical DPI, this ruler will have this size regardless of any user or // OS DPI settings. xPos = ConvertInchesToDips(1.0f); yPos = ConvertInchesToDips(2.0f); width = ConvertInchesToDips(0.5f); height = ConvertInchesToDips(3.0f); deviceContext->FillRectangle( D2D1::RectF(xPos, yPos, xPos + width, yPos + height), m_goldBrush.Get() ); // Draw ticks to denote the one-inch intervals on the ruler. for (float tick = 1.0f; tick <= 2.0f; tick += 1.0f) { deviceContext->DrawLine( D2D1::Point2F(xPos, yPos + ConvertInchesToDips(tick)), D2D1::Point2F(xPos + ConvertInchesToDips(0.2f), yPos + ConvertInchesToDips(tick)), m_blackBrush.Get() ); } // Draw ticks to denote the half-inch intervals on the ruler. for (float tick = 0.5f; tick <= 2.5f; tick += 1.0f) { deviceContext->DrawLine( D2D1::Point2F(xPos, yPos + ConvertInchesToDips(tick)), D2D1::Point2F(xPos + ConvertInchesToDips(0.1f), yPos + ConvertInchesToDips(tick)), m_blackBrush.Get() ); } // Draw ticks to denote the quarter-inch intervals on the ruler. for (float tick = 0.25f; tick <= 2.75f; tick += 0.5f) { deviceContext->DrawLine( D2D1::Point2F(xPos, yPos + ConvertInchesToDips(tick)), D2D1::Point2F(xPos + ConvertInchesToDips(0.05f), yPos + ConvertInchesToDips(tick)), m_blackBrush.Get() ); } // Draw a message explaining the sizes of the rulers. String^ message = "The ruler above is 5 inches long. The ruler to the left is 3 " "inches long. Their sizes are independent of user and OS DPI settings."; xPos = ConvertInchesToDips(2.0f); yPos = ConvertInchesToDips(2.0f); deviceContext->DrawText( message->Data(), message->Length(), m_textFormat.Get(), D2D1::RectF(xPos, yPos, physicalSize.Width - ConvertInchesToDips(1.0f), physicalSize.Height - ConvertInchesToDips(1.0f)), m_whiteBrush.Get() ); // We ignore D2DERR_RECREATE_TARGET here. This error indicates that the device // is lost. It will be handled during the next call to Present. HRESULT hr = deviceContext->EndDraw(); if (hr != D2DERR_RECREATE_TARGET) { DX::ThrowIfFailed(hr); } // Restore the DPI Direct2D was using. The rest of the sample will be drawn using the // logical DPI, rather than the physical DPI, so it will change size in accordance // with user DPI settings. deviceContext->SetDpi(savedDpiX, savedDpiY); }