myJanee.com Photoshop ResourcesComplete Site MapAbout JaneeJanee's Art ChallengePut me to work for you! myJanee.com Graphic CreationsJanee's Photoshop TutorialsPhotoshopmyJanee.com Home
myJanee.com --> Photoshop Resources --> Janee's Tutorials -> this tutorial
/* Applet to construct knots */
/* Knotwork.java */
/* Copyright 1998, Andrew D. Birrell */
/* This program and source code are available free, under the terms of the GNU
general public license. Use at your own risk! The GNU general public
license is available at http://www.gnu.org/copyleft/gpl.html */

/* An applet to design Celtic knots */

/* Requires an image with the knot fragments. The URL for the image is
provided as the "image" applet parameter. */

import java.awt.*;
import java.awt.image.*;
import java.applet.*;
import java.net.*;

public class Knotwork extends Applet {

/* Shape of the knot. Note that sqSize must match the downloaded image */
/* All three of these are normally overridden by applet parameters. */
int sqSize = 10; /* Size of the unit square */
int xSquares = 10; /* Squares wide, must be even */
int ySquares = 10; /* Squares high, must be even */

/* The downloaded image has the following shapes in its first row: */
static final int sh_TLBR = 0; /* shape: straight diagonal TL */
static final int sh_BLTR = 1; /* shape: straight diagonal BL */
static final int sh_TB = 2; /* shape: straight top -> bottom */
static final int sh_LR = 3; /* shape: straight left -> right */
static final int sh_TLB = 4; /* shape: curve TL -> bottom */
static final int sh_TLR = 5; /* shape: curve TL -> right */
static final int sh_BRT = 6; /* shape: curve BR -> top */
static final int sh_BRL = 7; /* shape: curve BR -> left */
static final int sh_BLT = 8; /* shape: curve BL -> top */
static final int sh_BLR = 9; /* shape: curve BL -> right */
static final int sh_TRB = 10; /* shape: curve TR -> bottom */
static final int sh_TRL = 11; /* shape: curve TR -> left */
static final int sh_TL = 12; /* shape: arc top -> left */
static final int sh_BR = 13; /* shape: arc bottom -> right */
static final int sh_BL = 14; /* shape: arc bottom -> left */
static final int sh_TR = 15; /* shape: arc top -> right */
/* The second row is variants that go underneath at intersections */
static final int sh_uTLBR = 16; /* shape: straight diagonal TL */
static final int sh_uBLTR = 17; /* shape: straight diagonal BL */
static final int sh_uTB = 18; /* shape: straight top -> bottom */
static final int sh_uLR = 19; /* shape: straight left -> right */
static final int sh_uTLB = 20; /* shape: curve TL -> bottom */
static final int sh_uTLR = 21; /* shape: curve TL -> right */
static final int sh_uBRT = 22; /* shape: curve BR -> top */
static final int sh_uBRL = 23; /* shape: curve BR -> left */
static final int sh_uBLT = 24; /* shape: curve BL -> top */
static final int sh_uBLR = 25; /* shape: curve BL -> right */
static final int sh_uTRB = 26; /* shape: curve TR -> bottom */
static final int sh_uTRL = 27; /* shape: curve TR -> left */
static final int sh_uTL = 28; /* shape: arc top -> left */
static final int sh_uBR = 29; /* shape: arc bottom -> right */
static final int sh_uBL = 30; /* shape: arc bottom -> left */
static final int sh_uTR = 31; /* shape: arc top -> right */

/* State of cuts */
static final int cut_none = 0; /* no cut, normal intersection */
static final int cut_hor = 1; /* horizontal cut */
static final int cut_ver = 2; /* vertical cut */

Image[] images = null; /* component images for squares */
Image theImage = null; /* the downloaded image */
MediaTracker tracker = null;
int[] shapes = null; /* Cached shape in each square */

/* The cut points are at top-left of the squares [x,y] where x+y is odd.

The state of cut/intersection points is held in the "cuts" array,
indexed as if there were xSquares/2+1 entries per row (even although
there are actually 1 less in even rows).

Note that there's an asymmetry in our data structures between x and y
directions. This is an arbitrary choice - we could have chosen to pack
the "cuts" array with either axis first.
*/

int xCuts; /* cuts per row in the x direction */
int[] cuts;

/* The over-under rule is that lines going diagonally down-right pass
underneath in even rows, and over in odd rows. This rule always
produces properly woven knots.

We treat the cells in groups of four, named by their position in the
following diagram, where the square at the top-left is [x,y] with x
and y both even, and where "o" is an intersection or cut point.

-- o --
| TL TR |
| |
o o
| BL BR |
| |
-- o --

The following arrays are indexed by the value of the cuts adjacent to a
square. The index is the adjacent even-row cut plus 3 * the adjacent
odd-row cut. The result is the shape for the cell, based on the cell's
position in the above diagram. This deals with shape of thread fragment,
and with the over-under choice. */

/* none hor ver */
int[] tlShapes = { sh_BLTR, sh_uBLR, sh_uBLT,/* none */
sh_TRL, sh_LR, sh_TL, /* hor */
sh_TRB, sh_BR, sh_TB}; /* ver */
int[] trShapes = { sh_uTLBR, sh_BRL, sh_BRT, /* none */
sh_uTLR, sh_LR, sh_TR, /* hor */
sh_uTLB, sh_BL, sh_TB}; /* ver */
int[] blShapes = { sh_TLBR, sh_TLR, sh_TLB, /* none */
sh_uBRL, sh_LR, sh_BL, /* hor */
sh_uBRT, sh_TR, sh_TB}; /* ver */
int[] brShapes = { sh_uBLTR, sh_uTRL, sh_uTRB,/* none */
sh_BLR, sh_LR, sh_BR, /* hor */
sh_BLT, sh_TL, sh_TB}; /* ver */

private void computeShape(int x, int y) {
/* Compute appropriate shape for square [x,y], looking at adjacent cuts */
int evenRow = cuts[((y+1)/2) * 2 * xCuts + x/2];
int oddRow = cuts[((y/2)*2+1) * xCuts + (x+1)/2];
int s = evenRow + oddRow*3;
shapes[y*xSquares+x] = y%2==0 ? ( x%2==0 ? tlShapes[s] : trShapes[s] ) :
( x%2==0 ? blShapes[s] : brShapes[s] );
}

private void initKnot() {
/* Initialize the knot to a simple weave - no cuts except at perimeter */
for (int n = 0; n < cuts.length; n++) {
cuts[n] = cut_none;
}
/* Place fixed cuts around the perimeter */
for (int y = 0; y <= ySquares; y++) {
if (y%2 == 1) {
/* cut at left and right edges in odd rows */
int x0 = y * xCuts;
cuts[x0] = cut_ver;
cuts[x0+xCuts-1] = cut_ver;
}
}
int xLast = ySquares * xCuts;
for (int x = 0; x < xCuts; x++) {
cuts[x] = cut_hor;
cuts[xLast+x] = cut_hor;
}
/* Compute the initial shapes */
for (int y = 0; y < ySquares; y++) {
for (int x = 0; x < xSquares; x++) {
computeShape(x,y);
}
}
}

static final int inset = 3; /* width of decorative border */

private void paintSquare(Graphics g, int nx, int ny) {
/* Paint a square of the knot */
int shape = shapes[ny*xSquares+nx];
g.drawImage(images[shape], inset+nx*sqSize, inset+ny*sqSize, this);
}

private void paintCut(Graphics g, int cut, int nx, int ny) {
/* Paint a given cut, which is centered at top-left of given cell */
int x = inset+nx*sqSize;
int y = inset+ny*sqSize;
int halfLen = sqSize / 4;
if (cut == cut_hor) {
g.drawLine(x-halfLen, y, x+halfLen, y);
} else if (cut == cut_ver) {
g.drawLine(x, y-halfLen, x, y+halfLen);
}
}

public synchronized void paint(Graphics g) {
Dimension d = size();
Rectangle clip = g.getClipRect();
g.setColor(getBackground());
g.drawRect(1, 1, d.width-3, d.height-3);
g.setColor(getForeground());
g.drawRect(0, 0, d.width-1, d.height-1);
g.drawRect(2, 2, d.width-5, d.height-5);
if (tracker==null || !tracker.checkAll(true)) {
String s = tracker==null ? "No image!" : "Working on it ...";
int sWidth = (g.getFontMetrics()).stringWidth(s);
g.drawString(s, (d.width-sWidth)/2, d.height/2);
if (tracker!=null) repaint(250, inset, inset,
d.width-2*inset, d.height-2*inset);
} else {
if (images == null) {
images = new Image[32];
for (int row = 0; row < 2; row++) {
for (int i = 0; i<16; i++) {
images[row*16+i] = createImage(sqSize, sqSize);
Graphics imageG = images[row*16+i].getGraphics();
imageG.drawImage(theImage, -i*sqSize, -row*sqSize, this);
}
}
}
/* For efficiency, paint only squares that intersect with the cliprect */
int firstX = Math.max((clip.x-inset) / sqSize, 0);
int endX = Math.min((clip.x-inset+clip.width+sqSize)/sqSize, xSquares);
int firstY = Math.max((clip.y-inset) / sqSize, 0);
int endY = Math.min((clip.y-inset+clip.height+sqSize)/sqSize, ySquares);
for (int y = firstY; y < endY; y++) {
for (int x = firstX; x < endX; x++) {
paintSquare(g, x, y);
}
}
if (true) {
/* Paint the cuts, excluding those on the edges */
g.setColor(Color.blue);
for (int y = 1; y < ySquares; y++) {
int x0 = y * xCuts;
for (int x = 1; x < xSquares; x++) {
if ( (x+y)%2 == 1 ) {
paintCut(g, cuts[x0+x/2], x, y);
};
}
}
}
}
}

public void update(Graphics g) {
paint(g);
}

public boolean mouseDown(Event evt, int xCoord, int yCoord) {
/* Respond to mouse-down by letting the user adjust the cut-points */
/* Round to index of square whose TL the click was closest to. */
int x = (xCoord - inset + sqSize/2) / sqSize;
int y = (yCoord - inset + sqSize/2) / sqSize;
if ( (x+y)%2 == 1 && x > 0 && x < xSquares && y > 0 && y < ySquares) {
/* (x+y) is odd, so the closest square is a mutable cut point */
int cutIndex = y * xCuts + x/2; /* index of the cut point */
cuts[cutIndex] = (cuts[cutIndex] + 1) % 3;
computeShape(x-1, y-1);
computeShape(x, y-1);
computeShape(x-1, y);
computeShape(x, y);
repaint(inset+(x-1)*sqSize, inset+(y-1)*sqSize, 2*sqSize, 2*sqSize);
}
return true;
}

public synchronized void init() {
/* Initialize, and arrange to fetch the image */
try {
String cStr = getParameter("COLUMNS");
String rStr = getParameter("ROWS");
String pStr = getParameter("PIXELS");
if (cStr != null) xSquares = Integer.parseInt(cStr);
if (rStr != null) ySquares = Integer.parseInt(rStr);
if (pStr != null) sqSize = Integer.parseInt(pStr);
} catch (NumberFormatException e) {
System.err.println("Missing or malformed applet parameter");
}
URL u = null;
String urlStr = getParameter("IMAGE");
if (urlStr != null) {
try {
u = new URL(getDocumentBase(), urlStr);
} catch ( MalformedURLException e) {
}
}
xCuts = xSquares/2 + 1;
shapes = new int[xSquares*ySquares];
cuts = new int[(ySquares+1) * xCuts];
setBackground(Color.white);
setForeground(new Color(153,153,153));
if (u!=null) {
tracker = new MediaTracker(this);
theImage = getAppletContext().getImage(u);
tracker.addImage(theImage, 0);
}
initKnot();
}

public synchronized void start() {
}

public synchronized void stop() {
}

public synchronized void destroy() {
}

}
Have fun!

Janee

  RETURN to Janee's Tutorial Index

email me!

Ask tutorial questions in the myJanee.community:

myJanee.com Message Board

sign my guestbook!

All material in this site is ©2001-2003 by myJanee.com Graphic Creations. No part of it may be used without my written permission. If you have questions or comments about this site or its construction, contact Janee at myJanee.com Graphic Creations, 7193 W Gifford Rd, Bloomington, Indiana, USA 47403 or by email.