/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: LayerDrawing.java
*
* Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
*
* 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.ImmutableNodeInst;
import com.sun.electric.database.geometry.EGraphics;
import com.sun.electric.database.geometry.EPoint;
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.topology.NodeInst;
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.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.Artwork;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.tool.user.GraphicsPreferences;
import com.sun.electric.tool.user.User;
import com.sun.electric.tool.user.UserInterfaceMain;
import com.sun.electric.tool.user.ui.EditWindow;
import com.sun.electric.tool.user.ui.LayerVisibility;
import com.sun.electric.tool.user.ui.WindowFrame;
import com.sun.electric.tool.util.concurrent.utils.ElapseTimer;
import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.GenMath;
import com.sun.electric.util.math.Orientation;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
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.DataBufferInt;
import java.awt.image.VolatileImage;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
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>
*
*/
class LayerDrawing {
/** Text smaller than this will not be drawn. */
public static final int MINIMUMTEXTSIZE = 5;
/** Number of singleton cells to cache when redisplaying. */
public static final int SINGLETONSTOADD = 5;
/** Text size is limited by this. */
public static final int MAXIMUMTEXTSIZE = 200;
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 = false;
private static int tinyCells, tinyPrims, totalCells, renderedCells, totalPrims, tinyArcs, linedArcs, totalArcs;
private static int offscreensCreated, offscreenPixelsCreated, offscreensUsed, offscreenPixelsUsed, cellsRendered;
private static Set<ExpandedCellKey> offscreensUsedSet = new HashSet<ExpandedCellKey>();
private static int boxArrayCount, boxCount, boxDisplayCount, lineCount, polygonCount, crossCount, circleCount, discCount, arcCount;
private static final boolean DEBUG = false;
private static class ExpandedCellKey {
private CellId cellId;
private Orientation orient;
private ExpandedCellKey(CellId cellId, Orientation orient) {
this.cellId = cellId;
this.orient = orient;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ExpandedCellKey) {
ExpandedCellKey that = (ExpandedCellKey) obj;
return this.cellId == that.cellId && this.orient.equals(that.orient);
}
return false;
}
@Override
public int hashCode() {
return cellId.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 boolean tooLarge;
private LayerDrawing offscreen;
ExpandedCellInfo() {
singleton = true;
offscreen = null;
}
}
/** the size of the EditWindow */
private final Dimension sz;
/** the scale of the EditWindow */
private double scale;
/** the global text scale of the EditWindow */
private double globalTextScale;
/** the default font of the EditWindow */
private String defaultFont;
/** the VarContext of the EditWindow */
private VarContext varContext;
/** the X origin of the cell in display coordinates. */
private double originX;
/** the Y origin of the cell in display coordinates. */
private double originY;
/** the scale of the EditWindow */
private double scale_;
/** the window scale and pan factor */
private float factorX, factorY;
/** 0: color display, 1: color printing, 2: B&W printing */
private static final int nowPrinting = 0;
/** true if the last display was a full-instantiate */
private boolean lastFullInstantiate = false;
/** A set of subcells being in-place edited. */
private BitSet inPlaceSubcellPath;
/** The current cell being in-place edited. */
private Cell inPlaceCurrent;
/** true if text can be drawn (not too zoomed-out) */
private boolean canDrawText;
/** Threshold for relative text can be drawn */
private double canDrawRelativeText = Double.MAX_VALUE;
/** maximum size before an object is too small */
private static double maxObjectSize;
/** 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
/** size of the opaque layer of the window */
private final int total;
/** list of render text. */
private final ArrayList<RenderTextInfo> renderTextList = new ArrayList<RenderTextInfo>();
/** list of greek text. */
private final ArrayList<GreekTextInfo> greekTextList = new ArrayList<GreekTextInfo>();
/** list of cross text. */
private final ArrayList<CrossTextInfo> crossTextList = new ArrayList<CrossTextInfo>();
// the transparent bitmaps
/** the number of ints per row in offscreen maps */
private final int numIntsPerRow;
/** the map from layers to layer bitmaps */
private Map<Layer, TransparentRaster> layerRasters = new HashMap<Layer, TransparentRaster>();
/** Raster with unexpanded instances */
private TransparentRaster instanceRaster;
/** Raster with grid */
private TransparentRaster gridRaster;
/** temporary raster for patterned layers */
private PatternedTransparentRaster currentPatternedTransparentRaster = new PatternedTransparentRaster();
/** 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 EditWindow wnd;
/** the size of the top-level EditWindow */
private static Dimension topSz;
/** draw layers patterned (depends on scale). */
private boolean patternedDisplay;
/** Alpha blending with overcolor (depends on scale). */
private static boolean alphaBlendingOvercolor;
/** 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;
// Color things only at top offscreen
private GraphicsPreferences gp;
private AbstractDrawing.DrawingPreferences dp;
private Color textColor;
/** cache of port colors */
private HashMap<PrimitivePort, Color> portColorsCache;
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 globalTextScale; }
public String getDefaultFont() { return defaultFont; }
};
private static class DrawingData {
private final LayerDrawing offscreen;
private final int width;
private final int height;
private final int numIntsPerRow;
private final boolean patternedDisplay;
/** the map from layers to layer bitmaps */
private final Map<Layer, TransparentRaster> layerRasters;
private final TransparentRaster instanceRaster;
private final TransparentRaster gridRaster;
private final GreekTextInfo[] greekText;
private final RenderTextInfo[] renderText;
private final CrossTextInfo[] crossText;
DrawingData(LayerDrawing offscreen) {
this.offscreen = offscreen;
width = offscreen.sz.width;
height = offscreen.sz.height;
numIntsPerRow = offscreen.numIntsPerRow;
patternedDisplay = offscreen.patternedDisplay;
layerRasters = new HashMap<Layer, TransparentRaster>(offscreen.layerRasters);
instanceRaster = offscreen.instanceRaster;
gridRaster = offscreen.gridRaster;
greekText = offscreen.greekTextList.toArray(new GreekTextInfo[offscreen.greekTextList.size()]);
crossText = offscreen.crossTextList.toArray(new CrossTextInfo[offscreen.crossTextList.size()]);
renderText = offscreen.renderTextList.toArray(new RenderTextInfo[offscreen.renderTextList.size()]);
}
}
static class Drawing extends AbstractDrawing {
private static final int SMALL_IMG_HEIGHT = 2;
/** the offscreen opaque image of the window */
private VolatileImage vImg;
private BufferedImage smallImg;
private int[] smallOpaqueData;
/** alpha blender of layer maps */
private final AlphaBlender alphaBlender = new AlphaBlender();
// The following fields are produced by "render" method in Job thread.
private volatile boolean needComposite;
private volatile DrawingData drawingData;
/** whether any layers are highlighted/dimmed */
boolean highlightingLayers;
private final double patternedScaleLimit = User.getPatternedScaleLimit();
private final double alphaBlendingOvercolorLimit = User.getAlphaBlendingOvercolorLimit();
Drawing(EditWindow wnd) {
super(wnd);
}
/**
* This method is called from AWT thread.
*/
@Override
public boolean paintComponent(Graphics2D g, LayerVisibility lv, Dimension sz) {
assert SwingUtilities.isEventDispatchThread();
GraphicsPreferences gp = UserInterfaceMain.getGraphicsPreferences();
assert sz.equals(wnd.getSize());
DrawingData drawingData = this.drawingData;
if (drawingData == null || !drawingData.offscreen.getSize().equals(sz)) {
return false;
}
if (vImg == null || vImg.getWidth() != sz.width || vImg.getHeight() != sz.height) {
if (vImg != null) {
vImg.flush();
}
vImg = wnd.createVolatileImage(sz.width, sz.height);
}
// smallImg = (BufferedImage)wnd.createImage(sz.width, 1);
if (smallImg == null || smallImg.getWidth() != sz.width) {
smallImg = new BufferedImage(sz.width, SMALL_IMG_HEIGHT, BufferedImage.TYPE_INT_RGB);
DataBufferInt smallDbi = (DataBufferInt) smallImg.getRaster().getDataBuffer();
smallOpaqueData = smallDbi.getData();
}
// show the image
// copying from the image (here, gScreen is the Graphics
// object for the onscreen window)
do {
int returnCode = vImg.validate(wnd.getGraphicsConfiguration());
if (returnCode == VolatileImage.IMAGE_RESTORED) {
// Contents need to be restored
renderOffscreen(gp, lv, drawingData); // restore contents
} else if (returnCode == VolatileImage.IMAGE_INCOMPATIBLE) {
// old vImg doesn't work with new GraphicsConfig; re-create it
vImg.flush();
vImg = wnd.createVolatileImage(sz.width, sz.height);
renderOffscreen(gp, lv, drawingData);
} else if (needComposite) {
renderOffscreen(gp, lv, drawingData);
}
g.drawImage(vImg, 0, 0, wnd);
} while (vImg.contentsLost());
return true;
}
/**
* This method is called from AWT thread.
*/
private void renderOffscreen(GraphicsPreferences gp, LayerVisibility lv, DrawingData dd) {
needComposite = false;
do {
if (vImg.validate(wnd.getGraphicsConfiguration()) == VolatileImage.IMAGE_INCOMPATIBLE) {
// old vImg doesn't work with new GraphicsConfig; re-create it
vImg = wnd.createVolatileImage(dd.width, dd.height);
}
long startTime = System.currentTimeMillis();
Graphics2D g = vImg.createGraphics();
// 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;
}
}
if (User.isLegacyComposite()) {
legacyLayerComposite(g, gp, lv, dd);
} else {
layerComposite(g, gp, lv, dd);
}
// if (alphaBlendingComposite) {
// boolean TRY_OVERBLEND = false;
// if (TRY_OVERBLEND) {
// layerCompositeSlow(g);
// } else {
// layerComposite(g);
// }
// } else {
// layerCompositeCompatable(g);
// }
long compositeTime = System.currentTimeMillis();
for (GreekTextInfo greekInfo : dd.greekText) {
greekInfo.draw(g, lv);
}
for (CrossTextInfo crossInfo : dd.crossText) {
crossInfo.draw(g, lv);
}
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
for (RenderTextInfo textInfo : dd.renderText) {
textInfo.draw(g, lv);
}
g.dispose();
if (TAKE_STATS) {
long endTime = System.currentTimeMillis();
System.out.println((alphaBlendingOvercolor ? "alphaBlendingOvercolor took " : "alphaBlending took ")
+ (compositeTime - startTime) + " msec, textRendering " + dd.renderText.length + "+" + dd.greekText.length + "+" + dd.crossText.length + " took " + (endTime - compositeTime) + " msec");
}
} while (vImg.contentsLost());
}
@Override
public void opacityChanged() {
assert SwingUtilities.isEventDispatchThread();
needComposite = true;
}
/**
* Returns true if full repaint is necessary
* @return
*/
public boolean visibilityChanged() {
assert SwingUtilities.isEventDispatchThread();
needComposite = true;
return true;
}
@Override
public boolean hasOpacity() {
return !User.isLegacyComposite();
}
private void layerComposite(Graphics2D g, GraphicsPreferences gp, LayerVisibility lv, DrawingData dd) {
Map<Layer, int[]> layerBits = new HashMap<Layer, int[]>();
for (Map.Entry<Layer, TransparentRaster> e : dd.layerRasters.entrySet()) {
layerBits.put(e.getKey(), e.getValue().layerBitMap);
}
List<AbstractDrawing.LayerColor> blendingOrder = getBlendingOrder(layerBits.keySet(), gp, lv, dd.patternedDisplay, alphaBlendingOvercolor);
if (TAKE_STATS) {
System.out.print("BlendingOrder:");
for (AbstractDrawing.LayerColor lc : blendingOrder) {
int alpha = (int) ((1 - lc.inverseAlpha) * 100 + 0.5);
System.out.print(" " + lc.layer.getName() + ":" + alpha);
}
System.out.println();
}
ArrayList<AbstractDrawing.LayerColor> colors = new ArrayList<AbstractDrawing.LayerColor>();
ArrayList<int[]> bits = new ArrayList<int[]>();
for (AbstractDrawing.LayerColor layerColor : blendingOrder) {
int[] b = layerBits.get(layerColor.layer);
if (b == null) {
continue;
}
colors.add(layerColor);
bits.add(b);
}
if (dd.instanceRaster != null) {
colors.add(new AbstractDrawing.LayerColor(gp.getColor(User.ColorPrefType.INSTANCE)));
bits.add(dd.instanceRaster.layerBitMap);
}
if (dd.gridRaster != null) {
colors.add(new AbstractDrawing.LayerColor(gp.getColor(User.ColorPrefType.GRID)));
bits.add(dd.gridRaster.layerBitMap);
}
alphaBlender.init(gp.getColor(User.ColorPrefType.BACKGROUND), colors, bits);
int width = dd.width;
int height = dd.height;
int numIntsPerRow = dd.numIntsPerRow;
int baseByteIndex = 0;
int y = 0;
while (y < height) {
int h = Math.min(SMALL_IMG_HEIGHT, height - y);
int baseIndex = 0;
for (int k = 0; k < h; k++) {
alphaBlender.composeLine(baseByteIndex, 0, width - 1, smallOpaqueData, baseIndex);
baseByteIndex += numIntsPerRow;
baseIndex += width;
}
g.drawImage(smallImg, 0, y, null);
y += h;
}
}
/**
* 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.
*/
private void legacyLayerComposite(Graphics2D g, GraphicsPreferences gp, LayerVisibility lv, DrawingData dd) {
getBlendingOrder(dd.layerRasters.keySet(), gp, lv, false, false);
Technology curTech = Technology.getCurrent();
if (curTech == null) {
for (Layer layer : dd.layerRasters.keySet()) {
int transparentDepth = gp.getGraphics(layer).getTransparentLayer();
if (transparentDepth != 0 && layer.getTechnology() != null) {
curTech = layer.getTechnology();
}
}
}
if (curTech == null) {
curTech = Generic.tech();
}
// 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;
}
int numTransparent = 0, numOpaque = 0;
for (Layer layer : dd.layerRasters.keySet()) {
if (!lv.isVisible(layer)) {
continue;
}
if (gp.getGraphics(layer).getTransparentLayer() == 0) {
numOpaque++;
} else {
numTransparent++;
}
}
TransparentRaster[] transparentRasters = new TransparentRaster[numTransparent];
int[] transparentMasks = new int[numTransparent];
TransparentRaster[] opaqueRasters = new TransparentRaster[numOpaque];
int[] opaqueCols = new int[numOpaque];
numTransparent = numOpaque = 0;
for (Map.Entry<Layer, TransparentRaster> e : dd.layerRasters.entrySet()) {
Layer layer = e.getKey();
if (!lv.isVisible(layer)) {
continue;
}
TransparentRaster raster = e.getValue();
int transparentNum = gp.getGraphics(layer).getTransparentLayer();
if (transparentNum != 0) {
transparentMasks[numTransparent] = (1 << (transparentNum - 1)) & (colorMap.length - 1);
transparentRasters[numTransparent++] = raster;
} else {
opaqueCols[numOpaque] = getTheColor(gp.getGraphics(layer), !lv.isHighlighted(layer));
opaqueRasters[numOpaque++] = raster;
}
}
// determine range
int numIntsPerRow = dd.numIntsPerRow;
int backgroundColor = gp.getColor(User.ColorPrefType.BACKGROUND).getRGB() & GraphicsPreferences.RGB_MASK;
// int lx = 0;
int hx = dd.width - 1;
int ly = 0;
int hy = dd.height - 1;
for (int y = ly; y <= hy; y++) {
int baseByteIndex = y * numIntsPerRow;
// int baseIndex = y * dd.width;
for (int x = 0; x <= hx; x++) {
int entry = baseByteIndex + (x >> 5);
int maskBit = 1 << (x & 31);
int opaqueIndex = -1;
for (int i = 0; i < opaqueRasters.length; i++) {
if ((opaqueRasters[i].layerBitMap[entry] & maskBit) != 0) {
opaqueIndex = i;
}
}
int pixelValue;
if (opaqueIndex >= 0) {
pixelValue = opaqueCols[opaqueIndex];
} else {
int bits = 0;
for (int i = 0; i < transparentRasters.length; i++) {
if ((transparentRasters[i].layerBitMap[entry] & maskBit) != 0) {
bits |= transparentMasks[i];
}
}
pixelValue = bits != 0 ? colorMap[bits].getRGB() & 0xFFFFFF : backgroundColor;
}
smallOpaqueData[x] = pixelValue;
}
g.drawImage(smallImg, 0, y, null);
}
}
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;
}
/**
* Returns alpha blending order for this EditWindow.
* Alpha blending order specifies pixel color by such a way:
* Color col = backgroudColor;
* for (LayerColor layerColor: blendingOrder) {
* if (This pixel covers a piece of layer layerColor.layer) {
* alpha = layerColor.color.getAlpha();
* col = layerColor.color.getRGB()*alpha + col*(1 - alpha)
* }
* }
* return col;
* @param layersAvailable layers available in this EditWindow
* @return alpha blending order.
*/
private List<AbstractDrawing.LayerColor> getBlendingOrder(Set<Layer> layersAvailable, GraphicsPreferences gp, LayerVisibility lv, boolean patternedDisplay, boolean alphaBlendingOvercolor) {
List<AbstractDrawing.LayerColor> layerColors = new ArrayList<AbstractDrawing.LayerColor>();
List<Layer> sortedLayers = new ArrayList<Layer>(layersAvailable);
Collections.sort(sortedLayers, Technology.LAYERS_BY_HEIGHT_LIFT_CONTACTS);
float[] backgroundComps = gp.getColor(User.ColorPrefType.BACKGROUND).getRGBColorComponents(null);
float bRed = backgroundComps[0];
float bGreen = backgroundComps[1];
float bBlue = backgroundComps[2];
for (Layer layer : sortedLayers) {
if (!lv.isVisible(layer)) {
continue;
}
if (layer == Generic.tech().glyphLay && !patternedDisplay) {
continue;
}
Color color = gp.getGraphics(layer).getColor();
float[] compArray = color.getRGBComponents(null);
float red = compArray[0];
float green = compArray[1];
float blue = compArray[2];
float opacity = lv.getOpacity(layer);
if (opacity <= 0) {
continue;
}
float inverseAlpha = 1 - opacity;
if (alphaBlendingOvercolor) {
red -= bRed * inverseAlpha;
green -= bGreen * inverseAlpha;
blue -= bBlue * inverseAlpha;
} else {
red *= opacity;
green *= opacity;
blue *= opacity;
}
layerColors.add(new AbstractDrawing.LayerColor(layer, red, green, blue, inverseAlpha));
}
// final LayerTab layerTab = getWindowFrame().getLayersTab();
// final boolean showOpacity = !User.isLegacyComposite();
// if (layerTab != null)
// SwingUtilities.invokeLater(new Runnable() { public void run() { layerTab.setDisplayAlgorithm(showOpacity); }});
return layerColors;
}
/**
* This method is called from Job thread.
*/
@Override
public void render(Dimension sz, WindowFrame.DisplayAttributes da, GraphicsPreferences gp,
DrawingPreferences dp, boolean fullInstantiate, Rectangle2D bounds) {
LayerDrawing offscreen = null;
if (drawingData != null && drawingData.offscreen.getSize().equals(sz)) {
offscreen = drawingData.offscreen;
}
if (offscreen == null) {
offscreen = new LayerDrawing(sz);
}
this.da = da;
offscreen.gp = gp;
offscreen.dp = dp;
offscreen.textColor = gp.getColor(User.ColorPrefType.TEXT);
offscreen.portColorsCache = new HashMap<PrimitivePort, Color>();
// updateScaleAndOffset();
offscreen.drawImage(this, fullInstantiate, bounds, patternedScaleLimit, alphaBlendingOvercolorLimit);
needComposite = true;
drawingData = new DrawingData(offscreen);
}
private static boolean joglChecked = false;
private static Class<?> layerDrawerClass;
private static Method joglShowLayerMethod;
/**
* Method to tell whether JOGL redisplay is available.
* JOGL is Java extension.
* This method dynamically figures out whether the JOGL module is present by using reflection.
* @return true if the JOGL redisplay is available.
*/
public static boolean hasJogl() {
if (!joglChecked) {
joglChecked = true;
// find the LayerDrawer class
try {
layerDrawerClass = Class.forName("com.sun.electric.plugins.jogl.LayerDrawer");
joglShowLayerMethod = layerDrawerClass.getMethod("showLayer", new Class[]{Dimension.class, (new int[0]).getClass(), Double.TYPE, Double.TYPE, Double.TYPE});
} catch (Exception e) {
}
}
return joglShowLayerMethod != null;
}
@Override
public void testJogl() {
if (hasJogl()) {
try {
int numBoxes = 1000000;
int[] boxes = new int[numBoxes * 4];
for (int i = 0; i < numBoxes; i++) {
int x = (i * 5) % 501 - 100;
int y = (i * 7) % 500 - 200;
boxes[i * 4 + 0] = x;
boxes[i * 4 + 1] = y;
boxes[i * 4 + 2] = x + 10;
boxes[i * 4 + 3] = y + 10;
}
// joglShowLayerMethod.invoke(layerDrawerClass, new Object[] {offscreen___.sz, boxes, 1.0, 0.0, 0.0});
} catch (Exception e) {
System.out.println("Unable to run the LayerDrawer input module (" + e.getClass() + ")");
e.printStackTrace(System.out);
}
return;
}
// testJogl_();
}
// private void testJogl_() {
// JFrame frame = new JFrame("Jogl");
// GLCapabilities capabilities = new GLCapabilities();
// capabilities.setDoubleBuffered(false);
// capabilities.setHardwareAccelerated(false);
// System.out.println("Capabilities: " + capabilities);
// GLCanvas canvas = new GLCanvas(capabilities);
//
// canvas.addGLEventListener(new JoglEventListener());
// frame.add(canvas);
// frame.setSize(offscreen.getSize());
//
// frame.setVisible(true);
// }
//
// private static void showInt(GL gl, String s, int i) {
// IntBuffer intBuffer = IntBuffer.allocate(100);
// gl.glGetIntegerv(GL.GL_MULTISAMPLE, intBuffer);
// System.out.println(s + ": " + intBuffer.get(0));
// }
//
// private class JoglEventListener implements GLEventListener {
//
// public void init(GLAutoDrawable drawable) {
// GL gl = drawable.getGL();
// gl = new DebugGL(gl);
// drawable.setGL(gl);
//
//// gl.glDisable(GL.GL_MULTISAMPLE);
//
// System.out.println("GL_VENDOR: " + gl.glGetString(GL.GL_VENDOR));
// System.out.println("GL_RENDERER: " + gl.glGetString(GL.GL_RENDERER));
// System.out.println("GL_VERSION: " + gl.glGetString(GL.GL_VERSION));
// showInt(gl, "GL_MULTISAMPLE", GL.GL_MULTISAMPLE);
// showInt(gl, "GL_SAMPLE_BUFFERS", GL.GL_SAMPLE_BUFFERS);
// showInt(gl, "GL_SAMPLES", GL.GL_SAMPLES);
//
//// gl.glPixelStorei(GL.GL_UNPACK_SWAP_BYTES, 1);
//// gl.glPixelStorei(gl.GL_UNPACK_LSB_FIRST, 1);
//// gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1);
//
//// gl.glPixelTransferi(gl.GL_MAP_COLOR, 1);
// gl.glEnable(gl.GL_BLEND);
// gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA);
// }
//
// public void display(GLAutoDrawable drawable) {
// List<EditWindow.LayerColor> blendingOrder = offscreen.wnd.getBlendingOrder(offscreen.layerRasters.keySet(), false);
//
// GL gl = drawable.getGL();
//
// BufferedImage bImg = vImg.getSnapshot();
// DataBufferInt dbi = (DataBufferInt)bImg.getRaster().getDataBuffer();
// int[] opaqueData = dbi.getData();
// int w = bImg.getWidth(), h = bImg.getHeight();
// IntBuffer intBuffer = IntBuffer.allocate(w*h);
// ByteBuffer redBuffer = ByteBuffer.allocate(w*h);
// ByteBuffer greenBuffer = ByteBuffer.allocate(w*h);
// ByteBuffer blueBuffer = ByteBuffer.allocate(w*h);
// assert intBuffer.order() == ByteOrder.nativeOrder();
// for (int y = 0; y < h; y++) {
// for (int x = 0; x < w; x++) {
// int v = opaqueData[(h - y - 1)*w + x];
// intBuffer.put(v | 0xFF000000);
// redBuffer.put((byte)(v >> 16));
// greenBuffer.put((byte)(v >> 8));
// blueBuffer.put((byte)v);
// }
// }
// intBuffer.rewind();
// redBuffer.rewind();
// greenBuffer.rewind();
// blueBuffer.rewind();
//// IntBuffer intBuffer = IntBuffer.wrap(offscreen.opaqueData, 0, w*h);
//
// long startTime = System.currentTimeMillis();
//// float[] bg = (new Color(User.getColor(User.ColorPrefType.BACKGROUND))).getRGBComponents(null);
//// gl.glClearColor(bg[0], bg[1], bg[2], 1.0f);
// gl.glClearColor(1f, 1f, 1f, 1f);
// gl.glClear(GL.GL_COLOR_BUFFER_BIT);
//
// gl.glMatrixMode(GL.GL_PROJECTION);
// gl.glLoadIdentity();
//// gl.glFrustum(-1.0f, 1.0f, -h, h, 5.0f, 60.0f);
//
// gl.glMatrixMode(GL.GL_MODELVIEW);
// gl.glLoadIdentity();
//
//// ByteBuffer buf = ByteBuffer.allocate((w + 7)/8*h);
//// for (int i = 0; i < 100; i++)
//// buf.put(i*3, (byte)0x55);
//// gl.glPixelMapfv(gl.GL_PIXEL_MAP_I_TO_R, 2, new float[256], 0);
//// float[] green = new float[256];
//// Arrays.fill(green, 1.0f);
//// green[0] = 0;
//// gl.glPixelMapfv(gl.GL_PIXEL_MAP_I_TO_G, 2, green, 0);
//// gl.glPixelMapfv(gl.GL_PIXEL_MAP_I_TO_B, 2, new float[256], 0);
//// gl.glPixelMapfv(gl.GL_PIXEL_MAP_I_TO_A, 2, new float[256], 0);
//// gl.glDrawPixels(10, 10, gl.GL_COLOR_INDEX, gl.GL_BITMAP, buf);
//// gl.glBitmap(10, 10, 0f, 0f, 0f, 0f, buf);
//
//// byte[] bytes1 = new byte[(w+7)/8*h*2];
//// Arrays.fill(bytes1, (byte)-2);
//// bytes1[0] = 0;
//// bytes1[299] = 0;
// if (true) {
//// gl.glPixelMapfv(gl.GL_PIXEL_MAP_I_TO_R, 256, new float[256], 0);
//// float[] green = new float[256];
//// for (int i = 0; i < green.length; i++)
//// green[i] = i/255f;
//// gl.glPixelMapfv(gl.GL_PIXEL_MAP_I_TO_G, 256, green, 0);
//// gl.glPixelMapfv(gl.GL_PIXEL_MAP_I_TO_B, 256, new float[256], 0);
//// float[] alpha = new float[256];
//// Arrays.fill(alpha, 1f);
//// gl.glPixelMapfv(gl.GL_PIXEL_MAP_I_TO_A, 256, alpha, 0);
//
// for (int i = 0; i < 1000; i++)
// gl.glDrawPixels(w, h, GL.GL_BGRA, GL.GL_UNSIGNED_INT_8_8_8_8_REV, intBuffer);
//// gl.glDrawPixels(w, h, GL.GL_GREEN, GL.GL_UNSIGNED_BYTE, greenBuffer);
//// gl.glDrawPixels(w, h, GL.GL_BLUE, GL.GL_UNSIGNED_BYTE, blueBuffer);
//// gl.glDrawPixels(w, h, GL.GL_RED, GL.GL_UNSIGNED_BYTE, redBuffer);
//// ByteBuffer byteBuffer = ByteBuffer.allocate(w*h);
//// gl.glReadPixels(0, 0, w, h, GL.GL_BLUE, GL.GL_UNSIGNED_BYTE, byteBuffer);
//// for (int y = 0; y < h; y++) {
//// for (int x = 0; x < w; x++) {
//// int v = opaqueData[(h - y - 1)*w + x];
//// byte b = byteBuffer.get();
//// if (((v >> 16) & 0xFF) != (b & 0xFF))
//// System.out.println("Mismatch at x=" + x + " y=" + y + " v=" + Integer.toHexString(v) + " b=" + Integer.toHexString(b));
//// }
//// }
// } else {
// for (EditWindow.LayerColor layerColor: blendingOrder) {
// float[] c = layerColor.color.getComponents(null);
//// gl.glColor4f(c[0], c[1], c[2], c[3]);
// gl.glPixelMapfv(gl.GL_PIXEL_MAP_I_TO_R, 2, new float[] {0f, c[0]}, 0);
// gl.glPixelMapfv(gl.GL_PIXEL_MAP_I_TO_G, 2, new float[] {0f, c[1]}, 0);
// gl.glPixelMapfv(gl.GL_PIXEL_MAP_I_TO_B, 2, new float[] {0f, c[2]}, 0);
// gl.glPixelMapfv(gl.GL_PIXEL_MAP_I_TO_A, 2, new float[] {0f, c[3]}, 0);
//// byte[] b = offscreen.layerRasters.get(layerColor.layer).layerBitMap;
//// buf.rewind();
//// buf.put(b);
//// buf.rewind();
//// ByteBuffer buf = offscreen.layerBuffers.get(layerColor.layer);
//// gl.glDrawPixels(w, h, gl.GL_COLOR_INDEX, gl.GL_BITMAP, buf);
//// gl.glBitmap(w, h, 0f, 0f, 0f, 0f, buf);
// }
// }
// long endTime = System.currentTimeMillis();
// System.out.println("jogl display took " + (endTime - startTime) + " msec");
//
// }
//
// public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {}
// public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {}
// }
}
static void drawTechPalette(Graphics2D g, GraphicsPreferences gp, int imgX, int imgY, Rectangle entrySize, double scale, VectorCache.VectorBase[] shapes) {
BufferedImage smallImg = new BufferedImage(entrySize.width, Drawing.SMALL_IMG_HEIGHT, BufferedImage.TYPE_INT_RGB);
DataBufferInt smallDbi = (DataBufferInt) smallImg.getRaster().getDataBuffer();
int[] smallOpaqueData = smallDbi.getData();
LayerDrawing offscreen = new LayerDrawing(new Dimension(entrySize.width, entrySize.height));
offscreen.gp = gp;
// set colors to use
offscreen.textColor = gp.getColor(User.ColorPrefType.TEXT);
// initialize the cache of expanded cell displays
// varContext = wnd.getVarContext();
offscreen.initOrigin(scale, EPoint.ORIGIN);
offscreen.renderedWindow = true;
offscreen.patternedDisplay = true;
offscreen.canDrawText = true;
offscreen.canDrawRelativeText = 0;
// remember the true window size (since recursive calls may cache individual cells that are smaller)
// topSz = sz;
// initialize rendering into the offscreen image
offscreen.clipLX = 0;
offscreen.clipHX = offscreen.sz.width - 1;
offscreen.clipLY = 0;
offscreen.clipHY = offscreen.sz.height - 1;
offscreen.clearImage(null);
// now render it all
offscreen.renderTextList.clear();
offscreen.greekTextList.clear();
offscreen.crossTextList.clear();
ArrayList<VectorCache.VectorBase> shapeList = new ArrayList<VectorCache.VectorBase>();
for (VectorCache.VectorBase shape : shapes) {
shapeList.add(shape);
}
offscreen.drawList(0, 0, shapeList);
AlphaBlender alphaBlender = new AlphaBlender();
Map<Layer, int[]> layerBits = new HashMap<Layer, int[]>();
for (Map.Entry<Layer, TransparentRaster> e : offscreen.layerRasters.entrySet()) {
layerBits.put(e.getKey(), e.getValue().layerBitMap);
}
List<AbstractDrawing.LayerColor> blendingOrder = getBlendingOrderForTechPalette(gp, layerBits.keySet());
if (TAKE_STATS) {
System.out.print("BlendingOrder:");
for (AbstractDrawing.LayerColor lc : blendingOrder) {
int alpha = (int) ((1 - lc.inverseAlpha) * 100 + 0.5);
System.out.print(" " + lc.layer.getName() + ":" + alpha);
}
System.out.println();
}
ArrayList<AbstractDrawing.LayerColor> colors = new ArrayList<AbstractDrawing.LayerColor>();
ArrayList<int[]> bits = new ArrayList<int[]>();
for (AbstractDrawing.LayerColor layerColor : blendingOrder) {
int[] b = layerBits.get(layerColor.layer);
if (b == null) {
continue;
}
colors.add(layerColor);
bits.add(b);
}
alphaBlender.init(gp.getColor(User.ColorPrefType.BACKGROUND), colors, bits);
int width = offscreen.sz.width;
int height = offscreen.sz.height;
int numIntsPerRow = offscreen.numIntsPerRow;
int baseByteIndex = 0;
int y = 0;
while (y < height) {
int h = Math.min(Drawing.SMALL_IMG_HEIGHT, height - y);
int baseIndex = 0;
for (int k = 0; k < h; k++) {
alphaBlender.composeLine(baseByteIndex, 0, width - 1, smallOpaqueData, baseIndex);
baseByteIndex += numIntsPerRow;
baseIndex += width;
}
g.drawImage(smallImg, imgX, imgY + y, null);
y += h;
}
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
for (RenderTextInfo textInfo : offscreen.renderTextList) {
textInfo.offX += imgX;
textInfo.offY += imgY;
textInfo.draw(g, null);
}
// needComposite = true;
// layerRasters = new HashMap<Layer,TransparentRaster>(offscreen.layerRasters);
// greekText = offscreen.greekTextList.toArray(new GreekTextInfo[offscreen.greekTextList.size()]);
// crossText = offscreen.crossTextList.toArray(new CrossTextInfo[offscreen.crossTextList.size()]);
// renderText = offscreen.renderTextList.toArray(new RenderTextInfo[offscreen.renderTextList.size()]);
}
private static List<AbstractDrawing.LayerColor> getBlendingOrderForTechPalette(GraphicsPreferences gp, Set<Layer> layersAvailable) {
boolean alphaBlendingOvercolor = true;
ArrayList<AbstractDrawing.LayerColor> layerColors = new ArrayList<AbstractDrawing.LayerColor>();
ArrayList<Layer> sortedLayers = new ArrayList<Layer>(layersAvailable);
Collections.sort(sortedLayers, Technology.LAYERS_BY_HEIGHT_LIFT_CONTACTS);
float[] backgroundComps = gp.getColor(User.ColorPrefType.BACKGROUND).getRGBColorComponents(null);
float bRed = backgroundComps[0];
float bGreen = backgroundComps[1];
float bBlue = backgroundComps[2];
for (Layer layer : sortedLayers) {
Color color = gp.getGraphics(layer).getColor();
float[] compArray = color.getRGBComponents(null);
float red = compArray[0];
float green = compArray[1];
float blue = compArray[2];
float opacity = 0.7f;
float inverseAlpha = 1 - opacity;
if (alphaBlendingOvercolor) {
red -= bRed * inverseAlpha;
green -= bGreen * inverseAlpha;
blue -= bBlue * inverseAlpha;
} else {
red *= opacity;
green *= opacity;
blue *= opacity;
}
layerColors.add(new AbstractDrawing.LayerColor(layer, red, green, blue, inverseAlpha));
}
return layerColors;
}
// ************************************* TOP LEVEL *************************************
/**
* Constructor creates an offscreen PixelDrawing object.
* @param sz the size of an offscreen PixelDrawinf object.
*/
public LayerDrawing(Dimension sz) {
this.sz = new Dimension(sz);
// width = sz.width;
clipLX = 0;
clipHX = sz.width - 1;
clipLY = 0;
clipHY = sz.height - 1;
// allocate pointer to the opaque image
total = sz.height * sz.width;
numIntsPerRow = (sz.width + Integer.SIZE - 1) / Integer.SIZE;
renderedWindow = true;
}
public LayerDrawing(double scale, int lX, int hX, int lY, int hY) {
this.scale = scale;
scale_ = (float) (scale / DBMath.GRID);
this.originX = -lX;
this.originY = hY;
factorX = (float) (-originX / scale_);
factorY = (float) (originY / scale_);
this.sz = new Dimension(hX - lX + 1, hY - lY + 1);
// width = sz.width;
clipLX = 0;
clipHX = sz.width - 1;
clipLY = 0;
clipHY = sz.height - 1;
// allocate pointer to the opaque image
total = sz.height * sz.width;
numIntsPerRow = (sz.width + Integer.SIZE - 1) / Integer.SIZE;
}
void initOrigin(double scale, Point2D offset) {
this.scale = scale;
scale_ = (float) (scale / DBMath.GRID);
this.originX = sz.width / 2 - offset.getX() * scale;
this.originY = sz.height / 2 + offset.getY() * scale;
factorX = (float) (offset.getX() * DBMath.GRID - sz.width / 2 / scale_);
factorY = (float) (offset.getY() * DBMath.GRID + sz.height / 2 / scale_);
}
/**
* 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 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>();
}
/**
* 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,
double patternedScaleLimit, double alphaBlendingOvercolorLimit) {
long clearTime = 0, countTime = 0;
ElapseTimer timer = ElapseTimer.createInstance();
long initialUsed = 0;
if (TAKE_STATS) {
// Runtime.getRuntime().gc();
timer.start();
initialUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
tinyCells = tinyPrims = totalCells = renderedCells = totalPrims = tinyArcs = linedArcs = totalArcs = 0;
offscreensCreated = offscreenPixelsCreated = offscreensUsed = offscreenPixelsUsed = cellsRendered = 0;
offscreensUsedSet.clear();
boxArrayCount = boxCount = boxDisplayCount = lineCount = polygonCount = crossCount = circleCount = discCount = arcCount = 0;
}
if (fullInstantiate != lastFullInstantiate) {
clearSubCellCache();
lastFullInstantiate = fullInstantiate;
}
EditWindow wnd = drawing.wnd;
Cell cell = wnd.getInPlaceEditTopCell();
List<NodeInst> inPlaceNodePath = wnd.getInPlaceEditNodePath();
if (inPlaceNodePath.isEmpty()) {
inPlaceSubcellPath = null;
} else {
inPlaceSubcellPath = new BitSet();
for (NodeInst ni : inPlaceNodePath) {
Cell subCell = (Cell) ni.getProto();
inPlaceSubcellPath.set(subCell.getId().cellIndex);
}
}
inPlaceCurrent = wnd.getCell();
// initialize the cache of expanded cell displays
if (expandedScale != drawing.da.scale) {
clearSubCellCache();
expandedScale = drawing.da.scale;
}
varContext = wnd.getVarContext();
globalTextScale = wnd.getGlobalTextScale();
defaultFont = wnd.getDefaultFont();
initOrigin(expandedScale, new Point2D.Double(drawing.da.offX, drawing.da.offY));
patternedDisplay = expandedScale > patternedScaleLimit;
alphaBlendingOvercolor = expandedScale > alphaBlendingOvercolorLimit;
canDrawText = expandedScale > 1;
canDrawRelativeText = canDrawText ? 0 : MINIMUMTEXTSIZE;
maxObjectSize = 2 / expandedScale;
// remember the true window size (since recursive calls may cache individual cells that are smaller)
topSz = sz;
// 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();
// reset cached cell counts
numberToReconcile = SINGLETONSTOADD;
for (ExpandedCellInfo count : expandedCells.values()) {
count.instanceCount = 0;
}
if (TAKE_STATS) {
clearTime = System.currentTimeMillis();
}
// determine which cells should be cached (must have at least 2 instances)
countCell(cell, drawLimitBounds, fullInstantiate, Orientation.IDENT, DBMath.MATID);
if (TAKE_STATS) {
countTime = System.currentTimeMillis();
}
// now render it all
renderTextList.clear();
greekTextList.clear();
crossTextList.clear();
drawCell(cell, drawLimitBounds, fullInstantiate, Orientation.IDENT, 0, 0, true, wnd.getVarContext());
// if a grid is requested, overlay it
if (cell != null && wnd.isGrid()) {
drawGrid(wnd, drawing.da);
}
if (TAKE_STATS) {
timer.end();
long curUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long memConsumed = curUsed - initialUsed;
System.out.println("Took " + timer.toString()
+ "(" + (clearTime - timer.getStart()) + "+" + (countTime - clearTime) + "+" + (timer.getEnd() - countTime) + ")"
+ ", rendered " + cellsRendered + " cells, used " + offscreensUsed + " (" + offscreenPixelsUsed + " pixels) " + offscreensUsedSet.size() + "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"
+ " Texts " + renderTextList.size() + " Greeks " + greekTextList.size());
if (true) {
System.out.print(" " + (boxCount + polygonCount + discCount + lineCount + crossCount + circleCount + arcCount) + " rendered: ");
if (boxArrayCount != 0) {
System.out.print(boxCount + "(" + boxArrayCount + "," + boxDisplayCount + ") boxes ");
}
if (polygonCount != 0) {
System.out.print(polygonCount + " polygons ");
}
if (discCount != 0) {
System.out.print(discCount + " discs ");
}
if (lineCount != 0) {
System.out.print(lineCount + " lines ");
}
if (crossCount != 0) {
System.out.print(crossCount + " crosses ");
}
if (circleCount != 0) {
System.out.print(circleCount + " circles ");
}
if (arcCount != 0) {
System.out.print(arcCount + " circleArcs ");
}
System.out.println();
}
}
}
// ************************************* 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) {
// erase the patterned opaque layer bitmaps
if (bounds == null) {
for (TransparentRaster raster : layerRasters.values()) {
raster.eraseAll();
}
if (instanceRaster != null) {
instanceRaster.eraseAll();
}
if (gridRaster != null) {
gridRaster.eraseAll();
}
} else {
for (TransparentRaster raster : layerRasters.values()) {
raster.eraseBox(clipLX, clipHX, clipLY, clipHY);
}
if (instanceRaster != null) {
instanceRaster.eraseBox(clipLX, clipHX, clipLY, clipHY);
}
if (gridRaster != null) {
gridRaster.eraseBox(clipLX, clipHX, clipLY, clipHY);
}
}
}
/**
* 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 * dp.gridXBoldFrequency;
double boldSpacingY = spacingY * 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();
ERaster raster = getGridRaster();
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;
}
// draw box in opaque area
raster.fillBox(boxLX, boxHX, boxLY, boxHY);
// for(int yg=boxLY; yg<=boxHY; yg++) {
// int baseIndex = yg * sz.width;
// for(int xg=boxLX; xg<=boxHX; xg++)
// opaqueData[baseIndex + xg] = col;
// baseIndex += sz.width;
// }
if (x > 1) {
raster.fillPoint(x - 2, y);
}
if (x < sz.width - 2) {
raster.fillPoint(x + 2, y);
}
if (y > 1) {
raster.fillPoint(x, y - 2);
}
if (y < sz.height - 2) {
raster.fillPoint(x, y + 2);
}
// 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)) {
raster.fillPoint(x, y);
if (x > 0) {
raster.fillPoint(x - 1, y);
}
if (x < sz.width - 1) {
raster.fillPoint(x + 1, y);
}
if (y > 0) {
raster.fillPoint(x, y - 1);
}
if (y < sz.height - 1) {
raster.fillPoint(x, y + 1);
}
// 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
raster.fillPoint(x, y);
// 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) {
raster.fillVerLine(x, 0, sz.height - 1);
// int baseIndex = xy.x;
// for (int y = 0; y < sz.height; y++) {
// opaqueData[baseIndex] = col;
// baseIndex += sz.width;
// }
}
if (y >= 0 && y < sz.height) {
raster.fillHorLine(y, 0, sz.width - 1);
// int baseIndex = xy.y * sz.width;
// for (int x = 0; x < sz.width; x++)
// opaqueData[baseIndex + x] = 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 periodicRefresh() {
// handle refreshing
if (periodicRefresh) {
objectCount++;
if (objectCount > 100) {
objectCount = 0;
long currentTime = System.currentTimeMillis();
if (currentTime - lastRefreshTime > 1000) {
wnd.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, int oX, int oY, boolean topLevel, VarContext context) {
renderedCells++;
VectorCache.VectorCell vc = VectorCache.theCache.drawCell(cell.getId(), orient, context, scale);
// draw all subcells
for (VectorCache.VectorSubCell vsc : vc.subCells) {
totalCells++;
// get instance location
int soX = vsc.offsetX + oX;
int soY = vsc.offsetY + oY;
VectorCache.VectorCell subVC = VectorCache.theCache.findVectorCell(vsc.subCellId, vc.orient.concatenate(vsc.n.orient));
gridToScreen(subVC.lX + soX, subVC.hY + soY, tempPt1);
gridToScreen(subVC.hX + soX, subVC.lY + soY, tempPt2);
int lX = tempPt1.x;
int lY = tempPt1.y;
int hX = tempPt2.x;
int hY = tempPt2.y;
// see if the subcell is clipped
if (hX < clipLX || lX > clipHX) {
continue;
}
if (hY < clipLY || lY > clipHY) {
continue;
}
// see if drawing "down in place"
boolean onPathDown = inPlaceSubcellPath != null && inPlaceSubcellPath.get(vsc.subCellId.cellIndex);
// see if cell contents should be drawn
boolean isExpanded = cell.isExpanded(vsc.n.nodeId);
boolean expanded = isExpanded || fullInstantiate;
// if not expanded, but viewing this cell in-place, expand it
if (!expanded && onPathDown) {
expanded = true;
}
// two ways to draw a cell instance
CellId subCellId = vsc.subCellId;
Cell subCell = VectorCache.theCache.database.getCell(subCellId);
if (expanded) {
// show the contents of the cell
Orientation subOrient = orient.concatenate(vsc.n.orient);
int soX_ = vsc.offsetX + oX;
int soY_ = vsc.offsetY + oY;
if (!expandedCellCached(subCell, subOrient, soX_, soY_, context, fullInstantiate)) {
// just draw it directly
cellsRendered++;
// NodeInst ni = subCell.getNodeById(vsc.n.nodeId);
drawCell(subCell, drawLimitBounds, fullInstantiate, subOrient, soX_, soY_, false, context.push(cell, vsc.n));
}
} else {
// draw the black box of the instance
int[] op = subVC.outlinePoints;
int p1x = op[0] + soX;
int p1y = op[1] + soY;
int p2x = op[2] + soX;
int p2y = op[3] + soY;
int p3x = op[4] + soX;
int p3y = op[5] + soY;
int p4x = op[6] + soX;
int p4y = op[7] + soY;
gridToScreen(p1x, p1y, tempPt1);
gridToScreen(p2x, p2y, tempPt2);
ERaster instanceRaster = getInstanceRaster();
drawLine(tempPt1, tempPt2, 0, instanceRaster);
gridToScreen(p2x, p2y, tempPt1);
gridToScreen(p3x, p3y, tempPt2);
drawLine(tempPt1, tempPt2, 0, instanceRaster);
gridToScreen(p3x, p3y, tempPt1);
gridToScreen(p4x, p4y, tempPt2);
drawLine(tempPt1, tempPt2, 0, instanceRaster);
gridToScreen(p1x, p1y, tempPt1);
gridToScreen(p4x, p4y, tempPt2);
drawLine(tempPt1, tempPt2, 0, instanceRaster);
// draw the instance name
if (canDrawText && gp.isTextVisibilityOn(TextDescriptor.TextType.INSTANCE)) {
tempRect.setBounds(lX, lY, hX - lX, hY - lY);
TextDescriptor descript = vsc.n.protoDescriptor;
NodeProto np = VectorCache.theCache.database.getCell(vsc.subCellId);
drawText(tempRect, Poly.Type.TEXTBOX, descript, np.describe(false), textColor, null);
}
}
if (canDrawText && (topLevel || onPathDown || inPlaceCurrent == cell)) {
drawPortList(vsc, subVC, soX, soY, expanded, onPathDown);
}
// drawPortList(vsc, subVC, soX, soY, isExpanded);
}
// draw primitives
drawList(oX, oY, vc.shapes);
// show cell variables if at the top level
if (topLevel) {
drawList(oX, oY, vc.getTopOnlyShapes());
}
}
/**
* @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, int oX, int oY, VarContext context, 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.getId(), orient);
ExpandedCellInfo expandedCellCount = expandedCells.get(expansionKey);
if (expandedCellCount != null && expandedCellCount.offscreen == null) {
if (expandedCellCount.tooLarge) {
return false;
}
// if this combination is not used multiple times, do not cache it
if (expandedCellCount.singleton && expandedCellCount.instanceCount < 2) {
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());
if (canDrawText) {
Rectangle2D textBounds = subCell.getTextBounds(dummyWnd);
if (textBounds != null) {
cellBounds.add(textBounds);
}
}
AffineTransform rotTrans = orient.pureRotate();
DBMath.transformRect(cellBounds, rotTrans);
int lX = (int) Math.ceil(cellBounds.getMinX() * scale - 0.5);
int hX = (int) Math.floor(cellBounds.getMaxX() * scale + 0.5);
int lY = (int) Math.ceil(cellBounds.getMinY() * scale - 0.5);
int hY = (int) Math.floor(cellBounds.getMaxY() * scale + 0.5);
assert lX <= hX && lY <= hY;
// if this is the first use, create the offscreen buffer
if (expandedCellCount == null) {
expandedCellCount = new ExpandedCellInfo();
expandedCells.put(expansionKey, expandedCellCount);
}
// do not cache if the cell is too large (creates immense offscreen buffers)
if (hX - lX >= topSz.width / 32 && hY - lY >= topSz.height / 32) {
expandedCellCount.tooLarge = true;
return false;
}
expandedCellCount.offscreen = new LayerDrawing(scale, lX, hX, lY, hY);
expandedCellCount.offscreen.drawCell(subCell, null, fullInstantiate, orient, 0, 0, false, context);
offscreensCreated++;
offscreenPixelsCreated += expandedCellCount.offscreen.total;
if (DEBUG) {
System.out.print(subCell + " " + orient + " rendered in " + expandedCellCount.offscreen.total + " pixels"
+ " t=" + System.currentTimeMillis());
for (Layer layer : expandedCellCount.offscreen.layerRasters.keySet()) {
System.out.print(" " + layer.getName());
}
if (instanceRaster != null) {
System.out.print(" INSTANCE");
}
System.out.println();
assert gridRaster == null;
}
}
// copy out of the offscreen buffer into the main buffer
gridToScreen(oX, oY, tempPt1);
copyBits(expandedCellCount.offscreen, tempPt1.x, tempPt1.y);
offscreensUsed++;
if (TAKE_STATS) {
offscreensUsedSet.add(expansionKey);
}
offscreenPixelsUsed += expandedCellCount.offscreen.total;
return true;
}
// ************************************* CELL CACHING *************************************
/**
* 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 (ImmutableNodeInst n: cell.backupUnsafe().cellRevision.nodes) {
if (!(n.protoId instanceof CellId)) {
continue;
}
// if limiting drawing, reject when out of area
if (drawLimitBounds != null) {
Cell subCell = cell.getDatabase().getCell((CellId)n.protoId);
Rectangle2D bounds = new Rectangle2D.Double();
n.orient.rectangleBounds(subCell.getBounds(), n.anchor, bounds);
DBMath.transformRect(bounds, prevTrans);
if (!DBMath.rectsIntersect(bounds, drawLimitBounds)) {
return;
}
}
countNode(cell, n, fullInstantiate, orient, prevTrans);
}
}
/**
* Recursive method to count the number of times that a cell-transformation is used
*/
private void countNode(Cell parent, ImmutableNodeInst n, boolean fullInstantiate, Orientation orient, AffineTransform trans) {
CellId subCellId = (CellId)n.protoId;
Cell subCell = parent.getDatabase().getCell(subCellId);
Rectangle2D cellBounds = subCell.getBounds();
// if the node is tiny, it will be approximated
double objWidth = Math.max(cellBounds.getWidth(), cellBounds.getHeight());
// double objWidth = Math.max(ni.getXSize(), ni.getYSize());
if (objWidth < maxObjectSize) {
return;
}
// transform into the subcell
Orientation subOrient = orient.concatenate(n.orient);
AffineTransform subTrans = n.orient.rotateAbout(n.anchor.getLambdaX(), n.anchor.getLambdaY(), 0, 0);
subTrans.preConcatenate(trans);
// AffineTransform subTrans = ni.transformOut(trans);
// compute where this cell lands on the screen
Poly poly = new Poly(cellBounds);
poly.transform(subTrans);
// if (wnd.isInPlaceEdit()) poly.transform(wnd.getInPlaceTransformIn());
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 = parent.isExpanded(n.nodeId);
// boolean expanded = ni.isExpanded();
if (fullInstantiate) {
expanded = true;
}
// if not expanded, but viewing this cell in-place, expand it
expanded = expanded || inPlaceSubcellPath != null && inPlaceSubcellPath.get(subCell.getId().cellIndex);
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.getId(), 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.cellId)) {
expandedCells.remove(expansionKey);
}
}
}
/**
* Method to copy the offscreen bits for a cell into the offscreen bits for the entire screen.
*/
private void copyBits(LayerDrawing 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;
}
// copy the patterned opaque layers
for (Map.Entry<Layer, TransparentRaster> e : srcOffscreen.layerRasters.entrySet()) {
Layer layer = e.getKey();
ERaster raster = getRaster(layer, null, false);
if (raster == null) {
continue;
}
TransparentRaster polSrc = e.getValue();
raster.copyBits(polSrc, minSrcX, maxSrcX, minSrcY, maxSrcY, cornerX, cornerY);
}
if (srcOffscreen.instanceRaster != null) {
ERaster raster = getInstanceRaster();
raster.copyBits(srcOffscreen.instanceRaster, minSrcX, maxSrcX, minSrcY, maxSrcY, cornerX, cornerY);
}
assert srcOffscreen.gridRaster == null;
}
// ************************************* 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 coordinates are inclusive.
* Filling might be patterned.
* @param lX left X coordinate
* @param hX right X coordinate
* @param lY top Y coordinate
* @param hY bottom Y coordinate
*/
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 coordinates are inclusive.
* Filling might be patterned.
* @param y Y coordinate
* @param lX left X coordinate
* @param hX right X coordinate
*/
public void fillHorLine(int y, int lX, int hX);
/**
* Method to fill a vertical scanline [x] x [lY,hY].
* Both low and high coordinates are inclusive.
* Filling might be patterned.
* @param x X coordinate
* @param lY top Y coordinate
* @param hY bottom Y coordinate
*/
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 coordinates are inclusive.
* Drawing is always solid.
* @param y Y coordinate
* @param lX left X coordinate
* @param hX right X coordinate
*/
public void drawHorLine(int y, int lX, int hX);
/**
* Method to draw a vertical line [x] x [lY,hY].
* Both low and high coordinates are inclusive.
* Drawing is always solid.
* @param x X coordinate
* @param lY top Y coordinate
* @param hY bottom Y coordinate
*/
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();
/**
* Method to copy bits from rectangle of source TransparentRaster to thus ERaster.
* @param polSrc source TransparentRaster.
* @param minSrcX left bound of source rectangle (inclusive).
* @param maxSrcX right bound of source rectangle (inclusive).
* @param minSrcY top bound of source rectangle (inclusive).
* @param maxSrcY bottom bound of source rectangle (inclusive).
* @param dx the X translation factor from src space to dst space of the copy.
* @param dy the Y translation factor from src space to dst space of the copy.
*/
public void copyBits(TransparentRaster src, int minSrcX, int maxSrcX, int minSrcY, int maxSrcY, int dx, int dy);
}
ERaster getRaster(Layer layer, EGraphics graphics, boolean forceVisible) {
if (layer == null) {
layer = Artwork.tech().defaultLayer;
}
assert !layer.isPseudoLayer();
TransparentRaster raster = layerRasters.get(layer);
if (raster == null) {
raster = new TransparentRaster(sz.height, numIntsPerRow);
layerRasters.put(layer, raster);
}
if (patternedDisplay && renderedWindow) {
int[] pattern = null;
if (graphics == null && layer != null) {
graphics = gp.getGraphics(layer);
}
if (nowPrinting != 0 ? graphics.isPatternedOnPrinter() : graphics.isPatternedOnDisplay()) {
pattern = graphics.getReversedPattern();
}
if (pattern != null) {
EGraphics.Outline o = graphics.getOutlined();
if (o == EGraphics.Outline.NOPAT) {
o = null;
}
currentPatternedTransparentRaster.init(raster.layerBitMap, raster.intsPerRow, pattern, o);
raster = currentPatternedTransparentRaster;
}
}
return raster;
}
ERaster getInstanceRaster() {
if (instanceRaster == null) {
instanceRaster = new TransparentRaster(sz.height, numIntsPerRow);
}
return instanceRaster;
}
ERaster getGridRaster() {
if (gridRaster == null) {
gridRaster = new TransparentRaster(sz.height, numIntsPerRow);
}
return gridRaster;
}
// /**
// * ERaster for solid opaque layers.
// */
// private static class OpaqueRaster implements ERaster {
// private static OpaqueRaster current = new OpaqueRaster();
//
// int[] opaqueData;
// int width;
// int col;
//
// void init(int[] opaqueData, int width, int col) {
// this.opaqueData = opaqueData;
// this.width = width;
// this.col = col;
// }
//
// public void fillBox(int lX, int hX, int lY, int hY) {
// int baseIndex = lY*width;
// for (int y = lY; y <= hY; y++) {
// for(int x = lX; x <= hX; x++)
// opaqueData[baseIndex + x] = col;
// baseIndex += width;
// }
// }
//
// public void fillHorLine(int y, int lX, int hX) {
// int baseIndex = y*width + lX;
// for (int x = lX; x <= hX; x++)
// opaqueData[baseIndex++] = col;
// }
//
// public void fillVerLine(int x, int lY, int hY) {
// int baseIndex = lY*width + x;
// for (int y = lY; y <= hY; y++) {
// opaqueData[baseIndex] = col;
// baseIndex += width;
// }
// }
//
// public void fillPoint(int x, int y) {
// opaqueData[y * width + x] = col;
// }
//
// public void drawHorLine(int y, int lX, int hX) {
// int baseIndex = y*width + lX;
// for (int x = lX; x <= hX; x++)
// opaqueData[baseIndex++] = col;
// }
//
// public void drawVerLine(int x, int lY, int hY) {
// int baseIndex = lY*width + x;
// for (int y = lY; y <= hY; y++) {
// opaqueData[baseIndex] = col;
// baseIndex += width;
// }
// }
//
// public void drawPoint(int x, int y) {
// opaqueData[y * width + x] = col;
// }
//
// public EGraphics.Outline getOutline() {
// return null;
// }
//
// public void copyBits(TransparentRaster src, int minSrcX, int maxSrcX, int minSrcY, int maxSrcY, int dx, int dy) {
// int[] srcLayerBitMap = src.layerBitMap;
// for (int srcY = minSrcY; srcY <= maxSrcY; srcY++) {
// int destY = srcY + dy;
// int destBase = destY * width;
// int srcBaseIndex = srcY*src.intsPerRow;
// for (int srcX = minSrcX; srcX <= maxSrcX; srcX++) {
// int destX = srcX + dx;
// if ((srcLayerBitMap[srcBaseIndex + (srcX>>5)] & (1<<(srcX&31))) != 0)
// opaqueData[destBase + destX] = col;
// }
// }
// }
// }
/**
* ERaster for solid transparent layers.
*/
private static class TransparentRaster implements ERaster {
private static TransparentRaster current = new TransparentRaster();
int[] layerBitMap;
int intsPerRow;
private TransparentRaster() {
}
TransparentRaster(int height, int numIntsPerRow) {
this.intsPerRow = numIntsPerRow;
layerBitMap = new int[height * numIntsPerRow];
}
void init(int[] layerBitMap, int intsPerRow) {
this.layerBitMap = layerBitMap;
this.intsPerRow = intsPerRow;
}
public void fillBox(int lX, int hX, int lY, int hY) {
int baseIndex = lY * intsPerRow;
int lIndex = baseIndex + (lX >> 5);
int hIndex = baseIndex + (hX >> 5);
if (lIndex == hIndex) {
int mask = (2 << (hX & 31)) - (1 << (lX & 31));
for (int y = lY; y < hY; y++) {
layerBitMap[lIndex] |= mask;
lIndex += intsPerRow;
}
} else {
int lMask = -(1 << (lX & 31));
int hMask = (2 << (hX & 31)) - 1;
for (int y = lY; y <= hY; y++) {
layerBitMap[lIndex] |= lMask;
for (int index = lIndex + 1; index < hIndex; index++) {
layerBitMap[index] = -1;
}
layerBitMap[hIndex] |= hMask;
lIndex += intsPerRow;
hIndex += intsPerRow;
}
}
}
public void eraseBox(int lX, int hX, int lY, int hY) {
int baseIndex = lY * intsPerRow;
int lIndex = baseIndex + (lX >> 5);
int hIndex = baseIndex + (hX >> 5);
if (lIndex == hIndex) {
int mask = (2 << (hX & 31)) - (1 << (lX & 31));
mask = ~mask;
for (int y = lY; y < hY; y++) {
layerBitMap[lIndex] &= mask;
lIndex += intsPerRow;
}
} else {
int lMask = -(1 << (lX & 31));
int hMask = (2 << (hX & 31)) - 1;
lMask = ~lMask;
hMask = ~hMask;
for (int y = lY; y <= hY; y++) {
layerBitMap[lIndex] &= lMask;
for (int index = lIndex + 1; index < hIndex; index++) {
layerBitMap[index] = 0;
}
layerBitMap[hIndex] &= hMask;
lIndex += intsPerRow;
hIndex += intsPerRow;
}
}
}
public void eraseAll() {
for (int i = 0; i < layerBitMap.length; i++) {
layerBitMap[i] = 0;
}
}
public void fillHorLine(int y, int lX, int hX) {
int baseIndex = y * intsPerRow;
int lIndex = baseIndex + (lX >> 5);
int hIndex = baseIndex + (hX >> 5);
if (lIndex == hIndex) {
layerBitMap[lIndex] |= (2 << (hX & 31)) - (1 << (lX & 31));
} else {
layerBitMap[lIndex++] |= -(1 << (lX & 31));
while (lIndex < hIndex) {
layerBitMap[lIndex++] |= -1;
}
layerBitMap[hIndex] |= (2 << (hX & 31)) - 1;
}
}
public void fillVerLine(int x, int lY, int hY) {
int baseIndex = lY * intsPerRow + (x >> 5);
int mask = 1 << (x & 31);
for (int y = lY; y <= hY; y++) {
layerBitMap[baseIndex] |= mask;
baseIndex += intsPerRow;
}
}
public void fillPoint(int x, int y) {
layerBitMap[y * intsPerRow + (x >> 5)] |= (1 << (x & 31));
}
public void drawHorLine(int y, int lX, int hX) {
int baseIndex = y * intsPerRow;
int lIndex = baseIndex + (lX >> 5);
int hIndex = baseIndex + (hX >> 5);
if (lIndex == hIndex) {
layerBitMap[lIndex] |= (2 << (hX & 31)) - (1 << (lX & 31));
} else {
layerBitMap[lIndex++] |= -(1 << (lX & 31));
while (lIndex < hIndex) {
layerBitMap[lIndex++] |= -1;
}
layerBitMap[hIndex] |= (2 << (hX & 31)) - 1;
}
}
public void drawVerLine(int x, int lY, int hY) {
int baseIndex = lY * intsPerRow + (x >> 5);
int mask = 1 << (x & 31);
for (int y = lY; y <= hY; y++) {
layerBitMap[baseIndex] |= mask;
baseIndex += intsPerRow;
}
}
public void drawPoint(int x, int y) {
layerBitMap[y * intsPerRow + (x >> 5)] |= (1 << (x & 31));
}
public EGraphics.Outline getOutline() {
return null;
}
public void copyBits(TransparentRaster src, int minSrcX, int maxSrcX, int minSrcY, int maxSrcY, int dx, int dy) {
int[] srcLayerBitMap = src.layerBitMap;
int minDestX = minSrcX + dx;
int maxDestX = maxSrcX + dx;
int minDestY = minSrcY + dy;
// int maxDestY = maxSrcY + dy;
int leftShift = dx & 31;
int rightShift = 32 - leftShift;
int srcBaseIndex = minSrcY * src.intsPerRow + (minSrcX >> 5);
int destBaseIndex = minDestY * intsPerRow + (minDestX >> 5);
int numDestInts = (maxDestX >> 5) - (minDestX >> 5);
if (numDestInts == 0) {
// Single destination byte.
int destMask = (2 << (maxDestX & 31)) - (1 << (minDestX & 31));
if ((minSrcX >> 5) != (maxSrcX >> 5)) {
// A pair of source bytes
for (int srcY = minSrcY; srcY <= maxSrcY; srcY++) {
int s0 = srcLayerBitMap[srcBaseIndex];
int s1 = srcLayerBitMap[srcBaseIndex + 1];
int v = ((s0 >>> rightShift) | (s1 << leftShift)) & destMask;
if (v != 0) {
layerBitMap[destBaseIndex] |= v;
}
srcBaseIndex += src.intsPerRow;
destBaseIndex += intsPerRow;
}
} else if ((minDestX & 31) >= (minSrcX & 31)) {
// source byte shifted left
for (int srcY = minSrcY; srcY <= maxSrcY; srcY++) {
int s = srcLayerBitMap[srcBaseIndex];
int v = (s << leftShift) & destMask;
if (v != 0) {
layerBitMap[destBaseIndex] |= v;
}
srcBaseIndex += src.intsPerRow;
destBaseIndex += intsPerRow;
}
} else {
// source byte shifted right
for (int srcY = minSrcY; srcY <= maxSrcY; srcY++) {
int s = srcLayerBitMap[srcBaseIndex];
int v = (s >>> rightShift) & destMask;
if (v != 0) {
layerBitMap[destBaseIndex] |= v;
}
srcBaseIndex += src.intsPerRow;
destBaseIndex += intsPerRow;
}
}
} else {
int minDestMask = -(1 << (minDestX & 31));
int maxDestMask = (2 << (maxDestX & 31)) - 1;
int srcIncr = src.intsPerRow - (maxSrcX >> 5) + (minSrcX >> 5) - 1;
if (leftShift == 0) {
for (int srcY = minSrcY; srcY <= maxSrcY; srcY++) {
assert srcBaseIndex == srcY * src.intsPerRow + (minSrcX >> 5);
assert destBaseIndex == (srcY + dy) * intsPerRow + (minDestX >> 5);
int v0 = srcLayerBitMap[srcBaseIndex++] & minDestMask;
if (v0 != 0) {
layerBitMap[destBaseIndex] |= v0;
}
destBaseIndex++;
for (int i = 1; i < numDestInts; i++) {
int v = srcLayerBitMap[srcBaseIndex++];
if (v != 0) {
layerBitMap[destBaseIndex] |= v;
}
destBaseIndex++;
}
int vf = srcLayerBitMap[srcBaseIndex++] & maxDestMask;
if (vf != 0) {
layerBitMap[destBaseIndex] |= vf;
}
srcBaseIndex += srcIncr;
destBaseIndex += (intsPerRow - numDestInts);
}
} else if (numDestInts == 2 && (minSrcX >> 5) == (maxSrcX >> 5)) {
for (int srcY = minSrcY; srcY <= maxSrcY; srcY++) {
assert srcBaseIndex == srcY * src.intsPerRow + (minSrcX >> 5);
assert destBaseIndex == (srcY + dy) * intsPerRow + (minDestX >> 5);
int s = srcLayerBitMap[srcBaseIndex];
int b0 = srcLayerBitMap[srcBaseIndex++];
int v0 = (s << leftShift) & minDestMask;
if (v0 != 0) {
layerBitMap[destBaseIndex] |= v0;
}
int vf = (s >>> rightShift) & maxDestMask;
if (vf != 0) {
layerBitMap[destBaseIndex + 1] |= vf;
}
srcBaseIndex += src.intsPerRow;
destBaseIndex += intsPerRow;
}
} else {
boolean minSrcPair = leftShift > (minDestX & 31);
boolean maxSrcPair = leftShift <= (maxDestX & 31);
for (int srcY = minSrcY; srcY <= maxSrcY; srcY++) {
assert srcBaseIndex == srcY * src.intsPerRow + (minSrcX >> 5);
assert destBaseIndex == (srcY + dy) * intsPerRow + (minDestX >> 5);
int s = minSrcPair ? srcLayerBitMap[srcBaseIndex++] : 0;
int b0 = srcLayerBitMap[srcBaseIndex++];
int v0 = ((s >>> rightShift) | (b0 << leftShift)) & minDestMask;
if (v0 != 0) {
layerBitMap[destBaseIndex] |= v0;
}
destBaseIndex++;
s = b0;
for (int i = 1; i < numDestInts; i++) {
int b = srcLayerBitMap[srcBaseIndex++];
int v = (s >>> rightShift) | (b << leftShift);
if (v != 0) {
layerBitMap[destBaseIndex] |= v;
}
destBaseIndex++;
s = b;
}
int bf = maxSrcPair ? srcLayerBitMap[srcBaseIndex++] : 0;
int vf = ((s >>> rightShift) | (bf << leftShift)) & maxDestMask;
if (vf != 0) {
layerBitMap[destBaseIndex] |= vf;
}
srcBaseIndex += srcIncr;
destBaseIndex += (intsPerRow - numDestInts);
}
}
}
}
}
// /**
// * ERaster for patterned opaque layers.
// */
// private static class PatternedOpaqueRaster extends OpaqueRaster {
// private static PatternedOpaqueRaster current = new PatternedOpaqueRaster();
//
// int[] pattern;
// EGraphics.Outline outline;
//
// private void init(int[] opaqueData, int width, int col, int[] pattern, EGraphics.Outline outline) {
// super.init(opaqueData, width, col);
// this.pattern = pattern;
// this.outline = outline;
// }
//
// public void fillBox(int lX, int hX, int lY, int hY) {
// for (int y = lY; y <= hY; y++) {
// // setup pattern for this row
// int pat = pattern[y&15];
// if (pat == 0) continue;
//
// int baseIndex = y * width;
// for (int x = lX; x <= hX; x++) {
// if ((pat & (0x8000 >> (x&15))) != 0)
// opaqueData[baseIndex + x] = col;
// }
// }
// }
//
// public void fillHorLine(int y, int lX, int hX) {
// int pat = pattern[y & 15];
// if (pat == 0) return;
// int baseIndex = y * width;
// for (int x = lX; x <= hX; x++) {
// if ((pat & (1 << (15-(x&15)))) != 0) {
// int index = baseIndex + x;
// opaqueData[index] = col;
// }
// }
// }
//
// public void fillVerLine(int x, int lY, int hY) {
// int patMask = 0x8000 >> (x&15);
// int baseIndex = lY*width + x;
// for (int y = lY; y <= hY; y++) {
// if ((pattern[y&15] & patMask) != 0)
// opaqueData[baseIndex] = col;
// baseIndex += width;
// }
// }
//
// public void fillPoint(int x, int y) {
// int patMask = 0x8000 >> (x&15);
// if ((pattern[y&15] & patMask) != 0)
// opaqueData[y*width + x] = col;
// }
//
// public EGraphics.Outline getOutline() {
// return outline;
// }
//
// public void copyBits(TransparentRaster src, int minSrcX, int maxSrcX, int minSrcY, int maxSrcY, int dx, int dy) {
// int[] srcLayerBitMap = src.layerBitMap;
// for (int srcY = minSrcY; srcY <= maxSrcY; srcY++) {
// int destY = srcY + dy;
// int destBase = destY * width;
// int pat = pattern[destY&15];
// if (pat == 0) continue;
// int srcBaseIndex = srcY*src.intsPerRow;
// for (int srcX = minSrcX; srcX <= maxSrcX; srcX++) {
// int destX = srcX + dx;
// if ((srcLayerBitMap[srcBaseIndex + (srcX>>5)] & (1<<(srcX&31))) != 0) {
// if ((pat & (0x8000 >> (destX&15))) != 0)
// opaqueData[destBase + destX] = col;
// }
// }
// }
// }
// }
/**
* ERaster for patterned transparent layers.
*/
private static class PatternedTransparentRaster extends TransparentRaster {
int[] pattern;
EGraphics.Outline outline;
PatternedTransparentRaster() {
}
private void init(int[] layerBitMap, int intsPerRow, int[] pattern, EGraphics.Outline outline) {
super.init(layerBitMap, intsPerRow);
this.pattern = pattern;
this.outline = outline;
}
public void fillBox(int lX, int hX, int lY, int hY) {
int baseIndex = lY * intsPerRow;
int lIndex = baseIndex + (lX >> 5);
int hIndex = baseIndex + (hX >> 5);
if (lIndex == hIndex) {
int mask = (2 << (hX & 31)) - (1 << (lX & 31));
for (int y = lY; y < hY; y++) {
int pat = mask & pattern[y & 15];
if (pat != 0) {
layerBitMap[lIndex] |= pat;
}
lIndex += intsPerRow;
}
} else {
int lMask = -(1 << (lX & 31));
int hMask = (2 << (hX & 31)) - 1;
for (int y = lY; y <= hY; y++) {
int pat = pattern[y & 15];
if (pat != 0) {
layerBitMap[lIndex] |= lMask & pat;
for (int index = lIndex + 1; index < hIndex; index++) {
layerBitMap[index] |= pat;
}
layerBitMap[hIndex] |= hMask & pat;
}
lIndex += intsPerRow;
hIndex += intsPerRow;
}
}
}
public void fillHorLine(int y, int lX, int hX) {
int pat = pattern[y & 15];
if (pat == 0) {
return;
}
int baseIndex = y * intsPerRow;
int lIndex = baseIndex + (lX >> 5);
int hIndex = baseIndex + (hX >> 5);
if (lIndex == hIndex) {
int mask = pat & ((2 << (hX & 31)) - (1 << (lX & 31)));
if (mask != 0) {
layerBitMap[lIndex] |= mask;
}
} else {
layerBitMap[lIndex++] |= pat & (-(1 << (lX & 31)));
while (lIndex < hIndex) {
layerBitMap[lIndex++] |= pat;
}
layerBitMap[hIndex] |= pat & ((2 << (hX & 31)) - 1);
}
}
public void fillVerLine(int x, int lY, int hY) {
int baseIndex = lY * intsPerRow + (x >> 5);
int mask = 1 << (x & 31);
for (int y = lY; y <= hY; y++) {
if ((pattern[y & 15] & mask) != 0) {
layerBitMap[baseIndex] |= mask;
}
baseIndex += intsPerRow;
}
}
public void fillPoint(int x, int y) {
int mask = (1 << (x & 31)) & pattern[y & 15];
if (mask != 0) {
layerBitMap[y * intsPerRow + (x >> 5)] |= mask;
}
}
public EGraphics.Outline getOutline() {
return outline;
}
public void copyBits(TransparentRaster src, int minSrcX, int maxSrcX, int minSrcY, int maxSrcY, int dx, int dy) {
int[] srcLayerBitMap = src.layerBitMap;
assert (minSrcY + dy) * intsPerRow + ((minSrcX + dx) >> 5) >= 0;
assert (maxSrcY + dy) * intsPerRow + ((maxSrcX + dx) >> 5) < layerBitMap.length;
for (int srcY = minSrcY; srcY <= maxSrcY; srcY++) {
int destY = srcY + dy;
int pat = pattern[destY & 15];
if (pat == 0) {
continue;
}
int srcBaseIndex = srcY * src.intsPerRow;
int destBaseIndex = destY * intsPerRow;
for (int srcX = minSrcX; srcX <= maxSrcX; srcX++) {
int destX = srcX + dx;
if ((srcLayerBitMap[srcBaseIndex + (srcX >> 5)] & (1 << (srcX & 31))) != 0) {
int destMask = 1 << (destX & 31);
if ((pat & destMask) != 0) {
layerBitMap[destBaseIndex + (destX >> 5)] |= destMask;
}
}
}
}
}
}
// ************************************* RENDERING POLY SHAPES *************************************
private static Rectangle tempRect = new Rectangle();
private void gridToScreen(int dbX, int dbY, Point result) {
double scrX = (dbX - factorX) * scale_;
double scrY = (factorY - dbY) * scale_;
result.x = (int) (scrX >= 0 ? scrX + 0.5 : scrX - 0.5);
result.y = (int) (scrY >= 0 ? scrY + 0.5 : scrY - 0.5);
}
/**
* Method to draw a list of cached shapes.
* @param oX the X offset to draw the shapes (in database grid coordinates).
* @param oY the Y offset to draw the shapes (in database grid coordinates).
* @param shapes the List of shapes (VectorBase objects).
*/
private void drawList(int oX, int oY, List<VectorCache.VectorBase> shapes) // throws AbortRenderingException
{
EditWindow0 textWnd = wnd != null ? wnd : dummyWnd;
// render all shapes
for (VectorCache.VectorBase vb : shapes) {
// if (stopRendering) throw new AbortRenderingException();
// handle refreshing
periodicRefresh();
if (vb instanceof VectorCache.VectorText) {
VectorCache.VectorText vt = (VectorCache.VectorText) vb;
TextDescriptor td = vt.descript;
if (td != null && !td.isAbsoluteSize()) {
double size = td.getTrueSize(scale, textWnd);
if (size <= canDrawRelativeText) {
continue;
}
} else {
if (!canDrawText) {
continue;
}
}
switch (vt.textType) {
case VectorCache.VectorText.TEXTTYPEARC:
if (!gp.isTextVisibilityOn(TextDescriptor.TextType.ARC)) {
continue;
}
break;
case VectorCache.VectorText.TEXTTYPENODE:
if (!gp.isTextVisibilityOn(TextDescriptor.TextType.NODE)) {
continue;
}
break;
case VectorCache.VectorText.TEXTTYPECELL:
if (!gp.isTextVisibilityOn(TextDescriptor.TextType.CELL)) {
continue;
}
break;
case VectorCache.VectorText.TEXTTYPEEXPORT:
if (!gp.isTextVisibilityOn(TextDescriptor.TextType.EXPORT)) {
continue;
}
break;
case VectorCache.VectorText.TEXTTYPEANNOTATION:
if (!gp.isTextVisibilityOn(TextDescriptor.TextType.ANNOTATION)) {
continue;
}
break;
case VectorCache.VectorText.TEXTTYPEINSTANCE:
if (!gp.isTextVisibilityOn(TextDescriptor.TextType.INSTANCE)) {
continue;
}
break;
}
// if (vt.height < maxTextSize) continue;
if (vt.tempName && !gp.isShowTempNames()) {
continue;
}
String drawString = vt.str;
int lX = vt.bounds.x;
int lY = vt.bounds.y;
int hX = lX + vt.bounds.width;
int hY = lY + vt.bounds.height;
gridToScreen(lX + oX, hY + oY, tempPt1);
gridToScreen(hX + oX, lY + oY, tempPt2);
lX = tempPt1.x;
lY = tempPt1.y;
hX = tempPt2.x;
hY = tempPt2.y;
Color color = textColor;
if (vt.layer != null) {
color = gp.getGraphics(vt.layer).getColor();
}
PrimitiveNode baseNode = null;
if (vt.textType == VectorCache.VectorText.TEXTTYPEEXPORT && vt.basePort != null) {
baseNode = vt.basePort.getParent();
int exportDisplayLevel = gp.exportDisplayLevel;
if (exportDisplayLevel == 2) {
// draw export as a cross
int cX = (lX + hX) / 2;
int cY = (lY + hY) / 2;
crossTextList.add(new CrossTextInfo(cX, cY, textColor, baseNode));
continue;
}
// draw export as text
if (exportDisplayLevel == 1) {
drawString = Export.getShortName(drawString);
}
color = textColor;
}
tempRect.setBounds(lX, lY, hX - lX, hY - lY);
drawText(tempRect, vt.style, vt.descript, drawString, color, baseNode);
continue;
}
ERaster raster = getRaster(vb.layer, vb.graphicsOverride, false);
if (raster == null) {
continue;
}
// handle each shape
if (vb instanceof VectorCache.VectorManhattan) {
boxCount++;
VectorCache.VectorManhattan vm = (VectorCache.VectorManhattan) vb;
boxArrayCount += vm.coords.length / 4;
for (int i = 0; i < vm.coords.length; i += 4) {
int c1X = vm.coords[i];
int c1Y = vm.coords[i + 1];
int c2X = vm.coords[i + 2];
int c2Y = vm.coords[i + 3];
// determine coordinates of rectangle on the screen
gridToScreen(c1X + oX, c2Y + oY, tempPt1);
gridToScreen(c2X + oX, c1Y + oY, tempPt2);
int lX = tempPt1.x;
int lY = tempPt1.y;
int hX = tempPt2.x;
int hY = tempPt2.y;
drawBox(lX, hX, lY, hY, raster);
}
} else if (vb instanceof VectorCache.VectorPolygon) {
polygonCount++;
VectorCache.VectorPolygon vp = (VectorCache.VectorPolygon) vb;
Point[] intPoints = new Point[vp.points.length];
for (int i = 0; i < vp.points.length; i++) {
intPoints[i] = new Point();
gridToScreen(vp.points[i].x + oX, vp.points[i].y + oY, intPoints[i]);
}
Point[] clippedPoints = GenMath.clipPoly(intPoints, clipLX, clipHX, clipLY, clipHY);
drawPolygon(clippedPoints, raster);
} else if (vb instanceof VectorCache.VectorLine) {
lineCount++;
VectorCache.VectorLine vl = (VectorCache.VectorLine) vb;
// determine coordinates of line on the screen
gridToScreen(vl.fX + oX, vl.fY + oY, tempPt1);
gridToScreen(vl.tX + oX, vl.tY + oY, tempPt2);
// clip and draw the line
drawLine(tempPt1, tempPt2, vl.texture, raster);
} else if (vb instanceof VectorCache.VectorCross) {
crossCount++;
VectorCache.VectorCross vcr = (VectorCache.VectorCross) vb;
gridToScreen(vcr.x + oX, vcr.y + oY, tempPt1);
int size = vcr.small ? 3 : 5;
drawCross(tempPt1.x, tempPt1.y, size, raster);
} else if (vb instanceof VectorCache.VectorCircle) {
VectorCache.VectorCircle vci = (VectorCache.VectorCircle) vb;
gridToScreen(vci.cX + oX, vci.cY + oY, tempPt1);
gridToScreen(vci.eX + oX, vci.eY + oY, tempPt2);
switch (vci.nature) {
case 0:
circleCount++;
drawCircle(tempPt1, tempPt2, raster);
break;
case 1:
circleCount++;
drawThickCircle(tempPt1, tempPt2, raster);
break;
case 2:
discCount++;
drawDisc(tempPt1, tempPt2, raster);
break;
}
} else if (vb instanceof VectorCache.VectorCircleArc) {
arcCount++;
VectorCache.VectorCircleArc vca = (VectorCache.VectorCircleArc) vb;
gridToScreen(vca.cX + oX, vca.cY + oY, tempPt1);
gridToScreen(vca.eX1 + oX, vca.eY1 + oY, tempPt2);
gridToScreen(vca.eX2 + oX, vca.eY2 + oY, tempPt3);
drawCircleArc(tempPt1, tempPt2, tempPt3, vca.thick, raster);
}
}
}
/**
* Method to draw a list of cached port shapes.
* @param oX the X offset to draw the shapes (in database grid coordinates).
* @param oY the Y offset to draw the shapes (in database grid coordinates).
* @param expanded true to draw a list on expanded instance.
* @param onPathDown true if this level of hierarchy is the current one in "down-in-place" editing.
*/
private void drawPortList(VectorCache.VectorSubCell vsc, VectorCache.VectorCell subVC_,
int oX, int oY, boolean expanded, boolean onPathDown)
// throws AbortRenderingException
{
if (!gp.isTextVisibilityOn(TextDescriptor.TextType.PORT)) {
return;
}
// render all shapes
List<VectorCache.VectorCellExport> portShapes = subVC_.vcg.getPortShapes();
int[] portCenters = subVC_.getPortCenters();
assert portShapes.size() * 2 == portCenters.length;
for (int i = 0; i < portShapes.size(); i++) {
VectorCache.VectorCellExport vce = portShapes.get(i);
// if (stopRendering) throw new AbortRenderingException();
// get visual characteristics of shape
if (!onPathDown && vsc.shownPorts.get(vce.getChronIndex())) {
continue;
}
// if (vt.height < maxTextSize) continue;
int cX = portCenters[i * 2];
int cY = portCenters[i * 2 + 1];
gridToScreen(cX + oX, cY + oY, tempPt1);
cX = tempPt1.x;
cY = tempPt1.y;
int portDisplayLevel = gp.portDisplayLevel;
Color portColor;
if (expanded) {
portColor = textColor;
} else {
PrimitivePort basePort = vce.getBasePort();
portColor = portColorsCache.get(basePort);
if (portColor == null) {
portColor = basePort.getPortColor(gp);
if (portColor == null) {
portColor = textColor;
}
portColorsCache.put(basePort, portColor);
}
}
if (portDisplayLevel == 2) {
// draw port as a cross
crossTextList.add(new CrossTextInfo(cX, cY, portColor, null));
continue;
}
// draw port as text
boolean shortName = portDisplayLevel == 1;
String drawString = vce.getName(shortName);
tempRect.setBounds(cX, cY, 0, 0);
drawText(tempRect, vce.style, vce.descript, drawString, portColor, null);
}
}
// ************************************* BOX DRAWING *************************************
/**
* Method to draw a box on the off-screen buffer.
*/
public void drawBox(int lX, int hX, int lY, int hY, ERaster raster) {
if (lX < clipLX) {
lX = clipLX;
}
if (hX > clipHX) {
hX = clipHX;
}
if (lY < clipLY) {
lY = clipLY;
}
if (hY > clipHY) {
hY = clipHY;
}
if (lX > hX || lY > hY) {
return;
}
boxDisplayCount++;
EGraphics.Outline o = raster.getOutline();
if (lY == hY) {
if (lX == hX) {
if (o == null) {
raster.fillPoint(lX, lY);
} else {
raster.drawPoint(lX, lY);
}
} else {
if (o == null) {
raster.fillHorLine(lY, lX, hX);
} else {
raster.drawHorLine(lY, lX, hX);
}
}
return;
}
if (lX == hX) {
if (o == null) {
raster.fillVerLine(lX, lY, hY);
} else {
raster.drawVerLine(lX, lY, hY);
}
return;
}
raster.fillBox(lX, hX, lY, hY);
if (o == null) {
return;
}
if (o.isSolidPattern()) {
raster.drawVerLine(lX, lY, hY);
raster.drawHorLine(hY, lX, hX);
raster.drawVerLine(hX, lY, hY);
raster.drawHorLine(lY, lX, hX);
if (o.getThickness() != 1) {
for (int i = 1; i < o.getThickness(); i++) {
if (lX + i <= clipHX) {
raster.drawVerLine(lX + i, lY, hY);
}
if (hY - i >= clipLX) {
raster.drawHorLine(hY - i, lX, hX);
}
if (hX - i >= clipLY) {
raster.drawVerLine(hX - i, lY, hY);
}
if (lY + i <= clipHY) {
raster.drawHorLine(lY + i, lX, hX);
}
}
}
} else {
int pattern = o.getPattern();
int len = o.getLen();
drawVerOutline(lX, lY, hY, pattern, len, raster);
drawHorOutline(hY, lX, hX, pattern, len, raster);
drawVerOutline(hX, lY, hY, pattern, len, raster);
drawHorOutline(lY, lX, hX, pattern, len, raster);
if (o.getThickness() != 1) {
for (int i = 1; i < o.getThickness(); i++) {
if (lX + i <= clipHX) {
drawVerOutline(lX + i, lY, hY, pattern, len, raster);
}
if (hY - i >= clipLX) {
drawHorOutline(hY - i, lX, hX, pattern, len, raster);
}
if (hX - i >= clipLY) {
drawVerOutline(hX - i, lY, hY, pattern, len, raster);
}
if (lY + i <= clipHY) {
drawHorOutline(lY + i, lX, hX, pattern, len, raster);
}
}
}
}
}
private static void drawHorOutline(int y, int lX, int hX, int pattern, int len, ERaster raster) {
int i = 0;
for (int x = lX; x <= hX; x++) {
if ((pattern & (1 << i)) != 0) {
raster.drawPoint(x, y);
}
i++;
if (i == len) {
i = 0;
}
}
}
private static void drawVerOutline(int x, int lY, int hY, int pattern, int len, ERaster raster) {
int i = 0;
for (int y = lY; y <= hY; y++) {
if ((pattern & (1 << i)) != 0) {
raster.drawPoint(x, y);
}
i++;
if (i == len) {
i = 0;
}
}
}
// /**
// * Method to draw a box on the off-screen buffer.
// */
// private void drawBox(int lX, int hX, int lY, int hY, byte[] layerBitMap, byte layerBitMask) {
// boxes++;
// int dx = hX - lX;
// int dy = hY - lY;
// int baseIndex = lY * width + lX;
// if (dx >= dy) {
// int baseIncr = width - (dx + 1);
// for (int i = dy; i >= 0; i--) {
// for (int j = dx; j >= 0; j--) {
// layerBitMap[baseIndex] |= layerBitMask;
// baseIndex += 1;
// }
// baseIndex += baseIncr;
// }
// } else {
// int baseIncr = 1 - (dy + 1) * width;
// for (int i = dx; i >= 0; i--) {
// for (int j = dy; j >= 0; j--) {
// layerBitMap[baseIndex] |= layerBitMask;
// baseIndex += width;
// }
// baseIndex += baseIncr;
// }
// }
// }
// ************************************* LINE DRAWING *************************************
/**
* Method to draw a line on the off-screen buffer.
*/
void drawLine(Point pt1, Point pt2, int texture, ERaster raster) {
// first clip the line
if (GenMath.clipLine(pt1, pt2, 0, sz.width - 1, 0, sz.height - 1)) {
return;
}
// now draw with the proper line type
switch (texture) {
case 0:
drawSolidLine(pt1.x, pt1.y, pt2.x, pt2.y, raster);
break;
case 1:
drawPatLine(pt1.x, pt1.y, pt2.x, pt2.y, 0x88, 8, raster);
break;
case 2:
drawPatLine(pt1.x, pt1.y, pt2.x, pt2.y, 0xE7, 8, raster);
break;
case 3:
drawThickLine(pt1.x, pt1.y, pt2.x, pt2.y, raster);
break;
}
}
private void drawCross(int cX, int cY, int size, ERaster raster) {
if (clipLY <= cY && cY <= clipHY) {
int lX = Math.max(clipLX, cX - size);
int hX = Math.min(clipHX, cX + size);
if (lX <= hX) {
raster.drawHorLine(cY, lX, hX);
}
}
if (clipLX <= cX && cX <= clipHX) {
int lY = Math.max(clipLY, cY - size);
int hY = Math.min(clipHY, cY + size);
if (lY <= hY) {
raster.drawVerLine(cX, lY, hY);
}
}
}
private void drawSolidLine(int x1, int y1, int x2, int y2, ERaster raster) {
// 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;
}
raster.drawPoint(x, y);
// draw line that increments along X
while (x < xend) {
x++;
if (d < 0) {
d += incr1;
} else {
y += yincr;
d += incr2;
}
raster.drawPoint(x, y);
}
} 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;
}
raster.drawPoint(x, y);
// draw line that increments along X
while (y < yend) {
y++;
if (d < 0) {
d += incr1;
} else {
x += xincr;
d += incr2;
}
raster.drawPoint(x, y);
}
}
}
// private void drawSolidLine(int x1, int y1, int x2, int y2, byte[] layerBitMap, byte layerBitMask) {
// solidLines++;
// // 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, yend;
// if (x1 <= x2) {
// x = x1; y = y1; yend = y2;
// } else {
// x = x2; y = y2; yend = y1;
// }
// int baseIndex = y * width + x;
// if (dy == 0) {
// // draw horizontal line
// for (int i = dx; i >= 0; i--)
// layerBitMap[baseIndex++] |= layerBitMask;
// } else {
// // draw line that increments along X
// int baseIncr = yend >= y ? 1 + width : 1 - width;
// for (int i = dx; i >= 0; i--) {
// layerBitMap[baseIndex] |= layerBitMask;
// if (d < 0) {
// d += incr1;
// baseIndex += 1;
// } else {
// d += incr2;
// baseIndex += baseIncr;
// }
// }
// }
// } else {
// // initialize for lines that increment along Y
// int incr1 = 2 * dx;
// int incr2 = 2 * (dx - dy);
// int d = incr2;
// int x, y, xend;
// if (y1 <= y2) {
// x = x1; y = y1; xend = x2;
// } else {
// x = x2; y = y2; xend = x1;
// }
// int baseIndex = y * width + x;
// if (dx == 0) {
// // draw vertical line
// for (int i = dy; i >= 0; i--) {
// layerBitMap[baseIndex] |= layerBitMask;
// baseIndex += width;
// }
// } else {
// int baseIncr = xend >= x ? width + 1 : width - 1;
// for (int i = dy; i >= 0; i--) {
// layerBitMap[baseIndex] |= layerBitMask;
// if (d < 0) {
// d += incr1;
// baseIndex += width;
// } else {
// d += incr2;
// baseIndex += baseIncr;
// }
// }
// }
// }
// }
private void drawOutline(int x1, int y1, int x2, int y2, int pattern, int len, ERaster raster) {
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, pattern, len, raster);
}
private void drawPatLine(int x1, int y1, int x2, int y2, int pattern, int len, ERaster raster) {
// 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;
}
raster.drawPoint(x, y);
// 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;
}
raster.drawPoint(x, y);
}
} 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;
}
raster.drawPoint(x, y);
// 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;
}
raster.drawPoint(x, y);
}
}
}
// private void drawPatLine(int x1, int y1, int x2, int y2, byte[] layerBitMap, byte layerBitMask, int pattern, int len) {
// patLines++;
// // 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, yend;
// if (x1 <= x2) {
// x = x1; y = y1; yend = y2;
// } else {
// x = x2; y = y2; yend = y1;
// }
// int baseIndex = y * width + x;
// if (dy == 0) {
// // draw horizontal line
// for (int i = 0; i <= dx; i++) {
// if ((pattern & (1 << (i&7))) != 0)
// layerBitMap[baseIndex++] |= layerBitMask;
// }
// } else {
// // draw line that increments along X
// int baseIncr = yend >= y ? 1 + width : 1 - width;
// for (int i = 0; i <= dx; i++) {
// if ((pattern & (1 << (i&7))) != 0)
// layerBitMap[baseIndex] |= layerBitMask;
// if (d < 0) {
// d += incr1;
// baseIndex += 1;
// } else {
// d += incr2;
// baseIndex += baseIncr;
// }
// }
// }
// } else {
// // initialize for lines that increment along Y
// int incr1 = 2 * dx;
// int incr2 = 2 * (dx - dy);
// int d = incr2;
// int x, y, xend;
// if (y1 <= y2) {
// x = x1; y = y1; xend = x2;
// } else {
// x = x2; y = y2; xend = x1;
// }
// int baseIndex = y * width + x;
// if (dx == 0) {
// // draw vertical line
// for (int i = 0; i <= dy; i++) {
// if ((pattern & (1 << (i&7))) != 0)
// layerBitMap[baseIndex] |= layerBitMask;
// baseIndex += width;
// }
// } else {
// int baseIncr = xend >= x ? width + 1 : width - 1;
// for (int i = 0; i <= dy; i++) {
// if ((pattern & (1 << (i&7))) != 0)
// layerBitMap[baseIndex] |= layerBitMask;
// if (d < 0) {
// d += incr1;
// baseIndex += width;
// } else {
// d += incr2;
// baseIndex += baseIncr;
// }
// }
// }
// }
// }
private void drawThickLine(int x1, int y1, int x2, int y2, ERaster raster) {
// 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, raster);
// draw line that increments along X
while (x < xend) {
x++;
if (d < 0) {
d += incr1;
} else {
y += yincr;
d += incr2;
}
drawThickPoint(x, y, raster);
}
} 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, raster);
// draw line that increments along X
while (y < yend) {
y++;
if (d < 0) {
d += incr1;
} else {
x += xincr;
d += incr2;
}
drawThickPoint(x, y, raster);
}
}
}
// private void drawThickLine(int x1, int y1, int x2, int y2, byte[] layerBitMap, byte layerBitMask) {
// thickLines++;
// // 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;
// if (x1 <= x2) {
// x = x1; y = y1; xend = x2; yend = y2;
// } else {
// x = x2; y = y2; xend = x1; yend = y1;
// }
// if (dy == 0) {
// // draw horizontal line
// drawBox(x, xend, Math.max(clipLY, y - 1), Math.min(clipHY, y + 1), layerBitMap, layerBitMask);
// if (x > clipLX)
// drawPoint(x - 1, y, layerBitMap, layerBitMask);
// if (xend < clipHX)
// drawPoint(xend + 1, y, layerBitMap, layerBitMask);
// } else {
// // draw line that increments along X
// int yIncr = yend >= y ? 1 : -1;
// for (int i = 0; i <= dx; i++) {
// drawThickPoint(x + i, y, layerBitMap, layerBitMask);
// if (d < 0) {
// d += incr1;
// } else {
// d += incr2;
// y += yIncr;
// }
// }
// }
// } 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;
// if (y1 <= y2) {
// x = x1; y = y1; xend = x2; yend = y2;
// } else {
// x = x2; y = y2; xend = x1; yend = x1;
// }
// if (dx == 0) {
// // draw vertical line
// drawBox(Math.max(clipLX, x - 1), Math.min(clipHX, x + 1), y, yend, layerBitMap, layerBitMask);
// if (y > clipLY)
// drawPoint(x, y - 1, layerBitMap, layerBitMask);
// if (yend < clipHY)
// drawPoint(x, yend + 1, layerBitMap, layerBitMask);
// } else {
// int xIncr = xend >= x ? 1 : - 1;
// for (int i = 0; i <= dy; i++) {
// drawThickPoint(x, y + i, layerBitMap, layerBitMask);
// if (d < 0) {
// d += incr1;
// } else {
// d += incr2;
// x += xIncr;
// }
// }
// }
// }
// }
// ************************************* POLYGON DRAWING *************************************
/**
* Method to draw a polygon on the off-screen buffer.
*/
void drawPolygon(Point[] points, ERaster raster) {
// 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;
raster.fillHorLine(ycur, j, k);
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
EGraphics.Outline o = raster.getOutline();
if (o == null) {
return;
}
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, o.getPattern(), o.getLen(), raster);
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, o.getPattern(), o.getLen(), raster);
}
}
}
}
// ************************************* TEXT DRAWING *************************************
/**
* Method to draw a text on the off-screen buffer
*/
public void drawText(Rectangle rect, Poly.Type style, TextDescriptor descript, String s, Color color, PrimitiveNode baseNode) {
// quit if string is null
if (s == null) {
return;
}
int len = s.length();
if (len == 0) {
return;
}
// get parameters
// if (color == null) color = new Color(0,0,0);
// int col = color.getRGB() & 0xFFFFFF;
// 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;
if (descript != null) {
rotation = descript.getRotation().getIndex();
int colorIndex = descript.getColorIndex();
if (colorIndex != 0) {
Color full = EGraphics.getColorFromIndex(colorIndex, null);
if (full != null) {
color = full;
}
// if (full != null) col = full.getRGB() & 0xFFFFFF;
}
double dSize = descript.getTrueSize(scale, wnd);
size = Math.min((int) dSize, MAXIMUMTEXTSIZE);
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;
}
}
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
RenderTextInfo renderInfo = new RenderTextInfo(color, baseNode);
if (!renderInfo.buildInfo(s, fontName, size, italic, bold, underline, rect, style, rotation)) {
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;
}
// greeked box in opaque area
if (lX > hX || lY > hY) {
return;
}
greekTextList.add(new GreekTextInfo(lX, hX, lY, hY, color, baseNode));
// 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;
// }
// }
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
renderTextList.add(renderInfo);
// renderInfo.draw();
}
private static class TextInfo {
private Color color;
private PrimitiveNode baseNode;
private TextInfo(Color color, PrimitiveNode baseNode) {
if (color == null) {
throw new NullPointerException();
}
this.color = color;
this.baseNode = baseNode;
}
boolean isDrawn(Graphics2D g, LayerVisibility lv) {
if (baseNode != null && !lv.isVisible(baseNode)) {
return false;
}
g.setColor(color);
return true;
}
}
private static class RenderTextInfo extends TextInfo {
private GlyphVector gv;
private LineMetrics lm;
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 int rotation;
private Rectangle rect;
private int offX, offY;
private RenderTextInfo(Color color, PrimitiveNode baseNode) {
super(color, baseNode);
}
private boolean buildInfo(String msg, String fontName, int tSize, boolean italic, boolean bold, boolean underline,
Rectangle probableBoxedBounds, Poly.Type style, int rotation) {
Font font = getFont(msg, fontName, tSize, italic, bold, underline);
this.underline = underline;
this.rotation = rotation;
rect = (Rectangle) probableBoxedBounds.clone();
// 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();
int boxedHeight = (int) probableBoxedBounds.getHeight();
// 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);
Point2D 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);
}
int textWidth = (int) rasBounds.getWidth();
int textHeight = (int) rasBounds.getHeight();
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;
}
return true;
}
private void draw(Graphics2D g, LayerVisibility lv) {
if (!isDrawn(g, lv)) {
return;
}
int width = (int) rasBounds.getWidth();
int height = (int) rasBounds.getHeight();
if (rotation == 0) {
int atX = (int) rect.getCenterX() + offX;
int atY = (int) rect.getCenterY() + offY;
g.drawGlyphVector(gv, (float) (atX - rasBounds.getX()), atY + (lm.getAscent() - lm.getLeading()));
if (underline) {
g.drawLine(atX, atY + height - 1, atX + width - 1, atY + height - 1);
}
} else {
AffineTransform saveAT = g.getTransform();
g.translate(rect.getCenterX(), rect.getCenterY());
g.rotate(-rotation * Math.PI / 2);
g.drawGlyphVector(gv, (float) (offX - rasBounds.getX()), offY + (lm.getAscent() - lm.getLeading()));
if (underline) {
g.drawLine(offX, offY + height - 1, offX + width - 1, offY + height - 1);
}
g.setTransform(saveAT);
}
}
}
private class GreekTextInfo extends TextInfo {
int lX, hX, lY, hY;
private GreekTextInfo(int lX, int hX, int lY, int hY, Color color, PrimitiveNode baseNode) {
super(color, baseNode);
this.lX = lX;
this.hX = hX;
this.lY = lY;
this.hY = hY;
}
private void draw(Graphics2D g, LayerVisibility lv) {
if (!isDrawn(g, lv)) {
return;
}
g.drawLine(lX, lY, hX, hY);
}
}
private class CrossTextInfo extends TextInfo {
int x, y;
private CrossTextInfo(int x, int y, Color color, PrimitiveNode baseNode) {
super(color, baseNode);
this.x = x;
this.y = y;
}
private void draw(Graphics2D g, LayerVisibility lv) {
if (!isDrawn(g, lv)) {
return;
}
g.drawLine(x - 3, y, x + 3, y);
g.drawLine(x, y - 3, x, y + 3);
}
}
/**
* 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);
}
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, ERaster raster) {
// get parameters
int radius = (int) center.distance(edge);
// 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) {
raster.drawPoint(center.x + x, center.y + y);
raster.drawPoint(center.x - x, center.y + y);
raster.drawPoint(center.x + x, center.y - y);
raster.drawPoint(center.x - x, center.y - y);
raster.drawPoint(center.x + y, center.y + x);
raster.drawPoint(center.x - y, center.y + x);
raster.drawPoint(center.x + y, center.y - x);
raster.drawPoint(center.x - y, center.y - x);
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) {
raster.drawPoint(thisx, thisy);
}
thisx = center.x - x;
if (thisx >= 0 && thisx < sz.width) {
raster.drawPoint(thisx, thisy);
}
}
thisy = center.y - y;
if (thisy >= 0 && thisy < sz.height) {
int thisx = center.x + x;
if (thisx >= 0 && thisx < sz.width) {
raster.drawPoint(thisx, thisy);
}
thisx = center.x - x;
if (thisx >= 0 && thisx < sz.width) {
raster.drawPoint(thisx, thisy);
}
}
thisy = center.y + x;
if (thisy >= 0 && thisy < sz.height) {
int thisx = center.x + y;
if (thisx >= 0 && thisx < sz.width) {
raster.drawPoint(thisx, thisy);
}
thisx = center.x - y;
if (thisx >= 0 && thisx < sz.width) {
raster.drawPoint(thisx, thisy);
}
}
thisy = center.y - x;
if (thisy >= 0 && thisy < sz.height) {
int thisx = center.x + y;
if (thisx >= 0 && thisx < sz.width) {
raster.drawPoint(thisx, thisy);
}
thisx = center.x - y;
if (thisx >= 0 && thisx < sz.width) {
raster.drawPoint(thisx, thisy);
}
}
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, ERaster raster) {
// get parameters
int radius = (int) center.distance(edge);
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, raster);
}
thisx = center.x - x;
if (thisx >= 0 && thisx < sz.width) {
drawThickPoint(thisx, thisy, raster);
}
}
thisy = center.y - y;
if (thisy >= 0 && thisy < sz.height) {
int thisx = center.x + x;
if (thisx >= 0 && thisx < sz.width) {
drawThickPoint(thisx, thisy, raster);
}
thisx = center.x - x;
if (thisx >= 0 && thisx < sz.width) {
drawThickPoint(thisx, thisy, raster);
}
}
thisy = center.y + x;
if (thisy >= 0 && thisy < sz.height) {
int thisx = center.x + y;
if (thisx >= 0 && thisx < sz.width) {
drawThickPoint(thisx, thisy, raster);
}
thisx = center.x - y;
if (thisx >= 0 && thisx < sz.width) {
drawThickPoint(thisx, thisy, raster);
}
}
thisy = center.y - x;
if (thisy >= 0 && thisy < sz.height) {
int thisx = center.x + y;
if (thisx >= 0 && thisx < sz.width) {
drawThickPoint(thisx, thisy, raster);
}
thisx = center.x - y;
if (thisx >= 0 && thisx < sz.width) {
drawThickPoint(thisx, thisy, raster);
}
}
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, ERaster raster) {
if (thisy < clipLY || thisy > clipHY) {
return;
}
if (startx < clipLX) {
startx = clipLX;
}
if (endx > clipHX) {
endx = clipHX;
}
if (startx > endx) {
return;
}
raster.fillHorLine(thisy, startx, endx);
}
/**
* Method to draw a filled-in circle of radius "radius" on the off-screen buffer
*/
void drawDisc(Point center, Point edge, ERaster raster) {
// get parameters
int radius = (int) center.distance(edge);
EGraphics.Outline o = raster.getOutline();
if (o != null) {
drawCircle(center, edge, raster);
}
// 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++) {
raster.drawPoint(x, y);
}
}
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, raster);
drawDiscRow(center.y - y, center.x - x, center.x + x, raster);
drawDiscRow(center.y + x, center.x - y, center.x + y, raster);
drawDiscRow(center.y - x, center.x - y, center.x + y, raster);
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 ERaster arcRaster;
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 < clipLX || x > clipHX || y < clipLY || y > clipHY) {
return;
}
if (arcThick) {
drawThickPoint(x, y, arcRaster);
} else {
arcRaster.drawPoint(x, y);
}
}
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, ERaster raster) {
// ignore tiny arcs
if (p1.x == p2.x && p1.y == p2.y) {
return;
}
// get parameters
arcRaster = raster;
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 *************************************
// private void drawPoint(int x, int y, byte[] layerBitMap, byte layerBitMask) {
// layerBitMap[y * width + x] |= layerBitMask;
// }
private void drawThickPoint(int x, int y, ERaster raster) {
raster.drawPoint(x, y);
if (x > clipLX) {
raster.drawPoint(x - 1, y);
}
if (x < clipHX) {
raster.drawPoint(x + 1, y);
}
if (y > clipLY) {
raster.drawPoint(x, y - 1);
}
if (y < sz.height - 1) {
raster.drawPoint(x, y + 1);
}
}
// private void drawThickPoint(int x, int y, byte[] layerBitMap, byte layerBitMask) {
// int baseIndex = y * sz.width + x;
// layerBitMap[baseIndex] |= layerBitMask;
// if (x > clipLX) {
// layerBitMap[baseIndex - 1] |= layerBitMask;
// }
// if (x < clipHX) {
// layerBitMap[baseIndex + 1] |= layerBitMask;
// }
// if (y > clipLY) {
// layerBitMap[baseIndex - width] |= layerBitMask;
// }
// if (y < sz.height - 1) {
// layerBitMap[baseIndex + width] |= layerBitMask;
// }
// }
/**
* 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);
}
}