/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
This file is part of the JUCE library .
Copyright ( c ) 2015 - ROLI Ltd .
Permission is granted to use this software under the terms of either :
a ) the GPL v2 ( or any later version )
b ) the Affero GPL v3
Details of these licenses can be found at : www . gnu . org / licenses
JUCE is distributed in the hope that it will be useful , but WITHOUT ANY
WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE . See the GNU General Public License for more details .
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
To release a closed - source product which uses JUCE , commercial licenses are
available : visit www . juce . com for more information .
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
namespace
{
template < typename Type >
Rectangle < Type > coordsToRectangle ( Type x , Type y , Type w , Type h )
{
# if JUCE_DEBUG
const int maxVal = 0x3fffffff ;
jassert ( ( int ) x > = - maxVal & & ( int ) x < = maxVal
& & ( int ) y > = - maxVal & & ( int ) y < = maxVal
& & ( int ) w > = 0 & & ( int ) w < = maxVal
& & ( int ) h > = 0 & & ( int ) h < = maxVal ) ;
# endif
return Rectangle < Type > ( x , y , w , h ) ;
}
}
//==============================================================================
LowLevelGraphicsContext : : LowLevelGraphicsContext ( ) { }
LowLevelGraphicsContext : : ~ LowLevelGraphicsContext ( ) { }
//==============================================================================
Graphics : : Graphics ( const Image & imageToDrawOnto )
: context ( * imageToDrawOnto . createLowLevelContext ( ) ) ,
contextToDelete ( & context ) ,
saveStatePending ( false )
{
jassert ( imageToDrawOnto . isValid ( ) ) ; // Can't draw into a null image!
}
Graphics : : Graphics ( LowLevelGraphicsContext & internalContext ) noexcept
: context ( internalContext ) ,
saveStatePending ( false )
{
}
Graphics : : ~ Graphics ( )
{
}
//==============================================================================
void Graphics : : resetToDefaultState ( )
{
saveStateIfPending ( ) ;
context . setFill ( FillType ( ) ) ;
context . setFont ( Font ( ) ) ;
context . setInterpolationQuality ( Graphics : : mediumResamplingQuality ) ;
}
bool Graphics : : isVectorDevice ( ) const
{
return context . isVectorDevice ( ) ;
}
bool Graphics : : reduceClipRegion ( const Rectangle < int > & area )
{
saveStateIfPending ( ) ;
return context . clipToRectangle ( area ) ;
}
bool Graphics : : reduceClipRegion ( const int x , const int y , const int w , const int h )
{
return reduceClipRegion ( Rectangle < int > ( x , y , w , h ) ) ;
}
bool Graphics : : reduceClipRegion ( const RectangleList < int > & clipRegion )
{
saveStateIfPending ( ) ;
return context . clipToRectangleList ( clipRegion ) ;
}
bool Graphics : : reduceClipRegion ( const Path & path , const AffineTransform & transform )
{
saveStateIfPending ( ) ;
context . clipToPath ( path , transform ) ;
return ! context . isClipEmpty ( ) ;
}
bool Graphics : : reduceClipRegion ( const Image & image , const AffineTransform & transform )
{
saveStateIfPending ( ) ;
context . clipToImageAlpha ( image , transform ) ;
return ! context . isClipEmpty ( ) ;
}
void Graphics : : excludeClipRegion ( const Rectangle < int > & rectangleToExclude )
{
saveStateIfPending ( ) ;
context . excludeClipRectangle ( rectangleToExclude ) ;
}
bool Graphics : : isClipEmpty ( ) const
{
return context . isClipEmpty ( ) ;
}
Rectangle < int > Graphics : : getClipBounds ( ) const
{
return context . getClipBounds ( ) ;
}
void Graphics : : saveState ( )
{
saveStateIfPending ( ) ;
saveStatePending = true ;
}
void Graphics : : restoreState ( )
{
if ( saveStatePending )
saveStatePending = false ;
else
context . restoreState ( ) ;
}
void Graphics : : saveStateIfPending ( )
{
if ( saveStatePending )
{
saveStatePending = false ;
context . saveState ( ) ;
}
}
void Graphics : : setOrigin ( Point < int > newOrigin )
{
saveStateIfPending ( ) ;
context . setOrigin ( newOrigin ) ;
}
void Graphics : : setOrigin ( int x , int y )
{
setOrigin ( Point < int > ( x , y ) ) ;
}
void Graphics : : addTransform ( const AffineTransform & transform )
{
saveStateIfPending ( ) ;
context . addTransform ( transform ) ;
}
bool Graphics : : clipRegionIntersects ( const Rectangle < int > & area ) const
{
return context . clipRegionIntersects ( area ) ;
}
void Graphics : : beginTransparencyLayer ( float layerOpacity )
{
saveStateIfPending ( ) ;
context . beginTransparencyLayer ( layerOpacity ) ;
}
void Graphics : : endTransparencyLayer ( )
{
context . endTransparencyLayer ( ) ;
}
//==============================================================================
void Graphics : : setColour ( Colour newColour )
{
saveStateIfPending ( ) ;
context . setFill ( newColour ) ;
}
void Graphics : : setOpacity ( const float newOpacity )
{
saveStateIfPending ( ) ;
context . setOpacity ( newOpacity ) ;
}
void Graphics : : setGradientFill ( const ColourGradient & gradient )
{
setFillType ( gradient ) ;
}
void Graphics : : setTiledImageFill ( const Image & imageToUse , const int anchorX , const int anchorY , const float opacity )
{
saveStateIfPending ( ) ;
context . setFill ( FillType ( imageToUse , AffineTransform : : translation ( ( float ) anchorX , ( float ) anchorY ) ) ) ;
context . setOpacity ( opacity ) ;
}
void Graphics : : setFillType ( const FillType & newFill )
{
saveStateIfPending ( ) ;
context . setFill ( newFill ) ;
}
//==============================================================================
void Graphics : : setFont ( const Font & newFont )
{
saveStateIfPending ( ) ;
context . setFont ( newFont ) ;
}
void Graphics : : setFont ( const float newFontHeight )
{
setFont ( context . getFont ( ) . withHeight ( newFontHeight ) ) ;
}
Font Graphics : : getCurrentFont ( ) const
{
return context . getFont ( ) ;
}
//==============================================================================
void Graphics : : drawSingleLineText ( const String & text , const int startX , const int baselineY ,
Justification justification ) const
{
if ( text . isNotEmpty ( ) )
{
// Don't pass any vertical placement flags to this method - they'll be ignored.
jassert ( justification . getOnlyVerticalFlags ( ) = = 0 ) ;
const int flags = justification . getOnlyHorizontalFlags ( ) ;
if ( flags = = Justification : : right )
{
if ( startX < context . getClipBounds ( ) . getX ( ) )
return ;
}
else if ( flags = = Justification : : left )
if ( startX > context . getClipBounds ( ) . getRight ( ) )
return ;
GlyphArrangement arr ;
arr . addLineOfText ( context . getFont ( ) , text , ( float ) startX , ( float ) baselineY ) ;
if ( flags ! = Justification : : left )
{
float w = arr . getBoundingBox ( 0 , - 1 , true ) . getWidth ( ) ;
if ( ( flags & ( Justification : : horizontallyCentred | Justification : : horizontallyJustified ) ) ! = 0 )
w / = 2.0f ;
arr . draw ( * this , AffineTransform : : translation ( - w , 0 ) ) ;
}
else
{
arr . draw ( * this ) ;
}
}
}
void Graphics : : drawMultiLineText ( const String & text , const int startX ,
const int baselineY , const int maximumLineWidth ) const
{
if ( text . isNotEmpty ( )
& & startX < context . getClipBounds ( ) . getRight ( ) )
{
GlyphArrangement arr ;
arr . addJustifiedText ( context . getFont ( ) , text ,
( float ) startX , ( float ) baselineY , ( float ) maximumLineWidth ,
Justification : : left ) ;
arr . draw ( * this ) ;
}
}
void Graphics : : drawText ( const String & text , const Rectangle < float > & area ,
Justification justificationType , bool useEllipsesIfTooBig ) const
{
if ( text . isNotEmpty ( ) & & context . clipRegionIntersects ( area . getSmallestIntegerContainer ( ) ) )
{
GlyphArrangement arr ;
arr . addCurtailedLineOfText ( context . getFont ( ) , text , 0.0f , 0.0f ,
area . getWidth ( ) , useEllipsesIfTooBig ) ;
arr . justifyGlyphs ( 0 , arr . getNumGlyphs ( ) ,
area . getX ( ) , area . getY ( ) , area . getWidth ( ) , area . getHeight ( ) ,
justificationType ) ;
arr . draw ( * this ) ;
}
}
void Graphics : : drawText ( const String & text , const Rectangle < int > & area ,
Justification justificationType , bool useEllipsesIfTooBig ) const
{
drawText ( text , area . toFloat ( ) , justificationType , useEllipsesIfTooBig ) ;
}
void Graphics : : drawText ( const String & text , const int x , const int y , const int width , const int height ,
Justification justificationType , const bool useEllipsesIfTooBig ) const
{
drawText ( text , Rectangle < int > ( x , y , width , height ) , justificationType , useEllipsesIfTooBig ) ;
}
void Graphics : : drawFittedText ( const String & text , const Rectangle < int > & area ,
Justification justification ,
const int maximumNumberOfLines ,
const float minimumHorizontalScale ) const
{
if ( text . isNotEmpty ( ) & & ( ! area . isEmpty ( ) ) & & context . clipRegionIntersects ( area ) )
{
GlyphArrangement arr ;
arr . addFittedText ( context . getFont ( ) , text ,
( float ) area . getX ( ) , ( float ) area . getY ( ) ,
( float ) area . getWidth ( ) , ( float ) area . getHeight ( ) ,
justification ,
maximumNumberOfLines ,
minimumHorizontalScale ) ;
arr . draw ( * this ) ;
}
}
void Graphics : : drawFittedText ( const String & text , const int x , const int y , const int width , const int height ,
Justification justification ,
const int maximumNumberOfLines ,
const float minimumHorizontalScale ) const
{
drawFittedText ( text , coordsToRectangle ( x , y , width , height ) ,
justification , maximumNumberOfLines , minimumHorizontalScale ) ;
}
//==============================================================================
void Graphics : : fillRect ( const Rectangle < int > & r ) const
{
context . fillRect ( r , false ) ;
}
void Graphics : : fillRect ( const Rectangle < float > & r ) const
{
context . fillRect ( r ) ;
}
void Graphics : : fillRect ( int x , int y , int width , int height ) const
{
context . fillRect ( coordsToRectangle ( x , y , width , height ) , false ) ;
}
void Graphics : : fillRect ( float x , float y , float width , float height ) const
{
fillRect ( coordsToRectangle ( x , y , width , height ) ) ;
}
void Graphics : : fillRectList ( const RectangleList < float > & rectangles ) const
{
context . fillRectList ( rectangles ) ;
}
void Graphics : : fillRectList ( const RectangleList < int > & rects ) const
{
for ( const Rectangle < int > * r = rects . begin ( ) , * const e = rects . end ( ) ; r ! = e ; + + r )
context . fillRect ( * r , false ) ;
}
void Graphics : : setPixel ( int x , int y ) const
{
context . fillRect ( Rectangle < int > ( x , y , 1 , 1 ) , false ) ;
}
void Graphics : : fillAll ( ) const
{
fillRect ( context . getClipBounds ( ) ) ;
}
void Graphics : : fillAll ( Colour colourToUse ) const
{
if ( ! colourToUse . isTransparent ( ) )
{
const Rectangle < int > clip ( context . getClipBounds ( ) ) ;
context . saveState ( ) ;
context . setFill ( colourToUse ) ;
context . fillRect ( clip , false ) ;
context . restoreState ( ) ;
}
}
//==============================================================================
void Graphics : : fillPath ( const Path & path , const AffineTransform & transform ) const
{
if ( ( ! context . isClipEmpty ( ) ) & & ! path . isEmpty ( ) )
context . fillPath ( path , transform ) ;
}
void Graphics : : strokePath ( const Path & path ,
const PathStrokeType & strokeType ,
const AffineTransform & transform ) const
{
Path stroke ;
strokeType . createStrokedPath ( stroke , path , transform , context . getPhysicalPixelScaleFactor ( ) ) ;
fillPath ( stroke ) ;
}
//==============================================================================
void Graphics : : drawRect ( float x , float y , float width , float height , float lineThickness ) const
{
drawRect ( coordsToRectangle ( x , y , width , height ) , lineThickness ) ;
}
void Graphics : : drawRect ( int x , int y , int width , int height , int lineThickness ) const
{
drawRect ( coordsToRectangle ( x , y , width , height ) , lineThickness ) ;
}
void Graphics : : drawRect ( const Rectangle < int > & r , int lineThickness ) const
{
drawRect ( r . toFloat ( ) , ( float ) lineThickness ) ;
}
void Graphics : : drawRect ( Rectangle < float > r , const float lineThickness ) const
{
jassert ( r . getWidth ( ) > = 0.0f & & r . getHeight ( ) > = 0.0f ) ;
RectangleList < float > rects ;
rects . addWithoutMerging ( r . removeFromTop ( lineThickness ) ) ;
rects . addWithoutMerging ( r . removeFromBottom ( lineThickness ) ) ;
rects . addWithoutMerging ( r . removeFromLeft ( lineThickness ) ) ;
rects . addWithoutMerging ( r . removeFromRight ( lineThickness ) ) ;
context . fillRectList ( rects ) ;
}
//==============================================================================
void Graphics : : fillEllipse ( const Rectangle < float > & area ) const
{
Path p ;
p . addEllipse ( area ) ;
fillPath ( p ) ;
}
void Graphics : : fillEllipse ( float x , float y , float w , float h ) const
{
fillEllipse ( Rectangle < float > ( x , y , w , h ) ) ;
}
void Graphics : : drawEllipse ( float x , float y , float width , float height , float lineThickness ) const
{
Path p ;
p . addEllipse ( x , y , width , height ) ;
strokePath ( p , PathStrokeType ( lineThickness ) ) ;
}
void Graphics : : drawEllipse ( const Rectangle < float > & area , float lineThickness ) const
{
drawEllipse ( area . getX ( ) , area . getY ( ) , area . getWidth ( ) , area . getHeight ( ) , lineThickness ) ;
}
void Graphics : : fillRoundedRectangle ( float x , float y , float width , float height , float cornerSize ) const
{
fillRoundedRectangle ( coordsToRectangle ( x , y , width , height ) , cornerSize ) ;
}
void Graphics : : fillRoundedRectangle ( const Rectangle < float > & r , const float cornerSize ) const
{
Path p ;
p . addRoundedRectangle ( r , cornerSize ) ;
fillPath ( p ) ;
}
void Graphics : : drawRoundedRectangle ( float x , float y , float width , float height ,
float cornerSize , float lineThickness ) const
{
drawRoundedRectangle ( coordsToRectangle ( x , y , width , height ) , cornerSize , lineThickness ) ;
}
void Graphics : : drawRoundedRectangle ( const Rectangle < float > & r , float cornerSize , float lineThickness ) const
{
Path p ;
p . addRoundedRectangle ( r , cornerSize ) ;
strokePath ( p , PathStrokeType ( lineThickness ) ) ;
}
void Graphics : : drawArrow ( const Line < float > & line , float lineThickness , float arrowheadWidth , float arrowheadLength ) const
{
Path p ;
p . addArrow ( line , lineThickness , arrowheadWidth , arrowheadLength ) ;
fillPath ( p ) ;
}
void Graphics : : fillCheckerBoard ( const Rectangle < int > & area ,
const int checkWidth , const int checkHeight ,
Colour colour1 , Colour colour2 ) const
{
jassert ( checkWidth > 0 & & checkHeight > 0 ) ; // can't be zero or less!
if ( checkWidth > 0 & & checkHeight > 0 )
{
context . saveState ( ) ;
if ( colour1 = = colour2 )
{
context . setFill ( colour1 ) ;
context . fillRect ( area , false ) ;
}
else
{
const Rectangle < int > clipped ( context . getClipBounds ( ) . getIntersection ( area ) ) ;
if ( ! clipped . isEmpty ( ) )
{
context . clipToRectangle ( clipped ) ;
const int checkNumX = ( clipped . getX ( ) - area . getX ( ) ) / checkWidth ;
const int checkNumY = ( clipped . getY ( ) - area . getY ( ) ) / checkHeight ;
const int startX = area . getX ( ) + checkNumX * checkWidth ;
const int startY = area . getY ( ) + checkNumY * checkHeight ;
const int right = clipped . getRight ( ) ;
const int bottom = clipped . getBottom ( ) ;
for ( int i = 0 ; i < 2 ; + + i )
{
context . setFill ( i = = ( ( checkNumX ^ checkNumY ) & 1 ) ? colour1 : colour2 ) ;
int cy = i ;
for ( int y = startY ; y < bottom ; y + = checkHeight )
for ( int x = startX + ( cy + + & 1 ) * checkWidth ; x < right ; x + = checkWidth * 2 )
context . fillRect ( Rectangle < int > ( x , y , checkWidth , checkHeight ) , false ) ;
}
}
}
context . restoreState ( ) ;
}
}
//==============================================================================
void Graphics : : drawVerticalLine ( const int x , float top , float bottom ) const
{
if ( top < bottom )
context . fillRect ( Rectangle < float > ( ( float ) x , top , 1.0f , bottom - top ) ) ;
}
void Graphics : : drawHorizontalLine ( const int y , float left , float right ) const
{
if ( left < right )
context . fillRect ( Rectangle < float > ( left , ( float ) y , right - left , 1.0f ) ) ;
}
void Graphics : : drawLine ( const Line < float > & line ) const
{
context . drawLine ( line ) ;
}
void Graphics : : drawLine ( float x1 , float y1 , float x2 , float y2 ) const
{
context . drawLine ( Line < float > ( x1 , y1 , x2 , y2 ) ) ;
}
void Graphics : : drawLine ( float x1 , float y1 , float x2 , float y2 , float lineThickness ) const
{
drawLine ( Line < float > ( x1 , y1 , x2 , y2 ) , lineThickness ) ;
}
void Graphics : : drawLine ( const Line < float > & line , const float lineThickness ) const
{
Path p ;
p . addLineSegment ( line , lineThickness ) ;
fillPath ( p ) ;
}
void Graphics : : drawDashedLine ( const Line < float > & line , const float * const dashLengths ,
const int numDashLengths , const float lineThickness , int n ) const
{
jassert ( n > = 0 & & n < numDashLengths ) ; // your start index must be valid!
const Point < double > delta ( ( line . getEnd ( ) - line . getStart ( ) ) . toDouble ( ) ) ;
const double totalLen = delta . getDistanceFromOrigin ( ) ;
if ( totalLen > = 0.1 )
{
const double onePixAlpha = 1.0 / totalLen ;
for ( double alpha = 0.0 ; alpha < 1.0 ; )
{
jassert ( dashLengths [ n ] > 0 ) ; // can't have zero-length dashes!
const double lastAlpha = alpha ;
alpha + = dashLengths [ n ] * onePixAlpha ;
n = ( n + 1 ) % numDashLengths ;
if ( ( n & 1 ) ! = 0 )
{
const Line < float > segment ( line . getStart ( ) + ( delta * lastAlpha ) . toFloat ( ) ,
line . getStart ( ) + ( delta * jmin ( 1.0 , alpha ) ) . toFloat ( ) ) ;
if ( lineThickness ! = 1.0f )
drawLine ( segment , lineThickness ) ;
else
context . drawLine ( segment ) ;
}
}
}
}
//==============================================================================
void Graphics : : setImageResamplingQuality ( const Graphics : : ResamplingQuality newQuality )
{
saveStateIfPending ( ) ;
context . setInterpolationQuality ( newQuality ) ;
}
//==============================================================================
void Graphics : : drawImageAt ( const Image & imageToDraw , int x , int y , bool fillAlphaChannel ) const
{
drawImageTransformed ( imageToDraw ,
AffineTransform : : translation ( ( float ) x , ( float ) y ) ,
fillAlphaChannel ) ;
}
void Graphics : : drawImageWithin ( const Image & imageToDraw ,
int dx , int dy , int dw , int dh ,
RectanglePlacement placementWithinTarget ,
const bool fillAlphaChannelWithCurrentBrush ) const
{
if ( imageToDraw . isValid ( ) )
drawImageTransformed ( imageToDraw ,
placementWithinTarget . getTransformToFit ( imageToDraw . getBounds ( ) . toFloat ( ) ,
coordsToRectangle ( dx , dy , dw , dh ) . toFloat ( ) ) ,
fillAlphaChannelWithCurrentBrush ) ;
}
void Graphics : : drawImage ( const Image & imageToDraw ,
int dx , int dy , int dw , int dh ,
int sx , int sy , int sw , int sh ,
const bool fillAlphaChannelWithCurrentBrush ) const
{
if ( imageToDraw . isValid ( ) & & context . clipRegionIntersects ( coordsToRectangle ( dx , dy , dw , dh ) ) )
drawImageTransformed ( imageToDraw . getClippedImage ( coordsToRectangle ( sx , sy , sw , sh ) ) ,
AffineTransform : : scale ( dw / ( float ) sw , dh / ( float ) sh )
. translated ( ( float ) dx , ( float ) dy ) ,
fillAlphaChannelWithCurrentBrush ) ;
}
void Graphics : : drawImageTransformed ( const Image & imageToDraw ,
const AffineTransform & transform ,
const bool fillAlphaChannelWithCurrentBrush ) const
{
if ( imageToDraw . isValid ( ) & & ! context . isClipEmpty ( ) )
{
if ( fillAlphaChannelWithCurrentBrush )
{
context . saveState ( ) ;
context . clipToImageAlpha ( imageToDraw , transform ) ;
fillAll ( ) ;
context . restoreState ( ) ;
}
else
{
context . drawImage ( imageToDraw , transform ) ;
}
}
}
//==============================================================================
Graphics : : ScopedSaveState : : ScopedSaveState ( Graphics & g ) : context ( g )
{
context . saveState ( ) ;
}
Graphics : : ScopedSaveState : : ~ ScopedSaveState ( )
{
context . restoreState ( ) ;
}