Bug 59753 - The XSSFPicture.getPreferredSize returns incorrect anchor col2 and row2 due to error in ImageUtils.setPreferredSize
Summary: The XSSFPicture.getPreferredSize returns incorrect anchor col2 and row2 due t...
Status: NEW
Alias: None
Product: POI
Classification: Unclassified
Component: XSSF (show other bugs)
Version: 3.14-FINAL
Hardware: PC All
: P2 regression with 4 votes (vote)
Target Milestone: ---
Assignee: POI Developers List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2016-06-24 17:57 UTC by Mike Ford
Modified: 2016-06-28 17:05 UTC (History)
0 users



Attachments
test.png (126.77 KB, image/png)
2016-06-24 17:57 UTC, Mike Ford
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Mike Ford 2016-06-24 17:57:57 UTC
Created attachment 33986 [details]
test.png

If I do the following:
  Workbook workbook = new XSSFWorkbook();
  Sheet sheet = workbook.createSheet();
  byte[] imageBytes = org.apache.commons.io.IOUtils.toByteArray(new FileInputStream(test.png));
  Drawing drawing = sheet.createDrawingPatriarch();
  int pictureIdx = workbook.addPicture(imageBytes, Workbook.PICTURE_TYPE_PNG);
  CreationHelper helper = workbook.getCreationHelper();
  ClientAnchor anchor = helper.createClientAnchor();
  anchor.setCol1(0); // Not needed, but let's make sure we are in the upper left
  anchor.setRow1(0); // corner of the spreadsheet.
  Picture pict = drawing.createPicture(anchor, pictureIdx);

  anchor.pict.getPreferredSize();  //This will return 1 and 1 for col2/row2
  pict.resize(0.75);  //This will return 0 and 0 for col2/row2

  int lastColumn = anchor.getCol2();
  int lastRow = anchor.getRow2();

The last row and column are useful for placing other data below or beside the image.  The byte array is used to get an image stored in memory and not on the file system.

ImageUtils.setPreferredSize snippet for col2:
        // 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 *= 1d - anchor.getDx1()/1024d;
        } else {
            w -= anchor.getDx1()/(double)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);

This is the snippet from XSSFPicture.getPreferredSize in 3.8:
        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);
Also please note as you debug that the scaledWidth and scaledHeight in 3.14 end up being almost identical to the value returned from columnWidthInPixels.  The columnWidthInPixels is ~6 pixels smaller that the 3.8 version.  The 3.8 version increments the columns with a check of w against the number of pixels in the width of the picture.  So you end up with an image that is always squeezed into a single cell.
Comment 1 Mike Ford 2016-06-24 18:00:35 UTC
You end up with a single cell in 3.14 and a correct image in 3.8.
Comment 2 Mike Ford 2016-06-28 17:05:05 UTC
I did some tests on my machine and found this worked as late as 3.10-FINAL, but was broken in the 3.11 release.  Interestingly 3.11, 12, and 13 return col2 as 1 and row2 as 0.  3.14 has both col2 and row2  as 0.