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..4ea41ca 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; @@ -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_Tests/Excel/Loading/LoadingTests.cs b/ClosedXML_Tests/Excel/Loading/LoadingTests.cs index 8a87569..8746443 100644 --- a/ClosedXML_Tests/Excel/Loading/LoadingTests.cs +++ b/ClosedXML_Tests/Excel/Loading/LoadingTests.cs @@ -157,6 +157,9 @@ [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)) { @@ -166,6 +169,9 @@ 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); } } }