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 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 /** 18 * @author Rustem V. Rafikov 19 * @version $Revision: 1.3 $ 20 */ 21 package org.apache.harmony.x.imageio.plugins.jpeg; 22 23 import com.android.internal.awt.ImageOutputStreamWrapper; 24 25 import javax.imageio.ImageWriter; 26 import javax.imageio.IIOImage; 27 import javax.imageio.ImageTypeSpecifier; 28 import javax.imageio.ImageWriteParam; 29 import javax.imageio.plugins.jpeg.JPEGImageWriteParam; 30 import javax.imageio.stream.ImageOutputStream; 31 import javax.imageio.spi.ImageWriterSpi; 32 import javax.imageio.metadata.IIOMetadata; 33 34 import android.graphics.Bitmap; 35 import android.graphics.BitmapFactory; 36 import android.graphics.Bitmap.CompressFormat; 37 38 import java.io.File; 39 import java.io.FileOutputStream; 40 import java.io.IOException; 41 import java.awt.image.*; 42 import java.awt.*; 43 import java.awt.color.ColorSpace; 44 45 /** 46 * @author Rustem V. Rafikov 47 * @version $Revision: 1.3 $ 48 */ 49 public class JPEGImageWriter extends ImageWriter { 50 51 // /* ???AWT: Debugging 52 private static final boolean DEBUG = false; 53 private static Bitmap bm; 54 public static Bitmap getBitmap() { 55 return bm; 56 } 57 private static BufferedImage bufImg; 58 public static BufferedImage getBufImage() { 59 return bufImg; 60 } 61 static private RenderedImage renImg; 62 static public RenderedImage getRenImage() { 63 return renImg; 64 } 65 // */ 66 67 private long cinfo; 68 private RenderedImage image; 69 private Raster sourceRaster; 70 private WritableRaster scanRaster; 71 private int srcXOff = 0; 72 private int srcYOff = 0; 73 private int srcWidth; 74 private int srcHeight; 75 76 //-- y step for image subsampling 77 private int deltaY = 1; 78 //-- x step for image subsampling 79 private int deltaX = 1; 80 81 private ImageOutputStream ios; 82 83 public JPEGImageWriter(ImageWriterSpi imageWriterSpi) { 84 super(imageWriterSpi); 85 //???AWT: cinfo = initCompressionObj(); 86 cinfo = System.currentTimeMillis(); 87 } 88 89 static { 90 //???AWT 91 /* 92 System.loadLibrary("jpegencoder"); 93 initWriterIds(ImageOutputStream.class); 94 */ 95 } 96 97 @Override 98 public void write(IIOMetadata iioMetadata, IIOImage iioImage, ImageWriteParam param) 99 throws IOException { 100 101 if (ios == null) { 102 throw new IllegalArgumentException("ios == null"); 103 } 104 if (iioImage == null) { 105 throw new IllegalArgumentException("Image equals null"); 106 } 107 108 RenderedImage img = null; 109 if (!iioImage.hasRaster()) { 110 img = iioImage.getRenderedImage(); 111 if (img instanceof BufferedImage) { 112 sourceRaster = ((BufferedImage) img).getRaster(); 113 } else { 114 sourceRaster = img.getData(); 115 } 116 } else { 117 sourceRaster = iioImage.getRaster(); 118 } 119 120 // AWT???: Debugging 121 if (DEBUG) { 122 if( img==null ) { 123 System.out.println("****J: Image is NULL"); 124 } else { 125 renImg = img; 126 bufImg = (BufferedImage)img; 127 } 128 } 129 130 int numBands = sourceRaster.getNumBands(); 131 int sourceIJGCs = img == null ? JPEGConsts.JCS_UNKNOW : getSourceCSType(img); 132 133 srcWidth = sourceRaster.getWidth(); 134 srcHeight = sourceRaster.getHeight(); 135 136 int destWidth = srcWidth; 137 int destHeight = srcHeight; 138 139 boolean progressive = false; 140 141 if (param != null) { 142 Rectangle reg = param.getSourceRegion(); 143 if (reg != null) { 144 srcXOff = reg.x; 145 srcYOff = reg.y; 146 147 srcWidth = reg.width + srcXOff > srcWidth 148 ? srcWidth - srcXOff 149 : reg.width; 150 srcHeight = reg.height + srcYOff > srcHeight 151 ? srcHeight - srcYOff 152 : reg.height; 153 } 154 155 //-- TODO uncomment when JPEGImageWriteParam be implemented 156 //-- Only default progressive mode yet 157 // progressive = param.getProgressiveMode() == ImageWriteParam.MODE_DEFAULT; 158 159 //-- def is 1 160 deltaX = param.getSourceXSubsampling(); 161 deltaY = param.getSourceYSubsampling(); 162 163 //-- def is 0 164 int offsetX = param.getSubsamplingXOffset(); 165 int offsetY = param.getSubsamplingYOffset(); 166 167 srcXOff += offsetX; 168 srcYOff += offsetY; 169 srcWidth -= offsetX; 170 srcHeight -= offsetY; 171 172 destWidth = (srcWidth + deltaX - 1) / deltaX; 173 destHeight = (srcHeight + deltaY - 1) / deltaY; 174 } 175 176 //-- default DQTs (see JPEGQTable java doc and JPEG spec K1 & K2 tables) 177 //-- at http://www.w3.org/Graphics/JPEG/itu-t81.pdf 178 //-- Only figuring out how to set DQT in IJG library for future metadata 179 //-- support. IJG def tables are the same. 180 //JPEGQTable[] dqt = new JPEGQTable[2]; 181 // int[][] dqt = null; 182 // int[][] dqt = new int[2][]; 183 // dqt[0] = JPEGQTable.K1Div2Luminance.getTable(); 184 // dqt[1] = JPEGQTable.K2Div2Chrominance.getTable(); 185 186 //???AWT: I think we don't need this amymore 187 /* 188 //-- using default color space 189 //-- TODO: Take destination cs from param or use default if there is no cs 190 int destIJGCs = img == null ? JPEGConsts.JCS_UNKNOW : getDestinationCSType(img); 191 192 DataBufferByte dbuffer = new DataBufferByte(numBands * srcWidth); 193 194 scanRaster = Raster.createInterleavedRaster(dbuffer, srcWidth, 1, 195 numBands * srcWidth, numBands, JPEGConsts.BAND_OFFSETS[numBands], null); 196 197 encode(dbuffer.getData(), srcWidth, destWidth, destHeight, deltaX, 198 sourceIJGCs, destIJGCs, numBands, progressive, 199 null, cinfo); 200 */ 201 202 SampleModel model = sourceRaster.getSampleModel(); 203 204 if (model instanceof SinglePixelPackedSampleModel) { 205 DataBufferInt ibuf = (DataBufferInt)sourceRaster.getDataBuffer(); 206 int[] pixels = ibuf.getData(); 207 208 // Create a bitmap with the pixel 209 bm = Bitmap.createBitmap(pixels, srcWidth, srcHeight, Bitmap.Config.ARGB_8888); 210 211 // Use Bitmap.compress() to write the image 212 ImageOutputStreamWrapper iosw = new ImageOutputStreamWrapper(ios); 213 bm.compress(CompressFormat.JPEG, 100, iosw); 214 } else { 215 // ???AWT: Add support for other color models 216 throw new RuntimeException("Color model not supported yet"); 217 } 218 219 } 220 221 @Override 222 public void dispose() { 223 super.dispose(); 224 if (cinfo != 0) { 225 //???AWT: dispose(cinfo); 226 cinfo = 0; 227 ios = null; 228 } 229 } 230 231 232 public IIOMetadata getDefaultStreamMetadata(ImageWriteParam imageWriteParam) { 233 throw new UnsupportedOperationException("not supported yet"); 234 } 235 236 public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageTypeSpecifier, ImageWriteParam imageWriteParam) { 237 throw new UnsupportedOperationException("not supported yet"); 238 } 239 240 @Override 241 public IIOMetadata convertStreamMetadata(IIOMetadata iioMetadata, ImageWriteParam imageWriteParam) { 242 throw new UnsupportedOperationException("not supported yet"); 243 } 244 245 @Override 246 public IIOMetadata convertImageMetadata(IIOMetadata iioMetadata, ImageTypeSpecifier imageTypeSpecifier, ImageWriteParam imageWriteParam) { 247 throw new UnsupportedOperationException("not supported yet"); 248 } 249 250 @Override 251 public void setOutput(Object output) { 252 super.setOutput(output); 253 ios = (ImageOutputStream) output; 254 //???AWT: setIOS(ios, cinfo); 255 sourceRaster = null; 256 scanRaster = null; 257 srcXOff = 0; 258 srcYOff = 0; 259 srcWidth = 0; 260 srcHeight = 0; 261 deltaY = 1; 262 } 263 264 /** 265 * Frees resources 266 * @param structPointer 267 */ 268 //???AWT: private native void dispose(long structPointer); 269 270 /** 271 * Inits methods Ids for native to java callbacks 272 * @param iosClass 273 */ 274 //???AWT: private native static void initWriterIds(Class<ImageOutputStream> iosClass); 275 276 /** 277 * Inits compression objects 278 * @return pointer to the native structure 279 */ 280 //???AWT: private native long initCompressionObj(); 281 282 /** 283 * Sets image output stream in IJG layer 284 * @param stream 285 */ 286 //???AWT: private native void setIOS(ImageOutputStream stream, long structPointer); 287 288 /** 289 * Runs encoding process. 290 * 291 * @param data image data buffer to encode 292 * @param srcWidth - source width 293 * @param width - destination width 294 * @param height destination height 295 * @param deltaX - x subsampling step 296 * @param inColorSpace - original color space 297 * @param outColorSpace - destination color space 298 * @param numBands - number of bands 299 * @param cinfo - native handler 300 * @return 301 */ 302 //???AWT: 303 /* 304 private native boolean encode(byte[] data, int srcWidth, 305 int width, int height, int deltaX, 306 int inColorSpace, int outColorSpace, 307 int numBands, boolean progressive, 308 int[][] dqt, 309 long cinfo); 310 */ 311 312 /** 313 * Callback for getting a next scanline 314 * @param scanline scan line number 315 */ 316 @SuppressWarnings("unused") 317 private void getScanLine(int scanline) { 318 //-- TODO: processImageProgress in ImageWriter 319 Raster child = sourceRaster.createChild(srcXOff, 320 srcYOff + scanline * deltaY, srcWidth, 1, 0, 0, null); 321 322 scanRaster.setRect(child); 323 } 324 325 /** 326 * Maps color space types to IJG color spaces 327 * @param image 328 * @return 329 */ 330 private int getSourceCSType(RenderedImage image) { 331 int type = JPEGConsts.JCS_UNKNOW; 332 ColorModel cm = image.getColorModel(); 333 334 if (null == cm) { 335 return type; 336 } 337 338 if (cm instanceof IndexColorModel) { 339 throw new UnsupportedOperationException("IndexColorModel is not supported yet"); 340 } 341 342 boolean hasAlpha = cm.hasAlpha(); 343 ColorSpace cs = cm.getColorSpace(); 344 switch(cs.getType()) { 345 case ColorSpace.TYPE_GRAY: 346 type = JPEGConsts.JCS_GRAYSCALE; 347 break; 348 case ColorSpace.TYPE_RGB: 349 type = hasAlpha ? JPEGConsts.JCS_RGBA : JPEGConsts.JCS_RGB; 350 break; 351 case ColorSpace.TYPE_YCbCr: 352 type = hasAlpha ? JPEGConsts.JCS_YCbCrA : JPEGConsts.JCS_YCbCr; 353 break; 354 case ColorSpace.TYPE_3CLR: 355 type = hasAlpha ? JPEGConsts.JCS_YCCA : JPEGConsts.JCS_YCC; 356 break; 357 case ColorSpace.TYPE_CMYK: 358 type = JPEGConsts.JCS_CMYK; 359 break; 360 } 361 return type; 362 } 363 364 /** 365 * Returns destination color space. 366 * (YCbCr[A] for RGB) 367 * 368 * @param image 369 * @return 370 */ 371 private int getDestinationCSType(RenderedImage image) { 372 int type = JPEGConsts.JCS_UNKNOW; 373 ColorModel cm = image.getColorModel(); 374 if (null != cm) { 375 boolean hasAlpha = cm.hasAlpha(); 376 ColorSpace cs = cm.getColorSpace(); 377 378 switch(cs.getType()) { 379 case ColorSpace.TYPE_GRAY: 380 type = JPEGConsts.JCS_GRAYSCALE; 381 break; 382 case ColorSpace.TYPE_RGB: 383 type = hasAlpha ? JPEGConsts.JCS_YCbCrA : JPEGConsts.JCS_YCbCr; 384 break; 385 case ColorSpace.TYPE_YCbCr: 386 type = hasAlpha ? JPEGConsts.JCS_YCbCrA : JPEGConsts.JCS_YCbCr; 387 break; 388 case ColorSpace.TYPE_3CLR: 389 type = hasAlpha ? JPEGConsts.JCS_YCCA : JPEGConsts.JCS_YCC; 390 break; 391 case ColorSpace.TYPE_CMYK: 392 type = JPEGConsts.JCS_CMYK; 393 break; 394 } 395 } 396 return type; 397 } 398 399 public ImageWriteParam getDefaultWriteParam() { 400 return new JPEGImageWriteParam(getLocale()); 401 } 402 } 403