2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../../../core/juce_StandardHeader.h"
30 #include "juce_Graphics.h"
31 #include "../fonts/juce_GlyphArrangement.h"
32 #include "../geometry/juce_PathStrokeType.h"
33 #include "juce_LowLevelGraphicsContext.h"
36 //==============================================================================
39 template <typename Type
>
40 bool areCoordsSensibleNumbers (Type x
, Type y
, Type w
, Type h
)
42 const int maxVal
= 0x3fffffff;
44 return (int) x
>= -maxVal
&& (int) x
<= maxVal
45 && (int) y
>= -maxVal
&& (int) y
<= maxVal
46 && (int) w
>= -maxVal
&& (int) w
<= maxVal
47 && (int) h
>= -maxVal
&& (int) h
<= maxVal
;
51 //==============================================================================
52 LowLevelGraphicsContext::LowLevelGraphicsContext()
56 LowLevelGraphicsContext::~LowLevelGraphicsContext()
60 //==============================================================================
61 Graphics::Graphics (const Image
& imageToDrawOnto
)
62 : context (imageToDrawOnto
.createLowLevelContext()),
63 contextToDelete (context
),
64 saveStatePending (false)
68 Graphics::Graphics (LowLevelGraphicsContext
* const internalContext
) noexcept
69 : context (internalContext
),
70 saveStatePending (false)
78 //==============================================================================
79 void Graphics::resetToDefaultState()
82 context
->setFill (FillType());
83 context
->setFont (Font());
84 context
->setInterpolationQuality (Graphics::mediumResamplingQuality
);
87 bool Graphics::isVectorDevice() const
89 return context
->isVectorDevice();
92 bool Graphics::reduceClipRegion (const Rectangle
<int>& area
)
95 return context
->clipToRectangle (area
);
98 bool Graphics::reduceClipRegion (const int x
, const int y
, const int w
, const int h
)
100 return reduceClipRegion (Rectangle
<int> (x
, y
, w
, h
));
103 bool Graphics::reduceClipRegion (const RectangleList
& clipRegion
)
105 saveStateIfPending();
106 return context
->clipToRectangleList (clipRegion
);
109 bool Graphics::reduceClipRegion (const Path
& path
, const AffineTransform
& transform
)
111 saveStateIfPending();
112 context
->clipToPath (path
, transform
);
113 return ! context
->isClipEmpty();
116 bool Graphics::reduceClipRegion (const Image
& image
, const AffineTransform
& transform
)
118 saveStateIfPending();
119 context
->clipToImageAlpha (image
, transform
);
120 return ! context
->isClipEmpty();
123 void Graphics::excludeClipRegion (const Rectangle
<int>& rectangleToExclude
)
125 saveStateIfPending();
126 context
->excludeClipRectangle (rectangleToExclude
);
129 bool Graphics::isClipEmpty() const
131 return context
->isClipEmpty();
134 const Rectangle
<int> Graphics::getClipBounds() const
136 return context
->getClipBounds();
139 void Graphics::saveState()
141 saveStateIfPending();
142 saveStatePending
= true;
145 void Graphics::restoreState()
147 if (saveStatePending
)
148 saveStatePending
= false;
150 context
->restoreState();
153 void Graphics::saveStateIfPending()
155 if (saveStatePending
)
157 saveStatePending
= false;
158 context
->saveState();
162 void Graphics::setOrigin (const int newOriginX
, const int newOriginY
)
164 saveStateIfPending();
165 context
->setOrigin (newOriginX
, newOriginY
);
168 void Graphics::addTransform (const AffineTransform
& transform
)
170 saveStateIfPending();
171 context
->addTransform (transform
);
174 bool Graphics::clipRegionIntersects (const Rectangle
<int>& area
) const
176 return context
->clipRegionIntersects (area
);
179 void Graphics::beginTransparencyLayer (float layerOpacity
)
181 saveStateIfPending();
182 context
->beginTransparencyLayer (layerOpacity
);
185 void Graphics::endTransparencyLayer()
187 context
->endTransparencyLayer();
190 //==============================================================================
191 void Graphics::setColour (const Colour
& newColour
)
193 saveStateIfPending();
194 context
->setFill (newColour
);
197 void Graphics::setOpacity (const float newOpacity
)
199 saveStateIfPending();
200 context
->setOpacity (newOpacity
);
203 void Graphics::setGradientFill (const ColourGradient
& gradient
)
205 setFillType (gradient
);
208 void Graphics::setTiledImageFill (const Image
& imageToUse
, const int anchorX
, const int anchorY
, const float opacity
)
210 saveStateIfPending();
211 context
->setFill (FillType (imageToUse
, AffineTransform::translation ((float) anchorX
, (float) anchorY
)));
212 context
->setOpacity (opacity
);
215 void Graphics::setFillType (const FillType
& newFill
)
217 saveStateIfPending();
218 context
->setFill (newFill
);
221 //==============================================================================
222 void Graphics::setFont (const Font
& newFont
)
224 saveStateIfPending();
225 context
->setFont (newFont
);
228 void Graphics::setFont (const float newFontHeight
, const int newFontStyleFlags
)
230 saveStateIfPending();
231 Font
f (context
->getFont());
232 f
.setSizeAndStyle (newFontHeight
, newFontStyleFlags
, 1.0f
, 0);
233 context
->setFont (f
);
236 Font
Graphics::getCurrentFont() const
238 return context
->getFont();
241 //==============================================================================
242 void Graphics::drawSingleLineText (const String
& text
, const int startX
, const int baselineY
) const
244 if (text
.isNotEmpty()
245 && startX
< context
->getClipBounds().getRight())
247 GlyphArrangement arr
;
248 arr
.addLineOfText (context
->getFont(), text
, (float) startX
, (float) baselineY
);
253 void Graphics::drawTextAsPath (const String
& text
, const AffineTransform
& transform
) const
255 if (text
.isNotEmpty())
257 GlyphArrangement arr
;
258 arr
.addLineOfText (context
->getFont(), text
, 0.0f
, 0.0f
);
259 arr
.draw (*this, transform
);
263 void Graphics::drawMultiLineText (const String
& text
, const int startX
, const int baselineY
, const int maximumLineWidth
) const
265 if (text
.isNotEmpty()
266 && startX
< context
->getClipBounds().getRight())
268 GlyphArrangement arr
;
269 arr
.addJustifiedText (context
->getFont(), text
,
270 (float) startX
, (float) baselineY
, (float) maximumLineWidth
,
271 Justification::left
);
276 void Graphics::drawText (const String
& text
,
277 const int x
, const int y
, const int width
, const int height
,
278 const Justification
& justificationType
,
279 const bool useEllipsesIfTooBig
) const
281 if (text
.isNotEmpty() && context
->clipRegionIntersects (Rectangle
<int> (x
, y
, width
, height
)))
283 GlyphArrangement arr
;
285 arr
.addCurtailedLineOfText (context
->getFont(), text
,
286 0.0f
, 0.0f
, (float) width
,
287 useEllipsesIfTooBig
);
289 arr
.justifyGlyphs (0, arr
.getNumGlyphs(),
290 (float) x
, (float) y
, (float) width
, (float) height
,
296 void Graphics::drawFittedText (const String
& text
,
297 const int x
, const int y
, const int width
, const int height
,
298 const Justification
& justification
,
299 const int maximumNumberOfLines
,
300 const float minimumHorizontalScale
) const
302 if (text
.isNotEmpty()
303 && width
> 0 && height
> 0
304 && context
->clipRegionIntersects (Rectangle
<int> (x
, y
, width
, height
)))
306 GlyphArrangement arr
;
308 arr
.addFittedText (context
->getFont(), text
,
309 (float) x
, (float) y
, (float) width
, (float) height
,
311 maximumNumberOfLines
,
312 minimumHorizontalScale
);
318 //==============================================================================
319 void Graphics::fillRect (int x
, int y
, int width
, int height
) const
321 // passing in a silly number can cause maths problems in rendering!
322 jassert (areCoordsSensibleNumbers (x
, y
, width
, height
));
324 context
->fillRect (Rectangle
<int> (x
, y
, width
, height
), false);
327 void Graphics::fillRect (const Rectangle
<int>& r
) const
329 context
->fillRect (r
, false);
332 void Graphics::fillRect (const float x
, const float y
, const float width
, const float height
) const
334 // passing in a silly number can cause maths problems in rendering!
335 jassert (areCoordsSensibleNumbers (x
, y
, width
, height
));
338 p
.addRectangle (x
, y
, width
, height
);
342 void Graphics::setPixel (int x
, int y
) const
344 context
->fillRect (Rectangle
<int> (x
, y
, 1, 1), false);
347 void Graphics::fillAll() const
349 fillRect (context
->getClipBounds());
352 void Graphics::fillAll (const Colour
& colourToUse
) const
354 if (! colourToUse
.isTransparent())
356 const Rectangle
<int> clip (context
->getClipBounds());
358 context
->saveState();
359 context
->setFill (colourToUse
);
360 context
->fillRect (clip
, false);
361 context
->restoreState();
366 //==============================================================================
367 void Graphics::fillPath (const Path
& path
, const AffineTransform
& transform
) const
369 if ((! context
->isClipEmpty()) && ! path
.isEmpty())
370 context
->fillPath (path
, transform
);
373 void Graphics::strokePath (const Path
& path
,
374 const PathStrokeType
& strokeType
,
375 const AffineTransform
& transform
) const
378 strokeType
.createStrokedPath (stroke
, path
, transform
, context
->getScaleFactor());
382 //==============================================================================
383 void Graphics::drawRect (const int x
, const int y
, const int width
, const int height
,
384 const int lineThickness
) const
386 // passing in a silly number can cause maths problems in rendering!
387 jassert (areCoordsSensibleNumbers (x
, y
, width
, height
));
389 context
->fillRect (Rectangle
<int> (x
, y
, width
, lineThickness
), false);
390 context
->fillRect (Rectangle
<int> (x
, y
+ lineThickness
, lineThickness
, height
- lineThickness
* 2), false);
391 context
->fillRect (Rectangle
<int> (x
+ width
- lineThickness
, y
+ lineThickness
, lineThickness
, height
- lineThickness
* 2), false);
392 context
->fillRect (Rectangle
<int> (x
, y
+ height
- lineThickness
, width
, lineThickness
), false);
395 void Graphics::drawRect (const float x
, const float y
, const float width
, const float height
, const float lineThickness
) const
397 // passing in a silly number can cause maths problems in rendering!
398 jassert (areCoordsSensibleNumbers (x
, y
, width
, height
));
401 p
.addRectangle (x
, y
, width
, lineThickness
);
402 p
.addRectangle (x
, y
+ lineThickness
, lineThickness
, height
- lineThickness
* 2.0f
);
403 p
.addRectangle (x
+ width
- lineThickness
, y
+ lineThickness
, lineThickness
, height
- lineThickness
* 2.0f
);
404 p
.addRectangle (x
, y
+ height
- lineThickness
, width
, lineThickness
);
408 void Graphics::drawRect (const Rectangle
<int>& r
, const int lineThickness
) const
410 drawRect (r
.getX(), r
.getY(), r
.getWidth(), r
.getHeight(), lineThickness
);
413 void Graphics::drawBevel (const int x
, const int y
, const int width
, const int height
,
414 const int bevelThickness
, const Colour
& topLeftColour
, const Colour
& bottomRightColour
,
415 const bool useGradient
, const bool sharpEdgeOnOutside
) const
417 // passing in a silly number can cause maths problems in rendering!
418 jassert (areCoordsSensibleNumbers (x
, y
, width
, height
));
420 if (clipRegionIntersects (Rectangle
<int> (x
, y
, width
, height
)))
422 context
->saveState();
424 const float oldOpacity
= 1.0f
;//xxx state->colour.getFloatAlpha();
425 const float ramp
= oldOpacity
/ bevelThickness
;
427 for (int i
= bevelThickness
; --i
>= 0;)
429 const float op
= useGradient
? ramp
* (sharpEdgeOnOutside
? bevelThickness
- i
: i
)
432 context
->setFill (topLeftColour
.withMultipliedAlpha (op
));
433 context
->fillRect (Rectangle
<int> (x
+ i
, y
+ i
, width
- i
* 2, 1), false);
434 context
->setFill (topLeftColour
.withMultipliedAlpha (op
* 0.75f
));
435 context
->fillRect (Rectangle
<int> (x
+ i
, y
+ i
+ 1, 1, height
- i
* 2 - 2), false);
436 context
->setFill (bottomRightColour
.withMultipliedAlpha (op
));
437 context
->fillRect (Rectangle
<int> (x
+ i
, y
+ height
- i
- 1, width
- i
* 2, 1), false);
438 context
->setFill (bottomRightColour
.withMultipliedAlpha (op
* 0.75f
));
439 context
->fillRect (Rectangle
<int> (x
+ width
- i
- 1, y
+ i
+ 1, 1, height
- i
* 2 - 2), false);
442 context
->restoreState();
446 //==============================================================================
447 void Graphics::fillEllipse (const float x
, const float y
, const float width
, const float height
) const
449 // passing in a silly number can cause maths problems in rendering!
450 jassert (areCoordsSensibleNumbers (x
, y
, width
, height
));
453 p
.addEllipse (x
, y
, width
, height
);
457 void Graphics::drawEllipse (const float x
, const float y
, const float width
, const float height
,
458 const float lineThickness
) const
460 // passing in a silly number can cause maths problems in rendering!
461 jassert (areCoordsSensibleNumbers (x
, y
, width
, height
));
464 p
.addEllipse (x
, y
, width
, height
);
465 strokePath (p
, PathStrokeType (lineThickness
));
468 void Graphics::fillRoundedRectangle (const float x
, const float y
, const float width
, const float height
, const float cornerSize
) const
470 // passing in a silly number can cause maths problems in rendering!
471 jassert (areCoordsSensibleNumbers (x
, y
, width
, height
));
474 p
.addRoundedRectangle (x
, y
, width
, height
, cornerSize
);
478 void Graphics::fillRoundedRectangle (const Rectangle
<float>& r
, const float cornerSize
) const
480 fillRoundedRectangle (r
.getX(), r
.getY(), r
.getWidth(), r
.getHeight(), cornerSize
);
483 void Graphics::drawRoundedRectangle (const float x
, const float y
, const float width
, const float height
,
484 const float cornerSize
, const float lineThickness
) const
486 // passing in a silly number can cause maths problems in rendering!
487 jassert (areCoordsSensibleNumbers (x
, y
, width
, height
));
490 p
.addRoundedRectangle (x
, y
, width
, height
, cornerSize
);
491 strokePath (p
, PathStrokeType (lineThickness
));
494 void Graphics::drawRoundedRectangle (const Rectangle
<float>& r
, const float cornerSize
, const float lineThickness
) const
496 drawRoundedRectangle (r
.getX(), r
.getY(), r
.getWidth(), r
.getHeight(), cornerSize
, lineThickness
);
499 void Graphics::drawArrow (const Line
<float>& line
, const float lineThickness
, const float arrowheadWidth
, const float arrowheadLength
) const
502 p
.addArrow (line
, lineThickness
, arrowheadWidth
, arrowheadLength
);
506 void Graphics::fillCheckerBoard (const Rectangle
<int>& area
,
507 const int checkWidth
, const int checkHeight
,
508 const Colour
& colour1
, const Colour
& colour2
) const
510 jassert (checkWidth
> 0 && checkHeight
> 0); // can't be zero or less!
512 if (checkWidth
> 0 && checkHeight
> 0)
514 context
->saveState();
516 if (colour1
== colour2
)
518 context
->setFill (colour1
);
519 context
->fillRect (area
, false);
523 const Rectangle
<int> clipped (context
->getClipBounds().getIntersection (area
));
525 if (! clipped
.isEmpty())
527 context
->clipToRectangle (clipped
);
529 const int checkNumX
= (clipped
.getX() - area
.getX()) / checkWidth
;
530 const int checkNumY
= (clipped
.getY() - area
.getY()) / checkHeight
;
531 const int startX
= area
.getX() + checkNumX
* checkWidth
;
532 const int startY
= area
.getY() + checkNumY
* checkHeight
;
533 const int right
= clipped
.getRight();
534 const int bottom
= clipped
.getBottom();
536 for (int i
= 0; i
< 2; ++i
)
538 context
->setFill (i
== ((checkNumX
^ checkNumY
) & 1) ? colour1
: colour2
);
541 for (int y
= startY
; y
< bottom
; y
+= checkHeight
)
542 for (int x
= startX
+ (cy
++ & 1) * checkWidth
; x
< right
; x
+= checkWidth
* 2)
543 context
->fillRect (Rectangle
<int> (x
, y
, checkWidth
, checkHeight
), false);
548 context
->restoreState();
552 //==============================================================================
553 void Graphics::drawVerticalLine (const int x
, float top
, float bottom
) const
555 context
->drawVerticalLine (x
, top
, bottom
);
558 void Graphics::drawHorizontalLine (const int y
, float left
, float right
) const
560 context
->drawHorizontalLine (y
, left
, right
);
563 void Graphics::drawLine (const float x1
, const float y1
, const float x2
, const float y2
) const
565 context
->drawLine (Line
<float> (x1
, y1
, x2
, y2
));
568 void Graphics::drawLine (const Line
<float>& line
) const
570 context
->drawLine (line
);
573 void Graphics::drawLine (const float x1
, const float y1
, const float x2
, const float y2
, const float lineThickness
) const
575 drawLine (Line
<float> (x1
, y1
, x2
, y2
), lineThickness
);
578 void Graphics::drawLine (const Line
<float>& line
, const float lineThickness
) const
581 p
.addLineSegment (line
, lineThickness
);
585 void Graphics::drawDashedLine (const Line
<float>& line
, const float* const dashLengths
,
586 const int numDashLengths
, const float lineThickness
, int n
) const
588 jassert (n
>= 0 && n
< numDashLengths
); // your start index must be valid!
590 const Point
<double> delta ((line
.getEnd() - line
.getStart()).toDouble());
591 const double totalLen
= delta
.getDistanceFromOrigin();
595 const double onePixAlpha
= 1.0 / totalLen
;
597 for (double alpha
= 0.0; alpha
< 1.0;)
599 jassert (dashLengths
[n
] > 0); // can't have zero-length dashes!
601 const double lastAlpha
= alpha
;
602 alpha
= jmin (1.0, alpha
+ dashLengths
[n
] * onePixAlpha
);
603 n
= (n
+ 1) % numDashLengths
;
607 const Line
<float> segment (line
.getStart() + (delta
* lastAlpha
).toFloat(),
608 line
.getStart() + (delta
* alpha
).toFloat());
610 if (lineThickness
!= 1.0f
)
611 drawLine (segment
, lineThickness
);
613 context
->drawLine (segment
);
619 //==============================================================================
620 void Graphics::setImageResamplingQuality (const Graphics::ResamplingQuality newQuality
)
622 saveStateIfPending();
623 context
->setInterpolationQuality (newQuality
);
626 //==============================================================================
627 void Graphics::drawImageAt (const Image
& imageToDraw
,
628 const int topLeftX
, const int topLeftY
,
629 const bool fillAlphaChannelWithCurrentBrush
) const
631 const int imageW
= imageToDraw
.getWidth();
632 const int imageH
= imageToDraw
.getHeight();
634 drawImage (imageToDraw
,
635 topLeftX
, topLeftY
, imageW
, imageH
,
636 0, 0, imageW
, imageH
,
637 fillAlphaChannelWithCurrentBrush
);
640 void Graphics::drawImageWithin (const Image
& imageToDraw
,
641 const int destX
, const int destY
,
642 const int destW
, const int destH
,
643 const RectanglePlacement
& placementWithinTarget
,
644 const bool fillAlphaChannelWithCurrentBrush
) const
646 // passing in a silly number can cause maths problems in rendering!
647 jassert (areCoordsSensibleNumbers (destX
, destY
, destW
, destH
));
649 if (imageToDraw
.isValid())
651 const int imageW
= imageToDraw
.getWidth();
652 const int imageH
= imageToDraw
.getHeight();
654 if (imageW
> 0 && imageH
> 0)
656 double newX
= 0.0, newY
= 0.0;
657 double newW
= imageW
;
658 double newH
= imageH
;
660 placementWithinTarget
.applyTo (newX
, newY
, newW
, newH
,
661 destX
, destY
, destW
, destH
);
663 if (newW
> 0 && newH
> 0)
665 drawImage (imageToDraw
,
666 roundToInt (newX
), roundToInt (newY
),
667 roundToInt (newW
), roundToInt (newH
),
668 0, 0, imageW
, imageH
,
669 fillAlphaChannelWithCurrentBrush
);
675 void Graphics::drawImage (const Image
& imageToDraw
,
676 int dx
, int dy
, int dw
, int dh
,
677 int sx
, int sy
, int sw
, int sh
,
678 const bool fillAlphaChannelWithCurrentBrush
) const
680 // passing in a silly number can cause maths problems in rendering!
681 jassert (areCoordsSensibleNumbers (dx
, dy
, dw
, dh
));
682 jassert (areCoordsSensibleNumbers (sx
, sy
, sw
, sh
));
684 if (imageToDraw
.isValid() && context
->clipRegionIntersects (Rectangle
<int> (dx
, dy
, dw
, dh
)))
686 drawImageTransformed (imageToDraw
.getClippedImage (Rectangle
<int> (sx
, sy
, sw
, sh
)),
687 AffineTransform::scale (dw
/ (float) sw
, dh
/ (float) sh
)
688 .translated ((float) dx
, (float) dy
),
689 fillAlphaChannelWithCurrentBrush
);
693 void Graphics::drawImageTransformed (const Image
& imageToDraw
,
694 const AffineTransform
& transform
,
695 const bool fillAlphaChannelWithCurrentBrush
) const
697 if (imageToDraw
.isValid() && ! context
->isClipEmpty())
699 if (fillAlphaChannelWithCurrentBrush
)
701 context
->saveState();
702 context
->clipToImageAlpha (imageToDraw
, transform
);
704 context
->restoreState();
708 context
->drawImage (imageToDraw
, transform
, false);
713 //==============================================================================
714 Graphics::ScopedSaveState::ScopedSaveState (Graphics
& g
)
720 Graphics::ScopedSaveState::~ScopedSaveState()
722 context
.restoreState();