/*
* 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.
/**
* 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 /**
* 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.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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.
Params: - premult – True if the ColorModel should have premultiplied alpha.
Returns: a ColorMdoel with Linear sRGB colorSpace and
the alpha channel set in accordance with
premult
/**
* 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
* <tt>premult</tt>
*/
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.
Params: - width – The desired width of the BufferedImage
- height – The desired height of the BufferedImage
- premult – The desired state of alpha premultiplied
Returns: The requested BufferedImage.
/**
* 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.
Params: - src – The image to convert to linear sRGB.
Returns: An equivilant image to src who's data is in
linear sRGB.
/**
* This method will return a CacheableRed that has it's data in
* the linear sRGB colorspace. If <tt>src</tt> is already in
* linear sRGB then this method does nothing and returns <tt>src</tt>.
* Otherwise it creates a transform that will convert
* <tt>src</tt>'s output to linear sRGB and returns that CacheableRed.
*
* @param src The image to convert to linear sRGB.
* @return An equivilant image to <tt>src</tt> 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.
Params: - src – The image to convert to sRGB.
Returns: An equivilant image to src who's data is in sRGB.
/**
* This method will return a CacheableRed that has it's data in
* the sRGB colorspace. If <tt>src</tt> is already in
* sRGB then this method does nothing and returns <tt>src</tt>.
* Otherwise it creates a transform that will convert
* <tt>src</tt>'s output to sRGB and returns that CacheableRed.
*
* @param src The image to convert to sRGB.
* @return An equivilant image to <tt>src</tt> 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.
Params: - ri – The RenderedImage to convert.
Returns: a CacheableRed that contains the same data as ri.
/**
* Convertes any RenderedImage to a CacheableRed. <p>
* If <tt>ri</tt> is already a CacheableRed it casts it down and
* returns it.<p>
*
* In cases where <tt>ri</tt> is not already a CacheableRed it
* wraps <tt>ri</tt> 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).
Params: - src – The source of the data
- dst – The destination for the data.
/**
* 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.
Params: - src – The source of the data
- dst – The destination for the data.
/**
* Copies data from one raster to another. Only the region of
* overlap between src and dst is copied. <tt>Src</tt> and
* <tt>Dst</tt> 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).
Params: - ras – The Raster to copy.
Returns: A writable copy of ras
/**
* Creates a new raster that has a <b>copy</b> of the data in
* <tt>ras</tt>. 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
* <tt>getData</tt> call).
* @param ras The Raster to copy.
* @return A writable copy of <tt>ras</tt>
*/
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).
Params: - ras – The Raster to copy.
- minX – The x location for the upper left corner of the
returned WritableRaster.
- minY – The y location for the upper left corner of the
returned WritableRaster.
Returns: A writable copy of ras
/**
* Creates a new raster that has a <b>copy</b> of the data in
* <tt>ras</tt>. 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
* <tt>getData</tt> 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 <tt>ras</tt>
*/
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).
Params: - ras – The raster to make writable.
Returns: A Writable version of ras (shares DataBuffer with
ras).
/**
* Coerces <tt>ras</tt> to be writable. The returned Raster continues to
* reference the DataBuffer from ras, so modifications to the returned
* WritableRaster will be seen in ras.<p>
*
* 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 <tt>ras</tt>,
* use copyRaster (above).
* @param ras The raster to make writable.
* @return A Writable version of ras (shares DataBuffer with
* <tt>ras</tt>).
*/
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).
Params: - ras – The raster to make writable.
- minX – The x location for the upper left corner of the
returned WritableRaster.
- minY – The y location for the upper left corner of the
returned WritableRaster.
Returns: A Writable version of ras with it's upper left
hand coordinate set to minX, minY (shares it's DataBuffer
with ras).
/**
* Coerces <tt>ras</tt> to be writable. The returned Raster continues to
* reference the DataBuffer from ras, so modifications to the returned
* WritableRaster will be seen in ras.<p>
*
* 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 <tt>ras</tt>,
* 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 <tT>ras</tt> with it's upper left
* hand coordinate set to minX, minY (shares it's DataBuffer
* with <tt>ras</tt>).
*/
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.
Params: - cm – The ColorModel to change the alpha premult state of.
- newAlphaPreMult – The new state of alpha premult.
Returns: A new colorModel that has isAlphaPremultiplied()
equal to newAlphaPreMult.
/**
* 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
Params: - wr – The raster to change the state of.
- cm – The colormodel currently associated with data in wr.
- newAlphaPreMult – The desired state of alpha Premult for raster.
Returns: A new colormodel that matches 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.
Params: - src – The source
- dst – The destination
/**
* 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.
Params: - src – The source
- srcRect – The Rectangle of source data to be copied
- dst – The destination
- destP – The Place for the upper left corner of srcRect in dst.
/**
* 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.
Params: - image – the image
Returns: the alpha raster
/**
* 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;
}
}