1. /*
  2. * @(#)WBMPImageWriter.java 1.3 03/09/20 21:28:34
  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.wbmp;
  8. import java.awt.Point;
  9. import java.awt.Rectangle;
  10. import java.awt.image.ColorModel;
  11. import java.awt.image.DataBuffer;
  12. import java.awt.image.DataBufferByte;
  13. import java.awt.image.IndexColorModel;
  14. import java.awt.image.MultiPixelPackedSampleModel;
  15. import java.awt.image.Raster;
  16. import java.awt.image.RenderedImage;
  17. import java.awt.image.SampleModel;
  18. import java.awt.image.WritableRaster;
  19. import java.io.IOException;
  20. import javax.imageio.IIOImage;
  21. import javax.imageio.IIOException;
  22. import javax.imageio.ImageTypeSpecifier;
  23. import javax.imageio.ImageWriteParam;
  24. import javax.imageio.ImageWriter;
  25. import javax.imageio.metadata.IIOMetadata;
  26. import javax.imageio.metadata.IIOMetadataFormatImpl;
  27. import javax.imageio.metadata.IIOInvalidTreeException;
  28. import javax.imageio.spi.ImageWriterSpi;
  29. import javax.imageio.stream.ImageOutputStream;
  30. import com.sun.imageio.plugins.common.I18N;
  31. /**
  32. * The Java Image IO plugin writer for encoding a binary RenderedImage into
  33. * a WBMP format.
  34. *
  35. * The encoding process may clip, subsample using the parameters
  36. * specified in the <code>ImageWriteParam</code>.
  37. *
  38. * @see com.sun.media.imageio.plugins.WBMPImageWriteParam
  39. */
  40. public class WBMPImageWriter extends ImageWriter {
  41. /** The output stream to write into */
  42. private ImageOutputStream stream = null;
  43. // Get the number of bits required to represent an int.
  44. private static int getNumBits(int intValue) {
  45. int numBits = 32;
  46. int mask = 0x80000000;
  47. while(mask != 0 && (intValue & mask) == 0) {
  48. numBits--;
  49. mask >>>= 1;
  50. }
  51. return numBits;
  52. }
  53. // Convert an int value to WBMP multi-byte format.
  54. private static byte[] intToMultiByte(int intValue) {
  55. int numBitsLeft = getNumBits(intValue);
  56. byte[] multiBytes = new byte[(numBitsLeft + 6)/7];
  57. int maxIndex = multiBytes.length - 1;
  58. for(int b = 0; b <= maxIndex; b++) {
  59. multiBytes[b] = (byte)((intValue >>> ((maxIndex - b)*7))&0x7f);
  60. if(b != maxIndex) {
  61. multiBytes[b] |= (byte)0x80;
  62. }
  63. }
  64. return multiBytes;
  65. }
  66. /** Constructs <code>WBMPImageWriter</code> based on the provided
  67. * <code>ImageWriterSpi</code>.
  68. */
  69. public WBMPImageWriter(ImageWriterSpi originator) {
  70. super(originator);
  71. }
  72. public void setOutput(Object output) {
  73. super.setOutput(output); // validates output
  74. if (output != null) {
  75. if (!(output instanceof ImageOutputStream))
  76. throw new IllegalArgumentException(I18N.getString("WBMPImageWriter"));
  77. this.stream = (ImageOutputStream)output;
  78. } else
  79. this.stream = null;
  80. }
  81. public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
  82. return null;
  83. }
  84. public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType,
  85. ImageWriteParam param) {
  86. WBMPMetadata meta = new WBMPMetadata();
  87. meta.wbmpType = 0; // default wbmp level
  88. return meta;
  89. }
  90. public IIOMetadata convertStreamMetadata(IIOMetadata inData,
  91. ImageWriteParam param) {
  92. return null;
  93. }
  94. public IIOMetadata convertImageMetadata(IIOMetadata metadata,
  95. ImageTypeSpecifier type,
  96. ImageWriteParam param) {
  97. return null;
  98. }
  99. public boolean canWriteRasters() {
  100. return true;
  101. }
  102. public void write(IIOMetadata streamMetadata,
  103. IIOImage image,
  104. ImageWriteParam param) throws IOException {
  105. if (stream == null) {
  106. throw new IllegalStateException(I18N.getString("WBMPImageWriter3"));
  107. }
  108. if (image == null) {
  109. throw new IllegalArgumentException(I18N.getString("WBMPImageWriter4"));
  110. }
  111. clearAbortRequest();
  112. processImageStarted(0);
  113. if (param == null)
  114. param = getDefaultWriteParam();
  115. RenderedImage input = null;
  116. Raster inputRaster = null;
  117. boolean writeRaster = image.hasRaster();
  118. Rectangle sourceRegion = param.getSourceRegion();
  119. SampleModel sampleModel = null;
  120. if (writeRaster) {
  121. inputRaster = image.getRaster();
  122. sampleModel = inputRaster.getSampleModel();
  123. } else {
  124. input = image.getRenderedImage();
  125. sampleModel = input.getSampleModel();
  126. inputRaster = input.getData();
  127. }
  128. checkSampleModel(sampleModel);
  129. if (sourceRegion == null)
  130. sourceRegion = inputRaster.getBounds();
  131. else
  132. sourceRegion = sourceRegion.intersection(inputRaster.getBounds());
  133. if (sourceRegion.isEmpty())
  134. throw new RuntimeException(I18N.getString("WBMPImageWriter1"));
  135. int scaleX = param.getSourceXSubsampling();
  136. int scaleY = param.getSourceYSubsampling();
  137. int xOffset = param.getSubsamplingXOffset();
  138. int yOffset = param.getSubsamplingYOffset();
  139. sourceRegion.translate(xOffset, yOffset);
  140. sourceRegion.width -= xOffset;
  141. sourceRegion.height -= yOffset;
  142. int minX = sourceRegion.x / scaleX;
  143. int minY = sourceRegion.y / scaleY;
  144. int w = (sourceRegion.width + scaleX - 1) / scaleX;
  145. int h = (sourceRegion.height + scaleY - 1) / scaleY;
  146. Rectangle destinationRegion = new Rectangle(minX, minY, w, h);
  147. sampleModel = sampleModel.createCompatibleSampleModel(w, h);
  148. SampleModel destSM= sampleModel;
  149. // If the data are not formatted nominally then reformat.
  150. if(sampleModel.getDataType() != DataBuffer.TYPE_BYTE ||
  151. !(sampleModel instanceof MultiPixelPackedSampleModel) ||
  152. ((MultiPixelPackedSampleModel)sampleModel).getDataBitOffset() != 0) {
  153. destSM =
  154. new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
  155. w, h, 1,
  156. w + 7 >> 3, 0);
  157. }
  158. if (!destinationRegion.equals(sourceRegion)) {
  159. if (scaleX == 1 && scaleY == 1)
  160. inputRaster = inputRaster.createChild(inputRaster.getMinX(),
  161. inputRaster.getMinY(),
  162. w, h, minX, minY, null);
  163. else {
  164. WritableRaster ras = Raster.createWritableRaster(destSM,
  165. new Point(minX, minY));
  166. byte[] data = ((DataBufferByte)ras.getDataBuffer()).getData();
  167. for(int j = minY, y = sourceRegion.y, k = 0;
  168. j < minY + h; j++, y += scaleY) {
  169. for (int i = 0, x = sourceRegion.x;
  170. i <w; i++, x +=scaleX) {
  171. int v = inputRaster.getSample(x, y, 0);
  172. data[k + (i >> 3)] |= v << (7 - (i & 7));
  173. }
  174. k += w + 7 >> 3;
  175. }
  176. inputRaster = ras;
  177. }
  178. }
  179. // If the data are not formatted nominally then reformat.
  180. if(!destSM.equals(inputRaster.getSampleModel())) {
  181. WritableRaster raster =
  182. Raster.createWritableRaster(destSM,
  183. new Point(inputRaster.getMinX(),
  184. inputRaster.getMinY()));
  185. raster.setRect(inputRaster);
  186. inputRaster = raster;
  187. }
  188. // Check whether the image is white-is-zero.
  189. boolean isWhiteZero = false;
  190. if(!writeRaster && input.getColorModel() instanceof IndexColorModel) {
  191. IndexColorModel icm = (IndexColorModel)input.getColorModel();
  192. isWhiteZero = icm.getRed(0) > icm.getRed(1);
  193. }
  194. // Get the line stride, bytes per row, and data array.
  195. int lineStride =
  196. ((MultiPixelPackedSampleModel)destSM).getScanlineStride();
  197. int bytesPerRow = (w + 7)/8;
  198. byte[] bdata = ((DataBufferByte)inputRaster.getDataBuffer()).getData();
  199. // Write WBMP header.
  200. stream.write(0); // TypeField
  201. stream.write(0); // FixHeaderField
  202. stream.write(intToMultiByte(w)); // width
  203. stream.write(intToMultiByte(h)); // height
  204. // Write the data.
  205. if(!isWhiteZero && lineStride == bytesPerRow) {
  206. // Write the entire image.
  207. stream.write(bdata, 0, h * bytesPerRow);
  208. processImageProgress(100.0F);
  209. } else {
  210. // Write the image row-by-row.
  211. int offset = 0;
  212. if(!isWhiteZero) {
  213. // Black-is-zero
  214. for(int row = 0; row < h; row++) {
  215. if (abortRequested())
  216. break;
  217. stream.write(bdata, offset, bytesPerRow);
  218. offset += lineStride;
  219. processImageProgress(100.0F * row / h);
  220. }
  221. } else {
  222. // White-is-zero: need to invert data.
  223. byte[] inverted = new byte[bytesPerRow];
  224. for(int row = 0; row < h; row++) {
  225. if (abortRequested())
  226. break;
  227. for(int col = 0; col < bytesPerRow; col++) {
  228. inverted[col] = (byte)(~(bdata[col+offset]));
  229. }
  230. stream.write(inverted, 0, bytesPerRow);
  231. offset += lineStride;
  232. processImageProgress(100.0F * row / h);
  233. }
  234. }
  235. }
  236. if (abortRequested())
  237. processWriteAborted();
  238. else {
  239. processImageComplete();
  240. stream.flushBefore(stream.getStreamPosition());
  241. }
  242. }
  243. public void reset() {
  244. super.reset();
  245. stream = null;
  246. }
  247. private void checkSampleModel(SampleModel sm) {
  248. int type = sm.getDataType();
  249. if (type < DataBuffer.TYPE_BYTE || type > DataBuffer.TYPE_INT
  250. || sm.getNumBands() != 1 || sm.getSampleSize(0) != 1)
  251. throw new IllegalArgumentException(I18N.getString("WBMPImageWriter2"));
  252. }
  253. }