| /* |
| * 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$ |
| * |
| * @date: Oct 14, 2005 |
| */ |
| |
| package java.awt.image; |
| |
| import java.awt.*; |
| import java.awt.geom.Rectangle2D; |
| import java.awt.geom.Point2D; |
| import java.util.Arrays; |
| |
| import org.apache.harmony.awt.gl.AwtImageBackdoorAccessor; |
| import org.apache.harmony.awt.internal.nls.Messages; |
| |
| /** |
| * The LookupOp class performs a lookup operation which transforms a source |
| * image by filtering each band using a table of data. The table may contain a |
| * single array or it may contain a different data array for each band of the |
| * image. |
| * |
| * @since Android 1.0 |
| */ |
| public class LookupOp implements BufferedImageOp, RasterOp { |
| |
| /** |
| * The lut. |
| */ |
| private final LookupTable lut; |
| |
| /** |
| * The hints. |
| */ |
| private RenderingHints hints; |
| |
| // TODO remove when this field is used |
| /** |
| * The can use ipp. |
| */ |
| @SuppressWarnings("unused") |
| private final boolean canUseIpp; |
| |
| // We don't create levels/values when it is possible to reuse old |
| /** |
| * The cached levels. |
| */ |
| private int cachedLevels[]; |
| |
| /** |
| * The cached values. |
| */ |
| private int cachedValues[]; |
| |
| // Number of channels for which cache is valid. |
| // If negative number of channels is same as positive but skipAlpha was |
| // specified |
| /** |
| * The valid for channels. |
| */ |
| private int validForChannels; |
| |
| /** |
| * The level initializer. |
| */ |
| static int levelInitializer[] = new int[0x10000]; |
| |
| static { |
| // TODO |
| // System.loadLibrary("imageops"); |
| |
| for (int i = 1; i <= 0x10000; i++) { |
| levelInitializer[i - 1] = i; |
| } |
| } |
| |
| /** |
| * Instantiates a new LookupOp object from the specified LookupTable object |
| * and a RenderingHints object. |
| * |
| * @param lookup |
| * the specified LookupTable object. |
| * @param hints |
| * the RenderingHints object or null. |
| */ |
| public LookupOp(LookupTable lookup, RenderingHints hints) { |
| if (lookup == null) { |
| throw new NullPointerException(Messages.getString("awt.01", "lookup")); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| lut = lookup; |
| this.hints = hints; |
| canUseIpp = lut instanceof ByteLookupTable || lut instanceof ShortLookupTable; |
| } |
| |
| /** |
| * Gets the LookupTable of the specified Object. |
| * |
| * @return the LookupTable of the specified Object. |
| */ |
| public final LookupTable getTable() { |
| return lut; |
| } |
| |
| public final RenderingHints getRenderingHints() { |
| return hints; |
| } |
| |
| public final Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { |
| if (dstPt == null) { |
| dstPt = new Point2D.Float(); |
| } |
| |
| dstPt.setLocation(srcPt); |
| return dstPt; |
| } |
| |
| public final Rectangle2D getBounds2D(Raster src) { |
| return src.getBounds(); |
| } |
| |
| public final Rectangle2D getBounds2D(BufferedImage src) { |
| return getBounds2D(src.getRaster()); |
| } |
| |
| public WritableRaster createCompatibleDestRaster(Raster src) { |
| return src.createCompatibleWritableRaster(); |
| } |
| |
| public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) { |
| if (dstCM == null) { |
| dstCM = src.getColorModel(); |
| |
| // Sync transfer type with LUT for component color model |
| if (dstCM instanceof ComponentColorModel) { |
| int transferType = dstCM.getTransferType(); |
| if (lut instanceof ByteLookupTable) { |
| transferType = DataBuffer.TYPE_BYTE; |
| } else if (lut instanceof ShortLookupTable) { |
| transferType = DataBuffer.TYPE_SHORT; |
| } |
| |
| dstCM = new ComponentColorModel(dstCM.cs, dstCM.hasAlpha(), |
| dstCM.isAlphaPremultiplied, dstCM.transparency, transferType); |
| } |
| } |
| |
| WritableRaster r = dstCM.isCompatibleSampleModel(src.getSampleModel()) ? src.getRaster() |
| .createCompatibleWritableRaster(src.getWidth(), src.getHeight()) : dstCM |
| .createCompatibleWritableRaster(src.getWidth(), src.getHeight()); |
| |
| return new BufferedImage(dstCM, r, dstCM.isAlphaPremultiplied(), null); |
| } |
| |
| public final WritableRaster filter(Raster src, WritableRaster dst) { |
| if (dst == null) { |
| dst = createCompatibleDestRaster(src); |
| } else { |
| if (src.getNumBands() != dst.getNumBands()) { |
| throw new IllegalArgumentException(Messages.getString("awt.237")); //$NON-NLS-1$ } |
| } |
| if (src.getWidth() != dst.getWidth()) { |
| throw new IllegalArgumentException(Messages.getString("awt.28F")); //$NON-NLS-1$ } |
| } |
| if (src.getHeight() != dst.getHeight()) { |
| throw new IllegalArgumentException(Messages.getString("awt.290")); //$NON-NLS-1$ } |
| } |
| } |
| |
| if (lut.getNumComponents() != 1 && lut.getNumComponents() != src.getNumBands()) { |
| // awt.238=The number of arrays in the LookupTable does not meet the |
| // restrictions |
| throw new IllegalArgumentException(Messages.getString("awt.238")); //$NON-NLS-1$ |
| } |
| |
| // TODO |
| // if (!canUseIpp || ippFilter(src, dst, BufferedImage.TYPE_CUSTOM, |
| // false) != 0) |
| if (slowFilter(src, dst, false) != 0) { |
| // awt.21F=Unable to transform source |
| throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$ |
| } |
| |
| return dst; |
| } |
| |
| public final BufferedImage filter(BufferedImage src, BufferedImage dst) { |
| ColorModel srcCM = src.getColorModel(); |
| |
| if (srcCM instanceof IndexColorModel) { |
| // awt.220=Source should not have IndexColorModel |
| throw new IllegalArgumentException(Messages.getString("awt.220")); //$NON-NLS-1$ |
| } |
| |
| // Check if the number of scaling factors matches the number of bands |
| int nComponents = srcCM.getNumComponents(); |
| int nLUTComponents = lut.getNumComponents(); |
| boolean skipAlpha; |
| if (srcCM.hasAlpha()) { |
| if (nLUTComponents == 1 || nLUTComponents == nComponents - 1) { |
| skipAlpha = true; |
| } else if (nLUTComponents == nComponents) { |
| skipAlpha = false; |
| } else { |
| // awt.229=Number of components in the LUT does not match the |
| // number of bands |
| throw new IllegalArgumentException(Messages.getString("awt.229")); //$NON-NLS-1$ |
| } |
| } else if (nLUTComponents == 1 || nLUTComponents == nComponents) { |
| skipAlpha = false; |
| } else { |
| // awt.229=Number of components in the LUT does not match the number |
| // of bands |
| throw new IllegalArgumentException(Messages.getString("awt.229")); //$NON-NLS-1$ |
| } |
| |
| BufferedImage finalDst = null; |
| if (dst == null) { |
| finalDst = dst; |
| dst = createCompatibleDestImage(src, null); |
| } else { |
| if (src.getWidth() != dst.getWidth()) { |
| throw new IllegalArgumentException(Messages.getString("awt.291")); //$NON-NLS-1$ |
| } |
| |
| if (src.getHeight() != dst.getHeight()) { |
| throw new IllegalArgumentException(Messages.getString("awt.292")); //$NON-NLS-1$ |
| } |
| |
| if (!srcCM.equals(dst.getColorModel())) { |
| // Treat BufferedImage.TYPE_INT_RGB and |
| // BufferedImage.TYPE_INT_ARGB as same |
| if (!((src.getType() == BufferedImage.TYPE_INT_RGB || src.getType() == BufferedImage.TYPE_INT_ARGB) && (dst |
| .getType() == BufferedImage.TYPE_INT_RGB || dst.getType() == BufferedImage.TYPE_INT_ARGB))) { |
| finalDst = dst; |
| dst = createCompatibleDestImage(src, null); |
| } |
| } |
| } |
| |
| // TODO |
| // if (!canUseIpp || ippFilter(src.getRaster(), dst.getRaster(), |
| // src.getType(), skipAlpha) != 0) |
| if (slowFilter(src.getRaster(), dst.getRaster(), skipAlpha) != 0) { |
| // awt.21F=Unable to transform source |
| throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$ |
| } |
| |
| if (finalDst != null) { |
| Graphics2D g = finalDst.createGraphics(); |
| g.setComposite(AlphaComposite.Src); |
| g.drawImage(dst, 0, 0, null); |
| } else { |
| finalDst = dst; |
| } |
| |
| return dst; |
| } |
| |
| /** |
| * Slow filter. |
| * |
| * @param src |
| * the src. |
| * @param dst |
| * the dst. |
| * @param skipAlpha |
| * the skip alpha. |
| * @return the int. |
| */ |
| private final int slowFilter(Raster src, WritableRaster dst, boolean skipAlpha) { |
| int minSrcX = src.getMinX(); |
| int minDstX = dst.getMinX(); |
| int minSrcY = src.getMinY(); |
| int minDstY = dst.getMinY(); |
| |
| int skippingChannels = skipAlpha ? 1 : 0; |
| int numBands2Process = src.getNumBands() - skippingChannels; |
| |
| int numBands = src.getNumBands(); |
| int srcHeight = src.getHeight(); |
| int srcWidth = src.getWidth(); |
| |
| int[] pixels = null; |
| int offset = lut.getOffset(); |
| |
| if (lut instanceof ByteLookupTable) { |
| byte[][] byteData = ((ByteLookupTable)lut).getTable(); |
| pixels = src.getPixels(minSrcX, minSrcY, srcWidth, srcHeight, pixels); |
| |
| if (lut.getNumComponents() != 1) { |
| for (int i = 0; i < pixels.length; i += numBands) { |
| for (int b = 0; b < numBands2Process; b++) { |
| pixels[i + b] = byteData[b][pixels[i + b] - offset] & 0xFF; |
| } |
| } |
| } else { |
| for (int i = 0; i < pixels.length; i += numBands) { |
| for (int b = 0; b < numBands2Process; b++) { |
| pixels[i + b] = byteData[0][pixels[i + b] - offset] & 0xFF; |
| } |
| } |
| } |
| |
| dst.setPixels(minDstX, minDstY, srcWidth, srcHeight, pixels); |
| } else if (lut instanceof ShortLookupTable) { |
| short[][] shortData = ((ShortLookupTable)lut).getTable(); |
| pixels = src.getPixels(minSrcX, minSrcY, srcWidth, srcHeight, pixels); |
| |
| if (lut.getNumComponents() != 1) { |
| for (int i = 0; i < pixels.length; i += numBands) { |
| for (int b = 0; b < numBands2Process; b++) { |
| pixels[i + b] = shortData[b][pixels[i + b] - offset] & 0xFFFF; |
| } |
| } |
| } else { |
| for (int i = 0; i < pixels.length; i += numBands) { |
| for (int b = 0; b < numBands2Process; b++) { |
| pixels[i + b] = shortData[0][pixels[i + b] - offset] & 0xFFFF; |
| } |
| } |
| } |
| |
| dst.setPixels(minDstX, minDstY, srcWidth, srcHeight, pixels); |
| } else { |
| int pixel[] = new int[src.getNumBands()]; |
| int maxY = minSrcY + srcHeight; |
| int maxX = minSrcX + srcWidth; |
| for (int srcY = minSrcY, dstY = minDstY; srcY < maxY; srcY++, dstY++) { |
| for (int srcX = minSrcX, dstX = minDstX; srcX < maxX; srcX++, dstX++) { |
| src.getPixel(srcX, srcY, pixel); |
| lut.lookupPixel(pixel, pixel); |
| dst.setPixel(dstX, dstY, pixel); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Creates the byte levels. |
| * |
| * @param channels |
| * the channels. |
| * @param skipAlpha |
| * the skip alpha. |
| * @param levels |
| * the levels. |
| * @param values |
| * the values. |
| * @param channelsOrder |
| * the channels order. |
| */ |
| private final void createByteLevels(int channels, boolean skipAlpha, int levels[], |
| int values[], int channelsOrder[]) { |
| byte data[][] = ((ByteLookupTable)lut).getTable(); |
| int nLevels = data[0].length; |
| int offset = lut.getOffset(); |
| |
| // Use one data array for all channels or use several data arrays |
| int dataIncrement = data.length > 1 ? 1 : 0; |
| |
| for (int ch = 0, dataIdx = 0; ch < channels; dataIdx += dataIncrement, ch++) { |
| int channelOffset = channelsOrder == null ? ch : channelsOrder[ch]; |
| int channelBase = nLevels * channelOffset; |
| |
| // Skip last channel if needed, zero values are OK - |
| // no changes to the channel information will be done in IPP |
| if ((channelOffset == channels - 1 && skipAlpha) || (dataIdx >= data.length)) { |
| continue; |
| } |
| |
| System.arraycopy(levelInitializer, offset, levels, channelBase, nLevels); |
| for (int from = 0, to = channelBase; from < nLevels; from++, to++) { |
| values[to] = data[dataIdx][from] & 0xFF; |
| } |
| } |
| } |
| |
| /** |
| * Creates the short levels. |
| * |
| * @param channels |
| * the channels. |
| * @param skipAlpha |
| * the skip alpha. |
| * @param levels |
| * the levels. |
| * @param values |
| * the values. |
| * @param channelsOrder |
| * the channels order. |
| */ |
| private final void createShortLevels(int channels, boolean skipAlpha, int levels[], |
| int values[], int channelsOrder[]) { |
| short data[][] = ((ShortLookupTable)lut).getTable(); |
| int nLevels = data[0].length; |
| int offset = lut.getOffset(); |
| |
| // Use one data array for all channels or use several data arrays |
| int dataIncrement = data.length > 1 ? 1 : 0; |
| |
| for (int ch = 0, dataIdx = 0; ch < channels; dataIdx += dataIncrement, ch++) { |
| int channelOffset = channelsOrder == null ? ch : channelsOrder[ch]; |
| |
| // Skip last channel if needed, zero values are OK - |
| // no changes to the channel information will be done in IPP |
| if ((channelOffset == channels - 1 && skipAlpha) || (dataIdx >= data.length)) { |
| continue; |
| } |
| |
| int channelBase = nLevels * channelOffset; |
| System.arraycopy(levelInitializer, offset, levels, channelBase, nLevels); |
| for (int from = 0, to = channelBase; from < nLevels; from++, to++) { |
| values[to] = data[dataIdx][from] & 0xFFFF; |
| } |
| } |
| } |
| |
| // TODO remove when this method is used |
| /** |
| * Ipp filter. |
| * |
| * @param src |
| * the src. |
| * @param dst |
| * the dst. |
| * @param imageType |
| * the image type. |
| * @param skipAlpha |
| * the skip alpha. |
| * @return the int. |
| */ |
| @SuppressWarnings("unused") |
| private final int ippFilter(Raster src, WritableRaster dst, int imageType, boolean skipAlpha) { |
| int res; |
| |
| int srcStride, dstStride; |
| int channels; |
| int offsets[] = null; |
| int channelsOrder[] = null; |
| |
| switch (imageType) { |
| case BufferedImage.TYPE_INT_ARGB: |
| case BufferedImage.TYPE_INT_ARGB_PRE: |
| case BufferedImage.TYPE_INT_RGB: { |
| channels = 4; |
| srcStride = src.getWidth() * 4; |
| dstStride = dst.getWidth() * 4; |
| channelsOrder = new int[] { |
| 2, 1, 0, 3 |
| }; |
| break; |
| } |
| |
| case BufferedImage.TYPE_4BYTE_ABGR: |
| case BufferedImage.TYPE_4BYTE_ABGR_PRE: |
| case BufferedImage.TYPE_INT_BGR: { |
| channels = 4; |
| srcStride = src.getWidth() * 4; |
| dstStride = dst.getWidth() * 4; |
| break; |
| } |
| |
| case BufferedImage.TYPE_BYTE_GRAY: { |
| channels = 1; |
| srcStride = src.getWidth(); |
| dstStride = dst.getWidth(); |
| break; |
| } |
| |
| case BufferedImage.TYPE_3BYTE_BGR: { |
| channels = 3; |
| srcStride = src.getWidth() * 3; |
| dstStride = dst.getWidth() * 3; |
| channelsOrder = new int[] { |
| 2, 1, 0 |
| }; |
| break; |
| } |
| |
| case BufferedImage.TYPE_USHORT_GRAY: |
| case BufferedImage.TYPE_USHORT_565_RGB: |
| case BufferedImage.TYPE_USHORT_555_RGB: |
| case BufferedImage.TYPE_BYTE_BINARY: { |
| return slowFilter(src, dst, skipAlpha); |
| } |
| |
| default: { |
| SampleModel srcSM = src.getSampleModel(); |
| SampleModel dstSM = dst.getSampleModel(); |
| |
| if (srcSM instanceof PixelInterleavedSampleModel |
| && dstSM instanceof PixelInterleavedSampleModel) { |
| // Check PixelInterleavedSampleModel |
| if (srcSM.getDataType() != DataBuffer.TYPE_BYTE |
| || dstSM.getDataType() != DataBuffer.TYPE_BYTE) { |
| return slowFilter(src, dst, skipAlpha); |
| } |
| |
| // Have IPP functions for 1, 3 and 4 channels |
| channels = srcSM.getNumBands(); |
| if (!(channels == 1 || channels == 3 || channels == 4)) { |
| return slowFilter(src, dst, skipAlpha); |
| } |
| |
| srcStride = ((ComponentSampleModel)srcSM).getScanlineStride(); |
| dstStride = ((ComponentSampleModel)dstSM).getScanlineStride(); |
| |
| channelsOrder = ((ComponentSampleModel)srcSM).getBandOffsets(); |
| } else if (srcSM instanceof SinglePixelPackedSampleModel |
| && dstSM instanceof SinglePixelPackedSampleModel) { |
| // Check SinglePixelPackedSampleModel |
| SinglePixelPackedSampleModel sppsm1 = (SinglePixelPackedSampleModel)srcSM; |
| SinglePixelPackedSampleModel sppsm2 = (SinglePixelPackedSampleModel)dstSM; |
| |
| channels = sppsm1.getNumBands(); |
| |
| // TYPE_INT_RGB, TYPE_INT_ARGB... |
| if (sppsm1.getDataType() != DataBuffer.TYPE_INT |
| || sppsm2.getDataType() != DataBuffer.TYPE_INT |
| || !(channels == 3 || channels == 4)) { |
| return slowFilter(src, dst, skipAlpha); |
| } |
| |
| // Check compatibility of sample models |
| if (!Arrays.equals(sppsm1.getBitOffsets(), sppsm2.getBitOffsets()) |
| || !Arrays.equals(sppsm1.getBitMasks(), sppsm2.getBitMasks())) { |
| return slowFilter(src, dst, skipAlpha); |
| } |
| |
| for (int i = 0; i < channels; i++) { |
| if (sppsm1.getSampleSize(i) != 8) { |
| return slowFilter(src, dst, skipAlpha); |
| } |
| } |
| |
| channelsOrder = new int[channels]; |
| int bitOffsets[] = sppsm1.getBitOffsets(); |
| for (int i = 0; i < channels; i++) { |
| channelsOrder[i] = bitOffsets[i] / 8; |
| } |
| |
| if (channels == 3) { // Don't skip channel now, could be |
| // optimized |
| channels = 4; |
| } |
| |
| srcStride = sppsm1.getScanlineStride() * 4; |
| dstStride = sppsm2.getScanlineStride() * 4; |
| } else { |
| return slowFilter(src, dst, skipAlpha); |
| } |
| |
| // Fill offsets if there's a child raster |
| if (src.getParent() != null || dst.getParent() != null) { |
| if (src.getSampleModelTranslateX() != 0 || src.getSampleModelTranslateY() != 0 |
| || dst.getSampleModelTranslateX() != 0 |
| || dst.getSampleModelTranslateY() != 0) { |
| offsets = new int[4]; |
| offsets[0] = -src.getSampleModelTranslateX() + src.getMinX(); |
| offsets[1] = -src.getSampleModelTranslateY() + src.getMinY(); |
| offsets[2] = -dst.getSampleModelTranslateX() + dst.getMinX(); |
| offsets[3] = -dst.getSampleModelTranslateY() + dst.getMinY(); |
| } |
| } |
| } |
| } |
| |
| int levels[] = null, values[] = null; |
| int channelMultiplier = skipAlpha ? -1 : 1; |
| if (channelMultiplier * channels == validForChannels) { // use existing |
| // levels/values |
| levels = cachedLevels; |
| values = cachedValues; |
| } else { // create new levels/values |
| if (lut instanceof ByteLookupTable) { |
| byte data[][] = ((ByteLookupTable)lut).getTable(); |
| levels = new int[channels * data[0].length]; |
| values = new int[channels * data[0].length]; |
| createByteLevels(channels, skipAlpha, levels, values, channelsOrder); |
| } else if (lut instanceof ShortLookupTable) { |
| short data[][] = ((ShortLookupTable)lut).getTable(); |
| levels = new int[channels * data[0].length]; |
| values = new int[channels * data[0].length]; |
| createShortLevels(channels, skipAlpha, levels, values, channelsOrder); |
| } |
| |
| // cache levels/values |
| validForChannels = channelMultiplier * channels; |
| cachedLevels = levels; |
| cachedValues = values; |
| } |
| |
| Object srcData, dstData; |
| AwtImageBackdoorAccessor dbAccess = AwtImageBackdoorAccessor.getInstance(); |
| try { |
| srcData = dbAccess.getData(src.getDataBuffer()); |
| dstData = dbAccess.getData(dst.getDataBuffer()); |
| } catch (IllegalArgumentException e) { |
| return -1; // Unknown data buffer type |
| } |
| |
| res = ippLUT(srcData, src.getWidth(), src.getHeight(), srcStride, dstData, dst.getWidth(), |
| dst.getHeight(), dstStride, levels, values, channels, offsets, false); |
| |
| return res; |
| } |
| |
| /** |
| * Ipp lut. |
| * |
| * @param src |
| * the src. |
| * @param srcWidth |
| * the src width. |
| * @param srcHeight |
| * the src height. |
| * @param srcStride |
| * the src stride. |
| * @param dst |
| * the dst. |
| * @param dstWidth |
| * the dst width. |
| * @param dstHeight |
| * the dst height. |
| * @param dstStride |
| * the dst stride. |
| * @param levels |
| * the levels. |
| * @param values |
| * the values. |
| * @param channels |
| * the channels. |
| * @param offsets |
| * the offsets. |
| * @param linear |
| * the linear. |
| * @return the int. |
| */ |
| final static native int ippLUT(Object src, int srcWidth, int srcHeight, int srcStride, |
| Object dst, int dstWidth, int dstHeight, int dstStride, int levels[], int values[], |
| int channels, int offsets[], boolean linear); |
| } |