| /* |
| * 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 Oleg V. Khaschansky |
| * @version $Revision$ |
| */ |
| package org.apache.harmony.awt.gl.color; |
| |
| import java.awt.image.BufferedImage; |
| import java.awt.image.ColorModel; |
| import java.awt.image.ComponentSampleModel; |
| import java.awt.image.DataBuffer; |
| import java.awt.image.Raster; |
| import java.awt.image.SampleModel; |
| import java.awt.image.SinglePixelPackedSampleModel; |
| import java.util.ArrayList; |
| |
| import org.apache.harmony.awt.gl.AwtImageBackdoorAccessor; |
| import org.apache.harmony.awt.internal.nls.Messages; |
| |
| |
| /** |
| * This class converts java color/sample models to the LCMS pixel formats. |
| * It also encapsulates all the information about the image format, which native CMM |
| * needs to have in order to read/write data. |
| * |
| * At present planar formats (multiple bands) are not supported |
| * and they are handled as a common (custom) case. |
| * Samples other than 1 - 7 bytes and multiple of 8 bits are |
| * also handled as custom (and won't be supported in the nearest future). |
| */ |
| class NativeImageFormat { |
| ////////////////////////////////////////////// |
| // LCMS Pixel types |
| private static final int PT_ANY = 0; // Don't check colorspace |
| // 1 & 2 are reserved |
| private static final int PT_GRAY = 3; |
| private static final int PT_RGB = 4; |
| // Skipping other since we don't use them here |
| /////////////////////////////////////////////// |
| |
| // Conversion of predefined BufferedImage formats to LCMS formats |
| private static final int INT_RGB_LCMS_FMT = |
| colorspaceSh(PT_RGB)| |
| extraSh(1)| |
| channelsSh(3)| |
| bytesSh(1)| |
| doswapSh(1)| |
| swapfirstSh(1); |
| |
| private static final int INT_ARGB_LCMS_FMT = INT_RGB_LCMS_FMT; |
| |
| private static final int INT_BGR_LCMS_FMT = |
| colorspaceSh(PT_RGB)| |
| extraSh(1)| |
| channelsSh(3)| |
| bytesSh(1); |
| |
| private static final int THREE_BYTE_BGR_LCMS_FMT = |
| colorspaceSh(PT_RGB)| |
| channelsSh(3)| |
| bytesSh(1)| |
| doswapSh(1); |
| |
| private static final int FOUR_BYTE_ABGR_LCMS_FMT = |
| colorspaceSh(PT_RGB)| |
| extraSh(1)| |
| channelsSh(3)| |
| bytesSh(1)| |
| doswapSh(1); |
| |
| private static final int BYTE_GRAY_LCMS_FMT = |
| colorspaceSh(PT_GRAY)| |
| channelsSh(1)| |
| bytesSh(1); |
| |
| private static final int USHORT_GRAY_LCMS_FMT = |
| colorspaceSh(PT_GRAY)| |
| channelsSh(1)| |
| bytesSh(2); |
| |
| // LCMS format packed into 32 bit value. For description |
| // of this format refer to LCMS documentation. |
| private int cmmFormat = 0; |
| |
| // Dimensions |
| private int rows = 0; |
| private int cols = 0; |
| |
| // Scanline may contain some padding in the end |
| private int scanlineStride = -1; |
| |
| private Object imageData; |
| // It's possible to have offset from the beginning of the array |
| private int dataOffset; |
| |
| // Has the image alpha channel? If has - here its band band offset goes |
| private int alphaOffset = -1; |
| |
| // initializes proper field IDs |
| private static native void initIDs(); |
| |
| static { |
| NativeCMM.loadCMM(); |
| initIDs(); |
| } |
| |
| //////////////////////////////////// |
| // LCMS image format encoders |
| //////////////////////////////////// |
| private static int colorspaceSh(int s) { |
| return (s << 16); |
| } |
| |
| private static int swapfirstSh(int s) { |
| return (s << 14); |
| } |
| |
| private static int flavorSh(int s) { |
| return (s << 13); |
| } |
| |
| private static int planarSh(int s) { |
| return (s << 12); |
| } |
| |
| private static int endianSh(int s) { |
| return (s << 11); |
| } |
| |
| private static int doswapSh(int s) { |
| return (s << 10); |
| } |
| |
| private static int extraSh(int s) { |
| return (s << 7); |
| } |
| |
| private static int channelsSh(int s) { |
| return (s << 3); |
| } |
| |
| private static int bytesSh(int s) { |
| return s; |
| } |
| //////////////////////////////////// |
| // End of LCMS image format encoders |
| //////////////////////////////////// |
| |
| // Accessors |
| Object getChannelData() { |
| return imageData; |
| } |
| |
| int getNumCols() { |
| return cols; |
| } |
| |
| int getNumRows() { |
| return rows; |
| } |
| |
| // Constructors |
| public NativeImageFormat() { |
| } |
| |
| /** |
| * Simple image layout for common case with |
| * not optimized workflow. |
| * |
| * For hifi colorspaces with 5+ color channels imgData |
| * should be <code>byte</code> array. |
| * |
| * For common colorspaces with up to 4 color channels it |
| * should be <code>short</code> array. |
| * |
| * Alpha channel is handled by caller, not by CMS. |
| * |
| * Color channels are in their natural order (not BGR but RGB). |
| * |
| * @param imgData - array of <code>byte</code> or <code>short</code> |
| * @param nChannels - number of channels |
| * @param nRows - number of scanlines in the image |
| * @param nCols - number of pixels in one row of the image |
| */ |
| public NativeImageFormat(Object imgData, int nChannels, int nRows, int nCols) { |
| if (imgData instanceof short[]) { |
| cmmFormat |= bytesSh(2); |
| } |
| else if (imgData instanceof byte[]) { |
| cmmFormat |= bytesSh(1); |
| } |
| else |
| // awt.47=First argument should be byte or short array |
| throw new IllegalArgumentException(Messages.getString("awt.47")); //$NON-NLS-1$ |
| |
| cmmFormat |= channelsSh(nChannels); |
| |
| rows = nRows; |
| cols = nCols; |
| |
| imageData = imgData; |
| |
| dataOffset = 0; |
| } |
| |
| /** |
| * Deduces image format from the buffered image type |
| * or color and sample models. |
| * @param bi - image |
| * @return image format object |
| */ |
| public static NativeImageFormat createNativeImageFormat(BufferedImage bi) { |
| NativeImageFormat fmt = new NativeImageFormat(); |
| |
| switch (bi.getType()) { |
| case BufferedImage.TYPE_INT_RGB: { |
| fmt.cmmFormat = INT_RGB_LCMS_FMT; |
| break; |
| } |
| |
| case BufferedImage.TYPE_INT_ARGB: |
| case BufferedImage.TYPE_INT_ARGB_PRE: { |
| fmt.cmmFormat = INT_ARGB_LCMS_FMT; |
| fmt.alphaOffset = 3; |
| break; |
| } |
| |
| case BufferedImage.TYPE_INT_BGR: { |
| fmt.cmmFormat = INT_BGR_LCMS_FMT; |
| break; |
| } |
| |
| case BufferedImage.TYPE_3BYTE_BGR: { |
| fmt.cmmFormat = THREE_BYTE_BGR_LCMS_FMT; |
| break; |
| } |
| |
| case BufferedImage.TYPE_4BYTE_ABGR_PRE: |
| case BufferedImage.TYPE_4BYTE_ABGR: { |
| fmt.cmmFormat = FOUR_BYTE_ABGR_LCMS_FMT; |
| fmt.alphaOffset = 0; |
| break; |
| } |
| |
| case BufferedImage.TYPE_BYTE_GRAY: { |
| fmt.cmmFormat = BYTE_GRAY_LCMS_FMT; |
| break; |
| } |
| |
| case BufferedImage.TYPE_USHORT_GRAY: { |
| fmt.cmmFormat = USHORT_GRAY_LCMS_FMT; |
| break; |
| } |
| |
| case BufferedImage.TYPE_BYTE_BINARY: |
| case BufferedImage.TYPE_USHORT_565_RGB: |
| case BufferedImage.TYPE_USHORT_555_RGB: |
| case BufferedImage.TYPE_BYTE_INDEXED: { |
| // A bunch of unsupported formats |
| return null; |
| } |
| |
| default: |
| break; // Try to look at sample model and color model |
| } |
| |
| |
| if (fmt.cmmFormat == 0) { |
| ColorModel cm = bi.getColorModel(); |
| SampleModel sm = bi.getSampleModel(); |
| |
| if (sm instanceof ComponentSampleModel) { |
| ComponentSampleModel csm = (ComponentSampleModel) sm; |
| fmt.cmmFormat = getFormatFromComponentModel(csm, cm.hasAlpha()); |
| fmt.scanlineStride = calculateScanlineStrideCSM(csm, bi.getRaster()); |
| } else if (sm instanceof SinglePixelPackedSampleModel) { |
| SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sm; |
| fmt.cmmFormat = getFormatFromSPPSampleModel(sppsm, cm.hasAlpha()); |
| fmt.scanlineStride = calculateScanlineStrideSPPSM(sppsm, bi.getRaster()); |
| } |
| |
| if (cm.hasAlpha()) |
| fmt.alphaOffset = calculateAlphaOffset(sm, bi.getRaster()); |
| } |
| |
| if (fmt.cmmFormat == 0) |
| return null; |
| |
| if (!fmt.setImageData(bi.getRaster().getDataBuffer())) { |
| return null; |
| } |
| |
| fmt.rows = bi.getHeight(); |
| fmt.cols = bi.getWidth(); |
| |
| fmt.dataOffset = bi.getRaster().getDataBuffer().getOffset(); |
| |
| return fmt; |
| } |
| |
| /** |
| * Deduces image format from the raster sample model. |
| * @param r - raster |
| * @return image format object |
| */ |
| public static NativeImageFormat createNativeImageFormat(Raster r) { |
| NativeImageFormat fmt = new NativeImageFormat(); |
| SampleModel sm = r.getSampleModel(); |
| |
| // Assume that there's no alpha |
| if (sm instanceof ComponentSampleModel) { |
| ComponentSampleModel csm = (ComponentSampleModel) sm; |
| fmt.cmmFormat = getFormatFromComponentModel(csm, false); |
| fmt.scanlineStride = calculateScanlineStrideCSM(csm, r); |
| } else if (sm instanceof SinglePixelPackedSampleModel) { |
| SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sm; |
| fmt.cmmFormat = getFormatFromSPPSampleModel(sppsm, false); |
| fmt.scanlineStride = calculateScanlineStrideSPPSM(sppsm, r); |
| } |
| |
| if (fmt.cmmFormat == 0) |
| return null; |
| |
| fmt.cols = r.getWidth(); |
| fmt.rows = r.getHeight(); |
| fmt.dataOffset = r.getDataBuffer().getOffset(); |
| |
| if (!fmt.setImageData(r.getDataBuffer())) |
| return null; |
| |
| return fmt; |
| } |
| |
| /** |
| * Obtains LCMS format from the component sample model |
| * @param sm - sample model |
| * @param hasAlpha - true if there's an alpha channel |
| * @return LCMS format |
| */ |
| private static int getFormatFromComponentModel(ComponentSampleModel sm, boolean hasAlpha) { |
| // Multiple data arrays (banks) not supported |
| int bankIndex = sm.getBankIndices()[0]; |
| for (int i=1; i < sm.getNumBands(); i++) { |
| if (sm.getBankIndices()[i] != bankIndex) { |
| return 0; |
| } |
| } |
| |
| int channels = hasAlpha ? sm.getNumBands()-1 : sm.getNumBands(); |
| int extra = hasAlpha ? 1 : 0; |
| int bytes = 1; |
| switch (sm.getDataType()) { |
| case DataBuffer.TYPE_BYTE: |
| bytes = 1; break; |
| case DataBuffer.TYPE_SHORT: |
| case DataBuffer.TYPE_USHORT: |
| bytes = 2; break; |
| case DataBuffer.TYPE_INT: |
| bytes = 4; break; |
| case DataBuffer.TYPE_DOUBLE: |
| bytes = 0; break; |
| default: |
| return 0; // Unsupported data type |
| } |
| |
| int doSwap = 0; |
| int swapFirst = 0; |
| boolean knownFormat = false; |
| |
| int i; |
| |
| // "RGBA" |
| for (i=0; i < sm.getNumBands(); i++) { |
| if (sm.getBandOffsets()[i] != i) break; |
| } |
| if (i == sm.getNumBands()) { // Ok, it is it |
| doSwap = 0; |
| swapFirst = 0; |
| knownFormat = true; |
| } |
| |
| // "ARGB" |
| if (!knownFormat) { |
| for (i=0; i < sm.getNumBands()-1; i++) { |
| if (sm.getBandOffsets()[i] != i+1) break; |
| } |
| if (sm.getBandOffsets()[i] == 0) i++; |
| if (i == sm.getNumBands()) { // Ok, it is it |
| doSwap = 0; |
| swapFirst = 1; |
| knownFormat = true; |
| } |
| } |
| |
| // "BGRA" |
| if (!knownFormat) { |
| for (i=0; i < sm.getNumBands()-1; i++) { |
| if (sm.getBandOffsets()[i] != sm.getNumBands() - 2 - i) break; |
| } |
| if (sm.getBandOffsets()[i] == sm.getNumBands()-1) i++; |
| if (i == sm.getNumBands()) { // Ok, it is it |
| doSwap = 1; |
| swapFirst = 1; |
| knownFormat = true; |
| } |
| } |
| |
| // "ABGR" |
| if (!knownFormat) { |
| for (i=0; i < sm.getNumBands(); i++) { |
| if (sm.getBandOffsets()[i] != sm.getNumBands() - 1 - i) break; |
| } |
| if (i == sm.getNumBands()) { // Ok, it is it |
| doSwap = 1; |
| swapFirst = 0; |
| knownFormat = true; |
| } |
| } |
| |
| // XXX - Planar formats are not supported yet |
| if (!knownFormat) |
| return 0; |
| |
| return |
| channelsSh(channels) | |
| bytesSh(bytes) | |
| extraSh(extra) | |
| doswapSh(doSwap) | |
| swapfirstSh(swapFirst); |
| } |
| |
| /** |
| * Obtains LCMS format from the single pixel packed sample model |
| * @param sm - sample model |
| * @param hasAlpha - true if there's an alpha channel |
| * @return LCMS format |
| */ |
| private static int getFormatFromSPPSampleModel(SinglePixelPackedSampleModel sm, |
| boolean hasAlpha) { |
| // Can we extract bytes? |
| int mask = sm.getBitMasks()[0] >>> sm.getBitOffsets()[0]; |
| if (!(mask == 0xFF || mask == 0xFFFF || mask == 0xFFFFFFFF)) |
| return 0; |
| |
| // All masks are same? |
| for (int i = 1; i < sm.getNumBands(); i++) { |
| if ((sm.getBitMasks()[i] >>> sm.getBitOffsets()[i]) != mask) |
| return 0; |
| } |
| |
| int pixelSize = 0; |
| // Check if data type is supported |
| if (sm.getDataType() == DataBuffer.TYPE_USHORT) |
| pixelSize = 2; |
| else if (sm.getDataType() == DataBuffer.TYPE_INT) |
| pixelSize = 4; |
| else |
| return 0; |
| |
| |
| int bytes = 0; |
| switch (mask) { |
| case 0xFF: |
| bytes = 1; |
| break; |
| case 0xFFFF: |
| bytes = 2; |
| break; |
| case 0xFFFFFFFF: |
| bytes = 4; |
| break; |
| default: return 0; |
| } |
| |
| |
| int channels = hasAlpha ? sm.getNumBands()-1 : sm.getNumBands(); |
| int extra = hasAlpha ? 1 : 0; |
| extra += pixelSize/bytes - sm.getNumBands(); // Unused bytes? |
| |
| // Form an ArrayList containing offset for each band |
| ArrayList<Integer> offsetsLst = new ArrayList<Integer>(); |
| for (int k=0; k < sm.getNumBands(); k++) { |
| offsetsLst.add(new Integer(sm.getBitOffsets()[k]/(bytes*8))); |
| } |
| |
| // Add offsets for unused space |
| for (int i=0; i<pixelSize/bytes; i++) { |
| if (offsetsLst.indexOf(new Integer(i)) < 0) |
| offsetsLst.add(new Integer(i)); |
| } |
| |
| int offsets[] = new int[pixelSize/bytes]; |
| for (int i=0; i<offsetsLst.size(); i++) { |
| offsets[i] = offsetsLst.get(i).intValue(); |
| } |
| |
| int doSwap = 0; |
| int swapFirst = 0; |
| boolean knownFormat = false; |
| |
| int i; |
| |
| // "RGBA" |
| for (i=0; i < pixelSize; i++) { |
| if (offsets[i] != i) break; |
| } |
| if (i == pixelSize) { // Ok, it is it |
| doSwap = 0; |
| swapFirst = 0; |
| knownFormat = true; |
| } |
| |
| // "ARGB" |
| if (!knownFormat) { |
| for (i=0; i < pixelSize-1; i++) { |
| if (offsets[i] != i+1) break; |
| } |
| if (offsets[i] == 0) i++; |
| if (i == pixelSize) { // Ok, it is it |
| doSwap = 0; |
| swapFirst = 1; |
| knownFormat = true; |
| } |
| } |
| |
| // "BGRA" |
| if (!knownFormat) { |
| for (i=0; i < pixelSize-1; i++) { |
| if (offsets[i] != pixelSize - 2 - i) break; |
| } |
| if (offsets[i] == pixelSize-1) i++; |
| if (i == pixelSize) { // Ok, it is it |
| doSwap = 1; |
| swapFirst = 1; |
| knownFormat = true; |
| } |
| } |
| |
| // "ABGR" |
| if (!knownFormat) { |
| for (i=0; i < pixelSize; i++) { |
| if (offsets[i] != pixelSize - 1 - i) break; |
| } |
| if (i == pixelSize) { // Ok, it is it |
| doSwap = 1; |
| swapFirst = 0; |
| knownFormat = true; |
| } |
| } |
| |
| // XXX - Planar formats are not supported yet |
| if (!knownFormat) |
| return 0; |
| |
| return |
| channelsSh(channels) | |
| bytesSh(bytes) | |
| extraSh(extra) | |
| doswapSh(doSwap) | |
| swapfirstSh(swapFirst); |
| } |
| |
| /** |
| * Obtains data array from the DataBuffer object |
| * @param db - data buffer |
| * @return - true if successful |
| */ |
| private boolean setImageData(DataBuffer db) { |
| AwtImageBackdoorAccessor dbAccess = AwtImageBackdoorAccessor.getInstance(); |
| try { |
| imageData = dbAccess.getData(db); |
| } catch (IllegalArgumentException e) { |
| return false; // Unknown data buffer type |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Calculates scanline stride in bytes |
| * @param csm - component sample model |
| * @param r - raster |
| * @return scanline stride in bytes |
| */ |
| private static int calculateScanlineStrideCSM(ComponentSampleModel csm, Raster r) { |
| if (csm.getScanlineStride() != csm.getPixelStride()*csm.getWidth()) { |
| int dataTypeSize = DataBuffer.getDataTypeSize(r.getDataBuffer().getDataType()) / 8; |
| return csm.getScanlineStride()*dataTypeSize; |
| } |
| return -1; |
| } |
| |
| /** |
| * Calculates scanline stride in bytes |
| * @param sppsm - sample model |
| * @param r - raster |
| * @return scanline stride in bytes |
| */ |
| private static int calculateScanlineStrideSPPSM(SinglePixelPackedSampleModel sppsm, Raster r) { |
| if (sppsm.getScanlineStride() != sppsm.getWidth()) { |
| int dataTypeSize = DataBuffer.getDataTypeSize(r.getDataBuffer().getDataType()) / 8; |
| return sppsm.getScanlineStride()*dataTypeSize; |
| } |
| return -1; |
| } |
| |
| /** |
| * Calculates byte offset of the alpha channel from the beginning of the pixel data |
| * @param sm - sample model |
| * @param r - raster |
| * @return byte offset of the alpha channel |
| */ |
| private static int calculateAlphaOffset(SampleModel sm, Raster r) { |
| if (sm instanceof ComponentSampleModel) { |
| ComponentSampleModel csm = (ComponentSampleModel) sm; |
| int dataTypeSize = |
| DataBuffer.getDataTypeSize(r.getDataBuffer().getDataType()) / 8; |
| return |
| csm.getBandOffsets()[csm.getBandOffsets().length - 1] * dataTypeSize; |
| } else if (sm instanceof SinglePixelPackedSampleModel) { |
| SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sm; |
| return sppsm.getBitOffsets()[sppsm.getBitOffsets().length - 1] / 8; |
| } else { |
| return -1; // No offset, don't copy alpha |
| } |
| } |
| } |