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