I wrote this program last Christmas Eve, having forgotten to buy any cards for my family, and thought I might share it this year. The code isn't particularly good, as I wrote most of it when I was a year younger and patched in the tree generator this year (as well as the colour - it was greyscale last year).
Hope you like it.
The jar is here: http://domclark.allalla.com/files/snows … wscape.jar
And the source is here:
Snowscape.java
package org.snowscape;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
@SuppressWarnings("serial")
public class Snowscape extends Canvas{
public static final int snowColor = 0xffffff;
public static final int skyColor = 0xeeeef5;
public static final int hillColor = 0xddddff;
public static final int treeColor = 0x776565;
public static final int groundColor = 0xe9e9ee;
private static final int LEN = 30;
private static final double BEND = 0.6;
public static final int TREES = 0;
public static final int SNOW = 1;
public static final int WIDTH = 2;
public static final int HEIGHT = 3;
public static final int DEPTH = 4;
public static final int HILLS = 5;
public static final int MIN_RAD = 6;
public static final int MAX_RAD = 7;
public static final int AVG_HEIGHT = 8;
public static final int HEIGHT_RANGE = 9;
public static final int SEED = 10;
public static int[] configValues = new int[] {7, 8, 480, 360, 150, 30, 40, 50, 30, 3, -1};
public BufferedImage img;
public JFrame frame;
public ControlPanel panel;
public Listener listener;
public Snowscape(){
this.listener = new Listener(this);
this.panel = new ControlPanel(this.listener);
this.frame = new JFrame("Snowscape Generator");
this.frame.setPreferredSize(new Dimension(960, 720));
this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.frame.setLayout(new BorderLayout());
this.frame.add(this, BorderLayout.CENTER);
this.frame.add(this.panel, BorderLayout.SOUTH);
generate();
this.frame.pack();
this.frame.setVisible(true);
}
public void generate(){
Random rand;
if(configValues[SEED] == -1) rand = new Random();
else rand = new Random(configValues[SEED]);
int[] config = Snowscape.configValues;
int w = config[WIDTH];
int h = config[HEIGHT];
int[] pixels = new int[w * h];
Arrays.fill(pixels, groundColor);
int its = config[SNOW] * w * h / 8192;
double[][] hillMap = new double[w][config[DEPTH]];
int hills = config[HILLS];
for(int a = 0; a < hills; a++){
int hr = rand.nextInt(config[MAX_RAD] - config[MIN_RAD]) + config[MIN_RAD];
int hillX = rand.nextInt(w);
int hillZ = rand.nextInt(config[DEPTH]);
int hillY = rand.nextInt(config[AVG_HEIGHT] - config[HEIGHT_RANGE]) + config[HEIGHT_RANGE] / 2;
for(int x = 0; x < 2 * hr; x++){
for(int z = 0; z < 2 * hr; z++){
int xc = hillX + x - hr;
int zc = hillZ + z - hr;
if(xc >= 0 && xc < w && zc >= 0 && zc < config[DEPTH]){
int dx = xc - hillX;
int dz = zc - hillZ;
double yc = Math.max(0, (hr * hr - (dx * dx + dz * dz)) / 1000d);
hillMap[xc][zc] += hillY * yc;
}
}
}
}
for(int y = 0; y < config[DEPTH] + 1; y++){
for(int p = 0; p < w; p++){
int col = hillColor;
int poscol = skyColor;
for(int b = config[DEPTH] - 1; b >= 0; b--){
if(hillMap[p][b] > y){
poscol = col;
}
if(b % 2 == 0) col -= 0x010101;
}
pixels[p + (config[DEPTH] - y) * w] = poscol;
}
}
for(int f = 0; f < its; f++){
int flakeX = rand.nextInt(w - 2) + 1;
int flakeY = rand.nextInt(h - 4) + 2;
pixels[flakeX + flakeY * w] = snowColor;
pixels[flakeX + (flakeY - 1) * w] = snowColor;
pixels[flakeX + 1 + (flakeY - 2) * w] = snowColor;
pixels[flakeX - 1 + (flakeY - 2) * w] = snowColor;
pixels[flakeX + 1 + (flakeY + 2) * w] = snowColor;
pixels[flakeX - 1 + (flakeY + 2) * w] = snowColor;
pixels[flakeX + (flakeY + 1) * w] = snowColor;
pixels[flakeX + 1 + flakeY * w] = snowColor;
pixels[flakeX - 1 + flakeY * w] = snowColor;
pixels[flakeX + 2 + flakeY * w] = snowColor;
pixels[flakeX - 2 + flakeY * w] = snowColor;
}
this.img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
this.img.setRGB(0, 0, w, h, pixels, 0, w);
Graphics g = this.img.getGraphics();
g.setColor(new Color(treeColor));
for(int i = 0; i < config[TREES]; i++)
tree(3, rand.nextInt(config[WIDTH]), config[DEPTH] + 80, Math.PI, rand, g);
g.dispose();
this.repaint();
}
public void save(){
JFileChooser jfc = new JFileChooser();
jfc.setDialogTitle("Save Snowscape");
jfc.setMultiSelectionEnabled(false);
if(! (jfc.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION)) return;
File f = jfc.getSelectedFile();
try {
ImageIO.write(img, "png", f);
} catch (IOException e) {
JDialog error = new JDialog(this.frame, "Error");
error.setModal(true);
error.setLayout(new java.awt.GridLayout(1, 1));
error.add(new java.awt.Label("Error saving file!"));
error.pack();
error.setVisible(true);
e.printStackTrace();
}
}
public void paint(Graphics g){
g.drawImage(img, (this.getWidth() - configValues[WIDTH]) / 2, (this.getHeight() - configValues[HEIGHT]) / 2, null);
g.drawRect((this.getWidth() - configValues[WIDTH]) / 2 - 1, (this.getHeight() - configValues[HEIGHT]) / 2 - 1, configValues[WIDTH] + 1, configValues[HEIGHT] + 1);
}
public void tree(int depth, int x, int y, double angle, Random rand, Graphics g){
int ex = (int) (x - Math.sin(angle) * LEN);
int ey = (int) (y + Math.cos(angle) * LEN);
g.drawLine(x, y, ex, ey);
if(depth <= 0) return;
if(rand.nextInt(3) == 0)
tree(depth - 1, ex, ey, angle + (rand(rand) * BEND), rand, g);
tree(depth - 1, ex, ey, angle + (rand(rand) * BEND), rand, g);
tree(depth - 1, ex, ey, angle + (rand(rand) * BEND), rand, g);
}
private double rand(Random rand){
return (1 - (rand.nextDouble() * rand.nextDouble())) * (rand.nextInt(2) * 2 - 1);
}
public static void main(String[] args) {
new Snowscape();
}
}ConfigDialog.java
package org.snowscape;
import java.awt.Button;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Label;
import java.awt.TextField;
import javax.swing.JDialog;
import javax.swing.JFrame;
public class ConfigDialog extends JDialog {
private static final long serialVersionUID = 1L;
public static String[] fields = new String[] {"Trees", "Snow density", "Width", "Height", "Terrain depth", "Hills", "Min hill radius", "Max hill radius", "Average hill height", "Hill height range", "Random seed"};
public Label[] labels;
public TextField[] boxes;
public ConfigDialog(JFrame f, Listener l) {
super(f);
this.setTitle("Configure Snowscape");
this.setModal(true);
this.setLayout(new GridLayout(fields.length + 1, 2));
this.setPreferredSize(new Dimension(230, (fields.length + 1) * 24 + 3));
labels = new Label[fields.length];
boxes = new TextField[fields.length];
for(int i = 0; i < fields.length; i++){
labels[i] = new Label(fields[i]);
boxes[i] = new TextField("" + Snowscape.configValues[i]);
this.add(labels[i]);
this.add(boxes[i]);
}
Button ok = new Button("OK");
ok.addActionListener(l);
Button cancel = new Button("Cancel");
cancel.addActionListener(l);
this.add(ok);
this.add(cancel);
this.pack();
}
public void writeValues() {
try{
for(int i = 0; i < Snowscape.configValues.length; i++){
Snowscape.configValues[i] = Integer.parseInt(this.boxes[i].getText());
}
} catch(NumberFormatException e) {
}
}
}ControlPanel.java
package org.snowscape;
import java.awt.Button;
import java.awt.Panel;
public class ControlPanel extends Panel {
private static final long serialVersionUID = 1L;
public Button[] buttons;
public static String[] titles = new String[] {"Configure", "Regenerate", "Save...", "Quit"};
public ControlPanel(Listener l){
for(String s : ControlPanel.titles){
Button b = new Button(s);
b.addActionListener(l);
this.add(b);
}
}
}Listener.java
package org.snowscape;
import java.awt.Button;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.HashMap;
public class Listener implements MouseListener, ActionListener {
public Snowscape owner;
public HashMap<String, Integer> eventMap = new HashMap<String, Integer>();
public ConfigDialog dialog;
public Listener(Snowscape snowscape) {
this.owner = snowscape;
eventMap.put(null, -1);
String[] events = ControlPanel.titles;
for(int i = 0; i < events.length; i++){
eventMap.put(events[i], i);
}
eventMap.put("OK", 100);
eventMap.put("Cancel", 101);
}
public void mouseClicked(MouseEvent arg0) {
}
public void mouseEntered(MouseEvent arg0) {
}
public void mouseExited(MouseEvent arg0) {
}
public void mousePressed(MouseEvent arg0) {
}
public void mouseReleased(MouseEvent arg0) {
}
public void actionPerformed(ActionEvent arg0) {
switch(idForEvent(arg0)){
case 0:
this.dialog = new ConfigDialog(owner.frame, owner.listener);
this.dialog.setVisible(true);
break;
case 1:
owner.generate();
break;
case 2:
owner.save();
break;
case 3:
System.exit(0);
break;
case 100:
this.dialog.writeValues();
case 101:
this.dialog.dispose();
break;
}
}
public int idForEvent(ActionEvent event){
Object source = event.getSource();
if(!(source instanceof Button)) return -1;
Button b = (Button) source;
Object ret = eventMap.get(b.getLabel());
if (ret == null) return -1;
return (Integer) ret;
}
}Offline
Nice - I love the hills in the background.

Offline
Nice!
The trees are cool, though sometimes you get two close to each other and it gets confusing with too many lines.
Offline