Rendering a table in a shape object to a bmp image

Hi,
I’m attempting to try the following example :
https://docs.aspose.com/words/net/rendering-shapes-separately-from-a-document/
The code I’m using is below. It is generally what i need as we need to convert a table object into an image. I’ve tried googling and searching the forums and have not found out how to get this to work.
Are you able to help please.
Thanks.
Gavin
-----------------------------------------------------------------------

Document document = new Document();
DocumentBuilder builder = new DocumentBuilder(document);
Aspose.Words.Drawing.Shape shape = new Drawing.Shape(document, Drawing.ShapeType.TextBox);
Tables.Table table = new Tables.Table(document);
Tables.Row row1 = new Tables.Row(document);
Tables.Row row2 = new Tables.Row(document);
Tables.Row row3 = new Tables.Row(document);
table.Rows.Add(row1);
table.Rows.Add(row2);
table.Rows.Add(row3);
Tables.Cell cell1 = new Tables.Cell(document);
Tables.Cell cell2 = new Tables.Cell(document);
row1.Cells.Add(cell1);
row1.Cells.Add(cell2);
Tables.Cell cell3 = new Tables.Cell(document);
Tables.Cell cell4 = new Tables.Cell(document);
row2.Cells.Add(cell3);
row2.Cells.Add(cell4);
Tables.Cell cell5 = new Tables.Cell(document);
Tables.Cell cell6 = new Tables.Cell(document);
row3.Cells.Add(cell5);
row3.Cells.Add(cell6);
ApplyBorders(cell1);
ApplyBorders(cell2);
ApplyBorders(cell3);
ApplyBorders(cell4);
ApplyBorders(cell5);
ApplyBorders(cell6);
shape.AppendChild(table);
shape.TextBox.FitShapeToText = true;
shape.AllowOverlap = true;
shape.RelativeHorizontalPosition = Drawing.RelativeHorizontalPosition.Page;
shape.RelativeVerticalPosition = Drawing.RelativeVerticalPosition.Page;
shape.Left = 0;
shape.Top = 0;
shape.Width = 100;
shape.Height = 100;
ShapeRenderer renderer = shape.GetShapeRenderer();
Size shapeSizeInPixels = renderer.GetSizeInPixels(1.0 f, 300.0 f);
int maxSide = System.Math.Max(shapeSizeInPixels.Width, shapeSizeInPixels.Height);
using(Bitmap image = new Bitmap((int)(shapeSizeInPixels.Width * 1.25), (int)(shapeSizeInPixels.Height * 1.25)))
{
    using(Graphics gr = Graphics.FromImage(image))
    {
        renderer.RenderToSize(gr, 0, 0, shapeSizeInPixels.Width, shapeSizeInPixels.Height);
    }
}
MemoryStream stream = new MemoryStream();
Saving.ImageSaveOptions options = new Saving.ImageSaveOptions(SaveFormat.Jpeg);
renderer.Save(stream, options);
builder.InsertImage(stream);

document.Save("C:\\Code\\SHL.ReportEngine.DocumentEngine\\Statements\\Tests\\Outputs\\TableToImageTest.doc");

Hi Gavin,
Thanks for your request. Unfortunately, there is no direct way to achieve this using Aspose.Words.
The only way to convert a table to image I can suggest is the following: Copy the table into the empty document, approximately calculate size of the table and adjust page size, convert the document to image.
Another option is copying table into a textbox shape and then render this shape, just as you do in your code.
Best regards,

Hi Alexey,
I have no problems with needing to copy it into a shape object but the code I posted results in nothing being shown. Are you able to help me on this please?
I’ve pasted an amended piece of code below. I hope it is clear.
Kind Regards,
Gavin

