/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: PixelDrawing.java
*
* Copyright (c) 2003 Sun Microsystems and Static Free Software
*
* Electric(tm) 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 3 of the License, or
* (at your option) any later version.
*
* Electric(tm) 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 Electric(tm); see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, Mass 02111-1307, USA.
*/
package com.sun.electric.tool.user.redisplay;
import com.sun.electric.database.geometry.DBMath;
import com.sun.electric.database.geometry.EGraphics;
import com.sun.electric.database.geometry.GenMath;
import com.sun.electric.database.geometry.Orientation;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.id.CellId;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.text.TextUtils;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.Connection;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.EditWindow0;
import com.sun.electric.database.variable.TextDescriptor;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.PrimitivePort;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.user.GraphicsPreferences;
import com.sun.electric.tool.user.User;
import com.sun.electric.tool.user.ui.EditWindow;
import com.sun.electric.tool.user.ui.ElectricPrinter;
import com.sun.electric.tool.user.ui.LayerVisibility;
import com.sun.electric.tool.user.ui.WindowFrame;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.LineMetrics;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.SwingUtilities;
/**
* This class manages an offscreen display for an associated EditWindow.
* It renders an Image for copying to the display.
* <P>
* Every offscreen display consists of two parts: the transparent layers and the opaque image.
* To tell how a layer is displayed, look at the "transparentLayer" field of its "EGraphics" object.
* When this is nonzero, the layer is drawn transparent.
* When this is zero, use the "red, green, blue" fields for the opaque color.
* <P>
* The opaque image is a full-color Image that is the size of the EditWindow.
* Any layers that are marked "opaque" are drawn in full color in the image.
* Colors are not combined in the opaque image: every color placed in it overwrites the previous color.
* For this reason, opaque colors are often stipple patterns, so that they won't completely obscure other
* opaque layers.
* <P>
* The transparent layers are able to combine with each other.
* Typically, the more popular layers are made transparent (metal, poly, active, etc.)
* For every transparent layer, there is a 1-bit deep bitmap that is the size of the EditWindow.
* The bitmap is an array of "byte []" pointers, one for every Y coordinate in the EditWindow.
* Each array contains the bits for that row, packed 8 per byte.
* All of this information is in the "layerBitMaps" field, which is triply indexed.
* <P>
* Thus, to find bit (x,y) of transparent layer T, first lookup the appropriate transparent layer,
* ("layerBitMaps[T]").
* Then, for that layer, find the array of bytes for the appropriate row
* (by indexing the the Y coordinate into the rowstart array, "layerBitMaps[T][y]").
* Next, figure out which byte has the bit (by dividing the X coordinate by 8: "layerBitMaps[T][y][x>>3]").
* Finally, determine which bit to use (by using the low 3 bits of the X coordinate,
* layerBitMaps[T][y][x>>3] & (1 << (x&7)) ).
* <P>
* Transparent layers are not allocated until needed. Thus, if there are 5 possible transparent layers,
* but only 2 are used, then only two bitplanes will be created.
* <P>
* Each technology declares the number of possible transparent layers that it can generate.
* In addition, it must provide a color map for describing every combination of transparent layer.
* This map is, of course, 2-to-the-number-of-possible-transparent-layers long.
* <P>
* The expected number of transparent layers is taken from the current technology. If the user switches
* the current technology, but draws something from a different technology, then the drawn circuitry
* may make use of transparent layers that don't exist in the current technology. In such a case,
* the transparent request is made opaque.
* <P>
* When all rendering is done, the full-color image is composited with the transparent layers to produce
* the final image.
* This is done by scanning the full-color image for any entries that were not filled-in.
* These are then replaced by the transparent color at that point.
* The transparent color is computed by looking at the bits in every transparent bitmap and
* constructing an index. This is looked-up in the color table and the appropriate color is used.
* If no transparent layers are set, the background color is used.
* <P>
* There are a number of efficiencies implemented here.
* <UL>
* <LI><B>Setting bits directly into the offscreen memory</B>.
* Although Java's Swing package has a rendering model, it was found to be 3 times slower than
* setting bits directly in the offscreen memory.</LI>
* <LI><B>Tiny nodes and arcs are approximated</B>.
* When a node or arc will be only 1 or 2 pixels in size on the screen, it is not necessary
* to actually compute the edges of all of its parts. Instead, a single pixel of color is placed.
* The color is taken from all of the layers that compose the node or arc.
* For arcs that are long but only 1 pixel wide, a line is drawn in the same manner.
* This optimization adds another factor of 2 to the speed of display.</LI>
* <LI><B>Expanded cell contents are cached</B>.
* When a cell is expanded, and its contents is drawn, the contents are preserved so that they
* need be rendered only once. Subsequent instances of that expanded cell are able to be instantly drawn.
* There are a number of extra considerations here:
* <UL>
* <LI>Cell instances can appear in any orientation. Therefore, the cache of drawn cells must
* include the orientation.</LI>
* <LI>Cached cells are retained as long as the current scale is maintained. But when zooming
* in and out, the cache is cleared.</LI>
* <LI>Cell instances may appear at different levels of the hierarchy, with different other circuitry over
* them. For example, an instance may have been rendered at one level of hierarchy, and other items at that
* same level then rendered over it. It is then no longer possible to copy those bits when the instance
* appears again at another place in the hierarchy because it has been altered by neighboring circuitry.
* The same problem happens when cell instances overlap. Therefore, it is necessary to render each expanded
* cell instance into its own offscreen map, with its own separate opaque and transparent layers (which allows
* it to be composited properly when re-instantiated). Thus, a new PixelDrawing" object is created for each
* cached cell.</LI>
* <LI>Subpixel alignment may not be the same for each cached instance. This turns out not to be
* a problem, because at such zoomed-out scales, it is impossible to see individual objects anyway.</LI>
* <LI>Large cell instances should not be cached. When zoomed-in, an expanded cell instance could
* be many megabytes in size, and only a portion of it appears on the screen. Therefore, large cell
* instances are not cached, but drawn directly. It is assumed that there will be few such instances.
* The rule currently is that any cell whose width is greater than half of the display size AND whose
* height is greater than half of the display size is too large to cache.</LI>
* <LI>If an instance only appears once, it is not cached. This requires a preprocessing step to scan
* the hierarchy and count the number of times that a particular cell-transformation is used. During
* rendering, if the count is only 1, it is not cached. The exception to this rule is if the screen
* is redisplayed without a change of magnification (during panning, for example). In such a case,
* all cells will eventually be cached because, even those used once are being displayed with each redraw. </LI>
* <LI>Texture patterns don't line-up. When drawing texture pattern to the final buffer, it is easy
* to use the screen coordinates to index the pattern map, causing all of them to line-up.
* Any two adjoining objects that use the same pattern will have their patterns line-up smoothly.
* However, when caching cell instances, it is not possible to know where the contents will be placed
* on the screen, and so the texture patterns rendered into the cache cannot be aligned globally.
* To solve this, there are additional bitmaps created for every Patterned-Opaque-Layer (POL).
* When rendering on a layer that is patterned and opaque, the bitmap is dynamically allocated
* and filled (all bits are filled on the bitmap, not just those in the pattern).
* When combining lower-level cell images with higher-level ones, these POLs are copied, too.
* When compositing at the top level, however, the POLs are converted back to patterns, so that they line-up.</LI>
* </UL>
* </UL>
*
*/
public class PixelDrawing
{
/** Text smaller than this will not be drawn. */ public static final int MINIMUMTEXTSIZE = 5;
/** Text larger than this is granular. */ public static final int MAXIMUMTEXTSIZE = 100;
/** Number of singleton cells to cache when redisplaying. */ public static final int SINGLETONSTOADD = 5;
private static class PolySeg
{
private int fx,fy, tx,ty, direction, increment;
private PolySeg nextedge;
private PolySeg nextactive;
}
// statistics stuff
private static final boolean TAKE_STATS = true;
private static int tinyCells, tinyPrims, totalCells, renderedCells, totalPrims, tinyArcs, linedArcs, totalArcs;
private static int offscreensCreated, offscreenPixelsCreated, offscreensUsed, offscreenPixelsUsed, cellsRendered;
private static final boolean DEBUGRENDERTIMING = false;
private static long renderTextTime;
private static long renderPolyTime;
private static class ExpandedCellKey {
private Cell cell;
private Orientation orient;
private ExpandedCellKey(Cell cell, Orientation orient) {
this.cell = cell;
this.orient = orient;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ExpandedCellKey) {
ExpandedCellKey that = (ExpandedCellKey)obj;
return this.cell == that.cell && this.orient.equals(that.orient);
}
return false;
}
@Override
public int hashCode() { return cell.hashCode()^orient.hashCode(); }
}
/**
* This class holds information about expanded cell instances.
* For efficiency, Electric remembers the bits in an expanded cell instance
* and uses them when another expanded instance appears elsewhere.
* Of course, the orientation of the instance matters, so each combination of
* cell and orientation forms a "cell cache". The Cell Cache is stored in the
* "wnd" field (which has its own PixelDrawing object).
*/
private static class ExpandedCellInfo
{
private boolean singleton;
private int instanceCount;
private PixelDrawing offscreen;
ExpandedCellInfo()
{
singleton = true;
offscreen = null;
}
}
/** the size of the EditWindow */ private final Dimension sz;
/** the scale of the EditWindow */ private double scale;
/** the VarContext of the EditWindow */ private VarContext varContext = VarContext.globalContext;
/** the X origin of the cell in display coordinates. */ private double originX;
/** the Y origin of the cell in display coordinates. */ private double originY;
/** 0: color display, 1: color printing, 2: B&W printing */ private int nowPrinting;
/** the area of the cell to draw, in DB units */ private Rectangle2D drawBounds;
/** whether any layers are highlighted/dimmed */ boolean highlightingLayers;
/** true if the last display was a full-instantiate */ private boolean lastFullInstantiate = false;
/** A List of NodeInsts to the cell being in-place edited. */private List<NodeInst> inPlaceNodePath;
/** true if text can be drawn (not too zoomed-out) */ private boolean canDrawText;
/** maximum size before an object is too small */ private static double maxObjectSize;
/** half of maximum object size */ private static double halfMaxObjectSize;
/** temporary objects (saves reallocation) */ private final Point tempPt1 = new Point(), tempPt2 = new Point();
/** temporary objects (saves reallocation) */ private final Point tempPt3 = new Point(), tempPt4 = new Point();
// the full-depth image
/** the offscreen opaque image of the window */ private final BufferedImage img;
/** opaque layer of the window */ private final int [] opaqueData;
/** size of the opaque layer of the window */ private final int total;
/** the background color of the offscreen image */ private int backgroundColor;
/** the "unset" color of the offscreen image */ private int backgroundValue;
/** cache of port colors */ private HashMap<PrimitivePort,EGraphics> portGraphicsCache = new HashMap<PrimitivePort,EGraphics>();
// the transparent bitmaps
/** the offscreen maps for transparent layers */ private byte [][][] layerBitMaps;
/** row pointers for transparent layers */ private byte [][] compositeRows;
/** the number of transparent layers */ int numLayerBitMaps;
/** the number of bytes per row in offscreen maps */ private final int numBytesPerRow;
/** the number of offscreen transparent maps made */ private int numLayerBitMapsCreated;
/** the technology of the window */ private Technology curTech;
// the patterned opaque bitmaps
private static class PatternedOpaqueLayer {
private byte[][] layerBitMap;
PatternedOpaqueLayer(int height, int numBytesPerRow) { layerBitMap = new byte[height][numBytesPerRow]; }
}
/** the map from layers to Patterned Opaque bitmaps */ private Map<Layer,PatternedOpaqueLayer> patternedOpaqueLayers = new HashMap<Layer,PatternedOpaqueLayer>();
/** the top-level window being rendered */ private boolean renderedWindow;
/** whether to occasionally update the display. */ private boolean periodicRefresh;
/** keeps track of when to update the display. */ private int objectCount;
/** keeps track of when to update the display. */ private long lastRefreshTime;
/** the EditWindow being drawn */ private EditWindow0 wnd;
/** the size of the top-level EditWindow */ private static Dimension topSz;
/** the last Technology that had transparent layers */ private static Technology techWithLayers = null;
/** list of cell expansions. */ private static Map<ExpandedCellKey,ExpandedCellInfo> expandedCells = null;
/** Set of changed cells. */ private static final Set<CellId> changedCells = new HashSet<CellId>();
/** scale of cell expansions. */ private static double expandedScale = 0;
/** number of extra cells to render this time */ private static int numberToReconcile;
/** zero rectangle */ private static final Rectangle2D CENTERRECT = new Rectangle2D.Double(0, 0, 0, 0);
static GraphicsPreferences gp;
static AbstractDrawing.DrawingPreferences dp;
static LayerVisibility lv;
static EGraphics textGraphics = new EGraphics(false, false, null, 0, 0,0,0, 1.0,true,
new int[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0});
private static EGraphics gridGraphics = textGraphics;
static EGraphics instanceGraphics = textGraphics;
private int clipLX, clipHX, clipLY, clipHY;
private final EditWindow0 dummyWnd = new EditWindow0() {
public VarContext getVarContext() { return varContext; }
public double getScale() { return scale; }
public double getGlobalTextScale()
{
return wnd == null ? User.getGlobalTextScale() : wnd.getGlobalTextScale();
}
public String getDefaultFont()
{
return wnd == null ? User.getDefaultFont() : wnd.getDefaultFont();
}
};
static class Drawing extends AbstractDrawing {
private final int displayAlgorithm = User.getDisplayAlgorithm();
private final boolean useCellGreekingImages = User.isUseCellGreekingImages();
private final double greekSizeLimit = User.getGreekSizeLimit();
private final double greekCellSizeLimit = User.getGreekCellSizeLimit();
private final VectorDrawing vd = new VectorDrawing(useCellGreekingImages);
private volatile PixelDrawing offscreen;
Drawing(EditWindow wnd) {
super(wnd);
}
@Override
public boolean paintComponent(Graphics2D g, LayerVisibility lv, Dimension sz) {
assert SwingUtilities.isEventDispatchThread();
assert sz.equals(wnd.getSize());
PixelDrawing offscreen = this.offscreen;
if (offscreen == null || !offscreen.getSize().equals(sz))
return false;
// show the image
g.drawImage(offscreen.getBufferedImage(), 0, 0, wnd);
return true;
}
@Override
public void render(Dimension sz, WindowFrame.DisplayAttributes da, GraphicsPreferences gp,
DrawingPreferences dp, boolean fullInstantiate, Rectangle2D bounds) {
PixelDrawing.gp = gp;
PixelDrawing.dp = dp;
PixelDrawing offscreen_ = this.offscreen;
if (offscreen_ == null || !offscreen_.getSize().equals(sz))
this.offscreen = offscreen_ = new PixelDrawing(sz);
this.da = da;
boolean isPixelDrawing = displayAlgorithm == 0;
offscreen_.drawImage(this, fullInstantiate, bounds, isPixelDrawing, greekSizeLimit, greekCellSizeLimit);
}
@Override
public void abortRendering() {
if (displayAlgorithm > 0)
vd.abortRendering();
}
}
// ************************************* TOP LEVEL *************************************
/**
* Constructor creates an offscreen PixelDrawing object.
* @param sz the size of an offscreen PixelDrawinf object.
*/
public PixelDrawing(Dimension sz)
{
this.sz = new Dimension(sz);
clipLX = 0;
clipHX = sz.width - 1;
clipLY = 0;
clipHY = sz.height - 1;
// allocate pointer to the opaque image
img = new BufferedImage(sz.width, sz.height, BufferedImage.TYPE_INT_RGB);
WritableRaster raster = img.getRaster();
DataBufferInt dbi = (DataBufferInt)raster.getDataBuffer();
opaqueData = dbi.getData();
total = sz.height * sz.width;
numBytesPerRow = (sz.width + 7) / 8;
renderedWindow = true;
}
public PixelDrawing(double scale, Rectangle screenBounds) {
this.scale = scale;
this.originX = -screenBounds.x;
this.originY = screenBounds.y + screenBounds.height;
this.sz = new Dimension(screenBounds.width, screenBounds.height);
clipLX = 0;
clipHX = sz.width - 1;
clipLY = 0;
clipHY = sz.height - 1;
// allocate pointer to the opaque image
img = null;
total = sz.height * sz.width;
opaqueData = new int[total];
numBytesPerRow = (sz.width + 7) / 8;
// initialize the data
clearImage(null);
}
void initOrigin(double scale, double offx, double offy) {
this.scale = scale;
this.originX = sz.width/2 - offx*scale;
this.originY = sz.height/2 + offy*scale;
}
void initDrawing(double scale) {
clearImage(null);
initOrigin(scale, 0, 0);
}
/**
* Method to set the printing mode used for all drawing.
* @param mode the printing mode: 0=color display (default), 1=color printing, 2=B&W printing.
*/
public void setPrintingMode(int mode) { nowPrinting = mode; }
/**
* Method to override the background color.
* Must be called before "drawImage()".
* This is used by printing, which forces the background to be white.
* @param bg the background color to use.
*/
public void setBackgroundColor(Color bg)
{
backgroundColor = bg.getRGB() & 0xFFFFFF;
}
/**
* Method for obtaining the rendered image after "drawImage" has finished.
* @return an Image for this edit window.
*/
public BufferedImage getBufferedImage() { return img; }
/**
* Method for obtaining the RGB array of the rendered image after "drawImage" has finished.
* @return an RGB array for this edit window.
*/
int[] getOpaqueData() { return opaqueData; }
/**
* Method for obtaining the size of the offscreen bitmap.
* @return the size of the offscreen bitmap.
*/
public Dimension getSize() { return sz; }
/**
* Method to clear the cache of expanded subcells.
* This is used by layer visibility which, when changed, causes everything to be redrawn.
*/
public static void clearSubCellCache()
{
expandedCells = new HashMap<ExpandedCellKey,ExpandedCellInfo>();
}
/**
* Method to set the EditWindow0 associated with the rendering.
* Useful when printing.
* @param wnd the EditWindow0 to use for scaling, context, etc.
*/
public void setWindow(EditWindow0 wnd) { this.wnd = wnd; }
/**
* This is the entry point for rendering.
* It displays a cell in this offscreen window.
* @param fullInstantiate true to display to the bottom of the hierarchy (for peeking).
* @param drawLimitBounds the area in the cell to display (null to show all).
* The rendered Image can then be obtained with "getImage()".
*/
private void drawImage(Drawing drawing, boolean fullInstantiate, Rectangle2D drawLimitBounds, boolean isPixelDrawing,
double greekSizeLimit, double greekCellSizeLimit)
{
long startTime = 0;
long initialUsed = 0;
if (TAKE_STATS)
{
startTime = System.currentTimeMillis();
initialUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
tinyCells = tinyPrims = totalCells = renderedCells = totalPrims = tinyArcs = linedArcs = totalArcs = 0;
offscreensCreated = offscreenPixelsCreated = offscreensUsed = offscreenPixelsUsed = cellsRendered = 0;
}
if (fullInstantiate != lastFullInstantiate)
{
clearSubCellCache();
lastFullInstantiate = fullInstantiate;
}
EditWindow wnd = drawing.wnd;
Cell cell = wnd.getInPlaceEditTopCell();
inPlaceNodePath = wnd.getInPlaceEditNodePath();
// set colors to use
textGraphics = textGraphics.withColor(gp.getColor(User.ColorPrefType.TEXT));
gridGraphics = gridGraphics.withColor(gp.getColor(User.ColorPrefType.GRID));
instanceGraphics = instanceGraphics.withColor(gp.getColor(User.ColorPrefType.INSTANCE));
portGraphicsCache.clear();
// initialize the cache of expanded cell displays
if (expandedScale != drawing.da.scale)
{
clearSubCellCache();
expandedScale = drawing.da.scale;
}
varContext = wnd.getVarContext();
initOrigin(expandedScale, drawing.da.offX, drawing.da.offY);
canDrawText = expandedScale > 1;
maxObjectSize = 2 / expandedScale;
halfMaxObjectSize = maxObjectSize / 2;
double width = sz.width/scale;
double height = sz.height/scale;
drawBounds = new Rectangle2D.Double(drawing.da.offX - width/2, drawing.da.offY - height/2, width, height);
// remember the true window size (since recursive calls may cache individual cells that are smaller)
topSz = sz;
// see if any layers are being highlighted/dimmed
lv = wnd.getLayerVisibility();
highlightingLayers = false;
for(Iterator<Layer> it = Technology.getCurrent().getLayers(); it.hasNext(); )
{
Layer layer = it.next();
if (lv.isHighlighted(layer))
{
highlightingLayers = true;
break;
}
}
// initialize rendering into the offscreen image
Rectangle renderBounds = null;
if (drawLimitBounds != null)
{
renderBounds = databaseToScreen(drawLimitBounds);
clipLX = Math.max(renderBounds.x, 0);
clipHX = Math.min(renderBounds.x + renderBounds.width, sz.width) - 1;
clipLY = Math.max(renderBounds.y, 0);
clipHY = Math.min(renderBounds.y + renderBounds.height, sz.height) - 1;
} else {
clipLX = 0;
clipHX = sz.width - 1;
clipLY = 0;
clipHY = sz.height - 1;
}
clearImage(renderBounds);
periodicRefresh = true;
this.wnd = wnd;
objectCount = 0;
lastRefreshTime = System.currentTimeMillis();
Set<CellId> changedCellsCopy;
synchronized (changedCells) {
changedCellsCopy = new HashSet<CellId>(changedCells);
changedCells.clear();
}
forceRedraw(changedCellsCopy);
VectorCache.theCache.forceRedraw();
if (isPixelDrawing)
{
// reset cached cell counts
numberToReconcile = SINGLETONSTOADD;
for(ExpandedCellInfo count : expandedCells.values())
count.instanceCount = 0;
// determine which cells should be cached (must have at least 2 instances)
countCell(cell, drawLimitBounds, fullInstantiate, Orientation.IDENT, DBMath.MATID);
// now render it all
drawCell(cell, drawLimitBounds, fullInstantiate, Orientation.IDENT, DBMath.MATID, wnd.getCell());
} else
{
drawing.vd.render(this, scale, new Point2D.Double(drawing.da.offX, drawing.da.offY), cell, fullInstantiate,
inPlaceNodePath, wnd.getCell(), renderBounds, varContext, greekSizeLimit, greekCellSizeLimit);
}
// merge transparent image into opaque one
synchronized(img)
{
// if a grid is requested, overlay it
if (cell != null && wnd.isGrid()) drawGrid(wnd, drawing.da);
// combine transparent and opaque colors into a final image
composite(renderBounds);
}
if (TAKE_STATS && isPixelDrawing)
{
long endTime = System.currentTimeMillis();
long curUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long memConsumed = curUsed - initialUsed;
System.out.println("Took "+com.sun.electric.database.text.TextUtils.getElapsedTime(endTime-startTime)+
", rendered "+cellsRendered+" cells, used "+offscreensUsed+" ("+offscreenPixelsUsed+" pixels) cached cells, created "+
offscreensCreated+" ("+offscreenPixelsCreated+" pixels) new cell caches (my size is "+total+" pixels), memory used="+memConsumed);
System.out.println(" Cells ("+totalCells+") "+tinyCells+" are tiny;"+
" Primitives ("+totalPrims+") "+tinyPrims+" are tiny;"+
" Arcs ("+totalArcs+") "+tinyArcs+" are tiny, "+linedArcs+" are lines");
}
}
/**
* This is the entry point for rendering.
* It displays a cell in this offscreen window.
* The rendered Image can then be obtained with "getImage()".
*/
public void printImage(double scale, Point2D offset, Cell cell, VarContext varContext, ElectricPrinter ep)
{
PixelDrawing.gp = ep.getGraphicsPreferences();
clearSubCellCache();
lastFullInstantiate = false;
expandedScale = this.scale = scale;
// set colors to use
textGraphics = textGraphics.withColor(gp.getColor(User.ColorPrefType.TEXT));
gridGraphics = gridGraphics.withColor(gp.getColor(User.ColorPrefType.GRID));
instanceGraphics = instanceGraphics.withColor(gp.getColor(User.ColorPrefType.INSTANCE));
portGraphicsCache.clear();
if (wnd != null) varContext = wnd.getVarContext();
initOrigin(scale, offset.getX(), offset.getY());
canDrawText = expandedScale > 1;
maxObjectSize = 2 / expandedScale;
halfMaxObjectSize = maxObjectSize / 2;
// remember the true window size (since recursive calls may cache individual cells that are smaller)
topSz = sz;
// see if any layers are being highlighted/dimmed
highlightingLayers = false;
for(Iterator<Layer> it = Technology.getCurrent().getLayers(); it.hasNext(); )
{
Layer layer = it.next();
if (lv.isHighlighted(layer))
{
highlightingLayers = true;
break;
}
}
// initialize rendering into the offscreen image
clipLX = 0;
clipHX = sz.width - 1;
clipLY = 0;
clipHY = sz.height - 1;
clearImage(null);
Set<CellId> changedCellsCopy;
synchronized (changedCells) {
changedCellsCopy = new HashSet<CellId>(changedCells);
changedCells.clear();
}
forceRedraw(changedCellsCopy);
VectorCache.theCache.forceRedraw();
if (ep.getDisplayAlgorithm() == 0)
{
// reset cached cell counts
numberToReconcile = SINGLETONSTOADD;
for(ExpandedCellInfo count : expandedCells.values())
count.instanceCount = 0;
// determine which cells should be cached (must have at least 2 instances)
countCell(cell, null, false, Orientation.IDENT, DBMath.MATID);
// now render it all
drawCell(cell, null, false, Orientation.IDENT, DBMath.MATID, cell);
} else
{
VectorDrawing vd = new VectorDrawing(ep.isUseCellGreekingImages());
vd.render(this, scale, offset, cell, false, null, null, null, varContext,
ep.getGreekSizeLimit(), ep.getGreekCellSizeLimit());
}
// merge transparent image into opaque one
synchronized(img)
{
// combine transparent and opaque colors into a final image
composite(null);
}
}
// ************************************* INTERMEDIATE CONTROL LEVEL *************************************
/**
* Method to erase the offscreen data in this PixelDrawing.
* This is called before any rendering is done.
* @param bounds the area of the image to actually draw (null to draw all).
*/
public void clearImage(Rectangle bounds)
{
// pickup new technology if it changed
initForTechnology();
backgroundValue = gp.getColor(User.ColorPrefType.BACKGROUND).getRGB();
backgroundColor = backgroundValue & GraphicsPreferences.RGB_MASK;
// erase the transparent bitmaps
for(int i=0; i<numLayerBitMaps; i++)
{
byte [][] layerBitMap = layerBitMaps[i];
if (layerBitMap == null) continue;
for(int y=0; y<sz.height; y++)
{
byte [] row = layerBitMap[y];
for(int x=0; x<numBytesPerRow; x++)
row[x] = 0;
}
}
// erase the patterned opaque layer bitmaps
assert patternedOpaqueLayers.isEmpty(); // empty at top level
for(Iterator<PatternedOpaqueLayer> it = patternedOpaqueLayers.values().iterator(); it.hasNext(); )
{
PatternedOpaqueLayer pol = it.next();
byte [][] layerBitMap = pol.layerBitMap;
for(int y=0; y<sz.height; y++)
{
byte [] row = layerBitMap[y];
for(int x=0; x<numBytesPerRow; x++)
row[x] = 0;
}
}
// erase opaque image
if (bounds == null)
{
// erase the entire image
for(int i=0; i<total; i++) opaqueData[i] = backgroundValue;
} else
{
// erase only part of the image
int lx = bounds.x;
int hx = lx + bounds.width;
int ly = bounds.y;
int hy = ly + bounds.height;
if (lx < 0) lx = 0;
if (hx >= sz.width) hx = sz.width - 1;
if (ly < 0) ly = 0;
if (hy >= sz.height) hy = sz.height - 1;
for(int y=ly; y<=hy; y++)
{
int baseIndex = y * sz.width;
for(int x=lx; x<=hx; x++)
opaqueData[baseIndex + x] = backgroundValue;
}
}
}
/**
* Method to complete rendering by combining the transparent and opaque imagery.
* This is called after all rendering is done.
* @return the offscreen Image with the final display.
*/
public Image composite(Rectangle bounds)
{
// merge in the transparent layers
if (numLayerBitMapsCreated > 0)
{
// get the technology's color map
Color [] colorMap = gp.getColorMap(curTech);
// adjust the colors if any of the transparent layers are dimmed
boolean dimmedTransparentLayers = false;
for(Iterator<Layer> it = curTech.getLayers(); it.hasNext(); )
{
Layer layer = it.next();
if (!highlightingLayers || lv.isHighlighted(layer)) continue;
if (gp.getGraphics(layer).getTransparentLayer() == 0) continue;
dimmedTransparentLayers = true;
break;
}
if (dimmedTransparentLayers)
{
Color [] newColorMap = new Color[colorMap.length];
int numTransparents = curTech.getNumTransparentLayers();
boolean [] dimLayer = new boolean[numTransparents];
for(int i=0; i<numTransparents; i++) dimLayer[i] = true;
for(Iterator<Layer> it = curTech.getLayers(); it.hasNext(); )
{
Layer layer = it.next();
if (!lv.isHighlighted(layer)) continue;
int tIndex = gp.getGraphics(layer).getTransparentLayer();
if (tIndex == 0) continue;
dimLayer[tIndex-1] = false;
}
for(int i=0; i<colorMap.length; i++)
{
newColorMap[i] = colorMap[i];
if (i == 0) continue;
boolean dimThisEntry = true;
for(int j=0; j<numTransparents; j++)
{
if ((i & (1<<j)) != 0)
{
if (!dimLayer[j])
{
dimThisEntry = false;
break;
}
}
}
if (dimThisEntry)
{
newColorMap[i] = new Color(dimColor(colorMap[i].getRGB()));
} else
{
newColorMap[i] = new Color(brightenColor(colorMap[i].getRGB()));
}
}
colorMap = newColorMap;
}
// determine range
int lx = 0, hx = sz.width-1;
int ly = 0, hy = sz.height-1;
if (bounds != null)
{
lx = bounds.x;
hx = lx + bounds.width;
ly = bounds.y;
hy = ly + bounds.height;
if (lx < 0) lx = 0;
if (hx >= sz.width) hx = sz.width - 1;
if (ly < 0) ly = 0;
if (hy >= sz.height) hy = sz.height - 1;
}
for(int y=ly; y<=hy; y++)
{
for(int i=0; i<numLayerBitMaps; i++)
{
byte [][] layerBitMap = layerBitMaps[i];
if (layerBitMap == null) compositeRows[i] = null; else
{
compositeRows[i] = layerBitMap[y];
}
}
int baseIndex = y * sz.width;
for(int x=lx; x<=hx; x++)
{
int index = baseIndex + x;
int pixelValue = opaqueData[index];
// the value of Alpha starts at 0xFF, which means "background"
// opaque drawing typically sets it to 0, which means "filled"
// Text drawing can antialias by setting the edge values in the range 0-254
// where the lower the value, the more saturated the color (so 0 means all color, 254 means little color)
int alpha = (pixelValue >> 24) & 0xFF;
if (alpha != 0)
{
// aggregate the transparent bitplanes at this pixel
int bits = 0;
int entry = x >> 3;
int maskBit = 1 << (x & 7);
for(int i=0; i<numLayerBitMaps; i++)
{
if (compositeRows[i] == null) continue;
int byt = compositeRows[i][entry];
if ((byt & maskBit) != 0) bits |= (1<<i);
}
// determine the transparent color to draw
int newColor = backgroundColor;
if (bits != 0)
{
// set a transparent color
newColor = colorMap[bits].getRGB() & 0xFFFFFF;
}
// if alpha blending, merge with the opaque data
if (alpha != 0xFF)
{
newColor = alphaBlend(pixelValue, newColor, alpha);
}
opaqueData[index] = newColor;
}
}
}
} else
{
// nothing in transparent layers: make sure background color is right
if (bounds == null)
{
// handle the entire image
for(int i=0; i<total; i++)
{
int pixelValue = opaqueData[i];
if (pixelValue == backgroundValue) opaqueData[i] = backgroundColor; else
{
if ((pixelValue&0xFF000000) != 0)
{
int alpha = (pixelValue >> 24) & 0xFF;
opaqueData[i] = alphaBlend(pixelValue, backgroundColor, alpha);
}
}
}
} else
{
// handle a partial image
int lx = bounds.x;
int hx = lx + bounds.width;
int ly = bounds.y;
int hy = ly + bounds.height;
if (lx < 0) lx = 0;
if (hx >= sz.width) hx = sz.width - 1;
if (ly < 0) ly = 0;
if (hy >= sz.height) hy = sz.height - 1;
for(int y=ly; y<=hy; y++)
{
int baseIndex = y * sz.width;
for(int x=lx; x<=hx; x++)
{
int index = baseIndex + x;
int pixelValue = opaqueData[index];
if (pixelValue == backgroundValue) opaqueData[index] = backgroundColor; else
{
if ((pixelValue&0xFF000000) != 0)
{
int alpha = (pixelValue >> 24) & 0xFF;
opaqueData[index] = alphaBlend(pixelValue, backgroundColor, alpha);
}
}
}
}
}
}
return img;
}
/**
* Method to draw the grid into the offscreen buffer
*/
private void drawGrid(EditWindow wnd, WindowFrame.DisplayAttributes da)
{
double spacingX = wnd.getGridXSpacing();
double spacingY = wnd.getGridYSpacing();
if (spacingX == 0 || spacingY == 0) return;
double boldSpacingX = spacingX * PixelDrawing.dp.gridXBoldFrequency;
double boldSpacingY = spacingY * PixelDrawing.dp.gridYBoldFrequency;
double boldSpacingThreshX = spacingX / 4;
double boldSpacingThreshY = spacingY / 4;
// screen extent
Rectangle2D displayable = displayableBounds(da.getIntoCellTransform());
double lX = displayable.getMinX(); double lY = displayable.getMaxY();
double hX = displayable.getMaxX(); double hY = displayable.getMinY();
double scaleX = sz.width / (hX - lX);
double scaleY = sz.height / (lY - hY);
// initial grid location
double x1 = DBMath.toNearest(lX, spacingX);
double y1 = DBMath.toNearest(lY, spacingY);
// adjust grid placement according to scale
boolean allBoldDots = false;
if (spacingX * scaleX < 5 || spacingY * scaleY < 5)
{
// normal grid is too fine: only show the "bold dots"
x1 = DBMath.toNearest(x1, boldSpacingX); spacingX = boldSpacingX;
y1 = DBMath.toNearest(y1, boldSpacingY); spacingY = boldSpacingY;
// if even the bold dots are too close, don't draw a grid
if (spacingX * scaleX < 10 || spacingY * scaleY < 10)
{
wnd.printGridWarning();
return;
}
} else if (spacingX * scaleX > 75 && spacingY * scaleY > 75)
{
// if zoomed-out far enough, show all bold dots
allBoldDots = true;
}
// draw the grid
Point2D.Double tmpPt = new Point2D.Double();
AffineTransform outofCellTransform = da.getOutofCellTransform();
int col = gp.getColor(User.ColorPrefType.GRID).getRGB() & GraphicsPreferences.RGB_MASK;
for(double i = y1; i > hY; i -= spacingY)
{
double boldValueY = i;
if (i < 0) boldValueY -= boldSpacingThreshY/2; else
boldValueY += boldSpacingThreshY/2;
boolean everyTenY = Math.abs(boldValueY) % boldSpacingY < boldSpacingThreshY;
for(double j = x1; j < hX; j += spacingX)
{
tmpPt.setLocation(j, i);
outofCellTransform.transform(tmpPt, tmpPt);
databaseToScreen(tmpPt.getX(), tmpPt.getY(), tempPt1);
int x = tempPt1.x;
int y = tempPt1.y;
if (x < 0 || x >= sz.width) continue;
if (y < 0 || y >= sz.height) continue;
double boldValueX = j;
if (j < 0) boldValueX -= boldSpacingThreshX/2; else
boldValueX += boldSpacingThreshX/2;
boolean everyTenX = Math.abs(boldValueX) % boldSpacingX < boldSpacingThreshX;
if (allBoldDots && everyTenX && everyTenY)
{
int boxLX = x-2; if (boxLX < 0) boxLX = 0;
int boxHX = x+2; if (boxHX >= sz.width) boxHX = sz.width-1;
int boxLY = y-2; if (boxLY < 0) boxLY = 0;
int boxHY = y+2; if (boxHY >= sz.height) boxHY = sz.height-1;
drawBox(boxLX, boxHX, boxLY, boxHY, null, gridGraphics, false);
if (x > 1) opaqueData[y * sz.width + (x-2)] = col;
if (x < sz.width-2) opaqueData[y * sz.width + (x+2)] = col;
if (y > 1) opaqueData[(y-2) * sz.width + x] = col;
if (y < sz.height-2) opaqueData[(y+2) * sz.width + x] = col;
continue;
}
// special case every 10 grid points in each direction
if (allBoldDots || (everyTenX && everyTenY))
{
opaqueData[y * sz.width + x] = col;
if (x > 0) opaqueData[y * sz.width + (x-1)] = col;
if (x < sz.width-1) opaqueData[y * sz.width + (x+1)] = col;
if (y > 0) opaqueData[(y-1) * sz.width + x] = col;
if (y < sz.height-1) opaqueData[(y+1) * sz.width + x] = col;
continue;
}
// just a single dot
opaqueData[y * sz.width + x] = col;
}
}
if (dp.gridAxesShown)
{
tmpPt.setLocation(0, 0);
outofCellTransform.transform(tmpPt, tmpPt);
databaseToScreen(tmpPt.getX(), tmpPt.getY(), tempPt1);
int x = tempPt1.x;
int y = tempPt1.y;
if (x >= 0 && x < sz.width)
drawSolidLine(x, 0, x, sz.height-1, null, col);
if (y >= 0 && y < sz.height)
drawSolidLine(0, y, sz.width-1, y, null, col);
}
}
/**
* Method to return a rectangle in database coordinates that covers the viewable extent of this window.
* @return a rectangle that describes the viewable extent of this window (database coordinates).
*/
private Rectangle2D displayableBounds(AffineTransform intoCellTransform)
{
Point2D low = new Point2D.Double();
screenToDatabase(0, 0, low);
intoCellTransform.transform(low, low);
Point2D high = new Point2D.Double();
screenToDatabase(sz.width-1, sz.height-1, high);
intoCellTransform.transform(high, high);
double lowX = Math.min(low.getX(), high.getX());
double lowY = Math.min(low.getY(), high.getY());
double sizeX = Math.abs(high.getX()-low.getX());
double sizeY = Math.abs(high.getY()-low.getY());
Rectangle2D bounds = new Rectangle2D.Double(lowX, lowY, sizeX, sizeY);
return bounds;
}
private void initForTechnology()
{
// allocate pointers to the overlappable layers
Technology tech = Technology.getCurrent();
if (tech == null) return;
int transLayers = gp.getNumTransparentLayers(tech);
if (tech == curTech && numLayerBitMaps == transLayers) return;
if (transLayers != 0)
{
techWithLayers = curTech = tech;
}
if (curTech == null) curTech = techWithLayers;
if (curTech == null) return;
numLayerBitMaps = gp.getNumTransparentLayers(curTech);
layerBitMaps = new byte[numLayerBitMaps][][];
compositeRows = new byte[numLayerBitMaps][];
for(int i=0; i<numLayerBitMaps; i++) layerBitMaps[i] = null;
numLayerBitMapsCreated = 0;
}
/**
* Method to handle periodic refreshing during long rendering.
*/
private void periodicRefresh()
{
if (periodicRefresh)
{
objectCount++;
if (objectCount > 100)
{
objectCount = 0;
if (wnd instanceof EditWindow)
{
EditWindow wndFull = (EditWindow)wnd;
long currentTime = System.currentTimeMillis();
if (currentTime - lastRefreshTime > 1000)
wndFull.repaint();
}
}
}
}
// ************************************* HIERARCHY TRAVERSAL *************************************
/**
* Method to draw the contents of a cell, transformed through "prevTrans".
*/
private void drawCell(Cell cell, Rectangle2D drawLimitBounds, boolean fullInstantiate, Orientation orient, AffineTransform prevTrans, Cell topCell)
{
renderedCells++;
renderPolyTime = 0;
renderTextTime = 0;
// draw all arcs
for(Iterator<ArcInst> arcs = cell.getArcs(); arcs.hasNext(); )
{
ArcInst ai = arcs.next();
// if limiting drawing, reject when out of area
if (drawLimitBounds != null)
{
Rectangle2D curBounds = ai.getBounds();
Rectangle2D bounds = new Rectangle2D.Double(curBounds.getX(), curBounds.getY(), curBounds.getWidth(), curBounds.getHeight());
DBMath.transformRect(bounds, prevTrans);
if (!DBMath.rectsIntersect(bounds, drawLimitBounds)) continue;
}
drawArc(ai, prevTrans, false);
}
// draw all nodes
for(Iterator<NodeInst> nodes = cell.getNodes(); nodes.hasNext(); )
{
NodeInst ni = nodes.next();
// if limiting drawing, reject when out of area
if (drawLimitBounds != null)
{
Rectangle2D curBounds = ni.getBounds();
Rectangle2D bounds = new Rectangle2D.Double(curBounds.getX(), curBounds.getY(), curBounds.getWidth(), curBounds.getHeight());
DBMath.transformRect(bounds, prevTrans);
if (!DBMath.rectsIntersect(bounds, drawLimitBounds)) continue;
}
drawNode(ni, orient, prevTrans, topCell, drawLimitBounds, fullInstantiate, false);
}
// show cell variables if at the top level
boolean topLevel = true;
if (topCell != null) topLevel = (cell == topCell);
if (canDrawText && topLevel && gp.isTextVisibilityOn(TextDescriptor.TextType.CELL))
{
// show displayable variables on the instance
Poly[] polys = cell.getDisplayableVariables(CENTERRECT, dummyWnd, true);
drawPolys(polys, prevTrans, false);
}
if (DEBUGRENDERTIMING) {
System.out.println("Total time to render polys: "+TextUtils.getElapsedTime(renderPolyTime));
System.out.println("Total time to render text: "+TextUtils.getElapsedTime(renderTextTime));
}
}
/**
* Method to draw a NodeInst into the offscreen image.
* @param ni the NodeInst to draw.
* @param trans the transformation of the NodeInst to the display.
* @param topCell the Cell at the top-level of display.
* @param drawLimitBounds bounds in which to draw.
* @param fullInstantiate true to draw to the bottom of the hierarchy ("peek" mode).
* @param forceVisible true if layer visibility information should be ignored and force the drawing
*/
private void drawNode(NodeInst ni, Orientation orient, AffineTransform trans, Cell topCell, Rectangle2D drawLimitBounds,
boolean fullInstantiate, boolean forceVisible)
{
NodeProto np = ni.getProto();
AffineTransform localTrans = ni.rotateOut(trans);
boolean topLevel = true;
if (topCell != null) topLevel = (ni.getParent() == topCell);
// draw the node
if (ni.isCellInstance())
{
// cell instance
totalCells++;
// see if it is on the screen
Cell subCell = (Cell)np;
Orientation subOrient = orient.concatenate(ni.getOrient());
AffineTransform subTrans = ni.translateOut(localTrans);
Rectangle2D cellBounds = subCell.getBounds();
Poly poly = new Poly(cellBounds);
poly.transform(subTrans);
cellBounds = poly.getBounds2D();
Rectangle screenBounds = databaseToScreen(cellBounds);
if (screenBounds.width <= 0 || screenBounds.height <= 0)
{
tinyCells++;
return;
}
if (screenBounds.x >= sz.width || screenBounds.x+screenBounds.width <= 0) return;
if (screenBounds.y >= sz.height || screenBounds.y+screenBounds.height <= 0) return;
boolean expanded = ni.isExpanded();
if (fullInstantiate) expanded = true;
// if not expanded, but viewing this cell in-place, expand it
boolean onPathDown = false;
if (!expanded)
{
if (inPlaceNodePath != null)
{
for(int pathIndex=0; pathIndex<inPlaceNodePath.size(); pathIndex++)
{
NodeInst niOnPath = inPlaceNodePath.get(pathIndex);
if (niOnPath.getProto() == subCell)
{
expanded = true;
onPathDown = true;
break;
}
}
}
}
// two ways to draw a cell instance
if (expanded)
{
// show the contents of the cell
if (!expandedCellCached(subCell, subOrient, subTrans, topCell, drawLimitBounds, fullInstantiate))
{
// just draw it directly
cellsRendered++;
varContext = varContext.push(ni);
drawCell(subCell, drawLimitBounds, fullInstantiate, subOrient, subTrans, topCell);
varContext = varContext.pop();
}
} else
{
// draw the black box of the instance
drawUnexpandedCell(ni, poly);
}
if (canDrawText) showCellPorts(ni, trans, expanded, onPathDown);
// draw any displayable variables on the instance
if (canDrawText && gp.isTextVisibilityOn(TextDescriptor.TextType.NODE))
{
Poly[] polys = ni.getDisplayableVariables(dummyWnd);
drawPolys(polys, localTrans, false);
}
} else
{
// primitive: see if it can be drawn
if (topLevel || (!ni.isVisInside() && np != Generic.tech().cellCenterNode))
{
// see if the node is completely clipped from the screen
Point2D ctr = ni.getTrueCenter();
trans.transform(ctr, ctr);
double halfWidth = Math.max(ni.getXSize(), ni.getYSize()) / 2;
double ctrX = ctr.getX();
double ctrY = ctr.getY();
if (renderedWindow && drawBounds != null) {
Rectangle2D databaseBounds = drawBounds;
if (ctrX + halfWidth < databaseBounds.getMinX()) return;
if (ctrX - halfWidth > databaseBounds.getMaxX()) return;
if (ctrY + halfWidth < databaseBounds.getMinY()) return;
if (ctrY - halfWidth > databaseBounds.getMaxY()) return;
}
PrimitiveNode prim = (PrimitiveNode)np;
totalPrims++;
if (!prim.isCanBeZeroSize() && halfWidth < halfMaxObjectSize && !forceVisible)
{
// draw a tiny primitive by setting a single dot from each layer
tinyPrims++;
databaseToScreen(ctrX, ctrY, tempPt1);
if (tempPt1.x >= 0 && tempPt1.x < sz.width && tempPt1.y >= 0 && tempPt1.y < sz.height)
{
drawTinyLayers(prim.getLayerIterator(), tempPt1.x, tempPt1.y);
}
return;
}
EditWindow0 nodeWnd = dummyWnd;
if (!forceVisible && (!canDrawText || !gp.isTextVisibilityOn(TextDescriptor.TextType.NODE))) nodeWnd = null;
if (prim == Generic.tech().invisiblePinNode)
{
if (!gp.isTextVisibilityOn(TextDescriptor.TextType.ANNOTATION)) nodeWnd = null;
}
Technology tech = prim.getTechnology();
drawPolys(tech.getShapeOfNode(ni, false, false, null), localTrans, forceVisible);
drawPolys(ni.getDisplayableVariables(nodeWnd), localTrans, forceVisible);
}
}
// draw any exports from the node
if (canDrawText && topLevel && gp.isTextVisibilityOn(TextDescriptor.TextType.EXPORT))
{
int exportDisplayLevel = gp.exportDisplayLevel;
Iterator<Export> it = ni.getExports();
while (it.hasNext())
{
Export e = it.next();
if (np instanceof PrimitiveNode && !lv.isVisible((PrimitiveNode)np)) continue;
Poly poly = e.getNamePoly();
poly.transform(trans);
Rectangle2D rect = (Rectangle2D)poly.getBounds2D().clone();
if (exportDisplayLevel == 2)
{
// draw port as a cross
drawCross(poly, textGraphics, false);
} else
{
// draw port as text
TextDescriptor descript = poly.getTextDescriptor();
Poly.Type type = descript.getPos().getPolyType();
String portName = e.getName();
if (exportDisplayLevel == 1)
{
// use shorter port name
portName = e.getShortName();
}
databaseToScreen(poly.getCenterX(), poly.getCenterY(), tempPt1);
Rectangle textRect = new Rectangle(tempPt1);
type = Poly.rotateType(type, ni);
drawText(textRect, type, descript, portName, null, textGraphics, false);
}
// draw variables on the export
Poly[] polys = e.getDisplayableVariables(rect, dummyWnd, true);
drawPolys(polys, localTrans, false);
}
}
}
/**
* Method to render an ArcInst into the offscreen image.
* @param ai the ArcInst to draw.
* @param trans the transformation of the ArcInst to the display.
* @param forceVisible true to ignore layer visibility and draw all layers.
*/
private void drawArc(ArcInst ai, AffineTransform trans, boolean forceVisible)
{
// if the arc is tiny, just approximate it with a single dot
Rectangle2D arcBounds = ai.getBounds();
double arcSize = Math.max(arcBounds.getWidth(), arcBounds.getHeight());
totalArcs++;
if (!forceVisible)
{
if (arcSize < maxObjectSize)
{
tinyArcs++;
return;
}
if (ai.getGridFullWidth() > 0)
{
arcSize = Math.min(arcBounds.getWidth(), arcBounds.getHeight());
if (arcSize < maxObjectSize)
{
linedArcs++;
// draw a tiny arc by setting a single dot from each layer
Point2D headEnd = new Point2D.Double(ai.getHeadLocation().getX(), ai.getHeadLocation().getY());
trans.transform(headEnd, headEnd);
databaseToScreen(headEnd.getX(), headEnd.getY(), tempPt1);
Point2D tailEnd = new Point2D.Double(ai.getTailLocation().getX(), ai.getTailLocation().getY());
trans.transform(tailEnd, tailEnd);
databaseToScreen(tailEnd.getX(), tailEnd.getY(), tempPt2);
ArcProto prim = ai.getProto();
drawTinyArc(prim.getLayerIterator(), tempPt1, tempPt2);
return;
}
}
}
// see if the arc is completely clipped from the screen
Rectangle2D dbBounds = new Rectangle2D.Double(arcBounds.getX(), arcBounds.getY(), arcBounds.getWidth(), arcBounds.getHeight());
Poly p = new Poly(dbBounds);
p.transform(trans);
dbBounds = p.getBounds2D();
if (drawBounds != null && !DBMath.rectsIntersect(drawBounds, dbBounds)) return;
// draw the arc
ArcProto ap = ai.getProto();
Technology tech = ap.getTechnology();
drawPolys(tech.getShapeOfArc(ai), trans, forceVisible);
if (canDrawText && gp.isTextVisibilityOn(TextDescriptor.TextType.ARC))
drawPolys(ai.getDisplayableVariables(dummyWnd), trans, forceVisible);
}
private void showCellPorts(NodeInst ni, AffineTransform trans, boolean expanded, boolean onPathDown)
{
// show the ports that are not further exported or connected
int numPorts = ni.getProto().getNumPorts();
boolean[] shownPorts = new boolean[numPorts];
for(Iterator<Connection> it = ni.getConnections(); it.hasNext();)
{
Connection con = it.next();
PortInst pi = con.getPortInst();
Export e = (Export)pi.getPortProto();
if (!e.isAlwaysDrawn())
shownPorts[pi.getPortIndex()] = true;
}
for(Iterator<Export> it = ni.getExports(); it.hasNext();)
{
Export exp = it.next();
PortInst pi = exp.getOriginalPort();
Export e = (Export)pi.getPortProto();
if (!e.isAlwaysDrawn())
shownPorts[pi.getPortIndex()] = true;
}
int portDisplayLevel = gp.portDisplayLevel;
for(int i = 0; i < numPorts; i++)
{
if (!onPathDown && shownPorts[i]) continue;
Export pp = (Export)ni.getProto().getPort(i);
Poly portPoly = ni.getShapeOfPort(pp);
if (portPoly == null) continue;
portPoly.transform(trans);
EGraphics portGraphics = expanded ? textGraphics : getPortGraphics(pp.getBasePort());
if (portDisplayLevel == 2)
{
// draw port as a cross
drawCross(portPoly, portGraphics, false);
} else
{
// draw port as text
if (gp.isTextVisibilityOn(TextDescriptor.TextType.PORT))
{
// combine all features of port text with color of the port
TextDescriptor descript = pp.getNamePoly().getTextDescriptor();
TextDescriptor portDescript = pp.getTextDescriptor(Export.EXPORT_NAME).withColorIndex(descript.getColorIndex());
Poly.Type type = descript.getPos().getPolyType();
String portName = pp.getName();
if (portDisplayLevel == 1)
{
// use shorter port name
portName = pp.getShortName();
}
databaseToScreen(portPoly.getCenterX(), portPoly.getCenterY(), tempPt1);
Rectangle rect = new Rectangle(tempPt1);
drawText(rect, type, portDescript, portName, null, portGraphics, false);
}
}
}
}
private void drawUnexpandedCell(NodeInst ni, Poly poly)
{
// draw the instance outline
Point2D [] points = poly.getPoints();
for(int i=0; i<points.length; i++)
{
int lastI = i - 1;
if (lastI < 0) lastI = points.length - 1;
Point2D lastPt = points[lastI];
Point2D thisPt = points[i];
databaseToScreen(lastPt.getX(), lastPt.getY(), tempPt1);
databaseToScreen(thisPt.getX(), thisPt.getY(), tempPt2);
drawLine(tempPt1, tempPt2, null, instanceGraphics, 0, false);
}
// draw the instance name
if (canDrawText && gp.isTextVisibilityOn(TextDescriptor.TextType.INSTANCE))
{
Rectangle2D bounds = poly.getBounds2D();
Rectangle rect = databaseToScreen(bounds);
TextDescriptor descript = ni.getTextDescriptor(NodeInst.NODE_PROTO);
NodeProto np = ni.getProto();
drawText(rect, Poly.Type.TEXTBOX, descript, np.describe(false), null, textGraphics, false);
}
}
private void drawTinyLayers(Iterator<Layer> layerIterator, int x, int y)
{
for(Iterator<Layer> it = layerIterator; it.hasNext(); )
{
Layer layer = it.next();
if (layer == null) continue;
byte [][] layerBitMap = null;
int col = 0;
EGraphics graphics = gp.getGraphics(layer);
if (graphics != null)
{
if (nowPrinting != 0 ? graphics.isPatternedOnPrinter() : graphics.isPatternedOnDisplay())
{
int [] pattern = graphics.getPattern();
if (pattern != null)
{
int pat = pattern[y&15];
if (pat == 0 || (pat & (0x8000 >> (x&15))) == 0) continue;
}
}
int layerNum = graphics.getTransparentLayer() - 1;
if (layerNum < numLayerBitMaps) layerBitMap = getLayerBitMap(layerNum);
col = graphics.getRGB();
}
// set the bit
if (layerBitMap == null)
{
int index = y * sz.width + x;
int alpha = (opaqueData[index] >> 24) & 0xFF;
if (alpha == 0xFF) opaqueData[index] = col;
} else
{
layerBitMap[y][x>>3] |= (1 << (x&7));
}
}
}
private void drawTinyArc(Iterator<Layer> layerIterator, Point head, Point tail)
{
for(Iterator<Layer> it = layerIterator; it.hasNext(); )
{
Layer layer = it.next();
if (layer == null) continue;
EGraphics graphics = gp.getGraphics(layer);
byte [][] layerBitMap = null;
if (graphics != null)
{
int layerNum = graphics.getTransparentLayer() - 1;
if (layerNum < numLayerBitMaps) layerBitMap = getLayerBitMap(layerNum);
}
drawLine(head, tail, layerBitMap, graphics, 0, !lv.isHighlighted(layer));
}
}
// ************************************* CELL CACHING *************************************
/**
* @return true if the cell is properly handled and need no further processing.
* False to render the contents recursively.
*/
private boolean expandedCellCached(Cell subCell, Orientation orient, AffineTransform origTrans, Cell topCell, Rectangle2D drawLimitBounds, boolean fullInstantiate)
{
// if there is no global for remembering cached cells, do not cache
if (expandedCells == null) return false;
// do not cache icons: they can be redrawn each time
if (subCell.isIcon()) return false;
ExpandedCellKey expansionKey = new ExpandedCellKey(subCell, orient);
ExpandedCellInfo expandedCellCount = expandedCells.get(expansionKey);
if (expandedCellCount != null)
{
// if this combination is not used multiple times, do not cache it
if (expandedCellCount.singleton && expandedCellCount.instanceCount < 2 && expandedCellCount.offscreen == null)
{
if (numberToReconcile > 0)
{
numberToReconcile--;
expandedCellCount.singleton = false;
} else return false;
}
}
if (expandedCellCount == null || expandedCellCount.offscreen == null)
{
// compute the cell's location on the screen
Rectangle2D cellBounds = new Rectangle2D.Double();
cellBounds.setRect(subCell.getBounds());
Rectangle2D textBounds = subCell.getTextBounds(dummyWnd);
if (textBounds != null)
cellBounds.add(textBounds);
AffineTransform rotTrans = orient.pureRotate();
DBMath.transformRect(cellBounds, rotTrans);
int lX = (int)Math.floor(cellBounds.getMinX()*scale);
int hX = (int)Math.ceil(cellBounds.getMaxX()*scale);
int lY = (int)Math.floor(cellBounds.getMinY()*scale);
int hY = (int)Math.ceil(cellBounds.getMaxY()*scale);
Rectangle screenBounds = new Rectangle(lX, lY, hX - lX, hY - lY);
if (screenBounds.width <= 0 || screenBounds.height <= 0) return true;
// do not cache if the cell is too large (creates immense offscreen buffers)
if (screenBounds.width >= topSz.width/2 && screenBounds.height >= topSz.height/2)
return false;
// if this is the first use, create the offscreen buffer
if (expandedCellCount == null)
{
expandedCellCount = new ExpandedCellInfo();
expandedCells.put(expansionKey, expandedCellCount);
}
expandedCellCount.offscreen = new PixelDrawing(scale, screenBounds);
expandedCellCount.offscreen.drawCell(subCell, null, fullInstantiate, orient, rotTrans, topCell);
offscreensCreated++;
offscreenPixelsCreated += expandedCellCount.offscreen.total;
}
// copy out of the offscreen buffer into the main buffer
databaseToScreen(origTrans.getTranslateX(), origTrans.getTranslateY(), tempPt1);
copyBits(expandedCellCount.offscreen, tempPt1.x, tempPt1.y);
offscreensUsed++;
offscreenPixelsUsed += expandedCellCount.offscreen.total;
return true;
}
/**
* Recursive method to count the number of times that a cell-transformation is used
*/
private void countCell(Cell cell, Rectangle2D drawLimitBounds, boolean fullInstantiate, Orientation orient, AffineTransform prevTrans)
{
// look for subcells
for(Iterator<NodeInst> nodes = cell.getNodes(); nodes.hasNext(); )
{
NodeInst ni = nodes.next();
if (!ni.isCellInstance()) continue;
// if limiting drawing, reject when out of area
if (drawLimitBounds != null)
{
Rectangle2D curBounds = ni.getBounds();
Rectangle2D bounds = new Rectangle2D.Double(curBounds.getX(), curBounds.getY(), curBounds.getWidth(), curBounds.getHeight());
DBMath.transformRect(bounds, prevTrans);
if (!DBMath.rectsIntersect(bounds, drawLimitBounds)) return;
}
countNode(ni, drawLimitBounds, fullInstantiate, orient, prevTrans);
}
}
/**
* Recursive method to count the number of times that a cell-transformation is used
*/
private void countNode(NodeInst ni, Rectangle2D drawLimitBounds, boolean fullInstantiate, Orientation orient, AffineTransform trans)
{
// if the node is tiny, it will be approximated
double objWidth = Math.max(ni.getXSize(), ni.getYSize());
if (objWidth < maxObjectSize) return;
// transform into the subcell
Orientation subOrient = orient.concatenate(ni.getOrient());
AffineTransform subTrans = ni.transformOut(trans);
// compute where this cell lands on the screen
NodeProto np = ni.getProto();
Cell subCell = (Cell)np;
Rectangle2D cellBounds = subCell.getBounds();
Poly poly = new Poly(cellBounds);
poly.transform(subTrans);
cellBounds = poly.getBounds2D();
Rectangle screenBounds = databaseToScreen(cellBounds);
if (screenBounds.width <= 0 || screenBounds.height <= 0) return;
if (screenBounds.x > sz.width || screenBounds.x+screenBounds.width < 0) return;
if (screenBounds.y > sz.height || screenBounds.y+screenBounds.height < 0) return;
// only interested in expanded instances
boolean expanded = ni.isExpanded();
if (fullInstantiate) expanded = true;
// if not expanded, but viewing this cell in-place, expand it
if (!expanded)
{
if (inPlaceNodePath != null)
{
for(int pathIndex=0; pathIndex<inPlaceNodePath.size(); pathIndex++)
{
NodeInst niOnPath = inPlaceNodePath.get(pathIndex);
if (niOnPath.getProto() == subCell)
{
expanded = true;
break;
}
}
}
}
if (!expanded) return;
if (screenBounds.width < sz.width/2 || screenBounds.height <= sz.height/2)
{
// construct the cell name that combines with the transformation
ExpandedCellKey expansionKey = new ExpandedCellKey(subCell, subOrient);
ExpandedCellInfo expansionCount = expandedCells.get(expansionKey);
if (expansionCount == null)
{
expansionCount = new ExpandedCellInfo();
expansionCount.instanceCount = 1;
expandedCells.put(expansionKey, expansionCount);
} else
{
expansionCount.instanceCount++;
if (expansionCount.instanceCount > 1) return;
}
}
// now recurse
countCell(subCell, null, fullInstantiate, subOrient, subTrans);
}
public static void forceRedraw(Cell cell)
{
synchronized (changedCells) {
changedCells.add(cell.getId());
}
}
private static void forceRedraw(Set<CellId> changedCells)
{
// if there is no global for remembering cached cells, do not cache
if (expandedCells == null) return;
List<ExpandedCellKey> keys = new ArrayList<ExpandedCellKey>();
for(ExpandedCellKey eck : expandedCells.keySet() )
keys.add(eck);
for(ExpandedCellKey expansionKey : keys)
{
if (changedCells.contains(expansionKey.cell.getId()))
expandedCells.remove(expansionKey);
}
}
/**
* Method to copy the offscreen bits for a cell into the offscreen bits for the entire screen.
*/
private void copyBits(PixelDrawing srcOffscreen, int centerX, int centerY)
{
if (srcOffscreen == null) return;
Dimension dim = srcOffscreen.sz;
int cornerX = centerX - (int)srcOffscreen.originX;
int cornerY = centerY - (int)srcOffscreen.originY;
int minSrcX = Math.max(0, clipLX - cornerX);
int maxSrcX = Math.min(dim.width - 1, clipHX - cornerX);
int minSrcY = Math.max(0, clipLY - cornerY);
int maxSrcY = Math.min(dim.height - 1, clipHY - cornerY);
if (minSrcX > maxSrcX || minSrcY > maxSrcY) return;
if (Job.getDebug() && numLayerBitMaps != srcOffscreen.numLayerBitMaps)
System.out.println("Possible mixture of technologies in PixelDrawing.copyBits");
// copy the opaque and transparent layers
for (int srcY = minSrcY; srcY <= maxSrcY; srcY++) {
int destY = srcY + cornerY;
assert destY >= clipLY && destY <= clipHY;
if (destY < 0 || destY >= sz.height) continue;
int srcBase = srcY * dim.width;
int destBase = destY * sz.width;
for (int srcX = minSrcX; srcX <= maxSrcX; srcX++) {
int destX = srcX + cornerX;
assert destX >= clipLX && destX <= clipHX;
if (destX < 0 || destX >= sz.width) continue;
int srcColor = srcOffscreen.opaqueData[srcBase + srcX];
if (srcColor != backgroundValue)
opaqueData[destBase + destX] = srcColor;
}
}
for (int i = 0; i < numLayerBitMaps; i++) {
// out of range. Possible mixture of technologies.
if (i >= srcOffscreen.numLayerBitMaps) break;
byte [][] srcLayerBitMap = srcOffscreen.layerBitMaps[i];
if (srcLayerBitMap == null) continue;
byte [][] destLayerBitMap = getLayerBitMap(i);
for (int srcY = minSrcY; srcY <= maxSrcY; srcY++) {
int destY = srcY + cornerY;
assert destY >= clipLY && destY <= clipHY;
if (destY < 0 || destY >= sz.height) continue;
byte [] srcRow = srcLayerBitMap[srcY];
byte [] destRow = destLayerBitMap[destY];
for (int srcX = minSrcX; srcX <= maxSrcX; srcX++) {
int destX = srcX + cornerX;
assert destX >= clipLX && destX <= clipHX;
if (destX < 0 || destX >= sz.width) continue;
if ((srcRow[srcX>>3] & (1<<(srcX&7))) != 0)
destRow[destX>>3] |= (1 << (destX&7));
}
}
}
// copy the patterned opaque layers
for(Layer layer : srcOffscreen.patternedOpaqueLayers.keySet())
{
PatternedOpaqueLayer polSrc = srcOffscreen.patternedOpaqueLayers.get(layer);
byte [][] srcLayerBitMap = polSrc.layerBitMap;
if (srcLayerBitMap == null) continue;
if (renderedWindow)
{
// this is the top-level of display: convert patterned opaque to patterns
EGraphics desc = gp.getGraphics(layer);
int col = desc.getRGB();
int [] pattern = desc.getPattern();
// setup pattern for this row
for (int srcY = minSrcY; srcY <= maxSrcY; srcY++)
{
int destY = srcY + cornerY;
assert destY >= clipLY && destY <= clipHY;
if (destY < 0 || destY >= sz.height) continue;
int destBase = destY * sz.width;
int pat = pattern[destY&15];
if (pat == 0) continue;
byte [] srcRow = srcLayerBitMap[srcY];
for (int srcX = minSrcX; srcX <= maxSrcX; srcX++)
{
int destX = srcX + cornerX;
assert destX >= clipLX && destX <= clipHX;
if (destX < 0 || destX >= sz.width) continue;
if ((srcRow[srcX>>3] & (1<<(srcX&7))) != 0)
{
if ((pat & (0x8000 >> (destX&15))) != 0)
opaqueData[destBase + destX] = col;
}
}
}
} else
{
// a lower level being copied to a low level: just copy the patterned opaque layers
PatternedOpaqueLayer polDest = patternedOpaqueLayers.get(layer);
if (polDest == null)
{
polDest = new PatternedOpaqueLayer(sz.height, numBytesPerRow);
patternedOpaqueLayers.put(layer, polDest);
}
byte [][] destLayerBitMap = polDest.layerBitMap;
for(int srcY = minSrcY; srcY <= maxSrcY; srcY++)
{
int destY = srcY + cornerY;
assert destY >= clipLY && destY <= clipHY;
if (destY < 0 || destY >= sz.height) continue;
byte [] srcRow = srcLayerBitMap[srcY];
byte [] destRow = destLayerBitMap[destY];
for (int srcX = minSrcX; srcX <= maxSrcX; srcX++)
{
int destX = srcX + cornerX;
assert destX >= clipLX && destX <= clipHX;
if (destX < 0 || destX >= sz.width) continue;
if ((srcRow[srcX>>3] & (1<<(srcX&7))) != 0)
destRow[destX>>3] |= (1 << (destX&7));
}
}
}
}
}
// ************************************* RENDERING POLY SHAPES *************************************
/**
* A class representing a rectangular array of pixels. References
* to pixels outside of the bounding rectangle may result in an
* exception being thrown, or may result in references to unintended
* elements of the Raster's associated DataBuffer. It is the user's
* responsibility to avoid accessing such pixels.
*/
static interface ERaster {
/**
* Method to fill a box [lX,hX] x [lY,hY].
* Both low and high coordiantes are inclusive.
* Filling might be patterned.
* @param lX left X coordinate
* @param hX right X coordiante
* @param lY top Y coordinate
* @param hY bottom Y coordiante
*/
public void fillBox(int lX, int hX, int lY, int hY);
/**
* Method to fill a horizontal scanline [lX,hX] x [y].
* Both low and high coordiantes are inclusive.
* Filling might be patterned.
* @param y Y coordinate
* @param lX left X coordinate
* @param hX right X coordiante
*/
public void fillHorLine(int y, int lX, int hX);
/**
* Method to fill a verticaltal scanline [x] x [lY,hY].
* Both low and bigh coordiantes are inclusive.
* Filling might be patterned.
* @param x X coordinate
* @param lY top Y coordinate
* @param hY bottom Y coordiante
*/
public void fillVerLine(int x, int lY, int hY);
/**
* Method to fill a point.
* Filling might be patterned.
* @param x X coordinate
* @param y Y coordinate
*/
public void fillPoint(int x, int y);
/**
* Method to draw a horizontal line [lX,hX] x [y].
* Both low and high coordiantes are inclusive.
* Drawing is always solid.
* @param y Y coordinate
* @param lX left X coordinate
* @param hX right X coordiante
*/
public void drawHorLine(int y, int lX, int hX);
/**
* Method to draw a vertical line [x] x [lY,hY].
* Both low and high coordiantes are inclusive.
* Drawing is always solid.
* @param x X coordinate
* @param lY top Y coordinate
* @param hY bottom Y coordiante
*/
public void drawVerLine(int x, int lY, int hY);
/**
* Method to draw a point.
* @param x X coordinate
* @param y Y coordinate
*/
public void drawPoint(int x, int y);
/**
* Method to return Electric Outline style for this ERaster.
* @return Electric Outline style for this ERaster or null for no outline.
*/
public EGraphics.Outline getOutline();
}
// ************************************* RENDERING POLY SHAPES *************************************
/**
* Method to draw polygon "poly", transformed through "trans".
*/
private void drawPolys(Poly[] polys, AffineTransform trans, boolean forceVisible)
{
if (polys == null) return;
for(int i = 0; i < polys.length; i++)
{
// get the polygon and transform it
Poly poly = polys[i];
if (poly == null) continue;
Layer layer = poly.getLayer();
EGraphics graphics = poly.getGraphicsOverride();
if (graphics == null && layer != null)
graphics = gp.getGraphics(layer);
boolean dimmed = false;
if (layer != null)
{
if (!forceVisible && !lv.isVisible(layer)) continue;
dimmed = !lv.isHighlighted(layer);
}
// transform the bounds
poly.transform(trans);
// render the polygon
if (DEBUGRENDERTIMING)
{
long startTime = System.currentTimeMillis();
renderPoly(poly, graphics, dimmed);
renderPolyTime += (System.currentTimeMillis() - startTime);
} else
{
renderPoly(poly, graphics, dimmed);
}
// handle refreshing
periodicRefresh();
}
}
byte [][] getLayerBitMap(int layerNum)
{
if (layerNum < 0) return null;
byte [][] layerBitMap = layerBitMaps[layerNum];
if (layerBitMap != null) return layerBitMap;
// allocate this bitplane dynamically
return newLayerBitMap(layerNum);
}
private byte [][] newLayerBitMap(int layerNum) {
byte [][] layerBitMap= new byte[sz.height][];
for(int y=0; y<sz.height; y++) {
byte [] row = new byte[numBytesPerRow];
for(int x=0; x<numBytesPerRow; x++) row[x] = 0;
layerBitMap[y] = row;
}
layerBitMaps[layerNum] = layerBitMap;
numLayerBitMapsCreated++;
return layerBitMap;
}
/**
* Render a Poly to the offscreen buffer.
*/
private void renderPoly(Poly poly, EGraphics graphics, boolean dimmed)
{
byte [][] layerBitMap = null;
if (graphics != null)
{
int layerNum = graphics.getTransparentLayer() - 1;
if (layerNum < numLayerBitMaps) layerBitMap = getLayerBitMap(layerNum);
}
Poly.Type style = poly.getStyle();
// only do this for lower-level (cached cells)
if (!renderedWindow)
{
// for fills, handle patterned opaque layers specially
if (style == Poly.Type.FILLED || style == Poly.Type.DISC)
{
// see if it is opaque
if (layerBitMap == null)
{
// see if it is patterned
if (nowPrinting != 0 ? graphics.isPatternedOnPrinter() : graphics.isPatternedOnDisplay())
{
Layer layer = poly.getLayer();
PatternedOpaqueLayer pol = patternedOpaqueLayers.get(layer);
if (pol == null)
{
pol = new PatternedOpaqueLayer(sz.height, numBytesPerRow);
patternedOpaqueLayers.put(layer, pol);
}
layerBitMap = pol.layerBitMap;
graphics = null;
}
}
}
}
// now draw it
Point2D [] points = poly.getPoints();
if (style == Poly.Type.FILLED)
{
Rectangle2D bounds = poly.getBox();
if (bounds != null)
{
// convert coordinates
databaseToScreen(bounds.getMinX(), bounds.getMinY(), tempPt1);
databaseToScreen(bounds.getMaxX(), bounds.getMaxY(), tempPt2);
int lX = Math.min(tempPt1.x, tempPt2.x);
int hX = Math.max(tempPt1.x, tempPt2.x);
int lY = Math.min(tempPt1.y, tempPt2.y);
int hY = Math.max(tempPt1.y, tempPt2.y);
// do clipping
if (lX < 0) lX = 0;
if (hX >= sz.width) hX = sz.width-1;
if (lY < 0) lY = 0;
if (hY >= sz.height) hY = sz.height-1;
// draw the box
drawBox(lX, hX, lY, hY, layerBitMap, graphics, dimmed);
return;
}
Point [] intPoints = new Point[points.length];
for(int i=0; i<points.length; i++) {
intPoints[i] = new Point();
databaseToScreen(points[i].getX(), points[i].getY(), intPoints[i]);
}
Point [] clippedPoints = GenMath.clipPoly(intPoints, 0, sz.width-1, 0, sz.height-1);
drawPolygon(clippedPoints, layerBitMap, graphics, dimmed);
return;
}
if (style == Poly.Type.CROSSED)
{
databaseToScreen(points[0].getX(), points[0].getY(), tempPt1);
databaseToScreen(points[1].getX(), points[1].getY(), tempPt2);
databaseToScreen(points[2].getX(), points[2].getY(), tempPt3);
databaseToScreen(points[3].getX(), points[3].getY(), tempPt4);
drawLine(tempPt1, tempPt2, layerBitMap, graphics, 0, dimmed);
drawLine(tempPt2, tempPt3, layerBitMap, graphics, 0, dimmed);
drawLine(tempPt3, tempPt4, layerBitMap, graphics, 0, dimmed);
drawLine(tempPt4, tempPt1, layerBitMap, graphics, 0, dimmed);
drawLine(tempPt1, tempPt3, layerBitMap, graphics, 0, dimmed);
drawLine(tempPt2, tempPt4, layerBitMap, graphics, 0, dimmed);
return;
}
if (style.isText())
{
Rectangle2D bounds = poly.getBounds2D();
Rectangle rect = databaseToScreen(bounds);
TextDescriptor descript = poly.getTextDescriptor();
String str = poly.getString();
drawText(rect, style, descript, str, layerBitMap, graphics, dimmed);
return;
}
if (style == Poly.Type.CLOSED || style == Poly.Type.OPENED || style == Poly.Type.OPENEDT1 ||
style == Poly.Type.OPENEDT2 || style == Poly.Type.OPENEDT3)
{
int lineType = 0;
if (style == Poly.Type.OPENEDT1) lineType = 1; else
if (style == Poly.Type.OPENEDT2) lineType = 2; else
if (style == Poly.Type.OPENEDT3) lineType = 3;
for(int j=1; j<points.length; j++)
{
Point2D oldPt = points[j-1];
Point2D newPt = points[j];
databaseToScreen(oldPt.getX(), oldPt.getY(), tempPt1);
databaseToScreen(newPt.getX(), newPt.getY(), tempPt2);
drawLine(tempPt1, tempPt2, layerBitMap, graphics, lineType, dimmed);
}
if (style == Poly.Type.CLOSED)
{
Point2D oldPt = points[points.length-1];
Point2D newPt = points[0];
databaseToScreen(oldPt.getX(), oldPt.getY(), tempPt1);
databaseToScreen(newPt.getX(), newPt.getY(), tempPt2);
drawLine(tempPt1, tempPt2, layerBitMap, graphics, lineType, dimmed);
}
return;
}
if (style == Poly.Type.VECTORS)
{
for(int j=0; j<points.length; j+=2)
{
Point2D oldPt = points[j];
Point2D newPt = points[j+1];
databaseToScreen(oldPt.getX(), oldPt.getY(), tempPt1);
databaseToScreen(newPt.getX(), newPt.getY(), tempPt2);
drawLine(tempPt1, tempPt2, layerBitMap, graphics, 0, dimmed);
}
return;
}
if (style == Poly.Type.CIRCLE)
{
Point2D center = points[0];
Point2D edge = points[1];
databaseToScreen(center.getX(), center.getY(), tempPt1);
databaseToScreen(edge.getX(), edge.getY(), tempPt2);
drawCircle(tempPt1, tempPt2, layerBitMap, graphics, dimmed);
return;
}
if (style == Poly.Type.THICKCIRCLE)
{
Point2D center = points[0];
Point2D edge = points[1];
databaseToScreen(center.getX(), center.getY(), tempPt1);
databaseToScreen(edge.getX(), edge.getY(), tempPt2);
drawThickCircle(tempPt1, tempPt2, layerBitMap, graphics, dimmed);
return;
}
if (style == Poly.Type.DISC)
{
Point2D center = points[0];
Point2D edge = points[1];
databaseToScreen(center.getX(), center.getY(), tempPt1);
databaseToScreen(edge.getX(), edge.getY(), tempPt2);
drawDisc(tempPt1, tempPt2, layerBitMap, graphics, dimmed);
return;
}
if (style == Poly.Type.CIRCLEARC || style == Poly.Type.THICKCIRCLEARC)
{
Point2D center = points[0];
Point2D edge1 = points[1];
Point2D edge2 = points[2];
databaseToScreen(center.getX(), center.getY(), tempPt1);
databaseToScreen(edge1.getX(), edge1.getY(), tempPt2);
databaseToScreen(edge2.getX(), edge2.getY(), tempPt3);
drawCircleArc(tempPt1, tempPt2, tempPt3, style == Poly.Type.THICKCIRCLEARC, layerBitMap, graphics, dimmed);
return;
}
if (style == Poly.Type.CROSS)
{
// draw the cross
drawCross(poly, graphics, dimmed);
return;
}
if (style == Poly.Type.BIGCROSS)
{
// draw the big cross
Point2D center = points[0];
databaseToScreen(center.getX(), center.getY(), tempPt1);
int size = 5;
drawLine(new Point(tempPt1.x-size, tempPt1.y), new Point(tempPt1.x+size, tempPt1.y), layerBitMap, graphics, 0, dimmed);
drawLine(new Point(tempPt1.x, tempPt1.y-size), new Point(tempPt1.x, tempPt1.y+size), layerBitMap, graphics, 0, dimmed);
return;
}
}
// ************************************* BOX DRAWING *************************************
EGraphics getPortGraphics(PrimitivePort basePort) {
EGraphics portGraphics = portGraphicsCache.get(basePort);
if (portGraphics == null) {
Color graColor = basePort.getPortColor(gp);
if (graColor != null)
{
portGraphics = textGraphics.withColor(graColor);
portGraphicsCache.put(basePort, portGraphics);
}
}
return portGraphics;
}
int getTheColor(EGraphics desc, boolean dimmed)
{
if (nowPrinting == 2) return 0;
int col = desc.getRGB();
if (highlightingLayers)
{
if (dimmed) col = dimColor(col); else
col = brightenColor(col);
}
return col;
}
private double [] hsvTempArray = new double[3];
/**
* Method to dim a color by reducing its saturation.
* @param col the color as a 24-bit integer.
* @return the dimmed color, a 24-bit integer.
*/
private int dimColor(int col)
{
int r = col & 0xFF;
int g = (col >> 8) & 0xFF;
int b = (col >> 16) & 0xFF;
fromRGBtoHSV(r, g, b, hsvTempArray);
hsvTempArray[1] *= 0.2;
col = fromHSVtoRGB(hsvTempArray[0], hsvTempArray[1], hsvTempArray[2]);
return col;
}
/**
* Method to brighten a color by increasing its saturation.
* @param col the color as a 24-bit integer.
* @return the brightened color, a 24-bit integer.
*/
private int brightenColor(int col)
{
int r = col & 0xFF;
int g = (col >> 8) & 0xFF;
int b = (col >> 16) & 0xFF;
fromRGBtoHSV(r, g, b, hsvTempArray);
hsvTempArray[1] *= 1.5;
if (hsvTempArray[1] > 1) hsvTempArray[1] = 1;
col = fromHSVtoRGB(hsvTempArray[0], hsvTempArray[1], hsvTempArray[2]);
return col;
}
/**
* Method to convert a red/green/blue color to a hue/saturation/intensity color.
* Why not use Color.RGBtoHSB? It doesn't work as well.
*/
private void fromRGBtoHSV(int ir, int ig, int ib, double [] hsi)
{
double r = ir / 255.0f;
double g = ig / 255.0f;
double b = ib / 255.0f;
// "i" is maximum of "r", "g", and "b"
hsi[2] = Math.max(Math.max(r, g), b);
// "x" is minimum of "r", "g", and "b"
double x = Math.min(Math.min(r, g), b);
// "saturation" is (i-x)/i
if (hsi[2] == 0.0) hsi[1] = 0.0; else hsi[1] = (hsi[2] - x) / hsi[2];
// hue is quadrant-based
hsi[0] = 0.0;
if (hsi[1] != 0.0)
{
double rdot = (hsi[2] - r) / (hsi[2] - x);
double gdot = (hsi[2] - g) / (hsi[2] - x);
double bdot = (hsi[2] - b) / (hsi[2] - x);
if (b == x && r == hsi[2]) hsi[0] = (1.0 - gdot) / 6.0; else
if (b == x && g == hsi[2]) hsi[0] = (1.0 + rdot) / 6.0; else
if (r == x && g == hsi[2]) hsi[0] = (3.0 - bdot) / 6.0; else
if (r == x && b == hsi[2]) hsi[0] = (3.0 + gdot) / 6.0; else
if (g == x && b == hsi[2]) hsi[0] = (5.0 - rdot) / 6.0; else
if (g == x && r == hsi[2]) hsi[0] = (5.0 + bdot) / 6.0; else
System.out.println("Cannot convert (" + ir + "," + ig + "," + ib + "), for x=" + x + " i=" + hsi[2] + " s=" + hsi[1]);
}
}
/**
* Method to convert a hue/saturation/intensity color to a red/green/blue color.
* Why not use Color.HSBtoRGB? It doesn't work as well.
*/
private int fromHSVtoRGB(double h, double s, double v)
{
h = h * 6.0;
int i = (int)h;
double f = h - i;
double m = v * (1.0 - s);
double n = v * (1.0 - s * f);
double k = v * (1.0 - s * (1.0 - f));
int r = 0, g = 0, b = 0;
switch (i)
{
case 0: r = (int)(v*255.0); g = (int)(k*255.0); b = (int)(m*255.0); break;
case 1: r = (int)(n*255.0); g = (int)(v*255.0); b = (int)(m*255.0); break;
case 2: r = (int)(m*255.0); g = (int)(v*255.0); b = (int)(k*255.0); break;
case 3: r = (int)(m*255.0); g = (int)(n*255.0); b = (int)(v*255.0); break;
case 4: r = (int)(k*255.0); g = (int)(m*255.0); b = (int)(v*255.0); break;
case 5: r = (int)(v*255.0); g = (int)(m*255.0); b = (int)(n*255.0); break;
}
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255)
{
System.out.println("(" + h + "," + s + "," + v + ") -> (" + r + "," + g + "," + b + ") (i=" + i + ")");
if (r < 0) r = 0;
if (r > 255) r = 255;
if (g < 0) g = 0;
if (g > 255) g = 255;
if (b < 0) b = 0;
if (b > 255) b = 255;
}
return (b << 16) | (g << 8) | r;
}
/**
* Method to draw a box on the off-screen buffer.
*/
void drawBox(int lX, int hX, int lY, int hY, byte [][] layerBitMap, EGraphics desc, boolean dimmed)
{
// get color and pattern information
int col = 0;
int [] pattern = null;
if (desc != null)
{
col = getTheColor(desc, dimmed);
if (nowPrinting != 0 ? desc.isPatternedOnPrinter() : desc.isPatternedOnDisplay())
pattern = desc.getPattern();
}
// different code for patterned and solid
if (pattern == null)
{
// solid fill
if (layerBitMap == null)
{
// solid fill in opaque area
for(int y=lY; y<=hY; y++)
{
int baseIndex = y * sz.width + lX;
for(int x=lX; x<=hX; x++)
{
int index = baseIndex++;
int alpha = (opaqueData[index] >> 24) & 0xFF;
if (alpha == 0xFF) opaqueData[index] = col;
}
}
} else
{
// solid fill in transparent layers
for(int y=lY; y<=hY; y++)
{
byte [] row = layerBitMap[y];
for(int x=lX; x<=hX; x++)
row[x>>3] |= (1 << (x&7));
}
}
} else
{
// patterned fill
if (layerBitMap == null)
{
// patterned fill in opaque area
for(int y=lY; y<=hY; y++)
{
// setup pattern for this row
int pat = pattern[y&15];
if (pat == 0) continue;
int baseIndex = y * sz.width;
for(int x=lX; x<=hX; x++)
{
if ((pat & (0x8000 >> (x&15))) != 0)
opaqueData[baseIndex + x] = col;
}
}
} else
{
// patterned fill in transparent layers
for(int y=lY; y<=hY; y++)
{
// setup pattern for this row
int pat = pattern[y&15];
if (pat == 0) continue;
byte [] row = layerBitMap[y];
for(int x=lX; x<=hX; x++)
{
if ((pat & (0x8000 >> (x&15))) != 0)
row[x>>3] |= (1 << (x&7));
}
}
}
EGraphics.Outline o = desc.getOutlined();
if (o != EGraphics.Outline.NOPAT)
{
drawOutline(lX, lY, lX, hY, layerBitMap, col, o.getPattern(), o.getLen());
drawOutline(lX, hY, hX, hY, layerBitMap, col, o.getPattern(), o.getLen());
drawOutline(hX, hY, hX, lY, layerBitMap, col, o.getPattern(), o.getLen());
drawOutline(hX, lY, lX, lY, layerBitMap, col, o.getPattern(), o.getLen());
if (o.getThickness() != 1)
{
for(int i=1; i<o.getThickness(); i++)
{
if (lX+i < sz.width)
drawOutline(lX+i, lY, lX+i, hY, layerBitMap, col, o.getPattern(), o.getLen());
if (hY-i >= 0)
drawOutline(lX, hY-i, hX, hY-i, layerBitMap, col, o.getPattern(), o.getLen());
if (hX-i >= 0)
drawOutline(hX-i, hY, hX-i, lY, layerBitMap, col, o.getPattern(), o.getLen());
if (lY+i < sz.height)
drawOutline(hX, lY+i, lX, lY+i, layerBitMap, col, o.getPattern(), o.getLen());
}
}
}
}
}
// ************************************* LINE DRAWING *************************************
/**
* Method to draw a line on the off-screen buffer.
*/
void drawLine(Point pt1, Point pt2, byte [][] layerBitMap, EGraphics desc, int texture, boolean dimmed)
{
// first clip the line
if (GenMath.clipLine(pt1, pt2, 0, sz.width-1, 0, sz.height-1)) return;
int col = 0;
if (desc != null) col = getTheColor(desc, dimmed);
// now draw with the proper line type
switch (texture)
{
case 0: drawSolidLine(pt1.x, pt1.y, pt2.x, pt2.y, layerBitMap, col); break;
case 1: drawPatLine(pt1.x, pt1.y, pt2.x, pt2.y, layerBitMap, col, 0x88, 8); break;
case 2: drawPatLine(pt1.x, pt1.y, pt2.x, pt2.y, layerBitMap, col, 0xE7, 8); break;
case 3: drawThickLine(pt1.x, pt1.y, pt2.x, pt2.y, layerBitMap, col); break;
}
}
private void drawCross(Poly poly, EGraphics graphics, boolean dimmed)
{
Point2D [] points = poly.getPoints();
databaseToScreen(points[0].getX(), points[0].getY(), tempPt1);
int size = 3;
drawLine(new Point(tempPt1.x-size, tempPt1.y), new Point(tempPt1.x+size, tempPt1.y), null, graphics, 0, dimmed);
drawLine(new Point(tempPt1.x, tempPt1.y-size), new Point(tempPt1.x, tempPt1.y+size), null, graphics, 0, dimmed);
}
private void drawSolidLine(int x1, int y1, int x2, int y2, byte [][] layerBitMap, int col)
{
// initialize the Bresenham algorithm
int dx = Math.abs(x2-x1);
int dy = Math.abs(y2-y1);
if (dx > dy)
{
// initialize for lines that increment along X
int incr1 = 2 * dy;
int incr2 = 2 * (dy - dx);
int d = incr2;
int x, y, xend, yend, yincr;
if (x1 > x2)
{
x = x2; y = y2; xend = x1; yend = y1;
} else
{
x = x1; y = y1; xend = x2; yend = y2;
}
if (yend < y) yincr = -1; else yincr = 1;
if (layerBitMap == null) opaqueData[y * sz.width + x] = col; else
layerBitMap[y][x>>3] |= (1 << (x&7));
// draw line that increments along X
while (x < xend)
{
x++;
if (d < 0) d += incr1; else
{
y += yincr; d += incr2;
}
if (layerBitMap == null) opaqueData[y * sz.width + x] = col; else
layerBitMap[y][x>>3] |= (1 << (x&7));
}
} else
{
// initialize for lines that increment along Y
int incr1 = 2 * dx;
int incr2 = 2 * (dx - dy);
int d = incr2;
int x, y, xend, yend, xincr;
if (y1 > y2)
{
x = x2; y = y2; xend = x1; yend = y1;
} else
{
x = x1; y = y1; xend = x2; yend = y2;
}
if (xend < x) xincr = -1; else xincr = 1;
if (layerBitMap == null) opaqueData[y * sz.width + x] = col; else
layerBitMap[y][x>>3] |= (1 << (x&7));
// draw line that increments along X
while (y < yend)
{
y++;
if (d < 0) d += incr1; else
{
x += xincr; d += incr2;
}
if (layerBitMap == null) opaqueData[y * sz.width + x] = col; else
layerBitMap[y][x>>3] |= (1 << (x&7));
}
}
}
private void drawOutline(int x1, int y1, int x2, int y2, byte [][] layerBitMap, int col, int pattern, int len)
{
tempPt3.x = x1;
tempPt3.y = y1;
tempPt4.x = x2;
tempPt4.y = y2;
// first clip the line
if (GenMath.clipLine(tempPt3, tempPt4, 0, sz.width-1, 0, sz.height-1)) return;
drawPatLine(tempPt3.x, tempPt3.y, tempPt4.x, tempPt4.y, layerBitMap, col, pattern, len);
}
private void drawPatLine(int x1, int y1, int x2, int y2, byte [][] layerBitMap, int col, int pattern, int len)
{
// initialize counter for line style
int i = 0;
// initialize the Bresenham algorithm
int dx = Math.abs(x2-x1);
int dy = Math.abs(y2-y1);
if (dx > dy)
{
// initialize for lines that increment along X
int incr1 = 2 * dy;
int incr2 = 2 * (dy - dx);
int d = incr2;
int x, y, xend, yend, yincr;
if (x1 > x2)
{
x = x2; y = y2; xend = x1; yend = y1;
} else
{
x = x1; y = y1; xend = x2; yend = y2;
}
if (yend < y) yincr = -1; else yincr = 1;
if (layerBitMap == null) opaqueData[y * sz.width + x] = col; else
layerBitMap[y][x>>3] |= (1 << (x&7));
// draw line that increments along X
while (x < xend)
{
x++;
if (d < 0) d += incr1; else
{
y += yincr; d += incr2;
}
i++;
if (i == len) i = 0;
if ((pattern & (1 << i)) == 0) continue;
if (layerBitMap == null) opaqueData[y * sz.width + x] = col; else
layerBitMap[y][x>>3] |= (1 << (x&7));
}
} else
{
// initialize for lines that increment along Y
int incr1 = 2 * dx;
int incr2 = 2 * (dx - dy);
int d = incr2;
int x, y, xend, yend, xincr;
if (y1 > y2)
{
x = x2; y = y2; xend = x1; yend = y1;
} else
{
x = x1; y = y1; xend = x2; yend = y2;
}
if (xend < x) xincr = -1; else xincr = 1;
if (layerBitMap == null) opaqueData[y * sz.width + x] = col; else
layerBitMap[y][x>>3] |= (1 << (x&7));
// draw line that increments along X
while (y < yend)
{
y++;
if (d < 0) d += incr1; else
{
x += xincr; d += incr2;
}
i++;
if (i == len) i = 0;
if ((pattern & (1 << i)) == 0) continue;
if (layerBitMap == null) opaqueData[y * sz.width + x] = col; else
layerBitMap[y][x>>3] |= (1 << (x&7));
}
}
}
private void drawThickLine(int x1, int y1, int x2, int y2, byte [][] layerBitMap, int col)
{
// initialize the Bresenham algorithm
int dx = Math.abs(x2-x1);
int dy = Math.abs(y2-y1);
if (dx > dy)
{
// initialize for lines that increment along X
int incr1 = 2 * dy;
int incr2 = 2 * (dy - dx);
int d = incr2;
int x, y, xend, yend, yincr;
if (x1 > x2)
{
x = x2; y = y2; xend = x1; yend = y1;
} else
{
x = x1; y = y1; xend = x2; yend = y2;
}
if (yend < y) yincr = -1; else yincr = 1;
drawThickPoint(x, y, layerBitMap, col);
// draw line that increments along X
while (x < xend)
{
x++;
if (d < 0) d += incr1; else
{
y += yincr;
d += incr2;
}
drawThickPoint(x, y, layerBitMap, col);
}
} else
{
// initialize for lines that increment along Y
int incr1 = 2 * dx;
int incr2 = 2 * (dx - dy);
int d = incr2;
int x, y, xend, yend, xincr;
if (y1 > y2)
{
x = x2; y = y2; xend = x1; yend = y1;
} else
{
x = x1; y = y1; xend = x2; yend = y2;
}
if (xend < x) xincr = -1; else xincr = 1;
drawThickPoint(x, y, layerBitMap, col);
// draw line that increments along X
while (y < yend)
{
y++;
if (d < 0) d += incr1; else
{
x += xincr;
d += incr2;
}
drawThickPoint(x, y, layerBitMap, col);
}
}
}
// ************************************* POLYGON DRAWING *************************************
/**
* Method to draw a polygon on the off-screen buffer.
*/
void drawPolygon(Point [] points, byte [][] layerBitMap, EGraphics desc, boolean dimmed)
{
// get color and pattern information
int col = 0;
int [] pattern = null;
if (desc != null)
{
col = getTheColor(desc, dimmed);
if (nowPrinting != 0 ? desc.isPatternedOnPrinter() : desc.isPatternedOnDisplay())
pattern = desc.getPattern();
}
// fill in internal structures
PolySeg edgelist = null;
PolySeg [] polySegs = new PolySeg[points.length];
for(int i=0; i<points.length; i++)
{
polySegs[i] = new PolySeg();
if (i == 0)
{
polySegs[i].fx = points[points.length-1].x;
polySegs[i].fy = points[points.length-1].y;
} else
{
polySegs[i].fx = points[i-1].x;
polySegs[i].fy = points[i-1].y;
}
polySegs[i].tx = points[i].x; polySegs[i].ty = points[i].y;
}
for(int i=0; i<points.length; i++)
{
// compute the direction of this edge
int j = polySegs[i].ty - polySegs[i].fy;
if (j > 0) polySegs[i].direction = 1; else
if (j < 0) polySegs[i].direction = -1; else
polySegs[i].direction = 0;
// compute the X increment of this edge
if (j == 0) polySegs[i].increment = 0; else
{
polySegs[i].increment = polySegs[i].tx - polySegs[i].fx;
if (polySegs[i].increment != 0) polySegs[i].increment =
(polySegs[i].increment * 65536 - j + 1) / j;
}
polySegs[i].tx <<= 16; polySegs[i].fx <<= 16;
// make sure "from" is above "to"
if (polySegs[i].fy > polySegs[i].ty)
{
j = polySegs[i].tx;
polySegs[i].tx = polySegs[i].fx;
polySegs[i].fx = j;
j = polySegs[i].ty;
polySegs[i].ty = polySegs[i].fy;
polySegs[i].fy = j;
}
// insert this edge into the edgelist, sorted by ascending "fy"
if (edgelist == null)
{
edgelist = polySegs[i];
polySegs[i].nextedge = null;
} else
{
// insert by ascending "fy"
if (edgelist.fy > polySegs[i].fy)
{
polySegs[i].nextedge = edgelist;
edgelist = polySegs[i];
} else for(PolySeg a = edgelist; a != null; a = a.nextedge)
{
if (a.nextedge == null ||
a.nextedge.fy > polySegs[i].fy)
{
// insert after this
polySegs[i].nextedge = a.nextedge;
a.nextedge = polySegs[i];
break;
}
}
}
}
// scan polygon and render
int ycur = 0;
PolySeg active = null;
while (active != null || edgelist != null)
{
if (active == null)
{
active = edgelist;
active.nextactive = null;
edgelist = edgelist.nextedge;
ycur = active.fy;
}
// introduce edges from edge list into active list
while (edgelist != null && edgelist.fy <= ycur)
{
// insert "edgelist" into active list, sorted by "fx" coordinate
if (active.fx > edgelist.fx ||
(active.fx == edgelist.fx && active.increment > edgelist.increment))
{
edgelist.nextactive = active;
active = edgelist;
edgelist = edgelist.nextedge;
} else for(PolySeg a = active; a != null; a = a.nextactive)
{
if (a.nextactive == null ||
a.nextactive.fx > edgelist.fx ||
(a.nextactive.fx == edgelist.fx &&
a.nextactive.increment > edgelist.increment))
{
// insert after this
edgelist.nextactive = a.nextactive;
a.nextactive = edgelist;
edgelist = edgelist.nextedge;
break;
}
}
}
// generate regions to be filled in on current scan line
int wrap = 0;
PolySeg left = active;
for(PolySeg edge = active; edge != null; edge = edge.nextactive)
{
wrap = wrap + edge.direction;
if (wrap == 0)
{
int j = (left.fx + 32768) >> 16;
int k = (edge.fx + 32768) >> 16;
if (pattern != null)
{
int pat = pattern[ycur & 15];
if (pat != 0)
{
if (layerBitMap == null)
{
int baseIndex = ycur * sz.width;
for(int x=j; x<=k; x++)
{
if ((pat & (1 << (15-(x&15)))) != 0)
{
int index = baseIndex + x;
opaqueData[index] = col;
}
}
} else
{
byte [] row = layerBitMap[ycur];
for(int x=j; x<=k; x++)
{
if ((pat & (1 << (15-(x&15)))) != 0)
row[x>>3] |= (1 << (x&7));
}
}
}
} else
{
if (layerBitMap == null)
{
int baseIndex = ycur * sz.width;
for(int x=j; x<=k; x++)
{
opaqueData[baseIndex + x] = col;
}
} else
{
byte [] row = layerBitMap[ycur];
for(int x=j; x<=k; x++)
{
row[x>>3] |= (1 << (x&7));
}
}
}
left = edge.nextactive;
}
}
ycur++;
// update edges in active list
PolySeg lastedge = null;
for(PolySeg edge = active; edge != null; edge = edge.nextactive)
{
if (ycur >= edge.ty)
{
if (lastedge == null) active = edge.nextactive;
else lastedge.nextactive = edge.nextactive;
} else
{
edge.fx += edge.increment;
lastedge = edge;
}
}
}
// if outlined pattern, draw the outline
if (pattern != null)
{
EGraphics.Outline o = desc.getOutlined();
if (o != EGraphics.Outline.NOPAT)
{
for(int i=0; i<points.length; i++)
{
int last = i-1;
if (last < 0) last = points.length - 1;
int fX = points[last].x; int fY = points[last].y;
int tX = points[i].x; int tY = points[i].y;
drawOutline(fX, fY, tX, tY, layerBitMap, col, o.getPattern(), o.getLen());
if (o.getThickness() != 1)
{
int ang = GenMath.figureAngle(new Point2D.Double(fX, fY), new Point2D.Double(tX, tY));
double sin = DBMath.sin(ang+900);
double cos = DBMath.cos(ang+900);
for(int t=1; t<o.getThickness(); t++)
{
int dX = (int)(cos*t + 0.5);
int dY = (int)(sin*t + 0.5);
drawOutline(fX+dX, fY+dY, tX+dX, tY+dY, layerBitMap, col, o.getPattern(), o.getLen());
}
}
}
}
}
}
// ************************************* TEXT DRAWING *************************************
/**
* Method to draw a text on the off-screen buffer
*/
public void drawText(Rectangle rect, Poly.Type style, TextDescriptor descript, String s, byte [][] layerBitMap, EGraphics desc, boolean dimmed)
{
// quit if string is null
if (s == null) return;
int len = s.length();
if (len == 0) return;
// get parameters
int col = gp.getColor(User.ColorPrefType.TEXT).getRGB() & GraphicsPreferences.RGB_MASK;
if (desc != null) col = getTheColor(desc, dimmed);
// get text description
int size = EditWindow.getDefaultFontSize();
String fontName = gp.defaultFont;
boolean italic = false;
boolean bold = false;
boolean underline = false;
int rotation = 0;
int greekScale = 0;
int shiftUp = 0;
if (descript != null)
{
rotation = descript.getRotation().getIndex();
int colorIndex = descript.getColorIndex();
if (colorIndex != 0)
{
Color full = EGraphics.getColorFromIndex(colorIndex);
if (full != null) col = full.getRGB() & 0xFFFFFF;
}
double dSize = descript.getTrueSize(scale, wnd);
size = (int)dSize;
if (size < MINIMUMTEXTSIZE)
{
// text too small: scale it to get proper size
greekScale = 2;
for(;;)
{
size = (int)(dSize * greekScale);
if (size >= MINIMUMTEXTSIZE) break;
greekScale *= 2;
}
}
// prevent exceedingly large text
while (size > MAXIMUMTEXTSIZE)
{
size /= 2;
shiftUp++;
}
italic = descript.isItalic();
bold = descript.isBold();
underline = descript.isUnderline();
int fontIndex = descript.getFace();
if (fontIndex != 0)
{
TextDescriptor.ActiveFont af = TextDescriptor.ActiveFont.findActiveFont(fontIndex);
if (af != null) fontName = af.getName();
}
}
// get box information for limiting text size
if (style == Poly.Type.TEXTBOX)
{
if (rect.x >= sz.width || rect.x + rect.width < 0 || rect.y >= sz.height || rect.y + rect.height < 0)
return;
}
// create RenderInfo
long startTime = 0;
if (DEBUGRENDERTIMING) System.currentTimeMillis();
RenderTextInfo renderInfo = new RenderTextInfo();
if (!renderInfo.buildInfo(s, fontName, size, italic, bold, underline, rect, style, rotation, shiftUp))
return;
// if text was made "greek", just draw a line
if (greekScale != 0)
{
// text too small: make it "greek"
int width = (int)renderInfo.bounds.getWidth() / greekScale;
int sizeIndent = (size/greekScale+1) / 4;
Point pt = getTextCorner(width, size/greekScale, style, rect, rotation);
// do clipping
int lX = pt.x; int hX = lX + width;
int lY = pt.y + sizeIndent; int hY = lY;
if (lX < 0) lX = 0;
if (hX >= sz.width) hX = sz.width-1;
if (lY < 0) lY = 0;
if (hY >= sz.height) hY = sz.height-1;
drawBox(lX, hX, lY, hY, layerBitMap, desc, dimmed);
return;
}
// check if text is on-screen
if (renderInfo.bounds.getMinX() >= sz.width || renderInfo.bounds.getMaxX() < 0 ||
renderInfo.bounds.getMinY() >= sz.height || renderInfo.bounds.getMaxY() < 0)
return;
// render the text
Raster ras = renderText(renderInfo);
if (DEBUGRENDERTIMING) renderTextTime += (System.currentTimeMillis() - startTime);
if (ras == null) return;
int rasWidth = (int)renderInfo.rasBounds.getWidth() << shiftUp;
int rasHeight = (int)renderInfo.rasBounds.getHeight() << shiftUp;
Point pt = getTextCorner(rasWidth, rasHeight, style, rect, rotation);
int atX = pt.x;
int atY = pt.y;
DataBufferByte dbb = (DataBufferByte)ras.getDataBuffer();
byte [] samples = dbb.getData();
int sx, ex;
switch (rotation)
{
case 0: // no rotation
sx = Math.max(0, -atX);
ex = Math.min(rasWidth, sz.width - atX);
// if (atX < 0) sx = -atX; else sx = 0;
// if (atX+rasWidth >= sz.width) ex = sz.width-1 - atX; else
// ex = rasWidth;
for(int y=0; y<rasHeight; y++)
{
int trueY = atY + y;
if (trueY < 0 || trueY >= sz.height) continue;
// setup pointers for filling this row
byte [] row = null;
int baseIndex = 0;
if (layerBitMap == null) baseIndex = trueY * sz.width; else
row = layerBitMap[trueY];
int samp = (y>>shiftUp) * textImageWidth;
for(int x=sx; x<ex; x++)
{
int trueX = atX + x;
int alpha = samples[samp + (x>>shiftUp)] & 0xFF;
if (alpha == 0) continue;
if (layerBitMap == null)
{
// drawing opaque
int fullIndex = baseIndex + trueX;
int pixelValue = opaqueData[fullIndex];
int oldAlpha = (pixelValue >> 24) & 0xFF;
int color = col;
if (oldAlpha == 0)
{
// blend with opaque
if (alpha != 0xFF) color = alphaBlend(color, pixelValue, alpha);
} else if (oldAlpha == 0xFF)
{
// blend with background
if (alpha < 255) color = (color & 0xFFFFFF) | (alpha << 24);
}
opaqueData[fullIndex] = color;
} else
{
// draw in a transparent layer
if (alpha >= 128) row[trueX>>3] |= (1 << (trueX&7));
}
}
}
break;
case 1: // 90 degrees counterclockwise
sx = Math.max(0, -atX);
ex = Math.min(rasHeight, sz.width - atX);
// if (atX < 0) sx = -atX; else sx = 0;
// if (atX >= sz.width) ex = sz.width - atX; else
// ex = rasHeight;
for(int y=0; y<rasWidth; y++)
{
int trueY = atY - y;
if (trueY < 0 || trueY >= sz.height) continue;
// setup pointers for filling this row
byte [] row = null;
int baseIndex = 0;
if (layerBitMap == null) baseIndex = trueY * sz.width; else
row = layerBitMap[trueY];
for(int x=sx; x<ex; x++)
{
int trueX = atX + x;
int alpha = samples[(x>>shiftUp) * textImageWidth + (y>>shiftUp)] & 0xFF;
if (alpha == 0) continue;
if (layerBitMap == null)
{
// drawing opaque
int fullIndex = baseIndex + trueX;
int pixelValue = opaqueData[fullIndex];
int oldAlpha = (pixelValue >> 24) & 0xFF;
int color = col;
if (oldAlpha == 0)
{
// blend with opaque
if (alpha != 0xFF) color = alphaBlend(color, pixelValue, alpha);
} else if (oldAlpha == 0xFF)
{
// blend with background
if (alpha < 255) color = (color & 0xFFFFFF) | (alpha << 24);
}
opaqueData[fullIndex] = color;
} else
{
if (alpha >= 128) row[trueX>>3] |= (1 << (trueX&7));
}
}
}
break;
case 2: // 180 degrees
atX -= rasWidth;
atY -= rasHeight;
sx = Math.max(0, -atX);
ex = Math.min(rasWidth, sz.width - atX);
// if (atX < 0) sx = -atX; else sx = 0;
// if (atX+rasWidth >= sz.width) ex = sz.width-1 - atX; else
// ex = rasWidth;
for(int y=0; y<rasHeight; y++)
{
int trueY = atY + y;
if (trueY < 0 || trueY >= sz.height) continue;
// setup pointers for filling this row
byte [] row = null;
int baseIndex = 0;
if (layerBitMap == null) baseIndex = trueY * sz.width; else
row = layerBitMap[trueY];
for(int x=sx; x<ex; x++)
{
int trueX = atX + x;
int index = ((rasHeight-y-1)>>shiftUp) * textImageWidth + ((rasWidth-x-1)>>shiftUp);
int alpha = samples[index] & 0xFF;
if (alpha == 0) continue;
if (layerBitMap == null)
{
// drawing opaque
int fullIndex = baseIndex + trueX;
int pixelValue = opaqueData[fullIndex];
int oldAlpha = (pixelValue >> 24) & 0xFF;
int color = col;
if (oldAlpha == 0)
{
// blend with opaque
if (alpha != 0xFF) color = alphaBlend(color, pixelValue, alpha);
} else if (oldAlpha == 0xFF)
{
// blend with background
if (alpha < 255) color = (color & 0xFFFFFF) | (alpha << 24);
}
opaqueData[fullIndex] = color;
} else
{
if (alpha >= 128) row[trueX>>3] |= (1 << (trueX&7));
}
}
}
break;
case 3: // 90 degrees clockwise
sx = Math.max(0, atX - sz.width + 1);
ex = Math.min(rasHeight, atX + 1);
// if (atX < 0) sx = -atX; else sx = 0;
// if (atX >= sz.width) ex = sz.width - atX; else
// ex = rasHeight;
for(int y=0; y<rasWidth; y++)
{
int trueY = atY + y;
if (trueY < 0 || trueY >= sz.height) continue;
// setup pointers for filling this row
byte [] row = null;
int baseIndex = 0;
if (layerBitMap == null) baseIndex = trueY * sz.width; else
row = layerBitMap[trueY];
for(int x=sx; x<ex; x++)
{
int trueX = atX - x;
int alpha = samples[(x>>shiftUp) * textImageWidth + (y>>shiftUp)] & 0xFF;
if (alpha == 0) continue;
if (layerBitMap == null)
{
// drawing opaque
int fullIndex = baseIndex + trueX;
int pixelValue = opaqueData[fullIndex];
int oldAlpha = (pixelValue >> 24) & 0xFF;
int color = col;
if (oldAlpha == 0)
{
// blend with opaque
if (alpha != 0xFF) color = alphaBlend(color, pixelValue, alpha);
} else if (oldAlpha == 0xFF)
{
// blend with background
if (alpha < 255) color = (color & 0xFFFFFF) | (alpha << 24);
}
opaqueData[fullIndex] = color;
} else
{
if (alpha >= 128) row[trueX>>3] |= (1 << (trueX&7));
}
}
}
break;
}
}
private int alphaBlend(int color, int backgroundColor, int alpha)
{
int red = (color >> 16) & 0xFF;
int green = (color >> 8) & 0xFF;
int blue = color & 0xFF;
int inverseAlpha = 254 - alpha;
int redBack = (backgroundColor >> 16) & 0xFF;
int greenBack = (backgroundColor >> 8) & 0xFF;
int blueBack = backgroundColor & 0xFF;
red = ((red * alpha) + (redBack * inverseAlpha)) / 255;
green = ((green * alpha) + (greenBack * inverseAlpha)) / 255;
blue = ((blue * alpha) + (blueBack * inverseAlpha)) / 255;
color = (red << 16) | (green << 8) + blue;
return color;
}
private static class RenderTextInfo {
private Font font;
private GlyphVector gv;
private LineMetrics lm;
private Point2D anchorPoint;
private Rectangle2D rasBounds; // the raster bounds of the unrotated text, in pixels (screen units)
private Rectangle2D bounds; // the real bounds of the rotated, anchored text (in screen units)
private boolean underline;
private boolean buildInfo(String msg, String fontName, int tSize, boolean italic, boolean bold, boolean underline,
Rectangle probableBoxedBounds, Poly.Type style, int rotation, int shiftUp)
{
font = getFont(msg, fontName, tSize, italic, bold, underline);
this.underline = underline;
// convert the text to a GlyphVector
FontRenderContext frc = new FontRenderContext(null, true, true);
gv = font.createGlyphVector(frc, msg);
lm = font.getLineMetrics(msg, frc);
// figure bounding box of text
Rectangle2D rasRect = gv.getLogicalBounds();
int width = (int)rasRect.getWidth();
int height = (int)(lm.getHeight()+0.5);
if (width <= 0 || height <= 0) return false;
int fontStyle = font.getStyle();
int boxedWidth = (int)probableBoxedBounds.getWidth() >> shiftUp;
int boxedHeight = (int)probableBoxedBounds.getHeight() >> shiftUp;
// if text is to be "boxed", make sure it fits
if (boxedWidth > 1 && boxedHeight > 1)
{
if (width > boxedWidth || height > boxedHeight)
{
double scale = Math.min((double)boxedWidth / width, (double)boxedHeight / height);
font = new Font(fontName, fontStyle, (int)(tSize*scale));
if (font != null)
{
// convert the text to a GlyphVector
gv = font.createGlyphVector(frc, msg);
lm = font.getLineMetrics(msg, frc);
rasRect = gv.getLogicalBounds();
height = (int)(lm.getHeight()+0.5);
if (height <= 0) return false;
width = (int)rasRect.getWidth();
}
}
}
if (underline) height++;
rasBounds = new Rectangle2D.Double(0, lm.getAscent()-lm.getLeading(), width, height);
anchorPoint = getTextCorner(width, height, style, probableBoxedBounds, rotation);
if (rotation == 1 || rotation == 3)
{
bounds = new Rectangle2D.Double(anchorPoint.getX(), anchorPoint.getY(), height, width);
} else
{
bounds = new Rectangle2D.Double(anchorPoint.getX(), anchorPoint.getY(), width, height);
}
return true;
}
}
/**
* Method to return the coordinates of the lower-left corner of text in this window.
* @param rasterWidth the width of the text.
* @param rasterHeight the height of the text.
* @param style the anchor information for the text.
* @param rect the bounds of the polygon containing the text.
* @param rotation the rotation of the text (0=normal, 1=90 counterclockwise, 2=180, 3=90 clockwise).
* @return the coordinates of the lower-left corner of the text.
*/
private static Point getTextCorner(int rasterWidth, int rasterHeight, Poly.Type style, Rectangle rect, int rotation)
{
// adjust to place text in the center
int textWidth = rasterWidth;
int textHeight = rasterHeight;
int offX = 0, offY = 0;
if (style == Poly.Type.TEXTCENT)
{
offX = -textWidth/2;
offY = -textHeight/2;
} else if (style == Poly.Type.TEXTTOP)
{
offX = -textWidth/2;
} else if (style == Poly.Type.TEXTBOT)
{
offX = -textWidth/2;
offY = -textHeight;
} else if (style == Poly.Type.TEXTLEFT)
{
offY = -textHeight/2;
} else if (style == Poly.Type.TEXTRIGHT)
{
offX = -textWidth;
offY = -textHeight/2;
} else if (style == Poly.Type.TEXTTOPLEFT)
{
} else if (style == Poly.Type.TEXTBOTLEFT)
{
offY = -textHeight;
} else if (style == Poly.Type.TEXTTOPRIGHT)
{
offX = -textWidth;
} else if (style == Poly.Type.TEXTBOTRIGHT)
{
offX = -textWidth;
offY = -textHeight;
} if (style == Poly.Type.TEXTBOX)
{
offX = -textWidth/2;
offY = -textHeight/2;
}
if (rotation != 0)
{
int saveOffX = offX;
switch (rotation)
{
case 1:
offX = offY;
offY = -saveOffX;
break;
case 2:
offX = -offX;
offY = -offY;
break;
case 3:
offX = -offY;
offY = saveOffX;
break;
}
}
int cX = (int)rect.getCenterX() + offX;
int cY = (int)rect.getCenterY() + offY;
return new Point(cX, cY);
}
private int textImageWidth = 0, textImageHeight = 0;
private BufferedImage textImage = null;
private Graphics2D textImageGraphics;
private Raster renderText(RenderTextInfo renderInfo) {
Font theFont = renderInfo.font;
if (theFont == null) return null;
int width = (int)renderInfo.rasBounds.getWidth();
int height = (int)renderInfo.rasBounds.getHeight();
GlyphVector gv = renderInfo.gv;
LineMetrics lm = renderInfo.lm;
// Too small to appear in the screen
if (width <= 0 || height <= 0)
return null;
// if the new image is larger than what is saved, must rebuild
if (width > textImageWidth || height > textImageHeight)
textImage = null;
if (textImage == null)
{
// create a new text buffer
textImageWidth = width;
textImageHeight = height;
textImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
textImageGraphics = textImage.createGraphics();
} else
{
// clear and reuse the existing text buffer
textImageGraphics.setColor(Color.BLACK);
textImageGraphics.fillRect(0, 0, width, height);
}
// now render it
Graphics2D g2 = (Graphics2D)textImage.getGraphics();
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2.setColor(new Color(255,255,255));
g2.drawGlyphVector(gv, (float)-renderInfo.rasBounds.getX(), lm.getAscent()-lm.getLeading());
if (renderInfo.underline)
{
g2.drawLine(0, height-1, width-1, height-1);
}
// return the bits
return textImage.getData();
}
public static Font getFont(String msg, String font, int tSize, boolean italic, boolean bold, boolean underline) {
// get the font
int fontStyle = Font.PLAIN;
if (italic) fontStyle |= Font.ITALIC;
if (bold) fontStyle |= Font.BOLD;
Font theFont = new Font(font, fontStyle, tSize);
if (theFont == null)
{
System.out.println("Could not find font "+font+" to render text: "+msg);
return null;
}
return theFont;
}
// ************************************* CIRCLE DRAWING *************************************
/**
* Method to draw a circle on the off-screen buffer
*/
void drawCircle(Point center, Point edge, byte [][] layerBitMap, EGraphics desc, boolean dimmed)
{
// get parameters
int radius = (int)center.distance(edge);
int col = 0;
if (desc != null) col = getTheColor(desc, dimmed);
// set redraw area
int left = center.x - radius;
int right = center.x + radius + 1;
int top = center.y - radius;
int bottom = center.y + radius + 1;
int x = 0; int y = radius;
int d = 3 - 2 * radius;
if (left >= 0 && right < sz.width && top >= 0 && bottom < sz.height)
{
// no clip version is faster
while (x <= y)
{
if (layerBitMap == null)
{
int baseIndex = (center.y + y) * sz.width;
opaqueData[baseIndex + (center.x+x)] = col;
opaqueData[baseIndex + (center.x-x)] = col;
baseIndex = (center.y - y) * sz.width;
opaqueData[baseIndex + (center.x+x)] = col;
opaqueData[baseIndex + (center.x-x)] = col;
baseIndex = (center.y + x) * sz.width;
opaqueData[baseIndex + (center.x+y)] = col;
opaqueData[baseIndex + (center.x-y)] = col;
baseIndex = (center.y - x) * sz.width;
opaqueData[baseIndex + (center.x+y)] = col;
opaqueData[baseIndex + (center.x-y)] = col;
} else
{
byte [] row = layerBitMap[center.y + y];
row[(center.x+x)>>3] |= (1 << ((center.x+x)&7));
row[(center.x-x)>>3] |= (1 << ((center.x-x)&7));
row = layerBitMap[center.y - y];
row[(center.x+x)>>3] |= (1 << ((center.x+x)&7));
row[(center.x-x)>>3] |= (1 << ((center.x-x)&7));
row = layerBitMap[center.y + x];
row[(center.x+y)>>3] |= (1 << ((center.x+y)&7));
row[(center.x-y)>>3] |= (1 << ((center.x-y)&7));
row = layerBitMap[center.y - x];
row[(center.x+y)>>3] |= (1 << ((center.x+y)&7));
row[(center.x-y)>>3] |= (1 << ((center.x-y)&7));
}
if (d < 0) d += 4*x + 6; else
{
d += 4 * (x-y) + 10;
y--;
}
x++;
}
} else
{
// clip version
while (x <= y)
{
int thisy = center.y + y;
if (thisy >= 0 && thisy < sz.height)
{
int thisx = center.x + x;
if (thisx >= 0 && thisx < sz.width)
drawPoint(thisx, thisy, layerBitMap, col);
thisx = center.x - x;
if (thisx >= 0 && thisx < sz.width)
drawPoint(thisx, thisy, layerBitMap, col);
}
thisy = center.y - y;
if (thisy >= 0 && thisy < sz.height)
{
int thisx = center.x + x;
if (thisx >= 0 && thisx < sz.width)
drawPoint(thisx, thisy, layerBitMap, col);
thisx = center.x - x;
if (thisx >= 0 && thisx < sz.width)
drawPoint(thisx, thisy, layerBitMap, col);
}
thisy = center.y + x;
if (thisy >= 0 && thisy < sz.height)
{
int thisx = center.x + y;
if (thisx >= 0 && thisx < sz.width)
drawPoint(thisx, thisy, layerBitMap, col);
thisx = center.x - y;
if (thisx >= 0 && thisx < sz.width)
drawPoint(thisx, thisy, layerBitMap, col);
}
thisy = center.y - x;
if (thisy >= 0 && thisy < sz.height)
{
int thisx = center.x + y;
if (thisx >= 0 && thisx < sz.width)
drawPoint(thisx, thisy, layerBitMap, col);
thisx = center.x - y;
if (thisx >= 0 && thisx < sz.width)
drawPoint(thisx, thisy, layerBitMap, col);
}
if (d < 0) d += 4*x + 6; else
{
d += 4 * (x-y) + 10;
y--;
}
x++;
}
}
}
/**
* Method to draw a thick circle on the off-screen buffer
*/
void drawThickCircle(Point center, Point edge, byte [][] layerBitMap, EGraphics desc, boolean dimmed)
{
// get parameters
int radius = (int)center.distance(edge);
int col = 0;
if (desc != null) col = getTheColor(desc, dimmed);
int x = 0; int y = radius;
int d = 3 - 2 * radius;
while (x <= y)
{
int thisy = center.y + y;
if (thisy >= 0 && thisy < sz.height)
{
int thisx = center.x + x;
if (thisx >= 0 && thisx < sz.width)
drawThickPoint(thisx, thisy, layerBitMap, col);
thisx = center.x - x;
if (thisx >= 0 && thisx < sz.width)
drawThickPoint(thisx, thisy, layerBitMap, col);
}
thisy = center.y - y;
if (thisy >= 0 && thisy < sz.height)
{
int thisx = center.x + x;
if (thisx >= 0 && thisx < sz.width)
drawThickPoint(thisx, thisy, layerBitMap, col);
thisx = center.x - x;
if (thisx >= 0 && thisx < sz.width)
drawThickPoint(thisx, thisy, layerBitMap, col);
}
thisy = center.y + x;
if (thisy >= 0 && thisy < sz.height)
{
int thisx = center.x + y;
if (thisx >= 0 && thisx < sz.width)
drawThickPoint(thisx, thisy, layerBitMap, col);
thisx = center.x - y;
if (thisx >= 0 && thisx < sz.width)
drawThickPoint(thisx, thisy, layerBitMap, col);
}
thisy = center.y - x;
if (thisy >= 0 && thisy < sz.height)
{
int thisx = center.x + y;
if (thisx >= 0 && thisx < sz.width)
drawThickPoint(thisx, thisy, layerBitMap, col);
thisx = center.x - y;
if (thisx >= 0 && thisx < sz.width)
drawThickPoint(thisx, thisy, layerBitMap, col);
}
if (d < 0) d += 4*x + 6; else
{
d += 4 * (x-y) + 10;
y--;
}
x++;
}
}
// ************************************* DISC DRAWING *************************************
/**
* Method to draw a scan line of the filled-in circle of radius "radius"
*/
private void drawDiscRow(int thisy, int startx, int endx, byte [][] layerBitMap, int col, int [] pattern)
{
if (thisy < 0 || thisy >= sz.height) return;
if (startx < 0) startx = 0;
if (endx >= sz.width) endx = sz.width - 1;
if (pattern != null)
{
int pat = pattern[thisy & 15];
if (pat != 0)
{
if (layerBitMap == null)
{
int baseIndex = thisy * sz.width;
for(int x=startx; x<=endx; x++)
{
if ((pat & (1 << (15-(x&15)))) != 0)
opaqueData[baseIndex + x] = col;
}
} else
{
byte [] row = layerBitMap[thisy];
for(int x=startx; x<=endx; x++)
{
if ((pat & (1 << (15-(x&15)))) != 0)
row[x>>3] |= (1 << (x&7));
}
}
}
} else
{
if (layerBitMap == null)
{
int baseIndex = thisy * sz.width;
for(int x=startx; x<=endx; x++)
{
int index = baseIndex + x;
int alpha = (opaqueData[index] >> 24) & 0xFF;
if (alpha == 0xFF) opaqueData[index] = col;
}
} else
{
byte [] row = layerBitMap[thisy];
for(int x=startx; x<=endx; x++)
{
row[x>>3] |= (1 << (x&7));
}
}
}
}
/**
* Method to draw a filled-in circle of radius "radius" on the off-screen buffer
*/
void drawDisc(Point center, Point edge, byte [][] layerBitMap, EGraphics desc, boolean dimmed)
{
// get parameters
int radius = (int)center.distance(edge);
int col = 0;
int [] pattern = null;
if (desc != null)
{
col = getTheColor(desc, dimmed);
if (nowPrinting != 0 ? desc.isPatternedOnPrinter() : desc.isPatternedOnDisplay())
{
pattern = desc.getPattern();
if (desc.getOutlined() != EGraphics.Outline.NOPAT)
{
drawCircle(center, edge, layerBitMap, desc, dimmed);
}
}
}
// set redraw area
int left = center.x - radius;
int right = center.x + radius + 1;
int top = center.y - radius;
int bottom = center.y + radius + 1;
if (radius == 1)
{
// just fill the area for discs this small
if (left < 0) left = 0;
if (right >= sz.width) right = sz.width - 1;
for(int y=top; y<bottom; y++)
{
if (y < 0 || y >= sz.height) continue;
for(int x=left; x<right; x++)
drawPoint(x, y, layerBitMap, col);
}
return;
}
int x = 0; int y = radius;
int d = 3 - 2 * radius;
while (x <= y)
{
drawDiscRow(center.y+y, center.x-x, center.x+x, layerBitMap, col, pattern);
drawDiscRow(center.y-y, center.x-x, center.x+x, layerBitMap, col, pattern);
drawDiscRow(center.y+x, center.x-y, center.x+y, layerBitMap, col, pattern);
drawDiscRow(center.y-x, center.x-y, center.x+y, layerBitMap, col, pattern);
if (d < 0) d += 4*x + 6; else
{
d += 4 * (x-y) + 10;
y--;
}
x++;
}
}
// ************************************* ARC DRAWING *************************************
private boolean [] arcOctTable = new boolean[9];
private Point arcCenter;
private int arcRadius;
private int arcCol;
private byte [][] arcLayerBitMap;
private boolean arcThick;
private int arcFindOctant(int x, int y)
{
if (x > 0)
{
if (y >= 0)
{
if (y >= x) return 7;
return 8;
}
if (x >= -y) return 1;
return 2;
}
if (y > 0)
{
if (y > -x) return 6;
return 5;
}
if (y > x) return 4;
return 3;
}
private Point arcXformOctant(int x, int y, int oct)
{
switch (oct)
{
case 1 : return new Point(-y, x);
case 2 : return new Point( x, -y);
case 3 : return new Point(-x, -y);
case 4 : return new Point(-y, -x);
case 5 : return new Point( y, -x);
case 6 : return new Point(-x, y);
case 7 : return new Point( x, y);
case 8 : return new Point( y, x);
}
return null;
}
private void arcDoPixel(int x, int y)
{
if (x < 0 || x >= sz.width || y < 0 || y >= sz.height) return;
if (arcThick)
{
drawThickPoint(x, y, arcLayerBitMap, arcCol);
} else
{
drawPoint(x, y, arcLayerBitMap, arcCol);
}
}
private void arcOutXform(int x, int y)
{
if (arcOctTable[1]) arcDoPixel( y + arcCenter.x, -x + arcCenter.y);
if (arcOctTable[2]) arcDoPixel( x + arcCenter.x, -y + arcCenter.y);
if (arcOctTable[3]) arcDoPixel(-x + arcCenter.x, -y + arcCenter.y);
if (arcOctTable[4]) arcDoPixel(-y + arcCenter.x, -x + arcCenter.y);
if (arcOctTable[5]) arcDoPixel(-y + arcCenter.x, x + arcCenter.y);
if (arcOctTable[6]) arcDoPixel(-x + arcCenter.x, y + arcCenter.y);
if (arcOctTable[7]) arcDoPixel( x + arcCenter.x, y + arcCenter.y);
if (arcOctTable[8]) arcDoPixel( y + arcCenter.x, x + arcCenter.y);
}
private void arcBresCW(Point pt, Point pt1)
{
int d = 3 - 2 * pt.y + 4 * pt.x;
while (pt.x < pt1.x && pt.y > pt1.y)
{
arcOutXform(pt.x, pt.y);
if (d < 0) d += 4 * pt.x + 6; else
{
d += 4 * (pt.x-pt.y) + 10;
pt.y--;
}
pt.x++;
}
// get to the end
for ( ; pt.x < pt1.x; pt.x++) arcOutXform(pt.x, pt.y);
for ( ; pt.y > pt1.y; pt.y--) arcOutXform(pt.x, pt.y);
arcOutXform(pt1.x, pt1.y);
}
private void arcBresMidCW(Point pt)
{
int d = 3 - 2 * pt.y + 4 * pt.x;
while (pt.x < pt.y)
{
arcOutXform(pt.x, pt.y);
if (d < 0) d += 4 * pt.x + 6; else
{
d += 4 * (pt.x-pt.y) + 10;
pt.y--;
}
pt.x++;
}
if (pt.x == pt.y) arcOutXform(pt.x, pt.y);
}
private void arcBresMidCCW(Point pt)
{
int d = 3 + 2 * pt.y - 4 * pt.x;
while (pt.x > 0)
{
arcOutXform(pt.x, pt.y);
if (d > 0) d += 6-4 * pt.x; else
{
d += 4 * (pt.y-pt.x) + 10;
pt.y++;
}
pt.x--;
}
arcOutXform(0, arcRadius);
}
private void arcBresCCW(Point pt, Point pt1)
{
int d = 3 + 2 * pt.y + 4 * pt.x;
while(pt.x > pt1.x && pt.y < pt1.y)
{
// not always correct
arcOutXform(pt.x, pt.y);
if (d > 0) d += 6 - 4 * pt.x; else
{
d += 4 * (pt.y-pt.x) + 10;
pt.y++;
}
pt.x--;
}
// get to the end
for ( ; pt.x > pt1.x; pt.x--) arcOutXform(pt.x, pt.y);
for ( ; pt.y < pt1.y; pt.y++) arcOutXform(pt.x, pt.y);
arcOutXform(pt1.x, pt1.y);
}
/**
* draws an arc centered at (centerx, centery), clockwise,
* passing by (x1,y1) and (x2,y2)
*/
void drawCircleArc(Point center, Point p1, Point p2, boolean thick, byte [][] layerBitMap, EGraphics desc, boolean dimmed)
{
// ignore tiny arcs
if (p1.x == p2.x && p1.y == p2.y) return;
// get parameters
arcLayerBitMap = layerBitMap;
arcCol = 0;
if (desc != null) arcCol = getTheColor(desc, dimmed);
arcCenter = center;
int pa_x = p2.x - arcCenter.x;
int pa_y = p2.y - arcCenter.y;
int pb_x = p1.x - arcCenter.x;
int pb_y = p1.y - arcCenter.y;
arcRadius = (int)arcCenter.distance(p2);
int alternate = (int)arcCenter.distance(p1);
int start_oct = arcFindOctant(pa_x, pa_y);
int end_oct = arcFindOctant(pb_x, pb_y);
arcThick = thick;
// move the point
if (arcRadius != alternate)
{
int diff = arcRadius-alternate;
switch (end_oct)
{
case 6:
case 7: /* y > x */ pb_y += diff; break;
case 8: /* x > y */
case 1: /* x > -y */ pb_x += diff; break;
case 2: /* -y > x */
case 3: /* -y > -x */ pb_y -= diff; break;
case 4: /* -y < -x */
case 5: /* y < -x */ pb_x -= diff; break;
}
}
for(int i=1; i<9; i++) arcOctTable[i] = false;
if (start_oct == end_oct)
{
arcOctTable[start_oct] = true;
Point pa = arcXformOctant(pa_x, pa_y, start_oct);
Point pb = arcXformOctant(pb_x, pb_y, start_oct);
if ((start_oct&1) != 0) arcBresCW(pa, pb);
else arcBresCCW(pa, pb);
arcOctTable[start_oct] = false;
} else
{
arcOctTable[start_oct] = true;
Point pt = arcXformOctant(pa_x, pa_y, start_oct);
if ((start_oct&1) != 0) arcBresMidCW(pt);
else arcBresMidCCW(pt);
arcOctTable[start_oct] = false;
arcOctTable[end_oct] = true;
pt = arcXformOctant(pb_x, pb_y, end_oct);
if ((end_oct&1) != 0) arcBresMidCCW(pt);
else arcBresMidCW(pt);
arcOctTable[end_oct] = false;
if (MODP(start_oct+1) != end_oct)
{
if (MODP(start_oct+1) == MODM(end_oct-1))
{
arcOctTable[MODP(start_oct+1)] = true;
} else
{
for(int i = MODP(start_oct+1); i != end_oct; i = MODP(i+1))
arcOctTable[i] = true;
}
arcBresMidCW(new Point(0, arcRadius));
}
}
}
private int MODM(int x) { return (x<1) ? x+8 : x; }
private int MODP(int x) { return (x>8) ? x-8 : x; }
// ************************************* RENDERING SUPPORT *************************************
void drawPoint(int x, int y, byte [][] layerBitMap, int col)
{
if (layerBitMap == null)
{
opaqueData[y * sz.width + x] = col;
} else
{
layerBitMap[y][x>>3] |= (1 << (x&7));
}
}
private void drawThickPoint(int x, int y, byte [][] layerBitMap, int col)
{
if (layerBitMap == null)
{
int baseIndex = y * sz.width + x;
opaqueData[baseIndex] = col;
if (x > 0)
opaqueData[baseIndex - 1] = col;
if (x < sz.width-1)
opaqueData[baseIndex + 1] = col;
if (y > 0)
opaqueData[baseIndex - sz.width] = col;
if (y < sz.height-1)
opaqueData[baseIndex + sz.width] = col;
} else
{
layerBitMap[y][x>>3] |= (1 << (x&7));
if (x > 0)
layerBitMap[y][(x-1)>>3] |= (1 << ((x-1)&7));
if (x < sz.width-1)
layerBitMap[y][(x+1)>>3] |= (1 << ((x+1)&7));
if (y > 0)
layerBitMap[y-1][x>>3] |= (1 << (x&7));
if (y < sz.height-1)
layerBitMap[y+1][x>>3] |= (1 << (x&7));
}
}
/**
* Method to convert a database coordinate to screen coordinates.
* @param dbX the X coordinate (in database units).
* @param dbY the Y coordinate (in database units).
* @param result the Point in which to store the screen coordinates.
*/
private void databaseToScreen(double dbX, double dbY, Point result) {
double scrX = originX + dbX*scale;
double scrY = originY - dbY*scale;
result.x = (int)(scrX >= 0 ? scrX + 0.5 : scrX - 0.5);
result.y = (int)(scrY >= 0 ? scrY + 0.5 : scrY - 0.5);
}
private void screenToDatabase(int x, int y, Point2D result) {
result.setLocation((x - originX)/scale, (originY - y)/scale);
}
/**
* Method to convert a database rectangle to screen coordinates.
* @param db the rectangle (in database units).
* @return the rectangle on the screen.
*/
private Rectangle databaseToScreen(Rectangle2D db)
{
Point llPt = tempPt1;
Point urPt = tempPt2;
databaseToScreen(db.getMinX(), db.getMinY(), llPt);
databaseToScreen(db.getMaxX(), db.getMaxY(), urPt);
int screenLX = llPt.x;
int screenHX = urPt.x;
int screenLY = llPt.y;
int screenHY = urPt.y;
if (screenHX < screenLX) { int swap = screenHX; screenHX = screenLX; screenLX = swap; }
if (screenHY < screenLY) { int swap = screenHY; screenHY = screenLY; screenLY = swap; }
return new Rectangle(screenLX, screenLY, screenHX-screenLX+1, screenHY-screenLY+1);
}
}