You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AddDimensionedImage.java 52KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.examples.ss;
  16. import java.io.File;
  17. import java.io.FileOutputStream;
  18. import java.io.IOException;
  19. import java.net.URL;
  20. import java.util.Locale;
  21. import org.apache.poi.hssf.usermodel.HSSFSheet;
  22. import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  23. import org.apache.poi.ss.usermodel.ClientAnchor;
  24. import org.apache.poi.ss.usermodel.ClientAnchor.AnchorType;
  25. import org.apache.poi.ss.usermodel.Drawing;
  26. import org.apache.poi.ss.usermodel.Row;
  27. import org.apache.poi.ss.usermodel.Sheet;
  28. import org.apache.poi.ss.usermodel.Workbook;
  29. import org.apache.poi.ss.util.CellReference;
  30. import org.apache.poi.util.IOUtils;
  31. /**
  32. * Demonstrates how to add an image to a worksheet and set that images size
  33. * to a specific number of millimetres irrespective of the width of the columns
  34. * or height of the rows. Overridden methods are provided so that the location
  35. * of the image - the cells row and column coordinates that define the top
  36. * left hand corners of the image - can be identified either in the familiar
  37. * Excel manner - A1 for instance - or using POI's methodology of a column and
  38. * row index where 0, 0 would indicate cell A1.
  39. *
  40. * The best way to make use of these techniques is to delay adding the image to
  41. * the sheet until all other work has been completed. That way, the sizes of
  42. * all rows and columns will have been adjusted - assuming that step was
  43. * necessary. Even though the anchors type is set to prevent the image moving
  44. * or re-sizing, this setting does not have any effect until the sheet is being
  45. * viewed using the Excel application.
  46. *
  47. * The key to the process is the ClientAnchor class. It defines methods that allow
  48. * us to define the location of an image by specifying the following;
  49. *
  50. * * How far - in terms of coordinate positions - the image should be inset
  51. * from the left hand border of a cell.
  52. * * How far - in terms of coordinate positions - the image should be inset
  53. * from the from the top of the cell.
  54. * * How far - in terms of coordinate positions - the right hand edge of
  55. * the image should protrude into a cell (measured from the cells left hand
  56. * edge to the images right hand edge).
  57. * * How far - in terms of coordinate positions - the bottom edge of the
  58. * image should protrude into a row (measured from the cells top edge to
  59. * the images bottom edge).
  60. * * The index of the column that contains the cell whose top left hand
  61. * corner should be aligned with the top left hand corner of the image.
  62. * * The index of the row that contains the cell whose top left hand corner
  63. * should be aligned with the images top left hand corner.
  64. * * The index of the column that contains the cell whose top left hand
  65. * corner should be aligned with the images bottom right hand corner
  66. * * The index number of the row that contains the cell whose top left
  67. * hand corner should be aligned with the images bottom right hand corner.
  68. *
  69. * It can be used to add an image into cell A1, for example, in the following
  70. * manner;
  71. *
  72. * ClientAnchor anchor = sheet.getWorkbook().getCreationHelper().createClientAnchor();
  73. *
  74. * anchor.setDx1(0);
  75. * anchor.setDy1(0);
  76. * anchor.setDx2(0);
  77. * anchor.setDy2(0);
  78. * anchor.setCol1(0);
  79. * anchor.setRow1(0);
  80. * anchor.setCol2(1);
  81. * anchor.setRow2(1);
  82. *
  83. * Taken together, the first four methods define the locations of the top left
  84. * and bottom right hand corners of the image if you imagine that the image is
  85. * represented by a simple rectangle. The setDx1() and setDy1() methods locate
  86. * the top left hand corner of the image while setDx2() and and Dy2() locate the
  87. * bottom right hand corner of the image. An individual image can be inserted
  88. * into a single cell or is can lie across many cells and the latter four methods
  89. * are used to define just where the image should be positioned. They do this by
  90. * again by identifying where the top left and bottom right hand corners of the
  91. * image should be located but this time in terms of the indexes of the cells
  92. * in which those corners should be located. The setCol1() and setRow1() methods
  93. * together identify the cell that should contain the top left hand corner of
  94. * the image while setCol2() and setRow2() do the same for the images bottom
  95. * right hand corner.
  96. *
  97. * Knowing that, it is possible to look again at the example above and to see
  98. * that the top left hand corner of the image will be located in cell A1 (0, 0)
  99. * and it will be aligned with the very top left hand corner of the cell. Likewise,
  100. * the bottom right hand corner of the image will be located in cell B2 (1, 1) and
  101. * it will again be aligned with the top left hand corner of the cell. This has the
  102. * effect of making the image seem to occupy the whole of cell A1. Interestingly, it
  103. * also has an effect on the images resizing behaviour because testing has
  104. * demonstrated that if the image is wholly contained within one cell and is not
  105. * 'attached' for want of a better word, to a neighbouring cell, then that image
  106. * will not increase in size in response to the user dragging the column wider
  107. * or the cell higher.
  108. *
  109. * The following example demonstrates a slightly different way to insert an
  110. * image into cell A1 and to ensure that it occupies the whole of the cell. This
  111. * is accomplished by specifying the the images bottom right hand corner should be
  112. * aligned with the bottom right hand corner of the cell. It is also a case
  113. * where the image will not increase in size if the user increases the size of
  114. * the enclosing cell - irrespective of the anchors type - but it will reduce in
  115. * size if the cell is made smaller.
  116. *
  117. * ClientAnchor anchor = sheet.getWorkbook().getCreationHelper().createClientAnchor();
  118. *
  119. * anchor.setDx1(0);
  120. * anchor.setDy1(0);
  121. * anchor.setDx2(1023);
  122. * anchor.setDy2(255);
  123. * anchor.setCol1(0);
  124. * anchor.setRow1(0);
  125. * anchor.setCol2(0);
  126. * anchor.setRow2(0);
  127. *
  128. * Note that the final four method calls all pass the same value and seem to
  129. * indicate that the images top left hand corner is aligned with the top left
  130. * hand corner of cell A1 and that it's bottom right hand corner is also
  131. * aligned with the top left hand corner of cell A1. Yet, running this code
  132. * would see the image fully occupying cell A1. That is the result of the
  133. * values passed to parameters three and four; these I have referred to as
  134. * determining the images coordinates within the cell. They indicate that the
  135. * image should occupy - in order - the full width of the column and the full
  136. * height of the row.
  137. *
  138. * The co-ordinate values shown are the maxima; and they are independent of
  139. * row height/column width and of the font used. Passing 255 will always result
  140. * in the image occupying the full height of the row and passing 1023 will
  141. * always result in the image occupying the full width of the column. They help
  142. * in situations where an image is larger than a column/row and must overlap
  143. * into the next column/row. Using them does mean, however, that it is often
  144. * necessary to perform conversions between Excels characters units, points,
  145. * pixels and millimetres in order to establish how many rows/columns an image
  146. * should occupy and just what the various insets ought to be.
  147. *
  148. * Note that the setDx1(int) and setDy1(int) methods of the ClientAchor class
  149. * are not made use of in the code that follows. It would be fairly trivial
  150. * however to extend this example further and provide methods that would centre
  151. * an image within a cell or allow the user to specify that a plain border a
  152. * fixed number of millimetres wide should wrap around the image. Those first
  153. * two parameters would make this sort of functionality perfectly possible.
  154. *
  155. * Owing to the various conversions used, the actual size of the image may vary
  156. * from that required; testing has so far found this to be in the region of
  157. * plus or minus two millimetres. Most likely by modifying the way the
  158. * calculations are performed - possibly using double(s) throughout and
  159. * rounding the values at the correct point - it is likely that these errors
  160. * could be reduced or removed.
  161. *
  162. * A note concerning Excels image resizing behaviour. The ClientAnchor
  163. * class contains a method called setAnchorType(int) which can be used to
  164. * determine how Excel will resize an image in response to the user increasing
  165. * or decreasing the dimensions of the cell containing the image. There are
  166. * three values that can be passed to this method; 0 = To move and size the
  167. * image with the cell, 2 = To move but don't size the image with the cell,
  168. * 3 = To prevent the image from moving or being resized along with the cell. If
  169. * an image is inserted using this class and placed into a single cell then if
  170. * the setAnchorType(int) method is called and a value of either 0 or 2 passed
  171. * to it, the resultant resizing behaviour may be a surprise. The image will not
  172. * grow in size of the column is made wider or the row higher but it will shrink
  173. * if the columns width or rows height are reduced.
  174. *
  175. * @author Mark Beardsley [msb at apache.org] and Mark Southern [southern at scripps.edu]
  176. * @version 1.00 5th August 2009.
  177. * 2.00 26th February 2010.
  178. * Ported to make use of the the SS usermodel classes.
  179. * Ability to reuse the Drawing Patriarch so that multiple images
  180. * can be inserted without unintentionally erasing earlier images.
  181. * Check on image type added; i.e. jpg, jpeg or png.
  182. * The String used to contain the files name is now converted
  183. * into a URL.
  184. * 2.10 17th May 2012
  185. * Corrected gross error that occurred when using the code with
  186. * XSSF or SXSSF workbooks. In short, the code did not correctly
  187. * calculate the size of the image(s) owing to the use of EMUs
  188. * within the OOXML file format. That problem has largely been
  189. * corrected although it should be mentioned that images are not
  190. * sized with the same level of accuracy. Discrepancies of up to
  191. * 2mm have been noted in testing. Further investigation will
  192. * continue to rectify this issue.
  193. */
  194. @SuppressWarnings({"java:S106","java:S4823"})
  195. public class AddDimensionedImage {
  196. // Four constants that determine how - and indeed whether - the rows
  197. // and columns an image may overlie should be expanded to accommodate that
  198. // image.
  199. // Passing EXPAND_ROW will result in the height of a row being increased
  200. // to accommodate the image if it is not already larger. The image will
  201. // be layed across one or more columns.
  202. // Passing EXPAND_COLUMN will result in the width of the column being
  203. // increased to accommodate the image if it is not already larger. The image
  204. // will be layed across one or many rows.
  205. // Passing EXPAND_ROW_AND_COLUMN will result in the height of the row
  206. // bing increased along with the width of the column to accomdate the
  207. // image if either is not already larger.
  208. // Passing OVERLAY_ROW_AND_COLUMN will result in the image being layed
  209. // over one or more rows and columns. No row or column will be resized,
  210. // instead, code will determine how many rows and columns the image should
  211. // overlie.
  212. public static final int EXPAND_ROW = 1;
  213. public static final int EXPAND_COLUMN = 2;
  214. public static final int EXPAND_ROW_AND_COLUMN = 3;
  215. public static final int OVERLAY_ROW_AND_COLUMN = 7;
  216. // Modified to support EMU - English Metric Units - used within the OOXML
  217. // workbooks, this multoplier is used to convert between measurements in
  218. // millimetres and in EMUs
  219. private static final int EMU_PER_MM = 36000;
  220. /**
  221. * Add an image to a worksheet.
  222. *
  223. * @param cellNumber A String that contains the location of the cell whose
  224. * top left hand corner should be aligned with the top
  225. * left hand corner of the image; for example "A1", "A2"
  226. * etc. This is to support the familiar Excel syntax.
  227. * Whilst images are are not actually inserted into cells
  228. * this provides a convenient method of indicating where
  229. * the image should be positioned on the sheet.
  230. * @param sheet A reference to the sheet that contains the cell referenced
  231. * above.
  232. * @param drawing An instance of the DrawingPatriarch class. This is now
  233. * passed into the method where it was, previously, recovered
  234. * from the sheet in order to allow multiple pictures be
  235. * inserted. If the patriarch was not 'cached in this manner
  236. * each time it was created any previously positioned images
  237. * would be simply over-written.
  238. * @param imageFile An instance of the URL class that encapsulates the name
  239. * of and path to the image that is to be 'inserted into'
  240. * the sheet.
  241. * @param reqImageWidthMM A primitive double that contains the required
  242. * width of the image in millimetres.
  243. * @param reqImageHeightMM A primitive double that contains the required
  244. * height of the image in millimetres.
  245. * @param resizeBehaviour A primitive int whose value will determine how
  246. * the code should react if the image is larger than
  247. * the cell referenced by the cellNumber parameter.
  248. * Four constants are provided to determine what
  249. * should happen;
  250. * AddDimensionedImage.EXPAND_ROW
  251. * AddDimensionedImage.EXPAND_COLUMN
  252. * AddDimensionedImage.EXPAND_ROW_AND_COLUMN
  253. * AddDimensionedImage.OVERLAY_ROW_AND_COLUMN
  254. * @throws java.io.FileNotFoundException If the file containing the image
  255. * cannot be located.
  256. * @throws java.io.IOException If a problem occurs whilst reading the file
  257. * of image data.
  258. * @throws java.lang.IllegalArgumentException If an invalid value is passed
  259. * to the resizeBehaviour
  260. * parameter.
  261. */
  262. public void addImageToSheet(String cellNumber, Sheet sheet, Drawing<?> drawing,
  263. URL imageFile, double reqImageWidthMM, double reqImageHeightMM,
  264. int resizeBehaviour) throws IOException, IllegalArgumentException {
  265. // Convert the String into column and row indices then chain the
  266. // call to the overridden addImageToSheet() method.
  267. CellReference cellRef = new CellReference(cellNumber);
  268. this.addImageToSheet(cellRef.getCol(), cellRef.getRow(), sheet, drawing,
  269. imageFile, reqImageWidthMM, reqImageHeightMM,resizeBehaviour);
  270. }
  271. /**
  272. * Add an image to a worksheet.
  273. *
  274. * @param colNumber A primitive int that contains the index number of a
  275. * column on the worksheet; POI column indices are zero
  276. * based. Together with the rowNumber parameter's value,
  277. * this parameter identifies a cell on the worksheet. The
  278. * images top left hand corner will be aligned with the
  279. * top left hand corner of this cell.
  280. * @param rowNumber A primitive int that contains the index number of a row
  281. * on the worksheet; POI row indices are zero based.
  282. * Together with the rowNumber parameter's value, this
  283. * parameter identifies a cell on the worksheet. The
  284. * images top left hand corner will be aligned with the
  285. * top left hand corner of this cell.
  286. * @param sheet A reference to the sheet that contains the cell identified
  287. * by the two parameters above.
  288. * @param drawing An instance of the DrawingPatriarch class. This is now
  289. * passed into the method where it was, previously, recovered
  290. * from the sheet in order to allow multiple pictures be
  291. * inserted. If the patriarch was not 'cached in this manner
  292. * each time it was created any previously positioned images
  293. * would be simply over-written.
  294. * @param imageFile An instance of the URL class that encapsulates the name
  295. * of and path to the image that is to be 'inserted into'
  296. * the sheet.
  297. * @param reqImageWidthMM A primitive double that contains the required
  298. * width of the image in millimetres.
  299. * @param reqImageHeightMM A primitive double that contains the required
  300. * height of the image in millimetres.
  301. * @param resizeBehaviour A primitive int whose value will determine how
  302. * the code should react if the image is larger than
  303. * the cell referenced by the colNumber and
  304. * rowNumber parameters. Four constants are provided
  305. * to determine what should happen;
  306. * AddDimensionedImage.EXPAND_ROW
  307. * AddDimensionedImage.EXPAND_COLUMN
  308. * AddDimensionedImage.EXPAND_ROW_AND_COLUMN
  309. * AddDimensionedImage.OVERLAY_ROW_AND_COLUMN
  310. * @throws java.io.FileNotFoundException If the file containing the image
  311. * cannot be located.
  312. * @throws java.io.IOException If a problem occurs whilst reading the file
  313. * of image data.
  314. * @throws java.lang.IllegalArgumentException If an invalid value is passed
  315. * to the resizeBehaviour
  316. * parameter or if the extension
  317. * of the image file indicates that
  318. * it is of a type that cannot
  319. * currently be added to the worksheet.
  320. */
  321. public void addImageToSheet(int colNumber, int rowNumber, Sheet sheet, Drawing<?> drawing,
  322. URL imageFile, double reqImageWidthMM, double reqImageHeightMM,
  323. int resizeBehaviour) throws IOException,
  324. IllegalArgumentException {
  325. ClientAnchor anchor;
  326. ClientAnchorDetail rowClientAnchorDetail;
  327. ClientAnchorDetail colClientAnchorDetail;
  328. int imageType;
  329. // Validate the resizeBehaviour parameter.
  330. if((resizeBehaviour != AddDimensionedImage.EXPAND_COLUMN) &&
  331. (resizeBehaviour != AddDimensionedImage.EXPAND_ROW) &&
  332. (resizeBehaviour != AddDimensionedImage.EXPAND_ROW_AND_COLUMN) &&
  333. (resizeBehaviour != AddDimensionedImage.OVERLAY_ROW_AND_COLUMN)) {
  334. throw new IllegalArgumentException("Invalid value passed to the " +
  335. "resizeBehaviour parameter of AddDimensionedImage.addImageToSheet()");
  336. }
  337. // Call methods to calculate how the image and sheet should be
  338. // manipulated to accommodate the image; columns and then rows.
  339. colClientAnchorDetail = this.fitImageToColumns(sheet, colNumber,
  340. reqImageWidthMM, resizeBehaviour);
  341. rowClientAnchorDetail = this.fitImageToRows(sheet, rowNumber,
  342. reqImageHeightMM, resizeBehaviour);
  343. // Having determined if and how to resize the rows, columns and/or the
  344. // image, create the ClientAnchor object to position the image on
  345. // the worksheet. Note how the two ClientAnchorDetail records are
  346. // interrogated to recover the row/column co-ordinates and any insets.
  347. // The first two parameters are not used currently but could be if the
  348. // need arose to extend the functionality of this code by adding the
  349. // ability to specify that a clear 'border' be placed around the image.
  350. anchor = sheet.getWorkbook().getCreationHelper().createClientAnchor();
  351. anchor.setDx1(0);
  352. anchor.setDy1(0);
  353. if (colClientAnchorDetail != null) {
  354. anchor.setDx2(colClientAnchorDetail.getInset());
  355. anchor.setCol1(colClientAnchorDetail.getFromIndex());
  356. anchor.setCol2(colClientAnchorDetail.getToIndex());
  357. }
  358. if (rowClientAnchorDetail != null) {
  359. anchor.setDy2(rowClientAnchorDetail.getInset());
  360. anchor.setRow1(rowClientAnchorDetail.getFromIndex());
  361. anchor.setRow2(rowClientAnchorDetail.getToIndex());
  362. }
  363. // For now, set the anchor type to do not move or resize the
  364. // image as the size of the row/column is adjusted. This could easily
  365. // become another parameter passed to the method. Please read the note
  366. // above regarding the behaviour of image resizing.
  367. anchor.setAnchorType(AnchorType.MOVE_AND_RESIZE);
  368. // Now, add the picture to the workbook. Note that unlike the similar
  369. // method in the HSSF Examples section, the image type is checked. First,
  370. // the image files location is identified by interrogating the URL passed
  371. // to the method, the images type is identified before it is added to the
  372. // sheet.
  373. String sURL = imageFile.toString().toLowerCase(Locale.ROOT);
  374. if( sURL.endsWith(".png") ) {
  375. imageType = Workbook.PICTURE_TYPE_PNG;
  376. }
  377. else if( sURL.endsWith(".jpg") || sURL.endsWith(".jpeg") ) {
  378. imageType = Workbook.PICTURE_TYPE_JPEG;
  379. }
  380. else {
  381. throw new IllegalArgumentException("Invalid Image file : " +
  382. sURL);
  383. }
  384. int index = sheet.getWorkbook().addPicture(
  385. IOUtils.toByteArray(imageFile.openStream()), imageType);
  386. drawing.createPicture(anchor, index);
  387. }
  388. /**
  389. * Determines whether the sheets columns should be re-sized to accommodate
  390. * the image, adjusts the columns width if necessary and creates then
  391. * returns a ClientAnchorDetail object that facilitates construction of
  392. * an ClientAnchor that will fix the image on the sheet and establish
  393. * it's size.
  394. *
  395. * @param sheet A reference to the sheet that will 'contain' the image.
  396. * @param colNumber A primitive int that contains the index number of a
  397. * column on the sheet.
  398. * @param reqImageWidthMM A primitive double that contains the required
  399. * width of the image in millimetres
  400. * @param resizeBehaviour A primitive int whose value will indicate how the
  401. * width of the column should be adjusted if the
  402. * required width of the image is greater than the
  403. * width of the column.
  404. * @return An instance of the ClientAnchorDetail class that will contain
  405. * the index number of the column containing the cell whose top
  406. * left hand corner also defines the top left hand corner of the
  407. * image, the index number column containing the cell whose top
  408. * left hand corner also defines the bottom right hand corner of
  409. * the image and an inset that determines how far the right hand
  410. * edge of the image can protrude into the next column - expressed
  411. * as a specific number of coordinate positions.
  412. */
  413. private ClientAnchorDetail fitImageToColumns(Sheet sheet, int colNumber,
  414. double reqImageWidthMM, int resizeBehaviour) {
  415. double colWidthMM;
  416. double colCoordinatesPerMM;
  417. int pictureWidthCoordinates;
  418. ClientAnchorDetail colClientAnchorDetail = null;
  419. // Get the colum's width in millimetres
  420. colWidthMM = ConvertImageUnits.widthUnits2Millimetres(
  421. (short)sheet.getColumnWidth(colNumber));
  422. // Check that the column's width will accommodate the image at the
  423. // required dimension. If the width of the column is LESS than the
  424. // required width of the image, decide how the application should
  425. // respond - resize the column or overlay the image across one or more
  426. // columns.
  427. if(colWidthMM < reqImageWidthMM) {
  428. // Should the column's width simply be expanded?
  429. if((resizeBehaviour == AddDimensionedImage.EXPAND_COLUMN) ||
  430. (resizeBehaviour == AddDimensionedImage.EXPAND_ROW_AND_COLUMN)) {
  431. // Set the width of the column by converting the required image
  432. // width from millimetres into Excel's column width units.
  433. sheet.setColumnWidth(colNumber,
  434. ConvertImageUnits.millimetres2WidthUnits(reqImageWidthMM));
  435. // To make the image occupy the full width of the column, convert
  436. // the required width of the image into co-ordinates. This value
  437. // will become the inset for the ClientAnchorDetail class that
  438. // is then instantiated.
  439. if(sheet instanceof HSSFSheet) {
  440. colWidthMM = reqImageWidthMM;
  441. colCoordinatesPerMM = colWidthMM == 0 ? 0
  442. : ConvertImageUnits.TOTAL_COLUMN_COORDINATE_POSITIONS / colWidthMM;
  443. pictureWidthCoordinates = (int)(reqImageWidthMM * colCoordinatesPerMM);
  444. }
  445. else {
  446. pictureWidthCoordinates = (int)reqImageWidthMM * AddDimensionedImage.EMU_PER_MM;
  447. }
  448. colClientAnchorDetail = new ClientAnchorDetail(colNumber,
  449. colNumber, pictureWidthCoordinates);
  450. }
  451. // If the user has chosen to overlay both rows and columns or just
  452. // to expand ONLY the size of the rows, then calculate how to lay
  453. // the image out across one or more columns.
  454. else if ((resizeBehaviour == AddDimensionedImage.OVERLAY_ROW_AND_COLUMN) ||
  455. (resizeBehaviour == AddDimensionedImage.EXPAND_ROW)) {
  456. colClientAnchorDetail = this.calculateColumnLocation(sheet,
  457. colNumber, reqImageWidthMM);
  458. }
  459. }
  460. // If the column is wider than the image.
  461. else {
  462. if(sheet instanceof HSSFSheet) {
  463. // Mow many co-ordinate positions are there per millimetre?
  464. colCoordinatesPerMM = ConvertImageUnits.TOTAL_COLUMN_COORDINATE_POSITIONS /
  465. colWidthMM;
  466. // Given the width of the image, what should be it's co-ordinate?
  467. pictureWidthCoordinates = (int)(reqImageWidthMM * colCoordinatesPerMM);
  468. }
  469. else {
  470. pictureWidthCoordinates = (int)reqImageWidthMM *
  471. AddDimensionedImage.EMU_PER_MM;
  472. }
  473. colClientAnchorDetail = new ClientAnchorDetail(colNumber,
  474. colNumber, pictureWidthCoordinates);
  475. }
  476. return(colClientAnchorDetail);
  477. }
  478. /**
  479. * Determines whether the sheets row should be re-sized to accommodate
  480. * the image, adjusts the rows height if necessary and creates then
  481. * returns a ClientAnchorDetail object that facilitates construction of
  482. * a ClientAnchor that will fix the image on the sheet and establish
  483. * it's size.
  484. *
  485. * @param sheet A reference to the sheet that will 'contain' the image.
  486. * @param rowNumber A primitive int that contains the index number of a
  487. * row on the sheet.
  488. * @param reqImageHeightMM A primitive double that contains the required
  489. * height of the image in millimetres
  490. * @param resizeBehaviour A primitive int whose value will indicate how the
  491. * height of the row should be adjusted if the
  492. * required height of the image is greater than the
  493. * height of the row.
  494. * @return An instance of the ClientAnchorDetail class that will contain
  495. * the index number of the row containing the cell whose top
  496. * left hand corner also defines the top left hand corner of the
  497. * image, the index number of the row containing the cell whose
  498. * top left hand corner also defines the bottom right hand
  499. * corner of the image and an inset that determines how far the
  500. * bottom edge of the image can protrude into the next (lower)
  501. * row - expressed as a specific number of coordinate positions.
  502. */
  503. private ClientAnchorDetail fitImageToRows(Sheet sheet, int rowNumber,
  504. double reqImageHeightMM, int resizeBehaviour) {
  505. Row row;
  506. double rowHeightMM;
  507. double rowCoordinatesPerMM;
  508. int pictureHeightCoordinates;
  509. ClientAnchorDetail rowClientAnchorDetail = null;
  510. // Get the row and it's height
  511. row = sheet.getRow(rowNumber);
  512. if(row == null) {
  513. // Create row if it does not exist.
  514. row = sheet.createRow(rowNumber);
  515. }
  516. // Get the row's height in millimetres
  517. rowHeightMM = row.getHeightInPoints() / ConvertImageUnits.POINTS_PER_MILLIMETRE;
  518. // Check that the row's height will accommodate the image at the required
  519. // dimensions. If the height of the row is LESS than the required height
  520. // of the image, decide how the application should respond - resize the
  521. // row or overlay the image across a series of rows.
  522. if(rowHeightMM < reqImageHeightMM) {
  523. if((resizeBehaviour == AddDimensionedImage.EXPAND_ROW) ||
  524. (resizeBehaviour == AddDimensionedImage.EXPAND_ROW_AND_COLUMN)) {
  525. row.setHeightInPoints((float)(reqImageHeightMM *
  526. ConvertImageUnits.POINTS_PER_MILLIMETRE));
  527. if(sheet instanceof HSSFSheet) {
  528. rowHeightMM = reqImageHeightMM;
  529. rowCoordinatesPerMM = rowHeightMM == 0 ? 0
  530. : ConvertImageUnits.TOTAL_ROW_COORDINATE_POSITIONS / rowHeightMM;
  531. pictureHeightCoordinates = (int)(reqImageHeightMM *
  532. rowCoordinatesPerMM);
  533. }
  534. else {
  535. pictureHeightCoordinates = (int)(reqImageHeightMM *
  536. AddDimensionedImage.EMU_PER_MM);
  537. }
  538. rowClientAnchorDetail = new ClientAnchorDetail(rowNumber,
  539. rowNumber, pictureHeightCoordinates);
  540. }
  541. // If the user has chosen to overlay both rows and columns or just
  542. // to expand ONLY the size of the columns, then calculate how to lay
  543. // the image out ver one or more rows.
  544. else if((resizeBehaviour == AddDimensionedImage.OVERLAY_ROW_AND_COLUMN) ||
  545. (resizeBehaviour == AddDimensionedImage.EXPAND_COLUMN)) {
  546. rowClientAnchorDetail = this.calculateRowLocation(sheet,
  547. rowNumber, reqImageHeightMM);
  548. }
  549. }
  550. // Else, if the image is smaller than the space available
  551. else {
  552. if(sheet instanceof HSSFSheet) {
  553. rowCoordinatesPerMM = ConvertImageUnits.TOTAL_ROW_COORDINATE_POSITIONS /
  554. rowHeightMM;
  555. pictureHeightCoordinates = (int)(reqImageHeightMM * rowCoordinatesPerMM);
  556. }
  557. else {
  558. pictureHeightCoordinates = (int)(reqImageHeightMM *
  559. AddDimensionedImage.EMU_PER_MM);
  560. }
  561. rowClientAnchorDetail = new ClientAnchorDetail(rowNumber,
  562. rowNumber, pictureHeightCoordinates);
  563. }
  564. return(rowClientAnchorDetail);
  565. }
  566. /**
  567. * If the image is to overlie more than one column, calculations need to be
  568. * performed to determine how many columns and whether the image will
  569. * overlie just a part of one column in order to be presented at the
  570. * required size.
  571. *
  572. * @param sheet The sheet that will 'contain' the image.
  573. * @param startingColumn A primitive int whose value is the index of the
  574. * column that contains the cell whose top left hand
  575. * corner should be aligned with the top left hand
  576. * corner of the image.
  577. * @param reqImageWidthMM A primitive double whose value will indicate the
  578. * required width of the image in millimetres.
  579. * @return An instance of the ClientAnchorDetail class that will contain
  580. * the index number of the column containing the cell whose top
  581. * left hand corner also defines the top left hand corner of the
  582. * image, the index number column containing the cell whose top
  583. * left hand corner also defines the bottom right hand corner of
  584. * the image and an inset that determines how far the right hand
  585. * edge of the image can protrude into the next column - expressed
  586. * as a specific number of coordinate positions.
  587. */
  588. private ClientAnchorDetail calculateColumnLocation(Sheet sheet,
  589. int startingColumn,
  590. double reqImageWidthMM) {
  591. ClientAnchorDetail anchorDetail;
  592. double totalWidthMM = 0.0D;
  593. double colWidthMM = 0.0D;
  594. double overlapMM;
  595. double coordinatePositionsPerMM;
  596. int toColumn = startingColumn;
  597. int inset;
  598. // Calculate how many columns the image will have to
  599. // span in order to be presented at the required size.
  600. while(totalWidthMM < reqImageWidthMM) {
  601. colWidthMM = ConvertImageUnits.widthUnits2Millimetres(
  602. (short)(sheet.getColumnWidth(toColumn)));
  603. // Note use of the cell border width constant. Testing with an image
  604. // declared to fit exactly into one column demonstrated that it's
  605. // width was greater than the width of the column the POI returned.
  606. // Further, this difference was a constant value that I am assuming
  607. // related to the cell's borders. Either way, that difference needs
  608. // to be allowed for in this calculation.
  609. totalWidthMM += (colWidthMM + ConvertImageUnits.CELL_BORDER_WIDTH_MILLIMETRES);
  610. toColumn++;
  611. }
  612. // De-crement by one the last column value.
  613. toColumn--;
  614. // Highly unlikely that this will be true but, if the width of a series
  615. // of columns is exactly equal to the required width of the image, then
  616. // simply build a ClientAnchorDetail object with an inset equal to the
  617. // total number of co-ordinate positions available in a column, a
  618. // from column co-ordinate (top left hand corner) equal to the value
  619. // of the startingColumn parameter and a to column co-ordinate equal
  620. // to the toColumn variable.
  621. //
  622. // Convert both values to ints to perform the test.
  623. if((int)totalWidthMM == (int)reqImageWidthMM) {
  624. // A problem could occur if the image is sized to fit into one or
  625. // more columns. If that occurs, the value in the toColumn variable
  626. // will be in error. To overcome this, there are two options, to
  627. // ibcrement the toColumn variable's value by one or to pass the
  628. // total number of co-ordinate positions to the third paramater
  629. // of the ClientAnchorDetail constructor. For no sepcific reason,
  630. // the latter option is used below.
  631. if(sheet instanceof HSSFSheet) {
  632. anchorDetail = new ClientAnchorDetail(startingColumn,
  633. toColumn, ConvertImageUnits.TOTAL_COLUMN_COORDINATE_POSITIONS);
  634. }
  635. else {
  636. anchorDetail = new ClientAnchorDetail(startingColumn,
  637. toColumn, (int)reqImageWidthMM * AddDimensionedImage.EMU_PER_MM);
  638. }
  639. }
  640. // In this case, the image will overlap part of another column and it is
  641. // necessary to calculate just how much - this will become the inset
  642. // for the ClientAnchorDetail object.
  643. else {
  644. // Firstly, claculate how much of the image should overlap into
  645. // the next column.
  646. overlapMM = reqImageWidthMM - (totalWidthMM - colWidthMM);
  647. // When the required size is very close indded to the column size,
  648. // the calcaulation above can produce a negative value. To prevent
  649. // problems occuring in later caculations, this is simply removed
  650. // be setting the overlapMM value to zero.
  651. if(overlapMM < 0) {
  652. overlapMM = 0.0D;
  653. }
  654. if(sheet instanceof HSSFSheet) {
  655. // Next, from the columns width, calculate how many co-ordinate
  656. // positons there are per millimetre
  657. coordinatePositionsPerMM = (colWidthMM == 0) ? 0
  658. : ConvertImageUnits.TOTAL_COLUMN_COORDINATE_POSITIONS / colWidthMM;
  659. // From this figure, determine how many co-ordinat positions to
  660. // inset the left hand or bottom edge of the image.
  661. inset = (int)(coordinatePositionsPerMM * overlapMM);
  662. }
  663. else {
  664. inset = (int)overlapMM * AddDimensionedImage.EMU_PER_MM;
  665. }
  666. // Now create the ClientAnchorDetail object, setting the from and to
  667. // columns and the inset.
  668. anchorDetail = new ClientAnchorDetail(startingColumn, toColumn, inset);
  669. }
  670. return(anchorDetail);
  671. }
  672. /**
  673. * If the image is to overlie more than one rows, calculations need to be
  674. * performed to determine how many rows and whether the image will
  675. * overlie just a part of one row in order to be presented at the
  676. * required size.
  677. *
  678. * @param sheet The sheet that will 'contain' the image.
  679. * @param startingRow A primitive int whose value is the index of the row
  680. * that contains the cell whose top left hand corner
  681. * should be aligned with the top left hand corner of
  682. * the image.
  683. * @param reqImageHeightMM A primitive double whose value will indicate the
  684. * required height of the image in millimetres.
  685. * @return An instance of the ClientAnchorDetail class that will contain
  686. * the index number of the row containing the cell whose top
  687. * left hand corner also defines the top left hand corner of the
  688. * image, the index number of the row containing the cell whose top
  689. * left hand corner also defines the bottom right hand corner of
  690. * the image and an inset that determines how far the bottom edge
  691. * can protrude into the next (lower) row - expressed as a specific
  692. * number of co-ordinate positions.
  693. */
  694. private ClientAnchorDetail calculateRowLocation(Sheet sheet,
  695. int startingRow, double reqImageHeightMM) {
  696. ClientAnchorDetail clientAnchorDetail;
  697. Row row;
  698. double rowHeightMM = 0.0D;
  699. double totalRowHeightMM = 0.0D;
  700. double overlapMM;
  701. double rowCoordinatesPerMM;
  702. int toRow = startingRow;
  703. int inset;
  704. // Step through the rows in the sheet and accumulate a total of their
  705. // heights.
  706. while(totalRowHeightMM < reqImageHeightMM) {
  707. row = sheet.getRow(toRow);
  708. // Note, if the row does not already exist on the sheet then create
  709. // it here.
  710. if(row == null) {
  711. row = sheet.createRow(toRow);
  712. }
  713. // Get the row's height in millimetres and add to the running total.
  714. rowHeightMM = row.getHeightInPoints() /
  715. ConvertImageUnits.POINTS_PER_MILLIMETRE;
  716. totalRowHeightMM += rowHeightMM;
  717. toRow++;
  718. }
  719. // Owing to the way the loop above works, the rowNumber will have been
  720. // incremented one row too far. Undo that here.
  721. toRow--;
  722. // Check to see whether the image should occupy an exact number of
  723. // rows. If so, build the ClientAnchorDetail record to point
  724. // to those rows and with an inset of the total number of co-ordinate
  725. // position in the row.
  726. //
  727. // To overcome problems that can occur with comparing double values for
  728. // equality, cast both to int(s) to truncate the value; VERY crude and
  729. // I do not really like it!!
  730. if((int)totalRowHeightMM == (int)reqImageHeightMM) {
  731. if(sheet instanceof HSSFSheet) {
  732. clientAnchorDetail = new ClientAnchorDetail(startingRow, toRow,
  733. ConvertImageUnits.TOTAL_ROW_COORDINATE_POSITIONS);
  734. }
  735. else {
  736. clientAnchorDetail = new ClientAnchorDetail(startingRow, toRow,
  737. (int)reqImageHeightMM * AddDimensionedImage.EMU_PER_MM);
  738. }
  739. }
  740. else {
  741. // Calculate how far the image will project into the next row. Note
  742. // that the height of the last row assessed is subtracted from the
  743. // total height of all rows assessed so far.
  744. overlapMM = reqImageHeightMM - (totalRowHeightMM - rowHeightMM);
  745. // To prevent an exception being thrown when the required width of
  746. // the image is very close indeed to the column size.
  747. if(overlapMM < 0) {
  748. overlapMM = 0.0D;
  749. }
  750. if(sheet instanceof HSSFSheet) {
  751. rowCoordinatesPerMM = (rowHeightMM == 0) ? 0
  752. : ConvertImageUnits.TOTAL_ROW_COORDINATE_POSITIONS / rowHeightMM;
  753. inset = (int)(overlapMM * rowCoordinatesPerMM);
  754. }
  755. else {
  756. inset = (int)overlapMM * AddDimensionedImage.EMU_PER_MM;
  757. }
  758. clientAnchorDetail = new ClientAnchorDetail(startingRow,
  759. toRow, inset);
  760. }
  761. return(clientAnchorDetail);
  762. }
  763. /**
  764. * The main entry point to the program. It contains code that demonstrates
  765. * one way to use the program.
  766. *
  767. * Note, the code is not restricted to use on new workbooks only. If an
  768. * image is to be inserted into an existing workbook. just open that
  769. * workbook, gat a reference to a sheet and pass that;
  770. *
  771. * AddDimensionedImage addImage = new AddDimensionedImage();
  772. *
  773. * File file = new File("....... Existing Workbook .......");
  774. * FileInputStream fis = new FileInputStream(file);
  775. * Workbook workbook = new HSSFWorkbook(fis);
  776. * HSSFSheet sheet = workbook.getSheetAt(0);
  777. * addImage.addImageToSheet("C3", sheet, "image.jpg", 30, 20,
  778. * AddDimensionedImage.EXPAND.ROW);
  779. *
  780. * @param args the command line arguments
  781. */
  782. public static void main(String[] args) throws IOException {
  783. if(args.length < 2){
  784. System.err.println("Usage: AddDimensionedImage imageFile outputFile");
  785. return;
  786. }
  787. final String imageFile = args[0];
  788. final String outputFile = args[1];
  789. try (final Workbook workbook = new HSSFWorkbook();
  790. final FileOutputStream fos = new FileOutputStream(outputFile)) { // OR XSSFWorkbook
  791. Sheet sheet = workbook.createSheet("Picture Test");
  792. new AddDimensionedImage().addImageToSheet("B5", sheet, sheet.createDrawingPatriarch(),
  793. new File(imageFile).toURI().toURL(), 100, 40,
  794. AddDimensionedImage.EXPAND_ROW_AND_COLUMN);
  795. workbook.write(fos);
  796. }
  797. }
  798. /**
  799. * The HSSFClientAnchor class accepts eight arguments. In order, these are;
  800. *
  801. * * How far the left hand edge of the image is inset from the left hand
  802. * edge of the cell
  803. * * How far the top edge of the image is inset from the top of the cell
  804. * * How far the right hand edge of the image is inset from the left
  805. * hand edge of the cell
  806. * * How far the bottom edge of the image is inset from the top of the
  807. * cell.
  808. * * Together, arguments five and six determine the column and row
  809. * coordinates of the cell whose top left hand corner will be aligned
  810. * with the images top left hand corner.
  811. * * Together, arguments seven and eight determine the column and row
  812. * coordinates of the cell whose top left hand corner will be aligned
  813. * with the images bottom right hand corner.
  814. *
  815. * An instance of the ClientAnchorDetail class provides three of the eight
  816. * parameters, one of the coordinates for the images top left hand corner,
  817. * one of the coordinates for the images bottom right hand corner and
  818. * either how far the image should be inset from the top or the left hand
  819. * edge of the cell.
  820. *
  821. * @author Mark Beardsley [msb at apache.org]
  822. * @version 1.00 5th August 2009.
  823. */
  824. public class ClientAnchorDetail {
  825. private int fromIndex;
  826. private int toIndex;
  827. private int inset;
  828. /**
  829. * Create a new instance of the ClientAnchorDetail class using the
  830. * following parameters.
  831. *
  832. * @param fromIndex A primitive int that contains one of the
  833. * coordinates (row or column index) for the top left
  834. * hand corner of the image.
  835. * @param toIndex A primitive int that contains one of the
  836. * coordinates (row or column index) for the bottom
  837. * right hand corner of the image.
  838. * @param inset A primitive int that contains a value which indicates
  839. * how far the image should be inset from the top or the
  840. * left hand edge of a cell.
  841. */
  842. public ClientAnchorDetail(int fromIndex, int toIndex, int inset) {
  843. this.fromIndex = fromIndex;
  844. this.toIndex = toIndex;
  845. this.inset = inset;
  846. }
  847. /**
  848. * Get one of the number of the column or row that contains the cell
  849. * whose top left hand corner will be aligned with the top left hand
  850. * corner of the image.
  851. *
  852. * @return The value - row or column index - for one of the coordinates
  853. * of the top left hand corner of the image.
  854. */
  855. public int getFromIndex() {
  856. return(this.fromIndex);
  857. }
  858. /**
  859. * Get one of the number of the column or row that contains the cell
  860. * whose top left hand corner will be aligned with the bottom right hand
  861. * corner of the image.
  862. *
  863. * @return The value - row or column index - for one of the coordinates
  864. * of the bottom right hand corner of the image.
  865. */
  866. public int getToIndex() {
  867. return(this.toIndex);
  868. }
  869. /**
  870. * Get the images offset from the edge of a cell.
  871. *
  872. * @return How far either the right hand or bottom edge of the image is
  873. * inset from the left hand or top edge of a cell.
  874. */
  875. public int getInset() {
  876. return(this.inset);
  877. }
  878. }
  879. /**
  880. * Utility methods used to convert Excels character based column and row
  881. * size measurements into pixels and/or millimetres. The class also contains
  882. * various constants that are required in other calculations.
  883. *
  884. * @author xio[darjino@hotmail.com]
  885. * @version 1.01 30th July 2009.
  886. * Added by Mark Beardsley [msb at apache.org].
  887. * Additional constants.
  888. * widthUnits2Millimetres() and millimetres2Units() methods.
  889. */
  890. public static class ConvertImageUnits {
  891. // Each cell conatins a fixed number of co-ordinate points; this number
  892. // does not vary with row height or column width or with font. These two
  893. // constants are defined below.
  894. public static final int TOTAL_COLUMN_COORDINATE_POSITIONS = 1023;
  895. public static final int TOTAL_ROW_COORDINATE_POSITIONS = 255;
  896. // The resoultion of an image can be expressed as a specific number
  897. // of pixels per inch. Displays and printers differ but 96 pixels per
  898. // inch is an acceptable standard to beging with.
  899. public static final int PIXELS_PER_INCH = 96;
  900. // Cnstants that defines how many pixels and points there are in a
  901. // millimetre. These values are required for the conversion algorithm.
  902. public static final double PIXELS_PER_MILLIMETRES = 3.78;
  903. public static final double POINTS_PER_MILLIMETRE = 2.83;
  904. // The column width returned by HSSF and the width of a picture when
  905. // positioned to exactly cover one cell are different by almost exactly
  906. // 2mm - give or take rounding errors. This constant allows that
  907. // additional amount to be accounted for when calculating how many
  908. // celles the image ought to overlie.
  909. public static final double CELL_BORDER_WIDTH_MILLIMETRES = 2.0D;
  910. public static final short EXCEL_COLUMN_WIDTH_FACTOR = 256;
  911. public static final int UNIT_OFFSET_LENGTH = 7;
  912. private static final int[] UNIT_OFFSET_MAP = { 0, 36, 73, 109, 146, 182, 219 };
  913. /**
  914. * pixel units to excel width units(units of 1/256th of a character width)
  915. */
  916. public static short pixel2WidthUnits(int pxs) {
  917. short widthUnits = (short) (EXCEL_COLUMN_WIDTH_FACTOR *
  918. (pxs / UNIT_OFFSET_LENGTH));
  919. widthUnits += UNIT_OFFSET_MAP[(pxs % UNIT_OFFSET_LENGTH)];
  920. return widthUnits;
  921. }
  922. /**
  923. * excel width units(units of 1/256th of a character width) to pixel
  924. * units.
  925. */
  926. public static int widthUnits2Pixel(short widthUnits) {
  927. int pixels = (widthUnits / EXCEL_COLUMN_WIDTH_FACTOR)
  928. * UNIT_OFFSET_LENGTH;
  929. int offsetWidthUnits = widthUnits % EXCEL_COLUMN_WIDTH_FACTOR;
  930. pixels += Math.round(offsetWidthUnits /
  931. ((float) EXCEL_COLUMN_WIDTH_FACTOR / UNIT_OFFSET_LENGTH));
  932. return pixels;
  933. }
  934. /**
  935. * Convert Excels width units into millimetres.
  936. *
  937. * @param widthUnits The width of the column or the height of the
  938. * row in Excels units.
  939. * @return A primitive double that contains the columns width or rows
  940. * height in millimetres.
  941. */
  942. public static double widthUnits2Millimetres(short widthUnits) {
  943. return(ConvertImageUnits.widthUnits2Pixel(widthUnits) /
  944. ConvertImageUnits.PIXELS_PER_MILLIMETRES);
  945. }
  946. /**
  947. * Convert into millimetres Excels width units..
  948. *
  949. * @param millimetres A primitive double that contains the columns
  950. * width or rows height in millimetres.
  951. * @return A primitive int that contains the columns width or rows
  952. * height in Excels units.
  953. */
  954. public static int millimetres2WidthUnits(double millimetres) {
  955. return(ConvertImageUnits.pixel2WidthUnits((int)(millimetres *
  956. ConvertImageUnits.PIXELS_PER_MILLIMETRES)));
  957. }
  958. }
  959. }