org.apache.xmlgraphics.image.GraphicsUtil Maven / Gradle / Ivy
Show all versions of xmlgraphics-commons Show documentation
/*
* 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: GraphicsUtil.java 1732018 2016-02-24 04:51:06Z gadams $ */
package org.apache.xmlgraphics.image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferShort;
import java.awt.image.DataBufferUShort;
import java.awt.image.DirectColorModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;
import org.apache.xmlgraphics.image.rendered.Any2LsRGBRed;
import org.apache.xmlgraphics.image.rendered.Any2sRGBRed;
import org.apache.xmlgraphics.image.rendered.BufferedImageCachableRed;
import org.apache.xmlgraphics.image.rendered.CachableRed;
import org.apache.xmlgraphics.image.rendered.RenderedImageCachableRed;
// CSOFF: AvoidNestedBlocks
// CSOFF: ConstantName
// CSOFF: MethodName
// CSOFF: MultipleVariableDeclarations
// CSOFF: NeedBraces
// CSOFF: OneStatementPerLine
// CSOFF: OperatorWrap
// CSOFF: StaticVariableName
// CSOFF: WhitespaceAfter
// CSOFF: WhitespaceAround
/**
* Set of utility methods for Graphics.
* These generally bypass broken methods in Java2D or provide tweaked
* implementations.
*
* @version $Id: GraphicsUtil.java 1732018 2016-02-24 04:51:06Z gadams $
*
* Originally authored by Thomas DeWeese.
*/
public final class GraphicsUtil {
private GraphicsUtil() {
}
public static final AffineTransform IDENTITY = new AffineTransform();
/**
* Standard prebuilt Linear_sRGB color model with no alpha */
public static final ColorModel Linear_sRGB =
new DirectColorModel(ColorSpace.getInstance(
ColorSpace.CS_LINEAR_RGB), 24,
0x00FF0000, 0x0000FF00,
0x000000FF, 0x0, false,
DataBuffer.TYPE_INT);
/**
* Standard prebuilt Linear_sRGB color model with premultiplied alpha.
*/
public static final ColorModel Linear_sRGB_Pre =
new DirectColorModel(ColorSpace.getInstance(
ColorSpace.CS_LINEAR_RGB), 32,
0x00FF0000, 0x0000FF00,
0x000000FF, 0xFF000000, true,
DataBuffer.TYPE_INT);
/**
* Standard prebuilt Linear_sRGB color model with unpremultiplied alpha.
*/
public static final ColorModel Linear_sRGB_Unpre =
new DirectColorModel(ColorSpace.getInstance(
ColorSpace.CS_LINEAR_RGB), 32,
0x00FF0000, 0x0000FF00,
0x000000FF, 0xFF000000, false,
DataBuffer.TYPE_INT);
/**
* Standard prebuilt sRGB color model with no alpha.
*/
public static final ColorModel sRGB =
new DirectColorModel(ColorSpace.getInstance(
ColorSpace.CS_sRGB), 24,
0x00FF0000, 0x0000FF00,
0x000000FF, 0x0, false,
DataBuffer.TYPE_INT);
/**
* Standard prebuilt sRGB color model with premultiplied alpha.
*/
public static final ColorModel sRGB_Pre =
new DirectColorModel(ColorSpace.getInstance(
ColorSpace.CS_sRGB), 32,
0x00FF0000, 0x0000FF00,
0x000000FF, 0xFF000000, true,
DataBuffer.TYPE_INT);
/**
* Standard prebuilt sRGB color model with unpremultiplied alpha.
*/
public static final ColorModel sRGB_Unpre =
new DirectColorModel(ColorSpace.getInstance(
ColorSpace.CS_sRGB), 32,
0x00FF0000, 0x0000FF00,
0x000000FF, 0xFF000000, false,
DataBuffer.TYPE_INT);
/**
* Method that returns either Linear_sRGB_Pre or Linear_sRGB_UnPre
* based on premult flag.
* @param premult True if the ColorModel should have premultiplied alpha.
* @return a ColorMdoel with Linear sRGB colorSpace and
* the alpha channel set in accordance with
* premult
*/
public static ColorModel makeLinear_sRGBCM(boolean premult) {
return premult ? Linear_sRGB_Pre : Linear_sRGB_Unpre;
}
/**
* Constructs a BufferedImage with a linear sRGB colorModel, and alpha.
* @param width The desired width of the BufferedImage
* @param height The desired height of the BufferedImage
* @param premult The desired state of alpha premultiplied
* @return The requested BufferedImage.
*/
public static BufferedImage makeLinearBufferedImage(int width,
int height,
boolean premult) {
ColorModel cm = makeLinear_sRGBCM(premult);
WritableRaster wr = cm.createCompatibleWritableRaster(width, height);
return new BufferedImage(cm, wr, premult, null);
}
/**
* This method will return a CacheableRed that has it's data in
* the linear sRGB colorspace. If src is already in
* linear sRGB then this method does nothing and returns src.
* Otherwise it creates a transform that will convert
* src's output to linear sRGB and returns that CacheableRed.
*
* @param src The image to convert to linear sRGB.
* @return An equivilant image to src who's data is in
* linear sRGB.
*/
public static CachableRed convertToLsRGB(CachableRed src) {
ColorModel cm = src.getColorModel();
ColorSpace cs = cm.getColorSpace();
if (cs == ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB)) {
return src;
}
return new Any2LsRGBRed(src);
}
/**
* This method will return a CacheableRed that has it's data in
* the sRGB colorspace. If src is already in
* sRGB then this method does nothing and returns src.
* Otherwise it creates a transform that will convert
* src's output to sRGB and returns that CacheableRed.
*
* @param src The image to convert to sRGB.
* @return An equivilant image to src who's data is in sRGB.
*/
public static CachableRed convertTosRGB(CachableRed src) {
ColorModel cm = src.getColorModel();
ColorSpace cs = cm.getColorSpace();
if (cs == ColorSpace.getInstance(ColorSpace.CS_sRGB)) {
return src;
}
return new Any2sRGBRed(src);
}
/**
* Convertes any RenderedImage to a CacheableRed.
* If ri is already a CacheableRed it casts it down and
* returns it.
*
* In cases where ri is not already a CacheableRed it
* wraps ri with a helper class. The wrapped
* CacheableRed "Pretends" that it has no sources since it has no
* way of inteligently handling the dependency/dirty region calls
* if it exposed the source.
* @param ri The RenderedImage to convert.
* @return a CacheableRed that contains the same data as ri.
*/
public static CachableRed wrap(RenderedImage ri) {
if (ri instanceof CachableRed) {
return (CachableRed) ri;
}
if (ri instanceof BufferedImage) {
return new BufferedImageCachableRed((BufferedImage)ri);
}
return new RenderedImageCachableRed(ri);
}
/**
* An internal optimized version of copyData designed to work on
* Integer packed data with a SinglePixelPackedSampleModel. Only
* the region of overlap between src and dst is copied.
*
* Calls to this should be preflighted with is_INT_PACK_Data
* on both src and dest (requireAlpha can be false).
*
* @param src The source of the data
* @param dst The destination for the data.
*/
public static void copyData_INT_PACK(Raster src, WritableRaster dst) {
// System.out.println("Fast copyData");
int x0 = dst.getMinX();
if (x0 < src.getMinX()) {
x0 = src.getMinX();
}
int y0 = dst.getMinY();
if (y0 < src.getMinY()) {
y0 = src.getMinY();
}
int x1 = dst.getMinX() + dst.getWidth() - 1;
if (x1 > src.getMinX() + src.getWidth() - 1) {
x1 = src.getMinX() + src.getWidth() - 1;
}
int y1 = dst.getMinY() + dst.getHeight() - 1;
if (y1 > src.getMinY() + src.getHeight() - 1) {
y1 = src.getMinY() + src.getHeight() - 1;
}
int width = x1 - x0 + 1;
int height = y1 - y0 + 1;
SinglePixelPackedSampleModel srcSPPSM;
srcSPPSM = (SinglePixelPackedSampleModel)src.getSampleModel();
final int srcScanStride = srcSPPSM.getScanlineStride();
DataBufferInt srcDB = (DataBufferInt)src.getDataBuffer();
final int [] srcPixels = srcDB.getBankData()[0];
final int srcBase =
(srcDB.getOffset()
+ srcSPPSM.getOffset(x0 - src.getSampleModelTranslateX(),
y0 - src.getSampleModelTranslateY()));
SinglePixelPackedSampleModel dstSPPSM;
dstSPPSM = (SinglePixelPackedSampleModel)dst.getSampleModel();
final int dstScanStride = dstSPPSM.getScanlineStride();
DataBufferInt dstDB = (DataBufferInt)dst.getDataBuffer();
final int [] dstPixels = dstDB.getBankData()[0];
final int dstBase =
(dstDB.getOffset()
+ dstSPPSM.getOffset(x0 - dst.getSampleModelTranslateX(),
y0 - dst.getSampleModelTranslateY()));
if ((srcScanStride == dstScanStride)
&& (srcScanStride == width)) {
// System.out.println("VERY Fast copyData");
System.arraycopy(srcPixels, srcBase, dstPixels, dstBase,
width * height);
} else if (width > 128) {
int srcSP = srcBase;
int dstSP = dstBase;
for (int y = 0; y < height; y++) {
System.arraycopy(srcPixels, srcSP, dstPixels, dstSP, width);
srcSP += srcScanStride;
dstSP += dstScanStride;
}
} else {
for (int y = 0; y < height; y++) {
int srcSP = srcBase + y * srcScanStride;
int dstSP = dstBase + y * dstScanStride;
for (int x = 0; x < width; x++) {
dstPixels[dstSP++] = srcPixels[srcSP++];
}
}
}
}
public static void copyData_FALLBACK(Raster src, WritableRaster dst) {
// System.out.println("Fallback copyData");
int x0 = dst.getMinX();
if (x0 < src.getMinX()) {
x0 = src.getMinX();
}
int y0 = dst.getMinY();
if (y0 < src.getMinY()) {
y0 = src.getMinY();
}
int x1 = dst.getMinX() + dst.getWidth() - 1;
if (x1 > src.getMinX() + src.getWidth() - 1) {
x1 = src.getMinX() + src.getWidth() - 1;
}
int y1 = dst.getMinY() + dst.getHeight() - 1;
if (y1 > src.getMinY() + src.getHeight() - 1) {
y1 = src.getMinY() + src.getHeight() - 1;
}
int width = x1 - x0 + 1;
int [] data = null;
for (int y = y0; y <= y1; y++) {
data = src.getPixels(x0, y, width, 1, data);
dst.setPixels(x0, y, width, 1, data);
}
}
/**
* Copies data from one raster to another. Only the region of
* overlap between src and dst is copied. Src and
* Dst must have compatible SampleModels.
*
* @param src The source of the data
* @param dst The destination for the data.
*/
public static void copyData(Raster src, WritableRaster dst) {
if (is_INT_PACK_Data(src.getSampleModel(), false)
&& is_INT_PACK_Data(dst.getSampleModel(), false)) {
copyData_INT_PACK(src, dst);
return;
}
copyData_FALLBACK(src, dst);
}
/**
* Creates a new raster that has a copy of the data in
* ras. This is highly optimized for speed. There is
* no provision for changing any aspect of the SampleModel.
*
* This method should be used when you need to change the contents
* of a Raster that you do not "own" (ie the result of a
* getData call).
* @param ras The Raster to copy.
* @return A writable copy of ras
*/
public static WritableRaster copyRaster(Raster ras) {
return copyRaster(ras, ras.getMinX(), ras.getMinY());
}
/**
* Creates a new raster that has a copy of the data in
* ras. This is highly optimized for speed. There is
* no provision for changing any aspect of the SampleModel.
* However you can specify a new location for the returned raster.
*
* This method should be used when you need to change the contents
* of a Raster that you do not "own" (ie the result of a
* getData call).
*
* @param ras The Raster to copy.
*
* @param minX The x location for the upper left corner of the
* returned WritableRaster.
*
* @param minY The y location for the upper left corner of the
* returned WritableRaster.
*
* @return A writable copy of ras
*/
public static WritableRaster copyRaster(Raster ras, int minX, int minY) {
WritableRaster ret = Raster.createWritableRaster(
ras.getSampleModel(),
new Point(0, 0));
ret = ret.createWritableChild(
ras.getMinX() - ras.getSampleModelTranslateX(),
ras.getMinY() - ras.getSampleModelTranslateY(),
ras.getWidth(), ras.getHeight(),
minX, minY, null);
// Use System.arraycopy to copy the data between the two...
DataBuffer srcDB = ras.getDataBuffer();
DataBuffer retDB = ret.getDataBuffer();
if (srcDB.getDataType() != retDB.getDataType()) {
throw new IllegalArgumentException(
"New DataBuffer doesn't match original");
}
int len = srcDB.getSize();
int banks = srcDB.getNumBanks();
int [] offsets = srcDB.getOffsets();
for (int b = 0; b < banks; b++) {
switch (srcDB.getDataType()) {
case DataBuffer.TYPE_BYTE: {
DataBufferByte srcDBT = (DataBufferByte)srcDB;
DataBufferByte retDBT = (DataBufferByte)retDB;
System.arraycopy(srcDBT.getData(b), offsets[b],
retDBT.getData(b), offsets[b], len);
break;
}
case DataBuffer.TYPE_INT: {
DataBufferInt srcDBT = (DataBufferInt)srcDB;
DataBufferInt retDBT = (DataBufferInt)retDB;
System.arraycopy(srcDBT.getData(b), offsets[b],
retDBT.getData(b), offsets[b], len);
break;
}
case DataBuffer.TYPE_SHORT: {
DataBufferShort srcDBT = (DataBufferShort)srcDB;
DataBufferShort retDBT = (DataBufferShort)retDB;
System.arraycopy(srcDBT.getData(b), offsets[b],
retDBT.getData(b), offsets[b], len);
break;
}
case DataBuffer.TYPE_USHORT: {
DataBufferUShort srcDBT = (DataBufferUShort)srcDB;
DataBufferUShort retDBT = (DataBufferUShort)retDB;
System.arraycopy(srcDBT.getData(b), offsets[b],
retDBT.getData(b), offsets[b], len);
break;
}
default:
throw new
UnsupportedOperationException("unsupported data type: "
+ srcDB.getDataType());
}
}
return ret;
}
/**
* Coerces ras to be writable. The returned Raster continues to
* reference the DataBuffer from ras, so modifications to the returned
* WritableRaster will be seen in ras.
*
* This method should only be used if you need a WritableRaster due to
* an interface (such as to construct a BufferedImage), but have no
* intention of modifying the contents of the returned Raster. If
* you have any doubt about other users of the data in ras,
* use copyRaster (above).
* @param ras The raster to make writable.
* @return A Writable version of ras (shares DataBuffer with
* ras).
*/
public static WritableRaster makeRasterWritable(Raster ras) {
return makeRasterWritable(ras, ras.getMinX(), ras.getMinY());
}
/**
* Coerces ras to be writable. The returned Raster continues to
* reference the DataBuffer from ras, so modifications to the returned
* WritableRaster will be seen in ras.
*
* You can specify a new location for the returned WritableRaster, this
* is especially useful for constructing BufferedImages which require
* the Raster to be at (0,0).
*
* This method should only be used if you need a WritableRaster due to
* an interface (such as to construct a BufferedImage), but have no
* intention of modifying the contents of the returned Raster. If
* you have any doubt about other users of the data in ras,
* use copyRaster (above).
*
* @param ras The raster to make writable.
*
* @param minX The x location for the upper left corner of the
* returned WritableRaster.
*
* @param minY The y location for the upper left corner of the
* returned WritableRaster.
*
* @return A Writable version of ras with it's upper left
* hand coordinate set to minX, minY (shares it's DataBuffer
* with ras).
*/
public static WritableRaster makeRasterWritable(Raster ras,
int minX, int minY) {
WritableRaster ret = Raster.createWritableRaster(
ras.getSampleModel(),
ras.getDataBuffer(),
new Point(0, 0));
ret = ret.createWritableChild(
ras.getMinX() - ras.getSampleModelTranslateX(),
ras.getMinY() - ras.getSampleModelTranslateY(),
ras.getWidth(), ras.getHeight(),
minX, minY, null);
return ret;
}
/**
* Create a new ColorModel with it's alpha premultiplied state matching
* newAlphaPreMult.
* @param cm The ColorModel to change the alpha premult state of.
* @param newAlphaPreMult The new state of alpha premult.
* @return A new colorModel that has isAlphaPremultiplied()
* equal to newAlphaPreMult.
*/
public static ColorModel
coerceColorModel(ColorModel cm, boolean newAlphaPreMult) {
if (cm.isAlphaPremultiplied() == newAlphaPreMult) {
return cm;
}
// Easiest way to build proper colormodel for new Alpha state...
// Eventually this should switch on known ColorModel types and
// only fall back on this hack when the CM type is unknown.
WritableRaster wr = cm.createCompatibleWritableRaster(1, 1);
return cm.coerceData(wr, newAlphaPreMult);
}
/**
* Coerces data within a bufferedImage to match newAlphaPreMult,
* Note that this can not change the colormodel of bi so you
*
* @param wr The raster to change the state of.
* @param cm The colormodel currently associated with data in wr.
* @param newAlphaPreMult The desired state of alpha Premult for raster.
* @return A new colormodel that matches newAlphaPreMult.
*/
public static ColorModel
coerceData(WritableRaster wr, ColorModel cm, boolean newAlphaPreMult) {
// System.out.println("CoerceData: " + cm.isAlphaPremultiplied() +
// " Out: " + newAlphaPreMult);
if (!cm.hasAlpha()) {
// Nothing to do no alpha channel
return cm;
}
if (cm.isAlphaPremultiplied() == newAlphaPreMult) {
// nothing to do alpha state matches...
return cm;
}
// System.out.println("CoerceData: " + wr.getSampleModel());
if (newAlphaPreMult) {
multiplyAlpha(wr);
} else {
divideAlpha(wr);
}
return coerceColorModel(cm, newAlphaPreMult);
}
public static void multiplyAlpha(WritableRaster wr) {
if (is_BYTE_COMP_Data(wr.getSampleModel())) {
mult_BYTE_COMP_Data(wr);
} else if (is_INT_PACK_Data(wr.getSampleModel(), true)) {
mult_INT_PACK_Data(wr);
} else {
int [] pixel = null;
int bands = wr.getNumBands();
float norm = 1f / 255f;
int x0;
int x1;
int y0;
int y1;
int a;
int b;
float alpha;
x0 = wr.getMinX();
x1 = x0 + wr.getWidth();
y0 = wr.getMinY();
y1 = y0 + wr.getHeight();
for (int y = y0; y < y1; y++) {
for (int x = x0; x < x1; x++) {
pixel = wr.getPixel(x, y, pixel);
a = pixel[bands - 1];
if ((a >= 0) && (a < 255)) {
alpha = a * norm;
for (b = 0; b < bands - 1; b++) {
pixel[b] = (int)(pixel[b] * alpha + 0.5f);
}
wr.setPixel(x, y, pixel);
}
}
}
}
}
public static void divideAlpha(WritableRaster wr) {
if (is_BYTE_COMP_Data(wr.getSampleModel())) {
divide_BYTE_COMP_Data(wr);
} else if (is_INT_PACK_Data(wr.getSampleModel(), true)) {
divide_INT_PACK_Data(wr);
} else {
int x0;
int x1;
int y0;
int y1;
int a;
int b;
float ialpha;
int bands = wr.getNumBands();
int [] pixel = null;
x0 = wr.getMinX();
x1 = x0 + wr.getWidth();
y0 = wr.getMinY();
y1 = y0 + wr.getHeight();
for (int y = y0; y < y1; y++) {
for (int x = x0; x < x1; x++) {
pixel = wr.getPixel(x, y, pixel);
a = pixel[bands - 1];
if ((a > 0) && (a < 255)) {
ialpha = 255 / (float)a;
for (b = 0; b < bands - 1; b++) {
pixel[b] = (int)(pixel[b] * ialpha + 0.5f);
}
wr.setPixel(x, y, pixel);
}
}
}
}
}
/**
* Copies data from one bufferedImage to another paying attention
* to the state of AlphaPreMultiplied.
*
* @param src The source
* @param dst The destination
*/
public static void
copyData(BufferedImage src, BufferedImage dst) {
Rectangle srcRect = new Rectangle(0, 0,
src.getWidth(), src.getHeight());
copyData(src, srcRect, dst, new Point(0, 0));
}
/**
* Copies data from one bufferedImage to another paying attention
* to the state of AlphaPreMultiplied.
*
* @param src The source
* @param srcRect The Rectangle of source data to be copied
* @param dst The destination
* @param destP The Place for the upper left corner of srcRect in dst.
*/
public static void
copyData(BufferedImage src, Rectangle srcRect,
BufferedImage dst, Point destP) {
/*
if (srcCS != dstCS)
throw new IllegalArgumentException
("Images must be in the same ColorSpace in order "+
"to copy Data between them");
*/
boolean srcAlpha = src.getColorModel().hasAlpha();
boolean dstAlpha = dst.getColorModel().hasAlpha();
// System.out.println("Src has: " + srcAlpha +
// " is: " + src.isAlphaPremultiplied());
//
// System.out.println("Dst has: " + dstAlpha +
// " is: " + dst.isAlphaPremultiplied());
if (srcAlpha == dstAlpha) {
if (!srcAlpha
|| src.isAlphaPremultiplied() == dst.isAlphaPremultiplied()) {
// They match one another so just copy everything...
copyData(src.getRaster(), dst.getRaster());
return;
}
}
// System.out.println("Using Slow CopyData");
int [] pixel = null;
Raster srcR = src.getRaster();
WritableRaster dstR = dst.getRaster();
int bands = dstR.getNumBands();
int dx = destP.x - srcRect.x;
int dy = destP.y - srcRect.y;
int w = srcRect.width;
int x0 = srcRect.x;
int y0 = srcRect.y;
int y1 = y0 + srcRect.height - 1;
if (!srcAlpha) {
// Src has no alpha dest does so set alpha to 1.0 everywhere.
// System.out.println("Add Alpha");
int [] oPix = new int[bands * w];
int out = (w * bands) - 1; // The 2 skips alpha channel
while (out >= 0) {
// Fill alpha channel with 255's
oPix[out] = 255;
out -= bands;
}
int b;
int in;
for (int y = y0; y <= y1; y++) {
pixel = srcR.getPixels(x0, y, w, 1, pixel);
in = w * (bands - 1) - 1;
out = (w * bands) - 2; // The 2 skips alpha channel on last pix
switch (bands) {
case 4:
while (in >= 0) {
oPix[out--] = pixel[in--];
oPix[out--] = pixel[in--];
oPix[out--] = pixel[in--];
out--;
}
break;
default:
while (in >= 0) {
for (b = 0; b < bands - 1; b++) {
oPix[out--] = pixel[in--];
}
out--;
}
}
dstR.setPixels(x0 + dx, y + dy, w, 1, oPix);
}
} else if (dstAlpha && dst.isAlphaPremultiplied()) {
// Src and dest have Alpha but we need to multiply it for dst.
// System.out.println("Mult Case");
int a;
int b;
int alpha;
int in;
int fpNorm = (1 << 24) / 255;
int pt5 = 1 << 23;
for (int y = y0; y <= y1; y++) {
pixel = srcR.getPixels(x0, y, w, 1, pixel);
in = bands * w - 1;
switch (bands) {
case 4:
while (in >= 0) {
a = pixel[in];
if (a == 255) {
in -= 4;
} else {
in--;
alpha = fpNorm * a;
pixel[in] = (pixel[in] * alpha + pt5) >>> 24;
in--;
pixel[in] = (pixel[in] * alpha + pt5) >>> 24;
in--;
pixel[in] = (pixel[in] * alpha + pt5) >>> 24;
in--;
}
}
break;
default:
while (in >= 0) {
a = pixel[in];
if (a == 255) {
in -= bands;
} else {
in--;
alpha = fpNorm * a;
for (b = 0; b < bands - 1; b++) {
pixel[in] = (pixel[in] * alpha + pt5) >>> 24;
in--;
}
}
}
}
dstR.setPixels(x0 + dx, y + dy, w, 1, pixel);
}
} else if (dstAlpha && !dst.isAlphaPremultiplied()) {
// Src and dest have Alpha but we need to divide it out for dst.
// System.out.println("Div Case");
int a;
int b;
int ialpha;
int in;
int fpNorm = 0x00FF0000;
int pt5 = 1 << 15;
for (int y = y0; y <= y1; y++) {
pixel = srcR.getPixels(x0, y, w, 1, pixel);
in = (bands * w) - 1;
switch(bands) {
case 4:
while (in >= 0) {
a = pixel[in];
if ((a <= 0) || (a >= 255)) {
in -= 4;
} else {
in--;
ialpha = fpNorm / a;
pixel[in] = (pixel[in] * ialpha + pt5) >>> 16;
in--;
pixel[in] = (pixel[in] * ialpha + pt5) >>> 16;
in--;
pixel[in] = (pixel[in] * ialpha + pt5) >>> 16;
in--;
}
}
break;
default:
while (in >= 0) {
a = pixel[in];
if ((a <= 0) || (a >= 255)) {
in -= bands;
} else {
in--;
ialpha = fpNorm / a;
for (b = 0; b < bands - 1; b++) {
pixel[in] = (pixel[in] * ialpha + pt5) >>> 16;
in--;
}
}
}
}
dstR.setPixels(x0 + dx, y + dy, w, 1, pixel);
}
} else if (src.isAlphaPremultiplied()) {
int [] oPix = new int[bands * w];
// Src has alpha dest does not so unpremult and store...
// System.out.println("Remove Alpha, Div Case");
int a;
int b;
int ialpha;
int in;
int out;
int fpNorm = 0x00FF0000;
int pt5 = 1 << 15;
for (int y = y0; y <= y1; y++) {
pixel = srcR.getPixels(x0, y, w, 1, pixel);
in = (bands + 1) * w - 1;
out = (bands * w) - 1;
while (in >= 0) {
a = pixel[in];
in--;
if (a > 0) {
if (a < 255) {
ialpha = fpNorm / a;
for (b = 0; b < bands; b++) {
oPix[out--] = (pixel[in--] * ialpha + pt5) >>> 16;
}
} else {
for (b = 0; b < bands; b++) {
oPix[out--] = pixel[in--];
}
}
} else {
in -= bands;
for (b = 0; b < bands; b++) {
oPix[out--] = 255;
}
}
}
dstR.setPixels(x0 + dx, y + dy, w, 1, oPix);
}
} else {
// Src has unpremult alpha, dest does not have alpha,
// just copy the color channels over.
Rectangle dstRect = new Rectangle(destP.x, destP.y,
srcRect.width, srcRect.height);
for (int b = 0; b < bands; b++) {
copyBand(srcR, srcRect, b,
dstR, dstRect, b);
}
}
}
public static void copyBand(Raster src, int srcBand,
WritableRaster dst, int dstBand) {
Rectangle sR = src.getBounds();
Rectangle dR = dst.getBounds();
Rectangle cpR = sR.intersection(dR);
copyBand(src, cpR, srcBand, dst, cpR, dstBand);
}
public static void copyBand(Raster src, Rectangle sR, int sBand,
WritableRaster dst, Rectangle dR, int dBand) {
int dy = dR.y - sR.y;
int dx = dR.x - sR.x;
sR = sR.intersection(src.getBounds());
dR = dR.intersection(dst.getBounds());
int width;
int height;
if (dR.width < sR.width) {
width = dR.width;
} else {
width = sR.width;
}
if (dR.height < sR.height) {
height = dR.height;
} else {
height = sR.height;
}
int x = sR.x + dx;
int [] samples = null;
for (int y = sR.y; y < sR.y + height; y++) {
samples = src.getSamples(sR.x, y, width, 1, sBand, samples);
dst.setSamples(x, y + dy, width, 1, dBand, samples);
}
}
public static boolean is_INT_PACK_Data(SampleModel sm,
boolean requireAlpha) {
// Check ColorModel is of type DirectColorModel
if (!(sm instanceof SinglePixelPackedSampleModel)) {
return false;
}
// Check transfer type
if (sm.getDataType() != DataBuffer.TYPE_INT) {
return false;
}
SinglePixelPackedSampleModel sppsm;
sppsm = (SinglePixelPackedSampleModel)sm;
int [] masks = sppsm.getBitMasks();
if (masks.length == 3) {
if (requireAlpha) {
return false;
}
} else if (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;
}
public static boolean is_BYTE_COMP_Data(SampleModel sm) {
// Check ColorModel is of type DirectColorModel
if (!(sm instanceof ComponentSampleModel)) {
return false;
}
// Check transfer type
if (sm.getDataType() != DataBuffer.TYPE_BYTE) {
return false;
}
return true;
}
protected static void divide_INT_PACK_Data(WritableRaster wr) {
// System.out.println("Divide Int");
SinglePixelPackedSampleModel sppsm;
sppsm = (SinglePixelPackedSampleModel)wr.getSampleModel();
final int width = wr.getWidth();
final int scanStride = sppsm.getScanlineStride();
DataBufferInt db = (DataBufferInt)wr.getDataBuffer();
final int base
= (db.getOffset()
+ sppsm.getOffset(wr.getMinX() - wr.getSampleModelTranslateX(),
wr.getMinY() - wr.getSampleModelTranslateY()));
// Access the pixel data array
final int[] pixels = db.getBankData()[0];
for (int y = 0; y < wr.getHeight(); y++) {
int sp = base + y * scanStride;
final int end = sp + width;
while (sp < end) {
int pixel = pixels[sp];
int a = pixel >>> 24;
if (a <= 0) {
pixels[sp] = 0x00FFFFFF;
} else if (a < 255) {
int aFP = (0x00FF0000 / a);
pixels[sp] =
((a << 24)
| (((((pixel & 0xFF0000) >> 16) * aFP) & 0xFF0000))
| (((((pixel & 0x00FF00) >> 8) * aFP) & 0xFF0000) >> 8)
| (((((pixel & 0x0000FF)) * aFP) & 0xFF0000) >> 16));
}
sp++;
}
}
}
protected static void mult_INT_PACK_Data(WritableRaster wr) {
// System.out.println("Multiply Int: " + wr);
SinglePixelPackedSampleModel sppsm;
sppsm = (SinglePixelPackedSampleModel)wr.getSampleModel();
final int width = wr.getWidth();
final int scanStride = sppsm.getScanlineStride();
DataBufferInt db = (DataBufferInt)wr.getDataBuffer();
final int base
= (db.getOffset()
+ sppsm.getOffset(wr.getMinX() - wr.getSampleModelTranslateX(),
wr.getMinY() - wr.getSampleModelTranslateY()));
// Access the pixel data array
final int[] pixels = db.getBankData()[0];
for (int y = 0; y < wr.getHeight(); y++) {
int sp = base + y * scanStride;
final int end = sp + width;
while (sp < end) {
int pixel = pixels[sp];
int a = pixel >>> 24;
if ((a >= 0) && (a < 255)) { // this does NOT include a == 255 (0xff) !
pixels[sp] = ((a << 24)
| ((((pixel & 0xFF0000) * a) >> 8) & 0xFF0000)
| ((((pixel & 0x00FF00) * a) >> 8) & 0x00FF00)
| ((((pixel & 0x0000FF) * a) >> 8) & 0x0000FF));
}
sp++;
}
}
}
protected static void divide_BYTE_COMP_Data(WritableRaster wr) {
// System.out.println("Multiply Int: " + wr);
ComponentSampleModel csm;
csm = (ComponentSampleModel)wr.getSampleModel();
final int width = wr.getWidth();
final int scanStride = csm.getScanlineStride();
final int pixStride = csm.getPixelStride();
final int [] bandOff = csm.getBandOffsets();
DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
final int base
= (db.getOffset()
+ csm.getOffset(wr.getMinX() - wr.getSampleModelTranslateX(),
wr.getMinY() - wr.getSampleModelTranslateY()));
int aOff = bandOff[bandOff.length - 1];
int bands = bandOff.length - 1;
// Access the pixel data array
final byte[] pixels = db.getBankData()[0];
for (int y = 0; y < wr.getHeight(); y++) {
int sp = base + y * scanStride;
final int end = sp + width * pixStride;
while (sp < end) {
int a = pixels[sp + aOff] & 0xFF;
if (a == 0) {
for (int b = 0; b < bands; b++) {
pixels[sp + bandOff[b]] = (byte)0xFF;
}
} else if (a < 255) { // this does NOT include a == 255 (0xff) !
int aFP = (0x00FF0000 / a);
for (int b = 0; b < bands; b++) {
int i = sp + bandOff[b];
pixels[i] = (byte)(((pixels[i] & 0xFF) * aFP) >>> 16);
}
}
sp += pixStride;
}
}
}
protected static void mult_BYTE_COMP_Data(WritableRaster wr) {
// System.out.println("Multiply Int: " + wr);
ComponentSampleModel csm;
csm = (ComponentSampleModel)wr.getSampleModel();
final int width = wr.getWidth();
final int scanStride = csm.getScanlineStride();
final int pixStride = csm.getPixelStride();
final int [] bandOff = csm.getBandOffsets();
DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
final int base
= (db.getOffset()
+ csm.getOffset(wr.getMinX() - wr.getSampleModelTranslateX(),
wr.getMinY() - wr.getSampleModelTranslateY()));
int aOff = bandOff[bandOff.length - 1];
int bands = bandOff.length - 1;
// Access the pixel data array
final byte[] pixels = db.getBankData()[0];
for (int y = 0; y < wr.getHeight(); y++) {
int sp = base + y * scanStride;
final int end = sp + width * pixStride;
while (sp < end) {
int a = pixels[sp + aOff] & 0xFF;
if (a != 0xFF) {
for (int b = 0; b < bands; b++) {
int i = sp + bandOff[b];
pixels[i] = (byte)(((pixels[i] & 0xFF) * a) >> 8);
}
}
sp += pixStride;
}
}
}
/*
This is skanky debugging code that might be useful in the future:
if (count == 33) {
String label = "sub [" + x + ", " + y + "]: ";
org.ImageDisplay.showImage
(label, subBI);
org.ImageDisplay.printImage
(label, subBI,
new Rectangle(75-iR.x, 90-iR.y, 32, 32));
}
// if ((count++ % 50) == 10)
// org.ImageDisplay.showImage("foo: ", subBI);
Graphics2D realG2D = g2d;
while (realG2D instanceof sun.java2d.ProxyGraphics2D) {
realG2D = ((sun.java2d.ProxyGraphics2D)realG2D).getDelegate();
}
if (realG2D instanceof sun.awt.image.BufferedImageGraphics2D) {
count++;
if (count == 34) {
RenderedImage ri;
ri = ((sun.awt.image.BufferedImageGraphics2D)realG2D).bufImg;
// g2d.setComposite(SVGComposite.OVER);
// org.ImageDisplay.showImage("Bar: " + count, cr);
org.ImageDisplay.printImage("Bar: " + count, cr,
new Rectangle(75, 90, 32, 32));
org.ImageDisplay.showImage ("Foo: " + count, ri);
org.ImageDisplay.printImage("Foo: " + count, ri,
new Rectangle(75, 90, 32, 32));
System.out.println("BI: " + ri);
System.out.println("BISM: " + ri.getSampleModel());
System.out.println("BICM: " + ri.getColorModel());
System.out.println("BICM class: " + ri.getColorModel().getClass());
System.out.println("BICS: " + ri.getColorModel().getColorSpace());
System.out.println
("sRGB CS: " +
ColorSpace.getInstance(ColorSpace.CS_sRGB));
System.out.println("G2D info");
System.out.println("\tComposite: " + g2d.getComposite());
System.out.println("\tTransform" + g2d.getTransform());
java.awt.RenderingHints rh = g2d.getRenderingHints();
java.util.Set keys = rh.keySet();
java.util.Iterator iter = keys.iterator();
while (iter.hasNext()) {
Object o = iter.next();
System.out.println("\t" + o.toString() + " -> " +
rh.get(o).toString());
}
ri = cr;
System.out.println("RI: " + ri);
System.out.println("RISM: " + ri.getSampleModel());
System.out.println("RICM: " + ri.getColorModel());
System.out.println("RICM class: " + ri.getColorModel().getClass());
System.out.println("RICS: " + ri.getColorModel().getColorSpace());
}
}
*/
/**
* Extracts an alpha raster from a RenderedImage. The method tries to avoid copying data
* unnecessarily by checking if the RenderedImage is a BufferedImage which offers suitable
* direct methods.
* @param image the image
* @return the alpha raster
*/
public static Raster getAlphaRaster(RenderedImage image) {
ColorModel cm = image.getColorModel();
if (!cm.hasAlpha() || cm.getTransparency() != ColorModel.TRANSLUCENT) {
throw new IllegalStateException("Image doesn't have an alpha channel");
}
Raster alpha;
if (image instanceof BufferedImage) {
//Optimization possible with BufferedImage (No copying)
alpha = ((BufferedImage)image).getAlphaRaster();
} else {
WritableRaster wraster = GraphicsUtil.makeRasterWritable(image.getData());
alpha = image.getColorModel().getAlphaRaster(wraster);
}
return alpha;
}
}