1. /*
  2. * @(#)BMPImageWriter.java 1.8 03/09/22 13:03:28
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package com.sun.imageio.plugins.bmp;
  8. import java.awt.Point;
  9. import java.awt.Rectangle;
  10. import java.awt.image.ColorModel;
  11. import java.awt.image.ComponentSampleModel;
  12. import java.awt.image.DataBuffer;
  13. import java.awt.image.DataBufferByte;
  14. import java.awt.image.DataBufferInt;
  15. import java.awt.image.DataBufferShort;
  16. import java.awt.image.DataBufferUShort;
  17. import java.awt.image.IndexColorModel;
  18. import java.awt.image.MultiPixelPackedSampleModel;
  19. import java.awt.image.BandedSampleModel;
  20. import java.awt.image.Raster;
  21. import java.awt.image.RenderedImage;
  22. import java.awt.image.SampleModel;
  23. import java.awt.image.SinglePixelPackedSampleModel;
  24. import java.awt.image.WritableRaster;
  25. import java.awt.image.BufferedImage;
  26. import java.io.IOException;
  27. import java.io.ByteArrayOutputStream;
  28. import java.nio.ByteOrder;
  29. import java.util.Iterator;
  30. import javax.imageio.IIOImage;
  31. import javax.imageio.IIOException;
  32. import javax.imageio.ImageIO;
  33. import javax.imageio.ImageTypeSpecifier;
  34. import javax.imageio.ImageWriteParam;
  35. import javax.imageio.ImageWriter;
  36. import javax.imageio.metadata.IIOMetadata;
  37. import javax.imageio.metadata.IIOMetadataNode;
  38. import javax.imageio.metadata.IIOMetadataFormatImpl;
  39. import javax.imageio.metadata.IIOInvalidTreeException;
  40. import javax.imageio.spi.ImageWriterSpi;
  41. import javax.imageio.stream.ImageOutputStream;
  42. import javax.imageio.event.IIOWriteProgressListener;
  43. import javax.imageio.event.IIOWriteWarningListener;
  44. import org.w3c.dom.Node;
  45. import org.w3c.dom.NodeList;
  46. import javax.imageio.plugins.bmp.BMPImageWriteParam;
  47. import com.sun.imageio.plugins.common.ImageUtil;
  48. import com.sun.imageio.plugins.common.I18N;
  49. /**
  50. * The Java Image IO plugin writer for encoding a binary RenderedImage into
  51. * a BMP format.
  52. *
  53. * The encoding process may clip, subsample using the parameters
  54. * specified in the <code>ImageWriteParam</code>.
  55. *
  56. * @see javax.imageio.plugins.bmp.BMPImageWriteParam
  57. */
  58. public class BMPImageWriter extends ImageWriter implements BMPConstants {
  59. /** The output stream to write into */
  60. private ImageOutputStream stream = null;
  61. private ByteArrayOutputStream embedded_stream = null;
  62. private int version;
  63. private int compressionType;
  64. private boolean isTopDown;
  65. private int w, h;
  66. private int compImageSize = 0;
  67. private int[] bitPos;
  68. private byte[] bpixels;
  69. private short[] spixels;
  70. private int[] ipixels;
  71. /** Constructs <code>BMPImageWriter</code> based on the provided
  72. * <code>ImageWriterSpi</code>.
  73. */
  74. public BMPImageWriter(ImageWriterSpi originator) {
  75. super(originator);
  76. }
  77. public void setOutput(Object output) {
  78. super.setOutput(output); // validates output
  79. if (output != null) {
  80. if (!(output instanceof ImageOutputStream))
  81. throw new IllegalArgumentException(I18N.getString("BMPImageWriter0"));
  82. this.stream = (ImageOutputStream)output;
  83. stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
  84. } else
  85. this.stream = null;
  86. }
  87. public ImageWriteParam getDefaultWriteParam() {
  88. return new BMPImageWriteParam();
  89. }
  90. public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
  91. return null;
  92. }
  93. public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType,
  94. ImageWriteParam param) {
  95. BMPMetadata meta = new BMPMetadata();
  96. meta.bmpVersion = VERSION_3;
  97. meta.compression = getPreferredCompressionType(imageType);
  98. if (param != null
  99. && param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) {
  100. meta.compression = getCompressionType(param.getCompressionType());
  101. }
  102. meta.bitsPerPixel = (short)imageType.getColorModel().getPixelSize();
  103. return meta;
  104. }
  105. public IIOMetadata convertStreamMetadata(IIOMetadata inData,
  106. ImageWriteParam param) {
  107. return null;
  108. }
  109. public IIOMetadata convertImageMetadata(IIOMetadata metadata,
  110. ImageTypeSpecifier type,
  111. ImageWriteParam param) {
  112. return null;
  113. }
  114. public boolean canWriteRasters() {
  115. return true;
  116. }
  117. public void write(IIOMetadata streamMetadata,
  118. IIOImage image,
  119. ImageWriteParam param) throws IOException {
  120. if (stream == null) {
  121. throw new IllegalStateException(I18N.getString("BMPImageWriter7"));
  122. }
  123. if (image == null) {
  124. throw new IllegalArgumentException(I18N.getString("BMPImageWriter8"));
  125. }
  126. clearAbortRequest();
  127. processImageStarted(0);
  128. if (param == null)
  129. param = getDefaultWriteParam();
  130. BMPImageWriteParam bmpParam = (BMPImageWriteParam)param;
  131. // Default is using 24 bits per pixel.
  132. int bitsPerPixel = 24;
  133. boolean isPalette = false;
  134. int paletteEntries = 0;
  135. IndexColorModel icm = null;
  136. RenderedImage input = null;
  137. Raster inputRaster = null;
  138. boolean writeRaster = image.hasRaster();
  139. Rectangle sourceRegion = param.getSourceRegion();
  140. SampleModel sampleModel = null;
  141. ColorModel colorModel = null;
  142. compImageSize = 0;
  143. if (writeRaster) {
  144. inputRaster = image.getRaster();
  145. sampleModel = inputRaster.getSampleModel();
  146. colorModel = ImageUtil.createColorModel(null, sampleModel);
  147. if (sourceRegion == null)
  148. sourceRegion = inputRaster.getBounds();
  149. else
  150. sourceRegion = sourceRegion.intersection(inputRaster.getBounds());
  151. } else {
  152. input = image.getRenderedImage();
  153. sampleModel = input.getSampleModel();
  154. colorModel = input.getColorModel();
  155. Rectangle rect = new Rectangle(input.getMinX(), input.getMinY(),
  156. input.getWidth(), input.getHeight());
  157. if (sourceRegion == null)
  158. sourceRegion = rect;
  159. else
  160. sourceRegion = sourceRegion.intersection(rect);
  161. }
  162. IIOMetadata imageMetadata = image.getMetadata();
  163. BMPMetadata bmpImageMetadata = null;
  164. if (imageMetadata != null
  165. && imageMetadata instanceof BMPMetadata)
  166. {
  167. bmpImageMetadata = (BMPMetadata)imageMetadata;
  168. } else {
  169. ImageTypeSpecifier imageType =
  170. new ImageTypeSpecifier(colorModel, sampleModel);
  171. bmpImageMetadata = (BMPMetadata)getDefaultImageMetadata(imageType,
  172. param);
  173. }
  174. if (sourceRegion.isEmpty())
  175. throw new RuntimeException(I18N.getString("BMPImageWrite0"));
  176. int scaleX = param.getSourceXSubsampling();
  177. int scaleY = param.getSourceYSubsampling();
  178. int xOffset = param.getSubsamplingXOffset();
  179. int yOffset = param.getSubsamplingYOffset();
  180. // cache the data type;
  181. int dataType = sampleModel.getDataType();
  182. sourceRegion.translate(xOffset, yOffset);
  183. sourceRegion.width -= xOffset;
  184. sourceRegion.height -= yOffset;
  185. int minX = sourceRegion.x / scaleX;
  186. int minY = sourceRegion.y / scaleY;
  187. w = (sourceRegion.width + scaleX - 1) / scaleX;
  188. h = (sourceRegion.height + scaleY - 1) / scaleY;
  189. xOffset = sourceRegion.x % scaleX;
  190. yOffset = sourceRegion.y % scaleY;
  191. Rectangle destinationRegion = new Rectangle(minX, minY, w, h);
  192. boolean noTransform = destinationRegion.equals(sourceRegion);
  193. // Raw data can only handle bytes, everything greater must be ASCII.
  194. int[] sourceBands = param.getSourceBands();
  195. boolean noSubband = true;
  196. int numBands = sampleModel.getNumBands();
  197. if (sourceBands != null) {
  198. sampleModel = sampleModel.createSubsetSampleModel(sourceBands);
  199. colorModel = null;
  200. noSubband = false;
  201. numBands = sampleModel.getNumBands();
  202. } else {
  203. sourceBands = new int[numBands];
  204. for (int i = 0; i < numBands; i++)
  205. sourceBands[i] = i;
  206. }
  207. int[] bandOffsets = null;
  208. boolean bgrOrder = true;
  209. if (sampleModel instanceof ComponentSampleModel) {
  210. bandOffsets = ((ComponentSampleModel)sampleModel).getBandOffsets();
  211. if (sampleModel instanceof BandedSampleModel) {
  212. // for images with BandedSampleModel we can not work
  213. // with raster directly and must use writePixels()
  214. bgrOrder = false;
  215. } else {
  216. // we can work with raster directly only in case of
  217. // RGB component order.
  218. // In any other case we must use writePixels()
  219. for (int i = 0; i < bandOffsets.length; i++)
  220. bgrOrder &= bandOffsets[i] == bandOffsets.length - i -1;
  221. }
  222. } else {
  223. bandOffsets = new int[numBands];
  224. for (int i = 0; i < numBands; i++)
  225. bandOffsets[i] = i;
  226. }
  227. // BugId 4892214: we can not work with raster directly
  228. // if image have different color order than RGB.
  229. // We should use writePixels() for such images.
  230. if (bgrOrder
  231. && sampleModel instanceof SinglePixelPackedSampleModel) {
  232. int[] bitOffsets = ((SinglePixelPackedSampleModel)sampleModel).getBitOffsets();
  233. for (int i=0; i<bitOffsets.length-1; i++) {
  234. bgrOrder &= bitOffsets[i] > bitOffsets[i+1];
  235. }
  236. }
  237. noTransform &= bgrOrder;
  238. int sampleSize[] = sampleModel.getSampleSize();
  239. //XXX: check more
  240. // Number of bytes that a scanline for the image written out will have.
  241. int destScanlineBytes = w * numBands;
  242. switch(bmpParam.getCompressionMode()) {
  243. case ImageWriteParam.MODE_EXPLICIT:
  244. compressionType = getCompressionType(bmpParam.getCompressionType());
  245. break;
  246. case ImageWriteParam.MODE_COPY_FROM_METADATA:
  247. compressionType = bmpImageMetadata.compression;
  248. break;
  249. case ImageWriteParam.MODE_DEFAULT:
  250. compressionType = getPreferredCompressionType(colorModel, sampleModel);
  251. break;
  252. default:
  253. // ImageWriteParam.MODE_DISABLED:
  254. compressionType = BI_RGB;
  255. }
  256. if (!canEncodeImage(compressionType, colorModel, sampleModel)) {
  257. throw new IOException("Image can not be encoded with compression type "
  258. + compressionTypeNames[compressionType]);
  259. }
  260. byte r[] = null, g[] = null, b[] = null, a[] = null;
  261. if (colorModel instanceof IndexColorModel) {
  262. isPalette = true;
  263. icm = (IndexColorModel)colorModel;
  264. paletteEntries = icm.getMapSize();
  265. if (paletteEntries <= 2) {
  266. bitsPerPixel = 1;
  267. destScanlineBytes = w + 7 >> 3;
  268. } else if (paletteEntries <= 16) {
  269. bitsPerPixel = 4;
  270. destScanlineBytes = w + 1 >> 1;
  271. } else if (paletteEntries <= 256) {
  272. bitsPerPixel = 8;
  273. } else {
  274. // Cannot be written as a Palette image. So write out as
  275. // 24 bit image.
  276. bitsPerPixel = 24;
  277. isPalette = false;
  278. paletteEntries = 0;
  279. destScanlineBytes = w * 3;
  280. }
  281. if (isPalette == true) {
  282. r = new byte[paletteEntries];
  283. g = new byte[paletteEntries];
  284. b = new byte[paletteEntries];
  285. a = new byte[paletteEntries];
  286. icm.getAlphas(a);
  287. icm.getReds(r);
  288. icm.getGreens(g);
  289. icm.getBlues(b);
  290. }
  291. } else {
  292. // Grey scale images
  293. if (numBands == 1) {
  294. isPalette = true;
  295. paletteEntries = 256;
  296. bitsPerPixel = sampleSize[0];
  297. destScanlineBytes = (w * bitsPerPixel + 7 >> 3);
  298. r = new byte[256];
  299. g = new byte[256];
  300. b = new byte[256];
  301. a = new byte[256];
  302. for (int i = 0; i < 256; i++) {
  303. r[i] = (byte)i;
  304. g[i] = (byte)i;
  305. b[i] = (byte)i;
  306. a[i] = (byte)255;
  307. }
  308. } else {
  309. if (sampleModel instanceof SinglePixelPackedSampleModel &&
  310. noSubband) {
  311. bitsPerPixel =
  312. DataBuffer.getDataTypeSize(sampleModel.getDataType());
  313. destScanlineBytes = w * bitsPerPixel + 7 >> 3;
  314. if (compressionType == BMPConstants.BI_BITFIELDS) {
  315. isPalette = true;
  316. paletteEntries = 3;
  317. r = new byte[paletteEntries];
  318. g = new byte[paletteEntries];
  319. b = new byte[paletteEntries];
  320. a = new byte[paletteEntries];
  321. if (bitsPerPixel == 16) {
  322. b[0]=(byte)0x00; g[0]=(byte)0x00; r[0]=(byte)0xF8; a[0]=(byte)0x00; // red mask 0x00000F800
  323. b[1]=(byte)0x00; g[1]=(byte)0x00; r[1]=(byte)0x07; a[1]=(byte)0xE0; // green mask 0x0000007E0
  324. b[2]=(byte)0x00; g[2]=(byte)0x00; r[2]=(byte)0x00; a[2]=(byte)0x1F; // blue mask 0x00000001F
  325. } else if (bitsPerPixel == 32) {
  326. b[0]=(byte)0x00; g[0]=(byte)0xFF; r[0]=(byte)0x00; a[0]=(byte)0x00; // red mask 0x00FF0000
  327. b[1]=(byte)0x00; g[1]=(byte)0x00; r[1]=(byte)0xFF; a[1]=(byte)0x00; // green mask 0x0000FF00
  328. b[2]=(byte)0x00; g[2]=(byte)0x00; r[2]=(byte)0x00; a[2]=(byte)0xFF; // blue mask 0x000000FF
  329. } else {
  330. throw new RuntimeException(I18N.getString("BMPImageWrite6"));
  331. }
  332. }
  333. }
  334. }
  335. }
  336. // actual writing of image data
  337. int fileSize = 0;
  338. int offset = 0;
  339. int headerSize = 0;
  340. int imageSize = 0;
  341. int xPelsPerMeter = 0;
  342. int yPelsPerMeter = 0;
  343. int colorsUsed = 0;
  344. int colorsImportant = paletteEntries;
  345. // Calculate padding for each scanline
  346. int padding = destScanlineBytes % 4;
  347. if (padding != 0) {
  348. padding = 4 - padding;
  349. }
  350. if (sampleModel instanceof SinglePixelPackedSampleModel && noSubband) {
  351. destScanlineBytes = w;
  352. bitPos =
  353. ((SinglePixelPackedSampleModel)sampleModel).getBitMasks();
  354. for (int i = 0; i < bitPos.length; i++)
  355. bitPos[i] = firstLowBit(bitPos[i]);
  356. }
  357. // FileHeader is 14 bytes, BitmapHeader is 40 bytes,
  358. // add palette size and that is where the data will begin
  359. offset = 54 + paletteEntries * 4;
  360. imageSize = (destScanlineBytes + padding) * h;
  361. fileSize = imageSize + offset;
  362. headerSize = 40;
  363. long headPos = stream.getStreamPosition();
  364. writeFileHeader(fileSize, offset);
  365. writeInfoHeader(headerSize, bitsPerPixel);
  366. // compression
  367. stream.writeInt(compressionType);
  368. // imageSize
  369. stream.writeInt(imageSize);
  370. // xPelsPerMeter
  371. stream.writeInt(xPelsPerMeter);
  372. // yPelsPerMeter
  373. stream.writeInt(yPelsPerMeter);
  374. // Colors Used
  375. stream.writeInt(colorsUsed);
  376. // Colors Important
  377. stream.writeInt(colorsImportant);
  378. // palette
  379. if (isPalette == true) {
  380. // write palette
  381. if (compressionType == BMPConstants.BI_BITFIELDS) {
  382. // write masks for red, green and blue components.
  383. for (int i=0; i<3; i++) {
  384. int mask = (a[i]&0xFF) + ((r[i]&0xFF)*0x100) + ((g[i]&0xFF)*0x10000) + ((b[i]&0xFF)*0x1000000);
  385. stream.writeInt(mask);
  386. }
  387. } else {
  388. for (int i=0; i<paletteEntries; i++) {
  389. stream.writeByte(b[i]);
  390. stream.writeByte(g[i]);
  391. stream.writeByte(r[i]);
  392. stream.writeByte(a[i]);
  393. }
  394. }
  395. }
  396. // Writing of actual image data
  397. int scanlineBytes = w * numBands;
  398. // Buffer for up to 8 rows of pixels
  399. int[] pixels = new int[scanlineBytes * scaleX];
  400. // Also create a buffer to hold one line of the data
  401. // to be written to the file, so we can use array writes.
  402. bpixels = new byte[destScanlineBytes];
  403. int l;
  404. if (compressionType == BMPConstants.BI_JPEG ||
  405. compressionType == BMPConstants.BI_PNG) {
  406. // prepare embedded buffer
  407. embedded_stream = new ByteArrayOutputStream();
  408. writeEmbedded(image, bmpParam);
  409. // update the file/image Size
  410. embedded_stream.flush();
  411. imageSize = embedded_stream.size();
  412. long endPos = stream.getStreamPosition();
  413. fileSize = (int)(offset + imageSize);
  414. stream.seek(headPos);
  415. writeSize(fileSize, 2);
  416. stream.seek(headPos);
  417. writeSize(imageSize, 34);
  418. stream.seek(endPos);
  419. stream.write(embedded_stream.toByteArray());
  420. embedded_stream = null;
  421. if (abortRequested()) {
  422. processWriteAborted();
  423. } else {
  424. processImageComplete();
  425. stream.flushBefore(stream.getStreamPosition());
  426. }
  427. return;
  428. }
  429. isTopDown = bmpParam.isTopDown();
  430. int maxBandOffset = bandOffsets[0];
  431. for (int i = 1; i < bandOffsets.length; i++)
  432. if (bandOffsets[i] > maxBandOffset)
  433. maxBandOffset = bandOffsets[i];
  434. int[] pixel = new int[maxBandOffset + 1];
  435. for (int i = 0; i < h; i++) {
  436. if (abortRequested()) {
  437. break;
  438. }
  439. int row = minY + i;
  440. if (!isTopDown)
  441. row = minY + h - i -1;
  442. // Get the pixels
  443. Raster src = inputRaster;
  444. Rectangle srcRect =
  445. new Rectangle(minX * scaleX + xOffset,
  446. row * scaleY + yOffset,
  447. (w - 1)* scaleX + 1,
  448. 1);
  449. if (!writeRaster)
  450. src = input.getData(srcRect);
  451. if (noTransform && noSubband) {
  452. SampleModel sm = src.getSampleModel();
  453. int pos = 0;
  454. int startX = srcRect.x - src.getSampleModelTranslateX();
  455. int startY = srcRect.y - src.getSampleModelTranslateY();
  456. if (sm instanceof ComponentSampleModel) {
  457. ComponentSampleModel csm = (ComponentSampleModel)sm;
  458. pos = csm.getOffset(startX, startY, 0);
  459. for(int nb=1; nb < csm.getNumBands(); nb++) {
  460. if (pos > csm.getOffset(startX, startY, nb)) {
  461. pos = csm.getOffset(startX, startY, nb);
  462. }
  463. }
  464. } else if (sm instanceof MultiPixelPackedSampleModel) {
  465. MultiPixelPackedSampleModel mppsm =
  466. (MultiPixelPackedSampleModel)sm;
  467. pos = mppsm.getOffset(startX, startY);
  468. } else if (sm instanceof SinglePixelPackedSampleModel) {
  469. SinglePixelPackedSampleModel sppsm =
  470. (SinglePixelPackedSampleModel)sm;
  471. pos = sppsm.getOffset(startX, startY);
  472. }
  473. if (compressionType == BMPConstants.BI_RGB || compressionType == BMPConstants.BI_BITFIELDS){
  474. switch(dataType) {
  475. case DataBuffer.TYPE_BYTE:
  476. byte[] bdata =
  477. ((DataBufferByte)src.getDataBuffer()).getData();
  478. stream.write(bdata, pos, destScanlineBytes);
  479. break;
  480. case DataBuffer.TYPE_SHORT:
  481. short[] sdata =
  482. ((DataBufferShort)src.getDataBuffer()).getData();
  483. stream.writeShorts(sdata, pos, destScanlineBytes);
  484. break;
  485. case DataBuffer.TYPE_USHORT:
  486. short[] usdata =
  487. ((DataBufferUShort)src.getDataBuffer()).getData();
  488. stream.writeShorts(usdata, pos, destScanlineBytes);
  489. break;
  490. case DataBuffer.TYPE_INT:
  491. int[] idata =
  492. ((DataBufferInt)src.getDataBuffer()).getData();
  493. stream.writeInts(idata, pos, destScanlineBytes);
  494. break;
  495. }
  496. for(int k=0; k<padding; k++) {
  497. stream.writeByte(0);
  498. }
  499. } else if (compressionType == BMPConstants.BI_RLE4) {
  500. if (bpixels == null || bpixels.length < scanlineBytes)
  501. bpixels = new byte[scanlineBytes];
  502. src.getPixels(srcRect.x, srcRect.y,
  503. srcRect.width, srcRect.height, pixels);
  504. for (int h=0; h<scanlineBytes; h++) {
  505. bpixels[h] = (byte)pixels[h];
  506. }
  507. encodeRLE4(bpixels, scanlineBytes);
  508. } else if (compressionType == BMPConstants.BI_RLE8) {
  509. //byte[] bdata =
  510. // ((DataBufferByte)src.getDataBuffer()).getData();
  511. //System.out.println("bdata.length="+bdata.length);
  512. //System.arraycopy(bdata, pos, bpixels, 0, scanlineBytes);
  513. if (bpixels == null || bpixels.length < scanlineBytes)
  514. bpixels = new byte[scanlineBytes];
  515. src.getPixels(srcRect.x, srcRect.y,
  516. srcRect.width, srcRect.height, pixels);
  517. for (int h=0; h<scanlineBytes; h++) {
  518. bpixels[h] = (byte)pixels[h];
  519. }
  520. encodeRLE8(bpixels, scanlineBytes);
  521. }
  522. } else {
  523. src.getPixels(srcRect.x, srcRect.y,
  524. srcRect.width, srcRect.height, pixels);
  525. if (scaleX != 1 || maxBandOffset != numBands -1 ||
  526. bgrOrder)
  527. for (int j = 0, k = 0, n=0; j < w;
  528. j++, k += scaleX * numBands, n += numBands) {
  529. System.arraycopy(pixels, k, pixel, 0, pixel.length);
  530. for (int m = 0; m < numBands; m++)
  531. pixels[n + numBands - m - 1] =
  532. pixel[bandOffsets[sourceBands[m]]];
  533. }
  534. writePixels(0, scanlineBytes, bitsPerPixel, pixels,
  535. padding, numBands, icm);
  536. }
  537. processImageProgress(100.0f * (((float)i) / ((float)h)));
  538. }
  539. if (compressionType == BMPConstants.BI_RLE4 ||
  540. compressionType == BMPConstants.BI_RLE8) {
  541. // Write the RLE EOF marker and
  542. stream.writeByte(0);
  543. stream.writeByte(1);
  544. incCompImageSize(2);
  545. // update the file/image Size
  546. imageSize = compImageSize;
  547. fileSize = compImageSize + offset;
  548. long endPos = stream.getStreamPosition();
  549. stream.seek(headPos);
  550. writeSize(fileSize, 2);
  551. stream.seek(headPos);
  552. writeSize(imageSize, 34);
  553. stream.seek(endPos);
  554. }
  555. if (abortRequested()) {
  556. processWriteAborted();
  557. } else {
  558. processImageComplete();
  559. stream.flushBefore(stream.getStreamPosition());
  560. }
  561. }
  562. private void writePixels(int l, int scanlineBytes, int bitsPerPixel,
  563. int pixels[],
  564. int padding, int numBands,
  565. IndexColorModel icm) throws IOException {
  566. int pixel = 0;
  567. int k = 0;
  568. switch (bitsPerPixel) {
  569. case 1:
  570. for (int j=0; j<scanlineBytes8; j++) {
  571. bpixels[k++] = (byte)((pixels[l++] << 7) |
  572. (pixels[l++] << 6) |
  573. (pixels[l++] << 5) |
  574. (pixels[l++] << 4) |
  575. (pixels[l++] << 3) |
  576. (pixels[l++] << 2) |
  577. (pixels[l++] << 1) |
  578. pixels[l++]);
  579. }
  580. // Partially filled last byte, if any
  581. if (scanlineBytes%8 > 0) {
  582. pixel = 0;
  583. for (int j=0; j<scanlineBytes%8; j++) {
  584. pixel |= (pixels[l++] << (7 - j));
  585. }
  586. bpixels[k++] = (byte)pixel;
  587. }
  588. stream.write(bpixels, 0, (scanlineBytes+7)/8);
  589. break;
  590. case 4:
  591. if (compressionType == BMPConstants.BI_RLE4){
  592. byte[] bipixels = new byte[scanlineBytes];
  593. for (int h=0; h<scanlineBytes; h++) {
  594. bipixels[h] = (byte)pixels[l++];
  595. }
  596. encodeRLE4(bipixels, scanlineBytes);
  597. }else {
  598. for (int j=0; j<scanlineBytes2; j++) {
  599. pixel = (pixels[l++] << 4) | pixels[l++];
  600. bpixels[k++] = (byte)pixel;
  601. }
  602. // Put the last pixel of odd-length lines in the 4 MSBs
  603. if ((scanlineBytes%2) == 1) {
  604. pixel = pixels[l] << 4;
  605. bpixels[k++] = (byte)pixel;
  606. }
  607. stream.write(bpixels, 0, (scanlineBytes+1)/2);
  608. }
  609. break;
  610. case 8:
  611. if(compressionType == BMPConstants.BI_RLE8) {
  612. for (int h=0; h<scanlineBytes; h++) {
  613. bpixels[h] = (byte)pixels[l++];
  614. }
  615. encodeRLE8(bpixels, scanlineBytes);
  616. }else {
  617. for (int j=0; j<scanlineBytes; j++) {
  618. bpixels[j] = (byte)pixels[l++];
  619. }
  620. stream.write(bpixels, 0, scanlineBytes);
  621. }
  622. break;
  623. case 16:
  624. if (spixels == null)
  625. spixels = new short[scanlineBytes / numBands];
  626. for (int j = 0, m = 0; j < scanlineBytes; m++) {
  627. spixels[m] = 0;
  628. for(int i = numBands -1 ; i >= 0; i--, j++)
  629. spixels[m] |= pixels[j] << bitPos[i];
  630. }
  631. stream.writeShorts(spixels, 0, spixels.length);
  632. break;
  633. case 24:
  634. if (numBands == 3) {
  635. for (int j=0; j<scanlineBytes; j+=3) {
  636. // Since BMP needs BGR format
  637. bpixels[k++] = (byte)(pixels[l+2]);
  638. bpixels[k++] = (byte)(pixels[l+1]);
  639. bpixels[k++] = (byte)(pixels[l]);
  640. l+=3;
  641. }
  642. stream.write(bpixels, 0, scanlineBytes);
  643. } else {
  644. // Case where IndexColorModel had > 256 colors.
  645. int entries = icm.getMapSize();
  646. byte r[] = new byte[entries];
  647. byte g[] = new byte[entries];
  648. byte b[] = new byte[entries];
  649. icm.getReds(r);
  650. icm.getGreens(g);
  651. icm.getBlues(b);
  652. int index;
  653. for (int j=0; j<scanlineBytes; j++) {
  654. index = pixels[l];
  655. bpixels[k++] = b[index];
  656. bpixels[k++] = g[index];
  657. bpixels[k++] = b[index];
  658. l++;
  659. }
  660. stream.write(bpixels, 0, scanlineBytes*3);
  661. }
  662. break;
  663. case 32:
  664. if (ipixels == null)
  665. ipixels = new int[scanlineBytes / numBands];
  666. for (int j = 0, m = 0; j < scanlineBytes; m++) {
  667. ipixels[m] = 0;
  668. for(int i = numBands -1 ; i >= 0; i--, j++)
  669. ipixels[m] |= pixels[j] << bitPos[i];
  670. }
  671. stream.writeInts(ipixels, 0, ipixels.length);
  672. break;
  673. }
  674. // Write out the padding
  675. if (compressionType == BMPConstants.BI_RGB){
  676. for(k=0; k<padding; k++) {
  677. stream.writeByte(0);
  678. }
  679. }
  680. }
  681. private void encodeRLE8(byte[] bpixels, int scanlineBytes)
  682. throws IOException{
  683. int runCount = 1, absVal = -1, j = -1;
  684. byte runVal = 0, nextVal =0 ;
  685. runVal = bpixels[++j];
  686. byte[] absBuf = new byte[256];
  687. while (j < scanlineBytes-1) {
  688. nextVal = bpixels[++j];
  689. if (nextVal == runVal ){
  690. if(absVal >= 3 ){
  691. /// Check if there was an existing Absolute Run
  692. stream.writeByte(0);
  693. stream.writeByte(absVal);
  694. incCompImageSize(2);
  695. for(int a=0; a<absVal;a++){
  696. stream.writeByte(absBuf[a]);
  697. incCompImageSize(1);
  698. }
  699. if (!isEven(absVal)){
  700. //Padding
  701. stream.writeByte(0);
  702. incCompImageSize(1);
  703. }
  704. }
  705. else if(absVal > -1){
  706. /// Absolute Encoding for less than 3
  707. /// treated as regular encoding
  708. /// Do not include the last element since it will
  709. /// be inclued in the next encoding/run
  710. for (int b=0;b<absVal;b++){
  711. stream.writeByte(1);
  712. stream.writeByte(absBuf[b]);
  713. incCompImageSize(2);
  714. }
  715. }
  716. absVal = -1;
  717. runCount++;
  718. if (runCount == 256){
  719. /// Only 255 values permitted
  720. stream.writeByte(runCount-1);
  721. stream.writeByte(runVal);
  722. incCompImageSize(2);
  723. runCount = 1;
  724. }
  725. }
  726. else {
  727. if (runCount > 1){
  728. /// If there was an existing run
  729. stream.writeByte(runCount);
  730. stream.writeByte(runVal);
  731. incCompImageSize(2);
  732. } else if (absVal < 0){
  733. // First time..
  734. absBuf[++absVal] = runVal;
  735. absBuf[++absVal] = nextVal;
  736. } else if (absVal < 254){
  737. // 0-254 only
  738. absBuf[++absVal] = nextVal;
  739. } else {
  740. stream.writeByte(0);
  741. stream.writeByte(absVal+1);
  742. incCompImageSize(2);
  743. for(int a=0; a<=absVal;a++){
  744. stream.writeByte(absBuf[a]);
  745. incCompImageSize(1);
  746. }
  747. // padding since 255 elts is not even
  748. stream.writeByte(0);
  749. incCompImageSize(1);
  750. absVal = -1;
  751. }
  752. runVal = nextVal;
  753. runCount = 1;
  754. }
  755. if (j == scanlineBytes-1){ // EOF scanline
  756. // Write the run
  757. if (absVal == -1){
  758. stream.writeByte(runCount);
  759. stream.writeByte(runVal);
  760. incCompImageSize(2);
  761. runCount = 1;
  762. }
  763. else {
  764. // write the Absolute Run
  765. if(absVal >= 2){
  766. stream.writeByte(0);
  767. stream.writeByte(absVal+1);
  768. incCompImageSize(2);
  769. for(int a=0; a<=absVal;a++){
  770. stream.writeByte(absBuf[a]);
  771. incCompImageSize(1);
  772. }
  773. if (!isEven(absVal+1)){
  774. //Padding
  775. stream.writeByte(0);
  776. incCompImageSize(1);
  777. }
  778. }
  779. else if(absVal > -1){
  780. for (int b=0;b<=absVal;b++){
  781. stream.writeByte(1);
  782. stream.writeByte(absBuf[b]);
  783. incCompImageSize(2);
  784. }
  785. }
  786. }
  787. /// EOF scanline
  788. stream.writeByte(0);
  789. stream.writeByte(0);
  790. incCompImageSize(2);
  791. }
  792. }
  793. }
  794. private void encodeRLE4(byte[] bipixels, int scanlineBytes)
  795. throws IOException {
  796. int runCount=2, absVal=-1, j=-1, pixel=0, q=0;
  797. byte runVal1=0, runVal2=0, nextVal1=0, nextVal2=0;
  798. byte[] absBuf = new byte[256];
  799. runVal1 = bipixels[++j];
  800. runVal2 = bipixels[++j];
  801. while (j < scanlineBytes-2){
  802. nextVal1 = bipixels[++j];
  803. nextVal2 = bipixels[++j];
  804. if (nextVal1 == runVal1 ) {
  805. //Check if there was an existing Absolute Run
  806. if(absVal >= 4){
  807. stream.writeByte(0);
  808. stream.writeByte(absVal - 1);
  809. incCompImageSize(2);
  810. // we need to exclude last 2 elts, similarity of
  811. // which caused to enter this part of the code
  812. for(int a=0; a<absVal-2;a+=2){
  813. pixel = (absBuf[a] << 4) | absBuf[a+1];
  814. stream.writeByte((byte)pixel);
  815. incCompImageSize(1);
  816. }
  817. // if # of elts is odd - read the last element
  818. if(!(isEven(absVal-1))){
  819. q = absBuf[absVal-2] << 4| 0;
  820. stream.writeByte(q);
  821. incCompImageSize(1);
  822. }
  823. // Padding to word align absolute encoding
  824. if ( !isEven((int)Math.ceil((absVal-1)/2)) ) {
  825. stream.writeByte(0);
  826. incCompImageSize(1);
  827. }
  828. } else if (absVal > -1){
  829. stream.writeByte(2);
  830. pixel = (absBuf[0] << 4) | absBuf[1];
  831. stream.writeByte(pixel);
  832. incCompImageSize(2);
  833. }
  834. absVal = -1;
  835. if (nextVal2 == runVal2){
  836. // Even runlength
  837. runCount+=2;
  838. if(runCount == 256){
  839. stream.writeByte(runCount-1);
  840. pixel = ( runVal1 << 4) | runVal2;
  841. stream.writeByte(pixel);
  842. incCompImageSize(2);
  843. runCount =2;
  844. if(j< scanlineBytes - 1){
  845. runVal1 = runVal2;
  846. runVal2 = bipixels[++j];
  847. } else {
  848. stream.writeByte(01);
  849. int r = runVal2 << 4 | 0;
  850. stream.writeByte(r);
  851. incCompImageSize(2);
  852. runCount = -1;/// Only EOF required now
  853. }
  854. }
  855. } else {
  856. // odd runlength and the run ends here
  857. // runCount wont be > 254 since 256/255 case will
  858. // be taken care of in above code.
  859. runCount++;
  860. pixel = ( runVal1 << 4) | runVal2;
  861. stream.writeByte(runCount);
  862. stream.writeByte(pixel);
  863. incCompImageSize(2);
  864. runCount = 2;
  865. runVal1 = nextVal2;
  866. // If end of scanline
  867. if (j < scanlineBytes -1){
  868. runVal2 = bipixels[++j];
  869. }else {
  870. stream.writeByte(01);
  871. int r = nextVal2 << 4 | 0;
  872. stream.writeByte(r);
  873. incCompImageSize(2);
  874. runCount = -1;/// Only EOF required now
  875. }
  876. }
  877. } else{
  878. // Check for existing run
  879. if (runCount > 2){
  880. pixel = ( runVal1 << 4) | runVal2;
  881. stream.writeByte(runCount);
  882. stream.writeByte(pixel);
  883. incCompImageSize(2);
  884. } else if (absVal < 0){ // first time
  885. absBuf[++absVal] = runVal1;
  886. absBuf[++absVal] = runVal2;
  887. absBuf[++absVal] = nextVal1;
  888. absBuf[++absVal] = nextVal2;
  889. } else if (absVal < 253){ // only 255 elements
  890. absBuf[++absVal] = nextVal1;
  891. absBuf[++absVal] = nextVal2;
  892. } else {
  893. stream.writeByte(0);
  894. stream.writeByte(absVal+1);
  895. incCompImageSize(2);
  896. for(int a=0; a<absVal;a+=2){
  897. pixel = (absBuf[a] << 4) | absBuf[a+1];
  898. stream.writeByte((byte)pixel);
  899. incCompImageSize(1);
  900. }
  901. // Padding for word align
  902. // since it will fit into 127 bytes
  903. stream.writeByte(0);
  904. incCompImageSize(1);
  905. absVal = -1;
  906. }
  907. runVal1 = nextVal1;
  908. runVal2 = nextVal2;
  909. runCount = 2;
  910. }
  911. // Handle the End of scanline for the last 2 4bits
  912. if (j >= scanlineBytes-2 ) {
  913. if (absVal == -1 && runCount >= 2){
  914. if (j == scanlineBytes-2){
  915. if(bipixels[++j] == runVal1){
  916. runCount++;
  917. pixel = ( runVal1 << 4) | runVal2;
  918. stream.writeByte(runCount);
  919. stream.writeByte(pixel);
  920. incCompImageSize(2);
  921. } else {
  922. pixel = ( runVal1 << 4) | runVal2;
  923. stream.writeByte(runCount);
  924. stream.writeByte(pixel);
  925. stream.writeByte(01);
  926. pixel = bipixels[j]<<4 |0;
  927. stream.writeByte(pixel);
  928. int n = bipixels[j]<<4|0;
  929. incCompImageSize(4);
  930. }
  931. } else {
  932. stream.writeByte(runCount);
  933. pixel =( runVal1 << 4) | runVal2 ;
  934. stream.writeByte(pixel);
  935. incCompImageSize(2);
  936. }
  937. } else if(absVal > -1){
  938. if (j == scanlineBytes-2){
  939. absBuf[++absVal] = bipixels[++j];
  940. }
  941. if (absVal >=2){
  942. stream.writeByte(0);
  943. stream.writeByte(absVal+1);
  944. incCompImageSize(2);
  945. for(int a=0; a<absVal;a+=2){
  946. pixel = (absBuf[a] << 4) | absBuf[a+1];
  947. stream.writeByte((byte)pixel);
  948. incCompImageSize(1);
  949. }
  950. if(!(isEven(absVal+1))){
  951. q = absBuf[absVal] << 4|0;
  952. stream.writeByte(q);
  953. incCompImageSize(1);
  954. }
  955. // Padding
  956. if ( !isEven((int)Math.ceil((absVal+1)/2)) ) {
  957. stream.writeByte(0);
  958. incCompImageSize(1);
  959. }
  960. } else {
  961. switch (absVal){
  962. case 0:
  963. stream.writeByte(1);
  964. int n = absBuf[0]<<4 | 0;
  965. stream.writeByte(n);
  966. incCompImageSize(2);
  967. break;
  968. case 1:
  969. stream.writeByte(2);
  970. pixel = (absBuf[0] << 4) | absBuf[1];
  971. stream.writeByte(pixel);
  972. incCompImageSize(2);
  973. break;
  974. }
  975. }
  976. }
  977. stream.writeByte(0);
  978. stream.writeByte(0);
  979. incCompImageSize(2);
  980. }
  981. }
  982. }
  983. private synchronized void incCompImageSize(int value){
  984. compImageSize = compImageSize + value;
  985. }
  986. private boolean isEven(int number) {
  987. return (number%2 == 0 ? true : false);
  988. }
  989. private void writeFileHeader(int fileSize, int offset) throws IOException {
  990. // magic value
  991. stream.writeByte('B');
  992. stream.writeByte('M');
  993. // File size
  994. stream.writeInt(fileSize);
  995. // reserved1 and reserved2
  996. stream.writeInt(0);
  997. // offset to image data
  998. stream.writeInt(offset);
  999. }
  1000. private void writeInfoHeader(int headerSize,
  1001. int bitsPerPixel) throws IOException {
  1002. // size of header
  1003. stream.writeInt(headerSize);
  1004. // width
  1005. stream.writeInt(w);
  1006. // height
  1007. stream.writeInt(h);
  1008. // number of planes
  1009. stream.writeShort(1);
  1010. // Bits Per Pixel
  1011. stream.writeShort(bitsPerPixel);
  1012. }
  1013. private void writeSize(int dword, int offset) throws IOException {
  1014. stream.skipBytes(offset);
  1015. stream.writeInt(dword);
  1016. }
  1017. public void reset() {
  1018. super.reset();
  1019. stream = null;
  1020. }
  1021. private int getCompressionType(String typeString) {
  1022. for (int i = 0; i < BMPConstants.compressionTypeNames.length; i++)
  1023. if (BMPConstants.compressionTypeNames[i].equals(typeString))
  1024. return i;
  1025. return 0;
  1026. }
  1027. private void writeEmbedded(IIOImage image,
  1028. ImageWriteParam bmpParam) throws IOException {
  1029. String format =
  1030. compressionType == BMPConstants.BI_JPEG ? "jpeg" : "png";
  1031. Iterator iterator = ImageIO.getImageWritersByFormatName(format);
  1032. ImageWriter writer = null;
  1033. if (iterator.hasNext())
  1034. writer = (ImageWriter)iterator.next();
  1035. if (writer != null) {
  1036. if (embedded_stream == null) {
  1037. throw new RuntimeException("No stream for writing embedded image!");
  1038. }
  1039. writer.addIIOWriteProgressListener(new IIOWriteProgressAdapter() {
  1040. public void imageProgress(ImageWriter source, float percentageDone) {
  1041. processImageProgress(percentageDone);
  1042. }
  1043. });
  1044. writer.addIIOWriteWarningListener(new IIOWriteWarningListener() {
  1045. public void warningOccurred(ImageWriter source, int imageIndex, String warning) {
  1046. processWarningOccurred(imageIndex, warning);
  1047. }
  1048. });
  1049. writer.setOutput(ImageIO.createImageOutputStream(embedded_stream));
  1050. ImageWriteParam param = writer.getDefaultWriteParam();
  1051. //param.setDestinationBands(bmpParam.getDestinationBands());
  1052. param.setDestinationOffset(bmpParam.getDestinationOffset());
  1053. param.setSourceBands(bmpParam.getSourceBands());
  1054. param.setSourceRegion(bmpParam.getSourceRegion());
  1055. param.setSourceSubsampling(bmpParam.getSourceXSubsampling(),
  1056. bmpParam.getSourceYSubsampling(),
  1057. bmpParam.getSubsamplingXOffset(),
  1058. bmpParam.getSubsamplingYOffset());
  1059. writer.write(null, image, param);
  1060. } else
  1061. throw new RuntimeException(I18N.getString("BMPImageWrite5") + " " + format);
  1062. }
  1063. private int firstLowBit(int num) {
  1064. int count = 0;
  1065. while ((num & 1) == 0) {
  1066. count++;
  1067. num >>>= 1;
  1068. }
  1069. return count;
  1070. }
  1071. private class IIOWriteProgressAdapter implements IIOWriteProgressListener {
  1072. public void imageComplete(ImageWriter source) {
  1073. }
  1074. public void imageProgress(ImageWriter source, float percentageDone) {
  1075. }
  1076. public void imageStarted(ImageWriter source, int imageIndex) {
  1077. }
  1078. public void thumbnailComplete(ImageWriter source) {
  1079. }
  1080. public void thumbnailProgress(ImageWriter source, float percentageDone) {
  1081. }
  1082. public void thumbnailStarted(ImageWriter source, int imageIndex, int thumbnailIndex) {
  1083. }
  1084. public void writeAborted(ImageWriter source) {
  1085. }
  1086. }
  1087. /*
  1088. * Returns preferred compression type for given image.
  1089. * The default compression type is BI_RGB, but some image types can't be
  1090. * encodeed with using default compression without cahnge color resolution.
  1091. * For example, TYPE_USHORT_565_RGB may be encodeed only by using BI_BITFIELDS
  1092. * compression type.
  1093. *
  1094. * NB: we probably need to extend this method if we encounter other image
  1095. * types which can not be encoded with BI_RGB compression type.
  1096. */
  1097. protected int getPreferredCompressionType(ColorModel cm, SampleModel sm) {
  1098. ImageTypeSpecifier imageType = new ImageTypeSpecifier(cm, sm);
  1099. return getPreferredCompressionType(imageType);
  1100. }
  1101. protected int getPreferredCompressionType(ImageTypeSpecifier imageType) {
  1102. if (imageType.getBufferedImageType() == BufferedImage.TYPE_USHORT_565_RGB) {
  1103. return BI_BITFIELDS;
  1104. }
  1105. return BI_RGB;
  1106. }
  1107. /*
  1108. * Check whether we can encode image of given type using compression method in question.
  1109. *
  1110. * For example, TYPE_USHORT_565_RGB can be encodeed with BI_BITFIELDS compression only.
  1111. *
  1112. * NB: method should be extended if other cases when we can not encode
  1113. * with given compression will be discovered.
  1114. */
  1115. protected boolean canEncodeImage(int compression, ColorModel cm, SampleModel sm) {
  1116. ImageTypeSpecifier imgType = new ImageTypeSpecifier(cm, sm);
  1117. return canEncodeImage(compression, imgType);
  1118. }
  1119. protected boolean canEncodeImage(int compression, ImageTypeSpecifier imgType) {
  1120. ImageWriterSpi spi = this.getOriginatingProvider();
  1121. if (!spi.canEncodeImage(imgType)) {
  1122. return false;
  1123. }
  1124. int biType = imgType.getBufferedImageType();
  1125. if (biType == BufferedImage.TYPE_USHORT_565_RGB
  1126. && compression != BI_BITFIELDS) {
  1127. return false;
  1128. }
  1129. int bpp = imgType.getColorModel().getPixelSize();
  1130. if (compressionType == BI_RLE4 && bpp != 4) {
  1131. // only 4bpp images can be encoded as BI_RLE4
  1132. return false;
  1133. }
  1134. if (compressionType == BI_RLE8 && bpp != 8) {
  1135. // only 8bpp images can be encoded as BI_RLE8
  1136. return false;
  1137. }
  1138. return true;
  1139. }
  1140. }