Infinite scrolling gradient background

328 views Asked by At

I've searched and not found this question answered on SO, so I'm asking it here directly.

Does anyone have a clean method to create an infinitely scrolling gradient background? (the gradient shifts, so you can follow the colors from one side/corner to the other)

I've done this in VB like 15 years ago, but it's been so long since I touched VB it's all greek to me.

Assuming someone has done something like this in C# before-- Think demo scene kind of animation.

The VB code snippet is from a working form background I did many years ago, it doesn't scroll so much as bounce back and forth from edge to edge.

Private Sub picCanvas_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Panel1.Paint
    Dim rect As New Rectangle(-10, -10, Me.ClientSize.Width + 20, Me.ClientSize.Height + 20)
    Dim halfw As Integer = CType(Me.ClientSize.Width, Integer)
    Dim br As New LinearGradientBrush(New Point(-120, 500), New Point(Me.ClientSize.Width + 120, 0), Color.Red, Color.Blue)
    Dim color_blend As New ColorBlend
    color_blend.Colors = New Color() {Color.Black, Color.Purple, Color.Teal, Color.Purple, Color.Black}
    m_Theta += m_Delta
    color_blend.Positions = New Single() {0, 0.01, m_Theta, 0.99, 1}
    br.InterpolationColors = color_blend
    e.Graphics.FillRectangle(br, rect)
    br.Dispose()
    If (m_Theta > 0.75) Or (m_Theta < 0.25) Then m_Delta = -m_Delta
End Sub

I would greatly appreciate any help in getting this kind of thing to work in WinForms using only GDI and brushes, no XML or anything please ^^/

1

There are 1 answers

3
Jimi On BEST ANSWER

I'm not exactly sure this is what you're trying to do, anyway, from the semi-pseudo code presented here, it appears you want to shift the position of a gradient fill along an axis.

It appears the fill is meant to be inclined, so I've added means to determine a rotation angle.
I've kept the LinearGradientBrush to generate the blended fill, though the combination of GraphicsPath and PathGradientBrush is probably more flexible.

To move the gradient fill, I've used a standard System.Windows.Forms.Timer. It's used to translate the fill, incrementing a value that is then set to the translation components of a Matrix in the OnPaint override of a double-buffered Form used as canvas (of course, you can use a PictureBox instead)

The Matrix is also used to rotate the fill, in case it's needed

The Timer's Tick handler also verifies other conditions (bool Fields), that can be used to alter the fill:

  • useThetaShift enables semi-dynamic motions of the blend intervals (the Position Property)
  • useTriangular enables and alternate blending feature, generated by the SetBlendTriangularShape() method, which considers only the starting and ending Colors of the LinearGradientBrush and defines the center point of the Colors' fall-off

The sample Form shown here can also be set to auto-scroll, the blending is extended to the DisplayRectangle
The blend is animated also when a modal Dialog is shown (you mentioned an About Window...)

internal class SomeForm : Form {
    private System.Windows.Forms.Timer gradientTimer = null;

    public SomeForm() {
        InitializeComponent();
        if (components is null) components = new Container();
        ResizeRedraw = true;

        startColor = blendColors[0];
        meanColor = blendColors[1];
        endColor = blendColors[blendColors.Length - 1];
        gradientTimer = new System.Windows.Forms.Timer(components) { Interval = 100 };
        gradientTimer.Tick += GradientTimer_Tick;
        gradientTimer.Start();
    }

    float theta = .0f;
    float delta = .005f;
    float tringularShift = .25f;
    float tringularShiftDelta = .015f;
    float speed = 7.5f;
    float rotation = 0f;

    private Color[] blendColors = new[]{ 
        Color.Black, Color.Purple, Color.Teal, Color.Purple, Color.Black
    };
    Color startColor = Color.Empty;
    Color endColor = Color.Empty;
    Color meanColor = Color.Empty;
    PointF translateMx = PointF.Empty;
    bool useThetaShift = false;
    bool useTriangular = false;

    private void GradientTimer_Tick(object sender, EventArgs e)
    {
        if (useTriangular) {
            tringularShift += tringularShiftDelta;
            tringularShift = Math.Max(Math.Min(tringularShift, 1.0f), .35f);
            if ((tringularShift >= 1.0f) | (tringularShift <= .35f)) tringularShiftDelta*= -1;
        }

        if (useThetaShift) {
            theta += delta;
            theta = Math.Max(Math.Min(theta, .15f), 0f);
            if ((theta >= .15f) | (theta <= 0f)) delta*= -1;
        }

        translateMx = PointF.Add(translateMx, new SizeF(speed, speed));
        if (Math.Abs(translateMx.X) >= short.MaxValue) translateMx = PointF.Empty;
        Invalidate();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        var display = DisplayRectangle;
        using (var mx = new Matrix(1f, 0f, 0f, 1f, translateMx.X, translateMx.Y))
        using (var brush = new LinearGradientBrush(display, startColor, endColor, rotation)) {
            var colorBlend = new ColorBlend(blendColors.Length) {
                Colors = blendColors,
                Positions = new float[] { .0f, .25f + theta, .5f + theta, .75f + theta, 1.0f },
            };
            brush.InterpolationColors = colorBlend;
            mx.Rotate(rotation);
            brush.Transform = mx;
            if (useTriangular) brush.SetBlendTriangularShape(.5f, tringularShift);
            e.Graphics.FillRectangle(brush, display);
        }
        base.OnPaint(e);
    }

    protected override void OnFormClosing(FormClosingEventArgs e) {
        // Move to OnFormClosed() if this action can be canceled
        gradientTimer.Stop();
        base.OnFormClosing(e);
    }
}

I cannot post an animation here, because of the size. You can see how it work directly on Imgur:
Animated LinearGradientPath