/* * 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 6, 2005 */ package java.awt.image; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.*; import java.util.Arrays; import org.apache.harmony.awt.gl.AwtImageBackdoorAccessor; import org.apache.harmony.awt.internal.nls.Messages; /** * The Class RescaleOp performs rescaling of the source image data by * multiplying the pixel values with a scale factor and then adding an offset. * * @since Android 1.0 */ public class RescaleOp implements BufferedImageOp, RasterOp { /** * The scale factors. */ private float scaleFactors[]; /** * The offsets. */ private float offsets[]; /** * The hints. */ private RenderingHints hints; static { // TODO // System.loadLibrary("imageops"); } /** * Instantiates a new RescaleOp object with the specified scale factors and * offsets. * * @param scaleFactors * the array of scale factor values. * @param offsets * the array of offset values. * @param hints * the RenderingHints or null. */ public RescaleOp(float[] scaleFactors, float[] offsets, RenderingHints hints) { int numFactors = Math.min(scaleFactors.length, offsets.length); this.scaleFactors = new float[numFactors]; this.offsets = new float[numFactors]; System.arraycopy(scaleFactors, 0, this.scaleFactors, 0, numFactors); System.arraycopy(offsets, 0, this.offsets, 0, numFactors); this.hints = hints; } /** * Instantiates a new RescaleOp object with the specified scale factor and * offset. * * @param scaleFactor * the scale factor. * @param offset * the offset. * @param hints * the RenderingHints or null. */ public RescaleOp(float scaleFactor, float offset, RenderingHints hints) { scaleFactors = new float[1]; offsets = new float[1]; scaleFactors[0] = scaleFactor; offsets[0] = offset; this.hints = hints; } /** * Gets the number of scaling factors. * * @return the number of scaling factors. */ public final int getNumFactors() { return scaleFactors.length; } public final RenderingHints getRenderingHints() { return hints; } /** * Gets the scale factors of this RescaleOp. * * @param scaleFactors * the desired scale factors array will be copied to this array. * @return the scale factors array. */ public final float[] getScaleFactors(float[] scaleFactors) { if (scaleFactors == null) { scaleFactors = new float[this.scaleFactors.length]; } int minLength = Math.min(scaleFactors.length, this.scaleFactors.length); System.arraycopy(this.scaleFactors, 0, scaleFactors, 0, minLength); return scaleFactors; } /** * Gets the offsets array of this RescaleOp. * * @param offsets * the desired offsets array will be copied to this array. * @return the offsets array of this RescaleOp. */ public final float[] getOffsets(float[] offsets) { if (offsets == null) { offsets = new float[this.offsets.length]; } int minLength = Math.min(offsets.length, this.offsets.length); System.arraycopy(this.offsets, 0, offsets, 0, minLength); return offsets; } 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(); } if (dstCM instanceof IndexColorModel) { dstCM = ColorModel.getRGBdefault(); } 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()) { // awt.21D=Number of src bands ({0}) does not match number of // dst bands ({1}) throw new IllegalArgumentException(Messages.getString("awt.21D", //$NON-NLS-1$ src.getNumBands(), dst.getNumBands())); } } if (this.scaleFactors.length != 1 && this.scaleFactors.length != src.getNumBands()) { // awt.21E=Number of scaling constants is not equal to the number of // bands throw new IllegalArgumentException(Messages.getString("awt.21E")); //$NON-NLS-1$ } // TODO // if (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; } /** * 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) { SampleModel sm = src.getSampleModel(); int numBands = src.getNumBands(); int srcHeight = src.getHeight(); int srcWidth = src.getWidth(); int srcMinX = src.getMinX(); int srcMinY = src.getMinY(); int dstMinX = dst.getMinX(); int dstMinY = dst.getMinY(); int[] maxValues = new int[numBands]; int[] masks = new int[numBands]; int[] sampleSizes = sm.getSampleSize(); for (int i = 0; i < numBands; i++) { maxValues[i] = (1 << sampleSizes[i]) - 1; masks[i] = ~(maxValues[i]); } // Processing bounds float[] pixels = null; pixels = src.getPixels(srcMinX, srcMinY, srcWidth, srcHeight, pixels); // Cycle over pixels to be calculated if (skipAlpha) { // Always suppose that alpha channel is the last band if (scaleFactors.length > 1) { for (int i = 0; i < pixels.length;) { for (int bandIdx = 0; bandIdx < numBands - 1; bandIdx++, i++) { pixels[i] = pixels[i] * scaleFactors[bandIdx] + offsets[bandIdx]; // Check for overflow now if (((int)pixels[i] & masks[bandIdx]) != 0) { if (pixels[i] < 0) { pixels[i] = 0; } else { pixels[i] = maxValues[bandIdx]; } } } i++; } } else { for (int i = 0; i < pixels.length;) { for (int bandIdx = 0; bandIdx < numBands - 1; bandIdx++, i++) { pixels[i] = pixels[i] * scaleFactors[0] + offsets[0]; // Check for overflow now if (((int)pixels[i] & masks[bandIdx]) != 0) { if (pixels[i] < 0) { pixels[i] = 0; } else { pixels[i] = maxValues[bandIdx]; } } } i++; } } } else { if (scaleFactors.length > 1) { for (int i = 0; i < pixels.length;) { for (int bandIdx = 0; bandIdx < numBands; bandIdx++, i++) { pixels[i] = pixels[i] * scaleFactors[bandIdx] + offsets[bandIdx]; // Check for overflow now if (((int)pixels[i] & masks[bandIdx]) != 0) { if (pixels[i] < 0) { pixels[i] = 0; } else { pixels[i] = maxValues[bandIdx]; } } } } } else { for (int i = 0; i < pixels.length;) { for (int bandIdx = 0; bandIdx < numBands; bandIdx++, i++) { pixels[i] = pixels[i] * scaleFactors[0] + offsets[0]; // Check for overflow now if (((int)pixels[i] & masks[bandIdx]) != 0) { if (pixels[i] < 0) { pixels[i] = 0; } else { pixels[i] = maxValues[bandIdx]; } } } } } } dst.setPixels(dstMinX, dstMinY, srcWidth, srcHeight, pixels); return 0; } 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(); boolean skipAlpha; if (srcCM.hasAlpha()) { if (scaleFactors.length == 1 || scaleFactors.length == nComponents - 1) { skipAlpha = true; } else if (scaleFactors.length == nComponents) { skipAlpha = false; } else { // awt.21E=Number of scaling constants is not equal to the // number of bands throw new IllegalArgumentException(Messages.getString("awt.21E")); //$NON-NLS-1$ } } else if (scaleFactors.length == 1 || scaleFactors.length == nComponents) { skipAlpha = false; } else { // awt.21E=Number of scaling constants is not equal to the number of // bands throw new IllegalArgumentException(Messages.getString("awt.21E")); //$NON-NLS-1$ } BufferedImage finalDst = null; if (dst == null) { finalDst = dst; dst = createCompatibleDestImage(src, srcCM); } else 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, srcCM); } } // TODO // if (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 finalDst; } // Don't forget to pass allocated arrays for levels and values, size should // be numBands*4 /** * Creates the levels. * * @param sm * the sm. * @param numBands * the num bands. * @param skipAlpha * the skip alpha. * @param levels * the levels. * @param values * the values. * @param channelsOrder * the channels order. */ private final void createLevels(SampleModel sm, int numBands, boolean skipAlpha, int levels[], int values[], int channelsOrder[]) { // Suppose same sample size for all channels, otherwise use slow filter int maxValue = (1 << sm.getSampleSize(0)) - 1; // For simplicity introduce these arrays float extScaleFactors[] = new float[numBands]; float extOffsets[] = new float[numBands]; if (scaleFactors.length != 1) { System.arraycopy(scaleFactors, 0, extScaleFactors, 0, scaleFactors.length); System.arraycopy(offsets, 0, extOffsets, 0, scaleFactors.length); } else { for (int i = 0; i < numBands; i++) { extScaleFactors[i] = scaleFactors[0]; extOffsets[i] = offsets[0]; } } if (skipAlpha) { extScaleFactors[numBands - 1] = 1; extOffsets[numBands - 1] = 0; } // Create a levels for (int i = 0; i < numBands; i++) { if (extScaleFactors[i] == 0) { levels[i * 4] = 0; levels[i * 4 + 1] = 0; levels[i * 4 + 2] = maxValue + 1; levels[i * 4 + 3] = maxValue + 1; } float minLevel = -extOffsets[i] / extScaleFactors[i]; float maxLevel = (maxValue - extOffsets[i]) / extScaleFactors[i]; if (minLevel < 0) { minLevel = 0; } else if (minLevel > maxValue) { minLevel = maxValue; } if (maxLevel < 0) { maxLevel = 0; } else if (maxLevel > maxValue) { maxLevel = maxValue; } levels[i * 4] = 0; if (minLevel > maxLevel) { levels[i * 4 + 1] = (int)maxLevel; levels[i * 4 + 2] = (int)minLevel; } else { levels[i * 4 + 1] = (int)minLevel; levels[i * 4 + 2] = (int)maxLevel; } levels[i * 4 + 3] = maxValue + 1; // Fill values for (int k = 0; k < 4; k++) { int idx = i * 4 + k; values[idx] = (int)(extScaleFactors[i] * levels[idx] + extOffsets[i]); if (values[idx] < 0) { values[idx] = 0; } else if (values[idx] > maxValue) { values[idx] = maxValue; } } } // Reorder data if channels are stored in different order if (channelsOrder != null) { int len = numBands * 4; int savedLevels[] = new int[len]; int savedValues[] = new int[len]; System.arraycopy(levels, 0, savedLevels, 0, len); System.arraycopy(values, 0, savedValues, 0, len); for (int i = 0; i < channelsOrder.length; i++) { System.arraycopy(savedLevels, i * 4, levels, channelsOrder[i] * 4, 4); System.arraycopy(savedValues, i * 4, values, channelsOrder[i] * 4, 4); } } } // 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); } channels = srcSM.getNumBands(); // Have IPP functions for 1, // 3 and 4 channels 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[] = new int[4 * channels]; int values[] = new int[4 * channels]; createLevels(src.getSampleModel(), channels, skipAlpha, levels, values, channelsOrder); 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 = LookupOp.ippLUT(srcData, src.getWidth(), src.getHeight(), srcStride, dstData, dst .getWidth(), dst.getHeight(), dstStride, levels, values, channels, offsets, true); return res; } }