Document document = new Document();
DocumentBuilder builder = new DocumentBuilder(document);
Aspose.Words.Drawing.Shape shape = new Drawing.Shape(document, Drawing.ShapeType.TextBox);
Tables.Table table = new Tables.Table(document);
Tables.Row row1 = new Tables.Row(document);
Tables.Row row2 = new Tables.Row(document);
Tables.Row row3 = new Tables.Row(document);
table.Rows.Add(row1);
table.Rows.Add(row2);
table.Rows.Add(row3);
Tables.Cell cell1 = new Tables.Cell(document);
Tables.Cell cell2 = new Tables.Cell(document);
row1.Cells.Add(cell1);
row1.Cells.Add(cell2);
Tables.Cell cell3 = new Tables.Cell(document);
Tables.Cell cell4 = new Tables.Cell(document);
row2.Cells.Add(cell3);
row2.Cells.Add(cell4);
Tables.Cell cell5 = new Tables.Cell(document);
Tables.Cell cell6 = new Tables.Cell(document);
row3.Cells.Add(cell5);
row3.Cells.Add(cell6);
ApplyBorders(cell1);
ApplyBorders(cell2);
ApplyBorders(cell3);
ApplyBorders(cell4);
ApplyBorders(cell5);
ApplyBorders(cell6);
Paragraph paragraph1 = new Paragraph(document);
paragraph1.Runs.Add(new Run(document, "Hello World1"));
cell1.Paragraphs.Add(paragraph1);
Paragraph paragraph2 = new Paragraph(document);
paragraph2.Runs.Add(new Run(document, "Hello World2"));
cell2.Paragraphs.Add(paragraph2);
shape.AppendChild(table);
shape.TextBox.FitShapeToText = true;
shape.AllowOverlap = true;
shape.RelativeHorizontalPosition = Drawing.RelativeHorizontalPosition.Page;
shape.RelativeVerticalPosition = Drawing.RelativeVerticalPosition.Page;
shape.Left = 0;
shape.Top = 0;
shape.Width = 100;
shape.Height = 100;
ShapeRenderer renderer = shape.GetShapeRenderer();
Size shapeSizeInPixels = renderer.GetSizeInPixels(1.0 f, 300.0 f);
int maxSide = System.Math.Max(shapeSizeInPixels.Width, shapeSizeInPixels.Height);
using(Bitmap image = new Bitmap((int)(shapeSizeInPixels.Width * 1.25), (int)(shapeSizeInPixels.Height * 1.25)))
{
    using(Graphics gr = Graphics.FromImage(image))
    {
        renderer.RenderToSize(gr, 0, 0, shapeSizeInPixels.Width, shapeSizeInPixels.Height);
    }
}
MemoryStream stream = new MemoryStream();
Saving.ImageSaveOptions options = new Saving.ImageSaveOptions(SaveFormat.Jpeg);
renderer.Save(stream, options);
builder.InsertImage(stream);

document.Save("C:\\Code\\SHL.ReportEngine.DocumentEngine\\Statements\\Tests\\Outputs\\TableToImageTest.doc");

Hi
Thanks for your request. I used the following code for testing and all works as expected:

Document document = new Document();
DocumentBuilder builder = new DocumentBuilder(document);
// Create textbox shape and insert it into the document.
Shape shape = new Shape(document, ShapeType.TextBox);
shape.Height = 110;
shape.Width = 110;
shape.Stroked = false;
shape.TextBox.InternalMarginBottom = 0;
shape.TextBox.InternalMarginLeft = 0;
shape.TextBox.InternalMarginRight = 0;
shape.TextBox.InternalMarginTop = 0;
builder.InsertNode(shape);
// Add a paragraph into shape and move DocumentBuilder cursor into it.
Paragraph par = new Paragraph(document);
shape.AppendChild(par);
builder.MoveTo(par);
// Build a simpel table.
builder.RowFormat.Alignment = RowAlignment.Center;
builder.CellFormat.Borders.LineStyle = LineStyle.Single;
builder.CellFormat.Width = 50;
builder.InsertCell();
builder.Write("test");
builder.InsertCell();
builder.Write("test");
builder.EndRow();
builder.InsertCell();
builder.Write("test");
builder.InsertCell();
builder.Write("test");
builder.EndRow();
builder.InsertCell();
builder.Write("test");
builder.InsertCell();
builder.Write("test");
builder.EndRow();
builder.EndTable();
// remove an empty paragrapha form the shape.
par.Remove();
// Render shape.
ShapeRenderer renderer = shape.GetShapeRenderer();
MemoryStream stream = new MemoryStream();
ImageSaveOptions options = new ImageSaveOptions(SaveFormat.Jpeg);
renderer.Save(stream, options);
stream.Position = 0;
// Remove the original shape.
shape.Remove();
// Insert image.
builder.MoveToDocumentEnd();
builder.InsertImage(stream);
document.Save(@"Test001\out.doc");

Best regards,

Hi there,
Thanks for your inquiry.
You can also try using the RenderNode method found attached in this post here. In this case you just need to pass the table node to the RenderNode method to render it to image.
Thanks,

Hi,
I tried using the method that was suggested but it does not create any visible outputs.
Here is the code.

