 |
April 2006 Archives
Hit series powered by Java, studio execs deny
Posted by kirillcool on April 23, 2006 at 03:45 AM | Permalink
| Comments (2)
Now that major drama series are about to be available for legal downloads, rumors about the behind-the-scenes engines start spreading. Here is a sample class behind Fox megahit, "24". Note that the code below may change from time to time and does not represent the quality of the final product.
package javax.ctu;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import javax.swing.Timer;
/**
* CTU agent.
*/
public class Agent implements PropertyChangeListener {
/**
* Name of <code>this</code> agent.
*/
protected String name;
/**
* Property support.
*/
protected List<PropertyChangeListener> propertySupport;
/**
* Can contain a single entity.
*/
public static interface BodyBag<T> extends Wrapper<T> {
}
/**
* Enum for the available bodily operations.
*/
public enum BasicBodilyOperation {
EAT, DRINK, WASH_HANDS, SLEEP, PEE, YAWN, BLINK, PERSPIRATE
}
/**
* Sets new value for the agent name.
*
* @param name
* New value for the agent name.
*/
public void setName(String name) {
String oldName = this.name;
this.name = name;
PropertyChangeEvent event = new PropertyChangeEvent(this, "name",
oldName, name);
for (PropertyChangeListener pcl : propertySupport)
pcl.propertyChange(event);
}
/*
* (non-Javadoc)
*
* @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
*/
public void propertyChange(final PropertyChangeEvent evt) {
if ("name".equals(evt.getPropertyName())) {
if (evt.getOldValue() == null) {
// This is the first time the agent name becomes known
if ("Curtis".equals(evt.getNewValue())) {
// defer to later stage
} else {
// choose random time in next five minutes
Timer timer = new Timer((int) (5 * 60 * 1000 * Math
.random()), new ActionListener() {
public void actionPerformed(ActionEvent e) {
Agent agent = (Agent) evt.getSource();
agent.dispose();
}
});
timer.setRepeats(false);
timer.start();
}
}
}
}
/**
* Disposes of this agent. See Bloch's Effective Java chapter 6 on why we
* don't use finalizers.
*/
public void dispose() {
if ("Jack".equals(name) || "Kim".equals(name)
// || "Tony".equals(name) || "Michelle".equals(name)
|| "Chloe".equals(name)) {
throw new UnsupportedOperationException("Agent '" + this.name
+ "' can not be killed due to rating concerns");
}
if (this.name != null) {
// How long this agent has been known by name - take a small
// pause accordingly letting the female audience weep a little.
Thread.currentThread().sleep(1000 * Production.getStatus(this));
}
BodyBag<Agent> bb = this.getBodyBag();
bb.add(this);
}
/**
* Tests if <code>this</code> agent is loyal to CTU.
*
* @return <code>true</code> if <code>this</code> agent is loyal to CTU,
* <code>false</code> otherwise.
* @deprecated As of the latest release, use {@link #isLoyalToJack()}
* instead.
*/
@Deprecated
public boolean isLoyalToCTU() {
// Commented when Nina got too old for the target 24-39 male audience.
// return true;
throw new IrrelevantOperationException("Call isLoyalToJack() instead");
}
/**
* Tests if <code>this</code> agent is loyal to Jack.
*
* @return <code>true</code> if <code>this</code> agent is loyal to
* Jack, <code>false</code> otherwise.
*/
public boolean isLoyalToJack() {
if ("Jack".equals(this.name)) {
// may revise later when we run out of plausible ideas.
return true;
}
if ("Curtis".equals(this.name)) {
// the least loveable by the target audience
return false;
}
if ("Chloe".equals(this.name)) {
// not a real agent and has some imaginary computer skills
return true;
}
double random = Math.random();
int hoursTillSeasonEnd = Integer.parseInt(Production.getTitle())
- Production.getEpisodeNumber();
// Make more twists towards season end
return (random / hoursTillSeasonEnd) > 0.1;
}
/**
* Checks whether the specified bodily operation is supported.
*
* @param operation
* Bodily operation.
* @return <code>true</code> if the specified operation is supported,
* <code>false</code> otherwise.
*/
public boolean supports(BasicBodilyOperation operation) {
if (operation == BasicBodilyOperation.BLINK)
return !"Jack".equals(this.name);
return false;
}
/**
* Returns the name of the love interest of <code>this</code> agent.
*
* @return The name of the love interest of <code>this</code> agent.
* @throws IrrelevantOperationException
* if irrelevant.
*/
public String getLoveInterestName() {
if ("Jack".equals(this.name)) {
// return "Nina";
// return "Teri";
// return "Kate";
// return "Audrey";
return "Work";
}
if ("Tony".equals(this.name)) {
// TODO - remove this piece of dead code
return "Michelle";
}
if ("Michelle".equals(this.name)) {
// TODO - remove this piece of dead code
return "Tony";
}
throw new IrrelevantOperationException("No time for this");
}
/**
* Returns the locale of the current foe. Serves up as an excuse for proper
* lack of internationalization.
*
* @return The locale of the current foe.
*/
public Locale getCurrentFoeLocale() {
// Locale result = new Locale("sr", "CS");
// Locale result = new Locale("es", "MX");
// Locale result = new Locale("fa", "IR");
// Locale result = new Locale("zh", "CN");
// Locale result = new Locale("ru", "RU");
return this.getCountry().getLeader().getLocale();
}
/**
* To be implemented in native code. What this really means is that we are
* not going to go into such insignificant details. OS-specific
* implementation may borrow details from division implementation of
* {@link Nikita}.
*
* @return Division of <code>this</code> agent.
*/
public native Division getDivision();
}
Spicing up your JTabbedPane - part IV
Posted by kirillcool on April 21, 2006 at 02:08 AM | Permalink
| Comments (2)
This is the fourth part of the series that describes the additional capabilities that you can get on your tabbed panes once you start using the Substance look-and-feel.
- The first part described close buttons and animation on modified tabs
- The second part described vetoable close buttons and vertical tabs.
- The third part described single-click close of multiple tabs and ustom alignment of tab texts on left and right placement.
This entry describes new features available in the next version of Substance look-and-feel (code-named Firenze, currently available in core code freeze - release candidate planned April 30).
The first feature is hover preview popup of tabs and is borrowed from Opera 9.0 beta browser. Move the mouse over a tab, wait for 2 seconds (configurable) and you will be shown a small preview popup window with the contents of the tab:
Later part of this entry describes how to customize the popup delay, popup dimension, popup existence itself (disable this feature on app-specific basis for some tabs) and preview painting (to provide app-specific preview painting logic).
The second feature is tab overview dialog and borrows from ideas from both IE7 and various Firefox extensions. First, the overview of existing functionality in the above browsers:
- QuickTabs in IE7 (currently in Beta2). When more there are at least two open tabs, a button is displayed in the left portion of the tab strip. When you click on it, the contents of the current tab are overlayed with a tab overview, showing a thumbnail for each open tab.
- Tab Sidebar Firefox extension. Opens a side bar (much like Office Taskbar) with single column thumbnail view of all open tabs.
- Reveal Firefox extension. When F2 or Alt-` is clicked, a horizontally scrollable two-row preview is shown. This preview allows tab rearranging as well as many other operations.
- foxPose Firefox extension. Adds a button in the bottom left corner of the window. When the button is clicked, a new tab with thumbnails is added to the browser.
- Tab Catalog Firefox extension. Adds a button in the right portion of the menu bar. When mouse hovers over this button, an overview window is shown.
- Ctrl Tab Firefox extension. When Ctrl-Tab is pressed, a three-window view allows switching between open tabs (like Alt-Tab in Windows Vista).
The current drop of Substance 2.3 allows adding the overview functionality to your tabbed panes. When enabled, the overview button is shown alongside your tabs (depending on the tab placement and orientation):
When this button is clicked, an overview dialog is shown. This dialog contains thumbnails for all tabs. Clicking on a thumbnail close the overview dialog and selects the corresponding tab:
The overview thumbnails are generated in a separate thread and faded-in as soon as they are available. This keeps your application responsive and user experience pleasant (hopefully). Below I describe how to customize the dialog dimension and location, overview existence itself (disable this feature on app-specific basis for some tabbed panes) and preview painting (to provide app-specific preview painting logic).
In order to enable the above functionality, all you need to do is to put the following client property on your tabbed pane(s) or globally on the UIManager:
jtp.putClientProperty(SubstanceLookAndFeel.TABBED_PANE_PREVIEW_PAINTER,
new DefaultTabPreviewPainter());
The DefaultTabPreviewPainter is the default implementation of the tab preview and overview logic. Depending on your need, you can easily provide you own specific logic for all or some of the functionality. Here is a list of all customizable features:
/**
* Base class for tab preview painters.
*
* @author Kirill Grouchnikov
*/
public abstract class TabPreviewPainter {
/**
* Draws a tab preview on the specified graphics.
*
* @param tabPane
* Tabbed pane.
* @param tabIndex
* tabIndex Tab index for the preview paint.
* @param g
* Graphics context.
* @param x
* X coordinate of the preview area.
* @param y
* Y coordinate of the preview area.
* @param w
* Width of the preview area.
* @param h
* Height of the preview area.
*/
public void previewTab(JTabbedPane tabPane, int tabIndex, Graphics g,
int x, int y, int w, int h) {
}
/**
* Checks whether the specified tab component is previewable.
*
* @param tabPane
* Tabbed pane.
* @param tabIndex
* Tab index for the preview paint.
* @return <code>true</code> if the specified tab component is
* previewable, <code>false</code> otherwise.
*/
public boolean hasPreview(JTabbedPane tabPane, int tabIndex) {
return false;
}
/**
* Returns the screen bounds of the tab preview dialog window.
*
* @param tabPane
* Tabbed pane.
* @return Screen bounds of the preview dialog window of the specified
* tabbed pane.
*/
public Rectangle getPreviewDialogScreenBounds(JTabbedPane tabPane) {
Rectangle tabPaneBounds = tabPane.getBounds();
Point tabPaneScreenLoc = tabPane.getLocationOnScreen();
return new Rectangle(tabPaneScreenLoc.x, tabPaneScreenLoc.y,
tabPaneBounds.width, tabPaneBounds.height);
}
/**
* Returns the owner of the overview dialog of the specified tabbed pane. If
* this function retuns a non-<code>null</code> value, the overview
* dialog will be modal for the corresponding frame.
*
* @param tabPane
* Tabbed pane.
* @return If not <code>null</code>, the overview dialog for the
* specified tabbed pane will be modal for the corresponding frame.
*/
public JFrame getModalOwner(JTabbedPane tabPane) {
return null;
}
/**
* Checks whether the specified tabbed pane has an overview dialog.
*
* @param tabPane
* Tabbed pane.
* @return <code>true</code> if the specified tabbed pane has an overview
* dialog, <code>false</code> otherwise.
*/
public boolean hasOverviewDialog(JTabbedPane tabPane) {
return false;
}
/**
* Checks whether the specified tabbed pane has a preview window for the
* specified tab.
*
* @param tabPane
* Tabbed pane.
* @param tabIndex
* Tab index.
* @return <code>true</code> if the specified tabbed pane has a preview
* window for the specified tab, <code>false</code> otherwise.
*/
public boolean hasPreviewWindow(JTabbedPane tabPane, int tabIndex) {
return false;
}
/**
* Returns the dimension for the tab preview window.
*
* @param tabPane
* Tabbed pane.
* @param tabIndex
* Tab index.
* @return Dimension of the tab preview window for the specified tab in the
* specified tabbed pane.
*/
public Dimension getPreviewWindowDimension(JTabbedPane tabPane, int tabIndex) {
return new Dimension(300, 200);
}
/**
* Returns extra delay (in milliseconds) for showing the tab preview window.
* The base delay is 2000 milliseconds (2 seconds). This function must
* return a non-negative value.
*
* @param tabPane
* Tabbed pane.
* @param tabIndex
* Tab index.
* @return Non-negative extra delay (in milliseconds) for showing the tab
* preview window.
*/
public int getPreviewWindowExtraDelay(JTabbedPane tabPane, int tabIndex) {
return 0;
}
}
The default logic provides implementations of the following four functions (note the implementation of previewTab that simply scales down the current state of the tab - thanks to Romain for contributing the efficient thumbnail computation code):
/**
* Default implementation of the tab preview painter. The tab preview is a
* scaled-down (as necessary) thumbnail of the relevant tab.
*
* @author Kirill Grouchnikov
*/
public class DefaultTabPreviewPainter extends TabPreviewPainter {
/*
* (non-Javadoc)
*
* @see org.jvnet.substance.tabbed.TabPreviewPainter#hasPreview(javax.swing.JTabbedPane,
* int)
*/
@Override
public boolean hasPreview(JTabbedPane tabPane, int tabIndex) {
return (tabPane.getComponentAt(tabIndex) != null);
}
/*
* (non-Javadoc)
*
* @see org.jvnet.substance.tabbed.TabPreviewPainter#previewTab(javax.swing.JTabbedPane,
* int, java.awt.Graphics, int, int, int, int)
*/
@Override
public void previewTab(JTabbedPane tabPane, int tabIndex, Graphics g,
int x, int y, int w, int h) {
Component tabComponent = tabPane.getComponentAt(tabIndex);
if (tabComponent == null)
return;
// if (!tabComponent.isShowing())
// return;
int compWidth = tabComponent.getWidth();
int compHeight = tabComponent.getHeight();
if ((compWidth > 0) && (compHeight > 0)) {
// draw tab component
BufferedImage tempCanvas = new BufferedImage(compWidth, compHeight,
BufferedImage.TYPE_INT_ARGB);
Graphics tempCanvasGraphics = tempCanvas.getGraphics();
tabComponent.paint(tempCanvasGraphics);
// check if need to scale down
double coef = Math.min((double) w / (double) compWidth, (double) h
/ (double) compHeight);
if (coef < 1.0) {
int sdWidth = (int) (coef * compWidth);
int sdHeight = (int) (coef * compHeight);
int dx = (w - sdWidth) / 2;
int dy = (h - sdHeight) / 2;
g.drawImage(SubstanceCoreUtilities.createThumbnail(tempCanvas,
sdWidth), dx, dy, null);
} else {
// System.out.println("Putting " + frame.hashCode() + "
// -> " + snapshot.hashCode());
g.drawImage(tempCanvas, 0, 0, null);
}
}
}
/*
* (non-Javadoc)
*
* @see org.jvnet.substance.tabbed.TabPreviewPainter#hasPreviewWindow(javax.swing.JTabbedPane,
* int)
*/
@Override
public boolean hasPreviewWindow(JTabbedPane tabPane, int tabIndex) {
return true;
}
/*
* (non-Javadoc)
*
* @see org.jvnet.substance.tabbed.TabPreviewPainter#hasOverviewDialog(javax.swing.JTabbedPane)
*/
@Override
public boolean hasOverviewDialog(JTabbedPane tabPane) {
return true;
}
}
The features describe above were the last to be added before the core feature freeze for the next version of Substance LAF (RC on April 30, release on May 14). You are welcome to download the jars and play with the test application or run the WebStart demo application. Note that the first time you run the tab overview dialog it will be a bit slow due to the multi-colored buttons in the second tab. After that, the subsequent operations will be much faster (due to image caching in Substance).
In addition, i would like to thank the community for using and testing Substance, reporting the bugs, suggesting new features and providing bug fixes and helping in internationaliztion efforts.
Exploring noise
Posted by kirillcool on April 18, 2006 at 12:42 AM | Permalink
| Comments (1)
Inexpensive noise generation pioneered by Ken Perlin has earned him an Oscar award in 1997. See this page for a background of Perlin noise generator and transformations that can be applied to the resulting images in order to imitate various textures such as wood or marble.
In its original form, the 2D Perlin noise looks like this:
Not much to see here, but this is only the beginning. Following an idea similar to that of SwingX painters (hopefully this will not aggravate commercial Swing vendors - read the comments on this entry in Romain's blog), we can apply a chain of filters on this base noise to create some interesting textures. First, we start with the base interface for the noide filter:
public interface NoiseFilter {
/**
* Applies filter on the noise at the specified location.
*
* @param x
* X coordinate.
* @param y
* Y coordinate.
* @param z
* Z coordinate.
* @param origValue
* The original noise value.
* @return New noise value.
*/
public double apply(double x, double y, double z, double origValue);
/**
* Kind of trigonometric function.
*
* @author Kirill Grouchnikov
*/
public enum TrigKind {
SINE, COSINE
}
}
Simple implementation would be that of a sharpening filter:
public class SharpenFilter implements NoiseFilter {
/*
* (non-Javadoc)
*
* @see org.jvnet.substance.painter.noise.NoiseFilter#apply(double, double,
* double, double)
*/
public double apply(double x, double y, double z, double origValue) {
return Math.sqrt(origValue);
}
}
Another implementation focuses on the medium values and subdues values around 0 and 1 (effectively doubling the number of original noise features):
public class MedianBeakFilter implements NoiseFilter {
/*
* (non-Javadoc)
*
* @see org.jvnet.substance.painter.noise.NoiseFilter#apply(double, double,
* double, double)
*/
public double apply(double x, double y, double z, double origValue) {
return Math.sqrt(Math.abs(2 * origValue - 1));
}
}
Now we introduce a compound noise filter which is really a chain of simple or compound filters applied one after another:
public class CompoundNoiseFilter implements NoiseFilter {
/**
* Filter chain.
*/
protected LinkedList<NoiseFilter> chain;
/**
* Creates a new compound filter.
*
* @param chain
* Filter chain.
*/
public CompoundNoiseFilter(NoiseFilter... chain) {
this.chain = new LinkedList<NoiseFilter>();
for (NoiseFilter link : chain) {
this.chain.add(link);
}
}
/*
* (non-Javadoc)
*
* @see org.jvnet.substance.painter.noise.NoiseFilter#apply(double, double,
* double, double)
*/
public double apply(double x, double y, double z, double origValue) {
double val = origValue;
for (NoiseFilter link : this.chain)
val = link.apply(x, y, z, val);
return val;
}
}
Now we are ready to create two simple noise images based on the above filters:
is created by applying the median filter and sharpening the result (which brings out maze-like structure of values that were close to 0.5 in the original noise map):
public class SubstanceMazeWatermark extends SubstanceNoiseWatermark {
public SubstanceMazeWatermark() {
super(SubstanceMazeWatermark.getName(), 0.1, 0.1, false,
new CompoundNoiseFilter(new MedianBeakFilter(),
new SharpenFilter()), false);
}
public static String getName() {
return "Maze";
}
}
is created by applying the median filter twice (which quadruples the number of noise features and sharpens them as well):
public class SubstancePlanktonWatermark extends SubstanceNoiseWatermark {
public SubstancePlanktonWatermark() {
super(SubstancePlanktonWatermark.getName(), 0.1, 0.1, false,
new CompoundNoiseFilter(new MedianBeakFilter(),
new MedianBeakFilter()), false);
}
public static String getName() {
return "Plankton";
}
}
Now we can introduce another noise filter - this filter creates a wood-like texture:
public class WoodFilter implements NoiseFilter {
/**
* Stretch factor.
*/
protected double factor;
/**
* Creates new wood filter with default stretch factor.
*/
public WoodFilter() {
this(20.0);
}
/**
* Create new wood filter.
*
* @param factor
* Stretch factor.
*/
public WoodFilter(double factor) {
this.factor = factor;
}
/*
* (non-Javadoc)
*
* @see org.jvnet.substance.painter.noise.NoiseFilter#apply(double, double,
* double, double)
*/
public double apply(double x, double y, double z, double origValue) {
return this.factor * origValue - (int) (this.factor * origValue);
}
}
is created by simply applying the above filter:
public class SubstanceWoodWatermark extends SubstanceNoiseWatermark {
public SubstanceWoodWatermark() {
super(SubstanceWoodWatermark.getName(), 0.01, 0.01, false,
new WoodFilter(30.0), true);
}
public static String getName() {
return "Wood";
}
}
is created by applying a wood filter followed by median filter:
public class SubstanceMagneticFieldWatermark extends SubstanceNoiseWatermark {
public SubstanceMagneticFieldWatermark() {
super(SubstanceMagneticFieldWatermark.getName(), 0.01, 0.01, false,
new CompoundNoiseFilter(new WoodFilter(15.0),
new MedianBeakFilter()), true);
}
public static String getName() {
return "Magnetic Field";
}
}
Another simple noise filter uses trigonometric functions to produce marble-like structure:
public class MarbleFilter extends BaseNoiseFilter {
/**
* Creates a new marble filter.
*
* @param xFactor
* Stretch factor for X axis.
* @param yFactor
* Stretch factor for Y axis.
* @param zFactor
* Stretch factor for Z axis.
* @param trigKind
* Trigonometry function.
*/
public MarbleFilter(double xFactor, double yFactor, double zFactor,
TrigKind trigKind) {
super(xFactor, yFactor, zFactor, 1.0, trigKind);
}
/*
* (non-Javadoc)
*
* @see org.jvnet.substance.painter.noise.NoiseFilter#apply(double, double,
* double, double)
*/
public double apply(double x, double y, double z, double origValue) {
double trans = this.xFactor * x + this.yFactor * y + this.zFactor * z
+ origValue;
double trig = (this.trigKind == TrigKind.COSINE) ? Math.cos(trans)
: Math.sin(trans);
return 0.5 + 0.5 * trig;
}
is created by applying the above filter followed by a median filter:
public class SubstanceMarbleVeinWatermark extends SubstanceNoiseWatermark {
public SubstanceMarbleVeinWatermark() {
super(SubstanceMarbleVeinWatermark.getName(), 0.1, 0.1, false,
new CompoundNoiseFilter(MarbleFilter.getXFilter(0.1,
TrigKind.COSINE), new MedianBeakFilter()), false);
}
public static String getName() {
return "Marble Vein";
}
}
The last filter produces a fabric-like texture. It is in itself a chain of filters, each one producing an oriented fabric texture. Applying two differently oriented textures produces the following mix:
which is created by
public class SubstanceFabricWatermark extends SubstanceNoiseWatermark {
public SubstanceFabricWatermark() {
super(SubstanceFabricWatermark.getName(), 0.1, 0.1, false,
new FabricFilter(FabricFilterLink.getXLink(1.0, 10.0,
TrigKind.SINE), FabricFilterLink.getYLink(1.0, 10.0,
TrigKind.COSINE)), false);
}
public static String getName() {
return "Fabric";
}
}
The last transformation (and my favourite one) imitates copperplate engraving
and is created by a chain of wood, fabric and median filters:
public class SubstanceCopperplateEngravingWatermark extends
SubstanceNoiseWatermark {
public SubstanceCopperplateEngravingWatermark() {
super(SubstanceCopperplateEngravingWatermark.getName(), 0.01, 0.01,
false, new CompoundNoiseFilter(new WoodFilter(15.0),
new FabricFilter(FabricFilterLink.getXLink(1.0, 10.0,
&n |