clean debug code, move util

This commit is contained in:
Jörg Prante 2020-12-20 00:31:14 +01:00
parent 8c1393d95f
commit fec4554a43
6 changed files with 409 additions and 500 deletions

View file

@ -26,12 +26,12 @@ public class PDFProcessor implements Processor {
AbsoluteToRelativeTransformsFilter absoluteToRelativeTransformsFilter = new AbsoluteToRelativeTransformsFilter(commands); AbsoluteToRelativeTransformsFilter absoluteToRelativeTransformsFilter = new AbsoluteToRelativeTransformsFilter(commands);
FillPaintedShapeAsImageFilter paintedShapeAsImageFilter = new FillPaintedShapeAsImageFilter(absoluteToRelativeTransformsFilter); FillPaintedShapeAsImageFilter paintedShapeAsImageFilter = new FillPaintedShapeAsImageFilter(absoluteToRelativeTransformsFilter);
Iterable<Command<?>> filtered = new StateChangeGroupingFilter(paintedShapeAsImageFilter); Iterable<Command<?>> filtered = new StateChangeGroupingFilter(paintedShapeAsImageFilter);
PDFProcessorResult doc = new PDFProcessorResult(pageSize); PDFProcessorResult processorResult = new PDFProcessorResult(pageSize);
doc.setCompressed(compressed); processorResult.setCompressed(compressed);
for (Command<?> command : filtered) { for (Command<?> command : filtered) {
doc.handle(command); processorResult.handle(command);
} }
doc.close(); processorResult.close();
return doc; return processorResult;
} }
} }

View file