using System;
using System.Drawing;
using System.IO;
using Aspose.Words.Drawing;
using Aspose.Words.Saving;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Aspose.Words.Test
{
    [TestClass]
    public class TableTestSuite
    {

        [TestMethod]
        public void TableToImageTest()
        {
            Document document = new Document();
            DocumentBuilder builder = new DocumentBuilder(document);
            // Create table with 3 rows and 2 cells in each row.

            Tables.Table table = new Tables.Table(document);
            Tables.Row row1 = new Tables.Row(document);
            Tables.Row row2 = new Tables.Row(document);
            Tables.Row row3 = new Tables.Row(document);
            table.Rows.Add(row1);
            table.Rows.Add(row2);
            table.Rows.Add(row3);
            Tables.Cell cell1 = new Tables.Cell(document);
            Tables.Cell cell2 = new Tables.Cell(document);
            row1.Cells.Add(cell1);
            row1.Cells.Add(cell2);
            Tables.Cell cell3 = new Tables.Cell(document);
            Tables.Cell cell4 = new Tables.Cell(document);
            row2.Cells.Add(cell3);
            row2.Cells.Add(cell4);
            Tables.Cell cell5 = new Tables.Cell(document);
            Tables.Cell cell6 = new Tables.Cell(document);
            row3.Cells.Add(cell5);
            row3.Cells.Add(cell6);
            // Set cell border values.
            ApplyBorders(cell1);
            ApplyBorders(cell2);
            ApplyBorders(cell3);
            ApplyBorders(cell4);
            ApplyBorders(cell5);
            ApplyBorders(cell6);
            // Add text into cell1 and cell 2
            Paragraph paragraph1 = new Paragraph(document);
            paragraph1.Runs.Add(new Run(document, "Hello World1"));
            cell1.Paragraphs.Add(paragraph1);
            Paragraph paragraph2 = new Paragraph(document);
            paragraph2.Runs.Add(new Run(document, "Hello World2"));
            cell2.Paragraphs.Add(paragraph2);
            // Insert image rendered from table.
            builder.InsertImage(RenderNode(table, null));
            document.Save("C:\\Code\\SHL.ReportEngine.DocumentEngine\\Statements\\Tests\\Outputs\\TableToImageTest.doc");
        }
        private void ApplyBorders(Tables.Cell cell)
        {
            cell.CellFormat.Borders.Top.Color = Color.Black;
            cell.CellFormat.Borders.Right.Color = Color.Black;
            cell.CellFormat.Borders.Left.Color = Color.Black;
            cell.CellFormat.Borders.Bottom.Color = Color.Black;
            cell.CellFormat.Borders.LineWidth = 1;
            cell.CellFormat.Borders.LineStyle = LineStyle.Single;
        }

        ///
        /// Renders any node in a document to the path specified using the image save options.
        ///
        public static Image RenderNode(Node node, ImageSaveOptions imageOptions)
        {
            // Run some argument checks.
            if (node == null)
                throw new ArgumentException("Node cannot be null");
            // If no image options are supplied, create default options.
            if (imageOptions == null)
                imageOptions = new ImageSaveOptions(SaveFormat.Png);
            // Store the paper color to be used on the final image and change to transparent.
            // This will cause any content around the rendered node to be removed later on.
            Color savePaperColor = imageOptions.PaperColor;
            imageOptions.PaperColor = Color.Transparent;
            // There a bug which affects the cache of a cloned node. To avoid this we instead clone the entire document including all nodes,
            // find the matching node in the cloned document and render that instead.
            Document doc = (Document)node.Document.Clone(true);
            node = doc.GetChild(NodeType.Any, node.Document.GetChildNodes(NodeType.Any, true).IndexOf(node), true);
            // Create a temporary shape to store the target node in. This shape will be rendered to retrieve
            // the rendered content of the node.
            Shape shape = new Shape(doc, ShapeType.TextBox);
            Section parentSection = (Section)node.GetAncestor(NodeType.Section);
            // Assume that the node cannot be larger than the page in size.
            shape.Width = parentSection.PageSetup.PageWidth;
            shape.Height = parentSection.PageSetup.PageHeight;
            shape.FillColor = Color.Transparent; // We must make the shape and paper color transparent.
                                                 // Don't draw a surronding line on the shape.
            shape.Stroked = false;
            Node currentNode = node;
            // If the node contains block level nodes then just add a copy of these nodes to the shape.
            if (currentNode is InlineStory || currentNode is Story)
            {
                CompositeNode composite = (CompositeNode)currentNode;
                foreach (Node childNode in composite.ChildNodes)
                {
                    shape.AppendChild(childNode.Clone(true));
                }
            }
            else
            {
                // Move up through the DOM until we find node which is suitable to insert into a Shape (a node with a parent can contain paragraph, tables the same as a shape).
                // Each parent node is cloned on the way up so even a descendant node passed to this method can be rendered.
                // Since we are working with the actual nodes of the document we need to clone the target node into the temporary shape.
                while (!(currentNode.ParentNode is InlineStory || currentNode.ParentNode is Story || currentNode.ParentNode is ShapeBase || currentNode.NodeType == NodeType.Paragraph))
                {
                    CompositeNode parent = (CompositeNode)currentNode.ParentNode.Clone(false);
                    currentNode = currentNode.ParentNode;
                    parent.AppendChild(node.Clone(true));
                    node = parent; // Store this new node to be inserted into the shape.
                }
                // Add the node to the shape.
                shape.AppendChild(node.Clone(true));
            }
            // We must add the shape to the document tree to have it rendered.
            parentSection.Body.FirstParagraph.AppendChild(shape);
            // Render the shape to stream so we can take advantage of the effects of the ImageSaveOptions class.
            // Retrieve the rendered image and remove the shape from the document.
            MemoryStream stream = new MemoryStream();
            shape.GetShapeRenderer().Save("C:\\Code\\SHL.ReportEngine.DocumentEngine\\Statements\\Tests\\Outputs\\tableimage.png", imageOptions);
            shape.GetShapeRenderer().Save(stream, imageOptions);
            shape.Remove();
            Bitmap croppedImage;
            // Load the image into a new bitmap.
            using (Bitmap renderedImage = new Bitmap(stream))
            {
                // Extract the actual content of the image by cropping transparent space around
                // the rendered shape.
                Rectangle cropRectangle = FindBoundingBoxAroundNode(renderedImage);
                croppedImage = new Bitmap(cropRectangle.Width, cropRectangle.Height);
                croppedImage.SetResolution(imageOptions.Resolution, imageOptions.Resolution);
                // Create the final image with the proper background color.
                using (Graphics g = Graphics.FromImage(croppedImage))
                {
                    g.Clear(savePaperColor);
                    g.DrawImage(renderedImage, new Rectangle(0, 0, croppedImage.Width, croppedImage.Height), cropRectangle.X, cropRectangle.Y, cropRectangle.Width, cropRectangle.Height, GraphicsUnit.Pixel);
                }
            }
            return croppedImage;
        }

        ///
        /// Finds the minimum bounding box around non-transparent pixels in a Bitmap.
        ///
        public static Rectangle FindBoundingBoxAroundNode(Bitmap originalBitmap)
        {
            Point min = new Point(int.MaxValue, int.MaxValue);
            Point max = new Point(int.MinValue, int.MinValue);
            for (int x = 0; x < originalBitmap.Width; ++x)
            {
                for (int y = 0; y < originalBitmap.Height; ++y)
                {
                    // Note that you can speed up this part of the algorithm by using LockBits and unsafe code instead of GetPixel.
                    Color pixelColor = originalBitmap.GetPixel(x, y);
                    // For each pixel that is not transparent calculate the bounding box around it.
                    if (pixelColor.ToArgb() != Color.Empty.ToArgb())
                    {
                        min.X = System.Math.Min(x, min.X);
                        min.Y = System.Math.Min(y, min.Y);
                        max.X = System.Math.Max(x, max.X);
                        max.Y = System.Math.Max(y, max.Y);
                    }
                }
            }
            // Add one pixel to the width and height to avoid clipping.
            return new Rectangle(min.X, min.Y, (max.X - min.X) + 1, (max.Y - min.Y) + 1);
        }
    }
}

