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.

HSLFPictureData.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  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.hslf.usermodel;
  16. import java.awt.Dimension;
  17. import java.io.IOException;
  18. import java.io.OutputStream;
  19. import java.security.MessageDigest;
  20. import java.util.Arrays;
  21. import java.util.Collections;
  22. import java.util.Comparator;
  23. import java.util.LinkedHashMap;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Objects;
  27. import java.util.function.Supplier;
  28. import org.apache.logging.log4j.LogManager;
  29. import org.apache.logging.log4j.Logger;
  30. import org.apache.poi.common.usermodel.GenericRecord;
  31. import org.apache.poi.ddf.EscherBSERecord;
  32. import org.apache.poi.ddf.EscherContainerRecord;
  33. import org.apache.poi.ddf.EscherRecordTypes;
  34. import org.apache.poi.hslf.blip.DIB;
  35. import org.apache.poi.hslf.blip.EMF;
  36. import org.apache.poi.hslf.blip.JPEG;
  37. import org.apache.poi.hslf.blip.PICT;
  38. import org.apache.poi.hslf.blip.PNG;
  39. import org.apache.poi.hslf.blip.WMF;
  40. import org.apache.poi.poifs.crypt.CryptoFunctions;
  41. import org.apache.poi.poifs.crypt.HashAlgorithm;
  42. import org.apache.poi.sl.usermodel.PictureData;
  43. import org.apache.poi.util.Internal;
  44. import org.apache.poi.util.LittleEndian;
  45. import org.apache.poi.util.LittleEndianConsts;
  46. import org.apache.poi.util.Removal;
  47. import org.apache.poi.util.Units;
  48. /**
  49. * A class that represents image data contained in a slide show.
  50. */
  51. public abstract class HSLFPictureData implements PictureData, GenericRecord {
  52. private static final Logger LOGGER = LogManager.getLogger(HSLFPictureData.class);
  53. /**
  54. * Size of the image checksum calculated using MD5 algorithm.
  55. */
  56. protected static final int CHECKSUM_SIZE = 16;
  57. /**
  58. * Size of the image preamble in bytes.
  59. * <p>
  60. * The preamble describes how the image should be decoded. All image types have the same preamble format. The
  61. * preamble has little endian encoding. Below is a diagram of the preamble contents.
  62. *
  63. * <pre>
  64. * 0 1 2 3
  65. * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
  66. * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  67. * | Signature | Picture Type |
  68. * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  69. * | Formatted Length |
  70. * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  71. * </pre>
  72. */
  73. static final int PREAMBLE_SIZE = 8;
  74. /**
  75. * Binary data of the picture, formatted as it will be stored in the {@link HSLFSlideShow}.
  76. * <p>
  77. * This does not include the {@link #PREAMBLE_SIZE preamble}.
  78. */
  79. private byte[] formattedData;
  80. /**
  81. * The instance type/signatures defines if one or two UID instances will be included
  82. */
  83. private int uidInstanceCount = 1;
  84. /**
  85. * The 1-based index within the pictures stream
  86. */
  87. private int index = -1;
  88. /**
  89. * {@link EscherRecordTypes#BSTORE_CONTAINER BStore} record tracking all pictures. Should be attached to the
  90. * slideshow that this picture is linked to.
  91. */
  92. final EscherContainerRecord bStore;
  93. /**
  94. * Record referencing this picture. Should be attached to the slideshow that this picture is linked to.
  95. */
  96. final EscherBSERecord bse;
  97. /**
  98. * @deprecated Use {@link HSLFSlideShow#addPicture(byte[], PictureType)} or one of it's overloads to create new
  99. * {@link HSLFPictureData}. This API led to detached {@link HSLFPictureData} instances (See Bugzilla
  100. * 46122) and prevented adding additional functionality.
  101. */
  102. @Deprecated
  103. @Removal(version = "5.3")
  104. public HSLFPictureData() {
  105. this(new EscherContainerRecord(), new EscherBSERecord());
  106. LOGGER.atWarn().log("The no-arg constructor is deprecated. Some functionality such as updating pictures won't " +
  107. "work.");
  108. }
  109. /**
  110. * Creates a new instance.
  111. *
  112. * @param bStore {@link EscherRecordTypes#BSTORE_CONTAINER BStore} record tracking all pictures. Should be attached
  113. * to the slideshow that this picture is linked to.
  114. * @param bse Record referencing this picture. Should be attached to the slideshow that this picture is linked to.
  115. */
  116. @Internal
  117. protected HSLFPictureData(EscherContainerRecord bStore, EscherBSERecord bse) {
  118. this.bStore = Objects.requireNonNull(bStore);
  119. this.bse = Objects.requireNonNull(bse);
  120. }
  121. /**
  122. * Blip signature.
  123. */
  124. protected abstract int getSignature();
  125. public abstract void setSignature(int signature);
  126. /**
  127. * The instance type/signatures defines if one or two UID instances will be included
  128. */
  129. protected int getUIDInstanceCount() {
  130. return uidInstanceCount;
  131. }
  132. /**
  133. * The instance type/signatures defines if one or two UID instances will be included
  134. *
  135. * @param uidInstanceCount the number of uid sequences
  136. */
  137. protected void setUIDInstanceCount(int uidInstanceCount) {
  138. this.uidInstanceCount = uidInstanceCount;
  139. }
  140. /**
  141. * Returns the formatted, binary data of this picture excluding the {@link #PREAMBLE_SIZE preamble} bytes.
  142. * <p>
  143. * Primarily intended for internal POI use. Use {@link #getData()} to retrieve the picture represented by this
  144. * object.
  145. *
  146. * @return Picture data formatted for the HSLF format.
  147. * @see #getData()
  148. * @see #formatImageForSlideshow(byte[])
  149. */
  150. public byte[] getRawData(){
  151. return formattedData;
  152. }
  153. /**
  154. * Sets the formatted data for this picture.
  155. * <p>
  156. * Primarily intended for internal POI use. Use {@link #setData(byte[])} to change the picture represented by this
  157. * object.
  158. *
  159. * @param data Picture data formatted for the HSLF format. Excludes the {@link #PREAMBLE_SIZE preamble}.
  160. * @see #setData(byte[])
  161. * @see #formatImageForSlideshow(byte[])
  162. * @deprecated Set image data using {@link #setData(byte[])}.
  163. */
  164. @Deprecated
  165. @Removal(version = "5.3")
  166. public void setRawData(byte[] data){
  167. formattedData = (data == null) ? null : data.clone();
  168. }
  169. /**
  170. * File offset in the 'Pictures' stream
  171. *
  172. * @return offset in the 'Pictures' stream
  173. */
  174. public int getOffset(){
  175. return bse.getOffset();
  176. }
  177. /**
  178. * Set offset of this picture in the 'Pictures' stream.
  179. * We need to set it when a new picture is created.
  180. *
  181. * @param offset in the 'Pictures' stream
  182. * @deprecated This function was only intended for POI internal use. If you have a use case you're concerned about,
  183. * please open an issue in the POI issue tracker.
  184. */
  185. @Deprecated
  186. @Removal(version = "5.3")
  187. public void setOffset(int offset){
  188. LOGGER.atWarn().log("HSLFPictureData#setOffset is deprecated.");
  189. }
  190. /**
  191. * Returns 16-byte checksum of this picture
  192. */
  193. public byte[] getUID(){
  194. return Arrays.copyOf(formattedData, CHECKSUM_SIZE);
  195. }
  196. @Override
  197. public byte[] getChecksum() {
  198. return getUID();
  199. }
  200. /**
  201. * Compute 16-byte checksum of this picture using MD5 algorithm.
  202. */
  203. public static byte[] getChecksum(byte[] data) {
  204. MessageDigest md5 = CryptoFunctions.getMessageDigest(HashAlgorithm.md5);
  205. md5.update(data);
  206. return md5.digest();
  207. }
  208. /**
  209. * Write this picture into <code>OutputStream</code>
  210. */
  211. public void write(OutputStream out) throws IOException {
  212. byte[] data;
  213. data = new byte[LittleEndianConsts.SHORT_SIZE];
  214. LittleEndian.putUShort(data, 0, getSignature());
  215. out.write(data);
  216. data = new byte[LittleEndianConsts.SHORT_SIZE];
  217. PictureType pt = getType();
  218. LittleEndian.putUShort(data, 0, pt.nativeId + EscherRecordTypes.BLIP_START.typeID);
  219. out.write(data);
  220. byte[] rd = getRawData();
  221. data = new byte[LittleEndianConsts.INT_SIZE];
  222. LittleEndian.putInt(data, 0, rd.length);
  223. out.write(data);
  224. out.write(rd);
  225. }
  226. /**
  227. * Create an instance of {@link HSLFPictureData} by type.
  228. *
  229. * @param type type of picture.
  230. * @return concrete instance of {@link HSLFPictureData}.
  231. * @deprecated Use {@link HSLFSlideShow#addPicture(byte[], PictureType)} or one of it's overloads to create new
  232. * {@link HSLFPictureData}. This API led to detached {@link HSLFPictureData} instances (See Bugzilla
  233. * 46122) and prevented adding additional functionality.
  234. */
  235. @Deprecated
  236. @Removal(version = "5.3")
  237. public static HSLFPictureData create(PictureType type){
  238. LOGGER.atWarn().log("HSLFPictureData#create(PictureType) is deprecated. Some functionality such " +
  239. "as updating pictures won't work.");
  240. // This record code is a stub. It exists only for API compatibility.
  241. EscherContainerRecord record = new EscherContainerRecord();
  242. EscherBSERecord bse = new EscherBSERecord();
  243. return new HSLFSlideShowImpl.PictureFactory(record, type, new byte[0], 0, 0)
  244. .setRecord(bse)
  245. .build();
  246. }
  247. /**
  248. * Creates a new instance of the given image type using data already formatted for storage inside the slideshow.
  249. * <p>
  250. * This function is most handy when parsing an existing slideshow, as the picture data are already formatted.
  251. * @param type Image type.
  252. * @param recordContainer Record tracking all pictures. Should be attached to the slideshow that this picture is
  253. * linked to.
  254. * @param bse Record referencing this picture. Should be attached to the slideshow that this picture is linked to.
  255. * @param data Image data formatted for storage in the slideshow. This does not include the
  256. * {@link #PREAMBLE_SIZE preamble}.
  257. * @param signature Image format-specific signature. See subclasses for signature details.
  258. * @return New instance.
  259. *
  260. * @see #createFromImageData(PictureType, EscherContainerRecord, EscherBSERecord, byte[])
  261. */
  262. static HSLFPictureData createFromSlideshowData(
  263. PictureType type,
  264. EscherContainerRecord recordContainer,
  265. EscherBSERecord bse,
  266. byte[] data,
  267. int signature
  268. ) {
  269. HSLFPictureData instance = newInstance(type, recordContainer, bse);
  270. instance.setSignature(signature);
  271. instance.formattedData = data;
  272. return instance;
  273. }
  274. /**
  275. * Creates a new instance of the given image type using data already formatted for storage inside the slideshow.
  276. * <p>
  277. * This function is most handy when adding new pictures to a slideshow, as the image data provided by users is not
  278. * yet formatted.
  279. *
  280. * @param type Image type.
  281. * @param recordContainer Record tracking all pictures. Should be attached to the slideshow that this picture is
  282. * linked to.
  283. * @param bse Record referencing this picture. Should be attached to the slideshow that this picture is linked to.
  284. * @param data Original image data. If these bytes were written to a disk, a common image viewer would be able to
  285. * render the image.
  286. * @return New instance.
  287. *
  288. * @see #createFromSlideshowData(PictureType, EscherContainerRecord, EscherBSERecord, byte[], int)
  289. * @see #setData(byte[])
  290. */
  291. static HSLFPictureData createFromImageData(
  292. PictureType type,
  293. EscherContainerRecord recordContainer,
  294. EscherBSERecord bse,
  295. byte[] data
  296. ) {
  297. HSLFPictureData instance = newInstance(type, recordContainer, bse);
  298. instance.formattedData = instance.formatImageForSlideshow(data);
  299. return instance;
  300. }
  301. private static HSLFPictureData newInstance(
  302. PictureType type,
  303. EscherContainerRecord recordContainer,
  304. EscherBSERecord bse
  305. ) {
  306. switch (type) {
  307. case EMF:
  308. return new EMF(recordContainer, bse);
  309. case WMF:
  310. return new WMF(recordContainer, bse);
  311. case PICT:
  312. return new PICT(recordContainer, bse);
  313. case JPEG:
  314. return new JPEG(recordContainer, bse);
  315. case PNG:
  316. return new PNG(recordContainer, bse);
  317. case DIB:
  318. return new DIB(recordContainer, bse);
  319. default:
  320. throw new IllegalArgumentException("Unsupported picture type: " + type);
  321. }
  322. }
  323. /**
  324. * Return 24 byte header which preceeds the actual picture data.
  325. * <p>
  326. * The header consists of 2-byte signature, 2-byte type,
  327. * 4-byte image size and 16-byte checksum of the image data.
  328. * </p>
  329. *
  330. * @return the 24 byte header which preceeds the actual picture data.
  331. */
  332. public byte[] getHeader() {
  333. byte[] header = new byte[CHECKSUM_SIZE + PREAMBLE_SIZE];
  334. LittleEndian.putInt(header, 0, getSignature());
  335. LittleEndian.putInt(header, 4, getRawData().length);
  336. System.arraycopy(formattedData, 0, header, PREAMBLE_SIZE, CHECKSUM_SIZE);
  337. return header;
  338. }
  339. /**
  340. * Returns the 1-based index of this picture.
  341. * @return the 1-based index of this pictures within the pictures stream
  342. */
  343. public int getIndex() {
  344. return index;
  345. }
  346. /**
  347. * @param index sets the 1-based index of this pictures within the pictures stream
  348. */
  349. public void setIndex(int index) {
  350. this.index = index;
  351. }
  352. /**
  353. * Formats the picture data for storage in the slideshow.
  354. * <p>
  355. * Images stored in {@link HSLFSlideShow}s are represented differently than when they are standalone files. The
  356. * exact formatting differs for each image type.
  357. *
  358. * @param data Original image data. If these bytes were written to a disk, a common image viewer would be able to
  359. * render the image.
  360. * @return Formatted image representation.
  361. */
  362. protected abstract byte[] formatImageForSlideshow(byte[] data);
  363. /**
  364. * @return Size of this picture when stored in the image stream inside the {@link HSLFSlideShow}.
  365. */
  366. int getBseSize() {
  367. return formattedData.length + PREAMBLE_SIZE;
  368. }
  369. @Override
  370. public final void setData(byte[] data) throws IOException {
  371. /*
  372. * When working with slideshow pictures, we need to be aware of 2 container units. The first is a list of
  373. * HSLFPictureData that are the programmatic reference for working with the pictures. The second is the
  374. * Blip Store. For the purposes of this function, you can think of the Blip Store as containing a list of
  375. * pointers (with a small summary) to the picture in the slideshow.
  376. *
  377. * When updating a picture, we need to update the in-memory data structure (this instance), but we also need to
  378. * update the stored pointer. When modifying the pointer, we also need to modify all subsequent pointers, since
  379. * they might shift based on a change in the byte count of the underlying image.
  380. */
  381. int oldSize = getBseSize();
  382. formattedData = formatImageForSlideshow(data);
  383. int newSize = getBseSize();
  384. int changeInSize = newSize - oldSize;
  385. byte[] newUid = getUID();
  386. boolean foundBseForOldImage = false;
  387. // Get the BSE records & sort the list by offset, so we can proceed to shift offsets
  388. @SuppressWarnings("unchecked") // The BStore only contains BSE records
  389. List<EscherBSERecord> bseRecords = (List<EscherBSERecord>) (Object) bStore.getChildRecords();
  390. bseRecords.sort(Comparator.comparingInt(EscherBSERecord::getOffset));
  391. for (EscherBSERecord bse : bseRecords) {
  392. if (foundBseForOldImage) {
  393. // The BSE for this picture was modified in a previous iteration, and we are now adjusting
  394. // subsequent offsets.
  395. bse.setOffset(bse.getOffset() + changeInSize);
  396. } else if (bse == this.bse) { // Reference equals is safe because these BSE belong to the same slideshow
  397. // This BSE matches the current image. Update the size and UID.
  398. foundBseForOldImage = true;
  399. bse.setUid(newUid);
  400. // Image byte count may have changed, so update the pointer.
  401. bse.setSize(newSize);
  402. }
  403. }
  404. }
  405. @Override
  406. public final String getContentType() {
  407. return getType().contentType;
  408. }
  409. @Override
  410. public Dimension getImageDimensionInPixels() {
  411. Dimension dim = getImageDimension();
  412. return new Dimension(
  413. Units.pointsToPixel(dim.getWidth()),
  414. Units.pointsToPixel(dim.getHeight())
  415. );
  416. }
  417. @Override
  418. public Map<String, Supplier<?>> getGenericProperties() {
  419. final Map<String,Supplier<?>> m = new LinkedHashMap<>();
  420. m.put("type", this::getType);
  421. m.put("imageDimension", this::getImageDimension);
  422. m.put("signature", this::getSignature);
  423. m.put("uidInstanceCount", this::getUIDInstanceCount);
  424. m.put("offset", this::getOffset);
  425. m.put("uid", this::getUID);
  426. m.put("checksum", this::getChecksum);
  427. m.put("index", this::getIndex);
  428. m.put("rawData", this::getRawData);
  429. return Collections.unmodifiableMap(m);
  430. }
  431. }