@ -1,5 +1,6 @@
package org.xbib.graphics.io.vector; package org.xbib.graphics.io.vector;
import static org.xbib.graphics.io.vector.util.ImageUtil.toBufferedImage;
import org.xbib.graphics.io.vector.commands.CreateCommand; import org.xbib.graphics.io.vector.commands.CreateCommand;
import org.xbib.graphics.io.vector.commands.DisposeCommand; import org.xbib.graphics.io.vector.commands.DisposeCommand;
import org.xbib.graphics.io.vector.commands.DrawImageCommand; import org.xbib.graphics.io.vector.commands.DrawImageCommand;
@ -31,7 +32,6 @@ import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration; import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice; import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment; import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image; import java.awt.Image;
import java.awt.Paint; import java.awt.Paint;
import java.awt.Polygon; import java.awt.Polygon;
@ -40,7 +40,6 @@ import java.awt.RenderingHints;
import java.awt.RenderingHints.Key; import java.awt.RenderingHints.Key;
import java.awt.Shape; import java.awt.Shape;
import java.awt.Stroke; import java.awt.Stroke;
import java.awt.Transparency;
import java.awt.font.FontRenderContext; import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector; import java.awt.font.GlyphVector;
import java.awt.font.TextLayout; import java.awt.font.TextLayout;
@ -56,22 +55,17 @@ import java.awt.geom.RoundRectangle2D;
import java.awt.image.AffineTransformOp; import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp; import java.awt.image.BufferedImageOp;
import java.awt.image.ColorModel;
import java.awt.image.ImageObserver; import java.awt.image.ImageObserver;
import java.awt.image.PixelGrabber;
import java.awt.image.RenderedImage; import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.awt.image.renderable.RenderableImage; import java.awt.image.renderable.RenderableImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.text.AttributedCharacterIterator; import java.text.AttributedCharacterIterator;
import java.util.Hashtable;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import javax.swing.ImageIcon;
/** /**
* Base for classes that want to implement vector export. * Base for classes that want to implement vector export.
@ -101,13 +95,17 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
private GraphicsState state; private GraphicsState state;
private Graphics2D debugValidateGraphics; private Graphics2D graphics2D;
public VectorGraphics2D() { public VectorGraphics2D() {
this(null, null); this(null, null, null);
} }
public VectorGraphics2D(Processor processor, PageSize pageSize) { public VectorGraphics2D(Processor processor, PageSize pageSize) {
this(processor, pageSize, null);
}
public VectorGraphics2D(Processor processor, PageSize pageSize, Graphics2D graphics2D) {
this.processor = processor; this.processor = processor;
this.pageSize = pageSize; this.pageSize = pageSize;
this.commands = new LinkedList<>(); this.commands = new LinkedList<>();
@ -121,9 +119,13 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
} }
fontRenderContext = new FontRenderContext(null, false, true); fontRenderContext = new FontRenderContext(null, false, true);
state = new GraphicsState(); state = new GraphicsState();
BufferedImage _debug_validate_bimg = new BufferedImage(200, 250, BufferedImage.TYPE_INT_ARGB); if (graphics2D == null) {
debugValidateGraphics = (Graphics2D) _debug_validate_bimg.getGraphics(); BufferedImage bufferedImage = new BufferedImage(200, 250, BufferedImage.TYPE_INT_ARGB);
debugValidateGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); this.graphics2D = (Graphics2D) bufferedImage.getGraphics();
this.graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
} else {
this.graphics2D = graphics2D;
}
} }
public PageSize getPageSize() { public PageSize getPageSize() {
@ -161,29 +163,17 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
@Override @Override
public void clip(Shape s) { public void clip(Shape s) {
debugValidateGraphics.clip(s);
Shape clipOld = getClip();
Shape clip = getClip();
if ((clip != null) && (s != null)) {
s = intersectShapes(clip, s);
}
setClip(s);
Shape clipNew = getClip();
if ((clipNew == null || debugValidateGraphics.getClip() == null) && clipNew != debugValidateGraphics.getClip()) {
throw new IllegalStateException("clip() validation failed: clip(" + clipOld + ", " + s + ") => " + clipNew + " != " + debugValidateGraphics.getClip());
}
if (clipNew != null && notEquals(clipNew, debugValidateGraphics.getClip())) {
throw new IllegalStateException("clip() validation failed: clip(" + clipOld + ", " + s + ") => " + clipNew + " != " + debugValidateGraphics.getClip());
}
}
@Override
public void draw(Shape s) {
if (isDisposed() || s == null) { if (isDisposed() || s == null) {
return; return;
} }
emit(new DrawShapeCommand(s)); if (graphics2D != null) {
debugValidateGraphics.draw(s); graphics2D.clip(s);
}
Shape clip = getClip();
if (clip != null) {
s = intersectShapes(clip, s);
}
setClip(s);
} }
@Override @Override
@ -192,11 +182,22 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
draw(s); draw(s);
} }
@Override
public void draw(Shape s) {
if (isDisposed() || s == null) {
return;
}
emit(new DrawShapeCommand(s));
if (graphics2D != null) {
graphics2D.draw(s);
}
}
@Override @Override
public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) { public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) {
BufferedImage bimg = getTransformedImage(img, xform); BufferedImage bufferedImage = getTransformedImage(img, xform);
return drawImage(bimg, bimg.getMinX(), bimg.getMinY(), return drawImage(bufferedImage, bufferedImage.getMinX(), bufferedImage.getMinY(),
bimg.getWidth(), bimg.getHeight(), null, null); bufferedImage.getWidth(), bufferedImage.getHeight(), null, null);
} }
@Override @Override
@ -228,16 +229,9 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
if (isDisposed() || str == null || str.trim().length() == 0) { if (isDisposed() || str == null || str.trim().length() == 0) {
return; return;
} }
boolean isTextAsVectors = false;
if (isTextAsVectors) {
TextLayout layout = new TextLayout(str, getFont(),
getFontRenderContext());
Shape s = layout.getOutline(
AffineTransform.getTranslateInstance(x, y));
fill(s);
} else {
emit(new DrawStringCommand(str, x, y)); emit(new DrawStringCommand(str, x, y));
debugValidateGraphics.drawString(str, x, y); if (graphics2D != null) {
graphics2D.drawString(str, x, y);
} }
} }
@ -249,8 +243,7 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
@Override @Override
public void drawString(AttributedCharacterIterator iterator, float x, float y) { public void drawString(AttributedCharacterIterator iterator, float x, float y) {
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
for (char c = iterator.first(); c != AttributedCharacterIterator.DONE; for (char c = iterator.first(); c != AttributedCharacterIterator.DONE; c = iterator.next()) {
c = iterator.next()) {
buf.append(c); buf.append(c);
} }
drawString(buf.toString(), x, y); drawString(buf.toString(), x, y);
@ -262,7 +255,9 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
return; return;
} }
emit(new FillShapeCommand(s)); emit(new FillShapeCommand(s));
debugValidateGraphics.fill(s); if (graphics2D != null) {
graphics2D.fill(s);
}
} }
@Override @Override
@ -277,9 +272,8 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
} }
emit(new SetBackgroundCommand(color)); emit(new SetBackgroundCommand(color));
state.setBackground(color); state.setBackground(color);
debugValidateGraphics.setBackground(color); if (graphics2D != null) {
if (!getBackground().equals(debugValidateGraphics.getBackground())) { graphics2D.setBackground(color);
throw new IllegalStateException("setBackground() validation failed");
} }
} }
@ -298,9 +292,8 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
} }
emit(new SetCompositeCommand(comp)); emit(new SetCompositeCommand(comp));
state.setComposite(comp); state.setComposite(comp);
debugValidateGraphics.setComposite(comp); if (graphics2D != null) {
if (!getComposite().equals(debugValidateGraphics.getComposite())) { graphics2D.setComposite(comp);
throw new IllegalStateException("setComposite() validation failed");
} }
} }
@ -333,9 +326,8 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
} }
emit(new SetPaintCommand(paint)); emit(new SetPaintCommand(paint));
state.setPaint(paint); state.setPaint(paint);
debugValidateGraphics.setPaint(paint); if (graphics2D != null) {
if (!getPaint().equals(debugValidateGraphics.getPaint())) { graphics2D.setPaint(paint);
throw new IllegalStateException("setPaint() validation failed");
} }
} }
@ -382,10 +374,8 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
} }
emit(new SetStrokeCommand(s)); emit(new SetStrokeCommand(s));
state.setStroke(s); state.setStroke(s);
if (graphics2D != null) {
debugValidateGraphics.setStroke(s); graphics2D.setStroke(s);
if (!getStroke().equals(debugValidateGraphics.getStroke())) {
throw new IllegalStateException("setStroke() validation failed");
} }
} }
@ -397,12 +387,9 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
} }
hitShape = state.transformShape(hitShape); hitShape = state.transformShape(hitShape);
boolean hit = hitShape.intersects(rect); boolean hit = hitShape.intersects(rect);
if (graphics2D != null) {
boolean _debug_hit = debugValidateGraphics.hit(rect, s, onStroke); graphics2D.hit(rect, s, onStroke);
if (hit != _debug_hit) {
throw new IllegalStateException("setClip() validation failed");
} }
return hit; return hit;
} }
@ -427,10 +414,8 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
} }
emit(new SetTransformCommand(tx)); emit(new SetTransformCommand(tx));
state.setTransform(tx); state.setTransform(tx);
if (graphics2D != null) {
debugValidateGraphics.setTransform(tx); graphics2D.setTransform(tx);
if (!getTransform().equals(debugValidateGraphics.getTransform())) {
throw new IllegalStateException("setTransform() validation failed");
} }
} }
@ -443,10 +428,8 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
txNew.shear(shx, shy); txNew.shear(shx, shy);
emit(new ShearCommand(shx, shy)); emit(new ShearCommand(shx, shy));
state.setTransform(txNew); state.setTransform(txNew);
if ( graphics2D != null) {
debugValidateGraphics.shear(shx, shy); graphics2D.shear(shx, shy);
if (!getTransform().equals(debugValidateGraphics.getTransform())) {
throw new IllegalStateException("shear() validation failed");
} }
} }
@ -459,10 +442,8 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
txNew.concatenate(tx); txNew.concatenate(tx);
emit(new TransformCommand(tx)); emit(new TransformCommand(tx));
state.setTransform(txNew); state.setTransform(txNew);
if (graphics2D != null) {
debugValidateGraphics.transform(tx); graphics2D.transform(tx);
if (!getTransform().equals(debugValidateGraphics.getTransform())) {
throw new IllegalStateException("transform() validation failed");
} }
} }
@ -480,9 +461,8 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
txNew.translate(tx, ty); txNew.translate(tx, ty);
emit(new TranslateCommand(tx, ty)); emit(new TranslateCommand(tx, ty));
state.setTransform(txNew); state.setTransform(txNew);
debugValidateGraphics.translate(tx, ty); if (graphics2D != null) {
if (!getTransform().equals(debugValidateGraphics.getTransform())) { graphics2D.translate(tx, ty);
throw new IllegalStateException("translate() validation failed");
} }
} }
@ -505,14 +485,12 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
emit(new RotateCommand(theta, x, y)); emit(new RotateCommand(theta, x, y));
state.setTransform(txNew); state.setTransform(txNew);
if (x == 0.0 && y == 0.0) { if (x == 0.0 && y == 0.0) {
debugValidateGraphics.rotate(theta); if (graphics2D != null) {
if (!getTransform().equals(debugValidateGraphics.getTransform())) { graphics2D.rotate(theta);
throw new IllegalStateException("rotate(theta) validation failed");
} }
} else { } else {
debugValidateGraphics.rotate(theta, x, y); if (graphics2D != null) {
if (!getTransform().equals(debugValidateGraphics.getTransform())) { graphics2D.rotate(theta, x, y);
throw new IllegalStateException("rotate(theta,x,y) validation failed");
} }
} }
} }
@ -526,9 +504,8 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
affineTransform.scale(sx, sy); affineTransform.scale(sx, sy);
emit(new ScaleCommand(sx, sy)); emit(new ScaleCommand(sx, sy));
state.setTransform(affineTransform); state.setTransform(affineTransform);
debugValidateGraphics.scale(sx, sy); if (graphics2D != null) {
if (!getTransform().equals(debugValidateGraphics.getTransform())) { graphics2D.scale(sx, sy);
throw new IllegalStateException("scale() validation failed");
} }
} }
@ -547,6 +524,7 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
@Override @Override
public void copyArea(int x, int y, int width, int height, int dx, int dy) { public void copyArea(int x, int y, int width, int height, int dx, int dy) {
// unable to implement
} }
@Override @Override
@ -561,7 +539,9 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
return null; return null;
} }
emit(new CreateCommand(clone)); emit(new CreateCommand(clone));
clone.debugValidateGraphics = (Graphics2D) debugValidateGraphics.create(); if (graphics2D != null) {
clone.graphics2D = (Graphics2D) graphics2D.create();
}
return clone; return clone;
} }
@ -572,13 +552,14 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
} }
emit(new DisposeCommand(this)); emit(new DisposeCommand(this));
disposed = true; disposed = true;
debugValidateGraphics.dispose(); if (graphics2D != null) {
graphics2D.dispose();
}
} }
@Override @Override
public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
draw(new Arc2D.Double(x, y, width, height, draw(new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN));
startAngle, arcAngle, Arc2D.OPEN));
} }
@Override @Override
@ -587,21 +568,17 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
} }
@Override @Override
public boolean drawImage(Image img, int x, int y, Color bgcolor, public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) {
ImageObserver observer) { return drawImage(img, x, y, img.getWidth(observer), img.getHeight(observer), bgcolor, observer);
return drawImage(img, x, y, img.getWidth(observer),
img.getHeight(observer), bgcolor, observer);
} }
@Override @Override
public boolean drawImage(Image img, int x, int y, int width, int height, public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) {
ImageObserver observer) {
return drawImage(img, x, y, width, height, null, observer); return drawImage(img, x, y, width, height, null, observer);
} }
@Override @Override
public boolean drawImage(Image img, int x, int y, int width, int height, public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) {
Color bgcolor, ImageObserver observer) {
if (isDisposed() || img == null) { if (isDisposed() || img == null) {
return true; return true;
} }
@ -615,7 +592,9 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
setColor(bgcolorOld); setColor(bgcolorOld);
} }
emit(new DrawImageCommand(img, imageWidth, imageHeight, x, y, width, height)); emit(new DrawImageCommand(img, imageWidth, imageHeight, x, y, width, height));
debugValidateGraphics.drawImage(img, x, y, width, height, bgcolor, observer); if (graphics2D != null) {
graphics2D.drawImage(img, x, y, width, height, bgcolor, observer);
}
return true; return true;
} }
@ -737,16 +716,8 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
} }
emit(new SetClipCommand(clip)); emit(new SetClipCommand(clip));
state.setClip(clip); state.setClip(clip);
if (graphics2D != null) {
debugValidateGraphics.setClip(clip); graphics2D.setClip(clip);
if (getClip() == null) {
if (debugValidateGraphics.getClip() != null) {
throw new IllegalStateException("setClip() validation failed: clip=null, validation=" +
debugValidateGraphics.getClip());
}
} else if (notEquals(getClip(), debugValidateGraphics.getClip())) {
throw new IllegalStateException("setClip() validation failed: clip=" + getClip() + ", validation=" +
debugValidateGraphics.getClip());
} }
} }
@ -771,9 +742,8 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
emit(new SetColorCommand(c)); emit(new SetColorCommand(c));
state.setColor(c); state.setColor(c);
state.setPaint(c); state.setPaint(c);
debugValidateGraphics.setColor(c); if (graphics2D != null) {
if (!getColor().equals(debugValidateGraphics.getColor())) { graphics2D.setColor(c);
throw new IllegalStateException("setColor() validation failed");
} }
} }
@ -789,9 +759,8 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
} }
emit(new SetFontCommand(font)); emit(new SetFontCommand(font));
state.setFont(font); state.setFont(font);
debugValidateGraphics.setFont(font); if (graphics2D != null) {
if (!getFont().equals(debugValidateGraphics.getFont())) { graphics2D.setFont(font);
throw new IllegalStateException("setFont() validation failed");
} }
} }
@ -812,7 +781,9 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
@Override @Override
public void setPaintMode() { public void setPaintMode() {
setComposite(AlphaComposite.SrcOver); setComposite(AlphaComposite.SrcOver);
debugValidateGraphics.setPaintMode(); if (graphics2D != null) {
graphics2D.setPaintMode();
}
} }
@Override @Override
@ -822,7 +793,9 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
} }
emit(new SetXORModeCommand(c1)); emit(new SetXORModeCommand(c1));
state.setXorMode(c1); state.setXorMode(c1);
debugValidateGraphics.setXORMode(c1); if (graphics2D != null) {
graphics2D.setXORMode(c1);
}
} }
public Color getXORMode() { public Color getXORMode() {
@ -849,13 +822,10 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
* @return Image with transformed content * @return Image with transformed content
*/ */
private BufferedImage getTransformedImage(Image image, AffineTransform xform) { private BufferedImage getTransformedImage(Image image, AffineTransform xform) {
Integer interpolationType = Integer interpolationType = (Integer) getRenderingHint(RenderingHints.KEY_INTERPOLATION);
(Integer) getRenderingHint(RenderingHints.KEY_INTERPOLATION); if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(interpolationType)) {
if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR
.equals(interpolationType)) {
interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR; interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
} else if (RenderingHints.VALUE_INTERPOLATION_BILINEAR } else if (RenderingHints.VALUE_INTERPOLATION_BILINEAR.equals(interpolationType)) {
.equals(interpolationType)) {
interpolationType = AffineTransformOp.TYPE_BILINEAR; interpolationType = AffineTransformOp.TYPE_BILINEAR;
} else { } else {
interpolationType = AffineTransformOp.TYPE_BICUBIC; interpolationType = AffineTransformOp.TYPE_BICUBIC;
@ -865,7 +835,7 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
return op.filter(bufferedImage, null); return op.filter(bufferedImage, null);
} }
private static boolean notEquals(Shape shapeA, Shape shapeB) { /*private static boolean notEquals(Shape shapeA, Shape shapeB) {
PathIterator pathAIterator = shapeA.getPathIterator(null); PathIterator pathAIterator = shapeA.getPathIterator(null);
PathIterator pathBIterator = shapeB.getPathIterator(null); PathIterator pathBIterator = shapeB.getPathIterator(null);
if (pathAIterator.getWindingRule() != pathBIterator.getWindingRule()) { if (pathAIterator.getWindingRule() != pathBIterator.getWindingRule()) {
@ -888,99 +858,7 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
pathBIterator.next(); pathBIterator.next();
} }
return !pathBIterator.isDone(); return !pathBIterator.isDone();
} }*/
/**
* Converts an arbitrary image to a {@code BufferedImage}.
*
* @param image Image that should be converted.
* @return a buffered image containing the image pixels, or the original
* instance if the image already was of type {@code BufferedImage}.
*/
static BufferedImage toBufferedImage(RenderedImage image) {
if (image instanceof BufferedImage) {
return (BufferedImage) image;
}
ColorModel cm = image.getColorModel();
WritableRaster raster = cm.createCompatibleWritableRaster(image.getWidth(), image.getHeight());
boolean isRasterPremultiplied = cm.isAlphaPremultiplied();
Hashtable<String, Object> properties = null;
if (image.getPropertyNames() != null) {
properties = new Hashtable<>();
for (String key : image.getPropertyNames()) {
properties.put(key, image.getProperty(key));
}
}
BufferedImage bimage = new BufferedImage(cm, raster, isRasterPremultiplied, properties);
image.copyData(raster);
return bimage;
}
/**
* This method returns a buffered image with the contents of an image.
* Taken from http://www.exampledepot.com/egs/java.awt.image/Image2Buf.html
*
* @param image Image to be converted
* @return a buffered image with the contents of the specified image
*/
static BufferedImage toBufferedImage(Image image) {
if (image instanceof BufferedImage) {
return (BufferedImage) image;
}
image = new ImageIcon(image).getImage();
boolean hasAlpha = hasAlpha(image);
BufferedImage bimage;
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
try {
int transparency = Transparency.OPAQUE;
if (hasAlpha) {
transparency = Transparency.TRANSLUCENT;
}
GraphicsDevice gs = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gs.getDefaultConfiguration();
bimage = gc.createCompatibleImage(image.getWidth(null), image.getHeight(null), transparency);
} catch (HeadlessException e) {
bimage = null;
}
if (bimage == null) {
int type = BufferedImage.TYPE_INT_RGB;
if (hasAlpha) {
type = BufferedImage.TYPE_INT_ARGB;
}
bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
}
Graphics g = bimage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return bimage;
}
/**
* This method returns {@code true} if the specified image has the
* possibility to store transparent pixels.
* Inspired by http://www.exampledepot.com/egs/java.awt.image/HasAlpha.html
*
* @param image Image that should be checked for alpha channel.
* @return {@code true} if the specified image can have transparent pixels,
* {@code false} otherwise
*/
static boolean hasAlpha(Image image) {
ColorModel cm;
if (image instanceof BufferedImage) {
BufferedImage bimage = (BufferedImage) image;
cm = bimage.getColorModel();
} else {
PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
try {
pg.grabPixels();
} catch (InterruptedException e) {
return false;
}
cm = pg.getColorModel();
}
return cm.hasAlpha();
}
private static Shape intersectShapes(Shape s1, Shape s2) { private static Shape intersectShapes(Shape s1, Shape s2) {
if (s1 instanceof Rectangle2D && s2 instanceof Rectangle2D) { if (s1 instanceof Rectangle2D && s2 instanceof Rectangle2D) {

View file

@ -0,0 +1,111 @@
package org.xbib.graphics.io.vector.util;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.PixelGrabber;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.util.Hashtable;
import javax.swing.ImageIcon;
public class ImageUtil {
/**
* Converts an arbitrary image to a {@code BufferedImage}.
*
* @param image Image that should be converted.
* @return a buffered image containing the image pixels, or the original
* instance if the image already was of type {@code BufferedImage}.
*/
public static BufferedImage toBufferedImage(RenderedImage image) {
if (image instanceof BufferedImage) {
return (BufferedImage) image;
}
ColorModel cm = image.getColorModel();
WritableRaster raster = cm.createCompatibleWritableRaster(image.getWidth(), image.getHeight());
boolean isRasterPremultiplied = cm.isAlphaPremultiplied();
Hashtable<String, Object> properties = null;
if (image.getPropertyNames() != null) {
properties = new Hashtable<>();
for (String key : image.getPropertyNames()) {
properties.put(key, image.getProperty(key));
}
}
BufferedImage bimage = new BufferedImage(cm, raster, isRasterPremultiplied, properties);
image.copyData(raster);
return bimage;
}
/**
* This method returns a buffered image with the contents of an image.
* Taken from http://www.exampledepot.com/egs/java.awt.image/Image2Buf.html
*
* @param image Image to be converted
* @return a buffered image with the contents of the specified image
*/
public static BufferedImage toBufferedImage(Image image) {
if (image instanceof BufferedImage) {
return (BufferedImage) image;
}
image = new ImageIcon(image).getImage();
boolean hasAlpha = hasAlpha(image);
BufferedImage bimage;
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
try {
int transparency = Transparency.OPAQUE;
if (hasAlpha) {
transparency = Transparency.TRANSLUCENT;
}
GraphicsDevice gs = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gs.getDefaultConfiguration();
bimage = gc.createCompatibleImage(image.getWidth(null), image.getHeight(null), transparency);
} catch (HeadlessException e) {
bimage = null;
}
if (bimage == null) {
int type = BufferedImage.TYPE_INT_RGB;
if (hasAlpha) {
type = BufferedImage.TYPE_INT_ARGB;
}
bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
}
Graphics g = bimage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return bimage;
}
/**
* This method returns {@code true} if the specified image has the
* possibility to store transparent pixels.
* Inspired by http://www.exampledepot.com/egs/java.awt.image/HasAlpha.html
*
* @param image Image that should be checked for alpha channel.
* @return {@code true} if the specified image can have transparent pixels,
* {@code false} otherwise
*/
public static boolean hasAlpha(Image image) {
ColorModel cm;
if (image instanceof BufferedImage) {
BufferedImage bimage = (BufferedImage) image;
cm = bimage.getColorModel();
} else {
PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
try {
pg.grabPixels();
} catch (InterruptedException e) {
return false;
}
cm = pg.getColorModel();
}
return cm.hasAlpha();
}
}

View file

@ -1,265 +0,0 @@
package org.xbib.graphics.io.vector;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class TestUtils {
protected TestUtils() {
throw new UnsupportedOperationException();
}
public static void assertTemplateEquals(Template expected, Template actual) {
Iterator<Object> itExpected = expected.iterator();
Iterator<Object> itActual = actual.iterator();
while (itExpected.hasNext() && itActual.hasNext()) {
Object lineExpected = itExpected.next();
Object lineActual = itActual.next();
if (lineExpected == null) {
continue;
}
assertTrue(lineActual instanceof String,
String.format("Line is of type %s, expected String.", lineActual.getClass()));
if (lineExpected instanceof String) {
assertEquals(lineExpected, lineActual);
} else if (lineExpected instanceof Pattern) {
Pattern expectedPattern = (Pattern) lineExpected;
Matcher matcher = expectedPattern.matcher((String) lineActual);
assertTrue(matcher.matches(),
String.format("Line didn't match pattern.\nExpected: \"%s\"\nActual: \"%s\"", matcher.pattern(), lineActual));
}
}
assertEquals(expected.size(), actual.size(), "Wrong number of lines in template.");
}
private static List<XMLFragment> parseXML(String xmlString) {
XMLFragment frag;
List<XMLFragment> fragments = new LinkedList<XMLFragment>();
int startPos = 0;
while ((frag = XMLFragment.parse(xmlString, startPos)) != null) {
fragments.add(frag);
startPos = frag.matchEnd;
}
return fragments;
}
public static void assertXMLEquals(String expected, String actual) {
List<XMLFragment> expectedFrags = parseXML(expected);
List<XMLFragment> actualFrags = parseXML(actual);
Iterator<XMLFragment> itExpected = expectedFrags.iterator();
Iterator<XMLFragment> itActual = actualFrags.iterator();
while (itExpected.hasNext() && itActual.hasNext()) {
XMLFragment expectedFrag = itExpected.next();
XMLFragment actualFrag = itActual.next();
assertEquals(expectedFrag, actualFrag);
}
assertEquals(expectedFrags.size(), actualFrags.size());
}
@SuppressWarnings("serial")
public static class Template extends LinkedList<Object> {
public Template(Object[] lines) {
Collections.addAll(this, lines);
}
public Template(Template[] templates) {
for (Template template : templates) {
addAll(template);
}
}
}
public static class XMLFragment {
private static final Pattern CDATA = Pattern.compile("\\s*<!\\[CDATA\\[(.*?)\\]\\]>");
private static final Pattern COMMENT = Pattern.compile("\\s*<!--(.*?)-->");
private static final Pattern TAG_BEGIN = Pattern.compile("\\s*<(/|\\?|!)?\\s*([^\\s>/\\?]+)");
private static final Pattern TAG_END = Pattern.compile("\\s*(/|\\?)?>");
private static final Pattern TAG_ATTRIBUTE = Pattern.compile("\\s*([^\\s>=]+)=(\"[^\"]*\"|'[^']*')");
private static final Pattern DOCTYPE_PART = Pattern.compile("\\s*(\"[^\"]*\"|'[^']*'|[^\\s>]+)");
public final String name;
public final FragmentType type;
public final Map<String, String> attributes;
public final int matchStart;
public final int matchEnd;
public XMLFragment(String name, FragmentType type, Map<String, String> attributes,
int matchStart, int matchEnd) {
this.name = name;
this.type = type;
this.attributes = Collections.unmodifiableMap(
new TreeMap<String, String>(attributes));
this.matchStart = matchStart;
this.matchEnd = matchEnd;
}
public static XMLFragment parse(String xmlString, int matchStart) {
Map<String, String> attrs = new IdentityHashMap<String, String>();
Matcher cdataMatch = CDATA.matcher(xmlString);
cdataMatch.region(matchStart, xmlString.length());
if (cdataMatch.lookingAt()) {
attrs.put("value", cdataMatch.group(1));
return new XMLFragment("", FragmentType.CDATA, attrs, matchStart, cdataMatch.end());
}
Matcher commentMatch = COMMENT.matcher(xmlString);
commentMatch.region(matchStart, xmlString.length());
if (commentMatch.lookingAt()) {
attrs.put("value", commentMatch.group(1).trim());
return new XMLFragment("", FragmentType.COMMENT, attrs, matchStart, commentMatch.end());
}
Matcher beginMatch = TAG_BEGIN.matcher(xmlString);
beginMatch.region(matchStart, xmlString.length());
if (!beginMatch.lookingAt()) {
return null;
}
int matchEndPrev = beginMatch.end();
String modifiers = beginMatch.group(1);
String name = beginMatch.group(2);
boolean endTag = "/".equals(modifiers);
boolean declarationStart = "?".equals(modifiers);
boolean doctype = "!".equals(modifiers) && "DOCTYPE".equals(name);
if (doctype) {
int partNo = 0;
while (true) {
Matcher attrMatch = DOCTYPE_PART.matcher(xmlString);
attrMatch.region(matchEndPrev, xmlString.length());
if (!attrMatch.lookingAt()) {
break;
}
matchEndPrev = attrMatch.end();
String partValue = attrMatch.group(1);
if (partValue.startsWith("\"") || partValue.startsWith("'")) {
partValue = partValue.substring(1, partValue.length() - 1);
}
String partId = String.format("doctype %02d", partNo++);
attrs.put(partId, partValue);
}
} else {
while (true) {
Matcher attrMatch = TAG_ATTRIBUTE.matcher(xmlString);
attrMatch.region(matchEndPrev, xmlString.length());
if (!attrMatch.lookingAt()) {
break;
}
matchEndPrev = attrMatch.end();
String attrName = attrMatch.group(1);
String attrValue = attrMatch.group(2);
attrValue = attrValue.substring(1, attrValue.length() - 1);
attrs.put(attrName, attrValue);
}
}
Matcher endMatch = TAG_END.matcher(xmlString);
endMatch.region(matchEndPrev, xmlString.length());
if (!endMatch.lookingAt()) {
throw new AssertionError(String.format("No tag end found: %s", xmlString.substring(0, matchEndPrev)));
}
matchEndPrev = endMatch.end();
modifiers = endMatch.group(1);
boolean emptyElement = "/".equals(modifiers);
boolean declarationEnd = "?".equals(modifiers);
FragmentType type = FragmentType.START_TAG;
if (endTag) {
type = FragmentType.END_TAG;
} else if (emptyElement) {
type = FragmentType.EMPTY_ELEMENT;
} else if (declarationStart && declarationEnd) {
type = FragmentType.DECLARATION;
} else if (doctype) {
type = FragmentType.DOCTYPE;
}
return new XMLFragment(name, type, attrs, matchStart, matchEndPrev);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof XMLFragment)) {
return false;
}
XMLFragment frag = (XMLFragment) o;
if (!type.equals(frag.type) || !name.equals(frag.name)) {
return false;
}
Iterator<Map.Entry<String, String>> itThis = attributes.entrySet().iterator();
Iterator<Map.Entry<String, String>> itFrag = frag.attributes.entrySet().iterator();
while (itThis.hasNext() && itFrag.hasNext()) {
Map.Entry<String, String> attrThis = itThis.next();
Map.Entry<String, String> attrFrag = itFrag.next();
if (!attrThis.getKey().equals(attrFrag.getKey()) ||
!attrThis.getValue().equals(attrFrag.getValue())) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return type.hashCode() ^ attributes.hashCode();
}
@Override
public String toString() {
StringBuilder s = new StringBuilder("<");
if (FragmentType.END_TAG.equals(type)) {
s.append("/");
} else if (FragmentType.DECLARATION.equals(type)) {
s.append("?");
}
if (FragmentType.DOCTYPE.equals(type)) {
s.append("!").append(name);
for (String partValue : attributes.values()) {
s.append(" ").append(partValue);
}
} else {
s.append(name);
for (Map.Entry<String, String> attr : attributes.entrySet()) {
s.append(" ").append(attr.getKey()).append("=\"").append(attr.getValue()).append("\"");
}
}
if (FragmentType.DECLARATION.equals(type)) {
s.append("?");
}
s.append(">");
return s.toString();
}
public enum FragmentType {
START_TAG, END_TAG, EMPTY_ELEMENT, CDATA,
DECLARATION, DOCTYPE, COMMENT
}
}
}

View file

@ -3,8 +3,13 @@ package org.xbib.graphics.io.vector;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.graphics.io.vector.TestUtils.XMLFragment; import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TestUtilsTest { public class TestUtilsTest {
@ -186,4 +191,188 @@ public class TestUtilsTest {
assertEquals(0, frag.matchStart); assertEquals(0, frag.matchStart);
assertEquals(xmlString.length(), frag.matchEnd); assertEquals(xmlString.length(), frag.matchEnd);
} }
public static class XMLFragment {
private static final Pattern CDATA = Pattern.compile("\\s*<!\\[CDATA\\[(.*?)\\]\\]>");
private static final Pattern COMMENT = Pattern.compile("\\s*<!--(.*?)-->");
private static final Pattern TAG_BEGIN = Pattern.compile("\\s*<(/|\\?|!)?\\s*([^\\s>/\\?]+)");
private static final Pattern TAG_END = Pattern.compile("\\s*(/|\\?)?>");
private static final Pattern TAG_ATTRIBUTE = Pattern.compile("\\s*([^\\s>=]+)=(\"[^\"]*\"|'[^']*')");
private static final Pattern DOCTYPE_PART = Pattern.compile("\\s*(\"[^\"]*\"|'[^']*'|[^\\s>]+)");
public final String name;
public final FragmentType type;
public final Map<String, String> attributes;
public final int matchStart;
public final int matchEnd;
public XMLFragment(String name, FragmentType type, Map<String, String> attributes,
int matchStart, int matchEnd) {
this.name = name;
this.type = type;
this.attributes = Collections.unmodifiableMap(
new TreeMap<String, String>(attributes));
this.matchStart = matchStart;
this.matchEnd = matchEnd;
}
public static XMLFragment parse(String xmlString, int matchStart) {
Map<String, String> attrs = new IdentityHashMap<String, String>();
Matcher cdataMatch = CDATA.matcher(xmlString);
cdataMatch.region(matchStart, xmlString.length());
if (cdataMatch.lookingAt()) {
attrs.put("value", cdataMatch.group(1));
return new XMLFragment("", FragmentType.CDATA, attrs, matchStart, cdataMatch.end());
}
Matcher commentMatch = COMMENT.matcher(xmlString);
commentMatch.region(matchStart, xmlString.length());
if (commentMatch.lookingAt()) {
attrs.put("value", commentMatch.group(1).trim());
return new XMLFragment("", FragmentType.COMMENT, attrs, matchStart, commentMatch.end());
}
Matcher beginMatch = TAG_BEGIN.matcher(xmlString);
beginMatch.region(matchStart, xmlString.length());
if (!beginMatch.lookingAt()) {
return null;
}
int matchEndPrev = beginMatch.end();
String modifiers = beginMatch.group(1);
String name = beginMatch.group(2);
boolean endTag = "/".equals(modifiers);
boolean declarationStart = "?".equals(modifiers);
boolean doctype = "!".equals(modifiers) && "DOCTYPE".equals(name);
if (doctype) {
int partNo = 0;
while (true) {
Matcher attrMatch = DOCTYPE_PART.matcher(xmlString);
attrMatch.region(matchEndPrev, xmlString.length());
if (!attrMatch.lookingAt()) {
break;
}
matchEndPrev = attrMatch.end();
String partValue = attrMatch.group(1);
if (partValue.startsWith("\"") || partValue.startsWith("'")) {
partValue = partValue.substring(1, partValue.length() - 1);
}
String partId = String.format("doctype %02d", partNo++);
attrs.put(partId, partValue);
}
} else {
while (true) {
Matcher attrMatch = TAG_ATTRIBUTE.matcher(xmlString);
attrMatch.region(matchEndPrev, xmlString.length());
if (!attrMatch.lookingAt()) {
break;
}
matchEndPrev = attrMatch.end();
String attrName = attrMatch.group(1);
String attrValue = attrMatch.group(2);
attrValue = attrValue.substring(1, attrValue.length() - 1);
attrs.put(attrName, attrValue);
}
}
Matcher endMatch = TAG_END.matcher(xmlString);
endMatch.region(matchEndPrev, xmlString.length());
if (!endMatch.lookingAt()) {
throw new AssertionError(String.format("No tag end found: %s", xmlString.substring(0, matchEndPrev)));
}
matchEndPrev = endMatch.end();
modifiers = endMatch.group(1);
boolean emptyElement = "/".equals(modifiers);
boolean declarationEnd = "?".equals(modifiers);
FragmentType type = FragmentType.START_TAG;
if (endTag) {
type = FragmentType.END_TAG;
} else if (emptyElement) {
type = FragmentType.EMPTY_ELEMENT;
} else if (declarationStart && declarationEnd) {
type = FragmentType.DECLARATION;
} else if (doctype) {
type = FragmentType.DOCTYPE;
}
return new XMLFragment(name, type, attrs, matchStart, matchEndPrev);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof XMLFragment)) {
return false;
}
XMLFragment frag = (XMLFragment) o;
if (!type.equals(frag.type) || !name.equals(frag.name)) {
return false;
}
Iterator<Map.Entry<String, String>> itThis = attributes.entrySet().iterator();
Iterator<Map.Entry<String, String>> itFrag = frag.attributes.entrySet().iterator();
while (itThis.hasNext() && itFrag.hasNext()) {
Map.Entry<String, String> attrThis = itThis.next();
Map.Entry<String, String> attrFrag = itFrag.next();
if (!attrThis.getKey().equals(attrFrag.getKey()) ||
!attrThis.getValue().equals(attrFrag.getValue())) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return type.hashCode() ^ attributes.hashCode();
}
@Override
public String toString() {
StringBuilder s = new StringBuilder("<");
if (FragmentType.END_TAG.equals(type)) {
s.append("/");
} else if (FragmentType.DECLARATION.equals(type)) {
s.append("?");
}
if (FragmentType.DOCTYPE.equals(type)) {
s.append("!").append(name);
for (String partValue : attributes.values()) {
s.append(" ").append(partValue);
}
} else {
s.append(name);
for (Map.Entry<String, String> attr : attributes.entrySet()) {
s.append(" ").append(attr.getKey()).append("=\"").append(attr.getValue()).append("\"");
}
}
if (FragmentType.DECLARATION.equals(type)) {
s.append("?");
}
s.append(">");
return s.toString();
}
public enum FragmentType {
START_TAG, END_TAG, EMPTY_ELEMENT, CDATA,
DECLARATION, DOCTYPE, COMMENT
}
}
} }

View file

@ -4,8 +4,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.xbib.graphics.io.vector.VectorGraphics2D.hasAlpha; import static org.xbib.graphics.io.vector.util.ImageUtil.hasAlpha;
import static org.xbib.graphics.io.vector.VectorGraphics2D.toBufferedImage; import static org.xbib.graphics.io.vector.util.ImageUtil.toBufferedImage;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.graphics.io.vector.commands.CreateCommand; import org.xbib.graphics.io.vector.commands.CreateCommand;
import org.xbib.graphics.io.vector.commands.DisposeCommand; import org.xbib.graphics.io.vector.commands.DisposeCommand;
@ -62,7 +62,6 @@ public class VectorGraphics2DTest {
assertEquals(Color.BLUE, ((DisposeCommand) lastCommand).getValue().getColor()); assertEquals(Color.BLUE, ((DisposeCommand) lastCommand).getValue().getColor());
} }
@Test @Test
public void testToBufferedImage() { public void testToBufferedImage() {
Image[] images = { Image[] images = {
@ -78,7 +77,6 @@ public class VectorGraphics2DTest {
} }
)) ))
}; };
for (Image image : images) { for (Image image : images) {
BufferedImage bimage = toBufferedImage(image); BufferedImage bimage = toBufferedImage(image);
assertNotNull(bimage); assertNotNull(bimage);
@ -90,11 +88,9 @@ public class VectorGraphics2DTest {
@Test @Test
public void testHasAlpha() { public void testHasAlpha() {
Image image; Image image = new BufferedImage(320, 240, BufferedImage.TYPE_INT_ARGB);
image = new BufferedImage(320, 240, BufferedImage.TYPE_INT_ARGB);
assertTrue(hasAlpha(image)); assertTrue(hasAlpha(image));
image = new BufferedImage(320, 240, BufferedImage.TYPE_INT_RGB); image = new BufferedImage(320, 240, BufferedImage.TYPE_INT_RGB);
assertFalse(hasAlpha(image)); assertFalse(hasAlpha(image));
} }
} }