Inserting Image into DocX using OpenXML and setting the size
Since this is one of the first hits when searching for images and OpenXml, and the world has turned a bit further I'd like to share @ssarabando's solution for calculating the EMUs adapted for .net core where System.Drawing
is not available, using ImageSharp (still beta as of nov 2018):
const int emusPerInch = 914400;
const int emusPerCm = 360000;
long widthEmus;
long heightEmus;
Image<Rgba32> img = Image.Load(sourceStream, new PngDecoder());
switch (img.MetaData.ResolutionUnits)
{
case PixelResolutionUnit.PixelsPerCentimeter :
widthEmus = (long)(img.Width / img.MetaData.HorizontalResolution * emusPerCm);
heightEmus = (long)(img.Height / img.MetaData.VerticalResolution * emusPerCm);
break;
case PixelResolutionUnit.PixelsPerInch:
widthEmus = (long)(img.Width / img.MetaData.HorizontalResolution * emusPerInch);
heightEmus = (long)(img.Height / img.MetaData.VerticalResolution * emusPerInch);
break;
case PixelResolutionUnit.PixelsPerMeter:
widthEmus = (long)(img.Width / img.MetaData.HorizontalResolution * emusPerCm * 100);
heightEmus = (long)(img.Height / img.MetaData.VerticalResolution * emusPerCm * 100);
break;
default:
widthEmus = 2000000;
heightEmus = 2000000;
break;
}
Hope it will save someone's time.
This code works for me.
Source: http://msdn.microsoft.com/en-us/library/office/bb497430(v=office.15).aspx
public static void Do()
{
string filename = @"c:\temp\m.docx";
byte[] reportData = GetWordReport();
// File.WriteAllBytes(filename, reportData);
//MessageBox.Show("File " + filename + " created");
}
private static byte[] GetWordReport()
{
// using (MemoryStream stream = new MemoryStream())
// {
//var template = GetTemplateData();
//stream.Write(template, 0, template.Length);
using (WordprocessingDocument docx = WordprocessingDocument.Open(@"c:\temp\m.docx", true))
{
// Some changes on docx
docx.MainDocumentPart.Document = GenerateMainDocumentPart(6,4);
var imagePart = docx.MainDocumentPart.AddNewPart<ImagePart>("image/jpeg", "rIdImagePart1");
GenerateImagePart(imagePart);
}
// stream.Seek(0, SeekOrigin.Begin);
// return stream.ToArray();
// }
return null;
}
private static byte[] GetTemplateData()
{
using (MemoryStream targetStream = new MemoryStream())
using (BinaryReader sourceReader = new BinaryReader(File.Open(@"c:\temp\m_2.docx", FileMode.Open)))
{
byte[] buffer = new byte[4096];
int num = 0;
do
{
num = sourceReader.Read(buffer, 0, 4096);
if (num > 0)
targetStream.Write(buffer, 0, num);
}
while (num > 0);
targetStream.Seek(0, SeekOrigin.Begin);
return targetStream.ToArray();
}
}
private static void GenerateImagePart(OpenXmlPart part)
{
using (Stream imageStream = File.Open(@"c:\temp\image002.jpg", FileMode.Open))
{
part.FeedData(imageStream);
}
}
private static Document GenerateMainDocumentPart(int cx,int cy)
{
long LCX = cx*261257L;
long LCY = cy*261257L;
var element =
new Document(
new Body(
new Paragraph(
new Run(
new RunProperties(
new NoProof()),
new Drawing(
new wp.Inline(
new wp.Extent() { Cx = LCX, Cy = LCY },
new wp.EffectExtent() { LeftEdge = 0L, TopEdge = 19050L, RightEdge = 0L, BottomEdge = 0L },
new wp.DocProperties() { Id = (UInt32Value)1U, Name = "Picture 0", Description = "Forest Flowers.jpg" },
new wp.NonVisualGraphicFrameDrawingProperties(
new a.GraphicFrameLocks() { NoChangeAspect = true }),
new a.Graphic(
new a.GraphicData(
new pic.Picture(
new pic.NonVisualPictureProperties(
new pic.NonVisualDrawingProperties() { Id = (UInt32Value)0U, Name = "Forest Flowers.jpg" },
new pic.NonVisualPictureDrawingProperties()),
new pic.BlipFill(
new a.Blip() { Embed = "rIdImagePart1", CompressionState = a.BlipCompressionValues.Print },
new a.Stretch(
new a.FillRectangle())),
new pic.ShapeProperties(
new a.Transform2D(
new a.Offset() { X = 0L, Y = 0L },
new a.Extents() { Cx = LCX, Cy = LCY }),
new a.PresetGeometry(
new a.AdjustValueList()
) { Preset = a.ShapeTypeValues.Rectangle }))
) { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" })
) { DistanceFromTop = (UInt32Value)0U, DistanceFromBottom = (UInt32Value)0U, DistanceFromLeft = (UInt32Value)0U, DistanceFromRight = (UInt32Value)0U }))
) { RsidParagraphAddition = "00A2180E", RsidRunAdditionDefault = "00EC4DA7" },
new SectionProperties(
new PageSize() { Width = (UInt32Value)11906U, Height = (UInt32Value)16838U },
new PageMargin() { Top = 1440, Right = (UInt32Value)1800U, Bottom = 1440, Left = (UInt32Value)1800U, Header = (UInt32Value)851U, Footer = (UInt32Value)992U, Gutter = (UInt32Value)0U },
new Columns() { Space = ((UInt32Value)425U).ToString() },
new DocGrid() { Type = DocGridValues.Lines, LinePitch = 312 }
) { RsidR = "00A2180E", RsidSect = "00A2180E" }));
return element;
}
The sizes, in EMUs (English Metric Unit -- read this for a good explanation), are set in the Extents (the Cx and Cy). In order to get a pic into a DocX I usually do it like so:
- Get the image's dimensions and resolution
- Compute the image's width in EMUs: wEmu = imgWidthPixels / imgHorizontalDpi * emuPerInch
- Compute the image's height in EMUs: hEmu = imgHeightPixels / imgVerticalDpi * emuPerInch
- Compute the max page width in EMUs (I've found that if the image is too wide, it won't show)
If the image's width in EMUs is greater than the max page width's, I scale the image's width and height so that the width of image is equal to that of the page (when I say page, I'm referring to the "usable" page, that is minus the margins):
var ratio = hEmu / wEmu;
wEmu = maxPageWidthEmu;
hEmu = wEmu * ratio;
I then use the width as the value of Cx and height as the value of Cy, both in the
DW.Extent
and in theA.Extents
(of theA.Transform2D
of thePIC.ShapeProperties
).
Note that the emuPerInch value is 914400.
I have this running (in a service) but I don't have the code with me right now.
UPDATE
This is the code I use:
var img = new BitmapImage(new Uri(fileName, UriKind.RelativeOrAbsolute));
var widthPx = img.PixelWidth;
var heightPx = img.PixelHeight;
var horzRezDpi = img.DpiX;
var vertRezDpi = img.DpiY;
const int emusPerInch = 914400;
const int emusPerCm = 360000;
var widthEmus = (long)(widthPx / horzRezDpi * emusPerInch);
var heightEmus = (long)(heightPx / vertRezDpi * emusPerInch);
var maxWidthEmus = (long)(maxWidthCm * emusPerCm);
if (widthEmus > maxWidthEmus) {
var ratio = (heightEmus * 1.0m) / widthEmus;
widthEmus = maxWidthEmus;
heightEmus = (long)(widthEmus * ratio);
}
In my case, my page's measures are in cm, hence the emusPerCm you see above.
UPDATE 2 (to answer @andw)
To have the file locked during the minimum possible time, open it with a FileStream
and pass the resulting stream to the StreamSource
property of the BitmapImage
:
var img = new BitmapImage();
using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) {
img.BeginInit();
img.StreamSource = fs;
img.EndInit();
}
// The file is now unlocked
var widthPx = img.PixelWidth;
...
You can give to the image its original size this way:
First you get the width and height of the file:
int iWidth = 0;
int iHeight = 0;
using (System.Drawing.Bitmap bmp = new System.Drawing.Bitmap("yourFilePath"))
{
iWidth = bmp.Width;
iHeight = bmp.Height;
}
Then convert the pixels to EMUs this way:
iWidth = (int)Math.Round((decimal)iWidth * 9525);
iHeight = (int)Math.Round((decimal)iHeight * 9525);
And finally give the values when opening your file, I mean in this line of your code:
new DW.Extent() { Cx = 990000L, Cy = 792000L },
replace Cx and Cy with your calculated values so it looks like this:
new DW.Extent() { Cx = iWidth, Cy = iHeight },
Notice there is two lines in your code where you have to give the calculated values.
Then you have your image in its original size, hope this helps someone.