How to render HTML to raster image file

In this article, we'll look at examples of how we can create a snapshots of web pages using the Aspose.HTML library. It should be noted that basic algorithm generates sequence of images likes printed pages. Also, please note, that currently we have a limited support of JavaScript language and the result can be different from popular browser’s snapshots.

We need to make some steps to get an HTML document: create a Console Application project, add Aspose.HTML library, declare field of HTMLDocument type, generate (or load) HTML content.

Let's start with the simple example - getting a JPEG image with the default rendering settings. To accomplish this, we must take the following steps:

These steps are shown below:


private static void Main()
{
    const string outputImgFile = @"C:\aspose\files\demo.jpg";
    _document = new HTMLDocument("http://asposedemo20170904120448.azurewebsites.net/home/RasterImageDemo");           
    RenderSimpleExample(outputImgFile);
}
private static void RenderSimpleExample(string outputImgFile)
{
    var imgOptions = new ImageRenderingOptions
    {
        Format = ImageFormat.Jpeg,
        PageSetup =
        {
            AnyPage  = new Page(
                new Size(Unit.FromPixels(1366), Unit.FromPixels(768)),
                new Margin(0))                    
        }               
    };
    var imgDevice = new ImageDevice(imgOptions, outputImgFile);
    var renderer = new HtmlRenderer();
    Console.WriteLine("Render JPG file started...");
    renderer.Render(imgDevice, _document);
    Console.WriteLine("Render JPG file finished.");
}    

This is straightforward example with minimal settings. The rendering process and aspects of the produced image can be configured using the configuration properties shown below.

Name Description
Format Sets or gets ImageFormat. Now we supporting JPEG, PNG, BMP, GIF, TIFF file formats. By default this property is set to Png.
Compression Sets or gets Tagged Image File Format (TIFF) ImageRenderingOptions .
HorizontalResolution Sets or gets horizontal resolution, in pixels per inch. By default this property equals to 96 dpi.
VerticalResolution Sets or gets vertical resolution, in pixels per inch. By default this property equals to 96 dpi.
PageSetup Gets a page setup object is used for configuration output page-set.

It's great, but we think it's not enough. Assume we want save images in the cloud storage, what do we need to do? The ImageDevice class allows to use custom stream provider, so let's create one and customize it for DropBox service. Our stream provider must be implement an ICreateStreamProvider interface.


    public class DropboxStreamProvider : ICreateStreamProvider
    {
        public void Dispose()
        {
            //TODO: Dispose resources 
        }
    
        public Stream GetStream(string name, string extension)
        {
            //TODO: return new instance of Stream associated with some filename and extension.
        }
    
        public Stream GetStream(string name, string extension, int page)
        {
            //TODO: return new instance of Stream associated with some filename, extension and page
        }
    
        public void ReleaseStream(Stream stream)
        {
            //TODO: release stream
        }
    }

This interface has two methods: GetStream and ReleaseStream. The first method is called whenever the rendering engine need an instance of Stream to write data, and the second method is called when the data is already written. For implementation of custom providers we will use a MemoryStream object. This means that we will create a new MemoryStream object in GetStream and resend the data to Dropbox Service in ReleaseStream. Also we will use a Dropbox.NET SDK. To use the Dropbox API, you'll need to register a new app in the App Console. In this example we use "App folder" permission type (access to a single folder created specifically for your app).

Since the MemoryStream object does not store filename we also need to store the filename (extension, page number) to write file with corresponding filename to Dropbox. Let's add to our DropboxStreamProvider class fields for storing this information.


    public class DropboxStreamProvider : ICreateStreamProvider
    {
        private string _filename;
        private string _extension;
        private int _page;
        private readonly DropboxClient _dropboxClient;
        public DropboxStreamProvider(string oauth2AccessToken)
        {            
            _dropboxClient = new DropboxClient(oauth2AccessToken);
        }
        public void Dispose()
        {            
            _dropboxClient.Dispose();
        }
        ...
    }

At this point we also initialized Dropbox API сlient and dispose it in void Dispose() method. The implementation of methods GetStream is pretty straightforward. The name parameter got the value from the last segment of DocumentURI property, and the extension parameter got the value from the rendering options.

When the method ReleaseStream is called, it receive a stream with data. All we need is to move the current stream position to the start and call the method UploadAsync of Dropbox Api.


    public Stream GetStream(string name, string extension)
    {
        _filename = name;
        _extension = extension;
        return new MemoryStream();
    }
    public Stream GetStream(string name, string extension, int page)
    {
        _filename = name;
        _extension = extension;
        _page = page;
        return new MemoryStream();
    }
    public void ReleaseStream(Stream stream)
    {
        stream.Position = 0;
        _dropboxClient.Files.UploadAsync($"/{_filename}-{_page}{_extension}", WriteMode.Overwrite.Instance, body: stream).GetAwaiter().GetResult();
        stream.Dispose();
    }

