From d43ba17fe97c37d1f12583049638a95c9da26463 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Wed, 12 Nov 2014 00:19:00 +0000 Subject: [PATCH] Picture method to resize with different scales in width and height git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1638623 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/hssf/usermodel/HSSFPicture.java | 216 ++++++++---------- .../poi/hssf/usermodel/HSSFPictureData.java | 30 +++ .../apache/poi/hssf/usermodel/HSSFSheet.java | 18 ++ .../org/apache/poi/ss/usermodel/Picture.java | 73 ++++-- .../apache/poi/ss/usermodel/PictureData.java | 12 + .../org/apache/poi/ss/usermodel/Sheet.java | 21 +- .../org/apache/poi/ss/util/ImageUtils.java | 185 ++++++++++++++- .../java/org/apache/poi/util/Units.java | 14 +- .../apache/poi/xssf/streaming/SXSSFSheet.java | 13 ++ .../poi/xssf/usermodel/XSSFPicture.java | 155 ++++++------- .../apache/poi/xssf/usermodel/XSSFSheet.java | 17 ++ .../poi/xssf/usermodel/TestXSSFPicture.java | 38 ++- .../poi/hssf/usermodel/TestHSSFPicture.java | 49 +++- .../poi/ss/usermodel/BaseTestPicture.java | 53 +++-- test-data/spreadsheet/resize_compare.xls | Bin 0 -> 23040 bytes test-data/spreadsheet/resize_compare.xlsx | Bin 0 -> 37715 bytes 16 files changed, 620 insertions(+), 274 deletions(-) rename src/{ooxml => }/java/org/apache/poi/util/Units.java (78%) create mode 100644 test-data/spreadsheet/resize_compare.xls create mode 100644 test-data/spreadsheet/resize_compare.xlsx diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPicture.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPicture.java index 2c4b59f7b0..2d2eaa752e 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPicture.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPicture.java @@ -21,7 +21,17 @@ import java.awt.Dimension; import java.io.ByteArrayInputStream; import java.io.UnsupportedEncodingException; -import org.apache.poi.ddf.*; +import org.apache.poi.ddf.DefaultEscherRecordFactory; +import org.apache.poi.ddf.EscherBSERecord; +import org.apache.poi.ddf.EscherBlipRecord; +import org.apache.poi.ddf.EscherClientDataRecord; +import org.apache.poi.ddf.EscherComplexProperty; +import org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.ddf.EscherOptRecord; +import org.apache.poi.ddf.EscherProperties; +import org.apache.poi.ddf.EscherSimpleProperty; +import org.apache.poi.ddf.EscherTextboxRecord; +import org.apache.poi.hssf.model.InternalWorkbook; import org.apache.poi.hssf.record.CommonObjectDataSubRecord; import org.apache.poi.hssf.record.EscherAggregate; import org.apache.poi.hssf.record.ObjRecord; @@ -29,7 +39,6 @@ import org.apache.poi.ss.usermodel.Picture; import org.apache.poi.ss.util.ImageUtils; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; -import org.apache.poi.hssf.model.InternalWorkbook; /** * Represents a escher picture. Eg. A GIF, JPEG etc... @@ -44,20 +53,6 @@ public class HSSFPicture extends HSSFSimpleShape implements Picture { public static final int PICTURE_TYPE_PNG = HSSFWorkbook.PICTURE_TYPE_PNG; // PNG public static final int PICTURE_TYPE_DIB = HSSFWorkbook.PICTURE_TYPE_DIB; // Windows DIB - /** - * width of 1px in columns with default width in units of 1/256 of a character width - */ - private static final float PX_DEFAULT = 32.00f; - /** - * width of 1px in columns with overridden width in units of 1/256 of a character width - */ - private static final float PX_MODIFIED = 36.56f; - - /** - * Height of 1px of a row - */ - private static final int PX_ROW = 15; - public HSSFPicture(EscherContainerRecord spContainer, ObjRecord objRecord) { super(spContainer, objRecord); } @@ -98,37 +93,7 @@ public class HSSFPicture extends HSSFSimpleShape implements Picture { } /** - * Resize the image - *

- * Please note, that this method works correctly only for workbooks - * with default font size (Arial 10pt for .xls). - * If the default font is changed the resized image can be streched vertically or horizontally. - *

- * - * @param scale the amount by which image dimensions are multiplied relative to the original size. - * resize(1.0) sets the original size, resize(0.5) resize to 50% of the original, - * resize(2.0) resizes to 200% of the original. - */ - public void resize(double scale){ - HSSFClientAnchor anchor = (HSSFClientAnchor)getAnchor(); - anchor.setAnchorType(2); - - HSSFClientAnchor pref = getPreferredSize(scale); - - int row2 = anchor.getRow1() + (pref.getRow2() - pref.getRow1()); - int col2 = anchor.getCol1() + (pref.getCol2() - pref.getCol1()); - - anchor.setCol2((short)col2); - anchor.setDx1(0); - anchor.setDx2(pref.getDx2()); - - anchor.setRow2(row2); - anchor.setDy1(0); - anchor.setDy2(pref.getDy2()); - } - - /** - * Reset the image to the original size. + * Reset the image to the dimension of the embedded image * *

* Please note, that this method works correctly only for workbooks @@ -137,7 +102,51 @@ public class HSSFPicture extends HSSFSimpleShape implements Picture { *

*/ public void resize(){ - resize(1.0); + resize(Double.MAX_VALUE); + } + + /** + * Resize the image proportionally. + * + * @see #resize(double, double) + */ + public void resize(double scale) { + resize(scale,scale); + } + + /** + * Resize the image + *

+ * Please note, that this method works correctly only for workbooks + * with default font size (Arial 10pt for .xls). + * If the default font is changed the resized image can be streched vertically or horizontally. + *

+ *

+ * resize(1.0,1.0) keeps the original size,
+ * resize(0.5,0.5) resize to 50% of the original,
+ * resize(2.0,2.0) resizes to 200% of the original.
+ * resize({@link Double#MAX_VALUE},{@link Double#MAX_VALUE}) resizes to the dimension of the embedded image. + *

+ * + * @param scaleX the amount by which the image width is multiplied relative to the original width. + * @param scaleY the amount by which the image height is multiplied relative to the original height. + */ + public void resize(double scaleX, double scaleY) { + HSSFClientAnchor anchor = getClientAnchor(); + anchor.setAnchorType(2); + + HSSFClientAnchor pref = getPreferredSize(scaleX,scaleY); + + int row2 = anchor.getRow1() + (pref.getRow2() - pref.getRow1()); + int col2 = anchor.getCol1() + (pref.getCol2() - pref.getCol1()); + + anchor.setCol2((short)col2); + // anchor.setDx1(0); + anchor.setDx2(pref.getDx2()); + + anchor.setRow2(row2); + // anchor.setDy1(0); + anchor.setDy2(pref.getDy2()); } /** @@ -158,86 +167,30 @@ public class HSSFPicture extends HSSFSimpleShape implements Picture { * @since POI 3.0.2 */ public HSSFClientAnchor getPreferredSize(double scale){ - HSSFClientAnchor anchor = (HSSFClientAnchor)getAnchor(); - - Dimension size = getImageDimension(); - double scaledWidth = size.getWidth() * scale; - double scaledHeight = size.getHeight() * scale; - - float w = 0; - - //space in the leftmost cell - w += getColumnWidthInPixels(anchor.getCol1())*(1 - (float)anchor.getDx1()/1024); - short col2 = (short)(anchor.getCol1() + 1); - int dx2 = 0; - - while(w < scaledWidth){ - w += getColumnWidthInPixels(col2++); - } - - if(w > scaledWidth) { - //calculate dx2, offset in the rightmost cell - col2--; - double cw = getColumnWidthInPixels(col2); - double delta = w - scaledWidth; - dx2 = (int)((cw-delta)/cw*1024); - } - anchor.setCol2(col2); - anchor.setDx2(dx2); - - float h = 0; - h += (1 - (float)anchor.getDy1()/256)* getRowHeightInPixels(anchor.getRow1()); - int row2 = anchor.getRow1() + 1; - int dy2 = 0; - - while(h < scaledHeight){ - h += getRowHeightInPixels(row2++); - } - if(h > scaledHeight) { - row2--; - double ch = getRowHeightInPixels(row2); - double delta = h - scaledHeight; - dy2 = (int)((ch-delta)/ch*256); - } - anchor.setRow2(row2); - anchor.setDy2(dy2); - - return anchor; + return getPreferredSize(scale, scale); } - - private float getColumnWidthInPixels(int column){ - - int cw = getPatriarch().getSheet().getColumnWidth(column); - float px = getPixelWidth(column); - - return cw/px; - } - - private float getRowHeightInPixels(int i){ - - HSSFRow row = getPatriarch().getSheet().getRow(i); - float height; - if(row != null) height = row.getHeight(); - else height = getPatriarch().getSheet().getDefaultRowHeight(); - - return height/PX_ROW; - } - - private float getPixelWidth(int column){ - - int def = getPatriarch().getSheet().getDefaultColumnWidth()*256; - int cw = getPatriarch().getSheet().getColumnWidth(column); - - return cw == def ? PX_DEFAULT : PX_MODIFIED; + + /** + * Calculate the preferred size for this picture. + * + * @param scaleX the amount by which image width is multiplied relative to the original width. + * @param scaleY the amount by which image height is multiplied relative to the original height. + * @return HSSFClientAnchor with the preferred size for this image + * @since POI 3.11 + */ + public HSSFClientAnchor getPreferredSize(double scaleX, double scaleY){ + ImageUtils.setPreferredSize(this, scaleX, scaleY); + return getClientAnchor(); } /** - * Return the dimension of this image + * Return the dimension of the embedded image in pixel * - * @return image dimension + * @return image dimension in pixels */ public Dimension getImageDimension(){ - EscherBSERecord bse = getPatriarch().getSheet()._book.getBSERecord(getPictureIndex()); + InternalWorkbook iwb = getPatriarch().getSheet().getWorkbook().getWorkbook(); + EscherBSERecord bse = iwb.getBSERecord(getPictureIndex()); byte[] data = bse.getBlipRecord().getPicturedata(); int type = bse.getBlipTypeWin32(); return ImageUtils.getImageDimension(new ByteArrayInputStream(data), type); @@ -250,7 +203,8 @@ public class HSSFPicture extends HSSFSimpleShape implements Picture { */ public HSSFPictureData getPictureData(){ InternalWorkbook iwb = getPatriarch().getSheet().getWorkbook().getWorkbook(); - EscherBlipRecord blipRecord = iwb.getBSERecord(getPictureIndex()).getBlipRecord(); + EscherBSERecord bse = iwb.getBSERecord(getPictureIndex()); + EscherBlipRecord blipRecord = bse.getBlipRecord(); return new HSSFPictureData(blipRecord); } @@ -301,4 +255,22 @@ public class HSSFPicture extends HSSFSimpleShape implements Picture { ObjRecord obj = (ObjRecord) getObjRecord().cloneViaReserialise(); return new HSSFPicture(spContainer, obj); } + + /** + * @return the anchor that is used by this picture. + */ + @Override + public HSSFClientAnchor getClientAnchor() { + HSSFAnchor a = getAnchor(); + return (a instanceof HSSFClientAnchor) ? (HSSFClientAnchor)a : null; + } + + + /** + * @return the sheet which contains the picture shape + */ + @Override + public HSSFSheet getSheet() { + return getPatriarch().getSheet(); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPictureData.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPictureData.java index 93fa26f5ba..267bd2e1d6 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPictureData.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPictureData.java @@ -22,6 +22,7 @@ import org.apache.poi.ddf.EscherBitmapBlip; import org.apache.poi.ddf.EscherBlipRecord; import org.apache.poi.ddf.EscherMetafileBlip; import org.apache.poi.ss.usermodel.PictureData; +import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.util.PngUtils; /** @@ -131,4 +132,33 @@ public class HSSFPictureData implements PictureData return "image/unknown"; } } + + /** + * @return the POI internal image type, -1 if not unknown image type + * + * @see Workbook#PICTURE_TYPE_DIB + * @see Workbook#PICTURE_TYPE_EMF + * @see Workbook#PICTURE_TYPE_JPEG + * @see Workbook#PICTURE_TYPE_PICT + * @see Workbook#PICTURE_TYPE_PNG + * @see Workbook#PICTURE_TYPE_WMF + */ + public int getPictureType() { + switch (blip.getRecordId()) { + case EscherMetafileBlip.RECORD_ID_WMF: + return Workbook.PICTURE_TYPE_WMF; + case EscherMetafileBlip.RECORD_ID_EMF: + return Workbook.PICTURE_TYPE_EMF; + case EscherMetafileBlip.RECORD_ID_PICT: + return Workbook.PICTURE_TYPE_PICT; + case EscherBitmapBlip.RECORD_ID_PNG: + return Workbook.PICTURE_TYPE_PNG; + case EscherBitmapBlip.RECORD_ID_JPEG: + return Workbook.PICTURE_TYPE_JPEG; + case EscherBitmapBlip.RECORD_ID_DIB: + return Workbook.PICTURE_TYPE_DIB; + default: + return -1; + } + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index ce09d8012d..e7ed838ecc 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -76,6 +76,16 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { private static final POILogger log = POILogFactory.getLogger(HSSFSheet.class); private static final int DEBUG = POILogger.DEBUG; + /** + * width of 1px in columns with default width in units of 1/256 of a character width + */ + private static final float PX_DEFAULT = 32.00f; + /** + * width of 1px in columns with overridden width in units of 1/256 of a character width + */ + private static final float PX_MODIFIED = 36.56f; + + /** * Used for compile-time optimization. This is the initial size for the collection of * rows. It is currently set to 20. If you generate larger sheets you may benefit @@ -555,6 +565,14 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { return _sheet.getColumnWidth(columnIndex); } + public float getColumnWidthInPixels(int column){ + int cw = getColumnWidth(column); + int def = getDefaultColumnWidth()*256; + float px = (cw == def ? PX_DEFAULT : PX_MODIFIED); + + return cw/px; + } + /** * get the default column width for the sheet (if the columns do not define their own width) in * characters diff --git a/src/java/org/apache/poi/ss/usermodel/Picture.java b/src/java/org/apache/poi/ss/usermodel/Picture.java index c79260c6ee..58ad448125 100644 --- a/src/java/org/apache/poi/ss/usermodel/Picture.java +++ b/src/java/org/apache/poi/ss/usermodel/Picture.java @@ -16,6 +16,9 @@ ==================================================================== */ package org.apache.poi.ss.usermodel; +import java.awt.Dimension; + + /** * Repersents a picture in a SpreadsheetML document * @@ -24,33 +27,61 @@ package org.apache.poi.ss.usermodel; public interface Picture { /** - * Reset the image to the original size. + * Reset the image to the dimension of the embedded image * - *

- * Please note, that this method works correctly only for workbooks - * with default font size (Arial 10pt for .xls and Calibri 11pt for .xlsx). - * If the default font is changed the resized image can be streched vertically or horizontally. - *

+ * @see #resize(double, double) */ void resize(); /** - * Reset the image to the original size. + * Resize the image proportionally. * - *

- * Please note, that this method works correctly only for workbooks - * with default font size (Arial 10pt for .xls and Calibri 11pt for .xlsx). - * If the default font is changed the resize() procedure can be 'off'. - *

- * - * @param scale the amount by which image dimensions are multiplied relative to the original size. - * resize(1.0) sets the original size, resize(0.5) resize to 50% of the original, - * resize(2.0) resizes to 200% of the original. + * @see #resize(double, double) */ void resize(double scale); + + /** + * Resize the image. + *

+ * Please note, that this method works correctly only for workbooks + * with the default font size (Arial 10pt for .xls and Calibri 11pt for .xlsx). + * If the default font is changed the resized image can be streched vertically or horizontally. + *

+ *

+ * resize(1.0,1.0) keeps the original size,
+ * resize(0.5,0.5) resize to 50% of the original,
+ * resize(2.0,2.0) resizes to 200% of the original.
+ * resize({@link Double#MAX_VALUE},{@link Double#MAX_VALUE}) resizes to the dimension of the embedded image. + *

+ * + * @param scaleX the amount by which the image width is multiplied relative to the original width. + * @param scaleY the amount by which the image height is multiplied relative to the original height. + */ + void resize(double scaleX, double scaleY); + /** + * Calculate the preferred size for this picture. + * + * @return XSSFClientAnchor with the preferred size for this image + */ ClientAnchor getPreferredSize(); + /** + * Calculate the preferred size for this picture. + * + * @param scaleX the amount by which image width is multiplied relative to the original width. + * @param scaleY the amount by which image height is multiplied relative to the original height. + * @return ClientAnchor with the preferred size for this image + */ + ClientAnchor getPreferredSize(double scaleX, double scaleY); + + /** + * Return the dimension of the embedded image in pixel + * + * @return image dimension in pixels + */ + Dimension getImageDimension(); + /** * Return picture data for this picture * @@ -58,4 +89,14 @@ public interface Picture { */ PictureData getPictureData(); + /** + * @return the anchor that is used by this picture + */ + ClientAnchor getClientAnchor(); + + + /** + * @return the sheet which contains the picture + */ + Sheet getSheet(); } diff --git a/src/java/org/apache/poi/ss/usermodel/PictureData.java b/src/java/org/apache/poi/ss/usermodel/PictureData.java index 954337829d..67d3cefcd1 100644 --- a/src/java/org/apache/poi/ss/usermodel/PictureData.java +++ b/src/java/org/apache/poi/ss/usermodel/PictureData.java @@ -37,4 +37,16 @@ public interface PictureData { * Returns the mime type for the image */ String getMimeType(); + + /** + * @return the POI internal image type, 0 if unknown image type + * + * @see Workbook#PICTURE_TYPE_DIB + * @see Workbook#PICTURE_TYPE_EMF + * @see Workbook#PICTURE_TYPE_JPEG + * @see Workbook#PICTURE_TYPE_PICT + * @see Workbook#PICTURE_TYPE_PNG + * @see Workbook#PICTURE_TYPE_WMF + */ + int getPictureType(); } \ No newline at end of file diff --git a/src/java/org/apache/poi/ss/usermodel/Sheet.java b/src/java/org/apache/poi/ss/usermodel/Sheet.java index 57c8785c6c..d1bd67416a 100644 --- a/src/java/org/apache/poi/ss/usermodel/Sheet.java +++ b/src/java/org/apache/poi/ss/usermodel/Sheet.java @@ -186,11 +186,26 @@ public interface Sheet extends Iterable { * using the default font (first font in the workbook) *

* - * @param columnIndex - the column to set (0-based) + * @param columnIndex - the column to get (0-based) * @return width - the width in units of 1/256th of a character width */ int getColumnWidth(int columnIndex); + /** + * get the width in pixel + * + *

+ * Please note, that this method works correctly only for workbooks + * with the default font size (Arial 10pt for .xls and Calibri 11pt for .xlsx). + * If the default font is changed the column width can be streched + *

+ * + * @param columnIndex - the column to set (0-based) + * @return width in pixels + */ + float getColumnWidthInPixels(int columnIndex); + + /** * Set the default column width for the sheet (if the columns do not define their own width) * in characters @@ -214,7 +229,7 @@ public interface Sheet extends Iterable { * @return default row height measured in twips (1/20 of a point) */ short getDefaultRowHeight(); - + /** * Get the default row height for the sheet (if the rows do not define their own height) in * points. @@ -922,7 +937,7 @@ public interface Sheet extends Iterable { public DataValidationHelper getDataValidationHelper(); - /** + /** * Returns the list of DataValidation in the sheet. * @return list of DataValidation in the sheet */ diff --git a/src/java/org/apache/poi/ss/util/ImageUtils.java b/src/java/org/apache/poi/ss/util/ImageUtils.java index 76ebc0370c..b470824519 100644 --- a/src/java/org/apache/poi/ss/util/ImageUtils.java +++ b/src/java/org/apache/poi/ss/util/ImageUtils.java @@ -16,21 +16,32 @@ ==================================================================== */ package org.apache.poi.ss.util; -import org.w3c.dom.NodeList; -import org.w3c.dom.Element; -import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.util.POILogger; -import org.apache.poi.util.POILogFactory; +import static org.apache.poi.util.Units.EMU_PER_PIXEL; -import javax.imageio.ImageReader; -import javax.imageio.ImageIO; -import javax.imageio.stream.ImageInputStream; +import java.awt.Dimension; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.awt.*; -import java.awt.image.BufferedImage; import java.util.Iterator; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; + +import org.apache.poi.hssf.usermodel.HSSFClientAnchor; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.Picture; +import org.apache.poi.ss.usermodel.PictureData; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; +import org.apache.poi.util.Units; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + /** * @author Yegor Kozlov */ @@ -60,8 +71,8 @@ public class ImageUtils { try { //read the image using javax.imageio.* ImageInputStream iis = ImageIO.createImageInputStream( is ); - Iterator i = ImageIO.getImageReaders( iis ); - ImageReader r = (ImageReader) i.next(); + Iterator i = ImageIO.getImageReaders( iis ); + ImageReader r = i.next(); r.setInput( iis ); BufferedImage img = r.read(0); @@ -113,4 +124,154 @@ public class ImageUtils { return new int[]{hdpi, vdpi}; } + /** + * Calculate and set the preferred size (anchor) for this picture. + * + * @param scaleX the amount by which image width is multiplied relative to the original width. + * @param scaleY the amount by which image height is multiplied relative to the original height. + * @return the new Dimensions of the scaled picture in EMUs + */ + public static Dimension setPreferredSize(Picture picture, double scaleX, double scaleY){ + ClientAnchor anchor = picture.getClientAnchor(); + boolean isHSSF = (anchor instanceof HSSFClientAnchor); + PictureData data = picture.getPictureData(); + Sheet sheet = picture.getSheet(); + + // in pixel + Dimension imgSize = getImageDimension(new ByteArrayInputStream(data.getData()), data.getPictureType()); + // in emus + Dimension anchorSize = ImageUtils.getDimensionFromAnchor(picture); + final double scaledWidth = (scaleX == Double.MAX_VALUE) + ? imgSize.getWidth() : anchorSize.getWidth()/EMU_PER_PIXEL * scaleX; + final double scaledHeight = (scaleY == Double.MAX_VALUE) + ? imgSize.getHeight() : anchorSize.getHeight()/EMU_PER_PIXEL * scaleY; + + double w = 0; + int col2 = anchor.getCol1(); + int dx2 = 0; + + //space in the leftmost cell + w = sheet.getColumnWidthInPixels(col2++); + if (isHSSF) { + w *= 1 - anchor.getDx1()/1024d; + } else { + w -= anchor.getDx1()/EMU_PER_PIXEL; + } + + while(w < scaledWidth){ + w += sheet.getColumnWidthInPixels(col2++); + } + + if(w > scaledWidth) { + //calculate dx2, offset in the rightmost cell + double cw = sheet.getColumnWidthInPixels(--col2); + double delta = w - scaledWidth; + if (isHSSF) { + dx2 = (int)((cw-delta)/cw*1024); + } else { + dx2 = (int)((cw-delta)*EMU_PER_PIXEL); + } + if (dx2 < 0) dx2 = 0; + } + anchor.setCol2(col2); + anchor.setDx2(dx2); + + double h = 0; + int row2 = anchor.getRow1(); + int dy2 = 0; + + h = getRowHeightInPixels(sheet,row2++); + if (isHSSF) { + h *= 1 - anchor.getDy1()/256d; + } else { + h -= anchor.getDy1()/EMU_PER_PIXEL; + } + + while(h < scaledHeight){ + h += getRowHeightInPixels(sheet,row2++); + } + + if(h > scaledHeight) { + double ch = getRowHeightInPixels(sheet,--row2); + double delta = h - scaledHeight; + if (isHSSF) { + dy2 = (int)((ch-delta)/ch*256); + } else { + dy2 = (int)((ch-delta)*EMU_PER_PIXEL); + } + if (dy2 < 0) dy2 = 0; + } + + anchor.setRow2(row2); + anchor.setDy2(dy2); + + Dimension dim = new Dimension( + (int)Math.round(scaledWidth*EMU_PER_PIXEL), + (int)Math.round(scaledHeight*EMU_PER_PIXEL) + ); + + return dim; + } + + /** + * Calculates the dimensions in EMUs for the anchor of the given picture + * + * @param picture the picture containing the anchor + * @return the dimensions in EMUs + */ + public static Dimension getDimensionFromAnchor(Picture picture) { + ClientAnchor anchor = picture.getClientAnchor(); + boolean isHSSF = (anchor instanceof HSSFClientAnchor); + Sheet sheet = picture.getSheet(); + + double w = 0; + int col2 = anchor.getCol1(); + + //space in the leftmost cell + w = sheet.getColumnWidthInPixels(col2++); + if (isHSSF) { + w *= 1 - anchor.getDx1()/1024d; + } else { + w -= anchor.getDx1()/EMU_PER_PIXEL; + } + + while(col2 < anchor.getCol2()){ + w += sheet.getColumnWidthInPixels(col2++); + } + + if (isHSSF) { + w += sheet.getColumnWidthInPixels(col2) * anchor.getDx2()/1024d; + } else { + w += anchor.getDx2()/EMU_PER_PIXEL; + } + + double h = 0; + int row2 = anchor.getRow1(); + + h = getRowHeightInPixels(sheet,row2++); + if (isHSSF) { + h *= 1 - anchor.getDy1()/256d; + } else { + h -= anchor.getDy1()/EMU_PER_PIXEL; + } + + while(row2 < anchor.getRow2()){ + h += getRowHeightInPixels(sheet,row2++); + } + + if (isHSSF) { + h += getRowHeightInPixels(sheet,row2) * anchor.getDy2()/256; + } else { + h += anchor.getDy2()/EMU_PER_PIXEL; + } + + return new Dimension((int)w*EMU_PER_PIXEL, (int)h*EMU_PER_PIXEL); + } + + + private static double getRowHeightInPixels(Sheet sheet, int rowNum) { + Row r = sheet.getRow(rowNum); + double points = (r == null) ? sheet.getDefaultRowHeightInPoints() : r.getHeightInPoints(); + return Units.toEMU(points)/EMU_PER_PIXEL; + } } diff --git a/src/ooxml/java/org/apache/poi/util/Units.java b/src/java/org/apache/poi/util/Units.java similarity index 78% rename from src/ooxml/java/org/apache/poi/util/Units.java rename to src/java/org/apache/poi/util/Units.java index 3a400399c7..91584b2ad5 100644 --- a/src/ooxml/java/org/apache/poi/util/Units.java +++ b/src/java/org/apache/poi/util/Units.java @@ -23,10 +23,20 @@ public class Units { public static final int EMU_PER_PIXEL = 9525; public static final int EMU_PER_POINT = 12700; - public static int toEMU(double value){ - return (int)Math.round(EMU_PER_POINT*value); + /** + * Converts points to EMUs + * @param points points + * @return emus + */ + public static int toEMU(double points){ + return (int)Math.round(EMU_PER_POINT*points); } + /** + * Converts EMUs to points + * @param emu emu + * @return points + */ public static double toPoints(long emu){ return (double)emu/EMU_PER_POINT; } diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java index d676eb599e..2700e16785 100644 --- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java @@ -264,6 +264,19 @@ public class SXSSFSheet implements Sheet, Cloneable return _sh.getColumnWidth(columnIndex); } + /** + * Get the actual column width in pixels + * + *

+ * Please note, that this method works correctly only for workbooks + * with the default font size (Calibri 11pt for .xlsx). + *

+ */ + @Override + public float getColumnWidthInPixels(int columnIndex) { + return _sh.getColumnWidthInPixels(columnIndex); + } + /** * Set the default column width for the sheet (if the columns do not define their own width) * in characters diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFPicture.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFPicture.java index 47402bdc35..1d87faceee 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFPicture.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFPicture.java @@ -20,15 +20,14 @@ package org.apache.poi.xssf.usermodel; import java.awt.Dimension; import java.io.IOException; -import org.apache.poi.POIXMLDocumentPart; import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.ss.usermodel.Picture; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.ImageUtils; +import org.apache.poi.util.Internal; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; -import org.apache.poi.util.Internal; import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties; import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualPictureProperties; @@ -40,7 +39,6 @@ import org.openxmlformats.schemas.drawingml.x2006.main.CTTransform2D; import org.openxmlformats.schemas.drawingml.x2006.main.STShapeType; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTPicture; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTPictureNonVisual; -import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCol; /** * Represents a picture shape in a SpreadsheetML drawing. @@ -57,7 +55,7 @@ public final class XSSFPicture extends XSSFShape implements Picture { * * This value is the same for default font in Office 2007 (Calibry) and Office 2003 and earlier (Arial) */ - private static float DEFAULT_COLUMN_WIDTH = 9.140625f; + // private static float DEFAULT_COLUMN_WIDTH = 9.140625f; /** * A default instance of CTShape used for creating new shapes. @@ -140,44 +138,56 @@ public final class XSSFPicture extends XSSFShape implements Picture { } /** - * Reset the image to the original size. + * Reset the image to the dimension of the embedded image * - *

- * Please note, that this method works correctly only for workbooks - * with the default font size (Calibri 11pt for .xlsx). - * If the default font is changed the resized image can be streched vertically or horizontally. - *

+ * @see #resize(double, double) */ public void resize(){ - resize(1.0); + resize(Double.MAX_VALUE); } /** - * Reset the image to the original size. + * Resize the image proportionally. + * + * @see #resize(double, double) + */ + public void resize(double scale) { + resize(scale, scale); + } + + /** + * Resize the image relatively to its current size. *

* Please note, that this method works correctly only for workbooks * with the default font size (Calibri 11pt for .xlsx). * If the default font is changed the resized image can be streched vertically or horizontally. *

+ *

+ * resize(1.0,1.0) keeps the original size,
+ * resize(0.5,0.5) resize to 50% of the original,
+ * resize(2.0,2.0) resizes to 200% of the original.
+ * resize({@link Double#MAX_VALUE},{@link Double#MAX_VALUE}) resizes to the dimension of the embedded image. + *

* - * @param scale the amount by which image dimensions are multiplied relative to the original size. - * resize(1.0) sets the original size, resize(0.5) resize to 50% of the original, - * resize(2.0) resizes to 200% of the original. + * @param scaleX the amount by which the image width is multiplied relative to the original width, + * when set to {@link java.lang.Double#MAX_VALUE} the width of the embedded image is used + * @param scaleY the amount by which the image height is multiplied relative to the original height, + * when set to {@link java.lang.Double#MAX_VALUE} the height of the embedded image is used */ - public void resize(double scale){ - XSSFClientAnchor anchor = (XSSFClientAnchor)getAnchor(); + public void resize(double scaleX, double scaleY){ + XSSFClientAnchor anchor = getClientAnchor(); - XSSFClientAnchor pref = getPreferredSize(scale); + XSSFClientAnchor pref = getPreferredSize(scaleX,scaleY); int row2 = anchor.getRow1() + (pref.getRow2() - pref.getRow1()); int col2 = anchor.getCol1() + (pref.getCol2() - pref.getCol1()); anchor.setCol2(col2); - anchor.setDx1(0); + // anchor.setDx1(0); anchor.setDx2(pref.getDx2()); anchor.setRow2(row2); - anchor.setDy1(0); + // anchor.setDy1(0); anchor.setDy2(pref.getDy2()); } @@ -197,71 +207,22 @@ public final class XSSFPicture extends XSSFShape implements Picture { * @return XSSFClientAnchor with the preferred size for this image */ public XSSFClientAnchor getPreferredSize(double scale){ - XSSFClientAnchor anchor = (XSSFClientAnchor)getAnchor(); - - XSSFPictureData data = getPictureData(); - Dimension size = getImageDimension(data.getPackagePart(), data.getPictureType()); - double scaledWidth = size.getWidth() * scale; - double scaledHeight = size.getHeight() * scale; - - float w = 0; - int col2 = anchor.getCol1(); - int dx2 = 0; - - for (;;) { - w += getColumnWidthInPixels(col2); - if(w > scaledWidth) break; - col2++; - } - - if(w > scaledWidth) { - double cw = getColumnWidthInPixels(col2 ); - double delta = w - scaledWidth; - dx2 = (int)(EMU_PER_PIXEL*(cw-delta)); - } - anchor.setCol2(col2); - anchor.setDx2(dx2); - - double h = 0; - int row2 = anchor.getRow1(); - int dy2 = 0; - - for (;;) { - h += getRowHeightInPixels(row2); - if(h > scaledHeight) break; - row2++; - } - - if(h > scaledHeight) { - double ch = getRowHeightInPixels(row2); - double delta = h - scaledHeight; - dy2 = (int)(EMU_PER_PIXEL*(ch-delta)); - } - anchor.setRow2(row2); - anchor.setDy2(dy2); - + return getPreferredSize(scale, scale); + } + + /** + * Calculate the preferred size for this picture. + * + * @param scaleX the amount by which image width is multiplied relative to the original width. + * @param scaleY the amount by which image height is multiplied relative to the original height. + * @return XSSFClientAnchor with the preferred size for this image + */ + public XSSFClientAnchor getPreferredSize(double scaleX, double scaleY){ + Dimension dim = ImageUtils.setPreferredSize(this, scaleX, scaleY); CTPositiveSize2D size2d = ctPicture.getSpPr().getXfrm().getExt(); - size2d.setCx((long)(scaledWidth*EMU_PER_PIXEL)); - size2d.setCy((long)(scaledHeight*EMU_PER_PIXEL)); - - return anchor; - } - - private float getColumnWidthInPixels(int columnIndex){ - XSSFSheet sheet = (XSSFSheet)getDrawing().getParent(); - - CTCol col = sheet.getColumnHelper().getColumn(columnIndex, false); - double numChars = col == null || !col.isSetWidth() ? DEFAULT_COLUMN_WIDTH : col.getWidth(); - - return (float)numChars*XSSFWorkbook.DEFAULT_CHARACTER_WIDTH; - } - - private float getRowHeightInPixels(int rowIndex){ - XSSFSheet sheet = (XSSFSheet)getDrawing().getParent(); - - XSSFRow row = sheet.getRow(rowIndex); - float height = row != null ? row.getHeightInPoints() : sheet.getDefaultRowHeightInPoints(); - return height*PIXEL_DPI/POINT_DPI; + size2d.setCx((int)dim.getWidth()); + size2d.setCy((int)dim.getHeight()); + return getClientAnchor(); } /** @@ -283,6 +244,16 @@ public final class XSSFPicture extends XSSFShape implements Picture { } } + /** + * Return the dimension of the embedded image in pixel + * + * @return image dimension in pixels + */ + public Dimension getImageDimension() { + XSSFPictureData picData = getPictureData(); + return getImageDimension(picData.getPackagePart(), picData.getPictureType()); + } + /** * Return picture data for this shape * @@ -297,4 +268,20 @@ public final class XSSFPicture extends XSSFShape implements Picture { return ctPicture.getSpPr(); } + /** + * @return the anchor that is used by this shape. + */ + @Override + public XSSFClientAnchor getClientAnchor() { + XSSFAnchor a = getAnchor(); + return (a instanceof XSSFClientAnchor) ? (XSSFClientAnchor)a : null; + } + + /** + * @return the sheet which contains the picture shape + */ + @Override + public XSSFSheet getSheet() { + return (XSSFSheet)getDrawing().getParent(); + } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java index ac72c76803..a5a8b311b6 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java @@ -704,6 +704,22 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet { return (int)(width*256); } + /** + * Get the actual column width in pixels + * + *

+ * Please note, that this method works correctly only for workbooks + * with the default font size (Calibri 11pt for .xlsx). + *

+ */ + @Override + public float getColumnWidthInPixels(int columnIndex) { + int styleIdx = getColumnHelper().getColDefaultStyle(columnIndex); + CellStyle cs = getWorkbook().getStylesSource().getStyleAt(styleIdx); + float widthIn256 = getColumnWidth(columnIndex); + return (float)(widthIn256/256.0*XSSFWorkbook.DEFAULT_CHARACTER_WIDTH); + } + /** * Get the default column width for the sheet (if the columns do not define their own width) in * characters. @@ -730,6 +746,7 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet { return (short)(getDefaultRowHeightInPoints() * 20); } + /** * Get the default row height for the sheet measued in point size (if the rows do not define their own height). * diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFPicture.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFPicture.java index f6b05586e6..5415502b12 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFPicture.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFPicture.java @@ -18,13 +18,25 @@ package org.apache.poi.xssf.usermodel; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.List; +import org.apache.poi.hssf.usermodel.HSSFPatriarch; +import org.apache.poi.hssf.usermodel.HSSFPicture; +import org.apache.poi.hssf.usermodel.HSSFShape; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.BaseTestPicture; import org.apache.poi.ss.usermodel.ClientAnchor; import org.apache.poi.xssf.XSSFITestDataProvider; import org.apache.poi.xssf.XSSFTestDataSamples; +import org.junit.Ignore; +import org.junit.Test; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTTwoCellAnchor; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.STEditAs; @@ -37,12 +49,21 @@ public final class TestXSSFPicture extends BaseTestPicture { super(XSSFITestDataProvider.instance); } - public void testResize() { - baseTestResize(new XSSFClientAnchor(0, 0, 504825, 85725, (short)0, 0, (short)1, 8)); + @Test + public void resize() throws Exception { + XSSFWorkbook wb = XSSFITestDataProvider.instance.openSampleWorkbook("resize_compare.xlsx"); + XSSFDrawing dp = wb.getSheetAt(0).createDrawingPatriarch(); + List pics = dp.getShapes(); + XSSFPicture inpPic = (XSSFPicture)pics.get(0); + XSSFPicture cmpPic = (XSSFPicture)pics.get(0); + + baseTestResize(inpPic, cmpPic, 2.0, 2.0); + wb.close(); } - public void testCreate(){ + @Test + public void create() throws IOException { XSSFWorkbook wb = new XSSFWorkbook(); XSSFSheet sheet = wb.createSheet(); XSSFDrawing drawing = sheet.createDrawingPatriarch(); @@ -70,6 +91,8 @@ public final class TestXSSFPicture extends BaseTestPicture { CTTwoCellAnchor ctShapeHolder = drawing.getCTDrawing().getTwoCellAnchorArray(0); // STEditAs.ABSOLUTE corresponds to ClientAnchor.DONT_MOVE_AND_RESIZE assertEquals(STEditAs.ABSOLUTE, ctShapeHolder.getEditAs()); + + wb.close(); } /** @@ -77,7 +100,8 @@ public final class TestXSSFPicture extends BaseTestPicture { * * See Bugzilla 50458 */ - public void testShapeId(){ + @Test + public void incrementShapeId() throws IOException { XSSFWorkbook wb = new XSSFWorkbook(); XSSFSheet sheet = wb.createSheet(); XSSFDrawing drawing = sheet.createDrawingPatriarch(); @@ -93,12 +117,15 @@ public final class TestXSSFPicture extends BaseTestPicture { jpegIdx = wb.addPicture(jpegData, XSSFWorkbook.PICTURE_TYPE_JPEG); XSSFPicture shape2 = drawing.createPicture(anchor, jpegIdx); assertEquals(2, shape2.getCTPicture().getNvPicPr().getCNvPr().getId()); + wb.close(); } /** * same image refrerred by mulitple sheets */ - public void testMultiRelationShips(){ + @SuppressWarnings("resource") + @Test + public void multiRelationShips() throws IOException { XSSFWorkbook wb = new XSSFWorkbook(); byte[] pic1Data = "test jpeg data".getBytes(); @@ -140,6 +167,7 @@ public final class TestXSSFPicture extends BaseTestPicture { XSSFPicture shape44 = (XSSFPicture)drawing2.getShapes().get(1); assertArrayEquals(shape4.getPictureData().getData(), shape44.getPictureData().getData()); + wb.close(); } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java index 132a45af5c..7a1ae1f1b6 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java @@ -18,7 +18,10 @@ package org.apache.poi.hssf.usermodel; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.util.List; @@ -32,6 +35,8 @@ import org.apache.poi.ss.usermodel.ClientAnchor; import org.apache.poi.ss.usermodel.CreationHelper; import org.apache.poi.ss.usermodel.PictureData; import org.apache.poi.ss.usermodel.Workbook; +import org.junit.Test; +import org.junit.Ignore; /** * Test HSSFPicture. @@ -44,14 +49,23 @@ public final class TestHSSFPicture extends BaseTestPicture { super(HSSFITestDataProvider.instance); } - public void testResize() { - baseTestResize(new HSSFClientAnchor(0, 0, 848, 240, (short)0, 0, (short)1, 9)); + @Test + public void resize() throws Exception { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("resize_compare.xls"); + HSSFPatriarch dp = wb.getSheetAt(0).createDrawingPatriarch(); + List pics = dp.getChildren(); + HSSFPicture inpPic = (HSSFPicture)pics.get(0); + HSSFPicture cmpPic = (HSSFPicture)pics.get(1); + + baseTestResize(inpPic, cmpPic, 2.0, 2.0); + wb.close(); } - + /** * Bug # 45829 reported ArithmeticException (/ by zero) when resizing png with zero DPI. */ - public void test45829() { + @Test + public void bug45829() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); HSSFSheet sh1 = wb.createSheet(); HSSFPatriarch p1 = sh1.createDrawingPatriarch(); @@ -60,10 +74,14 @@ public final class TestHSSFPicture extends BaseTestPicture { int idx1 = wb.addPicture( pictureData, HSSFWorkbook.PICTURE_TYPE_PNG ); HSSFPicture pic = p1.createPicture(new HSSFClientAnchor(), idx1); pic.resize(); + + wb.close(); } - public void testAddPictures(){ + @SuppressWarnings("resource") + @Test + public void addPictures() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); HSSFSheet sh = wb.createSheet("Pictures"); @@ -154,9 +172,13 @@ public final class TestHSSFPicture extends BaseTestPicture { assertArrayEquals(data2, ((HSSFPicture)dr.getChildren().get(1)).getPictureData().getData()); assertArrayEquals(data3, ((HSSFPicture)dr.getChildren().get(2)).getPictureData().getData()); assertArrayEquals(data4, ((HSSFPicture)dr.getChildren().get(3)).getPictureData().getData()); + + wb.close(); } - public void testBSEPictureRef(){ + @SuppressWarnings("unused") + @Test + public void bsePictureRef() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); HSSFSheet sh = wb.createSheet("Pictures"); @@ -180,9 +202,12 @@ public final class TestHSSFPicture extends BaseTestPicture { HSSFShapeGroup gr = dr.createGroup(new HSSFClientAnchor()); gr.createPicture(new HSSFChildAnchor(), idx1); assertEquals(bse.getRef(), 3); + + wb.close(); } - public void testReadExistingImage(){ + @Test + public void readExistingImage(){ HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("drawings.xls"); HSSFSheet sheet = wb.getSheet("picture"); HSSFPatriarch drawing = sheet.getDrawingPatriarch(); @@ -192,7 +217,9 @@ public final class TestHSSFPicture extends BaseTestPicture { assertEquals(picture.getFileName(), "test"); } - public void testSetGetProperties(){ + @SuppressWarnings("resource") + @Test + public void setGetProperties() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); HSSFSheet sh = wb.createSheet("Pictures"); @@ -214,9 +241,12 @@ public final class TestHSSFPicture extends BaseTestPicture { p1 = (HSSFPicture) dr.getChildren().get(0); assertEquals(p1.getFileName(), "aaa"); + wb.close(); } - public void test49658() throws IOException { + @SuppressWarnings("resource") + @Test + public void bug49658() throws IOException { // test if inserted EscherMetafileBlip will be read again HSSFWorkbook wb = new HSSFWorkbook(); @@ -262,6 +292,7 @@ public final class TestHSSFPicture extends BaseTestPicture { pictureDataOut = wb.getAllPictures().get(2).getData(); assertArrayEquals(wmfNoHeader, pictureDataOut); + wb.close(); } } diff --git a/src/testcases/org/apache/poi/ss/usermodel/BaseTestPicture.java b/src/testcases/org/apache/poi/ss/usermodel/BaseTestPicture.java index b19513759d..22247ebdf7 100644 --- a/src/testcases/org/apache/poi/ss/usermodel/BaseTestPicture.java +++ b/src/testcases/org/apache/poi/ss/usermodel/BaseTestPicture.java @@ -17,14 +17,19 @@ package org.apache.poi.ss.usermodel; -import junit.framework.TestCase; +import static org.junit.Assert.assertEquals; + +import java.awt.Dimension; +import java.io.FileOutputStream; import org.apache.poi.ss.ITestDataProvider; +import org.apache.poi.ss.util.ImageUtils; +import org.apache.poi.util.Units; /** * @author Yegor Kozlov */ -public abstract class BaseTestPicture extends TestCase { +public abstract class BaseTestPicture { private final ITestDataProvider _testDataProvider; @@ -32,26 +37,32 @@ public abstract class BaseTestPicture extends TestCase { _testDataProvider = testDataProvider; } - public void baseTestResize(ClientAnchor referenceAnchor) { - Workbook wb = _testDataProvider.createWorkbook(); - Sheet sh1 = wb.createSheet(); - Drawing p1 = sh1.createDrawingPatriarch(); - CreationHelper factory = wb.getCreationHelper(); + public void baseTestResize(Picture input, Picture compare, double scaleX, double scaleY) { + input.resize(scaleX, scaleY); + + ClientAnchor inpCA = input.getClientAnchor(); + ClientAnchor cmpCA = compare.getClientAnchor(); + + Dimension inpDim = ImageUtils.getDimensionFromAnchor(input); + Dimension cmpDim = ImageUtils.getDimensionFromAnchor(compare); - byte[] pictureData = _testDataProvider.getTestDataFileContent("logoKarmokar4.png"); - int idx1 = wb.addPicture( pictureData, Workbook.PICTURE_TYPE_PNG ); - Picture picture = p1.createPicture(factory.createClientAnchor(), idx1); - picture.resize(); - ClientAnchor anchor1 = picture.getPreferredSize(); + double emuPX = Units.EMU_PER_PIXEL; + + assertEquals("the image height differs", inpDim.getHeight(), cmpDim.getHeight(), emuPX*6); + assertEquals("the image width differs", inpDim.getWidth(), cmpDim.getWidth(), emuPX*6); + assertEquals("the starting column differs", inpCA.getCol1(), cmpCA.getCol1()); + assertEquals("the column x-offset differs", inpCA.getDx1(), cmpCA.getDx1(), 1); + assertEquals("the column y-offset differs", inpCA.getDy1(), cmpCA.getDy1(), 1); + assertEquals("the ending columns differs", inpCA.getCol2(), cmpCA.getCol2()); + // can't compare row heights because of variable test heights + + input.resize(); + inpDim = ImageUtils.getDimensionFromAnchor(input); + + Dimension imgDim = input.getImageDimension(); - //assert against what would BiffViewer print if we insert the image in xls and dump the file - assertEquals(referenceAnchor.getCol1(), anchor1.getCol1()); - assertEquals(referenceAnchor.getRow1(), anchor1.getRow1()); - assertEquals(referenceAnchor.getCol2(), anchor1.getCol2()); - assertEquals(referenceAnchor.getRow2(), anchor1.getRow2()); - assertEquals(referenceAnchor.getDx1(), anchor1.getDx1()); - assertEquals(referenceAnchor.getDy1(), anchor1.getDy1()); - assertEquals(referenceAnchor.getDx2(), anchor1.getDx2()); - assertEquals(referenceAnchor.getDy2(), anchor1.getDy2()); + assertEquals("the image height differs", imgDim.getHeight(), inpDim.getHeight()/emuPX, 1); + assertEquals("the image width differs", imgDim.getWidth(), inpDim.getWidth()/emuPX, 1); } + } diff --git a/test-data/spreadsheet/resize_compare.xls b/test-data/spreadsheet/resize_compare.xls new file mode 100644 index 0000000000000000000000000000000000000000..c41cc4b15dc0ef05adede8a957e5c80fa57adc2e GIT binary patch literal 23040 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

q+v;%g3s@ZHTc<+FbzStrx2Xk6{BYs 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 LG$FMhd1U(m1JsG_ literal 0 HcmV?d00001 diff --git a/test-data/spreadsheet/resize_compare.xlsx b/test-data/spreadsheet/resize_compare.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..7fb4437fe86662f6df30d334f2d81571e6cd7ab1 GIT binary patch literal 37715 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