Reputation: 3908
I am creating an OpenXML Word Document inside a C# Web Application and am writing a Google Chart Image to it. I'm specifying the dimensions of the image, it seems, correctly. However, when I open the Word Document, the image is the right height, but not the right width. In fact, it is almost a square.
When I click on the image inside of Word and go to Size and Position, the correct dimensions are displayed. In this case, it is 2.08" by 5.04". When I click "OK" after changing nothing, the image stretches to the right size.
If I try to identify what size it originally appears as, it is fairly close to half the defined width, but that may be a coincidence.
The native size of the image is actually 3.13" (h) by 7.55" (w), but that doesn't seem like it should matter.
I followed the following posts pretty closely:
I'm guessing I'm just missing a setting of some sort, but I don't know which one. Does the Paragraph or the Run need to be sized to the entire width of the page? Does the width of the document itself need to be specified?
public void AddImage(string imageName, byte[] imageData)
{
var img = System.Drawing.Image.FromStream(new MemoryStream(imageData));
MainDocumentPart mainPart = doc.MainDocumentPart;
ImagePart imagePart = mainPart.AddImagePart(ImagePartType.Png);
MemoryStream imageStream = new MemoryStream(imageData);
imagePart.FeedData(imageStream);
AddImageToBody(mainPart.GetIdOfPart(imagePart), img);
}
private void AddImageToBody(string relationshipId, System.Drawing.Image img)
{
var widthPx = img.Width;
var heightPx = img.Height;
var horzRezDpi = img.HorizontalResolution;
var vertRezDpi = img.VerticalResolution;
const int emusPerInch = 914400;
var widthEmus = (long)(widthPx / horzRezDpi * emusPerInch);
var heightEmus = (long)(heightPx / vertRezDpi * emusPerInch);
var maxWidthEmus = (long)(6.5 * emusPerInch);
if (widthEmus > maxWidthEmus)
{
var ratio = (heightEmus * 1.0m) / widthEmus;
widthEmus = maxWidthEmus;
heightEmus = (long)(widthEmus * ratio);
}
// Define the reference of the image.
var element =
new Drawing(
new DW.Inline(
new DW.Extent() { Cx = widthEmus, Cy = heightEmus }, // Cx = 990000L, Cy = 792000L },
new DW.EffectExtent()
{
LeftEdge = 0L,
TopEdge = 0L,
RightEdge = 0L,
BottomEdge = 0L
},
new DW.DocProperties()
{
Id = (UInt32Value)1U,
Name = "Picture 1"
},
new DW.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 = "New Bitmap Image.jpg"
},
new PIC.NonVisualPictureDrawingProperties()),
new PIC.BlipFill(
new A.Blip(
new A.BlipExtensionList(
new A.BlipExtension()
{
Uri =
"{28A0092B-C50C-407E-A947-70E740481C1C}"
})
)
{
Embed = relationshipId,
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 = 990000L, Cy = 792000L }),
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,
EditId = "50D07946"
});
// Append the reference to body, the element should be in a Run.
var p = new Paragraph();
doc.MainDocumentPart.Document.Body.AppendChild(new Paragraph(new Run(element)));
}
Upvotes: 4
Views: 4842
Reputation: 504
this is a pretty old question but I think it's worth answering anyway since this seems to come up a lot.
I also used the above sample and it works well, except that cx and cy are hard-coded and distort the image.
I created a class to handle my image then I use it to add detail to the element which I then can use to either append to the document or append to a paragraph etc.
EDIT: I added an ID field in ImageDetails because when I added several images to the document I needed to update the Id. By creating collections and a CounterState class, I was able to track the Id and make sure it was unique. If anyone wants to see this implementation, let me know in the comments and I'll include it in this answer.
So... The class is a little specific to what I required in my solution, but take what you need.
public class ImageDetails
{
// required to set the image id which needs to be unique in the document
// which is important if you're adding several images
public UInt32 Id { get; set; }
public ImageDetails(string fileName)
{
this.ImageFile = fileName;
}
#region Properties
private decimal _widthInCm;
/// <summary>
///
/// </summary>
/// <summary>
/// Sets the width in cm (note this is a user set to calculate cx. Leave blank for the class to calculate the cx based on pixels)
/// </summary>
public decimal WidthInCm
{
set { _widthInCm = value; }
}
private decimal _heightInCm;
/// <summary>
/// Sets the height in cm (note this is a user set to calculate cy. Leave blank for the class to calculate the cy based on pixels)
/// </summary>
public decimal HeightInCm
{
set { _heightInCm = value; }
}
const int emusPerInch = 914400;
const int emusPerCm = 360000;
/// <summary>
/// Returns the width in EMUS (English Metric Units)
/// </summary>
public long cx
{
get
{
if (_widthInCm > 0)
return (long)Math.Round(_widthInCm * emusPerCm);
else if (_image.Width > 0)
return (long)Math.Round((_image.Width / _image.HorizontalResolution) * emusPerInch);
else
{
throw new InvalidDataException("WidthInCm/WidthInPx has not been set");
}
}
}
/// <summary>
/// Returns the height in EMUS (English Metric Units)
/// </summary>
public long cy
{
get
{
if (_heightInCm > 0)
return (long)decimal.Round(_heightInCm * emusPerCm);
else if (_image.Height > 0)
return (long)Math.Round((_image.Height / _image.VerticalResolution) * emusPerInch);
else
{
throw new InvalidDataException("HeightInCm/HeightInPx has not been set");
}
}
}
public int WidthInPx
{
get { return _image.Width; }
}
public int HeightInPx
{
get { return _image.Height; }
}
private string _imageFileName;
private Image _image;
/// <summary>
/// Sets the Image file name and loads the Image object for later use
/// </summary>
public string ImageFile
{
get { return _imageFileName; }
set
{
_imageFileName = value;
// Limiting the time the image file is open in case others require it
using (var fs = new FileStream(value, FileMode.Open, FileAccess.Read, FileShare.Read))
{
_image = Image.FromStream(fs);
}
}
}
/// <summary>
/// Allows direct read/write access to the internal Image Object
/// </summary>
public Image ImageObject
{
get { return _image; }
set { _image = value; }
}
#endregion
/// <summary>
/// This method resizes the image and replaces the internal Drawing.Image object with the new size
/// </summary>
/// <param name="targetWidth">New target width in px. The aspect ratio is maintained</param>
public void ResizeImage(int targetWidth)
{
if (_image == null)
throw new InvalidOperationException("The Image has not been referenced. Add an image first using .ImageFile or .ImageObject");
double percent = (double)_image.Width / targetWidth;
int destWidth = (int)(_image.Width / percent);
int destHeight = (int)(_image.Height / percent);
Bitmap b = new Bitmap(destWidth, destHeight);
Graphics g = Graphics.FromImage((Image)b);
try
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
g.DrawImage(_image, 0, 0, destWidth, destHeight);
}
finally
{
g.Dispose();
}
_image = (Image)b;
}
}
If you need to resize the image beforehand, just call:
ImageDetails image = new ImageDetails("image.png");
image.Resize(45); // set new image to a width of 45px. Height is automatically adjusted
I have then used this in the image method:
private Drawing CreateImageElement(WordprocessingDocument wordDoc, string relationshipId, ImageDetails image)
{
// Define the reference of the image.
return
new Drawing(
new DW.Inline(
new DW.Extent() { Cx = image.cx, Cy = image.cy},
new DW.EffectExtent()
{
LeftEdge = 0L,
TopEdge = 0L,
RightEdge = 0L,
BottomEdge = 0L
},
new DW.DocProperties()
{
Id = image.Id,
Name = FileManipulation.GetFileName(image.ImageFile)
},
new DW.NonVisualGraphicFrameDrawingProperties(
new A.GraphicFrameLocks() { NoChangeAspect = true }),
new A.Graphic(
new A.GraphicData(
new PIC.Picture(
new PIC.NonVisualPictureProperties(
new PIC.NonVisualDrawingProperties()
{
Id = image.Id,
Name = Path.GetFileName(image.ImageFile)
},
new PIC.NonVisualPictureDrawingProperties()),
new PIC.BlipFill(
new A.Blip(
new A.BlipExtensionList(
new A.BlipExtension()
{
Uri =
"{28A0092B-C50C-407E-A947-70E740481C1C}"
})
)
{
Embed = relationshipId,
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 = image.cx, Cy = image.cy}),
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,
EditId = "50D07946"
});
}
Hope this helps someone.
Upvotes: 5
Reputation: 6856
According to Your code, you have to set Cx
and Cy
values for both Extent
properties.
new DW.Extent() { Cx = widthEmus, Cy = heightEmus }
First one for Inline
and second for Transform2D
.
Upvotes: 1
Reputation: 3908
Oops. There are two places where the size is set. I missed the second.
Upvotes: -2