protected internal virtual void OnPaint(TimelineViewTrackPaintEventArgs e) { Rectangle rect = e.TargetRect; // Draw curve switch (e.GetAdjustedQuality(this.parentTrack.CurveQuality)) { case QualityLevel.High: e.Graphics.SmoothingMode = SmoothingMode.HighQuality; break; default: case QualityLevel.Medium: case QualityLevel.Low: e.Graphics.SmoothingMode = SmoothingMode.HighSpeed; break; } float pixelWidth = this.ParentView.ConvertUnitsToPixels(this.model.EndTime - this.model.BeginTime); if (pixelWidth < 5) { e.Graphics.SmoothingMode = SmoothingMode.HighSpeed; } if (pixelWidth > 2) { this.OnPaintCurve(e); } e.Graphics.SmoothingMode = SmoothingMode.Default; // Draw overlay this.OnPaintBoundaries(e); }
protected virtual void OnPaintCurve(TimelineViewTrackPaintEventArgs e) { // Ignore e.BeginTime and e.EndTime for sampling, as we're heavily dependent on rounding errors, etc. while undersampling. Instead, always sample the whole curve. float beginUnitX = Math.Max(this.ParentView.UnitOriginOffset - this.ParentView.UnitScroll, this.model.BeginTime); float endUnitX = Math.Min(this.ParentView.UnitOriginOffset - this.ParentView.UnitScroll + this.ParentView.VisibleUnitWidth, this.model.EndTime); // Determine graph parameters float minPixelStep; switch (e.GetAdjustedQuality(this.parentTrack.CurvePrecision)) { case QualityLevel.High: minPixelStep = 0.25f; break; default: case QualityLevel.Medium: minPixelStep = 0.5f; break; case QualityLevel.Low: minPixelStep = 1.0f; break; } float visibleMax = this.model.GetMaxValueInRange(beginUnitX, endUnitX); float visibleMin = this.model.GetMinValueInRange(beginUnitX, endUnitX); float visiblePixelHeight = this.ParentTrack.ConvertUnitsToPixels(Math.Abs(visibleMax - visibleMin)); if (visiblePixelHeight < 10.0f) minPixelStep *= 8.0f; else if (visiblePixelHeight < 20.0f) minPixelStep *= 4.0f; else if (visiblePixelHeight < 40.0f) minPixelStep *= 2.0f; else if (visiblePixelHeight < 70.0f) minPixelStep *= 1.5f; minPixelStep = Math.Min(minPixelStep, 4); float minUnitStep = this.ParentView.ConvertPixelsToUnits(minPixelStep); Rectangle rect = e.TargetRect; if (this.curveCacheDirty) { // Begin a little sooner, so interpolation / error checking can gather some data beginUnitX -= minUnitStep * 5.0f; // Determine sample points PointF[] curvePointsEnvMax = null; PointF[] curvePointsEnvMin = null; if (this.curveOpacity > 0.0f) { this.cacheCurveVertices = this.GetCurvePoints(rect, e.GetAdjustedQuality(this.parentTrack.CurvePrecision), this.model.GetValueAtX, minPixelStep, beginUnitX, endUnitX); } if (this.envelopeOpacity > 0.0f && !this.skipEnvelope) { float envelopeUnitRadius = this.ParentView.ConvertPixelsToUnits(EnvelopeBasePixelRadius); float minEnvelopeStepFactor; switch (e.GetAdjustedQuality(this.parentTrack.EnvelopePrecision)) { case QualityLevel.High: minEnvelopeStepFactor = 0.1f; break; default: case QualityLevel.Medium: minEnvelopeStepFactor = 0.5f; break; case QualityLevel.Low: minEnvelopeStepFactor = 1.0f; break; } float envelopePixelStep = minEnvelopeStepFactor * EnvelopeBasePixelRadius; float envelopeUnitStep = minEnvelopeStepFactor * envelopeUnitRadius; curvePointsEnvMax = this.GetCurvePoints( rect, e.GetAdjustedQuality(this.parentTrack.CurvePrecision), x => this.model.GetMaxValueInRange(x - envelopeUnitRadius, x + envelopeUnitRadius), envelopePixelStep, envelopeUnitStep * (int)(beginUnitX / envelopeUnitStep), envelopeUnitStep * ((int)(endUnitX / envelopeUnitStep) + 1)); curvePointsEnvMin = this.GetCurvePoints( rect, e.GetAdjustedQuality(this.parentTrack.CurvePrecision), x => this.model.GetMinValueInRange(x - envelopeUnitRadius, x + envelopeUnitRadius), envelopePixelStep, envelopeUnitStep * (int)(beginUnitX / envelopeUnitStep), envelopeUnitStep * ((int)(endUnitX / envelopeUnitStep) + 1)); if (curvePointsEnvMax == null || curvePointsEnvMin == null || curvePointsEnvMax.Length + curvePointsEnvMin.Length < 3) this.skipEnvelope = true; } if (curvePointsEnvMax != null && curvePointsEnvMin != null && curvePointsEnvMax.Length + curvePointsEnvMin.Length >= 3) { // Calculate the visible envelope polygon this.cacheEnvelopeVertices = new PointF[curvePointsEnvMax.Length + curvePointsEnvMin.Length]; for (int i = 0; i < curvePointsEnvMax.Length; i++) { this.cacheEnvelopeVertices[i] = curvePointsEnvMax[i]; } for (int i = 0; i < curvePointsEnvMin.Length; i++) { this.cacheEnvelopeVertices[curvePointsEnvMax.Length + i] = curvePointsEnvMin[curvePointsEnvMin.Length - i - 1]; } // Calculate the envelope and curve gradients if (this.cacheCurveVertices != null) { float varianceUnitRadius = this.ParentView.ConvertPixelsToUnits(0.5f); KeyValuePair<float,float>[] baseBlend = new KeyValuePair<float,float>[Math.Max(this.cacheCurveVertices.Length, 2)]; for (int i = 0; i < baseBlend.Length; i++) { float relativeX = (float)(this.cacheCurveVertices[(int)((float)i * (this.cacheCurveVertices.Length - 1) / (float)(baseBlend.Length - 1))].X - rect.X) / (float)rect.Width; float unitX = this.ParentView.GetUnitAtPos(rect.X + relativeX * rect.Width); float localOpacity = this.GetEnvelopeVisibility( this.model.GetMaxValueInRange(unitX - varianceUnitRadius, unitX + varianceUnitRadius) - this.model.GetMinValueInRange(unitX - varianceUnitRadius, unitX + varianceUnitRadius)); baseBlend[i] = new KeyValuePair<float,float>(relativeX, localOpacity); } this.cacheEnvelopeGradient = new LinearGradientBrush(rect, Color.Transparent, Color.Transparent, LinearGradientMode.Horizontal); this.cacheCurveGradient = new LinearGradientBrush(rect, Color.Transparent, Color.Transparent, LinearGradientMode.Horizontal); const int Samples = 21; const int SamplesHalf = Samples / 2; const int BlendSamplesPerChunk = 4; float highestOpacity = 0.0f; ColorBlend envelopeBlend = new ColorBlend(Math.Max(baseBlend.Length * BlendSamplesPerChunk / Samples, 2)); ColorBlend curveBlend = new ColorBlend(Math.Max(baseBlend.Length * BlendSamplesPerChunk / Samples, 2)); for (int i = 0; i < envelopeBlend.Colors.Length; i++) { int firstIndex = Math.Min(Math.Max(i * Samples / BlendSamplesPerChunk - SamplesHalf, 0), baseBlend.Length - 1); int lastIndex = Math.Min(Math.Max(i * Samples / BlendSamplesPerChunk + SamplesHalf, 0), baseBlend.Length - 1); float sum = 0.0f; for (int j = firstIndex; j <= lastIndex; j++) { sum += baseBlend[j].Value; } float localOpacity = sum / (float)(1 + lastIndex - firstIndex); highestOpacity = Math.Max(highestOpacity, localOpacity); envelopeBlend.Colors[i] = Color.FromArgb((int)(localOpacity * this.envelopeOpacity * 255.0f), this.baseColor); envelopeBlend.Positions[i] = baseBlend[firstIndex + (lastIndex - firstIndex) / 2].Key; curveBlend.Colors[i] = Color.FromArgb((int)((1.0f - localOpacity) * (1.0f - localOpacity) * this.curveOpacity * 255.0f), this.baseColor); curveBlend.Positions[i] = baseBlend[firstIndex + (lastIndex - firstIndex) / 2].Key; } if (highestOpacity <= 0.05f) { this.cacheEnvelopeGradient = null; this.cacheCurveGradient = null; this.skipEnvelope = true; } else { envelopeBlend.Positions[0] = 0.0f; envelopeBlend.Positions[envelopeBlend.Positions.Length - 1] = 1.0f; this.cacheEnvelopeGradient.InterpolationColors = envelopeBlend; curveBlend.Positions[0] = 0.0f; curveBlend.Positions[curveBlend.Positions.Length - 1] = 1.0f; this.cacheCurveGradient.InterpolationColors = curveBlend; } } } } // Draw the envelope area if (this.cacheEnvelopeGradient != null && this.cacheEnvelopeVertices != null && this.cacheEnvelopeVertices.Length >= 3) { e.Graphics.FillPolygon(this.cacheEnvelopeGradient, this.cacheEnvelopeVertices); } // Draw the graph if (this.cacheCurveVertices != null && this.cacheCurveVertices.Length >= 2) { Pen linePen; if (this.cacheCurveGradient != null) { linePen = new Pen(this.cacheCurveGradient); } else { linePen = new Pen(Color.FromArgb((int)(this.curveOpacity * 255.0f), this.baseColor)); } e.Graphics.DrawLines(linePen, this.cacheCurveVertices); } // Keep in mind that our cache is no valid again if (e.GetAdjustedQuality(this.parentTrack.CurvePrecision) == this.parentTrack.CurvePrecision && e.GetAdjustedQuality(this.parentTrack.EnvelopePrecision) == this.parentTrack.EnvelopePrecision) { this.curveCacheDirty = false; } }
protected internal virtual void OnPaint(TimelineViewTrackPaintEventArgs e) { Rectangle rect = e.TargetRect; // Draw curve switch (e.GetAdjustedQuality(this.parentTrack.CurveQuality)) { case QualityLevel.High: e.Graphics.SmoothingMode = SmoothingMode.HighQuality; break; default: case QualityLevel.Medium: case QualityLevel.Low: e.Graphics.SmoothingMode = SmoothingMode.HighSpeed; break; } float pixelWidth = this.ParentView.ConvertUnitsToPixels(this.model.EndTime - this.model.BeginTime); if (pixelWidth < 5) e.Graphics.SmoothingMode = SmoothingMode.HighSpeed; if (pixelWidth > 2) { this.OnPaintCurve(e); } e.Graphics.SmoothingMode = SmoothingMode.Default; // Draw overlay this.OnPaintBoundaries(e); }
protected virtual void OnPaintCurve(TimelineViewTrackPaintEventArgs e) { // Ignore e.BeginTime and e.EndTime for sampling, as we're heavily dependent on rounding errors, etc. while undersampling. Instead, always sample the whole curve. float beginUnitX = Math.Max(this.ParentView.UnitOriginOffset - this.ParentView.UnitScroll, this.model.BeginTime); float endUnitX = Math.Min(this.ParentView.UnitOriginOffset - this.ParentView.UnitScroll + this.ParentView.VisibleUnitWidth, this.model.EndTime); // Determine graph parameters float minPixelStep; switch (e.GetAdjustedQuality(this.parentTrack.CurvePrecision)) { case QualityLevel.High: minPixelStep = 0.25f; break; default: case QualityLevel.Medium: minPixelStep = 0.5f; break; case QualityLevel.Low: minPixelStep = 1.0f; break; } float visibleMax = this.model.GetMaxValueInRange(beginUnitX, endUnitX); float visibleMin = this.model.GetMinValueInRange(beginUnitX, endUnitX); float visiblePixelHeight = this.ParentTrack.ConvertUnitsToPixels(Math.Abs(visibleMax - visibleMin)); if (visiblePixelHeight < 10.0f) { minPixelStep *= 8.0f; } else if (visiblePixelHeight < 20.0f) { minPixelStep *= 4.0f; } else if (visiblePixelHeight < 40.0f) { minPixelStep *= 2.0f; } else if (visiblePixelHeight < 70.0f) { minPixelStep *= 1.5f; } minPixelStep = Math.Min(minPixelStep, 4); float minUnitStep = this.ParentView.ConvertPixelsToUnits(minPixelStep); Rectangle rect = e.TargetRect; if (this.curveCacheDirty) { // Begin a little sooner, so interpolation / error checking can gather some data beginUnitX -= minUnitStep * 5.0f; // Determine sample points PointF[] curvePointsEnvMax = null; PointF[] curvePointsEnvMin = null; if (this.curveOpacity > 0.0f) { this.cacheCurveVertices = this.GetCurvePoints(rect, e.GetAdjustedQuality(this.parentTrack.CurvePrecision), this.model.GetValueAtX, minPixelStep, beginUnitX, endUnitX); } if (this.envelopeOpacity > 0.0f && !this.skipEnvelope) { float envelopeUnitRadius = this.ParentView.ConvertPixelsToUnits(EnvelopeBasePixelRadius); float minEnvelopeStepFactor; switch (e.GetAdjustedQuality(this.parentTrack.EnvelopePrecision)) { case QualityLevel.High: minEnvelopeStepFactor = 0.1f; break; default: case QualityLevel.Medium: minEnvelopeStepFactor = 0.5f; break; case QualityLevel.Low: minEnvelopeStepFactor = 1.0f; break; } float envelopePixelStep = minEnvelopeStepFactor * EnvelopeBasePixelRadius; float envelopeUnitStep = minEnvelopeStepFactor * envelopeUnitRadius; curvePointsEnvMax = this.GetCurvePoints( rect, e.GetAdjustedQuality(this.parentTrack.CurvePrecision), x => this.model.GetMaxValueInRange(x - envelopeUnitRadius, x + envelopeUnitRadius), envelopePixelStep, envelopeUnitStep * (int)(beginUnitX / envelopeUnitStep), envelopeUnitStep * ((int)(endUnitX / envelopeUnitStep) + 1)); curvePointsEnvMin = this.GetCurvePoints( rect, e.GetAdjustedQuality(this.parentTrack.CurvePrecision), x => this.model.GetMinValueInRange(x - envelopeUnitRadius, x + envelopeUnitRadius), envelopePixelStep, envelopeUnitStep * (int)(beginUnitX / envelopeUnitStep), envelopeUnitStep * ((int)(endUnitX / envelopeUnitStep) + 1)); if (curvePointsEnvMax == null || curvePointsEnvMin == null || curvePointsEnvMax.Length + curvePointsEnvMin.Length < 3) { this.skipEnvelope = true; } } if (curvePointsEnvMax != null && curvePointsEnvMin != null && curvePointsEnvMax.Length + curvePointsEnvMin.Length >= 3) { // Calculate the visible envelope polygon this.cacheEnvelopeVertices = new PointF[curvePointsEnvMax.Length + curvePointsEnvMin.Length]; for (int i = 0; i < curvePointsEnvMax.Length; i++) { this.cacheEnvelopeVertices[i] = curvePointsEnvMax[i]; } for (int i = 0; i < curvePointsEnvMin.Length; i++) { this.cacheEnvelopeVertices[curvePointsEnvMax.Length + i] = curvePointsEnvMin[curvePointsEnvMin.Length - i - 1]; } // Calculate the envelope and curve gradients if (this.cacheCurveVertices != null) { float varianceUnitRadius = this.ParentView.ConvertPixelsToUnits(0.5f); KeyValuePair <float, float>[] baseBlend = new KeyValuePair <float, float> [Math.Max(this.cacheCurveVertices.Length, 2)]; for (int i = 0; i < baseBlend.Length; i++) { float relativeX = (float)(this.cacheCurveVertices[(int)((float)i * (this.cacheCurveVertices.Length - 1) / (float)(baseBlend.Length - 1))].X - rect.X) / (float)rect.Width; float unitX = this.ParentView.GetUnitAtPos(rect.X + relativeX * rect.Width); float localOpacity = this.GetEnvelopeVisibility( this.model.GetMaxValueInRange(unitX - varianceUnitRadius, unitX + varianceUnitRadius) - this.model.GetMinValueInRange(unitX - varianceUnitRadius, unitX + varianceUnitRadius)); baseBlend[i] = new KeyValuePair <float, float>(relativeX, localOpacity); } this.cacheEnvelopeGradient = new LinearGradientBrush(rect, Color.Transparent, Color.Transparent, LinearGradientMode.Horizontal); this.cacheCurveGradient = new LinearGradientBrush(rect, Color.Transparent, Color.Transparent, LinearGradientMode.Horizontal); const int Samples = 21; const int SamplesHalf = Samples / 2; const int BlendSamplesPerChunk = 4; float highestOpacity = 0.0f; ColorBlend envelopeBlend = new ColorBlend(Math.Max(baseBlend.Length * BlendSamplesPerChunk / Samples, 2)); ColorBlend curveBlend = new ColorBlend(Math.Max(baseBlend.Length * BlendSamplesPerChunk / Samples, 2)); for (int i = 0; i < envelopeBlend.Colors.Length; i++) { int firstIndex = Math.Min(Math.Max(i * Samples / BlendSamplesPerChunk - SamplesHalf, 0), baseBlend.Length - 1); int lastIndex = Math.Min(Math.Max(i * Samples / BlendSamplesPerChunk + SamplesHalf, 0), baseBlend.Length - 1); float sum = 0.0f; for (int j = firstIndex; j <= lastIndex; j++) { sum += baseBlend[j].Value; } float localOpacity = sum / (float)(1 + lastIndex - firstIndex); highestOpacity = Math.Max(highestOpacity, localOpacity); envelopeBlend.Colors[i] = Color.FromArgb((int)(localOpacity * this.envelopeOpacity * 255.0f), this.baseColor); envelopeBlend.Positions[i] = baseBlend[firstIndex + (lastIndex - firstIndex) / 2].Key; curveBlend.Colors[i] = Color.FromArgb((int)((1.0f - localOpacity) * (1.0f - localOpacity) * this.curveOpacity * 255.0f), this.baseColor); curveBlend.Positions[i] = baseBlend[firstIndex + (lastIndex - firstIndex) / 2].Key; } if (highestOpacity <= 0.05f) { this.cacheEnvelopeGradient = null; this.cacheCurveGradient = null; this.skipEnvelope = true; } else { envelopeBlend.Positions[0] = 0.0f; envelopeBlend.Positions[envelopeBlend.Positions.Length - 1] = 1.0f; this.cacheEnvelopeGradient.InterpolationColors = envelopeBlend; curveBlend.Positions[0] = 0.0f; curveBlend.Positions[curveBlend.Positions.Length - 1] = 1.0f; this.cacheCurveGradient.InterpolationColors = curveBlend; } } } } // Draw the envelope area if (this.cacheEnvelopeGradient != null && this.cacheEnvelopeVertices != null && this.cacheEnvelopeVertices.Length >= 3) { e.Graphics.FillPolygon(this.cacheEnvelopeGradient, this.cacheEnvelopeVertices); } // Draw the graph if (this.cacheCurveVertices != null && this.cacheCurveVertices.Length >= 2) { Pen linePen; if (this.cacheCurveGradient != null) { linePen = new Pen(this.cacheCurveGradient); } else { linePen = new Pen(Color.FromArgb((int)(this.curveOpacity * 255.0f), this.baseColor)); } e.Graphics.DrawLines(linePen, this.cacheCurveVertices); } // Keep in mind that our cache is no valid again if (e.GetAdjustedQuality(this.parentTrack.CurvePrecision) == this.parentTrack.CurvePrecision && e.GetAdjustedQuality(this.parentTrack.EnvelopePrecision) == this.parentTrack.EnvelopePrecision) { this.curveCacheDirty = false; } }