1 /*
2  * Copyright (c) 2015-2020 Meltytech, LLC
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18 
19 #include "graph.h"
20 #include <QPainterPath>
21 #include <QVector>
22 #include <math.h>
23 
24 /*
25  * Apply the "bgcolor" and "angle" parameters to the QPainter
26  */
setup_graph_painter(QPainter & p,QRectF & r,mlt_properties filter_properties)27 void setup_graph_painter( QPainter& p, QRectF& r, mlt_properties filter_properties )
28 {
29 	mlt_color bg_color = mlt_properties_get_color( filter_properties, "bgcolor" );
30 	double angle = mlt_properties_get_double( filter_properties, "angle" );
31 
32 	p.setRenderHint(QPainter::Antialiasing);
33 
34 	// Fill background
35 	if ( bg_color.r || bg_color.g || bg_color.g || bg_color.a ) {
36 		QColor qbgcolor( bg_color.r, bg_color.g, bg_color.b, bg_color.a );
37 		p.fillRect( 0, 0, p.device()->width(), p.device()->height(), qbgcolor );
38 	}
39 
40 	// Apply rotation
41 	if ( angle ) {
42 		p.translate( r.x() + r.width() / 2, r.y() + r.height() / 2 );
43 		p.rotate( angle );
44 		p.translate( -(r.x() + r.width() / 2), -(r.y() + r.height() / 2) );
45 	}
46 }
47 
48 /*
49  * Apply the "thickness", "gorient" and "color.x" parameters to the QPainter
50  */
setup_graph_pen(QPainter & p,QRectF & r,mlt_properties filter_properties,double scale)51 void setup_graph_pen(QPainter& p, QRectF& r, mlt_properties filter_properties , double scale)
52 {
53 	int thickness = mlt_properties_get_int( filter_properties, "thickness" ) * scale;
54 	QString gorient = mlt_properties_get( filter_properties, "gorient" );
55 	QVector<QColor> colors;
56 	bool color_found = true;
57 
58 	QPen pen;
59 	pen.setWidth( qAbs(thickness) );
60 
61 	// Find user specified colors for the gradient
62 	while( color_found ) {
63 		QString prop_name = QString("color.") + QString::number(colors.size() + 1);
64 		if( mlt_properties_exists(filter_properties, prop_name.toUtf8().constData() ) ) {
65 			mlt_color mcolor = mlt_properties_get_color( filter_properties, prop_name.toUtf8().constData() );
66 			colors.append( QColor( mcolor.r, mcolor.g, mcolor.b, mcolor.a ) );
67 		} else {
68 			color_found = false;
69 		}
70 	}
71 
72 	if( !colors.size() ) {
73 		// No color specified. Just use white.
74 		pen.setBrush(Qt::white);
75 	} else if ( colors.size() == 1 ) {
76 		// Only use one color
77 		pen.setBrush(colors[0]);
78 	} else {
79 		QLinearGradient gradient;
80 		if( gorient.startsWith("h", Qt::CaseInsensitive) ) {
81 			gradient.setStart ( r.x(), r.y() );
82 			gradient.setFinalStop ( r.x() + r.width(), r.y() );
83 		} else { // Vertical
84 			gradient.setStart ( r.x(), r.y() );
85 			gradient.setFinalStop ( r.x(), r.y() + r.height() );
86 		}
87 
88 		qreal step = 1.0 / ( colors.size() - 1 );
89 		for( int i = 0; i < colors.size(); i++ )
90 		{
91 			gradient.setColorAt( (qreal)i * step, colors[i] );
92 		}
93 		pen.setBrush(gradient);
94 	}
95 
96 	p.setPen(pen);
97 }
98 
fix_point(QPointF & point,QRectF & rect)99 static inline void fix_point( QPointF& point, QRectF& rect )
100 {
101 	if( point.x() < rect.x() ) {
102 		point.setX( rect.x() );
103 	} else if ( point.x() > rect.x() + rect.width() ) {
104 		point.setX( rect.x() + rect.width() );
105 	}
106 
107 	if( point.y() < rect.y() ) {
108 		point.setY( rect.y() );
109 	} else if ( point.y() > rect.y() + rect.height() ) {
110 		point.setY( rect.y() + rect.height() );
111 	}
112 }
113 
paint_line_graph(QPainter & p,QRectF & rect,int points,float * values,double tension,int fill)114 void paint_line_graph( QPainter& p, QRectF& rect, int points, float* values, double tension, int fill )
115 {
116 	double width = rect.width();
117 	double height = rect.height();
118 	double pixelsPerPoint = width / (double)(points - 1);
119 
120 	// Calculate cubic control points
121 	QVector<QPointF> controlPoints( (points - 1) * 2 );
122 	int cpi = 0;
123 	// First control point is equal to first point
124 	controlPoints[cpi++] = QPointF( rect.x(), rect.y() + height - values[0] * height );
125 
126 	// Calculate control points between data points
127 	// Based on ideas from:
128 	// http://scaledinnovation.com/analytics/splines/aboutSplines.html
129 	for( int i = 0; i < (points - 2); i++ ) {
130 		double x0 = rect.x() + (double)i * pixelsPerPoint;
131 		double x1 = rect.x() + (double)(i + 1) * pixelsPerPoint;
132 		double x2 = rect.x() + (double)(i + 2) * pixelsPerPoint;
133 		double y0 = rect.y() + height - values[i] * height;
134 		double y1 = rect.y() + height - values[i + 1] * height;
135 		double y2 = rect.y() + height - values[i + 2] * height;
136 		double d01 = sqrt( pow( x1 - x0, 2 ) + pow( y1 - y0, 2) );
137 		double d12 = sqrt( pow( x2 - x1, 2 ) + pow( y2 - y1, 2) );
138 		double fa = tension * d01 / ( d01 + d12 );
139 		double fb = tension * d12 / ( d01 + d12 );
140 		double p1x = x1 - fa * ( x2 - x0 );
141 		double p1y = y1 - fa * ( y2 - y0 );
142 		QPointF c1( p1x, p1y );
143 		fix_point( c1, rect );
144 		double p2x = x1 + fb * ( x2 - x0 );
145 		double p2y = y1 + fb * ( y2 - y0 );
146 		QPointF c2( p2x, p2y );
147 		fix_point( c2, rect );
148 		controlPoints[cpi++] = c1;
149 		controlPoints[cpi++] = c2;
150 	}
151 
152 	// Last control point is equal to last point
153 	controlPoints[cpi++] = QPointF( rect.x() + width, rect.y() + height - values[points - 1] * height );
154 
155 	// Draw a sequence of bezier curves to interpolate between points.
156 	QPainterPath curvePath;
157 
158 	// Begin at the first data point.
159 	curvePath.moveTo( rect.x(), rect.y() + height - values[0] * height );
160 	cpi = 0;
161 	for( int i = 1; i < points; i++ ) {
162 		QPointF c1 = controlPoints[cpi++];
163 		QPointF c2 = controlPoints[cpi++];
164 		double endX = rect.x() + (double)i * pixelsPerPoint;
165 		double endY = rect.y() + height - values[i] * height;
166 		QPointF end( endX, endY );
167 		curvePath.cubicTo( c1, c2, end );
168 	}
169 
170 	if( fill ) {
171 		curvePath.lineTo( rect.x() + width, rect.y() + height );
172 		curvePath.lineTo( rect.x(), rect.y() + height );
173 		curvePath.closeSubpath();
174 		p.fillPath( curvePath, p.pen().brush() );
175 	} else {
176 		p.drawPath( curvePath );
177 	}
178 }
179 
paint_bar_graph(QPainter & p,QRectF & rect,int points,float * values)180 void paint_bar_graph( QPainter& p, QRectF& rect, int points, float* values )
181 {
182 	double width = rect.width();
183 	double height = rect.height();
184 	double pixelsPerPoint = width / (double)points;
185 	QPointF bottomPoint( rect.x() + pixelsPerPoint / 2, rect.y() + height );
186 	QPointF topPoint( rect.x() + pixelsPerPoint / 2, rect.y() );
187 
188 	for( int i = 0; i < points; i++ ) {
189 		topPoint.setY( rect.y() + height - values[i] * height );
190 		p.drawLine( bottomPoint, topPoint );
191 		bottomPoint.setX( bottomPoint.x() + pixelsPerPoint );
192 		topPoint.setX( bottomPoint.x() );
193 	}
194 }
195