blob: d7e2bd7684a9c4e69ee92286ae97fff5d336761a [file] [log] [blame]
/*
* 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;
}
}