diff --git a/ClosedXML/Excel/IXLWorksheet.cs b/ClosedXML/Excel/IXLWorksheet.cs index 7117744..b5aecd7 100644 --- a/ClosedXML/Excel/IXLWorksheet.cs +++ b/ClosedXML/Excel/IXLWorksheet.cs @@ -434,6 +434,8 @@ IXLPictures Pictures { get; } + IXLPicture Picture(String pictureName); + IXLPicture AddPicture(Stream stream); IXLPicture AddPicture(Stream stream, String name); @@ -450,4 +452,4 @@ IXLPicture AddPicture(String imageFile, String name); } -} \ No newline at end of file +} diff --git a/ClosedXML/Excel/XLWorkbook_ImageHandling.cs b/ClosedXML/Excel/XLWorkbook_ImageHandling.cs index 0533e1e..37e85da 100644 --- a/ClosedXML/Excel/XLWorkbook_ImageHandling.cs +++ b/ClosedXML/Excel/XLWorkbook_ImageHandling.cs @@ -38,12 +38,29 @@ return matchingAnchor.First(); } - public static NonVisualDrawingProperties GetPropertiesFromImageIndex(WorksheetPart worksheetPart, Int32 index) + public static NonVisualDrawingProperties GetPropertiesFromAnchor(OpenXmlElement anchor) { - var drawingsPart = worksheetPart.DrawingsPart; - return drawingsPart.WorksheetDrawing + if (!IsAllowedAnchor(anchor)) + return null; + + return anchor .Descendants() - .FirstOrDefault(x => x.Id.Value.Equals(Convert.ToUInt32(index + 1))); + .FirstOrDefault(); + } + + public static String GetImageRelIdFromAnchor(OpenXmlElement anchor) + { + if (!IsAllowedAnchor(anchor)) + return null; + + var blipFill = anchor.Descendants().FirstOrDefault(); + return blipFill?.Blip?.Embed?.Value; + } + + private static bool IsAllowedAnchor(OpenXmlElement anchor) + { + var allowedAnchorTypes = new Type[] { typeof(AbsoluteAnchor), typeof(OneCellAnchor), typeof(TwoCellAnchor) }; + return (allowedAnchorTypes.Any(t => t == anchor.GetType())); } } } diff --git a/ClosedXML/Excel/XLWorkbook_Load.cs b/ClosedXML/Excel/XLWorkbook_Load.cs index e74d134..af014f2 100644 --- a/ClosedXML/Excel/XLWorkbook_Load.cs +++ b/ClosedXML/Excel/XLWorkbook_Load.cs @@ -626,15 +626,13 @@ { var drawingsPart = wsPart.DrawingsPart; - var imageParts = drawingsPart.GetPartsOfType(); - for (int i = 0; i < imageParts.Count(); i++) + foreach (var anchor in drawingsPart.WorksheetDrawing.ChildElements) { - var imagePart = imageParts.ElementAt(i); - var imgId = drawingsPart.GetIdOfPart(imagePart); + var imgId = GetImageRelIdFromAnchor(anchor); + var imagePart = drawingsPart.GetPartById(imgId); using (var stream = imagePart.GetStream()) { - var anchor = GetAnchorFromImageId(wsPart, imgId); - var vsdp = GetPropertiesFromImageIndex(wsPart, i); + var vsdp = GetPropertiesFromAnchor(anchor); var picture = ws.AddPicture(stream, vsdp.Name) as XLPicture; picture.RelId = imgId; @@ -644,7 +642,15 @@ picture.Width = ConvertFromEnglishMetricUnits(spPr.Transform2D.Extents.Cx, GraphicsUtils.Graphics.DpiX); picture.Height = ConvertFromEnglishMetricUnits(spPr.Transform2D.Extents.Cy, GraphicsUtils.Graphics.DpiY); - if (anchor is Xdr.OneCellAnchor) + if (anchor is Xdr.AbsoluteAnchor) + { + var absoluteAnchor = anchor as Xdr.AbsoluteAnchor; + picture.MoveTo( + ConvertFromEnglishMetricUnits(absoluteAnchor.Position.X.Value, GraphicsUtils.Graphics.DpiX), + ConvertFromEnglishMetricUnits(absoluteAnchor.Position.Y.Value, GraphicsUtils.Graphics.DpiY) + ); + } + else if (anchor is Xdr.OneCellAnchor) { var oneCellAnchor = anchor as Xdr.OneCellAnchor; var from = LoadMarker(ws, oneCellAnchor.FromMarker); @@ -655,15 +661,26 @@ var twoCellAnchor = anchor as Xdr.TwoCellAnchor; var from = LoadMarker(ws, twoCellAnchor.FromMarker); var to = LoadMarker(ws, twoCellAnchor.ToMarker); - picture.MoveTo(from.Address, from.Offset, to.Address, to.Offset); - } - else if (anchor is Xdr.AbsoluteAnchor) - { - var absoluteAnchor = anchor as Xdr.AbsoluteAnchor; - picture.MoveTo( - ConvertFromEnglishMetricUnits(absoluteAnchor.Position.X.Value, GraphicsUtils.Graphics.DpiX), - ConvertFromEnglishMetricUnits(absoluteAnchor.Position.Y.Value, GraphicsUtils.Graphics.DpiY) - ); + + if (twoCellAnchor.EditAs == null || !twoCellAnchor.EditAs.HasValue || twoCellAnchor.EditAs.Value == Xdr.EditAsValues.TwoCell) + { + picture.MoveTo(from.Address, from.Offset, to.Address, to.Offset); + } + else if (twoCellAnchor.EditAs.Value == Xdr.EditAsValues.Absolute) + { + var shapeProperties = twoCellAnchor.Descendants().FirstOrDefault(); + if (shapeProperties != null) + { + picture.MoveTo( + ConvertFromEnglishMetricUnits(spPr.Transform2D.Offset.X, GraphicsUtils.Graphics.DpiX), + ConvertFromEnglishMetricUnits(spPr.Transform2D.Offset.Y, GraphicsUtils.Graphics.DpiY) + ); + } + } + else if (twoCellAnchor.EditAs.Value == Xdr.EditAsValues.OneCell) + { + picture.MoveTo(from.Address, from.Offset); + } } } } diff --git a/ClosedXML/Excel/XLWorkbook_Save.cs b/ClosedXML/Excel/XLWorkbook_Save.cs index 886655c..7604838 100644 --- a/ClosedXML/Excel/XLWorkbook_Save.cs +++ b/ClosedXML/Excel/XLWorkbook_Save.cs @@ -2557,7 +2557,10 @@ var extentsCx = ConvertToEnglishMetricUnits(pic.Width, GraphicsUtils.Graphics.DpiX); var extentsCy = ConvertToEnglishMetricUnits(pic.Height, GraphicsUtils.Graphics.DpiY); - var nvpId = Convert.ToUInt32(worksheetDrawing.DrawingsPart.ImageParts.ToList().IndexOf(imagePart) + 1); + var nvps = worksheetDrawing.Descendants(); + var nvpId = nvps.Any() ? + (UInt32Value)worksheetDrawing.Descendants().Max(p => p.Id.Value) + 1 : + 1U; Xdr.FromMarker fMark; Xdr.ToMarker tMark; @@ -2649,8 +2652,8 @@ { ColumnId = new Xdr.ColumnId((moveFromMarker.Address.ColumnNumber - 1).ToString()), RowId = new Xdr.RowId((moveFromMarker.Address.RowNumber - 1).ToString()), - ColumnOffset = new Xdr.ColumnOffset(ConvertToEnglishMetricUnits(moveFromMarker.Offset.X + pic.Left, GraphicsUtils.Graphics.DpiX).ToString()), - RowOffset = new Xdr.RowOffset(ConvertToEnglishMetricUnits(moveFromMarker.Offset.Y + pic.Top, GraphicsUtils.Graphics.DpiY).ToString()) + ColumnOffset = new Xdr.ColumnOffset(ConvertToEnglishMetricUnits(moveFromMarker.Offset.X, GraphicsUtils.Graphics.DpiX).ToString()), + RowOffset = new Xdr.RowOffset(ConvertToEnglishMetricUnits(moveFromMarker.Offset.Y, GraphicsUtils.Graphics.DpiY).ToString()) }; var oneCellAnchor = new Xdr.OneCellAnchor( @@ -2685,6 +2688,16 @@ } } + private static void RebasePictureIds(WorksheetPart worksheetPart) + { + for (var i = 0; i < worksheetPart.DrawingsPart.WorksheetDrawing.ChildElements.Count; i++) + { + var anchor = worksheetPart.DrawingsPart.WorksheetDrawing.ElementAt(i); + var props = GetPropertiesFromAnchor(anchor); + props.Id = Convert.ToUInt32(i + 1); + } + } + private static Vml.TextBox GetTextBox(IXLDrawingStyle ds) { var sb = new StringBuilder(); @@ -4760,6 +4773,9 @@ AddPictureAnchor(worksheetPart, pic, context); } + if (xlWorksheet.Pictures.Any()) + RebasePictureIds(worksheetPart); + if (xlWorksheet.Pictures.Any() && !worksheetPart.Worksheet.OfType().Any()) { var worksheetDrawing = new Drawing { Id = worksheetPart.GetIdOfPart(worksheetPart.DrawingsPart) }; @@ -4995,4 +5011,4 @@ #endregion GenerateWorksheetPartContent } -} \ No newline at end of file +} diff --git a/ClosedXML/Excel/XLWorksheet.cs b/ClosedXML/Excel/XLWorksheet.cs index ceb8cc7..6579424 100644 --- a/ClosedXML/Excel/XLWorksheet.cs +++ b/ClosedXML/Excel/XLWorksheet.cs @@ -1523,6 +1523,11 @@ public IXLPictures Pictures { get; private set; } + public IXLPicture Picture(string pictureName) + { + return Pictures.Picture(pictureName); + } + public IXLPicture AddPicture(Stream stream) { return Pictures.Add(stream); diff --git a/ClosedXML_Tests/ClosedXML_Tests.csproj b/ClosedXML_Tests/ClosedXML_Tests.csproj index 47cca6a..ba753e5 100644 --- a/ClosedXML_Tests/ClosedXML_Tests.csproj +++ b/ClosedXML_Tests/ClosedXML_Tests.csproj @@ -272,6 +272,7 @@ + diff --git a/ClosedXML_Tests/Excel/Loading/LoadingTests.cs b/ClosedXML_Tests/Excel/Loading/LoadingTests.cs index 5d53a55..8746443 100644 --- a/ClosedXML_Tests/Excel/Loading/LoadingTests.cs +++ b/ClosedXML_Tests/Excel/Loading/LoadingTests.cs @@ -22,7 +22,8 @@ @"Misc\EmptyTable.xlsx", @"Misc\LoadPivotTables.xlsx", @"Misc\LoadFileWithCustomSheetViews.xlsx", - @"Misc\LoadSheetsWithCommas.xlsx" + @"Misc\LoadSheetsWithCommas.xlsx", + @"Misc\ExcelProducedWorkbookWithImages.xlsx" }; foreach (var file in files) @@ -152,5 +153,26 @@ Assert.AreEqual(XLPictureFormat.Png, ws2.Pictures.First().Format); } } + + [Test] + public void CanLoadAndDeduceAnchorsFromExcelGeneratedFile() + { + // This file was produced by Excel. It contains 3 images, but the latter 2 were copied from the first. + // There is actually only 1 embedded image if you inspect the file's internals. + // Additionally, Excel saves all image anchors as TwoCellAnchor, but uses the EditAs attribute to distinguish the types + using (var stream = TestHelper.GetStreamFromResource(TestHelper.GetResourcePath(@"Misc\ExcelProducedWorkbookWithImages.xlsx"))) + using (var wb = new XLWorkbook(stream)) + { + var ws = wb.Worksheets.First(); + Assert.AreEqual(3, ws.Pictures.Count); + + Assert.AreEqual(XLPicturePlacement.MoveAndSize, ws.Picture("Picture 1").Placement); + Assert.AreEqual(XLPicturePlacement.Move, ws.Picture("Picture 2").Placement); + Assert.AreEqual(XLPicturePlacement.FreeFloating, ws.Picture("Picture 3").Placement); + + using (var ms = new MemoryStream()) + wb.SaveAs(ms, true); + } + } } } diff --git a/ClosedXML_Tests/Resource/Misc/ExcelProducedWorkbookWithImages.xlsx b/ClosedXML_Tests/Resource/Misc/ExcelProducedWorkbookWithImages.xlsx new file mode 100644 index 0000000..62a8107 --- /dev/null +++ b/ClosedXML_Tests/Resource/Misc/ExcelProducedWorkbookWithImages.xlsx Binary files differ