Create wxPaintDC in onPaint() to acknowledge paint events. Without this, wxWidgets keeps sending paint events because it thinks the window still needs to be painted. This caused continuous repaints in Cairo mode, consuming CPU when dialogs with preview canvases were open. The OpenGL GAL has the same issue but is not affected because wxGLCanvas handles paint events differently via its double-buffering mechanism. Fixes https://gitlab.com/kicad/code/kicad/-/issues/18823
1934 lines
56 KiB
C++
1934 lines
56 KiB
C++
/*
|
|
* This program source code file is part of KICAD, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
|
|
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
|
* Copyright (C) 2017-2018 CERN
|
|
*
|
|
* @author Maciej Suminski <[email protected]>
|
|
*
|
|
* CairoGal - Graphics Abstraction Layer for Cairo
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you may find one here:
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include <wx/image.h>
|
|
#include <wx/log.h>
|
|
#include <wx/dcclient.h>
|
|
|
|
#include <gal/cairo/cairo_gal.h>
|
|
#include <gal/cairo/cairo_compositor.h>
|
|
#include <gal/definitions.h>
|
|
#include <geometry/shape_poly_set.h>
|
|
#include <math/vector2wx.h>
|
|
#include <math/util.h> // for KiROUND
|
|
#include <trigo.h>
|
|
#include <bitmap_base.h>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <limits>
|
|
|
|
#include <pixman.h>
|
|
|
|
using namespace KIGFX;
|
|
|
|
|
|
CAIRO_GAL_BASE::CAIRO_GAL_BASE( GAL_DISPLAY_OPTIONS& aDisplayOptions ) : GAL( aDisplayOptions )
|
|
{
|
|
// Initialise grouping
|
|
m_isGrouping = false;
|
|
m_isElementAdded = false;
|
|
m_groupCounter = 0;
|
|
m_currentGroup = nullptr;
|
|
|
|
m_lineWidth = 1.0;
|
|
m_lineWidthInPixels = 1.0;
|
|
m_lineWidthIsOdd = true;
|
|
|
|
// Initialise Cairo state
|
|
cairo_matrix_init_identity( &m_cairoWorldScreenMatrix );
|
|
m_currentContext = nullptr;
|
|
m_context = nullptr;
|
|
m_surface = nullptr;
|
|
|
|
// Grid color settings are different in Cairo and OpenGL
|
|
SetGridColor( COLOR4D( 0.1, 0.1, 0.1, 0.8 ) );
|
|
SetAxesColor( COLOR4D( BLUE ) );
|
|
|
|
// Avoid uninitialized variables:
|
|
cairo_matrix_init_identity( &m_currentXform );
|
|
cairo_matrix_init_identity( &m_currentWorld2Screen );
|
|
}
|
|
|
|
|
|
CAIRO_GAL_BASE::~CAIRO_GAL_BASE()
|
|
{
|
|
ClearCache();
|
|
|
|
if( m_surface )
|
|
cairo_surface_destroy( m_surface );
|
|
|
|
if( m_context )
|
|
cairo_destroy( m_context );
|
|
|
|
for( _cairo_surface* imageSurface : m_imageSurfaces )
|
|
cairo_surface_destroy( imageSurface );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::BeginDrawing()
|
|
{
|
|
resetContext();
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::EndDrawing()
|
|
{
|
|
// Force remaining objects to be drawn
|
|
Flush();
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::updateWorldScreenMatrix()
|
|
{
|
|
cairo_matrix_multiply( &m_currentWorld2Screen, &m_currentXform, &m_cairoWorldScreenMatrix );
|
|
}
|
|
|
|
|
|
const VECTOR2D CAIRO_GAL_BASE::xform( double x, double y )
|
|
{
|
|
VECTOR2D rv;
|
|
|
|
rv.x = m_currentWorld2Screen.xx * x + m_currentWorld2Screen.xy * y + m_currentWorld2Screen.x0;
|
|
rv.y = m_currentWorld2Screen.yx * x + m_currentWorld2Screen.yy * y + m_currentWorld2Screen.y0;
|
|
return rv;
|
|
}
|
|
|
|
|
|
const VECTOR2D CAIRO_GAL_BASE::xform( const VECTOR2D& aP )
|
|
{
|
|
return xform( aP.x, aP.y );
|
|
}
|
|
|
|
|
|
double CAIRO_GAL_BASE::angle_xform( double aAngle )
|
|
{
|
|
// calculate rotation angle due to the rotation transform
|
|
// and if flipped on X axis.
|
|
double world_rotation = -std::atan2( m_currentWorld2Screen.xy, m_currentWorld2Screen.xx );
|
|
|
|
// When flipped on X axis, the rotation angle is M_PI - initial angle:
|
|
if( IsFlippedX() )
|
|
world_rotation = M_PI - world_rotation;
|
|
|
|
return std::fmod( aAngle + world_rotation, 2.0 * M_PI );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::arc_angles_xform_and_normalize( double& aStartAngle, double& aEndAngle )
|
|
{
|
|
// 360 deg arcs have a specific calculation.
|
|
bool is_360deg_arc = std::abs( aEndAngle - aStartAngle ) >= 2 * M_PI;
|
|
double startAngle = aStartAngle;
|
|
double endAngle = aEndAngle;
|
|
|
|
// When the view is flipped, the coordinates are flipped by the matrix transform
|
|
// However, arc angles need to be "flipped": the flipped angle is M_PI - initial angle.
|
|
if( IsFlippedX() )
|
|
{
|
|
startAngle = M_PI - startAngle;
|
|
endAngle = M_PI - endAngle;
|
|
}
|
|
|
|
// Normalize arc angles
|
|
normalize( startAngle, endAngle );
|
|
|
|
// now rotate arc according to the rotation transform matrix
|
|
// Remark:
|
|
// We call angle_xform() to calculate angles according to the flip/rotation
|
|
// transform and normalize between -2M_PI and +2M_PI.
|
|
// Therefore, if aStartAngle = aEndAngle + 2*n*M_PI, the transform gives
|
|
// aEndAngle = aStartAngle
|
|
// So, if this is the case, force the aEndAngle value to draw a circle.
|
|
aStartAngle = angle_xform( startAngle );
|
|
|
|
if( is_360deg_arc ) // arc is a full circle
|
|
aEndAngle = aStartAngle + 2 * M_PI;
|
|
else
|
|
aEndAngle = angle_xform( endAngle );
|
|
}
|
|
|
|
|
|
double CAIRO_GAL_BASE::xform( double x )
|
|
{
|
|
double dx = m_currentWorld2Screen.xx * x;
|
|
double dy = m_currentWorld2Screen.yx * x;
|
|
return sqrt( dx * dx + dy * dy );
|
|
}
|
|
|
|
|
|
static double roundp( double x )
|
|
{
|
|
return floor( x + 0.5 ) + 0.5;
|
|
}
|
|
|
|
|
|
const VECTOR2D CAIRO_GAL_BASE::roundp( const VECTOR2D& v )
|
|
{
|
|
if( m_lineWidthIsOdd )
|
|
return VECTOR2D( ::roundp( v.x ), ::roundp( v.y ) );
|
|
else
|
|
return VECTOR2D( floor( v.x + 0.5 ), floor( v.y + 0.5 ) );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
|
|
{
|
|
syncLineWidth();
|
|
|
|
VECTOR2D p0 = roundp( xform( aStartPoint ) );
|
|
VECTOR2D p1 = roundp( xform( aEndPoint ) );
|
|
|
|
cairo_move_to( m_currentContext, p0.x, p0.y );
|
|
cairo_line_to( m_currentContext, p1.x, p1.y );
|
|
flushPath();
|
|
m_isElementAdded = true;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::syncLineWidth( bool aForceWidth, double aWidth )
|
|
{
|
|
double w = floor( xform( aForceWidth ? aWidth : m_lineWidth ) + 0.5 );
|
|
|
|
if( w <= 1.0 )
|
|
{
|
|
w = 1.0;
|
|
cairo_set_line_join( m_currentContext, CAIRO_LINE_JOIN_MITER );
|
|
cairo_set_line_cap( m_currentContext, CAIRO_LINE_CAP_BUTT );
|
|
cairo_set_line_width( m_currentContext, 1.0 );
|
|
m_lineWidthIsOdd = true;
|
|
}
|
|
else
|
|
{
|
|
cairo_set_line_join( m_currentContext, CAIRO_LINE_JOIN_ROUND );
|
|
cairo_set_line_cap( m_currentContext, CAIRO_LINE_CAP_ROUND );
|
|
cairo_set_line_width( m_currentContext, w );
|
|
m_lineWidthIsOdd = ( (int) w % 2 ) == 1;
|
|
}
|
|
|
|
m_lineWidthInPixels = w;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawSegmentChain( const std::vector<VECTOR2D>& aPointList, double aWidth )
|
|
{
|
|
for( size_t i = 0; i + 1 < aPointList.size(); ++i )
|
|
DrawSegment( aPointList[i], aPointList[i + 1], aWidth );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawSegmentChain( const SHAPE_LINE_CHAIN& aLineChain, double aWidth )
|
|
{
|
|
int numPoints = aLineChain.PointCount();
|
|
|
|
if( aLineChain.IsClosed() )
|
|
numPoints += 1;
|
|
|
|
for( int i = 0; i + 1 < numPoints; ++i )
|
|
DrawSegment( aLineChain.CPoint( i ), aLineChain.CPoint( i + 1 ), aWidth );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawSegment( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint,
|
|
double aWidth )
|
|
{
|
|
if( m_isFillEnabled )
|
|
{
|
|
syncLineWidth( true, aWidth );
|
|
|
|
VECTOR2D p0 = roundp( xform( aStartPoint ) );
|
|
VECTOR2D p1 = roundp( xform( aEndPoint ) );
|
|
|
|
cairo_move_to( m_currentContext, p0.x, p0.y );
|
|
cairo_line_to( m_currentContext, p1.x, p1.y );
|
|
cairo_set_source_rgba( m_currentContext, m_fillColor.r, m_fillColor.g, m_fillColor.b,
|
|
m_fillColor.a );
|
|
cairo_stroke( m_currentContext );
|
|
}
|
|
else
|
|
{
|
|
aWidth /= 2.0;
|
|
SetLineWidth( 1.0 );
|
|
syncLineWidth();
|
|
|
|
// Outline mode for tracks
|
|
VECTOR2D startEndVector = aEndPoint - aStartPoint;
|
|
double lineAngle = atan2( startEndVector.y, startEndVector.x );
|
|
|
|
double sa = sin( lineAngle + M_PI / 2.0 );
|
|
double ca = cos( lineAngle + M_PI / 2.0 );
|
|
|
|
VECTOR2D pa0 = xform( aStartPoint + VECTOR2D( aWidth * ca, aWidth * sa ) );
|
|
VECTOR2D pa1 = xform( aStartPoint - VECTOR2D( aWidth * ca, aWidth * sa ) );
|
|
VECTOR2D pb0 = xform( aEndPoint + VECTOR2D( aWidth * ca, aWidth * sa ) );
|
|
VECTOR2D pb1 = xform( aEndPoint - VECTOR2D( aWidth * ca, aWidth * sa ) );
|
|
|
|
cairo_set_source_rgba( m_currentContext, m_strokeColor.r, m_strokeColor.g, m_strokeColor.b,
|
|
m_strokeColor.a );
|
|
|
|
cairo_move_to( m_currentContext, pa0.x, pa0.y );
|
|
cairo_line_to( m_currentContext, pb0.x, pb0.y );
|
|
|
|
cairo_move_to( m_currentContext, pa1.x, pa1.y );
|
|
cairo_line_to( m_currentContext, pb1.x, pb1.y );
|
|
flushPath();
|
|
|
|
// Calculate the segment angle and arc center in normal/mirrored transform for rounded ends.
|
|
VECTOR2D center_a = xform( aStartPoint );
|
|
VECTOR2D center_b = xform( aEndPoint );
|
|
startEndVector = center_b - center_a;
|
|
lineAngle = atan2( startEndVector.y, startEndVector.x );
|
|
double radius = ( pa0 - center_a ).EuclideanNorm();
|
|
|
|
// Draw the rounded end point of the segment
|
|
double arcStartAngle = lineAngle - M_PI / 2.0;
|
|
cairo_arc( m_currentContext, center_b.x, center_b.y, radius, arcStartAngle,
|
|
arcStartAngle + M_PI );
|
|
|
|
// Draw the rounded start point of the segment
|
|
arcStartAngle = lineAngle + M_PI / 2.0;
|
|
cairo_arc( m_currentContext, center_a.x, center_a.y, radius, arcStartAngle,
|
|
arcStartAngle + M_PI );
|
|
|
|
flushPath();
|
|
}
|
|
|
|
m_isElementAdded = true;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawHoleWall( const VECTOR2D& aCenterPoint, double aRadius, double aWallWidth )
|
|
{
|
|
DrawCircle( aCenterPoint, aRadius + aWallWidth );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius )
|
|
{
|
|
syncLineWidth();
|
|
|
|
VECTOR2D c = roundp( xform( aCenterPoint ) );
|
|
double r = ::roundp( xform( aRadius ) );
|
|
|
|
cairo_set_line_width( m_currentContext, std::min( 2.0 * r, m_lineWidthInPixels ) );
|
|
cairo_new_sub_path( m_currentContext );
|
|
cairo_arc( m_currentContext, c.x, c.y, r, 0.0, 2 * M_PI );
|
|
cairo_close_path( m_currentContext );
|
|
flushPath();
|
|
m_isElementAdded = true;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawArc( const VECTOR2D& aCenterPoint, double aRadius,
|
|
const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle )
|
|
{
|
|
syncLineWidth();
|
|
|
|
double startAngle = aStartAngle.AsRadians();
|
|
double endAngle = startAngle + aAngle.AsRadians();
|
|
|
|
// calculate start and end arc angles according to the rotation transform matrix
|
|
// and normalize:
|
|
arc_angles_xform_and_normalize( startAngle, endAngle );
|
|
|
|
double r = xform( aRadius );
|
|
|
|
// Adjust center and radius slightly to better match the rounding of endpoints.
|
|
VECTOR2D mid = roundp( xform( aCenterPoint ) );
|
|
|
|
VECTOR2D startPointS = VECTOR2D( r, 0.0 );
|
|
VECTOR2D endPointS = VECTOR2D( r, 0.0 );
|
|
RotatePoint( startPointS, -EDA_ANGLE( startAngle, RADIANS_T ) );
|
|
RotatePoint( endPointS, -EDA_ANGLE( endAngle, RADIANS_T ) );
|
|
|
|
VECTOR2D refStart = roundp( xform( aCenterPoint ) + startPointS );
|
|
VECTOR2D refEnd = roundp( xform( aCenterPoint ) + endPointS );
|
|
|
|
r = ( ( refStart - mid ).EuclideanNorm() + ( refEnd - mid ).EuclideanNorm() ) / 2.0;
|
|
|
|
cairo_set_line_width( m_currentContext, m_lineWidthInPixels );
|
|
cairo_new_sub_path( m_currentContext );
|
|
|
|
if( m_isFillEnabled )
|
|
cairo_move_to( m_currentContext, mid.x, mid.y );
|
|
|
|
cairo_arc( m_currentContext, mid.x, mid.y, r, startAngle, endAngle );
|
|
|
|
if( m_isFillEnabled )
|
|
cairo_close_path( m_currentContext );
|
|
|
|
flushPath();
|
|
|
|
m_isElementAdded = true;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawArcSegment( const VECTOR2D& aCenterPoint, double aRadius,
|
|
const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle,
|
|
double aWidth, double aMaxError )
|
|
{
|
|
// Note: aMaxError is not used because Cairo can draw true arcs
|
|
if( m_isFillEnabled )
|
|
{
|
|
m_lineWidth = aWidth;
|
|
m_isStrokeEnabled = true;
|
|
m_isFillEnabled = false;
|
|
DrawArc( aCenterPoint, aRadius, aStartAngle, aAngle );
|
|
m_isFillEnabled = true;
|
|
m_isStrokeEnabled = false;
|
|
return;
|
|
}
|
|
|
|
syncLineWidth();
|
|
|
|
// calculate start and end arc angles according to the rotation transform matrix
|
|
// and normalize:
|
|
double startAngleS = aStartAngle.AsRadians();
|
|
double endAngleS = startAngleS + aAngle.AsRadians();
|
|
arc_angles_xform_and_normalize( startAngleS, endAngleS );
|
|
|
|
double r = xform( aRadius );
|
|
|
|
VECTOR2D mid = xform( aCenterPoint );
|
|
double width = xform( aWidth / 2.0 );
|
|
VECTOR2D startPointS = VECTOR2D( r, 0.0 );
|
|
VECTOR2D endPointS = VECTOR2D( r, 0.0 );
|
|
RotatePoint( startPointS, -EDA_ANGLE( startAngleS, RADIANS_T ) );
|
|
RotatePoint( endPointS, -EDA_ANGLE( endAngleS, RADIANS_T ) );
|
|
|
|
cairo_save( m_currentContext );
|
|
|
|
cairo_set_source_rgba( m_currentContext, m_strokeColor.r, m_strokeColor.g, m_strokeColor.b,
|
|
m_strokeColor.a );
|
|
|
|
cairo_translate( m_currentContext, mid.x, mid.y );
|
|
|
|
cairo_new_sub_path( m_currentContext );
|
|
cairo_arc( m_currentContext, 0, 0, r - width, startAngleS, endAngleS );
|
|
|
|
cairo_new_sub_path( m_currentContext );
|
|
cairo_arc( m_currentContext, 0, 0, r + width, startAngleS, endAngleS );
|
|
|
|
cairo_new_sub_path( m_currentContext );
|
|
cairo_arc_negative( m_currentContext, startPointS.x, startPointS.y, width, startAngleS,
|
|
startAngleS + M_PI );
|
|
|
|
cairo_new_sub_path( m_currentContext );
|
|
cairo_arc( m_currentContext, endPointS.x, endPointS.y, width, endAngleS, endAngleS + M_PI );
|
|
|
|
cairo_restore( m_currentContext );
|
|
flushPath();
|
|
|
|
m_isElementAdded = true;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawRectangle( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
|
|
{
|
|
// Calculate the diagonal points
|
|
syncLineWidth();
|
|
|
|
const VECTOR2D p0 = roundp( xform( aStartPoint ) );
|
|
const VECTOR2D p1 = roundp( xform( VECTOR2D( aEndPoint.x, aStartPoint.y ) ) );
|
|
const VECTOR2D p2 = roundp( xform( aEndPoint ) );
|
|
const VECTOR2D p3 = roundp( xform( VECTOR2D( aStartPoint.x, aEndPoint.y ) ) );
|
|
|
|
// The path is composed from 4 segments
|
|
cairo_move_to( m_currentContext, p0.x, p0.y );
|
|
cairo_line_to( m_currentContext, p1.x, p1.y );
|
|
cairo_line_to( m_currentContext, p2.x, p2.y );
|
|
cairo_line_to( m_currentContext, p3.x, p3.y );
|
|
cairo_close_path( m_currentContext );
|
|
flushPath();
|
|
|
|
m_isElementAdded = true;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawPolygon( const SHAPE_POLY_SET& aPolySet, bool aStrokeTriangulation )
|
|
{
|
|
for( int i = 0; i < aPolySet.OutlineCount(); ++i )
|
|
drawPoly( aPolySet.COutline( i ) );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawPolygon( const SHAPE_LINE_CHAIN& aPolygon )
|
|
{
|
|
drawPoly( aPolygon );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawCurve( const VECTOR2D& aStartPoint, const VECTOR2D& aControlPointA,
|
|
const VECTOR2D& aControlPointB, const VECTOR2D& aEndPoint,
|
|
double aFilterValue )
|
|
{
|
|
// Note: aFilterValue is not used because the cubic Bezier curve is
|
|
// supported by Cairo.
|
|
syncLineWidth();
|
|
|
|
const VECTOR2D sp = roundp( xform( aStartPoint ) );
|
|
const VECTOR2D cpa = roundp( xform( aControlPointA ) );
|
|
const VECTOR2D cpb = roundp( xform( aControlPointB ) );
|
|
const VECTOR2D ep = roundp( xform( aEndPoint ) );
|
|
|
|
cairo_move_to( m_currentContext, sp.x, sp.y );
|
|
cairo_curve_to( m_currentContext, cpa.x, cpa.y, cpb.x, cpb.y, ep.x, ep.y );
|
|
cairo_line_to( m_currentContext, ep.x, ep.y );
|
|
|
|
flushPath();
|
|
m_isElementAdded = true;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawBitmap( const BITMAP_BASE& aBitmap, double alphaBlend )
|
|
{
|
|
cairo_save( m_currentContext );
|
|
|
|
alphaBlend = std::clamp( alphaBlend, 0.0, 1.0 );
|
|
|
|
// We have to calculate the pixel size in users units to draw the image.
|
|
// m_worldUnitLength is a factor used for converting IU to inches
|
|
double scale = 1.0 / ( aBitmap.GetPPI() * m_worldUnitLength );
|
|
|
|
// The position of the bitmap is the bitmap center.
|
|
// move the draw origin to the top left bitmap corner:
|
|
int w = aBitmap.GetSizePixels().x;
|
|
int h = aBitmap.GetSizePixels().y;
|
|
|
|
cairo_set_matrix( m_currentContext, &m_currentWorld2Screen );
|
|
cairo_scale( m_currentContext, scale, scale );
|
|
cairo_translate( m_currentContext, -w / 2.0, -h / 2.0 );
|
|
|
|
cairo_new_path( m_currentContext );
|
|
cairo_surface_t* image = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, w, h );
|
|
cairo_surface_flush( image );
|
|
|
|
unsigned char* pix_buffer = cairo_image_surface_get_data( image );
|
|
|
|
// The pixel buffer of the initial bitmap:
|
|
const wxImage& bm_pix_buffer = *aBitmap.GetImageData();
|
|
|
|
uint32_t mask_color = ( bm_pix_buffer.GetMaskRed() << 16 )
|
|
+ ( bm_pix_buffer.GetMaskGreen() << 8 ) + ( bm_pix_buffer.GetMaskBlue() );
|
|
|
|
// Copy the source bitmap to the cairo bitmap buffer.
|
|
// In cairo bitmap buffer, a ARGB32 bitmap is an ARGB pixel packed into a uint_32
|
|
// 24 low bits only are used for color, top 8 are transparency.
|
|
for( int row = 0; row < h; row++ )
|
|
{
|
|
for( int col = 0; col < w; col++ )
|
|
{
|
|
unsigned char r = bm_pix_buffer.GetRed( col, row );
|
|
unsigned char g = bm_pix_buffer.GetGreen( col, row );
|
|
unsigned char b = bm_pix_buffer.GetBlue( col, row );
|
|
unsigned char a = wxALPHA_OPAQUE;
|
|
|
|
if( bm_pix_buffer.HasAlpha() )
|
|
{
|
|
a = bm_pix_buffer.GetAlpha( col, row );
|
|
|
|
// ARGB32 format needs pre-multiplied alpha
|
|
r = uint32_t( r ) * a / 0xFF;
|
|
g = uint32_t( g ) * a / 0xFF;
|
|
b = uint32_t( b ) * a / 0xFF;
|
|
}
|
|
else if( bm_pix_buffer.HasMask() && (uint32_t)( r << 16 | g << 8 | b ) == mask_color )
|
|
{
|
|
a = wxALPHA_TRANSPARENT;
|
|
}
|
|
|
|
// Build the ARGB24 pixel:
|
|
uint32_t pixel = a << 24 | r << 16 | g << 8 | b;
|
|
|
|
// Write the pixel to the cairo image buffer:
|
|
uint32_t* pix_ptr = (uint32_t*) pix_buffer;
|
|
*pix_ptr = pixel;
|
|
pix_buffer += 4;
|
|
}
|
|
}
|
|
|
|
cairo_surface_mark_dirty( image );
|
|
cairo_set_source_surface( m_currentContext, image, 0, 0 );
|
|
cairo_paint_with_alpha( m_currentContext, alphaBlend );
|
|
|
|
// store the image handle so it can be destroyed later
|
|
m_imageSurfaces.push_back( image );
|
|
|
|
m_isElementAdded = true;
|
|
|
|
cairo_restore( m_currentContext );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::ResizeScreen( int aWidth, int aHeight )
|
|
{
|
|
m_screenSize = VECTOR2I( aWidth, aHeight );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::Flush()
|
|
{
|
|
storePath();
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::ClearScreen()
|
|
{
|
|
cairo_operator_t oldOp = cairo_get_operator( m_currentContext );
|
|
cairo_set_source_rgba( m_currentContext, m_clearColor.r, m_clearColor.g, m_clearColor.b, m_clearColor.a );
|
|
cairo_set_operator( m_currentContext, CAIRO_OPERATOR_SOURCE );
|
|
cairo_rectangle( m_currentContext, 0.0, 0.0, m_screenSize.x, m_screenSize.y );
|
|
cairo_fill( m_currentContext );
|
|
cairo_set_operator( m_currentContext, oldOp );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::SetIsFill( bool aIsFillEnabled )
|
|
{
|
|
storePath();
|
|
m_isFillEnabled = aIsFillEnabled;
|
|
|
|
if( m_isGrouping )
|
|
{
|
|
GROUP_ELEMENT groupElement;
|
|
groupElement.m_Command = CMD_SET_FILL;
|
|
groupElement.m_Argument.BoolArg = aIsFillEnabled;
|
|
m_currentGroup->push_back( groupElement );
|
|
}
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::SetIsStroke( bool aIsStrokeEnabled )
|
|
{
|
|
storePath();
|
|
m_isStrokeEnabled = aIsStrokeEnabled;
|
|
|
|
if( m_isGrouping )
|
|
{
|
|
GROUP_ELEMENT groupElement;
|
|
groupElement.m_Command = CMD_SET_STROKE;
|
|
groupElement.m_Argument.BoolArg = aIsStrokeEnabled;
|
|
m_currentGroup->push_back( groupElement );
|
|
}
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::SetStrokeColor( const COLOR4D& aColor )
|
|
{
|
|
storePath();
|
|
m_strokeColor = aColor;
|
|
|
|
if( m_isGrouping )
|
|
{
|
|
GROUP_ELEMENT groupElement;
|
|
groupElement.m_Command = CMD_SET_STROKECOLOR;
|
|
groupElement.m_Argument.DblArg[0] = m_strokeColor.r;
|
|
groupElement.m_Argument.DblArg[1] = m_strokeColor.g;
|
|
groupElement.m_Argument.DblArg[2] = m_strokeColor.b;
|
|
groupElement.m_Argument.DblArg[3] = m_strokeColor.a;
|
|
m_currentGroup->push_back( groupElement );
|
|
}
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::SetFillColor( const COLOR4D& aColor )
|
|
{
|
|
storePath();
|
|
m_fillColor = aColor;
|
|
|
|
if( m_isGrouping )
|
|
{
|
|
GROUP_ELEMENT groupElement;
|
|
groupElement.m_Command = CMD_SET_FILLCOLOR;
|
|
groupElement.m_Argument.DblArg[0] = m_fillColor.r;
|
|
groupElement.m_Argument.DblArg[1] = m_fillColor.g;
|
|
groupElement.m_Argument.DblArg[2] = m_fillColor.b;
|
|
groupElement.m_Argument.DblArg[3] = m_fillColor.a;
|
|
m_currentGroup->push_back( groupElement );
|
|
}
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::SetLineWidth( float aLineWidth )
|
|
{
|
|
storePath();
|
|
GAL::SetLineWidth( aLineWidth );
|
|
|
|
if( m_isGrouping )
|
|
{
|
|
GROUP_ELEMENT groupElement;
|
|
groupElement.m_Command = CMD_SET_LINE_WIDTH;
|
|
groupElement.m_Argument.DblArg[0] = aLineWidth;
|
|
m_currentGroup->push_back( groupElement );
|
|
}
|
|
else
|
|
{
|
|
m_lineWidth = aLineWidth;
|
|
}
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::SetLayerDepth( double aLayerDepth )
|
|
{
|
|
super::SetLayerDepth( aLayerDepth );
|
|
storePath();
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::Transform( const MATRIX3x3D& aTransformation )
|
|
{
|
|
cairo_matrix_t cairoTransformation, newXform;
|
|
|
|
cairo_matrix_init( &cairoTransformation, aTransformation.m_data[0][0],
|
|
aTransformation.m_data[1][0], aTransformation.m_data[0][1],
|
|
aTransformation.m_data[1][1], aTransformation.m_data[0][2],
|
|
aTransformation.m_data[1][2] );
|
|
|
|
cairo_matrix_multiply( &newXform, &m_currentXform, &cairoTransformation );
|
|
m_currentXform = newXform;
|
|
updateWorldScreenMatrix();
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::Rotate( double aAngle )
|
|
{
|
|
storePath();
|
|
|
|
if( m_isGrouping )
|
|
{
|
|
GROUP_ELEMENT groupElement;
|
|
groupElement.m_Command = CMD_ROTATE;
|
|
groupElement.m_Argument.DblArg[0] = aAngle;
|
|
m_currentGroup->push_back( groupElement );
|
|
}
|
|
else
|
|
{
|
|
cairo_matrix_rotate( &m_currentXform, aAngle );
|
|
updateWorldScreenMatrix();
|
|
}
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::Translate( const VECTOR2D& aTranslation )
|
|
{
|
|
storePath();
|
|
|
|
if( m_isGrouping )
|
|
{
|
|
GROUP_ELEMENT groupElement;
|
|
groupElement.m_Command = CMD_TRANSLATE;
|
|
groupElement.m_Argument.DblArg[0] = aTranslation.x;
|
|
groupElement.m_Argument.DblArg[1] = aTranslation.y;
|
|
m_currentGroup->push_back( groupElement );
|
|
}
|
|
else
|
|
{
|
|
cairo_matrix_translate( &m_currentXform, aTranslation.x, aTranslation.y );
|
|
updateWorldScreenMatrix();
|
|
}
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::Scale( const VECTOR2D& aScale )
|
|
{
|
|
storePath();
|
|
|
|
if( m_isGrouping )
|
|
{
|
|
GROUP_ELEMENT groupElement;
|
|
groupElement.m_Command = CMD_SCALE;
|
|
groupElement.m_Argument.DblArg[0] = aScale.x;
|
|
groupElement.m_Argument.DblArg[1] = aScale.y;
|
|
m_currentGroup->push_back( groupElement );
|
|
}
|
|
else
|
|
{
|
|
cairo_matrix_scale( &m_currentXform, aScale.x, aScale.y );
|
|
updateWorldScreenMatrix();
|
|
}
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::Save()
|
|
{
|
|
storePath();
|
|
|
|
if( m_isGrouping )
|
|
{
|
|
GROUP_ELEMENT groupElement;
|
|
groupElement.m_Command = CMD_SAVE;
|
|
m_currentGroup->push_back( groupElement );
|
|
}
|
|
else
|
|
{
|
|
m_xformStack.push_back( m_currentXform );
|
|
updateWorldScreenMatrix();
|
|
}
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::Restore()
|
|
{
|
|
storePath();
|
|
|
|
if( m_isGrouping )
|
|
{
|
|
GROUP_ELEMENT groupElement;
|
|
groupElement.m_Command = CMD_RESTORE;
|
|
m_currentGroup->push_back( groupElement );
|
|
}
|
|
else
|
|
{
|
|
if( !m_xformStack.empty() )
|
|
{
|
|
m_currentXform = m_xformStack.back();
|
|
m_xformStack.pop_back();
|
|
updateWorldScreenMatrix();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int CAIRO_GAL_BASE::BeginGroup()
|
|
{
|
|
// If the grouping is started: the actual path is stored in the group, when
|
|
// a attribute was changed or when grouping stops with the end group method.
|
|
storePath();
|
|
|
|
GROUP group;
|
|
int groupNumber = getNewGroupNumber();
|
|
m_groups.insert( std::make_pair( groupNumber, group ) );
|
|
m_currentGroup = &m_groups[groupNumber];
|
|
m_isGrouping = true;
|
|
|
|
return groupNumber;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::EndGroup()
|
|
{
|
|
storePath();
|
|
m_isGrouping = false;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawGroup( int aGroupNumber )
|
|
{
|
|
// This method implements a small Virtual Machine - all stored commands
|
|
// are executed; nested calling is also possible
|
|
|
|
storePath();
|
|
|
|
for( auto it = m_groups[aGroupNumber].begin(); it != m_groups[aGroupNumber].end(); ++it )
|
|
{
|
|
switch( it->m_Command )
|
|
{
|
|
case CMD_SET_FILL:
|
|
m_isFillEnabled = it->m_Argument.BoolArg;
|
|
break;
|
|
|
|
case CMD_SET_STROKE:
|
|
m_isStrokeEnabled = it->m_Argument.BoolArg;
|
|
break;
|
|
|
|
case CMD_SET_FILLCOLOR:
|
|
m_fillColor = COLOR4D( it->m_Argument.DblArg[0], it->m_Argument.DblArg[1],
|
|
it->m_Argument.DblArg[2], it->m_Argument.DblArg[3] );
|
|
break;
|
|
|
|
case CMD_SET_STROKECOLOR:
|
|
m_strokeColor = COLOR4D( it->m_Argument.DblArg[0], it->m_Argument.DblArg[1],
|
|
it->m_Argument.DblArg[2], it->m_Argument.DblArg[3] );
|
|
break;
|
|
|
|
case CMD_SET_LINE_WIDTH:
|
|
{
|
|
// Make lines appear at least 1 pixel wide, no matter of zoom
|
|
double x = 1.0, y = 1.0;
|
|
cairo_device_to_user_distance( m_currentContext, &x, &y );
|
|
double minWidth = std::min( fabs( x ), fabs( y ) );
|
|
cairo_set_line_width( m_currentContext,
|
|
std::max( it->m_Argument.DblArg[0], minWidth ) );
|
|
break;
|
|
}
|
|
|
|
|
|
case CMD_STROKE_PATH:
|
|
cairo_set_source_rgba( m_currentContext, m_strokeColor.r, m_strokeColor.g,
|
|
m_strokeColor.b, m_strokeColor.a );
|
|
cairo_append_path( m_currentContext, it->m_CairoPath );
|
|
cairo_stroke( m_currentContext );
|
|
break;
|
|
|
|
case CMD_FILL_PATH:
|
|
cairo_set_source_rgba( m_currentContext, m_fillColor.r, m_fillColor.g, m_fillColor.b,
|
|
m_strokeColor.a );
|
|
cairo_append_path( m_currentContext, it->m_CairoPath );
|
|
cairo_fill( m_currentContext );
|
|
break;
|
|
|
|
/*
|
|
case CMD_TRANSFORM:
|
|
cairo_matrix_t matrix;
|
|
cairo_matrix_init( &matrix, it->argument.DblArg[0], it->argument.DblArg[1],
|
|
it->argument.DblArg[2], it->argument.DblArg[3],
|
|
it->argument.DblArg[4], it->argument.DblArg[5] );
|
|
cairo_transform( m_currentContext, &matrix );
|
|
break;
|
|
*/
|
|
|
|
case CMD_ROTATE:
|
|
cairo_rotate( m_currentContext, it->m_Argument.DblArg[0] );
|
|
break;
|
|
|
|
case CMD_TRANSLATE:
|
|
cairo_translate( m_currentContext, it->m_Argument.DblArg[0], it->m_Argument.DblArg[1] );
|
|
break;
|
|
|
|
case CMD_SCALE:
|
|
cairo_scale( m_currentContext, it->m_Argument.DblArg[0], it->m_Argument.DblArg[1] );
|
|
break;
|
|
|
|
case CMD_SAVE:
|
|
cairo_save( m_currentContext );
|
|
break;
|
|
|
|
case CMD_RESTORE:
|
|
cairo_restore( m_currentContext );
|
|
break;
|
|
|
|
case CMD_CALL_GROUP:
|
|
DrawGroup( it->m_Argument.IntArg );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::ChangeGroupColor( int aGroupNumber, const COLOR4D& aNewColor )
|
|
{
|
|
storePath();
|
|
|
|
for( auto it = m_groups[aGroupNumber].begin(); it != m_groups[aGroupNumber].end(); ++it )
|
|
{
|
|
if( it->m_Command == CMD_SET_FILLCOLOR || it->m_Command == CMD_SET_STROKECOLOR )
|
|
{
|
|
it->m_Argument.DblArg[0] = aNewColor.r;
|
|
it->m_Argument.DblArg[1] = aNewColor.g;
|
|
it->m_Argument.DblArg[2] = aNewColor.b;
|
|
it->m_Argument.DblArg[3] = aNewColor.a;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::ChangeGroupDepth( int aGroupNumber, int aDepth )
|
|
{
|
|
// Cairo does not have any possibilities to change the depth coordinate of stored items,
|
|
// it depends only on the order of drawing
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DeleteGroup( int aGroupNumber )
|
|
{
|
|
storePath();
|
|
|
|
// Delete the Cairo paths
|
|
std::deque<GROUP_ELEMENT>::iterator it, end;
|
|
|
|
for( it = m_groups[aGroupNumber].begin(), end = m_groups[aGroupNumber].end(); it != end; ++it )
|
|
{
|
|
if( it->m_Command == CMD_FILL_PATH || it->m_Command == CMD_STROKE_PATH )
|
|
cairo_path_destroy( it->m_CairoPath );
|
|
}
|
|
|
|
// Delete the group
|
|
m_groups.erase( aGroupNumber );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::ClearCache()
|
|
{
|
|
for( auto it = m_groups.begin(); it != m_groups.end(); )
|
|
DeleteGroup( ( it++ )->first );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::SetNegativeDrawMode( bool aSetting )
|
|
{
|
|
cairo_set_operator( m_currentContext, aSetting ? CAIRO_OPERATOR_CLEAR : CAIRO_OPERATOR_OVER );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::StartDiffLayer()
|
|
{
|
|
SetTarget( TARGET_TEMP );
|
|
ClearTarget( TARGET_TEMP );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::EndDiffLayer()
|
|
{
|
|
m_compositor->DrawBuffer( m_tempBuffer, m_mainBuffer, CAIRO_OPERATOR_DIFFERENCE );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::StartNegativesLayer()
|
|
{
|
|
SetTarget( TARGET_TEMP );
|
|
ClearTarget( TARGET_TEMP );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::EndNegativesLayer()
|
|
{
|
|
m_compositor->DrawBuffer( m_tempBuffer, m_mainBuffer, CAIRO_OPERATOR_OVER );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawCursor( const VECTOR2D& aCursorPosition )
|
|
{
|
|
m_cursorPosition = aCursorPosition;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::EnableDepthTest( bool aEnabled )
|
|
{
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::resetContext()
|
|
{
|
|
for( _cairo_surface* imageSurface : m_imageSurfaces )
|
|
cairo_surface_destroy( imageSurface );
|
|
|
|
m_imageSurfaces.clear();
|
|
|
|
ClearScreen();
|
|
|
|
// Compute the world <-> screen transformations
|
|
ComputeWorldScreenMatrix();
|
|
|
|
cairo_matrix_init( &m_cairoWorldScreenMatrix, m_worldScreenMatrix.m_data[0][0],
|
|
m_worldScreenMatrix.m_data[1][0], m_worldScreenMatrix.m_data[0][1],
|
|
m_worldScreenMatrix.m_data[1][1], m_worldScreenMatrix.m_data[0][2],
|
|
m_worldScreenMatrix.m_data[1][2] );
|
|
|
|
// we work in screen-space coordinates and do the transforms outside.
|
|
cairo_identity_matrix( m_context );
|
|
|
|
cairo_matrix_init_identity( &m_currentXform );
|
|
|
|
// Start drawing with a new path
|
|
cairo_new_path( m_context );
|
|
m_isElementAdded = true;
|
|
|
|
updateWorldScreenMatrix();
|
|
|
|
m_lineWidth = 0;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::drawAxes( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
|
|
{
|
|
syncLineWidth();
|
|
|
|
VECTOR2D p0 = roundp( xform( aStartPoint ) );
|
|
VECTOR2D p1 = roundp( xform( aEndPoint ) );
|
|
VECTOR2D org = roundp( xform( VECTOR2D( 0.0, 0.0 ) ) ); // Axis origin = 0,0 coord
|
|
|
|
cairo_set_source_rgba( m_currentContext, m_axesColor.r, m_axesColor.g, m_axesColor.b,
|
|
m_axesColor.a );
|
|
cairo_move_to( m_currentContext, p0.x, org.y );
|
|
cairo_line_to( m_currentContext, p1.x, org.y );
|
|
cairo_move_to( m_currentContext, org.x, p0.y );
|
|
cairo_line_to( m_currentContext, org.x, p1.y );
|
|
cairo_stroke( m_currentContext );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::drawGridLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
|
|
{
|
|
syncLineWidth();
|
|
VECTOR2D p0 = roundp( xform( aStartPoint ) );
|
|
VECTOR2D p1 = roundp( xform( aEndPoint ) );
|
|
|
|
cairo_set_source_rgba( m_currentContext, m_gridColor.r, m_gridColor.g, m_gridColor.b,
|
|
m_gridColor.a );
|
|
cairo_move_to( m_currentContext, p0.x, p0.y );
|
|
cairo_line_to( m_currentContext, p1.x, p1.y );
|
|
cairo_stroke( m_currentContext );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::drawGridCross( const VECTOR2D& aPoint )
|
|
{
|
|
syncLineWidth();
|
|
VECTOR2D offset( 0, 0 );
|
|
double size = 2.0 * m_lineWidthInPixels + 0.5;
|
|
|
|
VECTOR2D p0 = roundp( xform( aPoint ) ) - VECTOR2D( size, 0 ) + offset;
|
|
VECTOR2D p1 = roundp( xform( aPoint ) ) + VECTOR2D( size, 0 ) + offset;
|
|
VECTOR2D p2 = roundp( xform( aPoint ) ) - VECTOR2D( 0, size ) + offset;
|
|
VECTOR2D p3 = roundp( xform( aPoint ) ) + VECTOR2D( 0, size ) + offset;
|
|
|
|
cairo_set_source_rgba( m_currentContext, m_gridColor.r, m_gridColor.g, m_gridColor.b,
|
|
m_gridColor.a );
|
|
cairo_move_to( m_currentContext, p0.x, p0.y );
|
|
cairo_line_to( m_currentContext, p1.x, p1.y );
|
|
cairo_move_to( m_currentContext, p2.x, p2.y );
|
|
cairo_line_to( m_currentContext, p3.x, p3.y );
|
|
cairo_stroke( m_currentContext );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::drawGridPoint( const VECTOR2D& aPoint, double aWidth, double aHeight )
|
|
{
|
|
VECTOR2D p = roundp( xform( aPoint ) );
|
|
|
|
double sw = std::max( 1.0, aWidth );
|
|
double sh = std::max( 1.0, aHeight );
|
|
|
|
cairo_set_source_rgba( m_currentContext, m_gridColor.r, m_gridColor.g, m_gridColor.b,
|
|
m_gridColor.a );
|
|
cairo_rectangle( m_currentContext, p.x - std::floor( sw / 2 ) - 0.5,
|
|
p.y - std::floor( sh / 2 ) - 0.5, sw, sh );
|
|
|
|
cairo_fill( m_currentContext );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::flushPath()
|
|
{
|
|
if( m_isFillEnabled )
|
|
{
|
|
cairo_set_source_rgba( m_currentContext, m_fillColor.r, m_fillColor.g, m_fillColor.b,
|
|
m_fillColor.a );
|
|
|
|
if( m_isStrokeEnabled )
|
|
{
|
|
cairo_set_line_width( m_currentContext, m_lineWidthInPixels );
|
|
cairo_fill_preserve( m_currentContext );
|
|
}
|
|
else
|
|
{
|
|
cairo_fill( m_currentContext );
|
|
}
|
|
}
|
|
|
|
if( m_isStrokeEnabled )
|
|
{
|
|
cairo_set_line_width( m_currentContext, m_lineWidthInPixels );
|
|
cairo_set_source_rgba( m_currentContext, m_strokeColor.r, m_strokeColor.g, m_strokeColor.b,
|
|
m_strokeColor.a );
|
|
cairo_stroke( m_currentContext );
|
|
}
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::storePath()
|
|
{
|
|
if( m_isElementAdded )
|
|
{
|
|
m_isElementAdded = false;
|
|
|
|
if( !m_isGrouping )
|
|
{
|
|
if( m_isFillEnabled )
|
|
{
|
|
cairo_set_source_rgba( m_currentContext, m_fillColor.r, m_fillColor.g,
|
|
m_fillColor.b, m_fillColor.a );
|
|
cairo_fill_preserve( m_currentContext );
|
|
}
|
|
|
|
if( m_isStrokeEnabled )
|
|
{
|
|
cairo_set_source_rgba( m_currentContext, m_strokeColor.r, m_strokeColor.g,
|
|
m_strokeColor.b, m_strokeColor.a );
|
|
cairo_stroke_preserve( m_currentContext );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Copy the actual path, append it to the global path list
|
|
// then check, if the path needs to be stroked/filled and
|
|
// add this command to the group list;
|
|
if( m_isStrokeEnabled )
|
|
{
|
|
GROUP_ELEMENT groupElement;
|
|
groupElement.m_CairoPath = cairo_copy_path( m_currentContext );
|
|
groupElement.m_Command = CMD_STROKE_PATH;
|
|
m_currentGroup->push_back( groupElement );
|
|
}
|
|
|
|
if( m_isFillEnabled )
|
|
{
|
|
GROUP_ELEMENT groupElement;
|
|
groupElement.m_CairoPath = cairo_copy_path( m_currentContext );
|
|
groupElement.m_Command = CMD_FILL_PATH;
|
|
m_currentGroup->push_back( groupElement );
|
|
}
|
|
}
|
|
|
|
cairo_new_path( m_currentContext );
|
|
}
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::blitCursor( wxMemoryDC& clientDC )
|
|
{
|
|
if( !IsCursorEnabled() )
|
|
return;
|
|
|
|
VECTOR2D p = ToScreen( m_cursorPosition );
|
|
const COLOR4D cColor = getCursorColor();
|
|
|
|
wxColour color( cColor.r * cColor.a * 255, cColor.g * cColor.a * 255, cColor.b * cColor.a * 255,
|
|
255 );
|
|
clientDC.SetPen( wxPen( color ) );
|
|
|
|
if( m_crossHairMode == CROSS_HAIR_MODE::FULLSCREEN_CROSS )
|
|
{
|
|
clientDC.DrawLine( 0, p.y, m_screenSize.x, p.y );
|
|
clientDC.DrawLine( p.x, 0, p.x, m_screenSize.y );
|
|
}
|
|
else if( m_crossHairMode == CROSS_HAIR_MODE::FULLSCREEN_DIAGONAL )
|
|
{
|
|
// Oversized but that's ok
|
|
int diagonalSize = m_screenSize.x + m_screenSize.y;
|
|
clientDC.DrawLine( p.x - diagonalSize, p.y - diagonalSize,
|
|
p.x + diagonalSize, p.y + diagonalSize );
|
|
clientDC.DrawLine( p.x - diagonalSize, p.y + diagonalSize,
|
|
p.x + diagonalSize, p.y - diagonalSize );
|
|
}
|
|
else
|
|
{
|
|
const int cursorSize = 80;
|
|
clientDC.DrawLine( p.x - cursorSize / 2, p.y, p.x + cursorSize / 2, p.y );
|
|
clientDC.DrawLine( p.x, p.y - cursorSize / 2, p.x, p.y + cursorSize / 2 );
|
|
}
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::drawPoly( const std::deque<VECTOR2D>& aPointList )
|
|
{
|
|
if( aPointList.size() <= 1 )
|
|
return;
|
|
|
|
// Iterate over the point list and draw the segments
|
|
std::deque<VECTOR2D>::const_iterator it = aPointList.begin();
|
|
|
|
syncLineWidth();
|
|
|
|
const VECTOR2D p = roundp( xform( it->x, it->y ) );
|
|
|
|
cairo_move_to( m_currentContext, p.x, p.y );
|
|
|
|
for( ++it; it != aPointList.end(); ++it )
|
|
{
|
|
const VECTOR2D p2 = roundp( xform( it->x, it->y ) );
|
|
|
|
cairo_line_to( m_currentContext, p2.x, p2.y );
|
|
}
|
|
|
|
flushPath();
|
|
m_isElementAdded = true;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::drawPoly( const std::vector<VECTOR2D>& aPointList )
|
|
{
|
|
if( aPointList.size() <= 1 )
|
|
return;
|
|
|
|
// Iterate over the point list and draw the segments
|
|
std::vector<VECTOR2D>::const_iterator it = aPointList.begin();
|
|
|
|
syncLineWidth();
|
|
|
|
const VECTOR2D p = roundp( xform( it->x, it->y ) );
|
|
|
|
cairo_move_to( m_currentContext, p.x, p.y );
|
|
|
|
for( ++it; it != aPointList.end(); ++it )
|
|
{
|
|
const VECTOR2D p2 = roundp( xform( it->x, it->y ) );
|
|
|
|
cairo_line_to( m_currentContext, p2.x, p2.y );
|
|
}
|
|
|
|
flushPath();
|
|
m_isElementAdded = true;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::drawPoly( const VECTOR2D aPointList[], int aListSize )
|
|
{
|
|
if( aListSize <= 1 )
|
|
return;
|
|
|
|
// Iterate over the point list and draw the segments
|
|
const VECTOR2D* ptr = aPointList;
|
|
|
|
syncLineWidth();
|
|
|
|
const VECTOR2D p = roundp( xform( ptr->x, ptr->y ) );
|
|
cairo_move_to( m_currentContext, p.x, p.y );
|
|
|
|
for( int i = 1; i < aListSize; ++i )
|
|
{
|
|
++ptr;
|
|
const VECTOR2D p2 = roundp( xform( ptr->x, ptr->y ) );
|
|
cairo_line_to( m_currentContext, p2.x, p2.y );
|
|
}
|
|
|
|
flushPath();
|
|
m_isElementAdded = true;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::drawPoly( const SHAPE_LINE_CHAIN& aLineChain )
|
|
{
|
|
if( aLineChain.PointCount() <= 1 )
|
|
return;
|
|
|
|
syncLineWidth();
|
|
|
|
auto numPoints = aLineChain.PointCount();
|
|
|
|
if( aLineChain.IsClosed() )
|
|
numPoints += 1;
|
|
|
|
const VECTOR2I start = aLineChain.CPoint( 0 );
|
|
const VECTOR2D p = roundp( xform( start.x, start.y ) );
|
|
cairo_move_to( m_currentContext, p.x, p.y );
|
|
|
|
for( int i = 1; i < numPoints; ++i )
|
|
{
|
|
const VECTOR2I& pw = aLineChain.CPoint( i );
|
|
const VECTOR2D ps = roundp( xform( pw.x, pw.y ) );
|
|
cairo_line_to( m_currentContext, ps.x, ps.y );
|
|
}
|
|
|
|
flushPath();
|
|
m_isElementAdded = true;
|
|
}
|
|
|
|
|
|
unsigned int CAIRO_GAL_BASE::getNewGroupNumber()
|
|
{
|
|
wxASSERT_MSG( m_groups.size() < std::numeric_limits<unsigned int>::max(),
|
|
wxT( "There are no free slots to store a group" ) );
|
|
|
|
while( m_groups.find( m_groupCounter ) != m_groups.end() )
|
|
m_groupCounter++;
|
|
|
|
return m_groupCounter++;
|
|
}
|
|
|
|
|
|
CAIRO_GAL::CAIRO_GAL( GAL_DISPLAY_OPTIONS& aDisplayOptions, wxWindow* aParent,
|
|
wxEvtHandler* aMouseListener, wxEvtHandler* aPaintListener,
|
|
const wxString& aName ) :
|
|
CAIRO_GAL_BASE( aDisplayOptions ),
|
|
wxWindow( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxEXPAND, aName )
|
|
{
|
|
// Initialise compositing state
|
|
m_mainBuffer = 0;
|
|
m_overlayBuffer = 0;
|
|
m_tempBuffer = 0;
|
|
m_savedBuffer = 0;
|
|
m_validCompositor = false;
|
|
m_currentTarget = TARGET_NONCACHED;
|
|
SetTarget( TARGET_NONCACHED );
|
|
|
|
#ifdef _WIN32
|
|
// need to fix broken cairo rendering on Windows with wx 3.3
|
|
SetDoubleBuffered( false );
|
|
#endif
|
|
|
|
m_bitmapBuffer = nullptr;
|
|
m_wxOutput = nullptr;
|
|
|
|
m_parentWindow = aParent;
|
|
m_mouseListener = aMouseListener;
|
|
m_paintListener = aPaintListener;
|
|
|
|
// Connect the native cursor handler
|
|
Connect( wxEVT_SET_CURSOR, wxSetCursorEventHandler( CAIRO_GAL::onSetNativeCursor ), nullptr,
|
|
this );
|
|
|
|
// Connecting the event handlers
|
|
Connect( wxEVT_PAINT, wxPaintEventHandler( CAIRO_GAL::onPaint ) );
|
|
|
|
// Mouse events are skipped to the parent
|
|
Connect( wxEVT_MOTION, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
Connect( wxEVT_LEFT_UP, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
Connect( wxEVT_MIDDLE_DOWN, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
Connect( wxEVT_MIDDLE_UP, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
Connect( wxEVT_MIDDLE_DCLICK, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
Connect( wxEVT_RIGHT_UP, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
Connect( wxEVT_RIGHT_DCLICK, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
Connect( wxEVT_AUX1_DOWN, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
Connect( wxEVT_AUX1_UP, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
Connect( wxEVT_AUX1_DCLICK, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
Connect( wxEVT_AUX2_DOWN, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
Connect( wxEVT_AUX2_UP, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
Connect( wxEVT_AUX2_DCLICK, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
Connect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
|
|
#if defined _WIN32 || defined _WIN64
|
|
Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
#endif
|
|
|
|
Bind( wxEVT_GESTURE_ZOOM, &CAIRO_GAL::skipGestureEvent, this );
|
|
Bind( wxEVT_GESTURE_PAN, &CAIRO_GAL::skipGestureEvent, this );
|
|
|
|
SetSize( aParent->GetClientSize() );
|
|
m_screenSize = ToVECTOR2I( aParent->GetClientSize() );
|
|
|
|
// Allocate memory for pixel storage
|
|
allocateBitmaps();
|
|
|
|
m_isInitialized = false;
|
|
}
|
|
|
|
|
|
CAIRO_GAL::~CAIRO_GAL()
|
|
{
|
|
deleteBitmaps();
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::BeginDrawing()
|
|
{
|
|
initSurface();
|
|
|
|
CAIRO_GAL_BASE::BeginDrawing();
|
|
|
|
if( !m_validCompositor )
|
|
setCompositor();
|
|
|
|
m_compositor->SetMainContext( m_context );
|
|
m_compositor->SetBuffer( m_mainBuffer );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::EndDrawing()
|
|
{
|
|
CAIRO_GAL_BASE::EndDrawing();
|
|
|
|
// Merge buffers on the screen
|
|
m_compositor->DrawBuffer( m_mainBuffer );
|
|
m_compositor->DrawBuffer( m_overlayBuffer );
|
|
|
|
// Now translate the raw context data from the format stored
|
|
// by cairo into a format understood by wxImage.
|
|
int height = m_screenSize.y;
|
|
int stride = m_stride;
|
|
|
|
unsigned char* srcRow = m_bitmapBuffer;
|
|
unsigned char* dst = m_wxOutput;
|
|
|
|
for( int y = 0; y < height; y++ )
|
|
{
|
|
for( int x = 0; x < stride; x += 4 )
|
|
{
|
|
const unsigned char* src = srcRow + x;
|
|
|
|
#if defined( __BYTE_ORDER__ ) && ( __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ )
|
|
// XRGB
|
|
dst[0] = src[1];
|
|
dst[1] = src[2];
|
|
dst[2] = src[3];
|
|
#else
|
|
// BGRX
|
|
dst[0] = src[2];
|
|
dst[1] = src[1];
|
|
dst[2] = src[0];
|
|
#endif
|
|
|
|
dst += 3;
|
|
}
|
|
|
|
srcRow += stride;
|
|
}
|
|
|
|
wxImage img( m_wxBufferWidth, m_screenSize.y, m_wxOutput, true );
|
|
wxBitmap bmp( img );
|
|
wxMemoryDC mdc( bmp );
|
|
wxClientDC clientDC( this );
|
|
|
|
// Now it is the time to blit the mouse cursor
|
|
blitCursor( mdc );
|
|
clientDC.Blit( 0, 0, m_screenSize.x, m_screenSize.y, &mdc, 0, 0, wxCOPY );
|
|
|
|
deinitSurface();
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::PostPaint( wxPaintEvent& aEvent )
|
|
{
|
|
// posts an event to m_paint_listener to ask for redraw the canvas.
|
|
if( m_paintListener )
|
|
wxPostEvent( m_paintListener, aEvent );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::ResizeScreen( int aWidth, int aHeight )
|
|
{
|
|
CAIRO_GAL_BASE::ResizeScreen( aWidth, aHeight );
|
|
|
|
// Recreate the bitmaps
|
|
deleteBitmaps();
|
|
allocateBitmaps();
|
|
|
|
if( m_validCompositor )
|
|
m_compositor->Resize( aWidth, aHeight );
|
|
|
|
m_validCompositor = false;
|
|
|
|
SetSize( wxSize( aWidth, aHeight ) );
|
|
}
|
|
|
|
|
|
bool CAIRO_GAL::Show( bool aShow )
|
|
{
|
|
bool s = wxWindow::Show( aShow );
|
|
|
|
if( aShow )
|
|
wxWindow::Raise();
|
|
|
|
return s;
|
|
}
|
|
|
|
|
|
int CAIRO_GAL::BeginGroup()
|
|
{
|
|
initSurface();
|
|
return CAIRO_GAL_BASE::BeginGroup();
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::EndGroup()
|
|
{
|
|
CAIRO_GAL_BASE::EndGroup();
|
|
deinitSurface();
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::SetTarget( RENDER_TARGET aTarget )
|
|
{
|
|
// If the compositor is not set, that means that there is a recaching process going on
|
|
// and we do not need the compositor now
|
|
if( !m_validCompositor )
|
|
return;
|
|
|
|
// Cairo grouping prevents display of overlapping items on the same layer in the lighter color
|
|
if( m_isInitialized )
|
|
storePath();
|
|
|
|
switch( aTarget )
|
|
{
|
|
default:
|
|
case TARGET_CACHED:
|
|
case TARGET_NONCACHED: m_compositor->SetBuffer( m_mainBuffer ); break;
|
|
case TARGET_OVERLAY: m_compositor->SetBuffer( m_overlayBuffer ); break;
|
|
case TARGET_TEMP: m_compositor->SetBuffer( m_tempBuffer ); break;
|
|
}
|
|
|
|
m_currentTarget = aTarget;
|
|
}
|
|
|
|
|
|
RENDER_TARGET CAIRO_GAL::GetTarget() const
|
|
{
|
|
return m_currentTarget;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::ClearTarget( RENDER_TARGET aTarget )
|
|
{
|
|
// Save the current state
|
|
unsigned int currentBuffer = m_compositor->GetBuffer();
|
|
|
|
switch( aTarget )
|
|
{
|
|
// Cached and noncached items are rendered to the same buffer
|
|
default:
|
|
case TARGET_CACHED:
|
|
case TARGET_NONCACHED: m_compositor->SetBuffer( m_mainBuffer ); break;
|
|
case TARGET_OVERLAY: m_compositor->SetBuffer( m_overlayBuffer ); break;
|
|
case TARGET_TEMP: m_compositor->SetBuffer( m_tempBuffer ); break;
|
|
}
|
|
|
|
m_compositor->ClearBuffer( COLOR4D::BLACK );
|
|
|
|
// Restore the previous state
|
|
m_compositor->SetBuffer( currentBuffer );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::initSurface()
|
|
{
|
|
if( m_isInitialized )
|
|
return;
|
|
|
|
m_surface = cairo_image_surface_create_for_data( m_bitmapBuffer, GAL_FORMAT, m_wxBufferWidth,
|
|
m_screenSize.y, m_stride );
|
|
|
|
m_context = cairo_create( m_surface );
|
|
|
|
#ifdef DEBUG
|
|
cairo_status_t status = cairo_status( m_context );
|
|
wxASSERT_MSG( status == CAIRO_STATUS_SUCCESS, wxT( "Cairo context creation error" ) );
|
|
#endif /* DEBUG */
|
|
|
|
m_currentContext = m_context;
|
|
|
|
m_isInitialized = true;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::deinitSurface()
|
|
{
|
|
if( !m_isInitialized )
|
|
return;
|
|
|
|
cairo_destroy( m_context );
|
|
m_context = nullptr;
|
|
cairo_surface_destroy( m_surface );
|
|
m_surface = nullptr;
|
|
|
|
m_isInitialized = false;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::allocateBitmaps()
|
|
{
|
|
m_wxBufferWidth = m_screenSize.x;
|
|
|
|
// Create buffer, use the system independent Cairo context backend
|
|
m_stride = cairo_format_stride_for_width( GAL_FORMAT, m_wxBufferWidth );
|
|
m_bufferSize = m_stride * m_screenSize.y;
|
|
|
|
wxASSERT( m_bitmapBuffer == nullptr );
|
|
m_bitmapBuffer = new unsigned char[m_bufferSize];
|
|
|
|
wxASSERT( m_wxOutput == nullptr );
|
|
m_wxOutput = new unsigned char[m_wxBufferWidth * 3 * m_screenSize.y];
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::deleteBitmaps()
|
|
{
|
|
delete[] m_bitmapBuffer;
|
|
m_bitmapBuffer = nullptr;
|
|
|
|
delete[] m_wxOutput;
|
|
m_wxOutput = nullptr;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::setCompositor()
|
|
{
|
|
// Recreate the compositor with the new Cairo context
|
|
m_compositor.reset( new CAIRO_COMPOSITOR( &m_currentContext ) );
|
|
m_compositor->Resize( m_screenSize.x, m_screenSize.y );
|
|
m_compositor->SetAntialiasingMode( m_options.antialiasing_mode );
|
|
|
|
// Prepare buffers
|
|
m_mainBuffer = m_compositor->CreateBuffer();
|
|
m_overlayBuffer = m_compositor->CreateBuffer();
|
|
m_tempBuffer = m_compositor->CreateBuffer();
|
|
|
|
m_validCompositor = true;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::onPaint( wxPaintEvent& aEvent )
|
|
{
|
|
// A wxPaintDC must be created in wxEVT_PAINT handlers. Without this, the system keeps
|
|
// sending paint events because it thinks the window still needs to be painted, causing
|
|
// high CPU usage in fallback mode (Cairo).
|
|
wxPaintDC dc( this );
|
|
PostPaint( aEvent );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::skipMouseEvent( wxMouseEvent& aEvent )
|
|
{
|
|
// Post the mouse event to the event listener registered in constructor, if any
|
|
if( m_mouseListener )
|
|
wxPostEvent( m_mouseListener, aEvent );
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::skipGestureEvent( wxGestureEvent& aEvent )
|
|
{
|
|
// Post the gesture event to the event listener registered in constructor, if any
|
|
if( m_mouseListener )
|
|
wxPostEvent( m_mouseListener, aEvent );
|
|
}
|
|
|
|
|
|
bool CAIRO_GAL::updatedGalDisplayOptions( const GAL_DISPLAY_OPTIONS& aOptions )
|
|
{
|
|
bool refresh = false;
|
|
|
|
if( m_validCompositor &&
|
|
aOptions.antialiasing_mode != m_compositor->GetAntialiasingMode() )
|
|
{
|
|
m_compositor->SetAntialiasingMode( m_options.antialiasing_mode );
|
|
m_validCompositor = false;
|
|
deinitSurface();
|
|
|
|
refresh = true;
|
|
}
|
|
|
|
if( super::updatedGalDisplayOptions( aOptions ) )
|
|
{
|
|
Refresh();
|
|
refresh = true;
|
|
}
|
|
|
|
return refresh;
|
|
}
|
|
|
|
|
|
bool CAIRO_GAL::SetNativeCursorStyle( KICURSOR aCursor, bool aHiDPI )
|
|
{
|
|
// Store the current cursor type and get the wx cursor for it
|
|
if( !GAL::SetNativeCursorStyle( aCursor, aHiDPI ) )
|
|
return false;
|
|
|
|
m_currentwxCursor = CURSOR_STORE::GetCursor( m_currentNativeCursor, aHiDPI );
|
|
|
|
#if wxCHECK_VERSION( 3, 3, 0 )
|
|
wxWindow::SetCursorBundle( m_currentwxCursor );
|
|
#else
|
|
wxWindow::SetCursor( m_currentwxCursor );
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::onSetNativeCursor( wxSetCursorEvent& aEvent )
|
|
{
|
|
#if wxCHECK_VERSION( 3, 3, 0 )
|
|
aEvent.SetCursor( m_currentwxCursor.GetCursorFor( this ) );
|
|
#else
|
|
aEvent.SetCursor( m_currentwxCursor );
|
|
#endif
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawGrid()
|
|
{
|
|
SetTarget( TARGET_NONCACHED );
|
|
|
|
// Draw the grid
|
|
// For the drawing the start points, end points and increments have
|
|
// to be calculated in world coordinates
|
|
VECTOR2D worldStartPoint = m_screenWorldMatrix * VECTOR2D( 0.0, 0.0 );
|
|
VECTOR2D worldEndPoint = m_screenWorldMatrix * VECTOR2D( m_screenSize );
|
|
|
|
// Compute the line marker or point radius of the grid
|
|
// Note: generic grids can't handle sub-pixel lines without
|
|
// either losing fine/course distinction or having some dots
|
|
// fail to render
|
|
float marker = std::fmax( 1.0f, m_gridLineWidth ) / m_worldScale;
|
|
float doubleMarker = 2.0f * marker;
|
|
|
|
// Draw axes if desired
|
|
if( m_axesEnabled )
|
|
{
|
|
SetLineWidth( marker );
|
|
drawAxes( worldStartPoint, worldEndPoint );
|
|
}
|
|
|
|
if( !m_gridVisibility || m_gridSize.x == 0 || m_gridSize.y == 0 )
|
|
return;
|
|
|
|
VECTOR2D gridScreenSize( m_gridSize );
|
|
|
|
double gridThreshold = KiROUND( computeMinGridSpacing() / m_worldScale );
|
|
|
|
if( m_gridStyle == GRID_STYLE::SMALL_CROSS )
|
|
gridThreshold *= 2.0;
|
|
|
|
// If we cannot display the grid density, scale down by a tick size and
|
|
// try again. Eventually, we get some representation of the grid
|
|
while( std::min( gridScreenSize.x, gridScreenSize.y ) <= gridThreshold )
|
|
{
|
|
gridScreenSize = gridScreenSize * static_cast<double>( m_gridTick );
|
|
}
|
|
|
|
// Compute grid starting and ending indexes to draw grid points on the
|
|
// visible screen area
|
|
// Note: later any point coordinate will be offsetted by m_gridOrigin
|
|
int gridStartX = KiROUND( ( worldStartPoint.x - m_gridOrigin.x ) / gridScreenSize.x );
|
|
int gridEndX = KiROUND( ( worldEndPoint.x - m_gridOrigin.x ) / gridScreenSize.x );
|
|
int gridStartY = KiROUND( ( worldStartPoint.y - m_gridOrigin.y ) / gridScreenSize.y );
|
|
int gridEndY = KiROUND( ( worldEndPoint.y - m_gridOrigin.y ) / gridScreenSize.y );
|
|
|
|
// Ensure start coordinate < end coordinate
|
|
normalize( gridStartX, gridEndX );
|
|
normalize( gridStartY, gridEndY );
|
|
|
|
// Ensure the grid fills the screen
|
|
--gridStartX;
|
|
++gridEndX;
|
|
--gridStartY;
|
|
++gridEndY;
|
|
|
|
// Draw the grid behind all other layers
|
|
SetLayerDepth( m_depthRange.y * 0.75 );
|
|
|
|
if( m_gridStyle == GRID_STYLE::LINES )
|
|
{
|
|
// Now draw the grid, every coarse grid line gets the double width
|
|
|
|
// Vertical lines
|
|
for( int j = gridStartY; j <= gridEndY; j++ )
|
|
{
|
|
const double y = j * gridScreenSize.y + m_gridOrigin.y;
|
|
|
|
if( m_axesEnabled && y == 0.0 )
|
|
continue;
|
|
|
|
SetLineWidth( ( j % m_gridTick ) ? marker : doubleMarker );
|
|
drawGridLine( VECTOR2D( gridStartX * gridScreenSize.x + m_gridOrigin.x, y ),
|
|
VECTOR2D( gridEndX * gridScreenSize.x + m_gridOrigin.x, y ) );
|
|
}
|
|
|
|
// Horizontal lines
|
|
for( int i = gridStartX; i <= gridEndX; i++ )
|
|
{
|
|
const double x = i * gridScreenSize.x + m_gridOrigin.x;
|
|
|
|
if( m_axesEnabled && x == 0.0 )
|
|
continue;
|
|
|
|
SetLineWidth( ( i % m_gridTick ) ? marker : doubleMarker );
|
|
drawGridLine( VECTOR2D( x, gridStartY * gridScreenSize.y + m_gridOrigin.y ),
|
|
VECTOR2D( x, gridEndY * gridScreenSize.y + m_gridOrigin.y ) );
|
|
}
|
|
}
|
|
else // Dots or Crosses grid
|
|
{
|
|
m_lineWidthIsOdd = true;
|
|
m_isStrokeEnabled = true;
|
|
|
|
for( int j = gridStartY; j <= gridEndY; j++ )
|
|
{
|
|
bool tickY = ( j % m_gridTick == 0 );
|
|
|
|
for( int i = gridStartX; i <= gridEndX; i++ )
|
|
{
|
|
bool tickX = ( i % m_gridTick == 0 );
|
|
VECTOR2D pos{ i * gridScreenSize.x + m_gridOrigin.x,
|
|
j * gridScreenSize.y + m_gridOrigin.y };
|
|
|
|
if( m_gridStyle == GRID_STYLE::SMALL_CROSS )
|
|
{
|
|
SetLineWidth( ( tickX && tickY ) ? doubleMarker : marker );
|
|
drawGridCross( pos );
|
|
}
|
|
else if( m_gridStyle == GRID_STYLE::DOTS )
|
|
{
|
|
double doubleGridLineWidth = m_gridLineWidth * 2.0f;
|
|
drawGridPoint( pos, ( tickX ) ? doubleGridLineWidth : m_gridLineWidth,
|
|
( tickY ) ? doubleGridLineWidth : m_gridLineWidth );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawGlyph( const KIFONT::GLYPH& aGlyph, int aNth, int aTotal )
|
|
{
|
|
if( aGlyph.IsStroke() )
|
|
{
|
|
const KIFONT::STROKE_GLYPH& glyph = static_cast<const KIFONT::STROKE_GLYPH&>( aGlyph );
|
|
|
|
for( const std::vector<VECTOR2D>& pointList : glyph )
|
|
drawPoly( pointList );
|
|
}
|
|
else if( aGlyph.IsOutline() )
|
|
{
|
|
const KIFONT::OUTLINE_GLYPH& glyph = static_cast<const KIFONT::OUTLINE_GLYPH&>( aGlyph );
|
|
|
|
if( aNth == 0 )
|
|
{
|
|
cairo_close_path( m_currentContext );
|
|
flushPath();
|
|
|
|
cairo_new_path( m_currentContext );
|
|
SetIsFill( true );
|
|
SetIsStroke( false );
|
|
}
|
|
|
|
// eventually glyphs should not be drawn as polygons at all,
|
|
// but as bitmaps with antialiasing, this is just a stopgap measure
|
|
// of getting some form of outline font display
|
|
|
|
glyph.Triangulate(
|
|
[&]( const VECTOR2D& aVertex1, const VECTOR2D& aVertex2, const VECTOR2D& aVertex3 )
|
|
{
|
|
syncLineWidth();
|
|
|
|
const VECTOR2D p0 = roundp( xform( aVertex1 ) );
|
|
const VECTOR2D p1 = roundp( xform( aVertex2 ) );
|
|
const VECTOR2D p2 = roundp( xform( aVertex3 ) );
|
|
|
|
cairo_move_to( m_currentContext, p0.x, p0.y );
|
|
cairo_line_to( m_currentContext, p1.x, p1.y );
|
|
cairo_line_to( m_currentContext, p2.x, p2.y );
|
|
cairo_close_path( m_currentContext );
|
|
cairo_set_fill_rule( m_currentContext, CAIRO_FILL_RULE_EVEN_ODD );
|
|
flushPath();
|
|
cairo_fill( m_currentContext );
|
|
} );
|
|
|
|
if( aNth == aTotal - 1 )
|
|
{
|
|
flushPath();
|
|
SetIsFill( false );
|
|
SetIsStroke( true );
|
|
m_isElementAdded = true;
|
|
}
|
|
}
|
|
}
|