diff --git a/ClosedXML/ClosedXML.csproj b/ClosedXML/ClosedXML.csproj
index d5ddc65..0539fcd 100644
--- a/ClosedXML/ClosedXML.csproj
+++ b/ClosedXML/ClosedXML.csproj
@@ -72,6 +72,10 @@
+
+
+
+
@@ -343,4 +347,4 @@
-->
-
\ No newline at end of file
+
diff --git a/ClosedXML/Excel/Drawings/IXLMarker.cs b/ClosedXML/Excel/Drawings/IXLMarker.cs
new file mode 100644
index 0000000..fd52192
--- /dev/null
+++ b/ClosedXML/Excel/Drawings/IXLMarker.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ClosedXML.Excel.Drawings
+{
+ public interface IXLMarker
+ {
+ Int32 ColumnId { get; set; }
+ Int32 RowId { get; set; }
+ Double ColumnOffset { get; set; }
+ Double RowOffset { get; set; }
+
+ ///
+ /// Get the zero-based column number.
+ ///
+ Int32 GetZeroBasedColumn();
+
+ ///
+ /// Get the zero-based row number.
+ ///
+ Int32 GetZeroBasedRow();
+ }
+}
diff --git a/ClosedXML/Excel/Drawings/IXLPicture.cs b/ClosedXML/Excel/Drawings/IXLPicture.cs
new file mode 100644
index 0000000..84829b7
--- /dev/null
+++ b/ClosedXML/Excel/Drawings/IXLPicture.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace ClosedXML.Excel.Drawings
+{
+ public interface IXLPicture
+ {
+ Stream ImageStream { get; set; }
+
+ List GetMarkers();
+ void AddMarker(IXLMarker marker);
+
+ long MaxHeight { get; set; }
+ long MaxWidth { get; set; }
+ long Width { get; set; }
+ long Height { get; set; }
+ long OffsetX { get; set; }
+ long OffsetY { get; set; }
+ long RawOffsetX { get; set; }
+ long RawOffsetY { get; set; }
+ bool IsAbsolute { get; set; }
+
+ ///
+ /// Type of image. The supported formats are defined by OpenXML's ImagePartType.
+ /// Default value is "jpeg"
+ ///
+ String Type { get; set; }
+
+ String Name { get; set; }
+
+ ///
+ /// Get the enum representation of the Picture type.
+ ///
+ ImagePartType GetImagePartType();
+ }
+}
diff --git a/ClosedXML/Excel/Drawings/XLMarker.cs b/ClosedXML/Excel/Drawings/XLMarker.cs
new file mode 100644
index 0000000..1ed01be
--- /dev/null
+++ b/ClosedXML/Excel/Drawings/XLMarker.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ClosedXML.Excel.Drawings
+{
+ public class XLMarker : IXLMarker
+ {
+ private Int32 colId;
+ private Int32 rowId;
+ private Double colOffset;
+ private Double rowOffset;
+
+ public Int32 ColumnId
+ {
+ set
+ {
+ if (value < 1 || value > XLHelper.MaxColumnNumber)
+ throw new ArgumentOutOfRangeException(String.Format("Column number must be between 1 and {0}",
+ XLHelper.MaxColumnNumber));
+ this.colId = value;
+ }
+ get
+ {
+ return this.colId;
+ }
+ }
+
+ public Int32 RowId
+ {
+ set
+ {
+ if (value < 1 || value > XLHelper.MaxRowNumber)
+ throw new ArgumentOutOfRangeException(String.Format("Row number must be between 1 and {0}",
+ XLHelper.MaxRowNumber));
+ this.rowId = value;
+ }
+ get
+ {
+ return this.rowId;
+ }
+ }
+
+ public Double ColumnOffset
+ {
+ set
+ {
+ this.colOffset = value;
+ }
+ get
+ {
+ return this.colOffset;
+ }
+ }
+
+ public Double RowOffset
+ {
+ set
+ {
+ this.rowOffset = value;
+ }
+ get
+ {
+ return this.rowOffset;
+ }
+ }
+
+ public Int32 GetZeroBasedColumn()
+ {
+ return colId - 1;
+ }
+
+ public Int32 GetZeroBasedRow()
+ {
+ return rowId - 1;
+ }
+ }
+}
diff --git a/ClosedXML/Excel/Drawings/XLPicture.cs b/ClosedXML/Excel/Drawings/XLPicture.cs
new file mode 100644
index 0000000..0d54b51
--- /dev/null
+++ b/ClosedXML/Excel/Drawings/XLPicture.cs
@@ -0,0 +1,244 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace ClosedXML.Excel.Drawings
+{
+ public class XLPicture : IXLPicture
+ {
+ private MemoryStream imgStream;
+ private List Markers;
+ private String name;
+ private bool isAbsolute;
+ private ImagePartType type = ImagePartType.Jpeg;
+
+ private long iMaxWidth = 500;
+ private long iMaxHeight = 500;
+
+ private long iWidth;
+ private long iHeight;
+
+ private long iOffsetX;
+ private long iOffsetY;
+
+ private float iVerticalResolution;
+ private float iHorizontalResolution;
+
+ private bool isResized = false;
+
+ private void Resize()
+ {
+ if (iWidth > iMaxHeight || iHeight > iMaxWidth)
+ {
+ var scaleX = (double)iWidth / (double)iMaxWidth;
+ var scaleY = (double)iHeight / (double)iMaxHeight;
+ var scale = Math.Max(scaleX, scaleY);
+ iWidth = (int)((double)iWidth / scale);
+ iHeight = (int)((double)iHeight / scale);
+ }
+ isResized = true;
+ }
+
+ public long MaxWidth
+ {
+ get
+ {
+ return ConvertToEmu(iMaxWidth, iHorizontalResolution);
+ }
+ set
+ {
+ iMaxWidth = value;
+ isResized = false;
+ }
+ }
+
+
+ public long MaxHeight
+ {
+ get
+ {
+ return ConvertToEmu(iMaxHeight, iVerticalResolution);
+ }
+ set
+ {
+ iMaxHeight = value;
+ isResized = false;
+ }
+ }
+
+ public long Width
+ {
+ get
+ {
+ if (!isResized)
+ {
+ Resize();
+ }
+ return ConvertToEmu(iWidth, iHorizontalResolution);
+ }
+ set { }
+ }
+
+ public long Height
+ {
+ get
+ {
+ if (!isResized)
+ {
+ Resize();
+ }
+ return ConvertToEmu(iHeight, iVerticalResolution);
+ }
+ set { }
+ }
+
+ public long RawHeight
+ {
+ get { return (long)iHeight; }
+ }
+ public long RawWidth
+ {
+ get { return (long)iWidth; }
+ }
+
+ public long OffsetX
+ {
+ get { return ConvertToEmu(iOffsetX, iHorizontalResolution); }
+ set { iOffsetX = value; }
+ }
+ public long OffsetY
+ {
+ get { return ConvertToEmu(iOffsetY, iVerticalResolution); }
+ set { iOffsetY = value; }
+ }
+
+ public long RawOffsetX
+ {
+ get
+ {
+ return iOffsetX;
+ }
+ set
+ {
+ iOffsetX = value;
+ }
+ }
+
+ public long RawOffsetY
+ {
+ get
+ {
+ return iOffsetY;
+ }
+ set
+ {
+ iOffsetY = value;
+ }
+ }
+
+ private long ConvertToEmu(long pixels, float resolution)
+ {
+ return (long)(914400 * pixels / resolution);
+ }
+
+ public Stream ImageStream
+ {
+ get
+ {
+ return imgStream;
+ }
+ set
+ {
+ if (imgStream == null)
+ {
+ imgStream = new MemoryStream();
+ }
+ else
+ {
+ imgStream.Dispose();
+ imgStream = new MemoryStream();
+ }
+ value.CopyTo(imgStream);
+ imgStream.Seek(0, SeekOrigin.Begin);
+
+ using (var bitmap = new System.Drawing.Bitmap(imgStream))
+ {
+ iWidth = (long)bitmap.Width;
+ iHeight = (long)bitmap.Height;
+ iHorizontalResolution = bitmap.HorizontalResolution;
+ iVerticalResolution = bitmap.VerticalResolution;
+ }
+ imgStream.Seek(0, SeekOrigin.Begin);
+ }
+ }
+
+ public List GetMarkers()
+ {
+ return Markers != null ? Markers : new List();
+ }
+ public void AddMarker(IXLMarker marker)
+ {
+ if (Markers == null)
+ {
+ Markers = new List();
+ }
+ Markers.Add(marker);
+ }
+
+ public String Name
+ {
+ get
+ {
+ return name;
+ }
+ set
+ {
+ name = value;
+ }
+ }
+
+ public bool IsAbsolute
+ {
+ get
+ {
+ return isAbsolute;
+ }
+ set
+ {
+ isAbsolute = value;
+ }
+ }
+
+ public String Type
+ {
+ get
+ {
+ return GetExtension(type);
+ }
+ set
+ {
+ try
+ {
+ type = (ImagePartType)Enum.Parse(typeof(ImagePartType), value, true);
+ }
+ catch
+ {
+ type = ImagePartType.Jpeg;
+ }
+ }
+ }
+
+ private String GetExtension(ImagePartType type)
+ {
+ return type.ToString().ToLower();
+ }
+
+ public ImagePartType GetImagePartType()
+ {
+ return type;
+ }
+ }
+}
diff --git a/ClosedXML/Excel/IXLWorksheet.cs b/ClosedXML/Excel/IXLWorksheet.cs
index 0d9dcd6..cf5b21b 100644
--- a/ClosedXML/Excel/IXLWorksheet.cs
+++ b/ClosedXML/Excel/IXLWorksheet.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
namespace ClosedXML.Excel
{
@@ -285,7 +286,7 @@
IXLTables Tables { get; }
///
- /// Copies the
+ /// Copies the
///
///
///
@@ -368,5 +369,13 @@
Object Evaluate(String expression);
String Author { get; set; }
+
+ List Pictures();
+
+ ///
+ /// Adds image to worksheet
+ ///
+ /// XLPicture object to be added.
+ void AddPicture(Drawings.XLPicture pic);
}
}
diff --git a/ClosedXML/Excel/XLWorkbook_Save.cs b/ClosedXML/Excel/XLWorkbook_Save.cs
index 9a99f83..9b099a8 100644
--- a/ClosedXML/Excel/XLWorkbook_Save.cs
+++ b/ClosedXML/Excel/XLWorkbook_Save.cs
@@ -3,6 +3,7 @@
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Text.RegularExpressions;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.CustomProperties;
using DocumentFormat.OpenXml.Drawing;
@@ -34,6 +35,7 @@
using Text = DocumentFormat.OpenXml.Spreadsheet.Text;
using TopBorder = DocumentFormat.OpenXml.Spreadsheet.TopBorder;
using Underline = DocumentFormat.OpenXml.Spreadsheet.Underline;
+using Xdr = DocumentFormat.OpenXml.Drawing.Spreadsheet;
using System.Xml;
using System.Xml.Linq;
using System.Text;
@@ -2501,6 +2503,184 @@
return stroke;
}
+ private static void AddPictureAnchor(WorksheetPart worksheetPart, Drawings.IXLPicture picture)
+ {
+ var drawingsPart = worksheetPart.DrawingsPart ??
+ worksheetPart.AddNewPart(
+ GeneratePartId(picture.Name, worksheetPart));
+
+ if (drawingsPart.WorksheetDrawing == null)
+ {
+ drawingsPart.WorksheetDrawing = new Xdr.WorksheetDrawing();
+ }
+
+ var worksheetDrawing = drawingsPart.WorksheetDrawing;
+
+ var imagePart = drawingsPart.AddImagePart(picture.GetImagePartType(),
+ GeneratePartId(picture.Name, drawingsPart));
+
+ using (Stream stream = new MemoryStream())
+ {
+ picture.ImageStream.CopyTo(stream);
+ stream.Seek(0, SeekOrigin.Begin);
+ imagePart.FeedData(stream);
+ }
+
+ var extentsCx = picture.Width;
+ var extentsCy = picture.Height;
+
+ var nvps = worksheetDrawing.Descendants();
+ var nvpId = nvps.Count() > 0 ?
+ (UInt32Value)worksheetDrawing.Descendants().Max(p => p.Id.Value) + 1 :
+ 1U;
+ if (picture.IsAbsolute)
+ {
+ Xdr.AbsoluteAnchor absoluteAnchor;
+ absoluteAnchor = new Xdr.AbsoluteAnchor(
+ new Xdr.Position
+ {
+ X = picture.OffsetX,
+ Y = picture.OffsetY
+ },
+ new Xdr.Extent
+ {
+ Cx = extentsCx,
+ Cy = extentsCy
+ },
+ new Xdr.Picture(
+ new Xdr.NonVisualPictureProperties(
+ new Xdr.NonVisualDrawingProperties { Id = nvpId, Name = picture.Name },
+ new Xdr.NonVisualPictureDrawingProperties(new PictureLocks { NoChangeAspect = true, NoMove = true, NoResize = true })
+ ),
+ new Xdr.BlipFill(
+ new Blip { Embed = drawingsPart.GetIdOfPart(imagePart), CompressionState = BlipCompressionValues.Print },
+ new Stretch(new FillRectangle())
+ ),
+ new Xdr.ShapeProperties(
+ new Transform2D(
+ new Offset { X = 0, Y = 0 },
+ new Extents { Cx = extentsCx, Cy = extentsCy }
+ ),
+ new PresetGeometry { Preset = ShapeTypeValues.Rectangle }
+ )
+ ),
+ new Xdr.ClientData()
+ );
+
+ worksheetDrawing.Append(absoluteAnchor);
+ }
+ else
+ {
+ var markers = picture.GetMarkers();
+ Xdr.FromMarker fMark;
+ Xdr.ToMarker tMark;
+ if (markers.Count == 2)
+ {
+ fMark = new Xdr.FromMarker
+ {
+ ColumnId = new Xdr.ColumnId(markers[0].GetZeroBasedColumn().ToString()),
+ RowId = new Xdr.RowId(markers[0].GetZeroBasedRow().ToString()),
+ ColumnOffset = new Xdr.ColumnOffset((markers[0].ColumnOffset + picture.OffsetX).ToString()),
+ RowOffset = new Xdr.RowOffset((markers[0].RowOffset + picture.OffsetY).ToString())
+ };
+ tMark = new Xdr.ToMarker
+ {
+ ColumnId = new Xdr.ColumnId(markers[1].GetZeroBasedColumn().ToString()),
+ RowId = new Xdr.RowId(markers[1].GetZeroBasedRow().ToString()),
+ ColumnOffset = new Xdr.ColumnOffset((markers[1].ColumnOffset + picture.OffsetX).ToString()),
+ RowOffset = new Xdr.RowOffset((markers[1].RowOffset + picture.OffsetY).ToString())
+ };
+
+ Xdr.TwoCellAnchor cellAnchor;
+ cellAnchor = new Xdr.TwoCellAnchor(
+ fMark,
+ tMark,
+ new Xdr.Picture(
+ new Xdr.NonVisualPictureProperties(
+ new Xdr.NonVisualDrawingProperties { Id = nvpId, Name = picture.Name },
+ new Xdr.NonVisualPictureDrawingProperties(new PictureLocks { NoChangeAspect = true, NoMove = true, NoResize = true })
+ ),
+ new Xdr.BlipFill(
+ new Blip { Embed = drawingsPart.GetIdOfPart(imagePart), CompressionState = BlipCompressionValues.Print },
+ new Stretch(new FillRectangle())
+ ),
+ new Xdr.ShapeProperties(
+ new Transform2D(
+ new Offset { X = 0, Y = 0 },
+ new Extents { Cx = extentsCx, Cy = extentsCy }
+ ),
+ new PresetGeometry { Preset = ShapeTypeValues.Rectangle }
+ )
+ ),
+ new Xdr.ClientData()
+ );
+
+ worksheetDrawing.Append(cellAnchor);
+ }
+ else if (markers.Count == 1)
+ {
+ fMark = new Xdr.FromMarker
+ {
+ ColumnId = new Xdr.ColumnId(markers[0].GetZeroBasedColumn().ToString()),
+ RowId = new Xdr.RowId(markers[0].GetZeroBasedRow().ToString()),
+ ColumnOffset = new Xdr.ColumnOffset((markers[0].ColumnOffset + picture.OffsetX).ToString()),
+ RowOffset = new Xdr.RowOffset((markers[0].RowOffset + picture.OffsetY).ToString())
+ };
+
+ Xdr.OneCellAnchor cellAnchor;
+ cellAnchor = new Xdr.OneCellAnchor(
+ fMark,
+ new Xdr.Extent
+ {
+ Cx = extentsCx,
+ Cy = extentsCy
+ },
+ new Xdr.Picture(
+ new Xdr.NonVisualPictureProperties(
+ new Xdr.NonVisualDrawingProperties { Id = nvpId, Name = picture.Name },
+ new Xdr.NonVisualPictureDrawingProperties(new PictureLocks { NoChangeAspect = true, NoMove = true, NoResize = true })
+ ),
+ new Xdr.BlipFill(
+ new Blip { Embed = drawingsPart.GetIdOfPart(imagePart), CompressionState = BlipCompressionValues.Print },
+ new Stretch(new FillRectangle())
+ ),
+ new Xdr.ShapeProperties(
+ new Transform2D(
+ new Offset { X = 0, Y = 0 },
+ new Extents { Cx = extentsCx, Cy = extentsCy }
+ ),
+ new PresetGeometry { Preset = ShapeTypeValues.Rectangle }
+ )
+ ),
+ new Xdr.ClientData()
+ );
+
+ worksheetDrawing.Append(cellAnchor);
+ }
+ }
+ }
+
+ private static Regex embedRegex = new Regex("[^a-zA-Z0-9]");
+
+ public static string GeneratePartId(string name, OpenXmlPart oxp)
+ {
+ var partId = name ?? "rId1";
+ partId = embedRegex.Replace(partId, "");
+
+ // We guarantee the id uniqueness based off the name
+ try
+ {
+ oxp.GetPartById(partId);
+ }
+ catch(ArgumentOutOfRangeException)
+ {
+ return partId;
+ }
+
+ partId += "c";
+ return GeneratePartId(partId, oxp);
+ }
+
private static Vml.TextBox GetTextBox(IXLDrawingStyle ds)
{
var sb = new StringBuilder();
@@ -4564,19 +4744,6 @@
#endregion
- #region Drawings
-
- //worksheetPart.Worksheet.RemoveAllChildren();
- //{
- // OpenXmlElement previousElement = cm.GetPreviousElementFor(XLWSContentManager.XLWSContents.Drawing);
- // worksheetPart.Worksheet.InsertAfter(new Drawing() { Id = String.Format("rId{0}", 1) }, previousElement);
- //}
-
- //Drawing drawing = worksheetPart.Worksheet.Elements().First();
- //cm.SetElement(XLWSContentManager.XLWSContents.Drawing, drawing);
-
- #endregion
-
#region Tables
worksheetPart.Worksheet.RemoveAllChildren();
@@ -4596,6 +4763,26 @@
#endregion
+ #region Drawings
+
+ var pics = xlWorksheet.Pictures();
+ if (pics != null)
+ {
+ foreach (Drawings.IXLPicture pic in pics)
+ {
+ AddPictureAnchor(worksheetPart, pic);
+ }
+ }
+
+ if (xlWorksheet.Pictures() != null && xlWorksheet.Pictures().Count > 0)
+ {
+ Drawing worksheetDrawing = new Drawing { Id = worksheetPart.GetIdOfPart(worksheetPart.DrawingsPart) };
+ worksheetDrawing.AddNamespaceDeclaration("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
+ worksheetPart.Worksheet.InsertBefore(worksheetDrawing, tableParts);
+ }
+
+ #endregion
+
#region LegacyDrawing
if (xlWorksheet.LegacyDrawingIsNew)
diff --git a/ClosedXML/Excel/XLWorksheet.cs b/ClosedXML/Excel/XLWorksheet.cs
index 227ee49..132ec9d 100644
--- a/ClosedXML/Excel/XLWorksheet.cs
+++ b/ClosedXML/Excel/XLWorksheet.cs
@@ -96,6 +96,7 @@
public Boolean LegacyDrawingIsNew;
private Double _columnWidth;
public XLWorksheetInternals Internals { get; private set; }
+ private List pictures;
public override IEnumerable Styles
{
@@ -1491,5 +1492,19 @@
}
public String Author { get; set; }
+
+ public List Pictures()
+ {
+ return pictures;
+ }
+
+ public void AddPicture(Drawings.XLPicture pic)
+ {
+ if (pictures == null)
+ {
+ pictures = new List();
+ }
+ pictures.Add(pic);
+ }
}
}
diff --git a/ClosedXML_Examples/ClosedXML_Examples.csproj b/ClosedXML_Examples/ClosedXML_Examples.csproj
index 2daeac4..de7cb8e 100644
--- a/ClosedXML_Examples/ClosedXML_Examples.csproj
+++ b/ClosedXML_Examples/ClosedXML_Examples.csproj
@@ -44,6 +44,9 @@
ClosedXML.snk
+
+ true
+
..\packages\DocumentFormat.OpenXml.2.7.1\lib\net45\DocumentFormat.OpenXml.dll
@@ -74,6 +77,8 @@
+
+
@@ -174,6 +179,12 @@
ClosedXML
+
+
+
+
+
+