From 0874e47a3536da3833da4dac284c0245e62b0275 Mon Sep 17 00:00:00 2001 From: tpoole Date: Tue, 27 Jun 2017 15:31:54 +0100 Subject: [PATCH] Graphics code refactoring --- modules/juce_graphics/juce_graphics.cpp | 2 +- modules/juce_graphics/juce_graphics.h | 4 + .../juce_win32_Direct2DGraphicsContext.cpp | 1535 ++++++++--------- .../juce_win32_Direct2DGraphicsContext.h | 103 ++ .../native/juce_win32_DirectWriteTypeface.cpp | 2 + .../native/juce_win32_Windowing.cpp | 54 +- 6 files changed, 898 insertions(+), 802 deletions(-) create mode 100644 modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.h diff --git a/modules/juce_graphics/juce_graphics.cpp b/modules/juce_graphics/juce_graphics.cpp index c7f02b145a..6884981880 100644 --- a/modules/juce_graphics/juce_graphics.cpp +++ b/modules/juce_graphics/juce_graphics.cpp @@ -57,7 +57,7 @@ #undef JUCE_USE_DIRECTWRITE #endif - #if JUCE_USE_DIRECTWRITE + #if JUCE_USE_DIRECTWRITE || JUCE_DIRECT2D /* If you hit a compile error trying to include these files, you may need to update your version of the Windows SDK to the latest one. The DirectWrite and Direct2D headers are in the version 7 SDKs. diff --git a/modules/juce_graphics/juce_graphics.h b/modules/juce_graphics/juce_graphics.h index b16cfd5cca..2c4c64776a 100644 --- a/modules/juce_graphics/juce_graphics.h +++ b/modules/juce_graphics/juce_graphics.h @@ -142,4 +142,8 @@ class LowLevelGraphicsContext; #include "native/juce_mac_CoreGraphicsContext.h" #endif +#if JUCE_DIRECT2D && JUCE_WINDOWS +#include "native/juce_win32_Direct2DGraphicsContext.h" +#endif + } diff --git a/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.cpp b/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.cpp index 550aa5f47b..40f848e58d 100644 --- a/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.cpp +++ b/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.cpp @@ -24,714 +24,110 @@ ============================================================================== */ -class Direct2DLowLevelGraphicsContext : public LowLevelGraphicsContext +template +D2D1_RECT_F rectangleToRectF (const Rectangle& r) { -public: - Direct2DLowLevelGraphicsContext (HWND hwnd_) - : hwnd (hwnd_), - currentState (nullptr) + return D2D1::RectF ((float) r.getX(), (float) r.getY(), (float) r.getRight(), (float) r.getBottom()); +} + +static D2D1_COLOR_F colourToD2D (Colour c) +{ + return D2D1::ColorF::ColorF (c.getFloatRed(), c.getFloatGreen(), c.getFloatBlue(), c.getFloatAlpha()); +} + +static void pathToGeometrySink (const Path& path, ID2D1GeometrySink* sink, const AffineTransform& transform) +{ + Path::Iterator it (path); + + while (it.next()) { - RECT windowRect; - GetClientRect (hwnd, &windowRect); - D2D1_SIZE_U size = { windowRect.right - windowRect.left, windowRect.bottom - windowRect.top }; - bounds.setSize (size.width, size.height); - - D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(); - D2D1_HWND_RENDER_TARGET_PROPERTIES propsHwnd = D2D1::HwndRenderTargetProperties (hwnd, size); - - if (factories->d2dFactory != nullptr) + switch (it.elementType) { - HRESULT hr = factories->d2dFactory->CreateHwndRenderTarget (props, propsHwnd, renderingTarget.resetAndGetPointerAddress()); - jassert (SUCCEEDED (hr)); ignoreUnused (hr); - hr = renderingTarget->CreateSolidColorBrush (D2D1::ColorF::ColorF (0.0f, 0.0f, 0.0f, 1.0f), colourBrush.resetAndGetPointerAddress()); - } - } - - ~Direct2DLowLevelGraphicsContext() - { - states.clear(); - } - - void resized() - { - RECT windowRect; - GetClientRect (hwnd, &windowRect); - D2D1_SIZE_U size = { windowRect.right - windowRect.left, windowRect.bottom - windowRect.top }; - - renderingTarget->Resize (size); - bounds.setSize (size.width, size.height); - } - - void clear() - { - renderingTarget->Clear (D2D1::ColorF (D2D1::ColorF::White, 0.0f)); // xxx why white and not black? - } - - void start() - { - renderingTarget->BeginDraw(); - saveState(); - } - - void end() - { - states.clear(); - currentState = 0; - renderingTarget->EndDraw(); - renderingTarget->CheckWindowState(); - } - - bool isVectorDevice() const { return false; } - - void setOrigin (Point o) - { - addTransform (AffineTransform::translation ((float) o.x, (float) o.y)); - } - - void addTransform (const AffineTransform& transform) - { - currentState->transform = transform.followedBy (currentState->transform); - } - - float getPhysicalPixelScaleFactor() - { - return currentState->transform.getScaleFactor(); - } - - bool clipToRectangle (const Rectangle& r) - { - currentState->clipToRectangle (r); - return ! isClipEmpty(); - } - - bool clipToRectangleList (const RectangleList& clipRegion) - { - currentState->clipToRectList (rectListToPathGeometry (clipRegion)); - return ! isClipEmpty(); - } - - void excludeClipRectangle (const Rectangle&) - { - //xxx - } - - void clipToPath (const Path& path, const AffineTransform& transform) - { - currentState->clipToPath (pathToPathGeometry (path, transform)); - } - - void clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform) - { - currentState->clipToImage (sourceImage, transform); - } - - bool clipRegionIntersects (const Rectangle& r) - { - return currentState->clipRect.intersects (r.toFloat().transformed (currentState->transform).getSmallestIntegerContainer()); - } - - Rectangle getClipBounds() const - { - // xxx could this take into account complex clip regions? - return currentState->clipRect.toFloat().transformed (currentState->transform.inverted()).getSmallestIntegerContainer(); - } - - bool isClipEmpty() const - { - return currentState->clipRect.isEmpty(); - } - - void saveState() - { - states.add (new SavedState (*this)); - currentState = states.getLast(); - } - - void restoreState() - { - jassert (states.size() > 1) //you should never pop the last state! - states.removeLast (1); - currentState = states.getLast(); - } - - void beginTransparencyLayer (float /*opacity*/) - { - jassertfalse; //xxx todo - } - - void endTransparencyLayer() - { - jassertfalse; //xxx todo - } - - void setFill (const FillType& fillType) - { - currentState->setFill (fillType); - } - - void setOpacity (float newOpacity) - { - currentState->setOpacity (newOpacity); - } - - void setInterpolationQuality (Graphics::ResamplingQuality /*quality*/) - { - } - - void fillRect (const Rectangle& r, bool /*replaceExistingContents*/) - { - fillRect (r.toFloat()); - } - - void fillRect (const Rectangle& r) - { - renderingTarget->SetTransform (transformToMatrix (currentState->transform)); - currentState->createBrush(); - renderingTarget->FillRectangle (rectangleToRectF (r), currentState->currentBrush); - renderingTarget->SetTransform (D2D1::IdentityMatrix()); - } - - void fillRectList (const RectangleList& list) - { - for (auto& r : list) - fillRect (r); - } - - void fillPath (const Path& p, const AffineTransform& transform) - { - currentState->createBrush(); - ComSmartPtr geometry (pathToPathGeometry (p, transform.followedBy (currentState->transform))); - - if (renderingTarget != nullptr) - renderingTarget->FillGeometry (geometry, currentState->currentBrush); - } - - void drawImage (const Image& image, const AffineTransform& transform) - { - renderingTarget->SetTransform (transformToMatrix (transform.followedBy (currentState->transform))); - - D2D1_SIZE_U size; - size.width = image.getWidth(); - size.height = image.getHeight(); - - D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties(); - - Image img (image.convertedToFormat (Image::ARGB)); - Image::BitmapData bd (img, Image::BitmapData::readOnly); - bp.pixelFormat = renderingTarget->GetPixelFormat(); - bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; - + case Path::Iterator::cubicTo: { - ComSmartPtr tempBitmap; - renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, tempBitmap.resetAndGetPointerAddress()); - if (tempBitmap != nullptr) - renderingTarget->DrawBitmap (tempBitmap); + D2D1_BEZIER_SEGMENT seg; + + transform.transformPoint (it.x1, it.y1); + seg.point1 = D2D1::Point2F (it.x1, it.y1); + + transform.transformPoint (it.x2, it.y2); + seg.point2 = D2D1::Point2F (it.x2, it.y2); + + transform.transformPoint (it.x3, it.y3); + seg.point3 = D2D1::Point2F (it.x3, it.y3); + + sink->AddBezier (seg); + break; } - renderingTarget->SetTransform (D2D1::IdentityMatrix()); + case Path::Iterator::lineTo: + { + transform.transformPoint (it.x1, it.y1); + sink->AddLine (D2D1::Point2F (it.x1, it.y1)); + break; + } + + case Path::Iterator::quadraticTo: + { + D2D1_QUADRATIC_BEZIER_SEGMENT seg; + + transform.transformPoint (it.x1, it.y1); + seg.point1 = D2D1::Point2F (it.x1, it.y1); + + transform.transformPoint (it.x2, it.y2); + seg.point2 = D2D1::Point2F (it.x2, it.y2); + + sink->AddQuadraticBezier (seg); + break; + } + + case Path::Iterator::closePath: + { + sink->EndFigure (D2D1_FIGURE_END_CLOSED); + break; + } + + case Path::Iterator::startNewSubPath: + { + transform.transformPoint (it.x1, it.y1); + sink->BeginFigure (D2D1::Point2F (it.x1, it.y1), D2D1_FIGURE_BEGIN_FILLED); + break; + } + } } - - void drawLine (const Line & line) - { - // xxx doesn't seem to be correctly aligned, may need nudging by 0.5 to match the software renderer's behaviour - renderingTarget->SetTransform (transformToMatrix (currentState->transform)); - currentState->createBrush(); - - renderingTarget->DrawLine (D2D1::Point2F (line.getStartX(), line.getStartY()), - D2D1::Point2F (line.getEndX(), line.getEndY()), - currentState->currentBrush); - renderingTarget->SetTransform (D2D1::IdentityMatrix()); - } - - void setFont (const Font& newFont) - { - currentState->setFont (newFont); - } - - const Font& getFont() - { - return currentState->font; - } - - void drawGlyph (int glyphNumber, const AffineTransform& transform) - { - currentState->createBrush(); - currentState->createFont(); - - float hScale = currentState->font.getHorizontalScale(); - - renderingTarget->SetTransform (transformToMatrix (AffineTransform::scale (hScale, 1.0f) - .followedBy (transform) - .followedBy (currentState->transform))); - - const UINT16 glyphIndices = (UINT16) glyphNumber; - const FLOAT glyphAdvances = 0; - DWRITE_GLYPH_OFFSET offset; - offset.advanceOffset = 0; - offset.ascenderOffset = 0; - - DWRITE_GLYPH_RUN glyphRun; - glyphRun.fontFace = currentState->currentFontFace; - glyphRun.fontEmSize = (FLOAT) (currentState->font.getHeight() * currentState->fontHeightToEmSizeFactor); - glyphRun.glyphCount = 1; - glyphRun.glyphIndices = &glyphIndices; - glyphRun.glyphAdvances = &glyphAdvances; - glyphRun.glyphOffsets = &offset; - glyphRun.isSideways = FALSE; - glyphRun.bidiLevel = 0; - - renderingTarget->DrawGlyphRun (D2D1::Point2F (0, 0), &glyphRun, currentState->currentBrush); - renderingTarget->SetTransform (D2D1::IdentityMatrix()); - } - - bool drawTextLayout (const AttributedString& text, const Rectangle& area) - { - renderingTarget->SetTransform (transformToMatrix (currentState->transform)); - - DirectWriteTypeLayout::drawToD2DContext (text, area, *renderingTarget, factories->directWriteFactory, - factories->d2dFactory, factories->systemFonts); - - renderingTarget->SetTransform (D2D1::IdentityMatrix()); - return true; - } - - //============================================================================== - class SavedState - { - public: - SavedState (Direct2DLowLevelGraphicsContext& owner_) - : owner (owner_), currentBrush (0), - fontHeightToEmSizeFactor (1.0f), currentFontFace (0), - clipsRect (false), shouldClipRect (false), - clipsRectList (false), shouldClipRectList (false), - clipsComplex (false), shouldClipComplex (false), - clipsBitmap (false), shouldClipBitmap (false) - { - if (owner.currentState != nullptr) - { - // xxx seems like a very slow way to create one of these, and this is a performance - // bottleneck.. Can the same internal objects be shared by multiple state objects, maybe using copy-on-write? - setFill (owner.currentState->fillType); - currentBrush = owner.currentState->currentBrush; - clipRect = owner.currentState->clipRect; - transform = owner.currentState->transform; - - font = owner.currentState->font; - currentFontFace = owner.currentState->currentFontFace; - } - else - { - const D2D1_SIZE_U size (owner.renderingTarget->GetPixelSize()); - clipRect.setSize (size.width, size.height); - setFill (FillType (Colours::black)); - } - } - - ~SavedState() - { - clearClip(); - clearFont(); - clearFill(); - clearPathClip(); - clearImageClip(); - complexClipLayer = 0; - bitmapMaskLayer = 0; - } - - void clearClip() - { - popClips(); - shouldClipRect = false; - } - - void clipToRectangle (const Rectangle& r) - { - clearClip(); - clipRect = r.toFloat().transformed (transform).getSmallestIntegerContainer(); - shouldClipRect = true; - pushClips(); - } - - void clearPathClip() - { - popClips(); - - if (shouldClipComplex) - { - complexClipGeometry = 0; - shouldClipComplex = false; - } - } - - void clipToPath (ID2D1Geometry* geometry) - { - clearPathClip(); - - if (complexClipLayer == 0) - owner.renderingTarget->CreateLayer (complexClipLayer.resetAndGetPointerAddress()); - - complexClipGeometry = geometry; - shouldClipComplex = true; - pushClips(); - } - - void clearRectListClip() - { - popClips(); - - if (shouldClipRectList) - { - rectListGeometry = 0; - shouldClipRectList = false; - } - } - - void clipToRectList (ID2D1Geometry* geometry) - { - clearRectListClip(); - - if (rectListLayer == 0) - owner.renderingTarget->CreateLayer (rectListLayer.resetAndGetPointerAddress()); - - rectListGeometry = geometry; - shouldClipRectList = true; - pushClips(); - } - - void clearImageClip() - { - popClips(); - - if (shouldClipBitmap) - { - maskBitmap = 0; - bitmapMaskBrush = 0; - shouldClipBitmap = false; - } - } - - void clipToImage (const Image& image, const AffineTransform& transform) - { - clearImageClip(); - - if (bitmapMaskLayer == 0) - owner.renderingTarget->CreateLayer (bitmapMaskLayer.resetAndGetPointerAddress()); - - D2D1_BRUSH_PROPERTIES brushProps; - brushProps.opacity = 1; - brushProps.transform = transformToMatrix (transform); - - D2D1_BITMAP_BRUSH_PROPERTIES bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP); - - D2D1_SIZE_U size; - size.width = image.getWidth(); - size.height = image.getHeight(); - - D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties(); - - maskImage = image.convertedToFormat (Image::ARGB); - Image::BitmapData bd (this->image, Image::BitmapData::readOnly); // xxx should be maskImage? - bp.pixelFormat = owner.renderingTarget->GetPixelFormat(); - bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; - - HRESULT hr = owner.renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, maskBitmap.resetAndGetPointerAddress()); - hr = owner.renderingTarget->CreateBitmapBrush (maskBitmap, bmProps, brushProps, bitmapMaskBrush.resetAndGetPointerAddress()); - - imageMaskLayerParams = D2D1::LayerParameters(); - imageMaskLayerParams.opacityBrush = bitmapMaskBrush; - - shouldClipBitmap = true; - pushClips(); - } - - void popClips() - { - if (clipsBitmap) - { - owner.renderingTarget->PopLayer(); - clipsBitmap = false; - } - - if (clipsComplex) - { - owner.renderingTarget->PopLayer(); - clipsComplex = false; - } - - if (clipsRectList) - { - owner.renderingTarget->PopLayer(); - clipsRectList = false; - } - - if (clipsRect) - { - owner.renderingTarget->PopAxisAlignedClip(); - clipsRect = false; - } - } - - void pushClips() - { - if (shouldClipRect && ! clipsRect) - { - owner.renderingTarget->PushAxisAlignedClip (rectangleToRectF (clipRect), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); - clipsRect = true; - } - - if (shouldClipRectList && ! clipsRectList) - { - D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters(); - rectListGeometry->GetBounds (D2D1::IdentityMatrix(), &layerParams.contentBounds); - layerParams.geometricMask = rectListGeometry; - owner.renderingTarget->PushLayer (layerParams, rectListLayer); - clipsRectList = true; - } - - if (shouldClipComplex && ! clipsComplex) - { - D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters(); - complexClipGeometry->GetBounds (D2D1::IdentityMatrix(), &layerParams.contentBounds); - layerParams.geometricMask = complexClipGeometry; - owner.renderingTarget->PushLayer (layerParams, complexClipLayer); - clipsComplex = true; - } - - if (shouldClipBitmap && ! clipsBitmap) - { - owner.renderingTarget->PushLayer (imageMaskLayerParams, bitmapMaskLayer); - clipsBitmap = true; - } - } - - void setFill (const FillType& newFillType) - { - if (fillType != newFillType) - { - fillType = newFillType; - clearFill(); - } - } - - void clearFont() - { - currentFontFace = localFontFace = 0; - } - - void setFont (const Font& newFont) - { - if (font != newFont) - { - font = newFont; - clearFont(); - } - } - - void createFont() - { - if (currentFontFace == nullptr) - { - WindowsDirectWriteTypeface* typeface = dynamic_cast (font.getTypeface()); - currentFontFace = typeface->getIDWriteFontFace(); - fontHeightToEmSizeFactor = typeface->unitsToHeightScaleFactor(); - } - } - - void setOpacity (float newOpacity) - { - fillType.setOpacity (newOpacity); - - if (currentBrush != nullptr) - currentBrush->SetOpacity (newOpacity); - } - - void clearFill() - { - gradientStops = 0; - linearGradient = 0; - radialGradient = 0; - bitmap = 0; - bitmapBrush = 0; - currentBrush = 0; - } - - void createBrush() - { - if (currentBrush == 0) - { - if (fillType.isColour()) - { - D2D1_COLOR_F colour = colourToD2D (fillType.colour); - owner.colourBrush->SetColor (colour); - currentBrush = owner.colourBrush; - } - else if (fillType.isTiledImage()) - { - D2D1_BRUSH_PROPERTIES brushProps; - brushProps.opacity = fillType.getOpacity(); - brushProps.transform = transformToMatrix (fillType.transform); - - D2D1_BITMAP_BRUSH_PROPERTIES bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP,D2D1_EXTEND_MODE_WRAP); - - image = fillType.image; - - D2D1_SIZE_U size; - size.width = image.getWidth(); - size.height = image.getHeight(); - - D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties(); - - this->image = image.convertedToFormat (Image::ARGB); - Image::BitmapData bd (this->image, Image::BitmapData::readOnly); - bp.pixelFormat = owner.renderingTarget->GetPixelFormat(); - bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; - - HRESULT hr = owner.renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, bitmap.resetAndGetPointerAddress()); - hr = owner.renderingTarget->CreateBitmapBrush (bitmap, bmProps, brushProps, bitmapBrush.resetAndGetPointerAddress()); - - currentBrush = bitmapBrush; - } - else if (fillType.isGradient()) - { - gradientStops = 0; - - D2D1_BRUSH_PROPERTIES brushProps; - brushProps.opacity = fillType.getOpacity(); - brushProps.transform = transformToMatrix (fillType.transform.followedBy (transform)); - - const int numColors = fillType.gradient->getNumColours(); - - HeapBlock stops (numColors); - - for (int i = fillType.gradient->getNumColours(); --i >= 0;) - { - stops[i].color = colourToD2D (fillType.gradient->getColour(i)); - stops[i].position = (FLOAT) fillType.gradient->getColourPosition(i); - } - - owner.renderingTarget->CreateGradientStopCollection (stops.getData(), numColors, gradientStops.resetAndGetPointerAddress()); - - if (fillType.gradient->isRadial) - { - radialGradient = 0; - - const Point p1 = fillType.gradient->point1; - const Point p2 = fillType.gradient->point2; - float r = p1.getDistanceFrom (p2); - - D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES props = - D2D1::RadialGradientBrushProperties (D2D1::Point2F (p1.x, p1.y), - D2D1::Point2F (0, 0), - r, r); - - owner.renderingTarget->CreateRadialGradientBrush (props, brushProps, gradientStops, radialGradient.resetAndGetPointerAddress()); - currentBrush = radialGradient; - } - else - { - linearGradient = 0; - - const Point p1 = fillType.gradient->point1; - const Point p2 = fillType.gradient->point2; - - D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES props = - D2D1::LinearGradientBrushProperties (D2D1::Point2F (p1.x, p1.y), - D2D1::Point2F (p2.x, p2.y)); - - owner.renderingTarget->CreateLinearGradientBrush (props, brushProps, gradientStops, linearGradient.resetAndGetPointerAddress()); - - currentBrush = linearGradient; - } - } - } - } - - //============================================================================== - //xxx most of these members should probably be private... - - Direct2DLowLevelGraphicsContext& owner; - - AffineTransform transform; - - Font font; - float fontHeightToEmSizeFactor; - IDWriteFontFace* currentFontFace; - ComSmartPtr localFontFace; - - FillType fillType; - - Image image; - ComSmartPtr bitmap; // xxx needs a better name - what is this for?? - - Rectangle clipRect; - bool clipsRect, shouldClipRect; - - ComSmartPtr complexClipGeometry; - D2D1_LAYER_PARAMETERS complexClipLayerParams; - ComSmartPtr complexClipLayer; - bool clipsComplex, shouldClipComplex; - - ComSmartPtr rectListGeometry; - D2D1_LAYER_PARAMETERS rectListLayerParams; - ComSmartPtr rectListLayer; - bool clipsRectList, shouldClipRectList; - - Image maskImage; - D2D1_LAYER_PARAMETERS imageMaskLayerParams; - ComSmartPtr bitmapMaskLayer; - ComSmartPtr maskBitmap; - ComSmartPtr bitmapMaskBrush; - bool clipsBitmap, shouldClipBitmap; - - ID2D1Brush* currentBrush; - ComSmartPtr bitmapBrush; - ComSmartPtr linearGradient; - ComSmartPtr radialGradient; - ComSmartPtr gradientStops; - - private: - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SavedState) - }; - - //============================================================================== -private: - SharedResourcePointer factories; - HWND hwnd; - ComSmartPtr renderingTarget; - ComSmartPtr colourBrush; - Rectangle bounds; - - SavedState* currentState; - OwnedArray states; - - //============================================================================== - template - static D2D1_RECT_F rectangleToRectF (const Rectangle& r) - { - return D2D1::RectF ((float) r.getX(), (float) r.getY(), (float) r.getRight(), (float) r.getBottom()); - } - - static D2D1_COLOR_F colourToD2D (Colour c) - { - return D2D1::ColorF::ColorF (c.getFloatRed(), c.getFloatGreen(), c.getFloatBlue(), c.getFloatAlpha()); - } - - static D2D1_POINT_2F pointTransformed (int x, int y, const AffineTransform& transform) - { - transform.transformPoint (x, y); - return D2D1::Point2F ((FLOAT) x, (FLOAT) y); - } - - static void rectToGeometrySink (const Rectangle& rect, ID2D1GeometrySink* sink) - { - sink->BeginFigure (pointTransformed (rect.getX(), rect.getY()), D2D1_FIGURE_BEGIN_FILLED); - sink->AddLine (pointTransformed (rect.getRight(), rect.getY())); - sink->AddLine (pointTransformed (rect.getRight(), rect.getBottom())); - sink->AddLine (pointTransformed (rect.getX(), rect.getBottom())); - sink->EndFigure (D2D1_FIGURE_END_CLOSED); - } - - static ID2D1PathGeometry* rectListToPathGeometry (const RectangleList& clipRegion) +} + +static D2D1::Matrix3x2F transformToMatrix (const AffineTransform& transform) +{ + D2D1::Matrix3x2F matrix; + matrix._11 = transform.mat00; + matrix._12 = transform.mat10; + matrix._21 = transform.mat01; + matrix._22 = transform.mat11; + matrix._31 = transform.mat02; + matrix._32 = transform.mat12; + return matrix; +} + +static D2D1_POINT_2F pointTransformed (int x, int y, const AffineTransform& transform) +{ + transform.transformPoint (x, y); + return D2D1::Point2F ((FLOAT) x, (FLOAT) y); +} + +static void rectToGeometrySink (const Rectangle& rect, ID2D1GeometrySink* sink, const AffineTransform& transform) +{ + sink->BeginFigure (pointTransformed (rect.getX(), rect.getY(), transform), D2D1_FIGURE_BEGIN_FILLED); + sink->AddLine (pointTransformed (rect.getRight(), rect.getY(), transform)); + sink->AddLine (pointTransformed (rect.getRight(), rect.getBottom(), transform)); + sink->AddLine (pointTransformed (rect.getX(), rect.getBottom(), transform)); + sink->EndFigure (D2D1_FIGURE_END_CLOSED); +} + +//============================================================================== +struct Direct2DLowLevelGraphicsContext::Pimpl +{ + ID2D1PathGeometry* rectListToPathGeometry (const RectangleList& clipRegion) { ID2D1PathGeometry* p = nullptr; factories->d2dFactory->CreatePathGeometry (&p); @@ -741,75 +137,13 @@ private: sink->SetFillMode (D2D1_FILL_MODE_WINDING); for (int i = clipRegion.getNumRectangles(); --i >= 0;) - rectToGeometrySink (clipRegion.getRectangle(i), sink); + rectToGeometrySink (clipRegion.getRectangle(i), sink, AffineTransform()); hr = sink->Close(); return p; } - static void pathToGeometrySink (const Path& path, ID2D1GeometrySink* sink, const AffineTransform& transform) - { - Path::Iterator it (path); - - while (it.next()) - { - switch (it.elementType) - { - case Path::Iterator::cubicTo: - { - D2D1_BEZIER_SEGMENT seg; - - transform.transformPoint (it.x1, it.y1); - seg.point1 = D2D1::Point2F (it.x1, it.y1); - - transform.transformPoint (it.x2, it.y2); - seg.point2 = D2D1::Point2F (it.x2, it.y2); - - transform.transformPoint(it.x3, it.y3); - seg.point3 = D2D1::Point2F (it.x3, it.y3); - - sink->AddBezier (seg); - break; - } - - case Path::Iterator::lineTo: - { - transform.transformPoint (it.x1, it.y1); - sink->AddLine (D2D1::Point2F (it.x1, it.y1)); - break; - } - - case Path::Iterator::quadraticTo: - { - D2D1_QUADRATIC_BEZIER_SEGMENT seg; - - transform.transformPoint (it.x1, it.y1); - seg.point1 = D2D1::Point2F (it.x1, it.y1); - - transform.transformPoint (it.x2, it.y2); - seg.point2 = D2D1::Point2F (it.x2, it.y2); - - sink->AddQuadraticBezier (seg); - break; - } - - case Path::Iterator::closePath: - { - sink->EndFigure (D2D1_FIGURE_END_CLOSED); - break; - } - - case Path::Iterator::startNewSubPath: - { - transform.transformPoint (it.x1, it.y1); - sink->BeginFigure (D2D1::Point2F (it.x1, it.y1), D2D1_FIGURE_BEGIN_FILLED); - break; - } - } - } - } - - static ID2D1PathGeometry* pathToPathGeometry (const Path& path, const AffineTransform& transform) + ID2D1PathGeometry* pathToPathGeometry (const Path& path, const AffineTransform& transform) { ID2D1PathGeometry* p = nullptr; factories->d2dFactory->CreatePathGeometry (&p); @@ -824,17 +158,668 @@ private: return p; } - static D2D1::Matrix3x2F transformToMatrix (const AffineTransform& transform) + SharedResourcePointer factories; + + ComSmartPtr renderingTarget; + ComSmartPtr colourBrush; +}; + +//============================================================================== +struct Direct2DLowLevelGraphicsContext::SavedState +{ +public: + SavedState (Direct2DLowLevelGraphicsContext& owner_) + : owner (owner_) { - D2D1::Matrix3x2F matrix; - matrix._11 = transform.mat00; - matrix._12 = transform.mat10; - matrix._21 = transform.mat01; - matrix._22 = transform.mat11; - matrix._31 = transform.mat02; - matrix._32 = transform.mat12; - return matrix; + if (owner.currentState != nullptr) + { + // xxx seems like a very slow way to create one of these, and this is a performance + // bottleneck.. Can the same internal objects be shared by multiple state objects, maybe using copy-on-write? + setFill (owner.currentState->fillType); + currentBrush = owner.currentState->currentBrush; + clipRect = owner.currentState->clipRect; + transform = owner.currentState->transform; + + font = owner.currentState->font; + currentFontFace = owner.currentState->currentFontFace; + } + else + { + const D2D1_SIZE_U size (owner.pimpl->renderingTarget->GetPixelSize()); + clipRect.setSize (size.width, size.height); + setFill (FillType (Colours::black)); + } } - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DLowLevelGraphicsContext) + ~SavedState() + { + clearClip(); + clearFont(); + clearFill(); + clearPathClip(); + clearImageClip(); + complexClipLayer = nullptr; + bitmapMaskLayer = nullptr; + } + + void clearClip() + { + popClips(); + shouldClipRect = false; + } + + void clipToRectangle (const Rectangle& r) + { + clearClip(); + clipRect = r.toFloat().transformed (transform).getSmallestIntegerContainer(); + shouldClipRect = true; + pushClips(); + } + + void clearPathClip() + { + popClips(); + + if (shouldClipComplex) + { + complexClipGeometry = nullptr; + shouldClipComplex = false; + } + } + + void Direct2DLowLevelGraphicsContext::SavedState::clipToPath (ID2D1Geometry* geometry) + { + clearPathClip(); + + if (complexClipLayer == nullptr) + owner.pimpl->renderingTarget->CreateLayer (complexClipLayer.resetAndGetPointerAddress()); + + complexClipGeometry = geometry; + shouldClipComplex = true; + pushClips(); + } + + void clearRectListClip() + { + popClips(); + + if (shouldClipRectList) + { + rectListGeometry = nullptr; + shouldClipRectList = false; + } + } + + void clipToRectList (ID2D1Geometry* geometry) + { + clearRectListClip(); + + if (rectListLayer == nullptr) + owner.pimpl->renderingTarget->CreateLayer (rectListLayer.resetAndGetPointerAddress()); + + rectListGeometry = geometry; + shouldClipRectList = true; + pushClips(); + } + + void clearImageClip() + { + popClips(); + + if (shouldClipBitmap) + { + maskBitmap = nullptr; + bitmapMaskBrush = nullptr; + shouldClipBitmap = false; + } + } + + void clipToImage (const Image& clipImage, const AffineTransform& clipTransform) + { + clearImageClip(); + + if (bitmapMaskLayer == nullptr) + owner.pimpl->renderingTarget->CreateLayer (bitmapMaskLayer.resetAndGetPointerAddress()); + + D2D1_BRUSH_PROPERTIES brushProps; + brushProps.opacity = 1; + brushProps.transform = transformToMatrix (clipTransform); + + D2D1_BITMAP_BRUSH_PROPERTIES bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP); + + D2D1_SIZE_U size; + size.width = clipImage.getWidth(); + size.height = clipImage.getHeight(); + + D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties(); + + maskImage = clipImage.convertedToFormat (Image::ARGB); + Image::BitmapData bd (maskImage, Image::BitmapData::readOnly); // xxx should be maskImage? + bp.pixelFormat = owner.pimpl->renderingTarget->GetPixelFormat(); + bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; + + HRESULT hr = owner.pimpl->renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, maskBitmap.resetAndGetPointerAddress()); + hr = owner.pimpl->renderingTarget->CreateBitmapBrush (maskBitmap, bmProps, brushProps, bitmapMaskBrush.resetAndGetPointerAddress()); + + imageMaskLayerParams = D2D1::LayerParameters(); + imageMaskLayerParams.opacityBrush = bitmapMaskBrush; + + shouldClipBitmap = true; + pushClips(); + } + + void popClips() + { + if (clipsBitmap) + { + owner.pimpl->renderingTarget->PopLayer(); + clipsBitmap = false; + } + + if (clipsComplex) + { + owner.pimpl->renderingTarget->PopLayer(); + clipsComplex = false; + } + + if (clipsRectList) + { + owner.pimpl->renderingTarget->PopLayer(); + clipsRectList = false; + } + + if (clipsRect) + { + owner.pimpl->renderingTarget->PopAxisAlignedClip(); + clipsRect = false; + } + } + + void pushClips() + { + if (shouldClipRect && !clipsRect) + { + owner.pimpl->renderingTarget->PushAxisAlignedClip (rectangleToRectF (clipRect), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + clipsRect = true; + } + + if (shouldClipRectList && !clipsRectList) + { + D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters(); + rectListGeometry->GetBounds (D2D1::IdentityMatrix(), &layerParams.contentBounds); + layerParams.geometricMask = rectListGeometry; + owner.pimpl->renderingTarget->PushLayer (layerParams, rectListLayer); + clipsRectList = true; + } + + if (shouldClipComplex && !clipsComplex) + { + D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters(); + complexClipGeometry->GetBounds (D2D1::IdentityMatrix(), &layerParams.contentBounds); + layerParams.geometricMask = complexClipGeometry; + owner.pimpl->renderingTarget->PushLayer (layerParams, complexClipLayer); + clipsComplex = true; + } + + if (shouldClipBitmap && !clipsBitmap) + { + owner.pimpl->renderingTarget->PushLayer (imageMaskLayerParams, bitmapMaskLayer); + clipsBitmap = true; + } + } + + void setFill (const FillType& newFillType) + { + if (fillType != newFillType) + { + fillType = newFillType; + clearFill(); + } + } + + void clearFont() + { + currentFontFace = localFontFace = nullptr; + } + + void setFont (const Font& newFont) + { + if (font != newFont) + { + font = newFont; + clearFont(); + } + } + + void createFont() + { + if (currentFontFace == nullptr) + { + WindowsDirectWriteTypeface* typeface = dynamic_cast (font.getTypeface()); + currentFontFace = typeface->getIDWriteFontFace(); + fontHeightToEmSizeFactor = typeface->getUnitsToHeightScaleFactor(); + } + } + + void setOpacity (float newOpacity) + { + fillType.setOpacity (newOpacity); + + if (currentBrush != nullptr) + currentBrush->SetOpacity (newOpacity); + } + + void clearFill() + { + gradientStops = nullptr; + linearGradient = nullptr; + radialGradient = nullptr; + bitmap = nullptr; + bitmapBrush = nullptr; + currentBrush = nullptr; + } + + void createBrush() + { + if (currentBrush == nullptr) + { + if (fillType.isColour()) + { + D2D1_COLOR_F colour = colourToD2D (fillType.colour); + owner.pimpl->colourBrush->SetColor (colour); + currentBrush = owner.pimpl->colourBrush; + } + else if (fillType.isTiledImage()) + { + D2D1_BRUSH_PROPERTIES brushProps; + brushProps.opacity = fillType.getOpacity(); + brushProps.transform = transformToMatrix (fillType.transform); + + D2D1_BITMAP_BRUSH_PROPERTIES bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP); + + image = fillType.image; + + D2D1_SIZE_U size; + size.width = image.getWidth(); + size.height = image.getHeight(); + + D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties(); + + this->image = image.convertedToFormat (Image::ARGB); + Image::BitmapData bd (this->image, Image::BitmapData::readOnly); + bp.pixelFormat = owner.pimpl->renderingTarget->GetPixelFormat(); + bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; + + HRESULT hr = owner.pimpl->renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, bitmap.resetAndGetPointerAddress()); + hr = owner.pimpl->renderingTarget->CreateBitmapBrush (bitmap, bmProps, brushProps, bitmapBrush.resetAndGetPointerAddress()); + + currentBrush = bitmapBrush; + } + else if (fillType.isGradient()) + { + gradientStops = nullptr; + + D2D1_BRUSH_PROPERTIES brushProps; + brushProps.opacity = fillType.getOpacity(); + brushProps.transform = transformToMatrix (fillType.transform.followedBy (transform)); + + const int numColors = fillType.gradient->getNumColours(); + + HeapBlock stops (numColors); + + for (int i = fillType.gradient->getNumColours(); --i >= 0;) + { + stops[i].color = colourToD2D (fillType.gradient->getColour (i)); + stops[i].position = (FLOAT) fillType.gradient->getColourPosition (i); + } + + owner.pimpl->renderingTarget->CreateGradientStopCollection (stops.getData(), numColors, gradientStops.resetAndGetPointerAddress()); + + if (fillType.gradient->isRadial) + { + radialGradient = nullptr; + + const Point p1 = fillType.gradient->point1; + const Point p2 = fillType.gradient->point2; + float r = p1.getDistanceFrom(p2); + + D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES props = + D2D1::RadialGradientBrushProperties(D2D1::Point2F(p1.x, p1.y), + D2D1::Point2F(0, 0), + r, r); + + owner.pimpl->renderingTarget->CreateRadialGradientBrush(props, brushProps, gradientStops, radialGradient.resetAndGetPointerAddress()); + currentBrush = radialGradient; + } + else + { + linearGradient = 0; + + const Point p1 = fillType.gradient->point1; + const Point p2 = fillType.gradient->point2; + + D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES props = + D2D1::LinearGradientBrushProperties(D2D1::Point2F(p1.x, p1.y), + D2D1::Point2F(p2.x, p2.y)); + + owner.pimpl->renderingTarget->CreateLinearGradientBrush (props, brushProps, gradientStops, linearGradient.resetAndGetPointerAddress()); + + currentBrush = linearGradient; + } + } + } + } + + Direct2DLowLevelGraphicsContext& owner; + + AffineTransform transform; + + Font font; + float fontHeightToEmSizeFactor = 1.0; + + IDWriteFontFace* currentFontFace = nullptr; + ComSmartPtr localFontFace; + + Rectangle clipRect; + bool clipsRect = false, shouldClipRect = false; + + Image image; + ComSmartPtr bitmap; // xxx needs a better name - what is this for?? + bool clipsBitmap = false, shouldClipBitmap = false; + + ComSmartPtr complexClipGeometry; + D2D1_LAYER_PARAMETERS complexClipLayerParams; + ComSmartPtr complexClipLayer; + bool clipsComplex = false, shouldClipComplex = false; + + ComSmartPtr rectListGeometry; + D2D1_LAYER_PARAMETERS rectListLayerParams; + ComSmartPtr rectListLayer; + bool clipsRectList = false, shouldClipRectList = false; + + Image maskImage; + D2D1_LAYER_PARAMETERS imageMaskLayerParams; + ComSmartPtr bitmapMaskLayer; + ComSmartPtr maskBitmap; + ComSmartPtr bitmapMaskBrush; + + ID2D1Brush* currentBrush = nullptr; + ComSmartPtr bitmapBrush; + ComSmartPtr linearGradient; + ComSmartPtr radialGradient; + ComSmartPtr gradientStops; + + FillType fillType; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SavedState) }; + +//============================================================================== +Direct2DLowLevelGraphicsContext::Direct2DLowLevelGraphicsContext (HWND hwnd_) + : hwnd (hwnd_), + currentState (nullptr), + pimpl (new Pimpl()) +{ + RECT windowRect; + GetClientRect (hwnd, &windowRect); + D2D1_SIZE_U size = { (UINT32) (windowRect.right - windowRect.left), (UINT32) (windowRect.bottom - windowRect.top) }; + bounds.setSize (size.width, size.height); + + D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(); + D2D1_HWND_RENDER_TARGET_PROPERTIES propsHwnd = D2D1::HwndRenderTargetProperties (hwnd, size); + + if (pimpl->factories->d2dFactory != nullptr) + { + HRESULT hr = pimpl->factories->d2dFactory->CreateHwndRenderTarget (props, propsHwnd, pimpl->renderingTarget.resetAndGetPointerAddress()); + jassert (SUCCEEDED (hr)); ignoreUnused (hr); + hr = pimpl->renderingTarget->CreateSolidColorBrush (D2D1::ColorF::ColorF (0.0f, 0.0f, 0.0f, 1.0f), pimpl->colourBrush.resetAndGetPointerAddress()); + } +} + +Direct2DLowLevelGraphicsContext::~Direct2DLowLevelGraphicsContext() +{ + states.clear(); +} + +void Direct2DLowLevelGraphicsContext::resized() +{ + RECT windowRect; + GetClientRect (hwnd, &windowRect); + D2D1_SIZE_U size = { (UINT32) (windowRect.right - windowRect.left), (UINT32) (windowRect.bottom - windowRect.top) }; + + pimpl->renderingTarget->Resize (size); + bounds.setSize (size.width, size.height); +} + +void Direct2DLowLevelGraphicsContext::clear() +{ + pimpl->renderingTarget->Clear (D2D1::ColorF (D2D1::ColorF::White, 0.0f)); // xxx why white and not black? +} + +void Direct2DLowLevelGraphicsContext::start() +{ + pimpl->renderingTarget->BeginDraw(); + saveState(); +} + +void Direct2DLowLevelGraphicsContext::end() +{ + states.clear(); + currentState = 0; + pimpl->renderingTarget->EndDraw(); + pimpl->renderingTarget->CheckWindowState(); +} + +void Direct2DLowLevelGraphicsContext::setOrigin (Point o) +{ + addTransform (AffineTransform::translation ((float) o.x, (float) o.y)); +} + +void Direct2DLowLevelGraphicsContext::addTransform (const AffineTransform& transform) +{ + currentState->transform = transform.followedBy (currentState->transform); +} + +float Direct2DLowLevelGraphicsContext::getPhysicalPixelScaleFactor() +{ + return currentState->transform.getScaleFactor(); +} + +bool Direct2DLowLevelGraphicsContext::clipToRectangle (const Rectangle& r) +{ + currentState->clipToRectangle (r); + return ! isClipEmpty(); +} + +bool Direct2DLowLevelGraphicsContext::clipToRectangleList (const RectangleList& clipRegion) +{ + currentState->clipToRectList (pimpl->rectListToPathGeometry (clipRegion)); + return ! isClipEmpty(); +} + +void Direct2DLowLevelGraphicsContext::excludeClipRectangle (const Rectangle&) +{ + //xxx +} + +void Direct2DLowLevelGraphicsContext::clipToPath (const Path& path, const AffineTransform& transform) +{ + currentState->clipToPath (pimpl->pathToPathGeometry (path, transform)); +} + +void Direct2DLowLevelGraphicsContext::clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform) +{ + currentState->clipToImage (sourceImage, transform); +} + +bool Direct2DLowLevelGraphicsContext::clipRegionIntersects (const Rectangle& r) +{ + return currentState->clipRect.intersects (r.toFloat().transformed (currentState->transform).getSmallestIntegerContainer()); +} + +Rectangle Direct2DLowLevelGraphicsContext::getClipBounds() const +{ + // xxx could this take into account complex clip regions? + return currentState->clipRect.toFloat().transformed (currentState->transform.inverted()).getSmallestIntegerContainer(); +} + +bool Direct2DLowLevelGraphicsContext::isClipEmpty() const +{ + return currentState->clipRect.isEmpty(); +} + +void Direct2DLowLevelGraphicsContext::saveState() +{ + states.add (new SavedState (*this)); + currentState = states.getLast(); +} + +void Direct2DLowLevelGraphicsContext::restoreState() +{ + jassert (states.size() > 1); //you should never pop the last state! + states.removeLast (1); + currentState = states.getLast(); +} + +void Direct2DLowLevelGraphicsContext::beginTransparencyLayer (float /*opacity*/) +{ + jassertfalse; //xxx todo +} + +void Direct2DLowLevelGraphicsContext::endTransparencyLayer() +{ + jassertfalse; //xxx todo +} + +void Direct2DLowLevelGraphicsContext::setFill (const FillType& fillType) +{ + currentState->setFill (fillType); +} + +void Direct2DLowLevelGraphicsContext::setOpacity (float newOpacity) +{ + currentState->setOpacity (newOpacity); +} + +void Direct2DLowLevelGraphicsContext::setInterpolationQuality (Graphics::ResamplingQuality /*quality*/) +{ +} + +void Direct2DLowLevelGraphicsContext::fillRect (const Rectangle& r, bool /*replaceExistingContents*/) +{ + fillRect (r.toFloat()); +} + +void Direct2DLowLevelGraphicsContext::fillRect (const Rectangle& r) +{ + pimpl->renderingTarget->SetTransform (transformToMatrix (currentState->transform)); + currentState->createBrush(); + pimpl->renderingTarget->FillRectangle (rectangleToRectF (r), currentState->currentBrush); + pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix()); +} + +void Direct2DLowLevelGraphicsContext::fillRectList (const RectangleList& list) +{ + for (auto& r : list) + fillRect (r); +} + +void Direct2DLowLevelGraphicsContext::fillPath (const Path& p, const AffineTransform& transform) +{ + currentState->createBrush(); + ComSmartPtr geometry (pimpl->pathToPathGeometry (p, transform.followedBy (currentState->transform))); + + if (pimpl->renderingTarget != nullptr) + pimpl->renderingTarget->FillGeometry (geometry, currentState->currentBrush); +} + +void Direct2DLowLevelGraphicsContext::drawImage (const Image& image, const AffineTransform& transform) +{ + pimpl->renderingTarget->SetTransform (transformToMatrix (transform.followedBy (currentState->transform))); + + D2D1_SIZE_U size; + size.width = image.getWidth(); + size.height = image.getHeight(); + + D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties(); + + Image img (image.convertedToFormat (Image::ARGB)); + Image::BitmapData bd (img, Image::BitmapData::readOnly); + bp.pixelFormat = pimpl->renderingTarget->GetPixelFormat(); + bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; + + { + ComSmartPtr tempBitmap; + pimpl->renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, tempBitmap.resetAndGetPointerAddress()); + if (tempBitmap != nullptr) + pimpl->renderingTarget->DrawBitmap (tempBitmap); + } + + pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix()); +} + +void Direct2DLowLevelGraphicsContext::drawLine (const Line & line) +{ + // xxx doesn't seem to be correctly aligned, may need nudging by 0.5 to match the software renderer's behaviour + pimpl->renderingTarget->SetTransform (transformToMatrix (currentState->transform)); + currentState->createBrush(); + + pimpl->renderingTarget->DrawLine (D2D1::Point2F (line.getStartX(), line.getStartY()), + D2D1::Point2F (line.getEndX(), line.getEndY()), + currentState->currentBrush); + pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix()); +} + +void Direct2DLowLevelGraphicsContext::setFont (const Font& newFont) +{ + currentState->setFont (newFont); +} + +const Font& Direct2DLowLevelGraphicsContext::getFont() +{ + return currentState->font; +} + +void Direct2DLowLevelGraphicsContext::drawGlyph (int glyphNumber, const AffineTransform& transform) +{ + currentState->createBrush(); + currentState->createFont(); + + float hScale = currentState->font.getHorizontalScale(); + + pimpl->renderingTarget->SetTransform (transformToMatrix (AffineTransform::scale (hScale, 1.0f) + .followedBy (transform) + .followedBy (currentState->transform))); + + const UINT16 glyphIndices = (UINT16) glyphNumber; + const FLOAT glyphAdvances = 0; + DWRITE_GLYPH_OFFSET offset; + offset.advanceOffset = 0; + offset.ascenderOffset = 0; + + DWRITE_GLYPH_RUN glyphRun; + glyphRun.fontFace = currentState->currentFontFace; + glyphRun.fontEmSize = (FLOAT) (currentState->font.getHeight() * currentState->fontHeightToEmSizeFactor); + glyphRun.glyphCount = 1; + glyphRun.glyphIndices = &glyphIndices; + glyphRun.glyphAdvances = &glyphAdvances; + glyphRun.glyphOffsets = &offset; + glyphRun.isSideways = FALSE; + glyphRun.bidiLevel = 0; + + pimpl->renderingTarget->DrawGlyphRun (D2D1::Point2F (0, 0), &glyphRun, currentState->currentBrush); + pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix()); +} + +bool Direct2DLowLevelGraphicsContext::drawTextLayout (const AttributedString& text, const Rectangle& area) +{ + pimpl->renderingTarget->SetTransform (transformToMatrix (currentState->transform)); + + DirectWriteTypeLayout::drawToD2DContext (text, area, + *(pimpl->renderingTarget), + *(pimpl->factories->directWriteFactory), + *(pimpl->factories->systemFonts)); + + pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix()); + return true; +} diff --git a/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.h b/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.h new file mode 100644 index 0000000000..2c923a76ed --- /dev/null +++ b/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.h @@ -0,0 +1,103 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#pragma once + +#ifndef _WINDEF_ +class HWND__; // Forward or never +typedef HWND__* HWND; +#endif + +class Direct2DLowLevelGraphicsContext : public LowLevelGraphicsContext +{ +public: + Direct2DLowLevelGraphicsContext (HWND); + ~Direct2DLowLevelGraphicsContext(); + + //============================================================================== + bool isVectorDevice() const override { return false; } + + void setOrigin (Point) override; + void addTransform (const AffineTransform&) override; + float getPhysicalPixelScaleFactor() override; + bool clipToRectangle (const Rectangle&) override; + bool clipToRectangleList (const RectangleList&) override; + void excludeClipRectangle (const Rectangle&) override; + void clipToPath (const Path&, const AffineTransform&) override; + void clipToImageAlpha (const Image&, const AffineTransform&) override; + bool clipRegionIntersects (const Rectangle&) override; + Rectangle getClipBounds() const override; + bool isClipEmpty() const override; + + //============================================================================== + void saveState() override; + void restoreState() override; + void beginTransparencyLayer (float opacity) override; + void endTransparencyLayer() override; + + //============================================================================== + void setFill (const FillType&) override; + void setOpacity (float) override; + void setInterpolationQuality (Graphics::ResamplingQuality) override; + + //============================================================================== + void fillRect (const Rectangle&, bool replaceExistingContents) override; + void fillRect (const Rectangle&) override; + void fillRectList (const RectangleList&) override; + void fillPath (const Path&, const AffineTransform&) override; + void drawImage (const Image& sourceImage, const AffineTransform&) override; + + //============================================================================== + void drawLine (const Line&) override; + void setFont (const Font&) override; + const Font& getFont() override; + void drawGlyph (int glyphNumber, const AffineTransform&) override; + bool drawTextLayout (const AttributedString&, const Rectangle&) override; + + void resized(); + void clear(); + + void start(); + void end(); + + //============================================================================== +private: + struct SavedState; + + HWND hwnd; + + SavedState* currentState; + OwnedArray states; + + Rectangle bounds; + + struct Pimpl; + friend struct Pimpl; + friend struct ContainerDeletePolicy; + ScopedPointer pimpl; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DLowLevelGraphicsContext) +}; diff --git a/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp b/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp index 8a45d42d4c..fb30ddb42d 100644 --- a/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp +++ b/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp @@ -265,6 +265,8 @@ public: IDWriteFontFace* getIDWriteFontFace() const noexcept { return dwFontFace; } + float getUnitsToHeightScaleFactor() const noexcept { return unitsToHeightScaleFactor; } + private: SharedResourcePointer factories; ComSmartPtr dwFontFace; diff --git a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp index 8be1926f09..1dd3a04e77 100644 --- a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -1837,34 +1837,36 @@ private: } else #endif - - HRGN rgn = CreateRectRgn (0, 0, 0, 0); - const int regionType = GetUpdateRgn (hwnd, rgn, false); - - PAINTSTRUCT paintStruct; - HDC dc = BeginPaint (hwnd, &paintStruct); // Note this can immediately generate a WM_NCPAINT - // message and become re-entrant, but that's OK - - // if something in a paint handler calls, e.g. a message box, this can become reentrant and - // corrupt the image it's using to paint into, so do a check here. - static bool reentrant = false; - if (! reentrant) { - const ScopedValueSetter setter (reentrant, true, false); + HRGN rgn = CreateRectRgn (0, 0, 0, 0); + const int regionType = GetUpdateRgn (hwnd, rgn, false); + + PAINTSTRUCT paintStruct; + HDC dc = BeginPaint (hwnd, &paintStruct); // Note this can immediately generate a WM_NCPAINT + // message and become re-entrant, but that's OK + + // if something in a paint handler calls, e.g. a message box, this can become reentrant and + // corrupt the image it's using to paint into, so do a check here. + static bool reentrant = false; + if (! reentrant) + { + const ScopedValueSetter setter (reentrant, true, false); + + if (dontRepaint) + component.handleCommandMessage (0); // (this triggers a repaint in the openGL context) + else + performPaint (dc, rgn, regionType, paintStruct); + } + + DeleteObject (rgn); + EndPaint (hwnd, &paintStruct); + + #if JUCE_MSVC + _fpreset(); // because some graphics cards can unmask FP exceptions + #endif - if (dontRepaint) - component.handleCommandMessage (0); // (this triggers a repaint in the openGL context) - else - performPaint (dc, rgn, regionType, paintStruct); } - DeleteObject (rgn); - EndPaint (hwnd, &paintStruct); - - #if JUCE_MSVC - _fpreset(); // because some graphics cards can unmask FP exceptions - #endif - lastPaintTime = Time::getMillisecondCounter(); } @@ -1993,8 +1995,8 @@ private: void updateDirect2DContext() { if (currentRenderingEngine != direct2DRenderingEngine) - direct2DContext = 0; - else if (direct2DContext == 0) + direct2DContext = nullptr; + else if (direct2DContext == nullptr) direct2DContext = new Direct2DLowLevelGraphicsContext (hwnd); } #endif