| /* |
| |
| 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. |
| |
| */ |
| package org.apache.batik.ext.awt.image.rendered; |
| |
| import java.awt.Graphics2D; |
| import java.awt.Point; |
| import java.awt.RenderingHints; |
| import java.awt.Transparency; |
| import java.awt.color.ColorSpace; |
| import java.awt.image.BandedSampleModel; |
| import java.awt.image.BufferedImage; |
| import java.awt.image.ColorConvertOp; |
| import java.awt.image.ColorModel; |
| import java.awt.image.ComponentColorModel; |
| import java.awt.image.DataBuffer; |
| import java.awt.image.DataBufferByte; |
| import java.awt.image.DirectColorModel; |
| import java.awt.image.Raster; |
| import java.awt.image.RenderedImage; |
| import java.awt.image.WritableRaster; |
| |
| import org.apache.batik.ext.awt.color.ICCColorSpaceExt; |
| |
| |
| /** |
| * This implementation of rendered image forces a color profile |
| * on its source |
| * |
| * @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a> |
| * @version $Id$ |
| */ |
| public class ProfileRed extends AbstractRed { |
| private static final ColorSpace sRGBCS |
| = ColorSpace.getInstance(ColorSpace.CS_sRGB); |
| private static final ColorModel sRGBCM |
| = new DirectColorModel(sRGBCS, |
| 32, |
| 0x00ff0000, |
| 0x0000ff00, |
| 0x000000ff, |
| 0xff000000, |
| false, |
| DataBuffer.TYPE_INT); |
| |
| private ICCColorSpaceExt colorSpace; |
| |
| /** |
| * @param src Images on which the input ColorSpace should |
| * be forced |
| * @param colorSpace colorSpace that should be forced on the |
| * source |
| */ |
| public ProfileRed(CachableRed src, |
| ICCColorSpaceExt colorSpace){ |
| this.colorSpace = colorSpace; |
| |
| init(src, src.getBounds(), |
| sRGBCM, sRGBCM.createCompatibleSampleModel(src.getWidth(), |
| src.getHeight()), |
| src.getTileGridXOffset(), src.getTileGridYOffset(), null); |
| |
| } |
| |
| public CachableRed getSource() { |
| return (CachableRed)getSources().get(0); |
| } |
| |
| /** |
| * This method will turn the input image in an sRGB image as follows. |
| * If there is no colorSpace defined, then the input image is |
| * simply converted to singlePixelPacked sRGB if needed. |
| * If there is a colorSpace defined, the the image data is 'interpreted' |
| * as being in that space, instead of that of the image's colorSpace. |
| * |
| * Here is how the input image is processed: |
| * a. It is converted to using a ComponentColorModel |
| * b. Its data is extracted, ignoring it's ColorSpace |
| * c. A new ComponentColorModel is built for the replacing colorSpace |
| * Note that if the number of components in the input image and |
| * the number of components in the replacing ColorSpace do not |
| * match, it is not possible to apply the conversion. |
| * d. A new BufferedImage is built, using the new |
| * ComponentColorModel and the data from the original image |
| * converted to the ComponentColorModel built in a. The alpha |
| * channel is excluded from that new BufferedImage. |
| * e. The BufferedImage created in d. is converted to sRGB using |
| * ColorConvertOp |
| * f. The alpha channel information is integrated back into the image. |
| * |
| * IMPORTANT NOTE: The code uses a BandedSampleModel in c.) and |
| * d.) and discard the alpha channel during the color conversions |
| * (it is restored in f.)), because of bugs in the interleaved |
| * model with alpha. The BandedSampleModel did not cause any bug |
| * as of JDK 1.3. |
| */ |
| public WritableRaster copyData(WritableRaster argbWR){ |
| try{ |
| RenderedImage img = getSource(); |
| |
| /** |
| * Check that the number of color components match in the input |
| * image and in the replacing profile. |
| */ |
| ColorModel imgCM = img.getColorModel(); |
| ColorSpace imgCS = imgCM.getColorSpace(); |
| int nImageComponents = imgCS.getNumComponents(); |
| int nProfileComponents = colorSpace.getNumComponents(); |
| if(nImageComponents != nProfileComponents){ |
| // Should we go in error???? Here we simply trace an error |
| // and return null |
| System.err.println("Input image and associated color profile have" + |
| " mismatching number of color components: conversion is not possible"); |
| return argbWR; |
| } |
| |
| /** |
| * Get the data from the source for the requested region |
| */ |
| int w = argbWR.getWidth(); |
| int h = argbWR.getHeight(); |
| int minX = argbWR.getMinX(); |
| int minY = argbWR.getMinY(); |
| WritableRaster srcWR = |
| imgCM.createCompatibleWritableRaster(w, h); |
| srcWR = srcWR.createWritableTranslatedChild(minX, minY); |
| img.copyData(srcWR); |
| |
| /** |
| * If the source data is not a ComponentColorModel using a |
| * BandedSampleModel, do the conversion now. |
| */ |
| if(!(imgCM instanceof ComponentColorModel) || |
| !(img.getSampleModel() instanceof BandedSampleModel) || |
| (imgCM.hasAlpha() && imgCM.isAlphaPremultiplied() )) { |
| ComponentColorModel imgCompCM |
| = new ComponentColorModel |
| (imgCS, // Same ColorSpace as img |
| imgCM.getComponentSize(), // Number of bits/comp |
| imgCM.hasAlpha(), // Same alpha as img |
| false, // unpremult alpha (so we can remove it next). |
| imgCM.getTransparency(), // Same trans as img |
| DataBuffer.TYPE_BYTE); // 8 bit/component. |
| |
| WritableRaster wr = Raster.createBandedRaster |
| (DataBuffer.TYPE_BYTE, |
| argbWR.getWidth(), argbWR.getHeight(), |
| imgCompCM.getNumComponents(), |
| new Point(0, 0)); |
| |
| BufferedImage imgComp = new BufferedImage |
| (imgCompCM, wr, imgCompCM.isAlphaPremultiplied(), null); |
| |
| BufferedImage srcImg = new BufferedImage |
| (imgCM, srcWR.createWritableTranslatedChild(0, 0), |
| imgCM.isAlphaPremultiplied(), null); |
| |
| Graphics2D g = imgComp.createGraphics(); |
| g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, |
| RenderingHints.VALUE_COLOR_RENDER_QUALITY); |
| g.drawImage(srcImg, 0, 0, null); |
| img = imgComp; |
| imgCM = imgCompCM; |
| srcWR = wr.createWritableTranslatedChild(minX, minY); |
| } |
| |
| /** |
| * Now, the input image is using a component color |
| * model. We can therefore create an image with the new |
| * profile, using a ComponentColorModel as well, because |
| * we know the number of components match (this was |
| * checked at the begining of this routine). */ |
| ComponentColorModel newCM = new ComponentColorModel |
| (colorSpace, // **** New ColorSpace **** |
| imgCM.getComponentSize(), // Array of number of bits per components |
| false, // No alpa |
| false, // Not premultiplied |
| Transparency.OPAQUE, // No transparency |
| DataBuffer.TYPE_BYTE); // 8 Bits |
| |
| // Build a raster with bands 0, 1 and 2 of img's raster |
| DataBufferByte data = (DataBufferByte)srcWR.getDataBuffer(); |
| srcWR = Raster.createBandedRaster |
| (data, argbWR.getWidth(), argbWR.getHeight(), |
| argbWR.getWidth(), new int[]{0, 1, 2}, |
| new int[]{0, 0, 0}, new Point(0, 0)); |
| BufferedImage newImg = new BufferedImage |
| (newCM, srcWR, newCM.isAlphaPremultiplied(), null); |
| |
| /** |
| * Now, convert the image to sRGB |
| */ |
| ComponentColorModel sRGBCompCM = new ComponentColorModel |
| (ColorSpace.getInstance(ColorSpace.CS_sRGB), |
| new int[]{8, 8, 8}, |
| false, |
| false, |
| Transparency.OPAQUE, |
| DataBuffer.TYPE_BYTE); |
| |
| WritableRaster wr = Raster.createBandedRaster |
| (DataBuffer.TYPE_BYTE, argbWR.getWidth(), argbWR.getHeight(), |
| sRGBCompCM.getNumComponents(), new Point(0, 0)); |
| |
| BufferedImage sRGBImage = new BufferedImage |
| (sRGBCompCM, wr, false, null); |
| ColorConvertOp colorConvertOp = new ColorConvertOp(null); |
| colorConvertOp.filter(newImg, sRGBImage); |
| |
| /** |
| * Integrate alpha back into the image if there is any |
| */ |
| if (imgCM.hasAlpha()){ |
| DataBufferByte rgbData = (DataBufferByte)wr.getDataBuffer(); |
| byte[][] imgBanks = data.getBankData(); |
| byte[][] rgbBanks = rgbData.getBankData(); |
| |
| byte[][] argbBanks = {rgbBanks[0], rgbBanks[1], |
| rgbBanks[2], imgBanks[3]}; |
| DataBufferByte argbData = new DataBufferByte(argbBanks, imgBanks[0].length); |
| srcWR = Raster.createBandedRaster |
| (argbData, argbWR.getWidth(), argbWR.getHeight(), |
| argbWR.getWidth(), new int[]{0, 1, 2, 3}, |
| new int[]{0, 0, 0, 0}, new Point(0, 0)); |
| sRGBCompCM = new ComponentColorModel |
| (ColorSpace.getInstance(ColorSpace.CS_sRGB), |
| new int[]{8, 8, 8, 8}, |
| true, |
| false, |
| Transparency.TRANSLUCENT, |
| DataBuffer.TYPE_BYTE); |
| sRGBImage = new BufferedImage(sRGBCompCM, |
| srcWR, |
| false, |
| null); |
| |
| } |
| |
| /*BufferedImage result = new BufferedImage(img.getWidth(), |
| img.getHeight(), |
| BufferedImage.TYPE_INT_ARGB);*/ |
| BufferedImage result = new BufferedImage(sRGBCM, |
| argbWR.createWritableTranslatedChild(0, 0), |
| false, |
| null); |
| |
| |
| /////////////////////////////////////////////// |
| // BUG IN ColorConvertOp: The following breaks: |
| // colorConvertOp.filter(sRGBImage, result); |
| // |
| // Using Graphics2D instead.... |
| /////////////////////////////////////////////// |
| Graphics2D g = result.createGraphics(); |
| g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, |
| RenderingHints.VALUE_COLOR_RENDER_QUALITY); |
| g.drawImage(sRGBImage, 0, 0, null); |
| g.dispose(); |
| |
| return argbWR; |
| }catch(Exception e){ |
| e.printStackTrace(); |
| throw new Error( e.getMessage() ); |
| } |
| } |
| |
| } |