| /* XGraphics2D.java -- A Java based Graphics2D impl for X |
| Copyright (C) 2006 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| package gnu.java.awt.peer.x; |
| |
| import java.awt.Color; |
| import java.awt.Font; |
| import java.awt.Graphics; |
| import java.awt.GraphicsConfiguration; |
| import java.awt.Image; |
| import java.awt.Paint; |
| import java.awt.Rectangle; |
| import java.awt.Shape; |
| import java.awt.Toolkit; |
| import java.awt.Transparency; |
| import java.awt.geom.AffineTransform; |
| import java.awt.image.BufferedImage; |
| import java.awt.image.ColorModel; |
| import java.awt.image.DataBuffer; |
| import java.awt.image.ImageObserver; |
| import java.awt.image.Raster; |
| import java.awt.peer.FontPeer; |
| import java.util.HashMap; |
| import java.util.WeakHashMap; |
| |
| import gnu.java.awt.image.AsyncImage; |
| import gnu.java.awt.java2d.AbstractGraphics2D; |
| import gnu.java.awt.java2d.ScanlineCoverage; |
| import gnu.x11.Colormap; |
| import gnu.x11.Drawable; |
| import gnu.x11.GC; |
| import gnu.x11.image.ZPixmap; |
| |
| public class XGraphics2D |
| extends AbstractGraphics2D |
| { |
| |
| /** |
| * When this property is set to true, then images are always rendered as |
| * opaque images, ignoring their translucence. This is intended for |
| * debugging and demonstration purposes. |
| */ |
| private static final boolean RENDER_OPAQUE = |
| Boolean.getBoolean("escherpeer.renderopaque"); |
| |
| /** |
| * The X Drawable to draw on. |
| */ |
| private Drawable xdrawable; |
| |
| /** |
| * The X graphics context (GC). |
| */ |
| private GC xgc; |
| |
| /** |
| * Indicates if this graphics has already been disposed. |
| */ |
| private boolean disposed; |
| |
| /** |
| * The current foreground color, possibly null. |
| */ |
| private Color foreground; |
| |
| XGraphics2D(Drawable d) |
| { |
| super(); |
| xdrawable = d; |
| xgc = new GC(d); |
| init(); |
| disposed = false; |
| //setClip(new Rectangle(0, 0, xdrawable.width, xdrawable.height)); |
| } |
| |
| @Override |
| protected void rawDrawLine(int x0, int y0, int x1, int y1) |
| { |
| xdrawable.segment(xgc, x0, y0, x1, y1); |
| } |
| |
| @Override |
| protected void rawDrawRect(int x, int y, int w, int h) |
| { |
| xdrawable.rectangle(xgc, x, y, w, h, false); |
| } |
| |
| @Override |
| protected void rawFillRect(int x, int y, int w, int h) |
| { |
| xdrawable.rectangle(xgc, x, y, w, h, true); |
| } |
| |
| /** |
| * Returns the color model of this Graphics object. |
| * |
| * @return the color model of this Graphics object |
| */ |
| protected ColorModel getColorModel() |
| { |
| return Toolkit.getDefaultToolkit().getColorModel(); |
| } |
| |
| /** |
| * Returns the color model of the target device. |
| * |
| * @return the color model of the target device |
| */ |
| protected ColorModel getDestinationColorModel() |
| { |
| return Toolkit.getDefaultToolkit().getColorModel(); |
| } |
| |
| /** |
| * Returns the bounds of the target. |
| * |
| * @return the bounds of the target |
| */ |
| protected Rectangle getDeviceBounds() |
| { |
| return new Rectangle(0, 0, xdrawable.width, xdrawable.height); |
| } |
| |
| public GraphicsConfiguration getDeviceConfiguration() |
| { |
| // FIXME: Implement this. |
| throw new UnsupportedOperationException("Not yet implemented"); |
| } |
| |
| public void dispose() |
| { |
| if (!disposed) |
| { |
| xgc.free(); |
| xdrawable.display.flush(); |
| disposed = true; |
| } |
| } |
| |
| public Graphics create() |
| { |
| // super.create() returns a copy created by clone(), so it should |
| // be a XGraphics2D. |
| XGraphics2D copy = (XGraphics2D) super.create(); |
| copy.xgc = xgc.copy(); |
| return copy; |
| } |
| |
| public void setClip(Shape c) |
| { |
| super.setClip(c); |
| if (c instanceof Rectangle) |
| { |
| Rectangle r = (Rectangle) c; |
| AffineTransform t = getTransform(); |
| int translateX = (int) t.getTranslateX(); |
| //System.err.println("translateX: " + translateX); |
| int translateY = (int) t.getTranslateY(); |
| //System.err.println("translateY: " + translateY); |
| //System.err.println("clip: " + c); |
| gnu.x11.Rectangle clip = new gnu.x11.Rectangle(r.x, r.y, r.width, |
| r.height); |
| xgc.set_clip_rectangles(translateX, translateY, |
| new gnu.x11.Rectangle[]{clip}, GC.UN_SORTED); |
| } |
| } |
| |
| /** |
| * Notifies the backend that the raster has changed in the specified |
| * rectangular area. The raster that is provided in this method is always |
| * the same as the one returned in {@link #getDestinationRaster}. |
| * Backends that reflect changes to this raster directly don't need to do |
| * anything here. |
| * |
| * @param raster the updated raster, identical to the raster returned |
| * by {@link #getDestinationRaster()} |
| * @param x the upper left corner of the updated region, X coordinate |
| * @param y the upper lef corner of the updated region, Y coordinate |
| * @param w the width of the updated region |
| * @param h the height of the updated region |
| */ |
| protected void updateRaster(Raster raster, int x, int y, int w, int h) |
| { |
| if (w > 0 && h > 0) |
| { |
| ZPixmap zPixmap = new ZPixmap(xdrawable.display, w, h, |
| xdrawable.display.default_pixmap_format); |
| int[] pixel = null; |
| int x1 = x + w; |
| int y1 = y + h; |
| for (int tx = x; tx < x1; tx++) |
| { |
| for (int ty = y; ty < y1; ty++) |
| { |
| pixel = raster.getPixel(tx, ty, pixel); |
| //System.err.println("tx: " + tx + ", ty: " + ty + ", pixel: " + pixel[0] + ", " + pixel[1] + ", " + pixel[2]); |
| // System.err.print("r: " + pixel[0]); |
| // System.err.print(", g: " + pixel[1]); |
| // System.err.println(", b: " + pixel[2]); |
| zPixmap.set_red(tx - x, ty - y, pixel[0]); |
| zPixmap.set_green(tx - x, ty - y, pixel[1]); |
| zPixmap.set_blue(tx - x, ty - y, pixel[2]); |
| } |
| } |
| xdrawable.put_image(xgc, zPixmap, x, y); |
| } |
| } |
| |
| @Override |
| public void renderScanline(int y, ScanlineCoverage c) |
| { |
| if (y >= xdrawable.height) |
| return; |
| |
| // TODO: Handle Composite and Paint. |
| ScanlineCoverage.Iterator iter = c.iterate(); |
| int coverageAlpha = 0; |
| int maxCoverage = c.getMaxCoverage(); |
| while (iter.hasNext()) |
| { |
| ScanlineCoverage.Range range = iter.next(); |
| |
| coverageAlpha = range.getCoverage(); |
| int x0 = range.getXPos(); |
| int l = range.getLength(); |
| if (coverageAlpha == c.getMaxCoverage()) |
| { |
| // Simply paint the current color over the existing pixels. |
| xdrawable.fill_rectangle(xgc, x0, y, l, 1); |
| } |
| else if (coverageAlpha > 0) |
| { |
| // Composite the current color with the existing pixels. |
| int x1 = x0 + l; |
| x0 = Math.min(Math.max(0, x0), xdrawable.width - 1); |
| x1 = Math.min(Math.max(0, x1), xdrawable.width - 1); |
| if ((x1 - x0) < 1) |
| continue; |
| l = x1 - x0; |
| gnu.x11.image.ZPixmap existing = (ZPixmap) |
| xdrawable.image(x0, y, l, 1, 0xFFFFFFFF, |
| gnu.x11.image.Image.Format.ZPIXMAP); |
| for (int x = 0; x < l; x++) |
| { |
| Color col = getColor(); |
| if (col == null) |
| { |
| col = Color.BLACK; |
| } |
| int red = col.getRed(); |
| int green = col.getGreen(); |
| int blue = col.getBlue(); |
| int redOut = existing.get_red(x, 0); |
| int greenOut = existing.get_green(x, 0); |
| int blueOut = existing.get_blue(x, 0); |
| int outAlpha = maxCoverage - coverageAlpha; |
| redOut = redOut * outAlpha + red * coverageAlpha; |
| redOut = redOut / maxCoverage; |
| greenOut = greenOut * outAlpha + green * coverageAlpha; |
| greenOut = greenOut / maxCoverage; |
| blueOut = blueOut * outAlpha + blue * coverageAlpha; |
| blueOut = blueOut / maxCoverage; |
| existing.set(x, 0, redOut, greenOut, blueOut); |
| } |
| xdrawable.put_image(xgc, existing, x0, y); |
| } |
| } |
| } |
| |
| protected void init() |
| { |
| super.init(); |
| } |
| |
| public void setPaint(Paint p) |
| { |
| super.setPaint(p); |
| if (p instanceof Color) |
| { |
| // TODO: Optimize for different standard bit-depths. |
| Color c = (Color) p; |
| /* XToolkit tk = (XToolkit) Toolkit.getDefaultToolkit(); |
| HashMap colorMap = tk.colorMap; |
| gnu.x11.Color col = (gnu.x11.Color) colorMap.get(c); |
| if (col == null) |
| { |
| Colormap map = xdrawable.display.default_colormap; |
| col = map.alloc_color (c.getRed() * 256, |
| c.getGreen() * 256, |
| c.getBlue() * 256); |
| colorMap.put(c, col); |
| }*/ |
| //xgc.set_foreground(col); |
| |
| xgc.set_foreground(c.getRGB()); |
| foreground = c; |
| } |
| } |
| |
| protected void fillShape(Shape s, boolean isFont) |
| { |
| synchronized (xdrawable.display) { |
| super.fillShape(s, isFont); |
| } |
| } |
| |
| private static WeakHashMap<Image,ZPixmap> imageCache = new WeakHashMap<Image,ZPixmap>(); |
| |
| protected boolean rawDrawImage(Image image, int x, int y, ImageObserver obs) |
| { |
| image = unwrap(image); |
| boolean ret; |
| if (image instanceof XImage) |
| { |
| XImage xImage = (XImage) image; |
| xdrawable.copy_area(xImage.pixmap, xgc, 0, 0, xImage.getWidth(obs), |
| xImage.getHeight(obs), x, y); |
| ret = true; |
| } |
| else if (image instanceof PixmapVolatileImage) |
| { |
| PixmapVolatileImage pvi = (PixmapVolatileImage) image; |
| xdrawable.copy_area(pvi.getPixmap(), xgc, 0, 0, pvi.getWidth(obs), |
| pvi.getHeight(obs), x, y); |
| ret = true; |
| } |
| else if (image instanceof BufferedImage) |
| { |
| BufferedImage bi = (BufferedImage) image; |
| DataBuffer db = bi.getRaster().getDataBuffer(); |
| if (db instanceof ZPixmapDataBuffer) |
| { |
| ZPixmapDataBuffer zpmdb = (ZPixmapDataBuffer) db; |
| ZPixmap zpixmap = zpmdb.getZPixmap(); |
| xdrawable.put_image(xgc, zpixmap, x, y); |
| ret = true; |
| } |
| else |
| { |
| int transparency = bi.getTransparency(); |
| int w = bi.getWidth(); |
| int h = bi.getHeight(); |
| if (imageCache.containsKey(image)) |
| { |
| ZPixmap zpixmap = imageCache.get(image); |
| xdrawable.put_image(xgc, zpixmap, x, y); |
| } |
| else if (transparency == Transparency.OPAQUE || RENDER_OPAQUE) |
| { |
| XGraphicsDevice gd = XToolkit.getDefaultDevice(); |
| ZPixmap zpixmap = new ZPixmap(gd.getDisplay(), w, h); |
| for (int yy = 0; yy < h; yy++) |
| { |
| for (int xx = 0; xx < w; xx++) |
| { |
| int rgb = bi.getRGB(xx, yy); |
| zpixmap.set(xx, yy, rgb); |
| } |
| } |
| xdrawable.put_image(xgc, zpixmap, x, y); |
| imageCache.put(image, zpixmap); |
| } else { |
| |
| // TODO optimize reusing the rectangles |
| Rectangle source = |
| new Rectangle(0, 0, xdrawable.width, xdrawable.height); |
| Rectangle target = new Rectangle(x, y, w, h); |
| |
| Rectangle destination = source.intersection(target); |
| |
| x = destination.x; |
| y = destination.y; |
| w = destination.width; |
| h = destination.height; |
| |
| ZPixmap zpixmap = |
| (ZPixmap) xdrawable.image(x, y, w, h, |
| 0xffffffff, |
| gnu.x11.image.Image.Format.ZPIXMAP); |
| for (int yy = 0; yy < h; yy++) |
| { |
| for (int xx = 0; xx < w; xx++) |
| { |
| int rgb = bi.getRGB(xx, yy); |
| int alpha = 0xff & (rgb >> 24); |
| if (alpha == 0) |
| { |
| // Completely translucent. |
| rgb = zpixmap.get_red(xx, yy) << 16 |
| | zpixmap.get_green(xx, yy) << 8 |
| | zpixmap.get_blue(xx, yy); |
| } |
| else if (alpha < 255) |
| { |
| // Composite pixels. |
| int red = 0xff & (rgb >> 16); |
| red = red * alpha |
| + (255 - alpha) * zpixmap.get_red(xx, yy); |
| red = red / 255; |
| int green = 0xff & (rgb >> 8); |
| green = green * alpha |
| + (255 - alpha) * zpixmap.get_green(xx, yy); |
| green = green / 255; |
| int blue = 0xff & rgb; |
| blue = blue * alpha |
| + (255 - alpha) * zpixmap.get_blue(xx, yy); |
| blue = blue / 255; |
| rgb = red << 16 | green << 8 | blue; |
| } |
| // else keep rgb value from source image. |
| |
| zpixmap.set(xx, yy, rgb); |
| } |
| } |
| xdrawable.put_image(xgc, zpixmap, x, y); |
| // We can't cache prerendered translucent images, because |
| // we never know how the background changes. |
| } |
| ret = true; |
| } |
| } |
| else |
| { |
| ret = super.rawDrawImage(image, x, y, obs); |
| } |
| return ret; |
| } |
| |
| public void setFont(Font f) |
| { |
| super.setFont(f); |
| FontPeer p = getFont().getPeer(); |
| if (p instanceof XFontPeer) |
| { |
| XFontPeer xFontPeer = (XFontPeer) p; |
| xgc.set_font(xFontPeer.getXFont()); |
| } |
| } |
| |
| public void drawString(String s, int x, int y) |
| { |
| FontPeer p = getFont().getPeer(); |
| if (p instanceof XFontPeer) |
| { |
| int tx = (int) transform.getTranslateX(); |
| int ty = (int) transform.getTranslateY(); |
| xdrawable.text(xgc, x + tx, y + ty, s); |
| } |
| else |
| { |
| super.drawString(s, x, y); |
| } |
| } |
| |
| /** |
| * Extracts an image instance out of an AsyncImage. If the image isn't |
| * an AsyncImage, then the original instance is returned. |
| * |
| * @param im the image |
| * |
| * @return the image to render |
| */ |
| private Image unwrap(Image im) |
| { |
| Image image = im; |
| if (image instanceof AsyncImage) |
| { |
| AsyncImage aIm = (AsyncImage) image; |
| image = aIm.getRealImage(); |
| } |
| return image; |
| } |
| |
| } |
| |