blob: a1cc169bb9a3a6671a90fcb76510ab2459872d45 [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$
*/
package org.apache.harmony.awt.gl.color;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
/**
* This class provides functionality for scaling color data when
* ranges of the source and destination color values differs.
*/
public class ColorScaler {
private static final float MAX_SHORT = 0xFFFF;
private static final float MAX_SIGNED_SHORT = 0x7FFF;
private static final float MAX_XYZ = 1f + (32767f/32768f);
// Cached values for scaling color data
private float[] channelMinValues = null;
private float[] channelMulipliers = null; // for scale
private float[] invChannelMulipliers = null; // for unscale
int nColorChannels = 0;
// For scaling rasters, false if transfer type is double or float
boolean isTTypeIntegral = false;
/**
* Loads scaling data for raster. Note, if profile pf is null,
* for non-integral data types multipliers are not initialized.
* @param r - raster
* @param pf - profile which helps to determine the ranges of the color data
*/
public void loadScalingData(Raster r, ICC_Profile pf) {
boolean isSrcTTypeIntegral =
r.getTransferType() != DataBuffer.TYPE_FLOAT &&
r.getTransferType() != DataBuffer.TYPE_DOUBLE;
if (isSrcTTypeIntegral)
loadScalingData(r.getSampleModel());
else if (pf != null)
loadScalingData(pf);
}
/**
* Use this method only for integral transfer types.
* Extracts min/max values from the sample model
* @param sm - sample model
*/
public void loadScalingData(SampleModel sm) {
// Supposing integral transfer type
isTTypeIntegral = true;
nColorChannels = sm.getNumBands();
channelMinValues = new float[nColorChannels];
channelMulipliers = new float[nColorChannels];
invChannelMulipliers = new float[nColorChannels];
boolean isSignedShort =
(sm.getTransferType() == DataBuffer.TYPE_SHORT);
float maxVal;
for (int i=0; i<nColorChannels; i++) {
channelMinValues[i] = 0;
if (isSignedShort) {
channelMulipliers[i] = MAX_SHORT / MAX_SIGNED_SHORT;
invChannelMulipliers[i] = MAX_SIGNED_SHORT / MAX_SHORT;
} else {
maxVal = ((1 << sm.getSampleSize(i)) - 1);
channelMulipliers[i] = MAX_SHORT / maxVal;
invChannelMulipliers[i] = maxVal / MAX_SHORT;
}
}
}
/**
* Use this method only for double of float transfer types.
* Extracts scaling data from the color space signature
* and other tags, stored in the profile
* @param pf - ICC profile
*/
public void loadScalingData(ICC_Profile pf) {
// Supposing double or float transfer type
isTTypeIntegral = false;
nColorChannels = pf.getNumComponents();
// Get min/max values directly from the profile
// Very much like fillMinMaxValues in ICC_ColorSpace
float maxValues[] = new float[nColorChannels];
float minValues[] = new float[nColorChannels];
switch (pf.getColorSpaceType()) {
case ColorSpace.TYPE_XYZ:
minValues[0] = 0;
minValues[1] = 0;
minValues[2] = 0;
maxValues[0] = MAX_XYZ;
maxValues[1] = MAX_XYZ;
maxValues[2] = MAX_XYZ;
break;
case ColorSpace.TYPE_Lab:
minValues[0] = 0;
minValues[1] = -128;
minValues[2] = -128;
maxValues[0] = 100;
maxValues[1] = 127;
maxValues[2] = 127;
break;
default:
for (int i=0; i<nColorChannels; i++) {
minValues[i] = 0;
maxValues[i] = 1;
}
}
channelMinValues = minValues;
channelMulipliers = new float[nColorChannels];
invChannelMulipliers = new float[nColorChannels];
for (int i = 0; i < nColorChannels; i++) {
channelMulipliers[i] =
MAX_SHORT / (maxValues[i] - channelMinValues[i]);
invChannelMulipliers[i] =
(maxValues[i] - channelMinValues[i]) / MAX_SHORT;
}
}
/**
* Extracts scaling data from the color space
* @param cs - color space
*/
public void loadScalingData(ColorSpace cs) {
nColorChannels = cs.getNumComponents();
channelMinValues = new float[nColorChannels];
channelMulipliers = new float[nColorChannels];
invChannelMulipliers = new float[nColorChannels];
for (int i = 0; i < nColorChannels; i++) {
channelMinValues[i] = cs.getMinValue(i);
channelMulipliers[i] =
MAX_SHORT / (cs.getMaxValue(i) - channelMinValues[i]);
invChannelMulipliers[i] =
(cs.getMaxValue(i) - channelMinValues[i]) / MAX_SHORT;
}
}
/**
* Scales and normalizes the whole raster and returns the result
* in the float array
* @param r - source raster
* @return scaled and normalized raster data
*/
public float[][] scaleNormalize(Raster r) {
int width = r.getWidth();
int height = r.getHeight();
float result[][] = new float[width*height][nColorChannels];
float normMultipliers[] = new float[nColorChannels];
int pos = 0;
if (isTTypeIntegral) {
// Change max value from MAX_SHORT to 1f
for (int i=0; i<nColorChannels; i++) {
normMultipliers[i] = channelMulipliers[i] / MAX_SHORT;
}
int sample;
for (int row=r.getMinX(); row<width; row++) {
for (int col=r.getMinY(); col<height; col++) {
for (int chan = 0; chan < nColorChannels; chan++) {
sample = r.getSample(row, col, chan);
result[pos][chan] = (sample * normMultipliers[chan]);
}
pos++;
}
}
} else { // Just get the samples...
for (int row=r.getMinX(); row<width; row++) {
for (int col=r.getMinY(); col<height; col++) {
for (int chan = 0; chan < nColorChannels; chan++) {
result[pos][chan] = r.getSampleFloat(row, col, chan);
}
pos++;
}
}
}
return result;
}
/**
* Unscale the whole float array and put the result
* in the raster
* @param r - destination raster
* @param data - input pixels
*/
public void unscaleNormalized(WritableRaster r, float data[][]) {
int width = r.getWidth();
int height = r.getHeight();
float normMultipliers[] = new float[nColorChannels];
int pos = 0;
if (isTTypeIntegral) {
// Change max value from MAX_SHORT to 1f
for (int i=0; i<nColorChannels; i++) {
normMultipliers[i] = invChannelMulipliers[i] * MAX_SHORT;
}
int sample;
for (int row=r.getMinX(); row<width; row++) {
for (int col=r.getMinY(); col<height; col++) {
for (int chan = 0; chan < nColorChannels; chan++) {
sample = (int) (data[pos][chan] * normMultipliers[chan] + 0.5f);
r.setSample(row, col, chan, sample);
}
pos++;
}
}
} else { // Just set the samples...
for (int row=r.getMinX(); row<width; row++) {
for (int col=r.getMinY(); col<height; col++) {
for (int chan = 0; chan < nColorChannels; chan++) {
r.setSample(row, col, chan, data[pos][chan]);
}
pos++;
}
}
}
}
/**
* Scales the whole raster to short and returns the result
* in the array
* @param r - source raster
* @return scaled and normalized raster data
*/
public short[] scale(Raster r) {
int width = r.getWidth();
int height = r.getHeight();
short result[] = new short[width*height*nColorChannels];
int pos = 0;
if (isTTypeIntegral) {
int sample;
for (int row=r.getMinX(); row<width; row++) {
for (int col=r.getMinY(); col<height; col++) {
for (int chan = 0; chan < nColorChannels; chan++) {
sample = r.getSample(row, col, chan);
result[pos++] =
(short) (sample * channelMulipliers[chan] + 0.5f);
}
}
}
} else {
float sample;
for (int row=r.getMinX(); row<width; row++) {
for (int col=r.getMinY(); col<height; col++) {
for (int chan = 0; chan < nColorChannels; chan++) {
sample = r.getSampleFloat(row, col, chan);
result[pos++] = (short) ((sample - channelMinValues[chan])
* channelMulipliers[chan] + 0.5f);
}
}
}
}
return result;
}
/**
* Unscales the whole data array and puts obtained values to the raster
* @param data - input data
* @param wr - destination raster
*/
public void unscale(short[] data, WritableRaster wr) {
int width = wr.getWidth();
int height = wr.getHeight();
int pos = 0;
if (isTTypeIntegral) {
int sample;
for (int row=wr.getMinX(); row<width; row++) {
for (int col=wr.getMinY(); col<height; col++) {
for (int chan = 0; chan < nColorChannels; chan++) {
sample = (int) ((data[pos++] & 0xFFFF) *
invChannelMulipliers[chan] + 0.5f);
wr.setSample(row, col, chan, sample);
}
}
}
} else {
float sample;
for (int row=wr.getMinX(); row<width; row++) {
for (int col=wr.getMinY(); col<height; col++) {
for (int chan = 0; chan < nColorChannels; chan++) {
sample = (data[pos++] & 0xFFFF) *
invChannelMulipliers[chan] + channelMinValues[chan];
wr.setSample(row, col, chan, sample);
}
}
}
}
}
/**
* Scales one pixel and puts obtained values to the chanData
* @param pixelData - input pixel
* @param chanData - output buffer
* @param chanDataOffset - output buffer offset
*/
public void scale(float[] pixelData, short[] chanData, int chanDataOffset) {
for (int chan = 0; chan < nColorChannels; chan++) {
chanData[chanDataOffset + chan] =
(short) ((pixelData[chan] - channelMinValues[chan]) *
channelMulipliers[chan] + 0.5f);
}
}
/**
* Unscales one pixel and puts obtained values to the pixelData
* @param pixelData - output pixel
* @param chanData - input buffer
* @param chanDataOffset - input buffer offset
*/
public void unscale(float[] pixelData, short[] chanData, int chanDataOffset) {
for (int chan = 0; chan < nColorChannels; chan++) {
pixelData[chan] = (chanData[chanDataOffset + chan] & 0xFFFF)
* invChannelMulipliers[chan] + channelMinValues[chan];
}
}
}