1

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 ^^/

Jimi
  • 29,621
  • 8
  • 43
  • 61
MisterNad
  • 432
  • 3
  • 14
  • Can you explain what kind of *effect* you want to achieve? This looks like a semi-diagonal gradient that just fills the client area, so it's not clear what *infinite scrolling* means; if you actually want the blend to adapt to a scrolling surface, you have to use DisplayRectangle instead of ClientRectangle and invalidate in `OnScroll()` -- Are `m_Theta` and `m_Delta` meant to make the mid teal section *fluctuate*? What's `halfw` for? -- Is the question something like: *can you translate this to C#*? -- BTW, you don't set the location of Controls in the `Paint` handler – Jimi Dec 05 '22 at 21:11
  • Wouldn't any infinitely scrolling gradient with a vertical component would just be a solid color? – NetMage Dec 05 '22 at 22:03
  • @Jimi my snippet was more an example- I was trying to get a continuous 1-direction scroll of all colours but the best I could get to work seamlessly was just bouncing back and forth. The m_Theta was just the position that went up and down. -- wanting more of a picture-box or panel with a gradient that's always flowing from left-to-right, endlessly. It *will* be a window background in OnPaint for an 'About' window at some point, but for now just proof-of-concept – MisterNad Dec 05 '22 at 23:11

1 Answers1

1

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

Jimi
  • 29,621
  • 8
  • 43
  • 61
  • You freaking NAILED IT, I love you man, thank you SO VERY MUCH <3 – MisterNad Dec 14 '22 at 04:47
  • PS; Why did you remove the "C#" and "(winforms)" from the title? It would've made this solution much easier to find via search-engines wouldn't it? This is sincerely the only solution I've found readily available on any site that my google-fu has produced.. All others refer to XML, Unity and/or XNA, or CSS for webpages. Clearly indicating this was/is GDI/winforms seems like a necessity for search results, to me, anyway. Either way, thank you so very much for the solution, it's beautiful my friend ^^/ – MisterNad Dec 15 '22 at 13:49
  • I've removed those parts because the general advice it use tags to specify these details, avoiding *Title Pollution*. Of course you can change the title to whatever else, if you prefer a different *layout*, my edit is not written in stone -- You're welcome, BTW :) – Jimi Dec 15 '22 at 15:26