If you want to store data with other filename, you can pass a new filename e.g. in constructor and ignore the name parameter in GetStream. This will be shown in the example of working with Google Drive.


public class GoogleStreamProvider : ICreateStreamProvider
    {
        readonly string[] _scopes = { DriveService.Scope.Drive,
            DriveService.Scope.DriveFile};
        private const string ApplicationName = "Aspose Demo";
        private readonly DriveService _service;
        private string _filename;
        private string _extension;
        private int _page;
        public GoogleStreamProvider(string filename, string clientId, string clientSecret)
        {
            var secrets = new ClientSecrets
            {
                ClientId = clientId,
                ClientSecret = clientSecret
            };
            var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(secrets, _scopes, "user",
                CancellationToken.None).Result;
            // Create Drive API service.
            _service = new DriveService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = ApplicationName,
            });
            _filename = filename;
        }
        public void Dispose()
        {
             _service.Dispose();            
        }
        public Stream GetStream(string name, string extension)
        {
            if (string.IsNullOrWhiteSpace(_filename)) _filename = name;
            _extension = extension;
            return new MemoryStream();
        }
        public Stream GetStream(string name, string extension, int page)
        {
            if (string.IsNullOrWhiteSpace(_filename)) _filename = name;
            _extension = extension;
            _page = page;
            return new MemoryStream();
        }
        public void ReleaseStream(Stream stream)
        {
            var fileMetadata = new Google.Apis.Drive.v3.Data.File()
            {
                Name = $"{_filename}-{_page}.{_extension}"
            };
            var request = _service.Files.Create(
                fileMetadata, stream, "image/jpeg");
            request.Fields = "id";
            request.Upload();
            var file = request.ResponseBody;
            Trace.WriteLine("File ID: " + file.Id);
        }
    }        
    

To write files in conventional file system we can use an instance of FileStreamProvider. As shown below we can set a directory and a name parameter.


        private static void RenderFileExample()
        {
            var fileStream = new FileCreateStreamProvider(@"C:\aspose\files\demo2", "test");
            var imgOptions = new ImageRenderingOptions
            {
                Format = ImageFormat.Jpeg,
                PageSetup =
                {
                    AnyPage  = new Page(
                        new Size(Unit.FromPixels(1366), Unit.FromPixels(768)),
                        new Margin(0)
                    )
                }
            };
            var imgDevice = new ImageDevice(imgOptions, fileStream);
            var renderer = new HtmlRenderer();
            Console.WriteLine("Render JPG file started...");
            renderer.Render(imgDevice, _document);
            Console.WriteLine("Render JPG file finished.");
        }

Unfortunately, the rendering algorithm splits source page into sections and if you want to write whole page in one file then you must merge they manually.

One way to solve this problem is using Aspose.Imaging for .NET. The following example shows a solution for merging files in the .jpeg format.


    public class SingleJpegStream : ICreateStreamProvider
    {
        private readonly string _filename;
        private int _page;
        public SingleJpegStream(string filename)
        {
            _filename = filename;
        }
        public void Dispose()
        {
        }
        public Stream GetStream(string name, string extension)
        {
            var filename = $"{_filename}.jpg";
            return new FileStream(filename, FileMode.CreateNew, FileAccess.ReadWrite);
        }
        public Stream GetStream(string name, string extension, int page)
        {
            var filename = $"{_filename}[{page}].jpg";
            _page = page;
            return new FileStream(filename, FileMode.Create, FileAccess.ReadWrite);
        }
        public void ReleaseStream(Stream stream)
        {
            stream.Dispose();
            if (_page > 1)
            {
                //Create an instance of JpegOptions and set its various properties
                var imageOptions = new JpegOptions
                {
                    Source = new FileOpenSource($"{_filename}.jpg")
                };
                var image1 = Image.Load($"{_filename}.jpg");
                var image2 = Image.Load(stream);
                //Create an instance of Image and define canvas size
                using (var image = Image.Create(imageOptions, image1.Width, image1.Height + image2.Height))
                {
                    //Create and initialize an instance of Graphics
                    var graphics = new Graphics(image);
                    // Draw Image
                    graphics.DrawImage(image1, 0, 0);
                    graphics.DrawImage(image2, 0, image1.Height);
                    // Call save method to save the resultant image.
                    image.Save();
                }
                File.Delete($"{_filename}[{_page}].jpg");
            }
            else
            {
                if (File.Exists($"{_filename}.jpg"))
                    File.Delete($"{_filename}.jpg");
                File.Move($"{ _filename}[1].jpg", $"{_filename}.jpg");
            }
        }
    }