Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  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 java.awt.AlphaComposite;
  17. import java.awt.BasicStroke;
  18. import java.awt.Color;
  19. import java.awt.Graphics2D;
  20. import java.awt.LinearGradientPaint;
  21. import java.awt.MultipleGradientPaint;
  22. import java.awt.RenderingHints;
  23. import java.awt.image.BufferedImage;
  24. import java.awt.image.IndexColorModel;
  25. import java.io.ByteArrayInputStream;
  26. import java.io.IOException;
  27. import java.io.InputStream;
  28. import java.util.Collections;
  29. import java.util.LinkedHashMap;
  30. import java.util.Map;
  31. import java.util.function.Supplier;
  32. import javax.imageio.ImageIO;
  33. import org.apache.poi.common.usermodel.GenericRecord;
  34. import org.apache.poi.hwmf.usermodel.HwmfPicture;
  35. import org.apache.poi.util.GenericRecordJsonWriter;
  36. import org.apache.poi.util.IOUtils;
  37. import org.apache.poi.util.LittleEndian;
  38. import org.apache.poi.util.LittleEndianConsts;
  39. import org.apache.poi.util.LittleEndianInputStream;
  40. import org.apache.poi.util.POILogFactory;
  41. import org.apache.poi.util.POILogger;
  42. import org.apache.poi.util.RecordFormatException;
  43. /**
  44. * The DeviceIndependentBitmap Object defines an image in device-independent bitmap (DIB) format.
  45. */
  46. public class HwmfBitmapDib implements GenericRecord {
  47. private static final POILogger logger = POILogFactory.getLogger(HwmfBitmapDib.class);
  48. private static final int BMP_HEADER_SIZE = 14;
  49. private static final int MAX_RECORD_LENGTH = HwmfPicture.MAX_RECORD_LENGTH;
  50. public enum BitCount {
  51. /**
  52. * The image SHOULD be in either JPEG or PNG format. <6> Neither of these formats includes
  53. * a color table, so this value specifies that no color table is present. See [JFIF] and [RFC2083]
  54. * for more information concerning JPEG and PNG compression formats.
  55. */
  56. BI_BITCOUNT_0(0x0000),
  57. /**
  58. * Each pixel in the bitmap is represented by a single bit. If the bit is clear, the pixel is displayed
  59. * with the color of the first entry in the color table; if the bit is set, the pixel has the color of the
  60. * second entry in the table.
  61. */
  62. BI_BITCOUNT_1(0x0001),
  63. /**
  64. * Each pixel in the bitmap is represented by a 4-bit index into the color table, and each byte
  65. * contains 2 pixels.
  66. */
  67. BI_BITCOUNT_2(0x0004),
  68. /**
  69. * Each pixel in the bitmap is represented by an 8-bit index into the color table, and each byte
  70. * contains 1 pixel.
  71. */
  72. BI_BITCOUNT_3(0x0008),
  73. /**
  74. * Each pixel in the bitmap is represented by a 16-bit value.
  75. * <br>
  76. * If the Compression field of the BitmapInfoHeader Object is BI_RGB, the Colors field of DIB
  77. * is NULL. Each WORD in the bitmap array represents a single pixel. The relative intensities of
  78. * red, green, and blue are represented with 5 bits for each color component. The value for blue
  79. * is in the least significant 5 bits, followed by 5 bits each for green and red. The most significant
  80. * bit is not used. The color table is used for optimizing colors on palette-based devices, and
  81. * contains the number of entries specified by the ColorUsed field of the BitmapInfoHeader
  82. * Object.
  83. * <br>
  84. * If the Compression field of the BitmapInfoHeader Object is BI_BITFIELDS, the Colors field
  85. * contains three DWORD color masks that specify the red, green, and blue components,
  86. * respectively, of each pixel. Each WORD in the bitmap array represents a single pixel.
  87. * <br>
  88. * When the Compression field is set to BI_BITFIELDS, bits set in each DWORD mask MUST be
  89. * contiguous and SHOULD NOT overlap the bits of another mask.
  90. */
  91. BI_BITCOUNT_4(0x0010),
  92. /**
  93. * The bitmap has a maximum of 2^24 colors, and the Colors field of DIB is
  94. * NULL. Each 3-byte triplet in the bitmap array represents the relative intensities of blue, green,
  95. * and red, respectively, for a pixel. The Colors color table is used for optimizing colors used on
  96. * palette-based devices, and MUST contain the number of entries specified by the ColorUsed
  97. * field of the BitmapInfoHeader Object.
  98. */
  99. BI_BITCOUNT_5(0x0018),
  100. /**
  101. * The bitmap has a maximum of 2^24 colors.
  102. * <br>
  103. * If the Compression field of the BitmapInfoHeader Object is set to BI_RGB, the Colors field
  104. * of DIB is set to NULL. Each DWORD in the bitmap array represents the relative intensities of
  105. * blue, green, and red, respectively, for a pixel. The high byte in each DWORD is not used. The
  106. * Colors color table is used for optimizing colors used on palette-based devices, and MUST
  107. * contain the number of entries specified by the ColorUsed field of the BitmapInfoHeader
  108. * Object.
  109. * <br>
  110. * If the Compression field of the BitmapInfoHeader Object is set to BI_BITFIELDS, the Colors
  111. * field contains three DWORD color masks that specify the red, green, and blue components,
  112. * respectively, of each pixel. Each DWORD in the bitmap array represents a single pixel.
  113. * <br>
  114. * When the Compression field is set to BI_BITFIELDS, bits set in each DWORD mask must be
  115. * contiguous and should not overlap the bits of another mask. All the bits in the pixel do not
  116. * need to be used.
  117. */
  118. BI_BITCOUNT_6(0x0020);
  119. int flag;
  120. BitCount(int flag) {
  121. this.flag = flag;
  122. }
  123. static BitCount valueOf(int flag) {
  124. for (BitCount bc : values()) {
  125. if (bc.flag == flag) return bc;
  126. }
  127. return null;
  128. }
  129. }
  130. public enum Compression {
  131. /**
  132. * The bitmap is in uncompressed red green blue (RGB) format that is not compressed
  133. * and does not use color masks.
  134. */
  135. BI_RGB(0x0000),
  136. /**
  137. * An RGB format that uses run-length encoding (RLE) compression for bitmaps
  138. * with 8 bits per pixel. The compression uses a 2-byte format consisting of a count byte
  139. * followed by a byte containing a color index.
  140. */
  141. BI_RLE8(0x0001),
  142. /**
  143. * An RGB format that uses RLE compression for bitmaps with 4 bits per pixel. The
  144. * compression uses a 2-byte format consisting of a count byte followed by two word-length
  145. * color indexes.
  146. */
  147. BI_RLE4(0x0002),
  148. /**
  149. * The bitmap is not compressed and the color table consists of three DWORD
  150. * color masks that specify the red, green, and blue components, respectively, of each pixel.
  151. * This is valid when used with 16 and 32-bits per pixel bitmaps.
  152. */
  153. BI_BITFIELDS(0x0003),
  154. /**
  155. * The image is a JPEG image, as specified in [JFIF]. This value SHOULD only be used in
  156. * certain bitmap operations, such as JPEG pass-through. The application MUST query for the
  157. * pass-through support, since not all devices support JPEG pass-through. Using non-RGB
  158. * bitmaps MAY limit the portability of the metafile to other devices. For instance, display device
  159. * contexts generally do not support this pass-through.
  160. */
  161. BI_JPEG(0x0004),
  162. /**
  163. * The image is a PNG image, as specified in [RFC2083]. This value SHOULD only be
  164. * used certain bitmap operations, such as JPEG/PNG pass-through. The application MUST query
  165. * for the pass-through support, because not all devices support JPEG/PNG pass-through. Using
  166. * non-RGB bitmaps MAY limit the portability of the metafile to other devices. For instance,
  167. * display device contexts generally do not support this pass-through.
  168. */
  169. BI_PNG(0x0005),
  170. /**
  171. * The image is an uncompressed CMYK format.
  172. */
  173. BI_CMYK(0x000B),
  174. /**
  175. * A CMYK format that uses RLE compression for bitmaps with 8 bits per pixel.
  176. * The compression uses a 2-byte format consisting of a count byte followed by a byte containing
  177. * a color index.
  178. */
  179. BI_CMYKRLE8(0x000C),
  180. /**
  181. * A CMYK format that uses RLE compression for bitmaps with 4 bits per pixel.
  182. * The compression uses a 2-byte format consisting of a count byte followed by two word-length
  183. * color indexes.
  184. */
  185. BI_CMYKRLE4(0x000D);
  186. int flag;
  187. Compression(int flag) {
  188. this.flag = flag;
  189. }
  190. static Compression valueOf(int flag) {
  191. for (Compression c : values()) {
  192. if (c.flag == flag) return c;
  193. }
  194. return null;
  195. }
  196. }
  197. private int headerSize;
  198. private int headerWidth;
  199. private int headerHeight;
  200. private int headerPlanes;
  201. private BitCount headerBitCount;
  202. private Compression headerCompression;
  203. private long headerImageSize = -1;
  204. @SuppressWarnings("unused")
  205. private int headerXPelsPerMeter = -1;
  206. @SuppressWarnings("unused")
  207. private int headerYPelsPerMeter = -1;
  208. private long headerColorUsed = -1;
  209. @SuppressWarnings("unused")
  210. private long headerColorImportant = -1;
  211. private Color[] colorTable;
  212. @SuppressWarnings("unused")
  213. private int colorMaskR,colorMaskG,colorMaskB;
  214. // size of header and color table, for start of image data calculation
  215. private int introSize;
  216. private byte[] imageData;
  217. public int init(LittleEndianInputStream leis, int recordSize) throws IOException {
  218. leis.mark(10000);
  219. // need to read the header to calculate start of bitmap data correct
  220. introSize = readHeader(leis);
  221. assert(introSize == headerSize);
  222. introSize += readColors(leis);
  223. assert(introSize < 10000);
  224. leis.reset();
  225. // The size and format of this data is determined by information in the DIBHeaderInfo field. If
  226. // it is a BitmapCoreHeader, the size in bytes MUST be calculated as follows:
  227. int bodySize = ((((headerWidth * headerPlanes * headerBitCount.flag + 31) & ~31) / 8) * Math.abs(headerHeight));
  228. // This formula SHOULD also be used to calculate the size of aData when DIBHeaderInfo is a
  229. // BitmapInfoHeader Object, using values from that object, but only if its Compression value is
  230. // BI_RGB, BI_BITFIELDS, or BI_CMYK.
  231. // Otherwise, the size of aData MUST be the BitmapInfoHeader Object value ImageSize.
  232. assert( headerSize != 0x0C || bodySize == headerImageSize);
  233. if (headerSize == 0x0C ||
  234. headerCompression == Compression.BI_RGB ||
  235. headerCompression == Compression.BI_BITFIELDS ||
  236. headerCompression == Compression.BI_CMYK) {
  237. int fileSize = (int)Math.min(introSize+bodySize,recordSize);
  238. imageData = IOUtils.safelyAllocate(fileSize, MAX_RECORD_LENGTH);
  239. leis.readFully(imageData, 0, introSize);
  240. leis.skipFully(recordSize-fileSize);
  241. // emfs are sometimes truncated, read as much as possible
  242. int readBytes = leis.read(imageData, introSize, fileSize-introSize);
  243. return introSize+(recordSize-fileSize)+readBytes;
  244. } else {
  245. imageData = IOUtils.safelyAllocate(recordSize, MAX_RECORD_LENGTH);
  246. leis.readFully(imageData);
  247. return recordSize;
  248. }
  249. }
  250. protected int readHeader(LittleEndianInputStream leis) throws IOException {
  251. int size = 0;
  252. /**
  253. * DIBHeaderInfo (variable): Either a BitmapCoreHeader Object or a
  254. * BitmapInfoHeader Object that specifies information about the image.
  255. *
  256. * The first 32 bits of this field is the HeaderSize value.
  257. * If it is 0x0000000C, then this is a BitmapCoreHeader; otherwise, this is a BitmapInfoHeader.
  258. */
  259. headerSize = leis.readInt();
  260. size += LittleEndianConsts.INT_SIZE;
  261. if (headerSize == 0x0C) {
  262. // BitmapCoreHeader
  263. // A 16-bit unsigned integer that defines the width of the DIB, in pixels.
  264. headerWidth = leis.readUShort();
  265. // A 16-bit unsigned integer that defines the height of the DIB, in pixels.
  266. headerHeight = leis.readUShort();
  267. // A 16-bit unsigned integer that defines the number of planes for the target
  268. // device. This value MUST be 0x0001.
  269. headerPlanes = leis.readUShort();
  270. // A 16-bit unsigned integer that defines the format of each pixel, and the
  271. // maximum number of colors in the DIB.
  272. headerBitCount = BitCount.valueOf(leis.readUShort());
  273. size += 4*LittleEndianConsts.SHORT_SIZE;
  274. } else {
  275. // fix header size, sometimes this is invalid
  276. headerSize = 40;
  277. // BitmapInfoHeader
  278. // A 32-bit signed integer that defines the width of the DIB, in pixels.
  279. // This value MUST be positive.
  280. // This field SHOULD specify the width of the decompressed image file,
  281. // if the Compression value specifies JPEG or PNG format.
  282. headerWidth = leis.readInt();
  283. // A 32-bit signed integer that defines the height of the DIB, in pixels.
  284. // This value MUST NOT be zero.
  285. // - If this value is positive, the DIB is a bottom-up bitmap,
  286. // and its origin is the lower-left corner.
  287. // This field SHOULD specify the height of the decompressed image file,
  288. // if the Compression value specifies JPEG or PNG format.
  289. // - If this value is negative, the DIB is a top-down bitmap,
  290. // and its origin is the upper-left corner. Top-down bitmaps do not support compression.
  291. headerHeight = leis.readInt();
  292. // A 16-bit unsigned integer that defines the number of planes for the target
  293. // device. This value MUST be 0x0001.
  294. headerPlanes = leis.readUShort();
  295. // A 16-bit unsigned integer that defines the format of each pixel, and the
  296. // maximum number of colors in the DIB.
  297. headerBitCount = BitCount.valueOf(leis.readUShort());
  298. // A 32-bit unsigned integer that defines the compression mode of the DIB.
  299. // This value MUST NOT specify a compressed format if the DIB is a top-down bitmap,
  300. // as indicated by the Height value.
  301. headerCompression = Compression.valueOf((int)leis.readUInt());
  302. // A 32-bit unsigned integer that defines the size, in bytes, of the image.
  303. // If the Compression value is BI_RGB, this value SHOULD be zero and MUST be ignored.
  304. // If the Compression value is BI_JPEG or BI_PNG, this value MUST specify the size of the JPEG
  305. // or PNG image buffer, respectively.
  306. headerImageSize = leis.readUInt();
  307. // A 32-bit signed integer that defines the horizontal resolution,
  308. // in pixels-per-meter, of the target device for the DIB.
  309. headerXPelsPerMeter = leis.readInt();
  310. // A 32-bit signed integer that defines the vertical resolution,
  311. headerYPelsPerMeter = leis.readInt();
  312. // A 32-bit unsigned integer that specifies the number of indexes in the
  313. // color table used by the DIB
  314. // in pixelsper-meter, of the target device for the DIB.
  315. headerColorUsed = leis.readUInt();
  316. // A 32-bit unsigned integer that defines the number of color indexes that are
  317. // required for displaying the DIB. If this value is zero, all color indexes are required.
  318. headerColorImportant = leis.readUInt();
  319. size += 8*LittleEndianConsts.INT_SIZE+2*LittleEndianConsts.SHORT_SIZE;
  320. }
  321. return size;
  322. }
  323. protected int readColors(LittleEndianInputStream leis) throws IOException {
  324. switch (headerBitCount) {
  325. default:
  326. case BI_BITCOUNT_0:
  327. // no table
  328. return 0;
  329. case BI_BITCOUNT_1:
  330. // 2 colors
  331. return readRGBQuad(leis, (int)(headerColorUsed == 0 ? 2 : Math.min(headerColorUsed,2)));
  332. case BI_BITCOUNT_2:
  333. // 16 colors
  334. return readRGBQuad(leis, (int)(headerColorUsed == 0 ? 16 : Math.min(headerColorUsed,16)));
  335. case BI_BITCOUNT_3:
  336. // 256 colors
  337. return readRGBQuad(leis, (int)(headerColorUsed == 0 ? 256 : Math.min(headerColorUsed,256)));
  338. case BI_BITCOUNT_4:
  339. switch (headerCompression) {
  340. case BI_RGB:
  341. colorMaskB = 0x1F;
  342. colorMaskG = 0x1F<<5;
  343. colorMaskR = 0x1F<<10;
  344. return 0;
  345. case BI_BITFIELDS:
  346. colorMaskB = leis.readInt();
  347. colorMaskG = leis.readInt();
  348. colorMaskR = leis.readInt();
  349. return 3*LittleEndianConsts.INT_SIZE;
  350. default:
  351. throw new IOException("Invalid compression option ("+headerCompression+") for bitcount ("+headerBitCount+").");
  352. }
  353. case BI_BITCOUNT_5:
  354. case BI_BITCOUNT_6:
  355. switch (headerCompression) {
  356. case BI_RGB:
  357. colorMaskR=0xFF;
  358. colorMaskG=0xFF;
  359. colorMaskB=0xFF;
  360. return 0;
  361. case BI_BITFIELDS:
  362. colorMaskB = leis.readInt();
  363. colorMaskG = leis.readInt();
  364. colorMaskR = leis.readInt();
  365. return 3*LittleEndianConsts.INT_SIZE;
  366. default:
  367. throw new IOException("Invalid compression option ("+headerCompression+") for bitcount ("+headerBitCount+").");
  368. }
  369. }
  370. }
  371. protected int readRGBQuad(LittleEndianInputStream leis, int count) throws IOException {
  372. int size = 0;
  373. colorTable = new Color[count];
  374. for (int i=0; i<count; i++) {
  375. int blue = leis.readUByte();
  376. int green = leis.readUByte();
  377. int red = leis.readUByte();
  378. @SuppressWarnings("unused")
  379. int reserved = leis.readUByte();
  380. colorTable[i] = new Color(red, green, blue);
  381. size += 4 * LittleEndianConsts.BYTE_SIZE;
  382. }
  383. return size;
  384. }
  385. public boolean isValid() {
  386. // the recordsize ended before the image data
  387. if (imageData == null) {
  388. return false;
  389. }
  390. // ignore all black mono-brushes
  391. if (this.headerBitCount == BitCount.BI_BITCOUNT_1) {
  392. if (colorTable == null) {
  393. return false;
  394. }
  395. for (Color c : colorTable) {
  396. if (!Color.BLACK.equals(c)) {
  397. return true;
  398. }
  399. }
  400. return false;
  401. }
  402. return true;
  403. }
  404. public InputStream getBMPStream() {
  405. return new ByteArrayInputStream(getBMPData());
  406. }
  407. public byte[] getBMPData() {
  408. if (headerWidth <= 0 || headerHeight <= 0) {
  409. return null;
  410. }
  411. if (imageData == null) {
  412. throw new RecordFormatException("used to throw exception: bitmap not initialized ... need to call init() before");
  413. }
  414. // sometimes there are missing bytes after the imageData which will be 0-filled
  415. int imageSize = (int)Math.max(imageData.length, introSize+headerImageSize);
  416. // create the image data and leave the parsing to the ImageIO api
  417. byte[] buf = IOUtils.safelyAllocate(BMP_HEADER_SIZE + (long)imageSize, MAX_RECORD_LENGTH);
  418. // https://en.wikipedia.org/wiki/BMP_file_format # Bitmap file header
  419. buf[0] = (byte)'B';
  420. buf[1] = (byte)'M';
  421. // the full size of the bmp
  422. LittleEndian.putInt(buf, 2, BMP_HEADER_SIZE+imageSize);
  423. // the next 4 bytes are unused
  424. LittleEndian.putInt(buf, 6, 0);
  425. // start of image = BMP header length + dib header length + color tables length
  426. LittleEndian.putInt(buf, 10, BMP_HEADER_SIZE + introSize);
  427. // fill the "known" image data
  428. System.arraycopy(imageData, 0, buf, BMP_HEADER_SIZE, imageData.length);
  429. return buf;
  430. }
  431. public BufferedImage getImage() {
  432. return getImage(null, null, false);
  433. }
  434. public BufferedImage getImage(Color foreground, Color background, boolean hasAlpha) {
  435. BufferedImage bi;
  436. try {
  437. bi = ImageIO.read(getBMPStream());
  438. } catch (IOException|RuntimeException e) {
  439. logger.log(POILogger.ERROR, "invalid bitmap data - returning placeholder image");
  440. return getPlaceholder();
  441. }
  442. if (foreground != null && background != null && headerBitCount == HwmfBitmapDib.BitCount.BI_BITCOUNT_1) {
  443. IndexColorModel cmOld = (IndexColorModel)bi.getColorModel();
  444. int fg = foreground.getRGB();
  445. int bg = background.getRGB() & (hasAlpha ? 0xFFFFFF : 0xFFFFFFFF);
  446. boolean ordered = (cmOld.getRGB(0) & 0xFFFFFF) == (bg & 0xFFFFFF);
  447. int transPixel = ordered ? 0 : 1;
  448. int[] cmap = ordered ? new int[]{ bg, fg } : new int[]{ fg, bg };
  449. int transferType = bi.getData().getTransferType();
  450. IndexColorModel cmNew = new IndexColorModel(1, 2, cmap, 0, hasAlpha, transPixel, transferType);
  451. bi = new BufferedImage(cmNew, bi.getRaster(), false, null);
  452. }
  453. return bi;
  454. }
  455. @Override
  456. public String toString() {
  457. return GenericRecordJsonWriter.marshal(this);
  458. }
  459. @Override
  460. public Map<String, Supplier<?>> getGenericProperties() {
  461. final Map<String,Supplier<?>> m = new LinkedHashMap<>();
  462. m.put("headerSize", () -> headerSize);
  463. m.put("width", () -> headerWidth);
  464. m.put("height", () -> headerHeight);
  465. m.put("planes", () -> headerPlanes);
  466. m.put("bitCount", () -> headerBitCount);
  467. m.put("compression", () -> headerCompression);
  468. m.put("imageSize", () -> headerImageSize);
  469. m.put("xPelsPerMeter", () -> headerXPelsPerMeter);
  470. m.put("yPelsPerMeter", () -> headerYPelsPerMeter);
  471. m.put("colorUsed", () -> headerColorUsed);
  472. m.put("colorImportant", () -> headerColorImportant);
  473. m.put("image", this::getImage);
  474. m.put("bmpData", this::getBMPData);
  475. return Collections.unmodifiableMap(m);
  476. }
  477. protected BufferedImage getPlaceholder() {
  478. if (headerHeight <= 0 || headerWidth <= 0) {
  479. return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
  480. }
  481. BufferedImage bi = new BufferedImage(headerWidth, headerHeight, BufferedImage.TYPE_INT_ARGB);
  482. Graphics2D g = bi.createGraphics();
  483. g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  484. g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
  485. g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
  486. g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
  487. g.setComposite(AlphaComposite.Clear);
  488. g.fillRect(0, 0, headerWidth, headerHeight);
  489. final int arcs = Math.min(headerWidth, headerHeight) / 7;
  490. Color bg = Color.LIGHT_GRAY;
  491. Color fg = Color.GRAY;
  492. LinearGradientPaint lgp = new LinearGradientPaint(0f, 0f, 5, 5,
  493. new float[] {0,.1f,.1001f}, new Color[] {fg,fg,bg}, MultipleGradientPaint.CycleMethod.REFLECT);
  494. g.setComposite(AlphaComposite.SrcOver.derive(0.4f));
  495. g.setPaint(lgp);
  496. g.fillRoundRect(0, 0, headerWidth-1, headerHeight-1, arcs, arcs);
  497. g.setColor(Color.DARK_GRAY);
  498. g.setComposite(AlphaComposite.Src);
  499. g.setStroke(new BasicStroke(2));
  500. g.drawRoundRect(0, 0, headerWidth-1, headerHeight-1, arcs, arcs);
  501. g.dispose();
  502. return bi;
  503. }
  504. }