diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /vcl/skia/win/gdiimpl.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/skia/win/gdiimpl.cxx')
-rw-r--r-- | vcl/skia/win/gdiimpl.cxx | 393 |
1 files changed, 393 insertions, 0 deletions
diff --git a/vcl/skia/win/gdiimpl.cxx b/vcl/skia/win/gdiimpl.cxx new file mode 100644 index 000000000..a4cbe062f --- /dev/null +++ b/vcl/skia/win/gdiimpl.cxx @@ -0,0 +1,393 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <skia/win/gdiimpl.hxx> + +#include <win/saldata.hxx> +#include <vcl/skia/SkiaHelper.hxx> +#include <skia/utils.hxx> +#include <skia/zone.hxx> +#include <win/winlayout.hxx> +#include <comphelper/windowserrorstring.hxx> + +#include <SkCanvas.h> +#include <SkPaint.h> +#include <SkPixelRef.h> +#include <SkTypeface_win.h> +#include <SkFont.h> +#include <SkFontMgr.h> +#include <SkFontLCDConfig.h> +#include <tools/sk_app/win/WindowContextFactory_win.h> +#include <tools/sk_app/WindowContext.h> + +#include <windows.h> + +WinSkiaSalGraphicsImpl::WinSkiaSalGraphicsImpl(WinSalGraphics& rGraphics, + SalGeometryProvider* mpProvider) + : SkiaSalGraphicsImpl(rGraphics, mpProvider) + , mWinParent(rGraphics) +{ +} + +void WinSkiaSalGraphicsImpl::createWindowContext(bool forceRaster) +{ + SkiaZone zone; + sk_app::DisplayParams displayParams; + switch (forceRaster ? SkiaHelper::RenderRaster : SkiaHelper::renderMethodToUse()) + { + case SkiaHelper::RenderRaster: + mWindowContext = sk_app::window_context_factory::MakeRasterForWin(mWinParent.gethWnd(), + displayParams); + break; + case SkiaHelper::RenderVulkan: + mWindowContext = sk_app::window_context_factory::MakeVulkanForWin(mWinParent.gethWnd(), + displayParams); + break; + } +} + +void WinSkiaSalGraphicsImpl::DeInit() +{ + SkiaZone zone; + SkiaSalGraphicsImpl::DeInit(); + mWindowContext.reset(); +} + +void WinSkiaSalGraphicsImpl::freeResources() {} + +void WinSkiaSalGraphicsImpl::performFlush() +{ + SkiaZone zone; + flushDrawing(); + if (mWindowContext) + mWindowContext->swapBuffers(); +} + +bool WinSkiaSalGraphicsImpl::TryRenderCachedNativeControl(ControlCacheKey const& rControlCacheKey, + int nX, int nY) +{ + static bool gbCacheEnabled = !getenv("SAL_WITHOUT_WIDGET_CACHE"); + if (!gbCacheEnabled) + return false; + + auto& controlsCache = SkiaControlsCache::get(); + SkiaControlCacheType::const_iterator iterator = controlsCache.find(rControlCacheKey); + if (iterator == controlsCache.end()) + return false; + + preDraw(); + SAL_INFO("vcl.skia.trace", "tryrendercachednativecontrol(" + << this << "): " + << SkIRect::MakeXYWH(nX, nY, iterator->second->width(), + iterator->second->height())); + mSurface->getCanvas()->drawImage(iterator->second, nX, nY); + postDraw(); + return true; +} + +bool WinSkiaSalGraphicsImpl::RenderAndCacheNativeControl(CompatibleDC& rWhite, CompatibleDC& rBlack, + int nX, int nY, + ControlCacheKey& aControlCacheKey) +{ + assert(dynamic_cast<SkiaCompatibleDC*>(&rWhite)); + assert(dynamic_cast<SkiaCompatibleDC*>(&rBlack)); + + sk_sp<SkImage> image = static_cast<SkiaCompatibleDC&>(rBlack).getAsImageDiff( + static_cast<SkiaCompatibleDC&>(rWhite)); + preDraw(); + SAL_INFO("vcl.skia.trace", + "renderandcachednativecontrol(" + << this << "): " << SkIRect::MakeXYWH(nX, nY, image->width(), image->height())); + mSurface->getCanvas()->drawImage(image, nX, nY); + postDraw(); + + if (!aControlCacheKey.canCacheControl()) + return true; + SkiaControlCachePair pair(aControlCacheKey, std::move(image)); + SkiaControlsCache::get().insert(std::move(pair)); + return true; +} + +#ifdef SAL_LOG_INFO +static HRESULT checkResult(HRESULT hr, const char* file, size_t line) +{ + if (FAILED(hr)) + { + OUString sLocationString + = OUString::createFromAscii(file) + ":" + OUString::number(line) + " "; + SAL_DETAIL_LOG_STREAM(SAL_DETAIL_ENABLE_LOG_INFO, ::SAL_DETAIL_LOG_LEVEL_INFO, "vcl.skia", + sLocationString.toUtf8().getStr(), + "HRESULT failed with: 0x" << OUString::number(hr, 16) << ": " + << WindowsErrorStringFromHRESULT(hr)); + } + return hr; +} + +#define CHECKHR(funct) checkResult(funct, __FILE__, __LINE__) +#else +#define CHECKHR(funct) (funct) +#endif + +sk_sp<SkTypeface> WinSkiaSalGraphicsImpl::createDirectWriteTypeface(const LOGFONTW& logFont) +{ + if (!dwriteDone) + { + if (SUCCEEDED( + CHECKHR(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), + reinterpret_cast<IUnknown**>(&dwriteFactory))))) + { + if (SUCCEEDED(CHECKHR(dwriteFactory->GetGdiInterop(&dwriteGdiInterop)))) + dwriteFontMgr = SkFontMgr_New_DirectWrite(dwriteFactory); + else + dwriteFactory->Release(); + } + dwriteDone = true; + } + IDWriteFont* font = nullptr; + IDWriteFontFace* fontFace; + IDWriteFontFamily* fontFamily; + if (FAILED(CHECKHR(dwriteGdiInterop->CreateFontFromLOGFONT(&logFont, &font)))) + return nullptr; + if (FAILED(CHECKHR(font->CreateFontFace(&fontFace)))) + return nullptr; + if (FAILED(CHECKHR(font->GetFontFamily(&fontFamily)))) + return nullptr; + return sk_sp<SkTypeface>( + SkCreateTypefaceDirectWrite(dwriteFontMgr, fontFace, font, fontFamily)); +} + +bool WinSkiaSalGraphicsImpl::DrawTextLayout(const GenericSalLayout& rLayout) +{ + const WinFontInstance& rWinFont = static_cast<const WinFontInstance&>(rLayout.GetFont()); + float fHScale = rWinFont.getHScale(); + + assert(dynamic_cast<const WinFontInstance*>(&rLayout.GetFont())); + const WinFontInstance* pWinFont = static_cast<const WinFontInstance*>(&rLayout.GetFont()); + const HFONT hLayoutFont = pWinFont->GetHFONT(); + LOGFONTW logFont; + if (GetObjectW(hLayoutFont, sizeof(logFont), &logFont) == 0) + { + assert(false); + return false; + } + sk_sp<SkTypeface> typeface = createDirectWriteTypeface(logFont); + GlyphOrientation glyphOrientation = GlyphOrientation::Apply; + if (!typeface) // fall back to GDI text rendering + { + typeface.reset(SkCreateTypefaceFromLOGFONT(logFont)); + glyphOrientation = GlyphOrientation::Ignore; + } + // lfHeight actually depends on DPI, so it's not really font height as such, + // but for LOGFONT-based typefaces Skia simply sets lfHeight back to this value + // directly. + double fontHeight = logFont.lfHeight; + if (fontHeight < 0) + fontHeight = -fontHeight; + SkFont font(typeface, fontHeight, fHScale, 0); + font.setEdging(getFontEdging()); + assert(dynamic_cast<SkiaSalGraphicsImpl*>(mWinParent.GetImpl())); + SkiaSalGraphicsImpl* impl = static_cast<SkiaSalGraphicsImpl*>(mWinParent.GetImpl()); + COLORREF color = ::GetTextColor(mWinParent.getHDC()); + Color salColor(GetRValue(color), GetGValue(color), GetBValue(color)); + // The font already is set up to have glyphs rotated as needed. + impl->drawGenericLayout(rLayout, salColor, font, glyphOrientation); + return true; +} + +SkFont::Edging WinSkiaSalGraphicsImpl::getFontEdging() +{ + if (fontEdgingDone) + return fontEdging; + // Skia needs to be explicitly told what kind of antialiasing should be used, + // get it from system settings. This does not actually matter for the text + // rendering itself, since Skia has been patched to simply use the setting + // from the LOGFONT, which gets set by VCL's ImplGetLogFontFromFontSelect() + // and that one normally uses DEFAULT_QUALITY, so Windows will select + // the appropriate AA setting. But Skia internally chooses the format to which + // the glyphs will be rendered based on this setting (subpixel AA requires colors, + // others do not). + fontEdging = SkFont::Edging::kAlias; + SkFontLCDConfig::LCDOrder lcdOrder = SkFontLCDConfig::kNONE_LCDOrder; + BOOL set; + if (SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &set, 0) && set) + { + UINT set2; + if (SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &set2, 0) + && set2 == FE_FONTSMOOTHINGCLEARTYPE) + { + fontEdging = SkFont::Edging::kSubpixelAntiAlias; + if (SystemParametersInfo(SPI_GETFONTSMOOTHINGORIENTATION, 0, &set2, 0) + && set2 == FE_FONTSMOOTHINGORIENTATIONBGR) + lcdOrder = SkFontLCDConfig::kBGR_LCDOrder; + else + lcdOrder = SkFontLCDConfig::kRGB_LCDOrder; // default + } + else + fontEdging = SkFont::Edging::kAntiAlias; + } + SkFontLCDConfig::SetSubpixelOrder(lcdOrder); + // Cache this, it is actually visible a little bit when profiling. + fontEdgingDone = true; + return fontEdging; +} + +void WinSkiaSalGraphicsImpl::ClearDevFontCache() +{ + dwriteFontMgr.reset(); + dwriteDone = false; + fontEdgingDone = false; +} + +SkiaCompatibleDC::SkiaCompatibleDC(SalGraphics& rGraphics, int x, int y, int width, int height) + : CompatibleDC(rGraphics, x, y, width, height, false) +{ +} + +std::unique_ptr<CompatibleDC::Texture> SkiaCompatibleDC::getAsMaskTexture() const +{ + auto ret = std::make_unique<SkiaCompatibleDC::Texture>(); + ret->image = getAsMaskImage(); + return ret; +} + +sk_sp<SkImage> SkiaCompatibleDC::getAsMaskImage() const +{ + SkiaZone zone; + // mpData is in the BGRA format, with A unused (and set to 0), and RGB are grey, + // so convert it to Skia format, then to 8bit and finally use as alpha mask + SkBitmap tmpBitmap; + if (!tmpBitmap.installPixels(SkImageInfo::Make(maRects.mnSrcWidth, maRects.mnSrcHeight, + kBGRA_8888_SkColorType, kOpaque_SkAlphaType), + mpData, maRects.mnSrcWidth * 4)) + abort(); + tmpBitmap.setImmutable(); + SkBitmap bitmap8; + if (!bitmap8.tryAllocPixels(SkImageInfo::Make(maRects.mnSrcWidth, maRects.mnSrcHeight, + kGray_8_SkColorType, kOpaque_SkAlphaType))) + abort(); + SkCanvas canvas8(bitmap8); + SkPaint paint8; + paint8.setBlendMode(SkBlendMode::kSrc); // copy and convert depth + // The data we got is upside-down. + SkMatrix matrix; + matrix.preTranslate(0, maRects.mnSrcHeight); + matrix.setConcat(matrix, SkMatrix::Scale(1, -1)); + canvas8.concat(matrix); + canvas8.drawBitmap(tmpBitmap, 0, 0, &paint8); + bitmap8.setImmutable(); + // use the 8bit data as an alpha channel + SkBitmap alpha; + alpha.setInfo(bitmap8.info().makeColorType(kAlpha_8_SkColorType), bitmap8.rowBytes()); + alpha.setPixelRef(sk_ref_sp(bitmap8.pixelRef()), bitmap8.pixelRefOrigin().x(), + bitmap8.pixelRefOrigin().y()); + alpha.setImmutable(); + return SkiaHelper::createSkImage(alpha); +} + +sk_sp<SkImage> SkiaCompatibleDC::getAsImage() const +{ + SkiaZone zone; + SkBitmap tmpBitmap; + if (!tmpBitmap.installPixels(SkImageInfo::Make(maRects.mnSrcWidth, maRects.mnSrcHeight, + kBGRA_8888_SkColorType, kUnpremul_SkAlphaType), + mpData, maRects.mnSrcWidth * 4)) + abort(); + tmpBitmap.setImmutable(); + sk_sp<SkSurface> surface = SkiaHelper::createSkSurface(tmpBitmap.width(), tmpBitmap.height()); + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha + SkCanvas* canvas = surface->getCanvas(); + canvas->save(); + // The data we got is upside-down. + SkMatrix matrix; + matrix.preTranslate(0, maRects.mnSrcHeight); + matrix.setConcat(matrix, SkMatrix::Scale(1, -1)); + canvas->concat(matrix); + canvas->drawBitmapRect(tmpBitmap, + SkRect::MakeXYWH(0, 0, maRects.mnSrcWidth, maRects.mnSrcHeight), + SkRect::MakeXYWH(0, 0, maRects.mnSrcWidth, maRects.mnSrcHeight), &paint); + canvas->restore(); + return SkiaHelper::makeCheckedImageSnapshot(surface); +} + +sk_sp<SkImage> SkiaCompatibleDC::getAsImageDiff(const SkiaCompatibleDC& white) const +{ + SkiaZone zone; + assert(maRects.mnSrcWidth == white.maRects.mnSrcWidth + || maRects.mnSrcHeight == white.maRects.mnSrcHeight); + SkBitmap tmpBitmap; + if (!tmpBitmap.tryAllocPixels(SkImageInfo::Make(maRects.mnSrcWidth, maRects.mnSrcHeight, + kBGRA_8888_SkColorType, kPremul_SkAlphaType), + maRects.mnSrcWidth * 4)) + abort(); + // Native widgets are drawn twice on black/white background to synthetize alpha + // (commit c6b66646870cb2bffaa73565affcf80bf74e0b5c). The problem is that + // most widgets when drawn on transparent background are drawn properly (and the result + // is in premultiplied alpha format), some such as "Edit" (used by ControlType::Editbox) + // keep the alpha channel as transparent. Therefore the alpha is actually computed + // from the difference in the premultiplied red channels when drawn one black and on white. + // Alpha is computed as "alpha = 1.0 - abs(black.red - white.red)". + // TODO I doubt this can be done using Skia, so do it manually here. Fortunately + // the bitmaps should be fairly small and are cached. + uint32_t* dest = tmpBitmap.getAddr32(0, 0); + assert(dest == tmpBitmap.getPixels()); + const sal_uInt32* src = mpData; + const sal_uInt32* whiteSrc = white.mpData; + uint32_t* end = dest + tmpBitmap.width() * tmpBitmap.height(); + while (dest < end) + { + uint32_t alpha = 255 - abs(int(*src & 0xff) - int(*whiteSrc & 0xff)); + *dest = (*src & 0x00ffffff) | (alpha << 24); + ++dest; + ++src; + ++whiteSrc; + } + tmpBitmap.notifyPixelsChanged(); + tmpBitmap.setImmutable(); + sk_sp<SkSurface> surface = SkiaHelper::createSkSurface(tmpBitmap.width(), tmpBitmap.height()); + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha + SkCanvas* canvas = surface->getCanvas(); + canvas->save(); + // The data we got is upside-down. + SkMatrix matrix; + matrix.preTranslate(0, tmpBitmap.height()); + matrix.setConcat(matrix, SkMatrix::Scale(1, -1)); + canvas->concat(matrix); + canvas->drawBitmap(tmpBitmap, 0, 0, &paint); + canvas->restore(); + return SkiaHelper::makeCheckedImageSnapshot(surface); +} + +SkiaControlsCache::SkiaControlsCache() + : cache(200) +{ +} + +SkiaControlCacheType& SkiaControlsCache::get() +{ + SalData* data = GetSalData(); + if (!data->m_pSkiaControlsCache) + data->m_pSkiaControlsCache.reset(new SkiaControlsCache); + return data->m_pSkiaControlsCache->cache; +} + +namespace +{ +std::unique_ptr<sk_app::WindowContext> createVulkanWindowContext(bool /*temporary*/) +{ + SkiaZone zone; + sk_app::DisplayParams displayParams; + return sk_app::window_context_factory::MakeVulkanForWin(nullptr, displayParams); +} +} + +void WinSkiaSalGraphicsImpl::prepareSkia() { SkiaHelper::prepareSkia(createVulkanWindowContext); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |