Let's assume we want to insert an image into an existing Excel document specifying the following parameters:
The full solution is available here.
- Image file name
- Row and column of a cell we want to insert it to
- Maximum size for an image (in pixels) so that scaling is performed if needed
- Image offset inside a cell (in pixels)
To do this we will use OpenXML SDK from Microsoft which can be easily obtained here. The first link includes a powerful tool for working with OpenXML documents but for now we only need the SDK libraries. So go to the second link (OpenXMLSDKv2.msi) and download the installer.
Here's the code that solves the problem. I'm not going to delve into details of how it works, you can easily find them following the msdn link above. In a few words we create a new Drawing if it's not already present in the sheet. Then we do resizing to make sure the image fits into the given maximum size. After that we create OneCellAnchor element which is a key element here that contains the image itself and the information about where exactly it will be placed.
private static void InsertImage(WorksheetPart worksheetPart, string imageFileName, int colNumber, int rowNumber, int maxWidth, int maxHeight, int offsetX, int offsetY) { var drawingsPart = worksheetPart.DrawingsPart ?? worksheetPart.AddNewPart<DrawingsPart>(); if (!worksheetPart.Worksheet.ChildElements.OfType<Drawing>().Any()) { worksheetPart.Worksheet.Append(new Drawing { Id = worksheetPart.GetIdOfPart(drawingsPart) }); } if (drawingsPart.WorksheetDrawing == null) { drawingsPart.WorksheetDrawing = new Xdr.WorksheetDrawing(); } var worksheetDrawing = drawingsPart.WorksheetDrawing; var imagePart = drawingsPart.AddImagePart(ImagePartType.Jpeg); using (var stream = new FileStream(imageFileName, FileMode.Open)) { imagePart.FeedData(stream); } var positioningEMU = new ImagePositioningEMU(imageFileName, maxWidth, maxHeight, offsetX, offsetY); var extentsCx = positioningEMU.Width; long extentsCy = positioningEMU.Height; if (extentsCy > positioningEMU.MaxHeight || extentsCx > positioningEMU.MaxWidth) { var scaleX = (double)extentsCx / (double)positioningEMU.MaxWidth; var scaleY = (double)extentsCy / (double)positioningEMU.MaxHeight; var scale = Math.Max(scaleX, scaleY); extentsCx = (int)((double)extentsCx / scale); extentsCy = (int)((double)extentsCy / scale); } var colOffset = positioningEMU.OffsetX; var rowOffset = positioningEMU.OffsetY; var nvps = worksheetDrawing.Descendants<Xdr.NonVisualDrawingProperties>(); var nvpId = nvps.Count() > 0 ? (UInt32Value)worksheetDrawing.Descendants<Xdr.NonVisualDrawingProperties>().Max(p => p.Id.Value) + 1 : 1U; var oneCellAnchor = new Xdr.OneCellAnchor( new Xdr.FromMarker { ColumnId = new Xdr.ColumnId((colNumber - 1).ToString()), RowId = new Xdr.RowId((rowNumber - 1).ToString()), ColumnOffset = new Xdr.ColumnOffset(colOffset.ToString()), RowOffset = new Xdr.RowOffset(rowOffset.ToString()) }, new Xdr.Extent { Cx = extentsCx, Cy = extentsCy }, new Xdr.Picture( new Xdr.NonVisualPictureProperties( new Xdr.NonVisualDrawingProperties { Id = nvpId, Name = "Picture " + nvpId, Description = imageFileName }, new Xdr.NonVisualPictureDrawingProperties(new A.PictureLocks { NoChangeAspect = true }) ), new Xdr.BlipFill( new A.Blip { Embed = drawingsPart.GetIdOfPart(imagePart), CompressionState = A.BlipCompressionValues.Print }, new A.Stretch(new A.FillRectangle()) ), new Xdr.ShapeProperties( new A.Transform2D( new A.Offset { X = 0, Y = 0 }, new A.Extents { Cx = extentsCx, Cy = extentsCy } ), new A.PresetGeometry { Preset = A.ShapeTypeValues.Rectangle } ) ), new Xdr.ClientData() ); worksheetDrawing.Append(oneCellAnchor); }
There is one interesting moment though. You may have noticed using of a strange class ImagePositioningEMU. The reason we need it is Excel stores most of its positional data in EMU units. And we can't just use pixels for image size, we need to convert them to EMU. This is a job of that helper class. It's very simple so I'm not putting it here but you can find it in the attached solution archive.
And finally this is how we call the method.
If we open the resulting file we can see the following picture.
And finally this is how we call the method.
static void Main(string[] args) { using (var document = SpreadsheetDocument.Open("Empty.xlsx", true)) { var worksheetPart = (WorksheetPart)document.WorkbookPart.GetPartById( document.WorkbookPart.Workbook.Sheets.ElementsHere we are inserting the picture at the 5th column and the 10th row. The dimensions of the inserted image must not exceed 200 pixels. And it must be inserted 20 pixels right and 10 pixels down from the top left corner of the cell.().Single(s => s.Name == "Sheet1").Id.Value ); InsertImage(worksheetPart, "lighthouse.jpg", 5, 10, 200, 200, 20, 10); } }
If we open the resulting file we can see the following picture.
The full solution is available here.
Excellent! Very good and useful piece of code. Thank you very much.
ReplyDeleteWork for me THANKS!!!! I was looking for a working example lot of time!
ReplyDeleteCouldn't find a better example. Thanks a lot!!
ReplyDeleteAntivirus detected a Trojan when attempted to download full solution zip file.
ReplyDeleteIt works! Great example :)
ReplyDeleteThis works perfectly when i use it on a new file, but my xml have been created by closedXML and then the data gets corrupted. Any one knows a work around for this?
ReplyDeleteThis Not Work in asp..,
ReplyDelete