1721 lines
50 KiB
C++
1721 lines
50 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 (C) 2012-2021 Kicad Developers, see AUTHORS.txt for contributors.
|
|
* Copyright (C) 2017-2018 CERN
|
|
*
|
|
* @author Maciej Suminski <maciej.suminski@cern.ch>
|
|
*
|
|
* 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 <gal/cairo/cairo_gal.h>
|
|
#include <gal/cairo/cairo_compositor.h>
|
|
#include <gal/definitions.h>
|
|
#include <geometry/shape_poly_set.h>
|
|
#include <math/util.h> // for KiROUND
|
|
#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 );
|
|
}
|
|
|
|
|
|
const double CAIRO_GAL_BASE::angle_xform( const 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
|
|
SWAP( 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 );
|
|
}
|
|
|
|
|
|
const 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 && m_isStrokeEnabled )
|
|
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::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 );
|
|
|
|
auto pa0 = xform( aStartPoint + VECTOR2D( aWidth * ca, aWidth * sa ) );
|
|
auto pa1 = xform( aStartPoint - VECTOR2D( aWidth * ca, aWidth * sa ) );
|
|
auto pb0 = xform( aEndPoint + VECTOR2D( aWidth * ca, aWidth * sa ) );
|
|
auto pb1 = xform( aEndPoint - VECTOR2D( aWidth * ca, aWidth * sa ) );
|
|
auto pa = xform( aStartPoint );
|
|
auto pb = xform( aEndPoint );
|
|
auto rb = ( pa0 - pa ).EuclideanNorm();
|
|
|
|
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 );
|
|
|
|
cairo_arc( m_currentContext, pb.x, pb.y, rb, lineAngle - M_PI / 2.0,
|
|
lineAngle + M_PI / 2.0 );
|
|
cairo_arc( m_currentContext, pa.x, pa.y, rb, lineAngle + M_PI / 2.0,
|
|
lineAngle + 3.0 * M_PI / 2.0 );
|
|
|
|
flushPath();
|
|
}
|
|
|
|
m_isElementAdded = true;
|
|
}
|
|
|
|
|
|
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, double aStartAngle,
|
|
double aEndAngle )
|
|
{
|
|
syncLineWidth();
|
|
|
|
// calculate start and end arc angles according to the rotation transform matrix
|
|
// and normalize:
|
|
arc_angles_xform_and_normalize( aStartAngle, aEndAngle );
|
|
|
|
double r = xform( aRadius );
|
|
|
|
// N.B. This is backwards. We set this because we want to adjust the center
|
|
// point that changes both endpoints. In the worst case, this is twice as far.
|
|
// We cannot adjust radius or center based on the other because this causes the
|
|
// whole arc to change position/size
|
|
m_lineWidthIsOdd = !( static_cast<int>( aRadius ) % 2 );
|
|
|
|
auto mid = roundp( xform( aCenterPoint ) );
|
|
|
|
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, aStartAngle, aEndAngle );
|
|
|
|
if( m_isFillEnabled )
|
|
cairo_close_path( m_currentContext );
|
|
|
|
flushPath();
|
|
|
|
m_isElementAdded = true;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL_BASE::DrawArcSegment( const VECTOR2D& aCenterPoint, double aRadius,
|
|
double aStartAngle, double aEndAngle, 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, aEndAngle );
|
|
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;
|
|
double endAngleS = aEndAngle;
|
|
arc_angles_xform_and_normalize( startAngleS, endAngleS );
|
|
|
|
double r = xform( aRadius );
|
|
|
|
// N.B. This is backwards. We set this because we want to adjust the center
|
|
// point that changes both endpoints. In the worst case, this is twice as far.
|
|
// We cannot adjust radius or center based on the other because this causes the
|
|
// whole arc to change position/size
|
|
m_lineWidthIsOdd = !( static_cast<int>( aRadius ) % 2 );
|
|
|
|
VECTOR2D mid = roundp( xform( aCenterPoint ) );
|
|
double width = xform( aWidth / 2.0 );
|
|
VECTOR2D startPointS = VECTOR2D( r, 0.0 ).Rotate( startAngleS );
|
|
VECTOR2D endPointS = VECTOR2D( r, 0.0 ).Rotate( endAngleS );
|
|
|
|
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 )
|
|
{
|
|
cairo_save( m_currentContext );
|
|
|
|
// 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++ )
|
|
{
|
|
// Build the RGB24 pixel:
|
|
uint32_t pixel = bm_pix_buffer.GetRed( col, row ) << 16;
|
|
pixel += bm_pix_buffer.GetGreen( col, row ) << 8;
|
|
pixel += bm_pix_buffer.GetBlue( col, row );
|
|
|
|
if( bm_pix_buffer.HasAlpha() )
|
|
pixel += bm_pix_buffer.GetAlpha( col, row ) << 24;
|
|
else if( bm_pix_buffer.HasMask() && pixel == mask_color )
|
|
pixel += ( wxALPHA_TRANSPARENT << 24 );
|
|
else
|
|
pixel += ( wxALPHA_OPAQUE << 24 );
|
|
|
|
// 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( m_currentContext );
|
|
|
|
// 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_set_source_rgb( m_currentContext, m_clearColor.r, m_clearColor.g, m_clearColor.b );
|
|
cairo_rectangle( m_currentContext, 0.0, 0.0, m_screenSize.x, m_screenSize.y );
|
|
cairo_fill( m_currentContext );
|
|
}
|
|
|
|
|
|
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_ADD );
|
|
}
|
|
|
|
|
|
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_fill_preserve( m_currentContext );
|
|
else
|
|
cairo_fill( 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( 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();
|
|
const int cursorSize = m_fullscreenCursor ? 8000 : 80;
|
|
|
|
wxColour color( cColor.r * cColor.a * 255, cColor.g * cColor.a * 255, cColor.b * cColor.a * 255,
|
|
255 );
|
|
clientDC.SetPen( wxPen( color ) );
|
|
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 )
|
|
{
|
|
wxCHECK( aPointList.size() > 1, /* void */ );
|
|
|
|
// 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 VECTOR2D aPointList[], int aListSize )
|
|
{
|
|
wxCHECK( aListSize > 1, /* void */ );
|
|
|
|
// 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 )
|
|
{
|
|
wxCHECK( aLineChain.PointCount() > 1, /* void */ );
|
|
|
|
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;
|
|
SetTarget( TARGET_NONCACHED );
|
|
|
|
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_MOUSEWHEEL, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
#if defined _WIN32 || defined _WIN64
|
|
Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
|
|
#endif
|
|
|
|
SetSize( aParent->GetClientSize() );
|
|
m_screenSize = VECTOR2I( 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.
|
|
pixman_image_t* dstImg = pixman_image_create_bits(
|
|
wxPlatformInfo::Get().GetEndianness() == wxENDIAN_LITTLE ? PIXMAN_b8g8r8
|
|
: PIXMAN_r8g8b8,
|
|
m_screenSize.x, m_screenSize.y, (uint32_t*) m_wxOutput, m_wxBufferWidth * 3 );
|
|
pixman_image_t* srcImg =
|
|
pixman_image_create_bits( PIXMAN_a8r8g8b8, m_screenSize.x, m_screenSize.y,
|
|
(uint32_t*) m_bitmapBuffer, m_wxBufferWidth * 4 );
|
|
|
|
pixman_image_composite( PIXMAN_OP_SRC, srcImg, nullptr, dstImg, 0, 0, 0, 0, 0, 0,
|
|
m_screenSize.x, m_screenSize.y );
|
|
|
|
// Free allocated memory
|
|
pixman_image_unref( srcImg );
|
|
pixman_image_unref( dstImg );
|
|
|
|
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;
|
|
|
|
while( ( ( m_wxBufferWidth * 3 ) % 4 ) != 0 )
|
|
m_wxBufferWidth++;
|
|
|
|
// 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 * 4];
|
|
|
|
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.cairo_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 )
|
|
{
|
|
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 );
|
|
}
|
|
|
|
|
|
bool CAIRO_GAL::updatedGalDisplayOptions( const GAL_DISPLAY_OPTIONS& aOptions )
|
|
{
|
|
bool refresh = false;
|
|
|
|
if( m_validCompositor &&
|
|
aOptions.cairo_antialiasing_mode != m_compositor->GetAntialiasingMode() )
|
|
{
|
|
m_compositor->SetAntialiasingMode( m_options.cairo_antialiasing_mode );
|
|
m_validCompositor = false;
|
|
deinitSurface();
|
|
|
|
refresh = true;
|
|
}
|
|
|
|
if( super::updatedGalDisplayOptions( aOptions ) )
|
|
{
|
|
Refresh();
|
|
refresh = true;
|
|
}
|
|
|
|
return refresh;
|
|
}
|
|
|
|
|
|
bool CAIRO_GAL::SetNativeCursorStyle( KICURSOR aCursor )
|
|
{
|
|
// Store the current cursor type and get the wxCursor for it
|
|
if( !GAL::SetNativeCursorStyle( aCursor ) )
|
|
return false;
|
|
|
|
m_currentwxCursor = CURSOR_STORE::GetCursor( m_currentNativeCursor );
|
|
|
|
// Update the cursor in the wx control
|
|
wxWindow::SetCursor( m_currentwxCursor );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void CAIRO_GAL::onSetNativeCursor( wxSetCursorEvent& aEvent )
|
|
{
|
|
aEvent.SetCursor( m_currentwxCursor );
|
|
}
|
|
|
|
|
|
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
|
|
SWAP( gridStartX, >, gridEndX );
|
|
SWAP( 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 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|