I am using Epplus library to generate Excel 2010 and up compatible files in Asp.Net C#.
I am using version 3.1.2 which is the latest at this moment.
I am setting the row height first, before adding any pictures like this:
ExcelPackage pck = new ExcelPackage();
var ws = pck.Workbook.Worksheets.Add("sheet 1");
while (i < dt.Rows.Count + offset)
{
ws.Row(i).Height = 84;
i++;
}
dt is my DataTable with DataRows.
After setting the height, I am looping again through the rows to add the pictures
while (i < dt.Rows.Count + offset)
{
var prodImg = ws.Drawings.AddPicture(dr["code"].ToString(), new FileInfo(path));
prodImg.SetPosition(i - 1, 0, 14, 0);
prodImg.SetSize(75);
}
This works, but this does not:
var prodImg = ws.Drawings.AddPicture(dr["code"].ToString(), new FileInfo(path));
int w = prodImg.Image.Width;
int h = prodImg.Image.Height;
if (h > 140) // because height of 84 is 140 pixels in excel
{
double scale = h / 140.0;
w = (int)Math.Floor(w / scale);
h = 140;
}
int xOff = (150 - w) / 2;
int yOff = (140 - h) / 2;
prodImg.SetPosition(i - 1, xOff, 11, yOff);
prodImg.SetSize(w, h);
This results in off center pictures and unresized images. And this code then which is in the same loop:
var prodImgDm = ws.Drawings.AddPicture("bcdm" + dr["code"].ToString(), new FileInfo(pathDm));
prodImgDm.SetPosition(i - 1, 25, 15, 40);
prodImgDm.SetSize(100);
This does work sometimes. the pictures prodImgDm are datamatrix images with a static width and height and do not need to be resized because they are always small/tiny. So also without the SetSize in some rows, it works and in some other rows, it does not work. Really strange because the code is the same. It might be something in the library and/or Excel. Perhaps I am using it wrong? Any epplus picture expert?
Thanks in advance!!
edit sometimes a picture is worth a thousand words, so here is the screenshot. As you can see the product images are not horizontal and vertical aligned in the cell. And the datamatrix on the far right is sometimes scaled about 120% even when I set SetSize(100) so it is really strange to me. So the last datamatrix has the correct size... I already found this SO thread but that does not help me out, I think.
edit 2013/04/09 Essenpillai gave me a hint to set
pck.DoAdjustDrawings = false;
but that gave me even stranger images:
the datamatrix is still changing on row basis. on row is ok, the other is not. and the ean13 code is too wide.
public static void CreatePicture(ExcelWorksheet worksheet, string name, Image image, int firstColumn, int lastColumn, int firstRow, int lastRow, int defaultOffsetPixels)
{
int columnWidth = GetWidthInPixels(worksheet.Cells[firstRow, firstColumn]);
int rowHeight = GetHeightInPixels(worksheet.Cells[firstRow, firstColumn]);
int totalColumnWidth = columnWidth * (lastColumn - firstColumn + 1);
int totalRowHeight = rowHeight * (lastRow - firstRow + 1);
double cellAspectRatio = Convert.ToDouble(totalColumnWidth) / Convert.ToDouble(totalRowHeight);
int imageWidth = image.Width;
int imageHeight = image.Height;
double imageAspectRatio = Convert.ToDouble(imageWidth) / Convert.ToDouble(imageHeight);
int pixelWidth;
int pixelHeight;
if (imageAspectRatio > cellAspectRatio)
{
pixelWidth = totalColumnWidth - defaultOffsetPixels * 2;
pixelHeight = pixelWidth * imageHeight / imageWidth;
}
else
{
pixelHeight = totalRowHeight - defaultOffsetPixels * 2;
pixelWidth = pixelHeight * imageWidth / imageHeight;
}
int rowOffsetPixels = (totalRowHeight - pixelHeight) / 2;
int columnOffsetPixels = (totalColumnWidth - pixelWidth) / 2;
int rowOffsetCount = 0;
int columnOffsetCount = 0;
if (rowOffsetPixels > rowHeight)
{
rowOffsetCount = (int)Math.Floor(Convert.ToDouble(rowOffsetPixels) / Convert.ToDouble(rowHeight));
rowOffsetPixels -= rowHeight * rowOffsetCount;
}
if (columnOffsetPixels > columnWidth)
{
columnOffsetCount = (int)Math.Floor(Convert.ToDouble(columnOffsetPixels) / Convert.ToDouble(columnWidth));
columnOffsetPixels -= columnWidth * columnOffsetCount;
}
int row = firstRow + rowOffsetCount - 1;
int column = firstColumn + columnOffsetCount - 1;
ExcelPicture pic = worksheet.Drawings.AddPicture(name, image);
pic.SetPosition(row, rowOffsetPixels, column, columnOffsetPixels);
pic.SetSize(pixelWidth, pixelHeight);
}
public static int GetHeightInPixels(ExcelRange cell)
{
using (Graphics graphics = Graphics.FromHwnd(IntPtr.Zero))
{
float dpiY = graphics.DpiY;
return (int)(cell.Worksheet.Row(cell.Start.Row).Height * (1 / 72.0) * dpiY);
}
}
public static float MeasureString(string s, Font font)
{
using (var g = Graphics.FromHwnd(IntPtr.Zero))
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
return g.MeasureString(s, font, int.MaxValue, StringFormat.GenericTypographic).Width;
}
}
public static int GetWidthInPixels(ExcelRange cell)
{
double columnWidth = cell.Worksheet.Column(cell.Start.Column).Width;
Font font = new Font(cell.Style.Font.Name, cell.Style.Font.Size, FontStyle.Regular);
double pxBaseline = Math.Round(MeasureString("1234567890", font) / 10);
return (int)(columnWidth * pxBaseline);
}
enter image description here
I have the same problem with Epplus library.
After I find no solution how to solve this in my code, I checked source code of this library.
Epplus create excel picture always as twoCellAnchor drawing. In xlsx files you can find drawingXYZ.xml with this code:
<xdr:twoCellAnchor editAs="oneCell">
<xdr:from> ... </xdr:from>
<xdr:to> ... </xdr:to>
<xdr:pic>
...
</xdr:twoCellAnchor>
So, picture is always connected to two cells, and this is not variable part of Epplus library. You can find this part of code in ExcelDrawing.cs file.
XmlElement drawNode = _drawingsXml.CreateElement(
"xdr", "twoCellAnchor", ExcelPackage.schemaSheetDrawings);
colNode.AppendChild(drawNode);
You can easy create your own copy of this dll. The good news is that you need to modify only two files to fix this problem. So..
Download your copy of source codes for Epplus library from this site and open in Visual Studio.
We need to generate code of drawing as oneCellAnchor, so we must remove <xdr:to> element for pictures and create element <xdr:ext /> with picture dimensions as parameters.
New xml structure will looks like:
<xdr:oneCellAnchor editAs="oneCell">
<xdr:from> ... </xdr:from>
<xdr:ext cx="1234567" cy="7654321" />
<xdr:pic>
...
</xdr:oneCellAnchor>
Ok, so, how to do this?
Changes in Epplus code
ExcelDrawings.cs (link to file here)
At first we modify method CreateDrawingXml() inside ExcelDrawings.cs. Order to preserve the original functionality we add an optional parameter (if create oneCellAnchor) with default value. And in method, based this parameter, we create one or tow cell anchor and create or not <xdr:to> element.
Important part of this method code:
private XmlElement CreateDrawingXml(bool twoCell = true) {
if (DrawingXml.OuterXml == "")
{ ... } // not changed
XmlNode colNode= _drawingsXml.SelectSingleNode("//xdr:wsDr", NameSpaceManager);
//First change in method code
XmlElement drawNode;
if (twoCell)
drawNode = _drawingsXml.CreateElement(
"xdr", "twoCellAnchor", ExcelPackage.schemaSheetDrawings);
else
drawNode = _drawingsXml.CreateElement(
"xdr", "oneCellAnchor", ExcelPackage.schemaSheetDrawings);
colNode.AppendChild(drawNode);
//Add from position Element; // Not changed
XmlElement fromNode = _drawingsXml.CreateElement(
"xdr", "from", ExcelPackage.schemaSheetDrawings);
drawNode.AppendChild(fromNode);
fromNode.InnerXml = "<xdr:col>0</xdr:col><xdr:colOff>0</xdr:colOff>"
+ "<xdr:row>0</xdr:row><xdr:rowOff>0</xdr:rowOff>";
//Add to position Element;
//Second change in method
if (twoCell)
{
XmlElement toNode = _drawingsXml.CreateElement(
"xdr", "to", ExcelPackage.schemaSheetDrawings);
drawNode.AppendChild(toNode);
toNode.InnerXml = "<xdr:col>10</xdr:col><xdr:colOff>0</xdr:colOff>"
+ "<xdr:row>10</xdr:row><xdr:rowOff>0</xdr:rowOff>";
}
return drawNode;
}
Then we modify two methods for AddPicture inside the same file:
public ExcelPicture AddPicture(string Name, Image image, Uri Hyperlink)
{
if (image != null) {
if (_drawingNames.ContainsKey(Name.ToLower())) {
throw new Exception("Name already exists in the drawings collection");
}
XmlElement drawNode = CreateDrawingXml(false);
// Change: we need create element with dimensions
// like: <xdr:ext cx="3857625" cy="1047750" />
XmlElement xdrext = _drawingsXml.CreateElement(
"xdr", "ext", ExcelPackage.schemaSheetDrawings);
xdrext.SetAttribute("cx",
(image.Width * ExcelDrawing.EMU_PER_PIXEL).ToString());
xdrext.SetAttribute("cy",
(image.Height * ExcelDrawing.EMU_PER_PIXEL).ToString());
drawNode.AppendChild(xdrext);
// End of change, next part of method is the same:
drawNode.SetAttribute("editAs", "oneCell");
...
}
}
And this method with FileInfo as input parameter:
public ExcelPicture AddPicture(string Name, FileInfo ImageFile, Uri Hyperlink)
{
if (ImageFile != null) {
if (_drawingNames.ContainsKey(Name.ToLower())) {
throw new Exception("Name already exists in the drawings collection");
}
XmlElement drawNode = CreateDrawingXml(false);
// Change: First create ExcelPicture object and calculate EMU dimensions
ExcelPicture pic = new ExcelPicture(this, drawNode, ImageFile, Hyperlink);
XmlElement xdrext = _drawingsXml.CreateElement(
"xdr", "ext", ExcelPackage.schemaSheetDrawings);
xdrext.SetAttribute("cx",
(pic.Image.Width * ExcelDrawing.EMU_PER_PIXEL).ToString());
xdrext.SetAttribute("cy",
(pic.Image.Height * ExcelDrawing.EMU_PER_PIXEL).ToString());
drawNode.AppendChild(xdrext);
// End of change, next part of method is the same (without create pic object)
drawNode.SetAttribute("editAs", "oneCell");
...
}
}
So, this are all important code. Now we must change code for searching nodes and preserve order in elements.
In private void AddDrawings() we change xpath from:
XmlNodeList list = _drawingsXml.SelectNodes(
"//xdr:twoCellAnchor", NameSpaceManager);
To this:
XmlNodeList list = _drawingsXml.SelectNodes(
"//(xdr:twoCellAnchor or xdr:oneCellAnchor)", NameSpaceManager);
It is all in this file, now we change
ExcelPicture.cs (link to file here)
Original code find node for append next code in constructor like this:
node.SelectSingleNode("xdr:to",NameSpaceManager);
Because we do not create <xdr:to> element always, we change this code:
internal ExcelPicture(ExcelDrawings drawings, XmlNode node
, Image image, Uri hyperlink)
: base(drawings, node, "xdr:pic/xdr:nvPicPr/xdr:cNvPr/#name")
{
XmlElement picNode = node.OwnerDocument.CreateElement(
"xdr", "pic", ExcelPackage.schemaSheetDrawings);
// Edited: find xdr:to, or xdr:ext if xdr:to not exists
XmlNode befor = node.SelectSingleNode("xdr:to",NameSpaceManager);
if (befor != null && befor.Name == "xdr:to")
node.InsertAfter(picNode, befor);
else {
befor = node.SelectSingleNode("xdr:ext", NameSpaceManager);
node.InsertAfter(picNode, befor);
}
// End of change, next part of constructor is unchanged
_hyperlink = hyperlink;
...
}
And the same for second constructor with FileInfo as input parameter:
internal ExcelPicture(ExcelDrawings drawings, XmlNode node
, FileInfo imageFile, Uri hyperlink)
: base(drawings, node, "xdr:pic/xdr:nvPicPr/xdr:cNvPr/#name")
{
XmlElement picNode = node.OwnerDocument.CreateElement(
"xdr", "pic", ExcelPackage.schemaSheetDrawings);
// Edited: find xdr:to, or xdr:ext if xdr:to not exists
XmlNode befor = node.SelectSingleNode("xdr:to", NameSpaceManager);
if (befor != null && befor.Name == "xdr:to")
node.InsertAfter(picNode, befor);
else {
befor = node.SelectSingleNode("xdr:ext", NameSpaceManager);
node.InsertAfter(picNode, befor);
}
// End of change, next part of constructor is unchanged
_hyperlink = hyperlink;
...
Now, pictures are created as oneCellAnchor. If you want, you can create multiple AddPicture methods for booth variants. Last step is build this project and create your own custom EPPlus.dll. Then close your project which use this dll and copy new files EPPlus.dll, EPPlus.pdb, EPPlus.XML inside your project (backup and replace your original dll files) at the same place (so you don't need do any change in your project references or settings).
Then open and rebuild your project and try if this solve your problem.
Maybe I am too late, but here is mine answer..
you can read it on codeplex issue as well
(https://epplus.codeplex.com/workitem/14846)
I got this problem as well.
And after some research I figured out where the bug is.
It's in the ExcelRow class on the 149 line of code (ExcelRow.cs file).
There is a mistake, when row's Height got changed it recalcs all pictures heights but uses pictures widths inplace of heights, so it's easy to fix.
Just change the line
var pos = _worksheet.Drawings.GetDrawingWidths();
to
var pos = _worksheet.Drawings.GetDrawingHeight();
See the code changes on image
P.S. Actual for version 4.0.4
Related
Trying to add multiple images to XSSFGroupShape. I have tried the following(modified for simplicity):
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
private void addImagesToGroupShape(Sheet sheet, List<XSSFGroupImage> groupImages) throws Exception{
// XSSFGroupShape dimension
int dx1 = 180975;
int dy1 = 409575;
int dx2 = 219075;
int dy2 = 638175;
int row1 = 9;
int col1 = 8;
int row2 = 38;
int col2 = 19;
XSSFDrawing drawing = (XSSFDrawing) sheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(dx1, dy1, dx2, dy2, col1, row1, col2, row2);
XSSFShapeGroup shapeGroup = drawing.createGroup(anchor);
// XSSFGroupImage is a custom class
// Included below
for (XSSFGroupImage groupImage : groupImages) {
byte[] imageData = Base64.getDecoder().decode(groupImage.data().getBytes());
int picType = 1;
switch (groupImage.extension()) {
case "jpeg":
picType = Workbook.PICTURE_TYPE_JPEG;
break;
case "png":
picType = Workbook.PICTURE_TYPE_PNG;
break;
case "emf":
picType = Workbook.PICTURE_TYPE_EMF;
break;
default:
var msg = "Extension not supported";
throw new Exception(msg);
}
int pictureIndex = sheet.getWorkbook().addPicture(imageData, picType);
// shapeGroup.createPicture(anchor, pictureIndex);
CTPicture ctPic = shapeGroup.getCTGroupShape().insertNewPic(pictureIndex);
CTTransform2D ct2D = ctPic.addNewSpPr().addNewXfrm();
ct2D.addNewOff();
ct2D.addNewExt();
ct2D.getOff().setX(groupImage.offsetX());
ct2D.getOff().setY(groupImage.offsetY());
ct2D.getExt().setCx(Long.parseLong(groupImage.extentsCx()));
ct2D.getExt().setCy(Long.parseLong(groupImage.extentsCy()));
XSSFChildAnchor childAnchor = new XSSFChildAnchor(Integer.parseInt(groupImage.offsetX()),
Integer.parseInt(groupImage.offsetY()), Integer.parseInt(groupImage.extentsCx()),
Integer.parseInt(groupImage.extentsCy()));
// shapeGroup.getCTGroupShape().addNewPic();
// shapeGroup.createPicture(childAnchor, picIndex);
// ctPic.addNewBlipFill().addNewBlip().setEmbed("");
}
}
#Accessors(fluent = true)
public class XSSFGroupImage {
#Getter
#Setter
private String data;
#Getter
#Setter
private String extension;
#Getter
#Setter
private String offsetX;
#Getter
#Setter
private String offsetY;
#Getter
#Setter
private String extentsCx;
#Getter
#Setter
private String extentsCy;
}
Added images outside group and retrieved properties and data of embedded images from group shape.
I understand the fact that an image cannot attached without an anchor. However, I couldn't find a way of adding an XSSFChildAnchor inside the XSSFGroupShape that would allow setting of properites of CTTransform2D object.
I also tried to add the image to the Workbook using int pictureIndex = sheet.getWorkbook().addPicture(imageData, picType) and use that index to set the CTPicture using shapeGroup.getCTGroupShape().insertNewPic(pictureIndex). However, by doing this the relationId that would point to that image is not being set resulting in no image being displayed.
Image of desired output
XSSFShapeGroup is not complete until now (apache 5.0.0 May 2021).
But if pictures shall be in that group then those pictures first must be created at workbook level using Workbook.addPicture. There is no way around.
Having the picture ids then one can put the pictures in the group using XSSFShapeGroup.createPicture. There the XSSFClientAnchor is useless until now and can be simply new XSSFClientAnchor(). Nevertheless what else is set for that XSSFClientAnchor it will not have effect, neither for size of picture nor for position in the group. So after XSSFPicture picture = shapeGroup.createPicture(new XSSFClientAnchor(), pictureIdx1); the picture will be at position 0, 0 and will be of size 0, 0.
The size can be set using XSSFPicture.resize. If that is not possible, then it would must be calculated in unit EMU and set using low level ooxml classes. For example along the lines of
...
XSSFPicture picture = shapeGroup.createPicture(new XSSFClientAnchor(), pictureIdx1);
picture.getCTPicture().getSpPr().getXfrm().getExt().setCx(widthInEMU);
picture.getCTPicture().getSpPr().getXfrm().getExt().setCy(heightInEMU);
...
The position must be set using low level ooxml classes. For position X and Y also EMU is the measurement unit. For example:
...
picture.getCTPicture().getSpPr().getXfrm().getOff().setX(xInEMU);
picture.getCTPicture().getSpPr().getXfrm().getOff().setY(yInEMU);
...
So knowing that, let's provide a complete example. The following works (tested) using apache poi 5.0.0. Please note: The native size of the image files image1.jpeg and image2.jpeg should not be bigger than the anchor for the group frame.
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.Units;
import java.io.FileInputStream;
import java.io.FileOutputStream;
class CreateExcelXSSFShapeGroup {
public static void main(String[] args) throws Exception{
Workbook workbook = new XSSFWorkbook();
CreationHelper helper = workbook.getCreationHelper();
//add picture data to this workbook.
FileInputStream is = new FileInputStream("./image1.jpeg");
byte[] bytes = IOUtils.toByteArray(is);
int pictureIdx1 = workbook.addPicture(bytes, Workbook.PICTURE_TYPE_JPEG);
is.close();
is = new FileInputStream("./image2.jpeg");
bytes = IOUtils.toByteArray(is);
int pictureIdx2 = workbook.addPicture(bytes, Workbook.PICTURE_TYPE_JPEG);
is.close();
Sheet sheet = workbook.createSheet("Sheet1");
Drawing drawing = sheet.createDrawingPatriarch();
//From here on XSSF only.
XSSFDrawing xssfdrawing = (XSSFDrawing)drawing;
XSSFClientAnchor anchor = (XSSFClientAnchor)helper.createClientAnchor();
int groupShapeStartsAtCol = 1;
int groupShapeStartsAtRow = 1;
anchor.setCol1(groupShapeStartsAtCol);
anchor.setRow1(groupShapeStartsAtRow);
int groupWidthInCols = 8;
anchor.setCol2(groupShapeStartsAtCol + groupWidthInCols);
int groupHeightInRows = 20;
anchor.setRow2(groupShapeStartsAtRow + groupHeightInRows);
XSSFShapeGroup shapeGroup = xssfdrawing.createGroup(anchor); // group shape is now of size from anchor abowe
XSSFPicture picture = shapeGroup.createPicture(new XSSFClientAnchor(), pictureIdx1);
//picture.resize(); // picture is now at pos 0, 0 (top left) of group shape and of it's native size
picture.getCTPicture().getSpPr().getXfrm().getExt().setCx(1234567);
picture.getCTPicture().getSpPr().getXfrm().getExt().setCy(1234567); // picture is now at pos 0, 0 (top left) of group shape and of size 1234567 x 1234567 EMU
picture = shapeGroup.createPicture(new XSSFClientAnchor(), pictureIdx2);
picture.resize(); // picture is now at pos 0, 0 (top left) of group shape and of it's native size
// picture shall be at position bottom right of group shape
// so top left of picture must be at x = group width - picture width and y = group height - picture height
int pictureWidthInPx = (int)Math.round(picture.getImageDimension().getWidth());
int pictureHeightInPx = (int)Math.round(picture.getImageDimension().getHeight());
int defaultColWidthInPx = Math.round(sheet.getColumnWidthInPixels(0));
int defaultRowHeightInPx = Units.pointsToPixel(sheet.getDefaultRowHeightInPoints());
int xInEMU = Units.pixelToEMU(groupWidthInCols * defaultColWidthInPx - pictureWidthInPx);
picture.getCTPicture().getSpPr().getXfrm().getOff().setX(xInEMU);
int yInEMU = Units.pixelToEMU(groupHeightInRows * defaultRowHeightInPx - pictureHeightInPx);
picture.getCTPicture().getSpPr().getXfrm().getOff().setY(yInEMU);
FileOutputStream out = new FileOutputStream("CreateExcelXSSFShapeGroup.xlsx");
workbook.write(out);
out.close();
workbook.close();
}
}
I am creating a test framework using selenium, testNG, and Apache POI. So far I have created a piece of code that will get each row from my spreadsheet and insert it into the test. However, on adding a new row it simply ignores the added row. Any help is greatly appreciated.
Code for getting inputs:
#DataProvider(name="ExcelData")
public Object[][] passData() {
ExcelDataConfig config = new ExcelDataConfig("C:\\Users\\dindo\\Documents\\tests\\d2c-lv-int-01_DATA.xlsx");
int rows = config.getRowCount(0);
int frows = rows-2;
Object[][] data = new Object[frows][11];
for(int i=2;i<rows;i++)
{
data[i-2][0]=config.getStrData(0, i, 0);
data[i-2][1]=config.getIntData(0, i, 1);
data[i-2][2]=config.getIntData(0, i, 2);
data[i-2][3]=config.getStrData(0, i, 3);
data[i-2][4]=config.getStrData(0, i, 4);
data[i-2][5]=config.getStrData(0, i, 5);
data[i-2][6]=config.getIntData(0, i, 6);
data[i-2][7]=config.getIntData(0, i, 7);
data[i-2][8]=config.getIntData(0, i, 8);
data[i-2][9]=config.getStrData(0, i, 9);
data[i-2][10]=config.getStrData(0, i, 10);
}
return data;
}
Code for Excel sheet:
public ExcelDataConfig(String excelPath) {
try {
File src = new File(excelPath);
FileInputStream fis = new FileInputStream(src);
wb = new XSSFWorkbook(fis);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
public String getStrData(int sheetNumber,int row, int col) {
Details = wb.getSheetAt(sheetNumber);
String strdata = Details.getRow(row).getCell(col).getStringCellValue();
return strdata;
}
public String getIntData(int sheetNumber,int row, int col) throws NullPointerException{
Details = wb.getSheetAt(sheetNumber);
double doubledata = Details.getRow(row).getCell(col).getNumericCellValue();
int intdata = (int) doubledata;
String strdata = Integer.toString(intdata);
return strdata;
}
public int getRowCount(int sheetIndex)
{
int row = wb.getSheetAt(sheetIndex).getLastRowNum();
return row;
}
Also, is it possible to merge getIntData and getStrData into one function which automatically knows if it is a string or integer.
Many Thanks in advance.
After some extensive keyboard thrashing I finally worked out the chink. It was simply that as Apache POI gets the first row in an xlsx file as 0, there was 1 to less rows in the array. Stupidly enough I found this out by accidentally saving my sheet with an extra letter in the row below the last.😂
I am using the following code to place an image on a spreadsheet:
var ms = new MemoryStream();
Image _logo = RoboReporterConstsAndUtils.GetURLImage("http://www.proactusa.com/bla/pa_logo_notag.png");
_logo.Save(ms, ImageFormat.Png);
ms.Position = 0;
locationWorksheet.Pictures.Add(0, 4, ms);
AutoFitterOptions options = new AutoFitterOptions { OnlyAuto = true };
locationWorksheet.AutoFitRows(options);
It works fine; however, I use this same code on two different reports, and the image displays at different sizes. On one it has a height of 0.85" (63%) and a width of 1.1" (53%), while on the other it has a height of 1.44" (106%) and a width of 2.07" (100%).
Why would they differ in size? And why wouldn't they be 100% of the original image size?
The other code, which seems to be exactly the same (although in this case the column at which the image appears is dynamic), is:
var ms = new MemoryStream();
Image _logo = RoboReporterConstsAndUtils.GetURLImage("http://www.proactusa.com/bla/pa_logo_notag.png");
_logo.Save(ms, ImageFormat.Png);
ms.Position = 0;
pivotTableSheet.Pictures.Add(0, _grandTotalsColumnPivotTable - 1, ms);
AutoFitterOptions options = new AutoFitterOptions { OnlyAuto = true };
pivotTableSheet.AutoFitRows(options);
The image itself, at the location referenced, has a height of 1.35" and a width of 2.07"
The method called is:
internal static Image GetURLImage(string url)
{
WebClient wc = new WebClient();
byte[] bytes = wc.DownloadData(url);
MemoryStream ms = new MemoryStream(bytes);
return Image.FromStream(ms);
}
How can I get the image to always display at 100%, or at least at a given size?
UPDATE
I also have (at least for now) some reports in the same project that are generated using EPPlus. In these, I have the following code, which allows me to set the exact size of the image:
private void AddImage(ExcelWorksheet oSheet, int rowIndex, int colIndex)
{
Image _logo = RoboReporterConstsAndUtils.GetURLImage("http://www.proactusa.com/bla/pa_logo_notag.png");
var excelImage = oSheet.Drawings.AddPicture("PRO*ACT Logo", _logo);
excelImage.From.Column = colIndex - 1;
excelImage.From.Row = rowIndex - 1;
excelImage.SetSize(199, 130); // 199WX130H is the actual size of the image
excelImage.From.ColumnOff = Pixel2MTU(2);
excelImage.From.RowOff = Pixel2MTU(2);
}
...which is called like so:
AddImage(deliveryPerformanceWorksheet, UNIT_ROW, LOGO_FIRST_COLUMN);
...but this won't fly in the Aspose code, because the sheet is of a different type - an Aspose.Cells.Worksheet instead of an ExcelWorksheet, and thus this code:
AddImage(locationWorksheet, 0, 4);
... won't compile in the Aspose report. I wish I could temporarily convert the Aspose.Cells.Worksheet to an ExcelWorksheet as cavalierly as this:
ExcelWorksheet ews = locationWorksheet; // naive attempt to magically morph an Aspose.Cells.Worksheet to an ExcelWorksheet
AddImage(ews, 0, 4);
...so that I could call AddImage(), but that flagrant attempt is tweeted to a halt by the compiler whistling, "Cannot implicitly convert type 'Aspose.Cells.Worksheet' to 'OfficeOpenXml.ExcelWorksheet'"
UPDATE 2
The image is the expected size; this code:
int h = _logo.Height; //130, as expected
int w = _logo.Width; //199, " "
...showed the image was the original size. Could the problem be the AutoFitterOptions setting? Does OnlyAuto allow stretching/squashing of the image, depending on the size of the cell into which it is plopped?
UPDATE 3
In EPPlus I can get the images to display at exactly the same size using this code:
private void AddImage(ExcelWorksheet oSheet, int rowIndex, int colIndex)
{
Image _logo = RoboReporterConstsAndUtils.GetURLImage("http://www.proactusa.com/bla/pa_logo_notag.png");
var excelImage = oSheet.Drawings.AddPicture("PRO*ACT Logo", _logo);
excelImage.From.Column = colIndex - 2;
excelImage.From.Row = rowIndex - 1;
excelImage.SetSize(199, 130);
excelImage.From.ColumnOff = Pixel2MTU(2);
excelImage.From.RowOff = Pixel2MTU(2);
}
...but in Aspose I can only come close using:
var ms = new MemoryStream();
Image _logo = RoboReporterConstsAndUtils.GetURLImage("http://www.proactusa.com/bla/pa_logo_notag.png");
_logo.Save(ms, ImageFormat.Png);
ms.Position = 0;
pivotTableSheet.Pictures.Add(0, _grandTotalsColumnPivotTable - 1, ms);
And the EPPlus code also retains the height/width ratio:
The original image is 199 pixels wide and 130 pixels high.
The EPPlus-plopped images are 1.33 X 2.05, so the ratio of 1.5:1 (close approximation) is retained.
The Aspose-plopped images, though, are 1.63 and 1.67 X 2.07, so the ratio is more like 1.25:1
So even with the AutoFitter jazz commented out of the Aspose code, the image still gets either squashed in width or stretched in height.
UPDATE 4
Based on a thread here, I tried this (afer copying the image to my bin folder):
int index = locationWorksheet.Pictures.Add(0, 4, 6, 5, "LogoFromSite.png");
Picture pic = locationWorksheet.Pictures[index];
pic.Placement = PlacementType.FreeFloating;
The first four arguments to [sheet].Pictures.Add() are Upper Left Row, Upper Left Column, Lower Right Row, and Lower Right Column.
However, this puts the image on the page in the right place, but then moves it to the left several columns (!?!)
UPDATE 5
I found another ray of hope here, and tried this code:
Aspose.Cells.Rendering.ImageOrPrintOptions opts = new Aspose.Cells.Rendering.ImageOrPrintOptions();
opts.OnePagePerSheet = true;
opts.ImageFormat = ImageFormat.Png;
opts.SetDesiredSize(199, 130);
Aspose.Cells.Rendering.SheetRender sr = new Aspose.Cells.Rendering.SheetRender(locationWorksheet, opts);
sr.ToImage(0, "LogoFromSite.png");
...but got this:
So: squashed again.
UPDATE 6
I tried some code provided by the Aspose Cells cats themselves, but they admitted there was a problem with it, and were looking into it. Just for grins, I gave it a shot to see what would transpire. This code:
byte[] bts1 = File.ReadAllBytes("LogoFromSite.png");
byte[] bts2 = File.ReadAllBytes("LogoFromSite.png");
MemoryStream ms1 = new MemoryStream();
ms1.Write(bts1, 0, bts1.Length);
ms1.Position = 0;
//This is for second picture in sheet2
MemoryStream ms2 = new MemoryStream();
ms2.Write(bts2, 0, bts2.Length);
ms2.Position = 0;
//Add picture in first worksheet
int idx = locationWorksheet.Pictures.Add(0, 4, ms1);
//Add picture in second worksheet with original size
idx = locationWorksheet.Pictures.Add(0, 10, ms2);
Picture pic = locationWorksheet.Pictures[idx];
pic.HeightScale = 100;
pic.WidthScale = 100;
...resulted in these "no image images":
UPDATE 7
I made another venture; as the height was being increased above and beyond 100%, I thought I would resize the image into another one, and use that:
var ms = new MemoryStream();
Image _logo = GetURLImage("http://www.proactusa.com/bla/pa_logo_notag.png");
double newHeightDbl = _logo.Height * 0.8;
int newHeightInt = (int)Math.Ceiling(newHeightDbl);
Image resizedImage = ResizeImage(_logo, newHeightInt, _logo.Width);
resizedImage.Save(ms, ImageFormat.Png);
ms.Position = 0;
locationWorksheet.Pictures.Add(0, 4, ms);
...but no! It stuffs the whole shebang into one measly column, like so:
...and gumbifies it galore vertically, thus making it look queasier than a lubber on a tempest-tossed tug.
Here is the (stolen/borrowed) code to resize the image:
// from http://stackoverflow.com/questions/1922040/resize-an-image-c-sharp
public static Bitmap ResizeImage(Image image, int width, int height)
{
var destRect = new Rectangle(0, 0, width, height);
var destImage = new Bitmap(width, height);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
}
return destImage;
}
Please check your thread in Aspose.Cells forum which answers two of your following questions.
1 - Can we reuse same memory stream object containing picture in workbooks and worksheets?
2 - How to add picture with original size?
Note: I am working as Developer Evangelist at Aspose
Simply a matter of commenting out the fancy-pants autofitting code:
//AutoFitterOptions options = new AutoFitterOptions { OnlyAuto = true };
//pivotTableSheet.AutoFitRows(options);
Now the image is displayed uniformly at pretty much its actual size (but note the caveat below); a scosh "spilly" at times, but if they complain about that, I'll create a second image, and resize it using this:
// from http://stackoverflow.com/questions/1922040/resize-an-image-c-sharp
public static Bitmap ResizeImage(Image image, int width, int height)
{
var destRect = new Rectangle(0, 0, width, height);
var destImage = new Bitmap(width, height);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
}
return destImage;
}
Caveat emptor: This works well enough that I am grudgingly accepting it, but the images placed on the sheet are not exactly the same size. One is 1.67" X 2.07", the other is 1.63" X 2.07" - close enough for horseshoes, hand-grenades, and images on Excel spreadsheets, I guess.
I'm a newbie in kinect programming, i am working on a ball tracking using kinect and opencv..we all know that kinect provides Depth data, and with the code below:
DepthImagePoint righthandDepthPoint =
sensor.CoordinateMapper.MapSkeletonPointToDepthPoint
(
me.Joints[JointType.HandRight].Position,
DepthImageFormat.Resolution640x480Fps30
);
double rightdepthmeters = (righthandDepthPoint.Depth);
using this, I am able to get the depth of a right hand, using the function MapSkeletonPointToDepthPoint() by specifing the jointtype..
Is it possible to get the depth of other objects by specifying in the image where?
given the coordinate..I want to get the depth of the object in that coordinate?
Pulling depth data from the Kinect SDK can be extracted from the DepthImagePixel structure.
The example code below loops through the entire DepthImageFrame to examine each of the pixels. If you have a specific coordinate you wish to look at, remove the for loop and set the x and y to a specific value.
// global variables
private const DepthImageFormat DepthFormat = DepthImageFormat.Resolution320x240Fps30;
private const ColorImageFormat ColorFormat = ColorImageFormat.RgbResolution640x480Fps30;
private DepthImagePixel[] depthPixels;
// defined in an initialization function
this.depthWidth = this.sensor.DepthStream.FrameWidth;
this.depthHeight = this.sensor.DepthStream.FrameHeight;
this.depthPixels = new DepthImagePixel[this.sensor.DepthStream.FramePixelDataLength];
private void SensorAllFramesReady(object sender, AllFramesReadyEventArgs e)
{
if (null == this.sensor)
return;
bool depthReceived = false;
using (DepthImageFrame depthFrame = e.OpenDepthImageFrame())
{
if (null != depthFrame)
{
// Copy the pixel data from the image to a temporary array
depthFrame.CopyDepthImagePixelDataTo(this.depthPixels);
depthReceived = true;
}
}
if (true == depthReceived)
{
// loop over each row and column of the depth
for (int y = 0; y < this.depthHeight; ++y)
{
for (int x = 0; x < this.depthWidth; ++x)
{
// calculate index into depth array
int depthIndex = x + (y * this.depthWidth);
// extract the given index
DepthImagePixel depthPixel = this.depthPixels[depthIndex];
Debug.WriteLine("Depth at [" + x + ", " + y + "] is: " + depthPixel.Depth);
}
}
}
}
How to autofit content in cell using jxl api?
I know this is an old question at this point, but I was looking for the solution to this and thought I would post it in case someone else needs it.
CellView Auto-Size
I'm not sure why the FAQ doesn't mention this, because it very clearly exists in the docs.
My code looked like the following:
for(int x=0;x<c;x++)
{
cell=sheet.getColumnView(x);
cell.setAutosize(true);
sheet.setColumnView(x, cell);
}
c stores the number of columns created
cell is just a temporary place holder for the returned CellView object
sheet is my WriteableSheet object
The Api warns that this is a processor intensive function, so it's probably not ideal for large files. But for a small file like mine (<100 rows) it took no noticeable time.
Hope this helps someone.
The method is self explanatory and commented:
private void sheetAutoFitColumns(WritableSheet sheet) {
for (int i = 0; i < sheet.getColumns(); i++) {
Cell[] cells = sheet.getColumn(i);
int longestStrLen = -1;
if (cells.length == 0)
continue;
/* Find the widest cell in the column. */
for (int j = 0; j < cells.length; j++) {
if ( cells[j].getContents().length() > longestStrLen ) {
String str = cells[j].getContents();
if (str == null || str.isEmpty())
continue;
longestStrLen = str.trim().length();
}
}
/* If not found, skip the column. */
if (longestStrLen == -1)
continue;
/* If wider than the max width, crop width */
if (longestStrLen > 255)
longestStrLen = 255;
CellView cv = sheet.getColumnView(i);
cv.setSize(longestStrLen * 256 + 100); /* Every character is 256 units wide, so scale it. */
sheet.setColumnView(i, cv);
}
}
for(int x=0;x<c;x++)
{
cell=sheet.getColumnView(x);
cell.setAutosize(true);
sheet.setColumnView(x, cell);
}
It is fine, instead of scanning all the columns. Pass the column as a parameter.
void display(column)
{
Cell = sheet.getColumnView(column);
cell.setAutosize(true);
sheet.setColumnView(column, cell);
}
So when you wiill be displaying your text you can set the particular length. Can be helpfull for huge excel files.
From the JExcelApi FAQ
How do I do the equivilent of Excel's "Format/Column/Auto Fit Selection"?
There is no API function to do this for you. You'll need to write code that scans the cells in each column, calculates the maximum length, and then calls setColumnView() accordingly. This will get you close to what Excel does but not exactly. Since most fonts have variable width characters, to get the exact same value, you would need to use FontMetrics to calculate the maximum width of each string in the column. No one has posted code on how to do this yet. Feel free to post code to the Yahoo! group or send it directly to the FAQ author's listed at the bottom of this page.
FontMetrics presumably refers to java.awt.FontMetrics. You should be able to work something out with the getLineMetrics(String, Graphics) method I would have though.
CellView's autosize method doesn't work for me all the time. My way of doing this is by programatically set the size(width) of the column based on the highest length of data in the column. Then perform some mathematical operations.
CellView cv = excelSheet.getColumnView(0);
cv.setSize((highest + ((highest/2) + (highest/4))) * 256);
where highest is an int that holds the longest length of data in the column.
setAutosize() method WILL NOT WORK if your cell has over 255 characters. This is related to the Excel 2003 max column width specification: http://office.microsoft.com/en-us/excel-help/excel-specifications-and-limits-HP005199291.aspx
You will need to write your own autosize method to handle this case.
Try this exemple:
expandColumns(sheet, 3);
workbook.write();
workbook.close();
private void expandColumn(WritableSheet sheet, int amountOfColumns){
int c = amountOfColumns;
for(int x=0;x<c;x++)
{
CellView cell = sheet.getColumnView(x);
cell.setAutosize(true);
sheet.setColumnView(x, cell);
}
}
Kotlin's implementation
private fun sheetAutoFitColumns(sheet: WritableSheet, columnsIndexesForFit: Array<Int>? = null, startFromRowWithIndex: Int = 0, excludeLastRows : Int = 0) {
for (columnIndex in columnsIndexesForFit?.iterator() ?: IntProgression.fromClosedRange(0, sheet.columns, 1).iterator()) {
val cells = sheet.getColumn(columnIndex)
var longestStrLen = -1
if (cells.isEmpty()) continue
for (j in startFromRowWithIndex until cells.size - excludeLastRows) {
if (cells[j].contents.length > longestStrLen) {
val str = cells[j].contents
if (str == null || str.isEmpty()) continue
longestStrLen = str.trim().length
}
}
if (longestStrLen == -1) continue
val newWidth = if (longestStrLen > 255) 255 else longestStrLen
sheet.setColumnView(columnIndex, newWidth)
}
}
example for use
sheetAutoFitColumns(sheet) // fit all columns by all rows
sheetAutoFitColumns(sheet, arrayOf(0, 3))// fit A and D columns by all rows
sheetAutoFitColumns(sheet, arrayOf(0, 3), 5)// fit A and D columns by rows after 5
sheetAutoFitColumns(sheet, arrayOf(0, 3), 5, 2)// fit A and D columns by rows after 5 and ignore two last rows