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.

HwmfFill.java 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991
  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.hwmf.record;
  16. import static org.apache.poi.hwmf.record.HwmfDraw.readPointS;
  17. import java.awt.Color;
  18. import java.awt.Shape;
  19. import java.awt.geom.Path2D;
  20. import java.awt.geom.Point2D;
  21. import java.awt.geom.Rectangle2D;
  22. import java.awt.image.BufferedImage;
  23. import java.io.IOException;
  24. import java.util.Map;
  25. import java.util.function.Supplier;
  26. import org.apache.poi.hwmf.draw.HwmfDrawProperties;
  27. import org.apache.poi.hwmf.draw.HwmfGraphics;
  28. import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
  29. import org.apache.poi.sl.draw.ImageRenderer;
  30. import org.apache.poi.util.GenericRecordJsonWriter;
  31. import org.apache.poi.util.GenericRecordUtil;
  32. import org.apache.poi.util.LittleEndianConsts;
  33. import org.apache.poi.util.LittleEndianInputStream;
  34. @SuppressWarnings("WeakerAccess")
  35. public class HwmfFill {
  36. /** A record which contains an image (to be extracted) */
  37. public interface HwmfImageRecord {
  38. default BufferedImage getImage() {
  39. return getImage(Color.BLACK, new Color(0x00FFFFFF, true), true);
  40. }
  41. /**
  42. * Provide an image using the fore-/background color, in case of a 1-bit pattern
  43. * @param foreground the foreground color
  44. * @param background the background color
  45. * @param hasAlpha if true, the background color is rendered transparent - see {@link HwmfMisc.WmfSetBkMode.HwmfBkMode}
  46. * @return the image
  47. *
  48. * @since POI 4.1.1
  49. */
  50. BufferedImage getImage(Color foreground, Color background, boolean hasAlpha);
  51. /**
  52. * @return the raw BMP data
  53. *
  54. * @see <a href="https://en.wikipedia.org/wiki/BMP_file_format">BMP format</a>
  55. * @since POI 4.1.1
  56. */
  57. byte[] getBMPData();
  58. }
  59. /**
  60. * The ColorUsage Enumeration (a 16-bit unsigned integer) specifies whether a color table
  61. * exists in a device-independent bitmap (DIB) and how to interpret its values,
  62. * i.e. if contains explicit RGB values or indexes into a palette.
  63. */
  64. public enum ColorUsage {
  65. /** The color table contains RGB values */
  66. DIB_RGB_COLORS(0x0000),
  67. /**
  68. * The color table contains 16-bit indices into the current logical palette in
  69. * the playback device context.
  70. */
  71. DIB_PAL_COLORS(0x0001),
  72. /**
  73. * No color table exists. The pixels in the DIB are indices into the current
  74. * logical palette in the playback device context.
  75. */
  76. DIB_PAL_INDICES(0x0002)
  77. ;
  78. public final int flag;
  79. ColorUsage(int flag) {
  80. this.flag = flag;
  81. }
  82. public static ColorUsage valueOf(int flag) {
  83. for (ColorUsage bs : values()) {
  84. if (bs.flag == flag) return bs;
  85. }
  86. return null;
  87. }
  88. }
  89. /**
  90. * The META_FILLREGION record fills a region using a specified brush.
  91. */
  92. public static class WmfFillRegion implements HwmfRecord {
  93. /**
  94. * A 16-bit unsigned integer used to index into the WMF Object Table to get
  95. * the region to be filled.
  96. */
  97. protected int regionIndex;
  98. /**
  99. * A 16-bit unsigned integer used to index into the WMF Object Table to get the
  100. * brush to use for filling the region.
  101. */
  102. protected int brushIndex;
  103. @Override
  104. public HwmfRecordType getWmfRecordType() {
  105. return HwmfRecordType.fillRegion;
  106. }
  107. @Override
  108. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  109. regionIndex = leis.readUShort();
  110. brushIndex = leis.readUShort();
  111. return 2*LittleEndianConsts.SHORT_SIZE;
  112. }
  113. @Override
  114. public void draw(HwmfGraphics ctx) {
  115. ctx.applyObjectTableEntry(regionIndex);
  116. ctx.applyObjectTableEntry(brushIndex);
  117. Shape region = ctx.getProperties().getRegion();
  118. if (region != null) {
  119. ctx.fill(region);
  120. }
  121. }
  122. public int getRegionIndex() {
  123. return regionIndex;
  124. }
  125. public int getBrushIndex() {
  126. return brushIndex;
  127. }
  128. @Override
  129. public Map<String, Supplier<?>> getGenericProperties() {
  130. return GenericRecordUtil.getGenericProperties(
  131. "regionIndex", this::getRegionIndex,
  132. "brushIndex", this::getBrushIndex
  133. );
  134. }
  135. }
  136. /**
  137. * The META_PAINTREGION record paints the specified region by using the brush that is
  138. * defined in the playback device context.
  139. */
  140. public static class WmfPaintRegion implements HwmfRecord {
  141. /**
  142. * A 16-bit unsigned integer used to index into the WMF Object Table to get
  143. * the region to be painted.
  144. */
  145. int regionIndex;
  146. @Override
  147. public HwmfRecordType getWmfRecordType() {
  148. return HwmfRecordType.paintRegion;
  149. }
  150. @Override
  151. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  152. regionIndex = leis.readUShort();
  153. return LittleEndianConsts.SHORT_SIZE;
  154. }
  155. @Override
  156. public void draw(HwmfGraphics ctx) {
  157. ctx.applyObjectTableEntry(regionIndex);
  158. Shape region = ctx.getProperties().getRegion();
  159. if (region != null) {
  160. ctx.fill(region);
  161. }
  162. }
  163. public int getRegionIndex() {
  164. return regionIndex;
  165. }
  166. @Override
  167. public Map<String, Supplier<?>> getGenericProperties() {
  168. return GenericRecordUtil.getGenericProperties("regionIndex", this::getRegionIndex);
  169. }
  170. }
  171. /**
  172. * The META_FLOODFILL record fills an area of the output surface with the brush that
  173. * is defined in the playback device context.
  174. */
  175. public static class WmfFloodFill implements HwmfRecord {
  176. /** A 32-bit ColorRef Object that defines the color value. */
  177. protected final HwmfColorRef colorRef = new HwmfColorRef();
  178. /** the point where filling is to start. */
  179. protected final Point2D start = new Point2D.Double();
  180. @Override
  181. public HwmfRecordType getWmfRecordType() {
  182. return HwmfRecordType.floodFill;
  183. }
  184. @Override
  185. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  186. int size = colorRef.init(leis);
  187. size += readPointS(leis, start);
  188. return size;
  189. }
  190. @Override
  191. public void draw(HwmfGraphics ctx) {
  192. }
  193. public HwmfColorRef getColorRef() {
  194. return colorRef;
  195. }
  196. public Point2D getStart() {
  197. return start;
  198. }
  199. @Override
  200. public Map<String, Supplier<?>> getGenericProperties() {
  201. return GenericRecordUtil.getGenericProperties(
  202. "colorRef", this::getColorRef,
  203. "start", this::getStart
  204. );
  205. }
  206. }
  207. /**
  208. * The META_SETPOLYFILLMODE record sets polygon fill mode in the playback device context for
  209. * graphics operations that fill polygons.
  210. */
  211. public static class WmfSetPolyfillMode implements HwmfRecord {
  212. /**
  213. * A 16-bit unsigned integer that defines polygon fill mode.
  214. * This MUST be one of the values: ALTERNATE = 0x0001, WINDING = 0x0002
  215. */
  216. public enum HwmfPolyfillMode {
  217. /**
  218. * Selects alternate mode (fills the area between odd-numbered and
  219. * even-numbered polygon sides on each scan line).
  220. */
  221. ALTERNATE(0x0001, Path2D.WIND_EVEN_ODD),
  222. /**
  223. * Selects winding mode (fills any region with a nonzero winding value).
  224. */
  225. WINDING(0x0002, Path2D.WIND_NON_ZERO);
  226. public final int wmfFlag;
  227. public final int awtFlag;
  228. HwmfPolyfillMode(int wmfFlag, int awtFlag) {
  229. this.wmfFlag = wmfFlag;
  230. this.awtFlag = awtFlag;
  231. }
  232. public static HwmfPolyfillMode valueOf(int wmfFlag) {
  233. for (HwmfPolyfillMode pm : values()) {
  234. if (pm.wmfFlag == wmfFlag) return pm;
  235. }
  236. return null;
  237. }
  238. }
  239. /**
  240. * An unsigned integer that defines polygon fill mode.
  241. * This MUST be one of the values: ALTERNATE = 0x0001, WINDING = 0x0002
  242. */
  243. protected HwmfPolyfillMode polyFillMode;
  244. @Override
  245. public HwmfRecordType getWmfRecordType() {
  246. return HwmfRecordType.setPolyFillMode;
  247. }
  248. @Override
  249. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  250. polyFillMode = HwmfPolyfillMode.valueOf(leis.readUShort() & 3);
  251. return LittleEndianConsts.SHORT_SIZE;
  252. }
  253. @Override
  254. public void draw(HwmfGraphics ctx) {
  255. ctx.getProperties().setPolyfillMode(polyFillMode);
  256. }
  257. @Override
  258. public String toString() {
  259. return GenericRecordJsonWriter.marshal(this);
  260. }
  261. public HwmfPolyfillMode getPolyFillMode() {
  262. return polyFillMode;
  263. }
  264. @Override
  265. public Map<String, Supplier<?>> getGenericProperties() {
  266. return GenericRecordUtil.getGenericProperties("polyFillMode", this::getPolyFillMode);
  267. }
  268. }
  269. /**
  270. * The META_EXTFLOODFILL record fills an area with the brush that is defined in
  271. * the playback device context.
  272. */
  273. public static class WmfExtFloodFill extends WmfFloodFill {
  274. public enum HwmfFloodFillMode {
  275. /**
  276. * The fill area is bounded by the color specified by the Color member.
  277. * This style is identical to the filling performed by the META_FLOODFILL record.
  278. */
  279. FLOOD_FILL_BORDER,
  280. /**
  281. * The fill area is bounded by the color that is specified by the Color member.
  282. * Filling continues outward in all directions as long as the color is encountered.
  283. * This style is useful for filling areas with multicolored boundaries.
  284. */
  285. FLOOD_FILL_SURFACE
  286. }
  287. protected HwmfFloodFillMode mode;
  288. @Override
  289. public HwmfRecordType getWmfRecordType() {
  290. return HwmfRecordType.extFloodFill;
  291. }
  292. @Override
  293. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  294. // A 16-bit unsigned integer that defines the fill operation to be performed. This
  295. // member MUST be one of the values in the FloodFill Enumeration table:
  296. mode = HwmfFloodFillMode.values()[leis.readUShort()];
  297. return super.init(leis, recordSize, recordFunction)+LittleEndianConsts.SHORT_SIZE;
  298. }
  299. @Override
  300. public void draw(HwmfGraphics ctx) {
  301. }
  302. public HwmfFloodFillMode getMode() {
  303. return mode;
  304. }
  305. @Override
  306. public Map<String, Supplier<?>> getGenericProperties() {
  307. return GenericRecordUtil.getGenericProperties("mode", this::getMode);
  308. }
  309. }
  310. /**
  311. * The META_INVERTREGION record draws a region in which the colors are inverted.
  312. */
  313. public static class WmfInvertRegion implements HwmfRecord {
  314. /**
  315. * A 16-bit unsigned integer used to index into the WMF Object Table to get
  316. * the region to be inverted.
  317. */
  318. private int regionIndex;
  319. @Override
  320. public HwmfRecordType getWmfRecordType() {
  321. return HwmfRecordType.invertRegion;
  322. }
  323. @Override
  324. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  325. regionIndex = leis.readUShort();
  326. return LittleEndianConsts.SHORT_SIZE;
  327. }
  328. @Override
  329. public void draw(HwmfGraphics ctx) {
  330. }
  331. public int getRegionIndex() {
  332. return regionIndex;
  333. }
  334. @Override
  335. public Map<String, Supplier<?>> getGenericProperties() {
  336. return GenericRecordUtil.getGenericProperties("regionIndex", this::getRegionIndex);
  337. }
  338. }
  339. /**
  340. * The META_PATBLT record paints a specified rectangle using the brush that is defined in the playback
  341. * device context. The brush color and the surface color or colors are combined using the specified
  342. * raster operation.
  343. */
  344. public static class WmfPatBlt implements HwmfRecord {
  345. /**
  346. * A 32-bit unsigned integer that defines the raster operation code.
  347. * This code MUST be one of the values in the Ternary Raster Operation enumeration table.
  348. */
  349. private HwmfTernaryRasterOp rasterOperation;
  350. private final Rectangle2D bounds = new Rectangle2D.Double();
  351. @Override
  352. public HwmfRecordType getWmfRecordType() {
  353. return HwmfRecordType.patBlt;
  354. }
  355. @Override
  356. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  357. rasterOperation = readRasterOperation(leis);
  358. return readBounds2(leis, bounds)+2*LittleEndianConsts.SHORT_SIZE;
  359. }
  360. @Override
  361. public void draw(HwmfGraphics ctx) {
  362. }
  363. public HwmfTernaryRasterOp getRasterOperation() {
  364. return rasterOperation;
  365. }
  366. public Rectangle2D getBounds() {
  367. return bounds;
  368. }
  369. @Override
  370. public Map<String, Supplier<?>> getGenericProperties() {
  371. return GenericRecordUtil.getGenericProperties(
  372. "rasterOperation", this::getRasterOperation,
  373. "bounds", this::getBounds);
  374. }
  375. }
  376. /**
  377. * The META_STRETCHBLT record specifies the transfer of a block of pixels according to a raster
  378. * operation, with possible expansion or contraction.
  379. * The destination of the transfer is the current output region in the playback device context.
  380. * There are two forms of META_STRETCHBLT, one which specifies a bitmap as the source, and the other
  381. * which uses the playback device context as the source. Definitions follow for the fields that are the
  382. * same in the two forms of META_STRETCHBLT are defined below. The subsections that follow specify
  383. * the packet structures of the two forms of META_STRETCHBLT.
  384. * The expansion or contraction is performed according to the stretching mode currently set in the
  385. * playback device context, which MUST be a value from the StretchMode.
  386. */
  387. public static class WmfStretchBlt implements HwmfRecord {
  388. /**
  389. * A 32-bit unsigned integer that defines how the source pixels, the current brush
  390. * in the playback device context, and the destination pixels are to be combined to form the new
  391. * image. This code MUST be one of the values in the Ternary Raster Operation Enumeration
  392. */
  393. protected HwmfTernaryRasterOp rasterOperation;
  394. /** the source rectangle */
  395. protected final Rectangle2D srcBounds = new Rectangle2D.Double();
  396. /** the destination rectangle */
  397. protected final Rectangle2D dstBounds = new Rectangle2D.Double();
  398. /**
  399. * A variable-sized Bitmap16 Object that defines source image content.
  400. * This object MUST be specified, even if the raster operation does not require a source.
  401. */
  402. protected HwmfBitmap16 target;
  403. @Override
  404. public HwmfRecordType getWmfRecordType() {
  405. return HwmfRecordType.stretchBlt;
  406. }
  407. @Override
  408. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  409. final boolean hasBitmap = hasBitmap(recordSize, recordFunction);
  410. rasterOperation = readRasterOperation(leis);
  411. int size = 2*LittleEndianConsts.SHORT_SIZE;
  412. size += readBounds2(leis, srcBounds);
  413. if (!hasBitmap) {
  414. /*int reserved =*/ leis.readShort();
  415. size += LittleEndianConsts.SHORT_SIZE;
  416. }
  417. size += readBounds2(leis, dstBounds);
  418. if (hasBitmap) {
  419. target = new HwmfBitmap16();
  420. size += target.init(leis);
  421. }
  422. return size;
  423. }
  424. @Override
  425. public void draw(HwmfGraphics ctx) {
  426. }
  427. @Override
  428. public String toString() {
  429. return GenericRecordJsonWriter.marshal(this);
  430. }
  431. public HwmfTernaryRasterOp getRasterOperation() {
  432. return rasterOperation;
  433. }
  434. public Rectangle2D getSrcBounds() {
  435. return srcBounds;
  436. }
  437. public Rectangle2D getDstBounds() {
  438. return dstBounds;
  439. }
  440. public HwmfBitmap16 getTarget() {
  441. return target;
  442. }
  443. @Override
  444. public Map<String, Supplier<?>> getGenericProperties() {
  445. return GenericRecordUtil.getGenericProperties(
  446. "rasterOperation", this::getRasterOperation,
  447. "srcBounds", this::getSrcBounds,
  448. "dstBounds", this::getDstBounds,
  449. "target", this::getTarget
  450. );
  451. }
  452. }
  453. /**
  454. * The META_STRETCHDIB record specifies the transfer of color data from a
  455. * block of pixels in device independent format according to a raster operation,
  456. * with possible expansion or contraction.
  457. * The source of the color data is a DIB, and the destination of the transfer is
  458. * the current output region in the playback device context.
  459. */
  460. public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord {
  461. /**
  462. * A 32-bit unsigned integer that defines how the source pixels, the current brush in
  463. * the playback device context, and the destination pixels are to be combined to
  464. * form the new image.
  465. */
  466. protected HwmfTernaryRasterOp rasterOperation;
  467. /**
  468. * A 16-bit unsigned integer that defines whether the Colors field of the
  469. * DIB contains explicit RGB values or indexes into a palette.
  470. */
  471. protected ColorUsage colorUsage;
  472. /** the source rectangle. */
  473. protected final Rectangle2D srcBounds = new Rectangle2D.Double();
  474. /** the destination rectangle. */
  475. protected final Rectangle2D dstBounds = new Rectangle2D.Double();
  476. /**
  477. * A variable-sized DeviceIndependentBitmap Object (section 2.2.2.9) that is the
  478. * source of the color data.
  479. */
  480. protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
  481. @Override
  482. public HwmfRecordType getWmfRecordType() {
  483. return HwmfRecordType.stretchDib;
  484. }
  485. @Override
  486. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  487. rasterOperation = readRasterOperation(leis);
  488. colorUsage = ColorUsage.valueOf(leis.readUShort());
  489. int size = 3*LittleEndianConsts.SHORT_SIZE;
  490. size += readBounds2(leis, srcBounds);
  491. size += readBounds2(leis, dstBounds);
  492. size += bitmap.init(leis, (int)(recordSize-6-size));
  493. return size;
  494. }
  495. @Override
  496. public void draw(HwmfGraphics ctx) {
  497. HwmfDrawProperties prop = ctx.getProperties();
  498. prop.setRasterOp3(rasterOperation);
  499. if (bitmap.isValid()) {
  500. BufferedImage bi = bitmap.getImage(prop.getPenColor().getColor(), prop.getBackgroundColor().getColor(),
  501. prop.getBkMode() == HwmfBkMode.TRANSPARENT);
  502. ctx.drawImage(bi, srcBounds, dstBounds);
  503. } else if (!dstBounds.isEmpty()) {
  504. ctx.drawImage((ImageRenderer)null, new Rectangle2D.Double(0,0,1,1), dstBounds);
  505. }
  506. }
  507. @Override
  508. public BufferedImage getImage(Color foreground, Color background, boolean hasAlpha) {
  509. return bitmap.getImage(foreground,background,hasAlpha);
  510. }
  511. public HwmfBitmapDib getBitmap() {
  512. return bitmap;
  513. }
  514. @Override
  515. public byte[] getBMPData() {
  516. return bitmap.getBMPData();
  517. }
  518. @Override
  519. public String toString() {
  520. return GenericRecordJsonWriter.marshal(this);
  521. }
  522. public HwmfTernaryRasterOp getRasterOperation() {
  523. return rasterOperation;
  524. }
  525. public ColorUsage getColorUsage() {
  526. return colorUsage;
  527. }
  528. public Rectangle2D getSrcBounds() {
  529. return srcBounds;
  530. }
  531. public Rectangle2D getDstBounds() {
  532. return dstBounds;
  533. }
  534. @Override
  535. public Map<String, Supplier<?>> getGenericProperties() {
  536. return GenericRecordUtil.getGenericProperties(
  537. "rasterOperation", this::getRasterOperation,
  538. "colorUsage", this::getColorUsage,
  539. "srcBounds", this::getSrcBounds,
  540. "dstBounds", this::getDstBounds
  541. );
  542. }
  543. }
  544. public static class WmfBitBlt extends WmfStretchBlt {
  545. @Override
  546. public HwmfRecordType getWmfRecordType() {
  547. return HwmfRecordType.bitBlt;
  548. }
  549. @Override
  550. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  551. final boolean hasBitmap = hasBitmap(recordSize/2, recordFunction);
  552. rasterOperation = readRasterOperation(leis);
  553. int size = 2*LittleEndianConsts.SHORT_SIZE;
  554. final Point2D srcPnt = new Point2D.Double();
  555. size += readPointS(leis, srcPnt);
  556. if (!hasBitmap) {
  557. /*int reserved =*/ leis.readShort();
  558. size += LittleEndianConsts.SHORT_SIZE;
  559. }
  560. size += readBounds2(leis, dstBounds);
  561. if (hasBitmap) {
  562. target = new HwmfBitmap16();
  563. size += target.init(leis);
  564. }
  565. srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
  566. return size;
  567. }
  568. }
  569. /**
  570. * The META_SETDIBTODEV record sets a block of pixels in the playback device context
  571. * using deviceindependent color data.
  572. * The source of the color data is a DIB
  573. */
  574. public static class WmfSetDibToDev implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {
  575. /**
  576. * A 16-bit unsigned integer that defines whether the Colors field of the
  577. * DIB contains explicit RGB values or indexes into a palette.
  578. */
  579. private ColorUsage colorUsage;
  580. /**
  581. * A 16-bit unsigned integer that defines the number of scan lines in the source.
  582. */
  583. private int scanCount;
  584. /**
  585. * A 16-bit unsigned integer that defines the starting scan line in the source.
  586. */
  587. private int startScan;
  588. /** the source rectangle */
  589. protected final Rectangle2D srcBounds = new Rectangle2D.Double();
  590. /** the destination rectangle, having the same dimension as the source rectangle */
  591. protected final Rectangle2D dstBounds = new Rectangle2D.Double();
  592. /**
  593. * A variable-sized DeviceIndependentBitmap Object that is the source of the color data.
  594. */
  595. private HwmfBitmapDib dib;
  596. @Override
  597. public HwmfRecordType getWmfRecordType() {
  598. return HwmfRecordType.setDibToDev;
  599. }
  600. @Override
  601. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  602. colorUsage = ColorUsage.valueOf(leis.readUShort());
  603. scanCount = leis.readUShort();
  604. startScan = leis.readUShort();
  605. int size = 3*LittleEndianConsts.SHORT_SIZE;
  606. final Point2D srcPnt = new Point2D.Double();
  607. size += readPointS(leis, srcPnt);
  608. size += readBounds2(leis, dstBounds);
  609. dib = new HwmfBitmapDib();
  610. size += dib.init(leis, (int)(recordSize-6-size));
  611. srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
  612. return size;
  613. }
  614. @Override
  615. public void draw(HwmfGraphics ctx) {
  616. ctx.addObjectTableEntry(this);
  617. }
  618. @Override
  619. public void applyObject(HwmfGraphics ctx) {
  620. }
  621. @Override
  622. public BufferedImage getImage(Color foreground, Color background, boolean hasAlpha) {
  623. return dib.getImage(foreground,background,hasAlpha);
  624. }
  625. @Override
  626. public byte[] getBMPData() {
  627. return dib.getBMPData();
  628. }
  629. public ColorUsage getColorUsage() {
  630. return colorUsage;
  631. }
  632. public int getScanCount() {
  633. return scanCount;
  634. }
  635. public int getStartScan() {
  636. return startScan;
  637. }
  638. public Rectangle2D getSrcBounds() {
  639. return srcBounds;
  640. }
  641. public Rectangle2D getDstBounds() {
  642. return dstBounds;
  643. }
  644. @Override
  645. public Map<String, Supplier<?>> getGenericProperties() {
  646. return GenericRecordUtil.getGenericProperties(
  647. "colorUsage", this::getColorUsage,
  648. "scanCount", this::getScanCount,
  649. "startScan", this::getStartScan,
  650. "srcBounds", this::getSrcBounds,
  651. "dstBounds", this::getDstBounds,
  652. "dib", () -> dib
  653. );
  654. }
  655. }
  656. public static class WmfDibBitBlt extends WmfDibStretchBlt {
  657. @Override
  658. public HwmfRecordType getWmfRecordType() {
  659. return HwmfRecordType.dibBitBlt;
  660. }
  661. @Override
  662. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  663. final boolean hasBitmap = hasBitmap(recordSize/2, recordFunction);
  664. rasterOperation = readRasterOperation(leis);
  665. int size = 2*LittleEndianConsts.SHORT_SIZE;
  666. final Point2D srcPnt = new Point2D.Double();
  667. size += readPointS(leis, srcPnt);
  668. if (!hasBitmap) {
  669. /*int reserved =*/ leis.readShort();
  670. size += LittleEndianConsts.SHORT_SIZE;
  671. }
  672. size += readBounds2(leis, dstBounds);
  673. if (hasBitmap) {
  674. target = new HwmfBitmapDib();
  675. size += target.init(leis, (int)(recordSize-6-size));
  676. }
  677. // the destination rectangle, having the same dimension as the source rectangle
  678. srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
  679. return size;
  680. }
  681. }
  682. /**
  683. * The META_DIBSTRETCHBLT record specifies the transfer of a block of pixels in device-independent format
  684. * according to a raster operation, with possible expansion or contraction.
  685. *
  686. * The destination of the transfer is the current output region in the playback device context.
  687. * There are two forms of META_DIBSTRETCHBLT, one which specifies a device-independent bitmap (DIB) as the source,
  688. * and the other which uses the playback device context as the source. Definitions follow for the fields that are
  689. * the same in the two forms of META_DIBSTRETCHBLT. The subsections that follow specify the packet structures of
  690. * the two forms of META_DIBSTRETCHBLT.
  691. */
  692. public static class WmfDibStretchBlt implements HwmfRecord, HwmfImageRecord {
  693. /**
  694. * A 32-bit unsigned integer that defines how the source pixels, the current brush
  695. * in the playback device context, and the destination pixels are to be combined to form the
  696. * new image. This code MUST be one of the values in the Ternary Raster Operation Enumeration.
  697. */
  698. protected HwmfTernaryRasterOp rasterOperation;
  699. /** the source rectangle */
  700. protected final Rectangle2D srcBounds = new Rectangle2D.Double();
  701. /** the destination rectangle */
  702. protected final Rectangle2D dstBounds = new Rectangle2D.Double();
  703. /**
  704. * A variable-sized DeviceIndependentBitmap Object that defines image content.
  705. * This object MUST be specified, even if the raster operation does not require a source.
  706. */
  707. protected HwmfBitmapDib target;
  708. @Override
  709. public HwmfRecordType getWmfRecordType() {
  710. return HwmfRecordType.dibStretchBlt;
  711. }
  712. @Override
  713. public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
  714. final boolean hasBitmap = hasBitmap(recordSize, recordFunction);
  715. rasterOperation = readRasterOperation(leis);
  716. int size = 2*LittleEndianConsts.SHORT_SIZE;
  717. size += readBounds2(leis, srcBounds);
  718. if (!hasBitmap) {
  719. /*int reserved =*/ leis.readShort();
  720. size += LittleEndianConsts.SHORT_SIZE;
  721. }
  722. size += readBounds2(leis, dstBounds);
  723. if (hasBitmap) {
  724. target = new HwmfBitmapDib();
  725. size += target.init(leis, (int)(recordSize-6-size));
  726. }
  727. return size;
  728. }
  729. @Override
  730. public void draw(HwmfGraphics ctx) {
  731. HwmfDrawProperties prop = ctx.getProperties();
  732. prop.setRasterOp3(rasterOperation);
  733. // TODO: implement second operation based on playback device context
  734. if (target != null) {
  735. HwmfBkMode oldMode = prop.getBkMode();
  736. prop.setBkMode(HwmfBkMode.TRANSPARENT);
  737. Color fgColor = prop.getPenColor().getColor();
  738. Color bgColor = prop.getBackgroundColor().getColor();
  739. BufferedImage bi = target.getImage(fgColor, bgColor, true);
  740. ctx.drawImage(bi, srcBounds, dstBounds);
  741. prop.setBkMode(oldMode);
  742. }
  743. }
  744. @Override
  745. public BufferedImage getImage(Color foreground, Color background, boolean hasAlpha) {
  746. return (target != null && target.isValid()) ? target.getImage(foreground,background,hasAlpha) : null;
  747. }
  748. @Override
  749. public byte[] getBMPData() {
  750. return (target != null && target.isValid()) ? target.getBMPData() : null;
  751. }
  752. public HwmfTernaryRasterOp getRasterOperation() {
  753. return rasterOperation;
  754. }
  755. public Rectangle2D getSrcBounds() {
  756. return srcBounds;
  757. }
  758. public Rectangle2D getDstBounds() {
  759. return dstBounds;
  760. }
  761. public HwmfBitmapDib getTarget() {
  762. return target;
  763. }
  764. @Override
  765. public Map<String, Supplier<?>> getGenericProperties() {
  766. return GenericRecordUtil.getGenericProperties(
  767. "rasterOperation", this::getRasterOperation,
  768. "srcBounds", this::getSrcBounds,
  769. "dstBounds", this::getDstBounds,
  770. "target", this::getTarget
  771. );
  772. }
  773. }
  774. static int readBounds2(LittleEndianInputStream leis, Rectangle2D bounds) {
  775. // The 16-bit signed integers that defines the corners of the bounding rectangle.
  776. int h = leis.readShort();
  777. int w = leis.readShort();
  778. int y = leis.readShort();
  779. int x = leis.readShort();
  780. bounds.setRect(x, y, w, h);
  781. return 4 * LittleEndianConsts.SHORT_SIZE;
  782. }
  783. private static boolean hasBitmap(long recordSize, int recordFunction) {
  784. return (recordSize > ((recordFunction >> 8) + 3));
  785. }
  786. private static HwmfTernaryRasterOp readRasterOperation(LittleEndianInputStream leis) {
  787. int rasterOpCode = leis.readUShort();
  788. int rasterOpIndex = leis.readUShort();
  789. HwmfTernaryRasterOp rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
  790. assert(rasterOperation != null && rasterOpCode == rasterOperation.getOpCode());
  791. return rasterOperation;
  792. }
  793. }