Thanks.

Hi there,
Thanks for your inquiry.
You need to attach the table to the DOM before calling this method in order for it to work. The table can then be removed straight after the method is complete. Please see the changes below.

document.FirstSection.Body.AppendChild(table);
// Insert image rendered from table.
builder.InsertImage(RenderNode(table, null));
table.Remove();

Thanks,

Hi,
I’ve created a document with the table in it then called the rendernode code as above.
However I am getting an exception here:

parentSection.Body.FirstParagraph.AppendChild(shape);

The reason for this is because the FirstParagraph is null.

I tried to solve it by replacing the code with the following code:

// We must add the shape to the document tree to have it rendered.
Aspose.Words.Paragraph tempParagraph = new Aspose.Words.Paragraph(doc);
tempParagraph.AppendChild(shape);

parentSection.Body.Paragraphs.Add(tempParagraph);
// Render the shape to stream so we can take advantage of the effects of the ImageSaveOptions class.
// Retrieve the rendered image and remove the shape from the document.
MemoryStream stream = new MemoryStream();
shape.GetShapeRenderer().Save(stream, imageOptions);
shape.Remove();
tempParagraph.Remove();

However this causes the following exception at the shape.GetShapeRenderer().Save() call.

{"Cannot translate from 'x11db8fc7f469a2fc' in 'Textbox' to 'xa65184d44a47025b'."}

I’m just a bit lost as how to why it is not working as I would have expected an error when attempting to add the object into the shape rather then at the saving call.

Hi there,
Thanks for this additional information.
A valid word document should have at least one paragraph in the main body, so I’m not sure how you created a document like this. In any case please use the code below instead, this works fine on my side.

doc.EnsureMinimum();
// We must add the shape to the document tree to have it rendered.
parentSection.Body.FirstParagraph.AppendChild(shape);

Also you need to make sure that your builder does not try inserting the image into the table that is to be removed. I would suggest moving the line above into your main code so it applies the extra paragraph to your original document.
If you have any further troubles, could you please attach your document here for testing?