Construct your shadow mask in a buffer (using DST_IN), and then draw it over the top of everything else.
Quick hacked together example (using my 4k template as a basis).
import java.applet.Applet;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Event;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.image.BufferedImage;
import java.util.Random;
public class G extends Applet implements Runnable {
static final boolean DEBUG = true;
static final int WIDTH = 800, HEIGHT = 600;
static final long NANO_SECOND = 1000000000;
static final long NANO_FRAMERATE_SAMPLE_LENGTH = NANO_SECOND/4;
static final long NANO_FRAME_LENGTH = (NANO_SECOND / 60);
public void start() {
new Thread(this).start();
}
public void run() {
Random r = new Random();
BufferedImage buffer = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D bg = (Graphics2D)buffer.getGraphics();
BufferedImage shadowBuffer = new BufferedImage(WIDTH,HEIGHT,BufferedImage.TYPE_INT_ARGB);
Graphics2D sg = (Graphics2D)shadowBuffer.getGraphics();
long previousSampleStartTime;
int frameCount;
float fps;
if (DEBUG) {
previousSampleStartTime = System.nanoTime();
frameCount = 0;
fps = 0f;
}
long nextFrameStartTime, frameStartTime;
nextFrameStartTime = System.nanoTime();
//offsets in the ctrls[] that the mouse left/right/middle button states are stored.
final int LMB = 0/Event.META_MASK;
final int RMB = Event.META_MASK/Event.META_MASK;
final int MMB = Event.ALT_MASK/Event.META_MASK;
final int KEY_START = MMB+1;
final int CONTROLS_LENGTH = 65536; // this needs to be large enough for no (remotely common) keys to throw out of bounds.
// traditional key state array
// For gui-type interfaces the processing should be done on a per event basis, so no state changes are missed.
int [] ctrls = new int[CONTROLS_LENGTH];
int mouseX=-1, mouseY=-1;
final int LIGHT_COUNT = 10;
int [] lightsX = new int[LIGHT_COUNT];
int [] lightsY = new int[LIGHT_COUNT];
int [] lightsDx = new int[LIGHT_COUNT];
int [] lightsDy = new int[LIGHT_COUNT];
for(int i = 1;i < LIGHT_COUNT;i++) {
lightsX[i] = r.nextInt(WIDTH);
lightsY[i] = r.nextInt(HEIGHT);
lightsDx[i] = (r.nextInt(2)+1)*(r.nextInt(2)*2-1);
lightsDy[i] = (r.nextInt(2)+1)*(r.nextInt(2)*2-1);
}
// enter game loop.
do {
while (nextFrameStartTime > (frameStartTime = System.nanoTime())) {
// try{Thread.sleep(1);}catch(Exception e){} //ruins framerate accuracy, not necessary in Windows.
}
// Render
bg.setColor(Color.black);
bg.fillRect(0, 0, WIDTH, HEIGHT);
// note. Do processing of the queued key events as late as possible in the game loop so as to maximize responsiveness
synchronized(this) {
Event e;
while((e = inputHead)!=null) {
final int eventType = e.id;
final int eventClass = eventType/100;
final int KEY_EVENT = (Event.KEY_PRESS-1)/100;
final int MOUSE_EVENT = (Event.MOUSE_DOWN-1)/100;
if(eventClass==MOUSE_EVENT) {
bg.setColor(Color.white);
bg.drawLine(mouseX, mouseY, e.x, e.y);
mouseX = e.x;
mouseY = e.y;
if(eventType<Event.MOUSE_MOVE) { // event must be MOUSE_DOWN or MOUSE_UP
ctrls[(e.modifiers & (Event.META_MASK|Event.ALT_MASK))/Event.META_MASK] = Event.MOUSE_UP-eventType;
}
}
if(eventClass==KEY_EVENT) {
ctrls[e.key] = eventType &1;
}
inputHead = e.evt;
}
}
// light 0 tracks the mouse position.
lightsX[0] = mouseX;
lightsY[0] = mouseY;
// other lights just fly around a bit.
for(int i = 1;i < LIGHT_COUNT;i++ ) {
lightsX[i]+=WIDTH+lightsDx[i];
lightsY[i]+=HEIGHT+lightsDy[i];
lightsX[i]%=WIDTH;
lightsY[i]%=HEIGHT;
}
// end of logic
/// draw background patternation
for(int i = 0;i < WIDTH;i+=40) {
bg.setColor(Color.BLUE);
bg.drawLine(i,0,i,HEIGHT);
}
for(int i = 0;i < HEIGHT;i+=40) {
bg.setColor(Color.RED);
bg.drawLine(0,i,WIDTH,i);
}
// clear the shadow buffer
sg.setComposite(AlphaComposite.Src);
sg.setColor(Color.BLACK);
sg.fillRect(0, 0, WIDTH,HEIGHT);
// and ready it for this frame's shadows
sg.setComposite(AlphaComposite.DstIn);
for(int i = 0;i < LIGHT_COUNT;i++) {
int radius = 100;
float [] dist = {0.0f, 1.0f};
Color [] colors = {new Color(0.0f, 0.0f, 0.0f, 0.0f), Color.BLACK};
RadialGradientPaint p = new RadialGradientPaint(new Point(lightsX[i], lightsY[i]),
radius, dist, colors);
sg.setPaint(p);
// there might be an 'off by one' bug here. Haven't spent the time thinking whether fillRect bounds match circle+/-radius.
sg.fillRect(lightsX[i]-radius, lightsY[i]-radius, radius*2, radius*2);
}
if (DEBUG) {
//framerate counter & key/mouse state reporting
frameCount++;
long now = System.nanoTime();
if (now - previousSampleStartTime >= NANO_FRAMERATE_SAMPLE_LENGTH) {
fps = frameCount * (float)NANO_FRAMERATE_SAMPLE_LENGTH/(now - previousSampleStartTime) * (float)NANO_SECOND/NANO_FRAMERATE_SAMPLE_LENGTH;
previousSampleStartTime = now;
frameCount = 0;
}
bg.setColor(Color.white);
bg.drawString(String.format("FPS: %.2f", fps), 20, 30);
bg.drawString(String.format("Mouse[left,middle,right]=[%d,%d,%d]", ctrls[LMB], ctrls[MMB], ctrls[RMB]), 20, 45);
int keyDownCount = 0;
for (int i = KEY_START; i < CONTROLS_LENGTH; i++) {
if (ctrls[i] != 0) {
keyDownCount++;
bg.drawString(new String(new char[] { (char) i }), 20, 60 + 15 * keyDownCount);
}
}
}
bg.drawImage(shadowBuffer,0,0,null);
// Draw the entire results on the screen.
final Graphics g = getGraphics();
if (g != null)
g.drawImage(buffer, 0, 0, null);
nextFrameStartTime = frameStartTime + NANO_FRAME_LENGTH;
}
while(isActive());
// shutdown here
}
Event inputHead;
public synchronized boolean handleEvent(Event e) {
// we abuse input Events by constructing a linkedlist from them.
if(inputHead==null) {
inputHead = e;
}
else {
// note we have to iterate to the end of the list every time we add an event
// not a performance concern as the list will never grow very large (it's reset upto 60 times/second)
Event ll = inputHead;
while(ll.evt!=null) ll = ll.evt;
ll.evt = e;
}
return false;
}
}