Hi @vyacheslav.deryushev , do you have any timeline when would I you be able to get back to me?
public static SKBitmap 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.
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.
var savePaperColor = new SKColor((uint)imageOptions.PaperColor.ToArgb());
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.
var 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.
var shape = new Shape(doc, ShapeType.TextBox);
var 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 surrounding line on the shape.
shape.Stroked = false;
var currentNode = node;
// If the node contains block level nodes then just add a copy of these nodes to the shape.
if (currentNode is InlineStory or Story)
{
var composite = (CompositeNode)currentNode;
foreach (var childNode in composite.ChildNodes)
{
shape.AppendChild(childNode.Clone(true));
}
}
else
{
// Move up through the DOM until we find a 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))
{
var 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.
using (var stream = new MemoryStream())
{
shape.GetShapeRenderer().Save(stream, imageOptions);
shape.Remove();
// Load the image into a new SKBitmap.
stream.Seek(0, SeekOrigin.Begin);
using (var renderedImage = SKBitmap.Decode(stream))
{
// Extract the actual content of the image by cropping transparent space around
// the rendered shape.
var cropRectangle = FindBoundingBoxAroundNode(renderedImage);
using (var croppedImage = new SKBitmap(cropRectangle.Width, cropRectangle.Height))
{
croppedImage.SetPixels(renderedImage.GetPixels());
// croppedImage.SetResolution(imageOptions.HorizontalResolution, imageOptions.VerticalResolution);
using (var canvas = new SKCanvas(croppedImage))
{
canvas.Clear(savePaperColor);
canvas.DrawBitmap(renderedImage, new SKRect(cropRectangle.Left, cropRectangle.Top, cropRectangle.Right, cropRectangle.Bottom), new SKRect(0, 0, croppedImage.Width, croppedImage.Height));
}
return croppedImage;
}
}
}
}
public static SKRectI FindBoundingBoxAroundNode(SKBitmap originalBitmap)
{
ArgumentNullException.ThrowIfNull(originalBitmap);
int minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
for (var x = 0; x < originalBitmap.Width; ++x)
{
for (var y = 0; y < originalBitmap.Height; ++y)
{
// Get the pixel color.
var pixelColor = originalBitmap.GetPixel(x, y);
// For each pixel that is not transparent calculate the bounding box around it.
if (pixelColor.Alpha != 0)
{
minX = Math.Min(x, minX);
minY = Math.Min(y, minY);
maxX = Math.Max(x, maxX);
maxY = Math.Max(y, maxY);
}
}
}
// Add one pixel to the width and height to avoid clipping.
return new SKRectI(minX, minY, maxX + 1, maxY + 1);
}
public static void SaveBitmap(SKBitmap bitmap, Stream stream, float dpiX, float dpiY)
{
try
{
// Your code that throws the exception
using (var image = SKImage.FromBitmap(bitmap))
{
using (var data = image.Encode(SKEncodedImageFormat.Png, 100))
{
data.SaveTo(stream);
}
}
}
catch (Exception ex)
{
// Log or print exception details for debugging
Console.WriteLine($"Exception: {ex.Message}");
Console.WriteLine($"Stack Trace: {ex.StackTrace}");
throw;
}
}
I have tried with this code but looks like it abruptly fail without any error on console.
var image = SKImage.FromBitmap(bitmap) here.
would be great if you can help.