/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @author Rustem V. Rafikov * @version $Revision: 1.3 $ */ package org.apache.harmony.x.imageio.plugins.jpeg; import com.android.internal.awt.ImageOutputStreamWrapper; import javax.imageio.ImageWriter; import javax.imageio.IIOImage; import javax.imageio.ImageTypeSpecifier; import javax.imageio.ImageWriteParam; import javax.imageio.plugins.jpeg.JPEGImageWriteParam; import javax.imageio.stream.ImageOutputStream; import javax.imageio.spi.ImageWriterSpi; import javax.imageio.metadata.IIOMetadata; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Bitmap.CompressFormat; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.awt.image.*; import java.awt.*; import java.awt.color.ColorSpace; /** * @author Rustem V. Rafikov * @version $Revision: 1.3 $ */ public class JPEGImageWriter extends ImageWriter { // /* ???AWT: Debugging private static final boolean DEBUG = false; private static Bitmap bm; public static Bitmap getBitmap() { return bm; } private static BufferedImage bufImg; public static BufferedImage getBufImage() { return bufImg; } static private RenderedImage renImg; static public RenderedImage getRenImage() { return renImg; } // */ private long cinfo; private RenderedImage image; private Raster sourceRaster; private WritableRaster scanRaster; private int srcXOff = 0; private int srcYOff = 0; private int srcWidth; private int srcHeight; //-- y step for image subsampling private int deltaY = 1; //-- x step for image subsampling private int deltaX = 1; private ImageOutputStream ios; public JPEGImageWriter(ImageWriterSpi imageWriterSpi) { super(imageWriterSpi); //???AWT: cinfo = initCompressionObj(); cinfo = System.currentTimeMillis(); } static { //???AWT /* System.loadLibrary("jpegencoder"); initWriterIds(ImageOutputStream.class); */ } @Override public void write(IIOMetadata iioMetadata, IIOImage iioImage, ImageWriteParam param) throws IOException { if (ios == null) { throw new IllegalArgumentException("ios == null"); } if (iioImage == null) { throw new IllegalArgumentException("Image equals null"); } RenderedImage img = null; if (!iioImage.hasRaster()) { img = iioImage.getRenderedImage(); if (img instanceof BufferedImage) { sourceRaster = ((BufferedImage) img).getRaster(); } else { sourceRaster = img.getData(); } } else { sourceRaster = iioImage.getRaster(); } // AWT???: Debugging if (DEBUG) { if( img==null ) { System.out.println("****J: Image is NULL"); } else { renImg = img; bufImg = (BufferedImage)img; } } int numBands = sourceRaster.getNumBands(); int sourceIJGCs = img == null ? JPEGConsts.JCS_UNKNOW : getSourceCSType(img); srcWidth = sourceRaster.getWidth(); srcHeight = sourceRaster.getHeight(); int destWidth = srcWidth; int destHeight = srcHeight; boolean progressive = false; if (param != null) { Rectangle reg = param.getSourceRegion(); if (reg != null) { srcXOff = reg.x; srcYOff = reg.y; srcWidth = reg.width + srcXOff > srcWidth ? srcWidth - srcXOff : reg.width; srcHeight = reg.height + srcYOff > srcHeight ? srcHeight - srcYOff : reg.height; } //-- TODO uncomment when JPEGImageWriteParam be implemented //-- Only default progressive mode yet // progressive = param.getProgressiveMode() == ImageWriteParam.MODE_DEFAULT; //-- def is 1 deltaX = param.getSourceXSubsampling(); deltaY = param.getSourceYSubsampling(); //-- def is 0 int offsetX = param.getSubsamplingXOffset(); int offsetY = param.getSubsamplingYOffset(); srcXOff += offsetX; srcYOff += offsetY; srcWidth -= offsetX; srcHeight -= offsetY; destWidth = (srcWidth + deltaX - 1) / deltaX; destHeight = (srcHeight + deltaY - 1) / deltaY; } //-- default DQTs (see JPEGQTable java doc and JPEG spec K1 & K2 tables) //-- at http://www.w3.org/Graphics/JPEG/itu-t81.pdf //-- Only figuring out how to set DQT in IJG library for future metadata //-- support. IJG def tables are the same. //JPEGQTable[] dqt = new JPEGQTable[2]; // int[][] dqt = null; // int[][] dqt = new int[2][]; // dqt[0] = JPEGQTable.K1Div2Luminance.getTable(); // dqt[1] = JPEGQTable.K2Div2Chrominance.getTable(); //???AWT: I think we don't need this amymore /* //-- using default color space //-- TODO: Take destination cs from param or use default if there is no cs int destIJGCs = img == null ? JPEGConsts.JCS_UNKNOW : getDestinationCSType(img); DataBufferByte dbuffer = new DataBufferByte(numBands * srcWidth); scanRaster = Raster.createInterleavedRaster(dbuffer, srcWidth, 1, numBands * srcWidth, numBands, JPEGConsts.BAND_OFFSETS[numBands], null); encode(dbuffer.getData(), srcWidth, destWidth, destHeight, deltaX, sourceIJGCs, destIJGCs, numBands, progressive, null, cinfo); */ SampleModel model = sourceRaster.getSampleModel(); if (model instanceof SinglePixelPackedSampleModel) { DataBufferInt ibuf = (DataBufferInt)sourceRaster.getDataBuffer(); int[] pixels = ibuf.getData(); // Create a bitmap with the pixel bm = Bitmap.createBitmap(pixels, srcWidth, srcHeight, Bitmap.Config.ARGB_8888); // Use Bitmap.compress() to write the image ImageOutputStreamWrapper iosw = new ImageOutputStreamWrapper(ios); bm.compress(CompressFormat.JPEG, 100, iosw); } else { // ???AWT: Add support for other color models throw new RuntimeException("Color model not supported yet"); } } @Override public void dispose() { super.dispose(); if (cinfo != 0) { //???AWT: dispose(cinfo); cinfo = 0; ios = null; } } public IIOMetadata getDefaultStreamMetadata(ImageWriteParam imageWriteParam) { throw new UnsupportedOperationException("not supported yet"); } public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageTypeSpecifier, ImageWriteParam imageWriteParam) { throw new UnsupportedOperationException("not supported yet"); } @Override public IIOMetadata convertStreamMetadata(IIOMetadata iioMetadata, ImageWriteParam imageWriteParam) { throw new UnsupportedOperationException("not supported yet"); } @Override public IIOMetadata convertImageMetadata(IIOMetadata iioMetadata, ImageTypeSpecifier imageTypeSpecifier, ImageWriteParam imageWriteParam) { throw new UnsupportedOperationException("not supported yet"); } @Override public void setOutput(Object output) { super.setOutput(output); ios = (ImageOutputStream) output; //???AWT: setIOS(ios, cinfo); sourceRaster = null; scanRaster = null; srcXOff = 0; srcYOff = 0; srcWidth = 0; srcHeight = 0; deltaY = 1; } /** * Frees resources * @param structPointer */ //???AWT: private native void dispose(long structPointer); /** * Inits methods Ids for native to java callbacks * @param iosClass */ //???AWT: private native static void initWriterIds(Class<ImageOutputStream> iosClass); /** * Inits compression objects * @return pointer to the native structure */ //???AWT: private native long initCompressionObj(); /** * Sets image output stream in IJG layer * @param stream */ //???AWT: private native void setIOS(ImageOutputStream stream, long structPointer); /** * Runs encoding process. * * @param data image data buffer to encode * @param srcWidth - source width * @param width - destination width * @param height destination height * @param deltaX - x subsampling step * @param inColorSpace - original color space * @param outColorSpace - destination color space * @param numBands - number of bands * @param cinfo - native handler * @return */ //???AWT: /* private native boolean encode(byte[] data, int srcWidth, int width, int height, int deltaX, int inColorSpace, int outColorSpace, int numBands, boolean progressive, int[][] dqt, long cinfo); */ /** * Callback for getting a next scanline * @param scanline scan line number */ @SuppressWarnings("unused") private void getScanLine(int scanline) { //-- TODO: processImageProgress in ImageWriter Raster child = sourceRaster.createChild(srcXOff, srcYOff + scanline * deltaY, srcWidth, 1, 0, 0, null); scanRaster.setRect(child); } /** * Maps color space types to IJG color spaces * @param image * @return */ private int getSourceCSType(RenderedImage image) { int type = JPEGConsts.JCS_UNKNOW; ColorModel cm = image.getColorModel(); if (null == cm) { return type; } if (cm instanceof IndexColorModel) { throw new UnsupportedOperationException("IndexColorModel is not supported yet"); } boolean hasAlpha = cm.hasAlpha(); ColorSpace cs = cm.getColorSpace(); switch(cs.getType()) { case ColorSpace.TYPE_GRAY: type = JPEGConsts.JCS_GRAYSCALE; break; case ColorSpace.TYPE_RGB: type = hasAlpha ? JPEGConsts.JCS_RGBA : JPEGConsts.JCS_RGB; break; case ColorSpace.TYPE_YCbCr: type = hasAlpha ? JPEGConsts.JCS_YCbCrA : JPEGConsts.JCS_YCbCr; break; case ColorSpace.TYPE_3CLR: type = hasAlpha ? JPEGConsts.JCS_YCCA : JPEGConsts.JCS_YCC; break; case ColorSpace.TYPE_CMYK: type = JPEGConsts.JCS_CMYK; break; } return type; } /** * Returns destination color space. * (YCbCr[A] for RGB) * * @param image * @return */ private int getDestinationCSType(RenderedImage image) { int type = JPEGConsts.JCS_UNKNOW; ColorModel cm = image.getColorModel(); if (null != cm) { boolean hasAlpha = cm.hasAlpha(); ColorSpace cs = cm.getColorSpace(); switch(cs.getType()) { case ColorSpace.TYPE_GRAY: type = JPEGConsts.JCS_GRAYSCALE; break; case ColorSpace.TYPE_RGB: type = hasAlpha ? JPEGConsts.JCS_YCbCrA : JPEGConsts.JCS_YCbCr; break; case ColorSpace.TYPE_YCbCr: type = hasAlpha ? JPEGConsts.JCS_YCbCrA : JPEGConsts.JCS_YCbCr; break; case ColorSpace.TYPE_3CLR: type = hasAlpha ? JPEGConsts.JCS_YCCA : JPEGConsts.JCS_YCC; break; case ColorSpace.TYPE_CMYK: type = JPEGConsts.JCS_CMYK; break; } } return type; } public ImageWriteParam getDefaultWriteParam() { return new JPEGImageWriteParam(getLocale()); } }