/* * 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]; } } }