[Chartjs]-Render Chart.js on backend side ASP.NET

2👍

The below is some code I’ve used in a similar situation, which takes some html and produces an image of it (cropping whitespace). I’m not sure when in the page lifecycle Chart.js draws it’s charts however so this may not work. You could adapt it to load an actual page rather than passing in HTML.

Not pretty, not platform agnostic, and could have varying results depending on versions of IE etc. (especially considering Chart.js needs polyfills on IE <= 8). But it worked for the situation for which I needed it.

public class HtmlToImageConverter
{
    public string Html { get; set; }
    public Bitmap Image { get; set; }

    public HtmlToImageConverter(string html)
    {
        this.Html = html;
    }

    public Bitmap Render()
    {
        var thread = new Thread(GenerateInternal);
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
        thread.Join();
        return Image;
    }

    private void GenerateInternal()
    {
        var webBrowser = new WebBrowser
        {
            ScrollBarsEnabled = false, 
            DocumentText = this.Html,
            ClientSize = new Size(3000, 3000)
        };

        webBrowser.DocumentCompleted += WebBrowser_DocumentCompleted;
        while (webBrowser.ReadyState != WebBrowserReadyState.Complete) Application.DoEvents();
        webBrowser.Dispose();
    }

    private void WebBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
    {
        var webBrowser = (WebBrowser)sender;

        this.Image = new Bitmap(webBrowser.Bounds.Width, webBrowser.Bounds.Height);
        webBrowser.BringToFront();
        webBrowser.DrawToBitmap(Image, webBrowser.Bounds);

        this.Image = AutoCrop(this.Image);
    }

    private static byte[][] GetRgb(Bitmap bmp)
    {
        var bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
        var ptr = bmpData.Scan0;
        var numPixels = bmp.Width * bmp.Height;
        var numBytes = bmpData.Stride*bmp.Height;
        var padding = bmpData.Stride - bmp.Width*3;
        var i = 0;
        var ct = 1;

        var r = new byte[numPixels];
        var g = new byte[numPixels];
        var b = new byte[numPixels];
        var rgb = new byte[numBytes];

        Marshal.Copy(ptr, rgb, 0, numBytes);

        for (var x = 0; x < numBytes - 3; x += 3)
        {
            if (x == (bmpData.Stride*ct - padding))
            {
                x += padding;
                ct++;
            }

            r[i] = rgb[x];
            g[i] = rgb[x + 1];
            b[i] = rgb[x + 2]; i++;
        }

        bmp.UnlockBits(bmpData);
        return new[] { r, g, b };
    }

    private static Bitmap AutoCrop(Bitmap bmp)
    {
        //Get an array containing the R,G,B components of each pixel
        var pixels = GetRgb(bmp);

        var h = bmp.Height - 1;
        var w = bmp.Width;
        var top = 0;
        var bottom = h;
        var left = bmp.Width;
        var right = 0;
        var white = 0;

        const int tolerance = 95;

        var prevColor = false;
        for (var i = 0; i < pixels[0].Length; i++)
        {
            int x = (i % (w)), y = (int)(Math.Floor((decimal)(i / w)));
            const int tol = 255 * tolerance / 100;
            if (pixels[0][i] >= tol && pixels[1][i] >= tol && pixels[2][i] >= tol)
            {
                white++;
                right = (x > right && white == 1) ? x : right;
            }
            else
            {
                left = (x < left && white >= 1) ? x : left;
                right = (x == w - 1 && white == 0) ? w - 1 : right;
                white = 0;
            }

            if (white == w)
            {
                top = (y - top < 3) ? y : top;
                bottom = (prevColor && x == w - 1 && y > top + 1) ? y : bottom;
            }

            left = (x == 0 && white == 0) ? 0 : left;
            bottom = (y == h && x == w - 1 && white != w && prevColor) ? h + 1 : bottom;

            if (x == w - 1)
            {
                prevColor = (white < w);
                white = 0;
            }
        }

        right = (right == 0) ? w : right;
        left = (left == w) ? 0 : left;

        //Crop the image
        if (bottom - top > 0)
        {
            return bmp.Clone(new Rectangle(left, top, right - left + 1, bottom - top), bmp.PixelFormat);
        }

        return bmp;
    }
}

1👍

You might want to look at Puppeteer (https://pptr.dev/), which lets you access browser features programmatically, from a server. I can also heavily recommend Browserless (https://www.browserless.io/), which builds on Puppeteer to provide even more feature.

Both are open source, Browserless.io offers a hosted option too. The /screenshot API endpoint might be what you are looking for: https://docs.browserless.io/docs/screenshot.html. It takes a URL as input and returns an image of the page.

If you have the HTML but no URL to a site, you can also pass the HTML content encoded as base64 in the URL parameter, for instance:

data:text/html;charset=UTF-8;base64,dGVzdAo=

Leave a comment