/*
 * 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.
 */

/* $Id: Any2sRGBRed.java 1732018 2016-02-24 04:51:06Z gadams $ */

package org.apache.xmlgraphics.image.rendered;

import java.awt.color.ColorSpace;
import java.awt.image.BandCombineOp;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;

import org.apache.xmlgraphics.image.GraphicsUtil;

// CSOFF: ConstantName
// CSOFF: MethodName
// CSOFF: MultipleVariableDeclarations
// CSOFF: NeedBraces
// CSOFF: OperatorWrap
// CSOFF: WhitespaceAfter
// CSOFF: WhitespaceAround

This function will tranform an image from any colorspace into a luminance image. The alpha channel if any will be copied to the new image.
Version:$Id: Any2sRGBRed.java 1732018 2016-02-24 04:51:06Z gadams $ Originally authored by Thomas DeWeese.
/** * This function will tranform an image from any colorspace into a * luminance image. The alpha channel if any will be copied to the * new image. * * @version $Id: Any2sRGBRed.java 1732018 2016-02-24 04:51:06Z gadams $ * * Originally authored by Thomas DeWeese. */
public class Any2sRGBRed extends AbstractRed { boolean srcIsLsRGB;
Construct a luminace image from src.
Params:
  • src – The image to convert to a luminance image
/** * Construct a luminace image from src. * * @param src The image to convert to a luminance image */
public Any2sRGBRed(CachableRed src) { super(src, src.getBounds(), fixColorModel(src), fixSampleModel(src), src.getTileGridXOffset(), src.getTileGridYOffset(), null); ColorModel srcCM = src.getColorModel(); if (srcCM == null) { return; } ColorSpace srcCS = srcCM.getColorSpace(); if (srcCS == ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB)) { srcIsLsRGB = true; } } public static boolean is_INT_PACK_COMP(SampleModel sm) { if (!(sm instanceof SinglePixelPackedSampleModel)) { return false; } // Check transfer types if (sm.getDataType() != DataBuffer.TYPE_INT) { return false; } SinglePixelPackedSampleModel sppsm; sppsm = (SinglePixelPackedSampleModel)sm; int [] masks = sppsm.getBitMasks(); if ((masks.length != 3) && (masks.length != 4)) { return false; } if (masks[0] != 0x00ff0000) { return false; } if (masks[1] != 0x0000ff00) { return false; } if (masks[2] != 0x000000ff) { return false; } if ((masks.length == 4) && (masks[3] != 0xff000000)) { return false; } return true; }
Exponent for linear to sRGB convertion
/** * Exponent for linear to sRGB convertion */
private static final double GAMMA = 2.4;
Lookup tables for RGB lookups. The linearToSRGBLut is used when noise values are considered to be on a linearScale. The linearToLinear table is used when the values are considered to be on the sRGB scale to begin with.
/** * Lookup tables for RGB lookups. The linearToSRGBLut is used * when noise values are considered to be on a linearScale. The * linearToLinear table is used when the values are considered to * be on the sRGB scale to begin with. */
private static final int[] linearToSRGBLut = new int[256]; static { final double scale = 1.0 / 255; final double exp = 1.0 / GAMMA; // System.out.print("L2S: "); for (int i = 0; i < 256; i++) { double value = i * scale; if (value <= 0.0031308) { value *= 12.92; } else { value = 1.055 * Math.pow(value, exp) - 0.055; } linearToSRGBLut[i] = (int)Math.round(value * 255.); // System.out.print(linearToSRGBLut[i] + ","); } // System.out.println(""); } public static WritableRaster applyLut_INT(WritableRaster wr, final int []lut) { SinglePixelPackedSampleModel sm = (SinglePixelPackedSampleModel)wr.getSampleModel(); DataBufferInt db = (DataBufferInt)wr.getDataBuffer(); final int srcBase = (db.getOffset() + sm.getOffset(wr.getMinX() - wr.getSampleModelTranslateX(), wr.getMinY() - wr.getSampleModelTranslateY())); // Access the pixel data array final int[] pixels = db.getBankData()[0]; final int width = wr.getWidth(); final int height = wr.getHeight(); final int scanStride = sm.getScanlineStride(); int end; int pix; // For alpha premult we need to multiply all comps. for (int y = 0; y < height; y++) { int sp = srcBase + y * scanStride; end = sp + width; while (sp < end) { pix = pixels[sp]; pixels[sp] = ((pix & 0xFF000000) | (lut[(pix >>> 16) & 0xFF] << 16) | (lut[(pix >>> 8) & 0xFF] << 8) | (lut[pix & 0xFF])); sp++; } } return wr; } public WritableRaster copyData(WritableRaster wr) { // Get my source. CachableRed src = (CachableRed)getSources().get(0); ColorModel srcCM = src.getColorModel(); SampleModel srcSM = src.getSampleModel(); // Fast case, Linear SRGB source, INT Pack writable raster... if (srcIsLsRGB && is_INT_PACK_COMP(wr.getSampleModel())) { src.copyData(wr); if (srcCM.hasAlpha()) { GraphicsUtil.coerceData(wr, srcCM, false); } applyLut_INT(wr, linearToSRGBLut); return wr; } if (srcCM == null) { // We don't really know much about this source, let's // guess based on the number of bands... float [][] matrix = null; switch (srcSM.getNumBands()) { case 1: matrix = new float[3][1]; matrix[0][0] = 1; // Red matrix[1][0] = 1; // Grn matrix[2][0] = 1; // Blu break; case 2: matrix = new float[4][2]; matrix[0][0] = 1; // Red matrix[1][0] = 1; // Grn matrix[3][0] = 1; // Blu matrix[3][1] = 1; // Alpha break; case 3: matrix = new float[3][3]; matrix[0][0] = 1; // Red matrix[1][1] = 1; // Grn matrix[2][2] = 1; // Blu break; default: matrix = new float[4][srcSM.getNumBands()]; matrix[0][0] = 1; // Red matrix[1][1] = 1; // Grn matrix[2][2] = 1; // Blu matrix[3][3] = 1; // Alpha break; } Raster srcRas = src.getData(wr.getBounds()); BandCombineOp op = new BandCombineOp(matrix, null); op.filter(srcRas, wr); return wr; } if (srcCM.getColorSpace() == ColorSpace.getInstance(ColorSpace.CS_GRAY)) { // This is a little bit of a hack. There is only // a linear grayscale ICC profile in the JDK so // many things use this when the data _really_ // has sRGB gamma applied. try { float [][] matrix = null; switch (srcSM.getNumBands()) { case 1: matrix = new float[3][1]; matrix[0][0] = 1; // Red matrix[1][0] = 1; // Grn matrix[2][0] = 1; // Blu break; case 2: default: matrix = new float[4][2]; matrix[0][0] = 1; // Red matrix[1][0] = 1; // Grn matrix[3][0] = 1; // Blu matrix[4][1] = 1; // Alpha break; } Raster srcRas = src.getData(wr.getBounds()); BandCombineOp op = new BandCombineOp(matrix, null); op.filter(srcRas, wr); } catch (Throwable t) { t.printStackTrace(); } return wr; } ColorModel dstCM = getColorModel(); if (srcCM.getColorSpace() == dstCM.getColorSpace()) { // No transform needed, just reformat data... // System.out.println("Bypassing"); if (is_INT_PACK_COMP(srcSM)) { src.copyData(wr); } else { GraphicsUtil.copyData(src.getData(wr.getBounds()), wr); } return wr; } Raster srcRas = src.getData(wr.getBounds()); assert srcRas instanceof WritableRaster; WritableRaster srcWr = (WritableRaster)srcRas; // Divide out alpha if we have it. We need to do this since // the color convert may not be a linear operation which may // lead to out of range values. ColorModel srcBICM = srcCM; if (srcCM.hasAlpha()) { srcBICM = GraphicsUtil.coerceData(srcWr, srcCM, false); } BufferedImage srcBI; BufferedImage dstBI; srcBI = new BufferedImage(srcBICM, srcWr.createWritableTranslatedChild(0, 0), false, null); // System.out.println("src: " + srcBI.getWidth() + "x" + // srcBI.getHeight()); ColorConvertOp op = new ColorConvertOp(dstCM.getColorSpace(), null); dstBI = op.filter(srcBI, null); // System.out.println("After filter:"); WritableRaster wr00 = wr.createWritableTranslatedChild(0, 0); for (int i = 0; i < dstCM.getColorSpace().getNumComponents(); i++) { copyBand(dstBI.getRaster(), i, wr00, i); } if (dstCM.hasAlpha()) { copyBand(srcWr, srcSM.getNumBands() - 1, wr, getSampleModel().getNumBands() - 1); } return wr; }
This function 'fixes' the source's color model. Right now it just selects if it should have one or two bands based on if the source had an alpha channel.
/** * This function 'fixes' the source's color model. Right now * it just selects if it should have one or two bands based on * if the source had an alpha channel. */
protected static ColorModel fixColorModel(CachableRed src) { ColorModel cm = src.getColorModel(); if (cm != null) { if (cm.hasAlpha()) { return GraphicsUtil.sRGB_Unpre; } return GraphicsUtil.sRGB; } else { // No ColorModel so try to make some intelligent // decisions based just on the number of bands... // 1 bands -> replicated into RGB // 2 bands -> Band 0 replicated into RGB & Band 1 -> alpha premult // 3 bands -> sRGB (not-linear?) // 4 bands -> sRGB premult (not-linear?) SampleModel sm = src.getSampleModel(); switch (sm.getNumBands()) { case 1: return GraphicsUtil.sRGB; case 2: return GraphicsUtil.sRGB_Unpre; case 3: return GraphicsUtil.sRGB; default: return GraphicsUtil.sRGB_Unpre; } } }
This function 'fixes' the source's sample model. Right now it just selects if it should have 3 or 4 bands based on if the source had an alpha channel.
/** * This function 'fixes' the source's sample model. * Right now it just selects if it should have 3 or 4 bands * based on if the source had an alpha channel. */
protected static SampleModel fixSampleModel(CachableRed src) { SampleModel sm = src.getSampleModel(); ColorModel cm = src.getColorModel(); boolean alpha = false; if (cm != null) { alpha = cm.hasAlpha(); } else { switch (sm.getNumBands()) { case 1: case 3: alpha = false; break; default: alpha = true; break; } } if (alpha) { return new SinglePixelPackedSampleModel( DataBuffer.TYPE_INT, sm.getWidth(), sm.getHeight(), new int [] {0xFF0000, 0xFF00, 0xFF, 0xFF000000}); } else { return new SinglePixelPackedSampleModel( DataBuffer.TYPE_INT, sm.getWidth(), sm.getHeight(), new int [] {0xFF0000, 0xFF00, 0xFF}); } } }