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 OnPaintBoundaries(TimelineViewTrackPaintEventArgs e) { Rectangle rect = e.TargetRect; float beginX = this.ParentView.GetPosAtUnit(this.model.BeginTime); float endX = this.ParentView.GetPosAtUnit(this.model.EndTime); // Draw boundaries { Pen boundaryPen = new Pen(Color.FromArgb(128, this.baseColor)); boundaryPen.DashStyle = DashStyle.Dash; e.Graphics.DrawLine(boundaryPen, beginX, rect.Top, beginX, rect.Bottom); e.Graphics.DrawLine(boundaryPen, endX, rect.Top, endX, rect.Bottom); } }
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 internal override void OnPaintRightSidebar(TimelineViewTrackPaintEventArgs e) { base.OnPaintRightSidebar(e); this.DrawLegend(e.Graphics, e.Renderer, e.TargetRect); }
protected internal override void OnPaintOverlay(TimelineViewTrackPaintEventArgs e) { base.OnPaintOverlay(e); // Display mouseover data / effects if (this.ParentView.MouseoverContent && this.ParentView.ActiveMouseAction == TimelineView.MouseAction.None) { float unitEnvelopeRadius = this.ParentView.ConvertPixelsToUnits(TimelineViewGraph.EnvelopeBasePixelRadius); float mouseoverTime = this.ParentView.MouseoverTime; float mouseoverPixels = this.ParentView.GetPosAtUnit(mouseoverTime); // Accumulate graph value text information Font textFont = e.Renderer.FontSmall; StringFormat textFormat = new StringFormat(); textFormat.FormatFlags |= StringFormatFlags.NoWrap; textFormat.Trimming = StringTrimming.EllipsisCharacter; Rectangle totalTextRect = Rectangle.Empty; List<GraphValueTextInfo> textInfoList = new List<GraphValueTextInfo>(); { int textYAdv = 0; float visibleTimeSpan = this.ParentView.VisibleUnitWidth; float visibleValueSpan = Math.Abs(this.verticalUnitTop - this.verticalUnitBottom); int timeDecimals = Math.Max(0, -(int)Math.Log10(visibleTimeSpan) + 2); int valueDecimals = Math.Max(0, -(int)Math.Log10(visibleValueSpan) + 2); // Time text { GraphValueTextInfo textInfo; textInfo.Text = string.Format( System.Globalization.CultureInfo.InvariantCulture, "{0:F" + timeDecimals + "}", mouseoverTime); textInfo.TargetRect = new Rectangle((int)mouseoverPixels + 2, e.TargetRect.Y + textYAdv + 1, MaxGraphValueTextWidth - 2, e.TargetRect.Height - textYAdv); SizeF textSize = e.Graphics.MeasureString(textInfo.Text, textFont, textInfo.TargetRect.Size, textFormat); textInfo.ActualRect = new Rectangle(textInfo.TargetRect.X, textInfo.TargetRect.Y, (int)textSize.Width, (int)textSize.Height); textInfo.Color = e.Renderer.ColorText; if (totalTextRect.IsEmpty) { totalTextRect = textInfo.ActualRect; } else { totalTextRect.X = Math.Min(totalTextRect.X, textInfo.ActualRect.X); totalTextRect.Y = Math.Min(totalTextRect.Y, textInfo.ActualRect.Y); totalTextRect.Width = Math.Max(totalTextRect.Width, textInfo.ActualRect.Right - totalTextRect.Left); totalTextRect.Height = Math.Max(totalTextRect.Height, textInfo.ActualRect.Bottom - totalTextRect.Top); } textInfoList.Add(textInfo); textYAdv += (int)textSize.Height; } // Graph texts foreach (TimelineViewGraph graph in this.graphList) { GraphAreaInfo info; if (!this.graphDisplayedInfo.TryGetValue(graph, out info)) continue; GraphValueTextInfo textInfo; if (info.EnvelopeVisibility < 0.25f) { textInfo.Text = string.Format( System.Globalization.CultureInfo.InvariantCulture, "{0:F" + valueDecimals + "}", Math.Round(info.AverageValue, 2)); } else { textInfo.Text = string.Format( System.Globalization.CultureInfo.InvariantCulture, "[{0:F" + valueDecimals + "}, {1:F" + valueDecimals + "}]", Math.Round(info.MinValue, 2), Math.Round(info.MaxValue, 2)); } textInfo.TargetRect = new Rectangle((int)mouseoverPixels + 2, e.TargetRect.Y + textYAdv + 1, MaxGraphValueTextWidth - 2, e.TargetRect.Height - textYAdv); SizeF textSize = e.Graphics.MeasureString(textInfo.Text, textFont, textInfo.TargetRect.Size, textFormat); textInfo.ActualRect = new Rectangle(textInfo.TargetRect.X, textInfo.TargetRect.Y, (int)textSize.Width, (int)textSize.Height); textInfo.Color = graph.BaseColor.ScaleBrightness(0.75f); if (totalTextRect.IsEmpty) { totalTextRect = textInfo.ActualRect; } else { totalTextRect.X = Math.Min(totalTextRect.X, textInfo.ActualRect.X); totalTextRect.Y = Math.Min(totalTextRect.Y, textInfo.ActualRect.Y); totalTextRect.Width = Math.Max(totalTextRect.Width, textInfo.ActualRect.Right - totalTextRect.Left); totalTextRect.Height = Math.Max(totalTextRect.Height, textInfo.ActualRect.Bottom - totalTextRect.Top); } textInfoList.Add(textInfo); textYAdv += (int)textSize.Height; } } // Draw the texts background rect if (!totalTextRect.IsEmpty) { e.Graphics.FillRectangle( new SolidBrush(e.Renderer.ColorLightBackground.ScaleAlpha(0.5f)), totalTextRect.X, totalTextRect.Y, totalTextRect.Width + 2, totalTextRect.Height + 2); } // Draw graph mouseover visualizations foreach (TimelineViewGraph graph in this.graphList) { GraphAreaInfo info; if (!this.graphDisplayedInfo.TryGetValue(graph, out info)) continue; // Determine mouseover data float averagePixels = e.TargetRect.Y + this.GetPosAtUnit(info.AverageValue); float minEnvelopePixels = Math.Min(e.TargetRect.Y + this.GetPosAtUnit(info.MinValue), e.TargetRect.Bottom - 2); float maxEnvelopePixels = Math.Max(e.TargetRect.Y + this.GetPosAtUnit(info.MaxValue), e.TargetRect.Top + 1); Color valueBaseColor = graph.BaseColor.ScaleBrightness(0.75f); // Draw value range if (info.EnvelopeVisibility > 0.05f) { SolidBrush brush = new SolidBrush(valueBaseColor.ScaleAlpha(info.EnvelopeVisibility)); e.Graphics.FillRectangle(brush, mouseoverPixels - 3, minEnvelopePixels, 7, 1); e.Graphics.FillRectangle(brush, mouseoverPixels - 3, maxEnvelopePixels, 7, 1); e.Graphics.FillRectangle(brush, mouseoverPixels, maxEnvelopePixels, 1, minEnvelopePixels - maxEnvelopePixels); } // Draw average / exact value knob if (info.EnvelopeVisibility < 0.95f) { e.Graphics.SmoothingMode = SmoothingMode.HighQuality; e.Graphics.FillEllipse( new SolidBrush(valueBaseColor.ScaleAlpha(1.0f - info.EnvelopeVisibility)), mouseoverPixels - 2.5f, averagePixels - 2.5f, 5, 5); e.Graphics.SmoothingMode = SmoothingMode.Default; } } // Draw value information texts foreach (GraphValueTextInfo textInfo in textInfoList) { e.Graphics.DrawString(textInfo.Text, textFont, new SolidBrush(textInfo.Color), textInfo.TargetRect, textFormat); } } }
protected internal override void OnPaintLeftSidebar(TimelineViewTrackPaintEventArgs e) { base.OnPaintLeftSidebar(e); this.DrawRuler(e.Graphics, e.Renderer, e.TargetRect, true); }
protected internal override void OnPaint(TimelineViewTrackPaintEventArgs e) { base.OnPaint(e); Rectangle rect = e.TargetRect; // Draw extended ruler markings in the background { Brush bigLineBrush = new SolidBrush(e.Renderer.ColorRulerMarkMajor.ScaleAlpha(0.25f)); Brush medLineBrush = new SolidBrush(e.Renderer.ColorRulerMarkRegular.ScaleAlpha(0.25f)); Brush minLineBrush = new SolidBrush(e.Renderer.ColorRulerMarkMinor.ScaleAlpha(0.25f)); this.drawBufferBigRuler.Clear(); this.drawBufferMedRuler.Clear(); this.drawBufferMinRuler.Clear(); // Vertical ruler marks foreach (TimelineViewRulerMark mark in this.GetVisibleRulerMarks()) { if (mark.PixelValue < e.Graphics.ClipBounds.Top) continue; if (mark.PixelValue > e.Graphics.ClipBounds.Bottom) break; Rectangle lineRect = new Rectangle((int)rect.X, (int)mark.PixelValue, (int)rect.Width, 1); switch (mark.Weight) { case TimelineViewRulerMarkWeight.Major: this.drawBufferBigRuler.Add(lineRect); break; default: case TimelineViewRulerMarkWeight.Regular: this.drawBufferMedRuler.Add(lineRect); break; case TimelineViewRulerMarkWeight.Minor: this.drawBufferMinRuler.Add(lineRect); break; } } if (this.drawBufferBigRuler.Count > 0) e.Graphics.FillRectangles(bigLineBrush, this.drawBufferBigRuler.ToArray()); if (this.drawBufferMedRuler.Count > 0) e.Graphics.FillRectangles(medLineBrush, this.drawBufferMedRuler.ToArray()); if (this.drawBufferMinRuler.Count > 0) e.Graphics.FillRectangles(minLineBrush, this.drawBufferMinRuler.ToArray()); } // Draw the graphs { float beginUnitX = Math.Max(this.ParentView.UnitOriginOffset - this.ParentView.UnitScroll, this.ContentBeginTime); float endUnitX = Math.Min(this.ParentView.UnitOriginOffset - this.ParentView.UnitScroll + this.ParentView.VisibleUnitWidth, this.ContentEndTime); beginUnitX = Math.Max(Math.Max(beginUnitX, this.ParentView.GetUnitAtPos(e.Graphics.ClipBounds.Left - 1)), e.BeginTime); endUnitX = Math.Min(Math.Min(endUnitX, this.ParentView.GetUnitAtPos(e.Graphics.ClipBounds.Right)), e.EndTime); if (beginUnitX <= endUnitX) { foreach (TimelineViewGraph graph in this.graphList) { graph.OnPaint(new TimelineViewTrackPaintEventArgs(this, e.Graphics, e.QualityHint, rect, beginUnitX, endUnitX)); } } } // Draw top and bottom borders e.Graphics.DrawLine(new Pen(e.Renderer.ColorVeryDarkBackground), rect.Left, rect.Top, rect.Right, rect.Top); e.Graphics.DrawLine(new Pen(e.Renderer.ColorVeryDarkBackground), rect.Left, rect.Bottom - 1, rect.Right, rect.Bottom - 1); }
protected internal virtual void OnPaintOverlay(TimelineViewTrackPaintEventArgs e) { }
protected internal virtual void OnPaintRightSidebar(TimelineViewTrackPaintEventArgs 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; } }