1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
29 template <typename Type>
rectangleToRectF(const Rectangle<Type> & r)30 D2D1_RECT_F rectangleToRectF (const Rectangle<Type>& r)
31 {
32     return { (float) r.getX(), (float) r.getY(), (float) r.getRight(), (float) r.getBottom() };
33 }
34 
colourToD2D(Colour c)35 static D2D1_COLOR_F colourToD2D (Colour c)
36 {
37     return { c.getFloatRed(), c.getFloatGreen(), c.getFloatBlue(), c.getFloatAlpha() };
38 }
39 
pathToGeometrySink(const Path & path,ID2D1GeometrySink * sink,const AffineTransform & transform)40 static void pathToGeometrySink (const Path& path, ID2D1GeometrySink* sink, const AffineTransform& transform)
41 {
42     Path::Iterator it (path);
43 
44     while (it.next())
45     {
46         switch (it.elementType)
47         {
48         case Path::Iterator::cubicTo:
49         {
50             transform.transformPoint (it.x1, it.y1);
51             transform.transformPoint (it.x2, it.y2);
52             transform.transformPoint (it.x3, it.y3);
53 
54             sink->AddBezier ({ { it.x1, it.y1 }, { it.x2, it.y2 }, { it.x3, it.y3 } });
55             break;
56         }
57 
58         case Path::Iterator::lineTo:
59         {
60             transform.transformPoint (it.x1, it.y1);
61             sink->AddLine ({ it.x1, it.y1 });
62             break;
63         }
64 
65         case Path::Iterator::quadraticTo:
66         {
67             transform.transformPoint (it.x1, it.y1);
68             transform.transformPoint (it.x2, it.y2);
69             sink->AddQuadraticBezier ({ { it.x1, it.y1 }, { it.x2, it.y2 } });
70             break;
71         }
72 
73         case Path::Iterator::closePath:
74         {
75             sink->EndFigure (D2D1_FIGURE_END_CLOSED);
76             break;
77         }
78 
79         case Path::Iterator::startNewSubPath:
80         {
81             transform.transformPoint (it.x1, it.y1);
82             sink->BeginFigure ({ it.x1, it.y1 }, D2D1_FIGURE_BEGIN_FILLED);
83             break;
84         }
85         }
86     }
87 }
88 
transformToMatrix(const AffineTransform & transform)89 static D2D1::Matrix3x2F transformToMatrix (const AffineTransform& transform)
90 {
91     return { transform.mat00, transform.mat10, transform.mat01, transform.mat11, transform.mat02, transform.mat12 };
92 }
93 
pointTransformed(int x,int y,const AffineTransform & transform)94 static D2D1_POINT_2F pointTransformed (int x, int y, const AffineTransform& transform)
95 {
96     transform.transformPoint (x, y);
97     return { (FLOAT) x, (FLOAT) y };
98 }
99 
rectToGeometrySink(const Rectangle<int> & rect,ID2D1GeometrySink * sink,const AffineTransform & transform)100 static void rectToGeometrySink (const Rectangle<int>& rect, ID2D1GeometrySink* sink, const AffineTransform& transform)
101 {
102     sink->BeginFigure (pointTransformed (rect.getX(),     rect.getY(),       transform), D2D1_FIGURE_BEGIN_FILLED);
103     sink->AddLine     (pointTransformed (rect.getRight(), rect.getY(),       transform));
104     sink->AddLine     (pointTransformed (rect.getRight(), rect.getBottom(),  transform));
105     sink->AddLine     (pointTransformed (rect.getX(),     rect.getBottom(),  transform));
106     sink->EndFigure (D2D1_FIGURE_END_CLOSED);
107 }
108 
109 //==============================================================================
110 struct Direct2DLowLevelGraphicsContext::Pimpl
111 {
rectListToPathGeometryjuce::Direct2DLowLevelGraphicsContext::Pimpl112     ID2D1PathGeometry* rectListToPathGeometry (const RectangleList<int>& clipRegion)
113     {
114         ID2D1PathGeometry* p = nullptr;
115         factories->d2dFactory->CreatePathGeometry (&p);
116 
117         ComSmartPtr<ID2D1GeometrySink> sink;
118         auto hr = p->Open (sink.resetAndGetPointerAddress()); // xxx handle error
119         sink->SetFillMode (D2D1_FILL_MODE_WINDING);
120 
121         for (int i = clipRegion.getNumRectangles(); --i >= 0;)
122             rectToGeometrySink (clipRegion.getRectangle(i), sink, AffineTransform());
123 
124         hr = sink->Close();
125         return p;
126     }
127 
pathToPathGeometryjuce::Direct2DLowLevelGraphicsContext::Pimpl128     ID2D1PathGeometry* pathToPathGeometry (const Path& path, const AffineTransform& transform)
129     {
130         ID2D1PathGeometry* p = nullptr;
131         factories->d2dFactory->CreatePathGeometry (&p);
132 
133         ComSmartPtr<ID2D1GeometrySink> sink;
134         auto hr = p->Open (sink.resetAndGetPointerAddress());
135         sink->SetFillMode (D2D1_FILL_MODE_WINDING); // xxx need to check Path::isUsingNonZeroWinding()
136 
137         pathToGeometrySink (path, sink, transform);
138 
139         hr = sink->Close();
140         return p;
141     }
142 
143     SharedResourcePointer<Direct2DFactories> factories;
144 
145     ComSmartPtr<ID2D1HwndRenderTarget> renderingTarget;
146     ComSmartPtr<ID2D1SolidColorBrush> colourBrush;
147 };
148 
149 //==============================================================================
150 struct Direct2DLowLevelGraphicsContext::SavedState
151 {
152 public:
SavedStatejuce::Direct2DLowLevelGraphicsContext::SavedState153     SavedState (Direct2DLowLevelGraphicsContext& owner_)
154         : owner (owner_)
155     {
156         if (owner.currentState != nullptr)
157         {
158             // xxx seems like a very slow way to create one of these, and this is a performance
159             // bottleneck.. Can the same internal objects be shared by multiple state objects, maybe using copy-on-write?
160             setFill (owner.currentState->fillType);
161             currentBrush = owner.currentState->currentBrush;
162             clipRect = owner.currentState->clipRect;
163             transform = owner.currentState->transform;
164 
165             font = owner.currentState->font;
166             currentFontFace = owner.currentState->currentFontFace;
167         }
168         else
169         {
170             const auto size = owner.pimpl->renderingTarget->GetPixelSize();
171             clipRect.setSize (size.width, size.height);
172             setFill (FillType (Colours::black));
173         }
174     }
175 
~SavedStatejuce::Direct2DLowLevelGraphicsContext::SavedState176     ~SavedState()
177     {
178         clearClip();
179         clearFont();
180         clearFill();
181         clearPathClip();
182         clearImageClip();
183         complexClipLayer = nullptr;
184         bitmapMaskLayer = nullptr;
185     }
186 
clearClipjuce::Direct2DLowLevelGraphicsContext::SavedState187     void clearClip()
188     {
189         popClips();
190         shouldClipRect = false;
191     }
192 
clipToRectanglejuce::Direct2DLowLevelGraphicsContext::SavedState193     void clipToRectangle (const Rectangle<int>& r)
194     {
195         clearClip();
196         clipRect = r.toFloat().transformedBy (transform).getSmallestIntegerContainer();
197         shouldClipRect = true;
198         pushClips();
199     }
200 
clearPathClipjuce::Direct2DLowLevelGraphicsContext::SavedState201     void clearPathClip()
202     {
203         popClips();
204 
205         if (shouldClipComplex)
206         {
207             complexClipGeometry = nullptr;
208             shouldClipComplex = false;
209         }
210     }
211 
clipToPath(ID2D1Geometry * geometry)212     void Direct2DLowLevelGraphicsContext::SavedState::clipToPath (ID2D1Geometry* geometry)
213     {
214         clearPathClip();
215 
216         if (complexClipLayer == nullptr)
217             owner.pimpl->renderingTarget->CreateLayer (complexClipLayer.resetAndGetPointerAddress());
218 
219         complexClipGeometry = geometry;
220         shouldClipComplex = true;
221         pushClips();
222     }
223 
clearRectListClipjuce::Direct2DLowLevelGraphicsContext::SavedState224     void clearRectListClip()
225     {
226         popClips();
227 
228         if (shouldClipRectList)
229         {
230             rectListGeometry = nullptr;
231             shouldClipRectList = false;
232         }
233     }
234 
clipToRectListjuce::Direct2DLowLevelGraphicsContext::SavedState235     void clipToRectList (ID2D1Geometry* geometry)
236     {
237         clearRectListClip();
238 
239         if (rectListLayer == nullptr)
240             owner.pimpl->renderingTarget->CreateLayer (rectListLayer.resetAndGetPointerAddress());
241 
242         rectListGeometry = geometry;
243         shouldClipRectList = true;
244         pushClips();
245     }
246 
clearImageClipjuce::Direct2DLowLevelGraphicsContext::SavedState247     void clearImageClip()
248     {
249         popClips();
250 
251         if (shouldClipBitmap)
252         {
253             maskBitmap = nullptr;
254             bitmapMaskBrush = nullptr;
255             shouldClipBitmap = false;
256         }
257     }
258 
clipToImagejuce::Direct2DLowLevelGraphicsContext::SavedState259     void clipToImage (const Image& clipImage, const AffineTransform& clipTransform)
260     {
261         clearImageClip();
262 
263         if (bitmapMaskLayer == nullptr)
264             owner.pimpl->renderingTarget->CreateLayer (bitmapMaskLayer.resetAndGetPointerAddress());
265 
266         D2D1_BRUSH_PROPERTIES brushProps = { 1, transformToMatrix (clipTransform) };
267         auto bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP);
268         D2D1_SIZE_U size = { (UINT32) clipImage.getWidth(), (UINT32) clipImage.getHeight() };
269         auto bp = D2D1::BitmapProperties();
270 
271         maskImage = clipImage.convertedToFormat (Image::ARGB);
272         Image::BitmapData bd (maskImage, Image::BitmapData::readOnly); // xxx should be maskImage?
273         bp.pixelFormat = owner.pimpl->renderingTarget->GetPixelFormat();
274         bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
275 
276         auto hr = owner.pimpl->renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, maskBitmap.resetAndGetPointerAddress());
277         hr = owner.pimpl->renderingTarget->CreateBitmapBrush (maskBitmap, bmProps, brushProps, bitmapMaskBrush.resetAndGetPointerAddress());
278 
279         imageMaskLayerParams = D2D1::LayerParameters();
280         imageMaskLayerParams.opacityBrush = bitmapMaskBrush;
281 
282         shouldClipBitmap = true;
283         pushClips();
284     }
285 
popClipsjuce::Direct2DLowLevelGraphicsContext::SavedState286     void popClips()
287     {
288         if (clipsBitmap)
289         {
290             owner.pimpl->renderingTarget->PopLayer();
291             clipsBitmap = false;
292         }
293 
294         if (clipsComplex)
295         {
296             owner.pimpl->renderingTarget->PopLayer();
297             clipsComplex = false;
298         }
299 
300         if (clipsRectList)
301         {
302             owner.pimpl->renderingTarget->PopLayer();
303             clipsRectList = false;
304         }
305 
306         if (clipsRect)
307         {
308             owner.pimpl->renderingTarget->PopAxisAlignedClip();
309             clipsRect = false;
310         }
311     }
312 
pushClipsjuce::Direct2DLowLevelGraphicsContext::SavedState313     void pushClips()
314     {
315         if (shouldClipRect && !clipsRect)
316         {
317             owner.pimpl->renderingTarget->PushAxisAlignedClip (rectangleToRectF (clipRect), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
318             clipsRect = true;
319         }
320 
321         if (shouldClipRectList && !clipsRectList)
322         {
323             auto layerParams = D2D1::LayerParameters();
324             rectListGeometry->GetBounds (D2D1::IdentityMatrix(), &layerParams.contentBounds);
325             layerParams.geometricMask = rectListGeometry;
326             owner.pimpl->renderingTarget->PushLayer (layerParams, rectListLayer);
327             clipsRectList = true;
328         }
329 
330         if (shouldClipComplex && !clipsComplex)
331         {
332             auto layerParams = D2D1::LayerParameters();
333             complexClipGeometry->GetBounds (D2D1::IdentityMatrix(), &layerParams.contentBounds);
334             layerParams.geometricMask = complexClipGeometry;
335             owner.pimpl->renderingTarget->PushLayer (layerParams, complexClipLayer);
336             clipsComplex = true;
337         }
338 
339         if (shouldClipBitmap && !clipsBitmap)
340         {
341             owner.pimpl->renderingTarget->PushLayer (imageMaskLayerParams, bitmapMaskLayer);
342             clipsBitmap = true;
343         }
344     }
345 
setFilljuce::Direct2DLowLevelGraphicsContext::SavedState346     void setFill (const FillType& newFillType)
347     {
348         if (fillType != newFillType)
349         {
350             fillType = newFillType;
351             clearFill();
352         }
353     }
354 
clearFontjuce::Direct2DLowLevelGraphicsContext::SavedState355     void clearFont()
356     {
357         currentFontFace = localFontFace = nullptr;
358     }
359 
setFontjuce::Direct2DLowLevelGraphicsContext::SavedState360     void setFont (const Font& newFont)
361     {
362         if (font != newFont)
363         {
364             font = newFont;
365             clearFont();
366         }
367     }
368 
createFontjuce::Direct2DLowLevelGraphicsContext::SavedState369     void createFont()
370     {
371         if (currentFontFace == nullptr)
372         {
373             auto* typeface = dynamic_cast<WindowsDirectWriteTypeface*> (font.getTypeface());
374             currentFontFace = typeface->getIDWriteFontFace();
375             fontHeightToEmSizeFactor = typeface->getUnitsToHeightScaleFactor();
376         }
377     }
378 
setOpacityjuce::Direct2DLowLevelGraphicsContext::SavedState379     void setOpacity (float newOpacity)
380     {
381         fillType.setOpacity (newOpacity);
382 
383         if (currentBrush != nullptr)
384             currentBrush->SetOpacity (newOpacity);
385     }
386 
clearFilljuce::Direct2DLowLevelGraphicsContext::SavedState387     void clearFill()
388     {
389         gradientStops = nullptr;
390         linearGradient = nullptr;
391         radialGradient = nullptr;
392         bitmap = nullptr;
393         bitmapBrush = nullptr;
394         currentBrush = nullptr;
395     }
396 
createBrushjuce::Direct2DLowLevelGraphicsContext::SavedState397     void createBrush()
398     {
399         if (currentBrush == nullptr)
400         {
401             if (fillType.isColour())
402             {
403                 auto colour = colourToD2D (fillType.colour);
404                 owner.pimpl->colourBrush->SetColor (colour);
405                 currentBrush = owner.pimpl->colourBrush;
406             }
407             else if (fillType.isTiledImage())
408             {
409                 D2D1_BRUSH_PROPERTIES brushProps = { fillType.getOpacity(), transformToMatrix (fillType.transform) };
410                 auto bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP);
411 
412                 image = fillType.image;
413 
414                 D2D1_SIZE_U size = { (UINT32) image.getWidth(), (UINT32) image.getHeight() };
415                 auto bp = D2D1::BitmapProperties();
416 
417                 this->image = image.convertedToFormat (Image::ARGB);
418                 Image::BitmapData bd (this->image, Image::BitmapData::readOnly);
419                 bp.pixelFormat = owner.pimpl->renderingTarget->GetPixelFormat();
420                 bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
421 
422                 auto hr = owner.pimpl->renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, bitmap.resetAndGetPointerAddress());
423                 hr = owner.pimpl->renderingTarget->CreateBitmapBrush (bitmap, bmProps, brushProps, bitmapBrush.resetAndGetPointerAddress());
424 
425                 currentBrush = bitmapBrush;
426             }
427             else if (fillType.isGradient())
428             {
429                 gradientStops = nullptr;
430 
431                 D2D1_BRUSH_PROPERTIES brushProps = { fillType.getOpacity(), transformToMatrix (fillType.transform.followedBy (transform)) };
432 
433                 const int numColors = fillType.gradient->getNumColours();
434 
435                 HeapBlock<D2D1_GRADIENT_STOP> stops (numColors);
436 
437                 for (int i = fillType.gradient->getNumColours(); --i >= 0;)
438                 {
439                     stops[i].color = colourToD2D (fillType.gradient->getColour (i));
440                     stops[i].position = (FLOAT) fillType.gradient->getColourPosition (i);
441                 }
442 
443                 owner.pimpl->renderingTarget->CreateGradientStopCollection (stops.getData(), numColors, gradientStops.resetAndGetPointerAddress());
444 
445                 if (fillType.gradient->isRadial)
446                 {
447                     radialGradient = nullptr;
448 
449                     const auto p1 = fillType.gradient->point1;
450                     const auto p2 = fillType.gradient->point2;
451                     const auto r = p1.getDistanceFrom(p2);
452                     const auto props = D2D1::RadialGradientBrushProperties ({ p1.x, p1.y }, {}, r, r);
453 
454                     owner.pimpl->renderingTarget->CreateRadialGradientBrush (props, brushProps, gradientStops, radialGradient.resetAndGetPointerAddress());
455                     currentBrush = radialGradient;
456                 }
457                 else
458                 {
459                     linearGradient = 0;
460 
461                     const auto p1 = fillType.gradient->point1;
462                     const auto p2 = fillType.gradient->point2;
463                     const auto props = D2D1::LinearGradientBrushProperties ({ p1.x, p1.y }, { p2.x, p2.y });
464 
465                     owner.pimpl->renderingTarget->CreateLinearGradientBrush (props, brushProps, gradientStops, linearGradient.resetAndGetPointerAddress());
466 
467                     currentBrush = linearGradient;
468                 }
469             }
470         }
471     }
472 
473     Direct2DLowLevelGraphicsContext& owner;
474 
475     AffineTransform transform;
476 
477     Font font;
478     float fontHeightToEmSizeFactor = 1.0f;
479 
480     IDWriteFontFace* currentFontFace = nullptr;
481     ComSmartPtr<IDWriteFontFace> localFontFace;
482 
483     Rectangle<int> clipRect;
484     bool clipsRect = false, shouldClipRect = false;
485 
486     Image image;
487     ComSmartPtr<ID2D1Bitmap> bitmap; // xxx needs a better name - what is this for??
488     bool clipsBitmap = false, shouldClipBitmap = false;
489 
490     ComSmartPtr<ID2D1Geometry> complexClipGeometry;
491     D2D1_LAYER_PARAMETERS complexClipLayerParams;
492     ComSmartPtr<ID2D1Layer> complexClipLayer;
493     bool clipsComplex = false, shouldClipComplex = false;
494 
495     ComSmartPtr<ID2D1Geometry> rectListGeometry;
496     D2D1_LAYER_PARAMETERS rectListLayerParams;
497     ComSmartPtr<ID2D1Layer> rectListLayer;
498     bool clipsRectList = false, shouldClipRectList = false;
499 
500     Image maskImage;
501     D2D1_LAYER_PARAMETERS imageMaskLayerParams;
502     ComSmartPtr<ID2D1Layer> bitmapMaskLayer;
503     ComSmartPtr<ID2D1Bitmap> maskBitmap;
504     ComSmartPtr<ID2D1BitmapBrush> bitmapMaskBrush;
505 
506     ID2D1Brush* currentBrush = nullptr;
507     ComSmartPtr<ID2D1BitmapBrush> bitmapBrush;
508     ComSmartPtr<ID2D1LinearGradientBrush> linearGradient;
509     ComSmartPtr<ID2D1RadialGradientBrush> radialGradient;
510     ComSmartPtr<ID2D1GradientStopCollection> gradientStops;
511 
512     FillType fillType;
513 
514     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SavedState)
515 };
516 
517 //==============================================================================
Direct2DLowLevelGraphicsContext(HWND hwnd_)518 Direct2DLowLevelGraphicsContext::Direct2DLowLevelGraphicsContext (HWND hwnd_)
519     : hwnd (hwnd_),
520       currentState (nullptr),
521       pimpl (new Pimpl())
522 {
523     RECT windowRect;
524     GetClientRect (hwnd, &windowRect);
525     D2D1_SIZE_U size = { (UINT32) (windowRect.right - windowRect.left), (UINT32) (windowRect.bottom - windowRect.top) };
526     bounds.setSize (size.width, size.height);
527 
528     if (pimpl->factories->d2dFactory != nullptr)
529     {
530         auto hr = pimpl->factories->d2dFactory->CreateHwndRenderTarget ({}, { hwnd, size }, pimpl->renderingTarget.resetAndGetPointerAddress());
531         jassert (SUCCEEDED (hr)); ignoreUnused (hr);
532         hr = pimpl->renderingTarget->CreateSolidColorBrush (D2D1::ColorF::ColorF (0.0f, 0.0f, 0.0f, 1.0f), pimpl->colourBrush.resetAndGetPointerAddress());
533     }
534 }
535 
~Direct2DLowLevelGraphicsContext()536 Direct2DLowLevelGraphicsContext::~Direct2DLowLevelGraphicsContext()
537 {
538     states.clear();
539 }
540 
resized()541 void Direct2DLowLevelGraphicsContext::resized()
542 {
543     RECT windowRect;
544     GetClientRect (hwnd, &windowRect);
545     D2D1_SIZE_U size = { (UINT32) (windowRect.right - windowRect.left), (UINT32) (windowRect.bottom - windowRect.top) };
546 
547     pimpl->renderingTarget->Resize (size);
548     bounds.setSize (size.width, size.height);
549 }
550 
clear()551 void Direct2DLowLevelGraphicsContext::clear()
552 {
553     pimpl->renderingTarget->Clear (D2D1::ColorF (D2D1::ColorF::White, 0.0f)); // xxx why white and not black?
554 }
555 
start()556 void Direct2DLowLevelGraphicsContext::start()
557 {
558     pimpl->renderingTarget->BeginDraw();
559     saveState();
560 }
561 
end()562 void Direct2DLowLevelGraphicsContext::end()
563 {
564     states.clear();
565     currentState = nullptr;
566     pimpl->renderingTarget->EndDraw();
567     pimpl->renderingTarget->CheckWindowState();
568 }
569 
setOrigin(Point<int> o)570 void Direct2DLowLevelGraphicsContext::setOrigin (Point<int> o)
571 {
572     addTransform (AffineTransform::translation ((float) o.x, (float) o.y));
573 }
574 
addTransform(const AffineTransform & transform)575 void Direct2DLowLevelGraphicsContext::addTransform (const AffineTransform& transform)
576 {
577     currentState->transform = transform.followedBy (currentState->transform);
578 }
579 
getPhysicalPixelScaleFactor()580 float Direct2DLowLevelGraphicsContext::getPhysicalPixelScaleFactor()
581 {
582     return std::sqrt (std::abs (currentState->transform.getDeterminant()));
583 }
584 
clipToRectangle(const Rectangle<int> & r)585 bool Direct2DLowLevelGraphicsContext::clipToRectangle (const Rectangle<int>& r)
586 {
587     currentState->clipToRectangle (r);
588     return ! isClipEmpty();
589 }
590 
clipToRectangleList(const RectangleList<int> & clipRegion)591 bool Direct2DLowLevelGraphicsContext::clipToRectangleList (const RectangleList<int>& clipRegion)
592 {
593     currentState->clipToRectList (pimpl->rectListToPathGeometry (clipRegion));
594     return ! isClipEmpty();
595 }
596 
excludeClipRectangle(const Rectangle<int> &)597 void Direct2DLowLevelGraphicsContext::excludeClipRectangle (const Rectangle<int>&)
598 {
599     //xxx
600 }
601 
clipToPath(const Path & path,const AffineTransform & transform)602 void Direct2DLowLevelGraphicsContext::clipToPath (const Path& path, const AffineTransform& transform)
603 {
604     currentState->clipToPath (pimpl->pathToPathGeometry (path, transform));
605 }
606 
clipToImageAlpha(const Image & sourceImage,const AffineTransform & transform)607 void Direct2DLowLevelGraphicsContext::clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform)
608 {
609     currentState->clipToImage (sourceImage, transform);
610 }
611 
clipRegionIntersects(const Rectangle<int> & r)612 bool Direct2DLowLevelGraphicsContext::clipRegionIntersects (const Rectangle<int>& r)
613 {
614     return currentState->clipRect.intersects (r.toFloat().transformedBy (currentState->transform).getSmallestIntegerContainer());
615 }
616 
getClipBounds() const617 Rectangle<int> Direct2DLowLevelGraphicsContext::getClipBounds() const
618 {
619     // xxx could this take into account complex clip regions?
620     return currentState->clipRect.toFloat().transformedBy (currentState->transform.inverted()).getSmallestIntegerContainer();
621 }
622 
isClipEmpty() const623 bool Direct2DLowLevelGraphicsContext::isClipEmpty() const
624 {
625     return currentState->clipRect.isEmpty();
626 }
627 
saveState()628 void Direct2DLowLevelGraphicsContext::saveState()
629 {
630     states.add (new SavedState (*this));
631     currentState = states.getLast();
632 }
633 
restoreState()634 void Direct2DLowLevelGraphicsContext::restoreState()
635 {
636     jassert (states.size() > 1); //you should never pop the last state!
637     states.removeLast (1);
638     currentState = states.getLast();
639 }
640 
beginTransparencyLayer(float)641 void Direct2DLowLevelGraphicsContext::beginTransparencyLayer (float /*opacity*/)
642 {
643     jassertfalse; //xxx todo
644 }
645 
endTransparencyLayer()646 void Direct2DLowLevelGraphicsContext::endTransparencyLayer()
647 {
648     jassertfalse; //xxx todo
649 }
650 
setFill(const FillType & fillType)651 void Direct2DLowLevelGraphicsContext::setFill (const FillType& fillType)
652 {
653     currentState->setFill (fillType);
654 }
655 
setOpacity(float newOpacity)656 void Direct2DLowLevelGraphicsContext::setOpacity (float newOpacity)
657 {
658     currentState->setOpacity (newOpacity);
659 }
660 
setInterpolationQuality(Graphics::ResamplingQuality)661 void Direct2DLowLevelGraphicsContext::setInterpolationQuality (Graphics::ResamplingQuality /*quality*/)
662 {
663 }
664 
fillRect(const Rectangle<int> & r,bool)665 void Direct2DLowLevelGraphicsContext::fillRect (const Rectangle<int>& r, bool /*replaceExistingContents*/)
666 {
667     fillRect (r.toFloat());
668 }
669 
fillRect(const Rectangle<float> & r)670 void Direct2DLowLevelGraphicsContext::fillRect (const Rectangle<float>& r)
671 {
672     pimpl->renderingTarget->SetTransform (transformToMatrix (currentState->transform));
673     currentState->createBrush();
674     pimpl->renderingTarget->FillRectangle (rectangleToRectF (r), currentState->currentBrush);
675     pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix());
676 }
677 
fillRectList(const RectangleList<float> & list)678 void Direct2DLowLevelGraphicsContext::fillRectList (const RectangleList<float>& list)
679 {
680     for (auto& r : list)
681         fillRect (r);
682 }
683 
fillPath(const Path & p,const AffineTransform & transform)684 void Direct2DLowLevelGraphicsContext::fillPath (const Path& p, const AffineTransform& transform)
685 {
686     currentState->createBrush();
687     ComSmartPtr<ID2D1Geometry> geometry (pimpl->pathToPathGeometry (p, transform.followedBy (currentState->transform)));
688 
689     if (pimpl->renderingTarget != nullptr)
690         pimpl->renderingTarget->FillGeometry (geometry, currentState->currentBrush);
691 }
692 
drawImage(const Image & image,const AffineTransform & transform)693 void Direct2DLowLevelGraphicsContext::drawImage (const Image& image, const AffineTransform& transform)
694 {
695     pimpl->renderingTarget->SetTransform (transformToMatrix (transform.followedBy (currentState->transform)));
696 
697     D2D1_SIZE_U size = { (UINT32) image.getWidth(), (UINT32) image.getHeight() };
698     auto bp = D2D1::BitmapProperties();
699 
700     Image img (image.convertedToFormat (Image::ARGB));
701     Image::BitmapData bd (img, Image::BitmapData::readOnly);
702     bp.pixelFormat = pimpl->renderingTarget->GetPixelFormat();
703     bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
704 
705     {
706         ComSmartPtr<ID2D1Bitmap> tempBitmap;
707         pimpl->renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, tempBitmap.resetAndGetPointerAddress());
708         if (tempBitmap != nullptr)
709             pimpl->renderingTarget->DrawBitmap (tempBitmap);
710     }
711 
712     pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix());
713 }
714 
drawLine(const Line<float> & line)715 void Direct2DLowLevelGraphicsContext::drawLine (const Line<float>& line)
716 {
717     // xxx doesn't seem to be correctly aligned, may need nudging by 0.5 to match the software renderer's behaviour
718     pimpl->renderingTarget->SetTransform (transformToMatrix (currentState->transform));
719     currentState->createBrush();
720 
721     pimpl->renderingTarget->DrawLine (D2D1::Point2F (line.getStartX(), line.getStartY()),
722                                       D2D1::Point2F (line.getEndX(), line.getEndY()),
723                                       currentState->currentBrush);
724     pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix());
725 }
726 
setFont(const Font & newFont)727 void Direct2DLowLevelGraphicsContext::setFont (const Font& newFont)
728 {
729     currentState->setFont (newFont);
730 }
731 
getFont()732 const Font& Direct2DLowLevelGraphicsContext::getFont()
733 {
734     return currentState->font;
735 }
736 
drawGlyph(int glyphNumber,const AffineTransform & transform)737 void Direct2DLowLevelGraphicsContext::drawGlyph (int glyphNumber, const AffineTransform& transform)
738 {
739     currentState->createBrush();
740     currentState->createFont();
741 
742     auto hScale = currentState->font.getHorizontalScale();
743 
744     pimpl->renderingTarget->SetTransform (transformToMatrix (AffineTransform::scale (hScale, 1.0f)
745                                                                              .followedBy (transform)
746                                                                              .followedBy (currentState->transform)));
747 
748     const auto glyphIndices = (UINT16) glyphNumber;
749     const auto glyphAdvances = 0.0f;
750     DWRITE_GLYPH_OFFSET offset = { 0.0f, 0.0f };
751 
752     DWRITE_GLYPH_RUN glyphRun;
753     glyphRun.fontFace = currentState->currentFontFace;
754     glyphRun.fontEmSize = (FLOAT) (currentState->font.getHeight() * currentState->fontHeightToEmSizeFactor);
755     glyphRun.glyphCount = 1;
756     glyphRun.glyphIndices = &glyphIndices;
757     glyphRun.glyphAdvances = &glyphAdvances;
758     glyphRun.glyphOffsets = &offset;
759     glyphRun.isSideways = FALSE;
760     glyphRun.bidiLevel = 0;
761 
762     pimpl->renderingTarget->DrawGlyphRun ({}, &glyphRun, currentState->currentBrush);
763     pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix());
764 }
765 
drawTextLayout(const AttributedString & text,const Rectangle<float> & area)766 bool Direct2DLowLevelGraphicsContext::drawTextLayout (const AttributedString& text, const Rectangle<float>& area)
767 {
768     pimpl->renderingTarget->SetTransform (transformToMatrix (currentState->transform));
769 
770     DirectWriteTypeLayout::drawToD2DContext (text, area,
771                                              *(pimpl->renderingTarget),
772                                              *(pimpl->factories->directWriteFactory),
773                                              *(pimpl->factories->systemFonts));
774 
775     pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix());
776     return true;
777 }
778 
779 } // namespace juce
780