Draw random black-and-white forest

66

25

Your task is to write program which will draw 800x600 black-and-white image with something resembling a forest.

Like this (it is dithered photo):

Rules

  • You are disallowed to use any existing images - you should generate image purely algorithmically
  • Use only 2 colors - black and white (no grayscale)
  • Each time program runs image should be new - random every time
  • One tree is not a forest (let say 5 trees minumum)
  • Special libraries for drawing trees/forests are disallowed
  • Answer with most votes wins

Somnium

Posted 2014-08-06T16:48:37.783

Reputation: 2 537

9This question appears to be off-topic because it is more an art contest rather than a programming contest. – ProgramFOX – 2014-08-06T16:50:18.103

19@ProgramFOX Isn't programming art? :) – Somnium – 2014-08-06T16:51:27.697

1

The question is about generating art, and the answers will likely compete by output, not by code. Example of a similar contest, which was also an art contest rather than a programming contest: http://codegolf.stackexchange.com/questions/20598/make-the-day-we-fight-back-logo

– ProgramFOX – 2014-08-06T16:54:49.773

1@ProgramFOX I thought for this task various interesting algorithms can be used. – Somnium – 2014-08-06T17:03:39.077

Those algorithms will generate beautiful art, and better output will attract more votes, I think. Anyway, we'll see what other people think about it. – ProgramFOX – 2014-08-06T17:09:07.067

1

I think this question is more similar to this one: http://codegolf.stackexchange.com/questions/16826/lets-simulate-a-random-snowflake?rq=1 just for the randomness factor, and that one was well received.

– BrunoJ – 2014-08-06T17:13:32.493

2@BrunoJ You're right, the community is getting tougher about closing art-based challenges. Some of my first answers (at the beginning of this year) were to questions similar to this one. I may mention it on Meta. – Level River St – 2014-08-06T17:35:58.753

15I, for one, would like to see some entries for this challenge, and am disappointed that it was put on hold. – Braden Best – 2014-08-06T21:08:03.560

@BrunoJ, the question you reference a) is less broad than this one (although it could still be improved considerably); b) was posted in the middle of a peak of activity which was characterised by a lot of poor questions and the absence due to holiday of some regulars. To get only 10 upvotes right then was not to be well received, and many questions which should have been closed managed not to be. – Peter Taylor – 2014-08-07T11:33:45.970

3I like this challenge. Answers that aren't in the spirit of it won't get upvoted as much, so what's the problem? – cjfaure – 2014-08-07T11:35:18.093

2Special libraries for drawing trees/forests are disallowed Why would those even exist? – cjfaure – 2014-08-07T12:55:16.743

3@cjfaure For different purposes, for example for generating models and images for games. – Somnium – 2014-08-07T13:54:11.420

3"The question is about generating art, and the answers will likely compete by output, not by code" - this is a crazy argument. Its the code that generates the output. A question that asks for code that produces numbers is not considered to be too much of a maths challenge. – rdans – 2014-08-07T18:05:31.680

There are already answers to this question, and they clearly required programming - both effort and skill. Personally I would like to see questions with more restrictions to stimulate creative ways around them - this is broader than my personal preference. However, this question does have a number of restrictions and is not sufficiently broad to justify closing it. – trichoplax – 2014-08-08T02:59:45.163

The dithering is optional, is it not? – Lars Ebert – 2014-08-08T09:37:01.580

@LarsEbert Dithering is not mentioned in question at all (except example) so you may not use it. – Somnium – 2014-08-08T10:43:32.723

1http://algorithmicbotany.org/TreeSketch/ – Dr. belisarius – 2014-08-08T15:23:04.117

I assume that "you may not use it" means you are not required to use dithering. I can't see how there can be a ban on dithering as it can be implemented algorithmically from an algorithmically generated image, which meets the requirements of the question. – trichoplax – 2014-08-08T23:14:14.410

@githubphagocyte I meant only 2 colors - black and white. You are confusing with grayscale. – Somnium – 2014-08-11T13:56:01.823

@githubphagocyte If you didn't see I already have mentioned that in rules. – Somnium – 2014-08-11T14:30:15.003

Yes, I saw that from the start, but since it is common for people to read "black and white" as meaning the same thing as "greyscale" I'm just suggesting you could avoid confusion by changing "only 2 colors - black and white" to "only 2 colors - black and white (not greyscale)". Your wording is already perfectly clear, but since there are already people mistaking the meaning, it is worth adding the extra clarification to avoid arguments later and having to delete answers that clutter up your question. I like this question and don't want to see it fill up with off topic answers. – trichoplax – 2014-08-11T14:43:13.720

I also think making that clear might help avoid the close votes since some of the close voters also may not have realised that this isn't just a grayscale question. – trichoplax – 2014-08-11T14:49:53.260

Answers

98

C: 3863 1144 1023 999 942 927

The original solution saves 2 pnm files per run (one with g appended, before dithering). Because the dithering wasn't beautiful for the first few lines, there is a hack in place to render more lines than needed, and crop during output.

The golfed solution has a simpler dithering and saves only the dithered image. (no warnings with gcc -std=c11 -pedantic -Wall -Wextra)

Example images from 3 original program runs and one run of the golfed version (last image):

example 1 example 2 example 3 example 4

Golfed version

  #include <math.h>
  #include <time.h>
  #include <stdlib.h>
  #include <stdio.h>
  #define D float
  #define R rand()/RAND_MAX
  #define Z(a,b,c) if(10.*R>a)T(h,s,j+b+c*R,g);
  #define U a[y][x]
  #define V e[y+1][x
  #define F(x) for(i=0;i<x;++i)
  int i,x,y,W=800,H=600;unsigned char a[600][800],c;D e[601][802],r,b,k,l,m,n,f,w,
  d,q=.01;void T(D h,D s,D j,D g){r=b=0;do{j+=.04*R-.02;h+=sin(j)*q;s+=cos(j)*q;b
  +=q;g+=q;f=525/d;r=.25-g/44;m=w*f+s*f+W/2;n=2*f-h*f+H/2;f*=r;for(y=n-f-2;y<n+f+2
  ;++y)if(y>=0&&y<H)for(x=m-f-2;x<m+f+2;++x)if(x>=0&&x<W)if(k=m-x,l=n-y,f>sqrt(k*k
  +l*l))if(U>d*3)U=d*3;}while(b<10*r+12*r*R&&r>q);if(r>q){Z(2,.26,.35)Z(2,-.26,-
  .35)Z(7,-.05,.1)}}int main(){FILE* o=fopen("i","wb");srand(time(0));F(W*H){y=i/W
  ;a[y][i%W]=(y<313)?255:6e3/(2*y-H);}F(200)w=1e2*R-60,d=80.*R+5,T(0,0,1.58,0);F(W
  *H){x=i%W;y=i/W;k=U+e[y][x+1];U=-(k>0);l=(k-U)*.1;e[y][x+2]+=l*4;V]+=l*2;V+1]+=l
  *3;V+2]+=l;}fprintf(o,"P5 800 600 255 ");fwrite(a,1,W*H,o);}

Original version

  #include <math.h>
  #include <stdio.h>
  #include <stdlib.h>

  #define W 800
  #define H 600
  #define SPEED 0.01
  #define HEIGHT 11.0

  #define R(m) ((double)(m) * rand() / RAND_MAX)
  #define RAD(deg) ((deg) / 180.0 * M_PI)
  #define LIMIT(x, min, max) ((x) < (min) ? (min) : (x) > (max) ? (max) : (x))

  void shade(void);
  void growTree(double dist, double side, double h, double s, double alpha, double grown);
  void plot(double dist, double side, double h, double s, double alpha, double diam);
  void dither(void);
  void writeImg(int dither);

  unsigned char img[H+10][W];
  double err[H+10+2][W+4];
  long tim;

  int main(void)
  {
     int i;
     tim = time(0);
     srand(tim);
     shade();
     for(i = 0; i < 200; ++i)
     {
        growTree(5 + R(75), -60 + R(120), 0.0, 0.0, RAD(90), 0.0);
     }
     writeImg(0);
     dither();
     writeImg(1);
  }

  void shade(void)
  {
     int y;
     for(y = -10; y < H; ++y)
     {
        double dist = H * 3.5 / (2 * y - H);
        unsigned char color = dist / 80 * 255;
        if(y <= H / 2 || dist > 80) color = 255;
        memset(img[y+10], color, W);
     }
  }

  void growTree(double dist, double side, double h, double s, double alpha, double grown)
  {
     double diam, branchLength = 0.0;

     do
     {
        alpha += R(RAD(3)) - RAD(1.5);
        h += sin(alpha) * SPEED;
        s += cos(alpha) * SPEED;
        branchLength += SPEED;
        grown += SPEED;
        diam = (1.0 - grown / HEIGHT) * 0.5;
        plot(dist, side, h, s, alpha, diam);
     } while(branchLength < 5 * diam + R(6 * diam) && diam > 0.02);

     if(diam > 0.02)
     {
        int br = 0;

        if(R(10) > 2) br++,growTree(dist, side, h, s, alpha + RAD(15) + R(RAD(20)), grown);
        if(R(10) > 2) br++,growTree(dist, side, h, s, alpha - RAD(15) - R(RAD(20)), grown);
        if(R(10) < 2 || br == 0) growTree(dist, side, h, s, alpha - RAD(2.5) + R(RAD(5)), grown);
     }
  }

  void plot(double dist, double side, double h, double s, double alpha, double diam)
  {
     int x, y;
     double scale = H / 4.0 * 3.5 / dist;
     double x0 = side * scale + s * scale + W / 2.0;
     double y0 = H / 2.0 + 2.0 * scale - h * scale;
     diam *= scale;
     h *= scale;
     s *= scale;
     for(y = y0 - diam / 2 - 2; y < y0 + diam / 2 + 2; ++y)
     {
        if(y < -10 || y >= H) continue;
        for(x = x0 - diam / 2 - 2; x < x0 + diam / 2 + 2; ++x)
        {
           double dx, dy, d;
           if(x < 0 || x >= W) continue;
           dx = x0 - x;
           dy = y0 - y;
           d = diam / 2 - sqrt(dx * dx + dy * dy) + 0.5;
           if(d > 0)
           {
              unsigned char color = dist / 80 * 255;
              if(img[y+10][x] > color) img[y+10][x] = color;
           }
        }
     }
  }

  void dither(void)
  {
     int x0, x, y;
     for(y = -10; y < H; ++y)
     {
        for(x0 = 0; x0 < W; ++x0)
        {
           double error, oldpixel;
           unsigned char newpixel;
           if(y%2) x = W - 1 - x0;
           else x = x0;
           oldpixel = img[y+10][x] + err[y+10][x+2];
           newpixel = oldpixel > 127 ? 255 : 0;
           img[y+10][x] = newpixel;
           error = oldpixel - newpixel;
           err[y+10  ][x+1+2*(1-y%2)] += error * 7 / 48;
           err[y+10  ][x+4*(1-y%2)] += error * 5 / 48;
           err[y+10+1][x  ] += error * 3 / 48;
           err[y+10+1][x+1] += error * 5 / 48;
           err[y+10+1][x+2] += error * 7 / 48;
           err[y+10+1][x+3] += error * 5 / 48;
           err[y+10+1][x+4] += error * 3 / 48;
           err[y+10+2][x  ] += error * 1 / 48;
           err[y+10+2][x+1] += error * 3 / 48;
           err[y+10+2][x+2] += error * 5 / 48;
           err[y+10+2][x+3] += error * 3 / 48;
           err[y+10+2][x+4] += error * 1 / 48;
        }
     }
  }

  void writeImg(int dither)
  {
     FILE* fp;
     char buffer[32];
     sprintf(buffer, "%ld%s.pnm", tim, dither ? "" : "g");
     fp = fopen(buffer, "wb");
     fprintf(fp, "P5\n%d %d\n255\n", W, H);
     fwrite(&img[10][0], 1, W * H, fp);
     fclose(fp);
  }

Manuel Kasten

Posted 2014-08-06T16:48:37.783

Reputation: 3 226

3+1. Nice pics. Anyway, this is popularity-contest, not code-golf. So, no need to golf. :) – Vectorized – 2014-08-07T15:41:07.423

1I like this very much, this uses algorithm which I thought will give best results. – Somnium – 2014-08-07T15:44:55.850

2Looks good, I had a similar idea but was too lazy=) I think you could try to randomize the recursion depth of the branches, I think that would even look more natural. – flawr – 2014-08-07T15:58:30.993

3I know I don't NEED to golf it, but that's the most fun part for me! – Manuel Kasten – 2014-08-07T16:13:08.120

2This is gorgeous. – Quentin – 2014-08-08T09:43:58.787

Great job! The undithered versions look even better! – DreamWarrior – 2014-08-08T19:22:31.077

12

Also, I put an undithered version into sepia tone -- gorgeous! See here: http://i.imgur.com/lCrJCp7.jpg

– DreamWarrior – 2014-08-08T19:28:49.263

If you were looking to golf it a bit more, you could get rid of the return 0; at the end of main and the int before main(). – syb0rg – 2014-08-09T15:53:52.370

@syb0rg I could also remove most includes and switch FILE* to int. But as it is now it compiles without warnings. – Manuel Kasten – 2014-08-09T16:07:31.240

Ahh. Well, removing the return 0; shouldn't give you a warning. Also, I don't see you using your FILE* anywhere besides in main(), so you could declare o when it is first used. Also, couldn't you use write() and open() to save a few characters? Lastly, you could get rid of your headers and still have no warnings, see this answer of mine.

– syb0rg – 2014-08-09T16:14:46.590

@syb0rg -ansi -pedantic -Wall -Wextra. GCC will warn for all those things. Implicit declaration. Main doesn't return int. Non void function doesn't return something. Etc. Moving FILE* o down will save something, thank you :) – Manuel Kasten – 2014-08-09T16:19:30.943

@ManuelKasten -ansi is the same as -std=c89, you should be using at least -std=c99 (or go the extra mile and use the latest standard, C11). Then you will be able to utilize items declared in the more recent standards, specifically C99 & C11 §5.1.2.2(3). – syb0rg – 2014-08-09T16:28:12.907

Looking at the if statements in your code, I learned about the comma operator in C. Thanks! – krs013 – 2014-08-09T17:53:04.647

42

Java Jungle

(954 golfed)

Full of deep, twisting undergrowth, this is a forest not easily traversed.

enter image description here

It's basically a fractal random walk with slowly shrinking, twisty vines. I draw 75 of them, gradually changing from white in the back to black up front. Then I dither the whole thing, shamelessly adapting Averroes' code here for that.

Golfed: (Just because others decided to)

import java.awt.*;import java.awt.image.*;import java.util.*;class P{static Random rand=new Random();public static void main(String[]a){float c=255;int i,j;Random rand=new Random();final BufferedImage m=new BufferedImage(800,600,BufferedImage.TYPE_INT_RGB);Graphics g=m.getGraphics();for(i=0;i++<75;g.setColor(new Color((int)c,(int)c,(int)c)),b(g,rand.nextInt(800),599,25+(rand.nextInt(21-10)),rand.nextInt(7)-3),c-=3.4);for(i=0;i<800;i++)for(j=0;j<600;j++)if(((m.getRGB(i,j)>>>16)&0xFF)/255d<rand.nextFloat()*.7+.05)m.setRGB(i,j,0);else m.setRGB(i,j,0xFFFFFF);new Frame(){public void paint(Graphics g){setSize(800,600);g.drawImage(m,0,0,null);}}.show();}static void b(Graphics g,float x,float y,float s,float a){if(s>1){g.fillOval((int)(x-s/2),(int)(y-s/2),(int)s,(int)s);s-=0.1;float n,t,u;for(int i=0,c=rand.nextInt(50)<1?2:1;i++<c;n=a+rand.nextFloat()-0.5f,n=n<-15?-15:n>15?15:n,t=x+s/2*(float)Math.cos(n),u=y-s/2*(float)Math.sin(n),b(g,t,u,s,n));}}}

Sane original code:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.JFrame;

public class Paint {

    static int minSize = 1;
    static int startSize = 25;
    static double shrink = 0.1;
    static int branch = 50;
    static int treeCount = 75;

    static Random rand = new Random();
    static BufferedImage img;

    public static void main(String[] args) {
        img = new BufferedImage(800,600,BufferedImage.TYPE_INT_ARGB);
        forest(img);
        dither(img);
        new JFrame() {
            public void paint(Graphics g) {
                setSize(800,600);
                g.drawImage(img,0,0,null);
            }
        }.show();
    }

    static void forest(BufferedImage img){
        Graphics g = img.getGraphics();
        for(int i=0;i<treeCount;i++){
            int c = 255-(int)((double)i/treeCount*256);
            g.setColor(new Color(c,c,c));
            tree(g,rand.nextInt(800), 599, startSize+(rand.nextInt(21-10)), rand.nextInt(7)-3);
        }
    }

    static void tree(Graphics g, double x, double y, double scale, double angle){
        if(scale < minSize)
            return;
        g.fillOval((int)(x-scale/2), (int)(y-scale/2), (int)scale, (int)scale);
        scale -= shrink;
        int count = rand.nextInt(branch)==0?2:1;
        for(int i=0;i<count;i++){
            double newAngle = angle + rand.nextDouble()-0.5;
            if(newAngle < -15) newAngle = -15;
            if(newAngle > 15) newAngle = 15;
            double nx = x + (scale/2)*Math.cos(newAngle);
            double ny = y - (scale/2)*Math.sin(newAngle);
            tree(g, nx, ny, scale, newAngle);
        }
    }

    static void dither(BufferedImage img) {
        for (int i=0;i<800;i++)
            for (int j=0;j<600;j++) {
                double lum = ((img.getRGB(i, j) >>> 16) & 0xFF) / 255d;
                if (lum <= threshold[rand.nextInt(threshold.length)]-0.2)
                    img.setRGB(i, j, 0xFF000000);
                else
                    img.setRGB(i, j, 0xFFFFFFFF);
            }
    }

    static double[] threshold = { 0.25, 0.26, 0.27, 0.28, 0.29, 0.3, 0.31,
            0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38, 0.39, 0.4, 0.41, 0.42,
            0.43, 0.44, 0.45, 0.46, 0.47, 0.48, 0.49, 0.5, 0.51, 0.52, 0.53,
            0.54, 0.55, 0.56, 0.57, 0.58, 0.59, 0.6, 0.61, 0.62, 0.63, 0.64,
            0.65, 0.66, 0.67, 0.68, 0.69 };

}

One more? Okay! This one has the dithering tuned down a bit, so the blacks in front are much flatter.

enter image description here

Unfortunately, the dither doesn't show the fine details of the vine layers. Here's a greyscale version, just for comparison:

enter image description here

Geobits

Posted 2014-08-06T16:48:37.783

Reputation: 19 061

1Nice. Glad my code was helpful ;) – Averroes – 2014-08-08T09:22:18.903

1I read nowhere in the rules that dithering is required. I think the undithered version looks more beautiful. – Lars Ebert – 2014-08-08T09:37:42.097

4@Lars dithering is not needed, BUT: only black and white may be used, no gray values allowed.... – Manuel Kasten – 2014-08-08T11:14:54.443

31

Javascript + HTML - not golfed

A javascript porting of the algorithm of @Manuel Kansten - it's amazing how good these trees look.

Just to do something different, I draw the image in color, then dither to b/w at the last step.

I don't know why, but my forest is less dark and less frightening respect to Manuel's.

Test with JSfiddle or run the new Snippet below. That's NOT fast. Be patient and watch the forest grow.

Forest 1 Forest 1 color

Forest 2 Forest 2 color

W=800
H=600
canvas.width = W;
canvas.height = H;

var ctx = canvas.getContext("2d");

R=function(m) { return m * Math.random()};
RAD=function(deg) { return deg / 180 * Math.PI};
LIMIT=function(x, min, max) {return x < min ? min : x > max ? max : x};
var SPEED = 0.01, HEIGHT = 11.0;

// Ground
var grd = ctx.createLinearGradient(0,0,0,H);
grd.addColorStop(0,"#88ccff");
grd.addColorStop(0.45,"#ffffee");
grd.addColorStop(0.5,"#80cc80");
grd.addColorStop(1,"#001100");
ctx.fillStyle = grd;
ctx.fillRect(0,0, W,H);


Plot = function(dist, side, h, s, alpha, diam)
{
    var x, y, a1,a2,scale = H/4 * 3.5 / dist, 
        x0 = side * scale + s * scale + W/2,
        y0 = H/2 + 2.5*scale - h*scale;
    
    k = dist
    if (diam > 0.05) {
        red = k*3|0;     
        green = k|0;
        a1=alpha+1
        a2=alpha-1
    }
    else
    {
        green= 80+(1-diam)*k*2|0;
        red = k|0;
        a1=0;
        a2=2*Math.PI;
    }
    diam *= scale;
    h *= scale;
    s *= scale;
    ctx.beginPath();
    ctx.arc(x0,y0,diam/2, a1,a2);//lpha-1, alpha+1);//0,2*Math.PI);
    ctx.fillStyle = 'rgb('+red+','+green+',0)';
    ctx.fill();
}

Grow = function(dist, side, h, s, alpha, grown)
{
    var diam, branchLength = 0.0;
    diam = (1.0 - grown / HEIGHT) * 0.5;
    do
    {
        alpha += R(RAD(3)) - RAD(1.5);
        h += Math.sin(alpha) * SPEED;
        s += Math.cos(alpha) * SPEED;
        branchLength += SPEED;
        grown += SPEED;
        diam = (1.0 - grown / HEIGHT) * 0.5;
        Plot(dist, side, h, s, alpha, diam);
    } while(branchLength < 5 * diam + R(6 * diam) && diam > 0.02);

    if (diam > 0.02)
    {
        var br = 0;

        if(R(10) > 2) br++,Grow(dist, side, h, s, alpha + RAD(15) + R(RAD(20)), grown);
        if(R(10) > 2) br++,Grow(dist, side, h, s, alpha - RAD(15) - R(RAD(20)), grown);
        if(R(10) < 2 || br == 0) Grow(dist, side, h, s, alpha - RAD(2.5) + R(RAD(5)), grown);
    }
}

trees=[]
for(i = 0; i < 300; ++i) trees.push({ z: 1+R(70), s:R(120)-60 });
trees.sort( function (a,b) { return a.z - b.z} );

Draw = function()
{
    t = trees.pop();
    if (t)
    {
        Grow(t.z, t.s, 0, 0, RAD(90), 0);
        setTimeout(Draw, 100);
    }
    else 
    {
        var e,c,d,p,i,l, img = ctx.getImageData(0,0,W,H);
        l = img.data.length;
        for (i = 0; i < l-W*4-4; i+=4)
        {
            c = (img.data[i]+img.data[i+1])/2|0
            c = img.data[i]
            d = c > 120 + R(16) ? 255 : 0
            e = c - d;
            img.data[i]=img.data[i+1]=img.data[i+2]=d
            c = (img.data[i+4]+img.data[i+5])/2|0
            
            c = LIMIT(c + ((e*7)>>4),0,255)
            img.data[i+4]=img.data[i+5]=img.data[i+6]=c
            p = i+W*4
            c = (img.data[p-4]+img.data[p-3])/2|0
            c = LIMIT(c + ((e*3)>>4),0,255)
            img.data[p-4]=img.data[p-3]=img.data[p-2]=c
            c = (img.data[p]+img.data[p+1])/2|0
            c = LIMIT(c+ ((e*5)>>4),0,255)
            img.data[p]=img.data[p+1]=img.data[p+2]=c
            c = (img.data[p+4]+img.data[p+5]*2)/3|0
            c = LIMIT(c + (e>>4),0,255)
            img.data[p+4]=img.data[p+5]=img.data[p+6]=c
    
        }
        bwcanvas.width = W;
        bwcanvas.height = H;
        var bwx = bwcanvas.getContext("2d");
        bwx.putImageData(img,0,0);
    }
}

setTimeout(Draw, 10);
<canvas id='bwcanvas'  width="2" height="2"></canvas>
<canvas id='canvas'  width="2" height="2"></canvas>

edc65

Posted 2014-08-06T16:48:37.783

Reputation: 31 086

2It's the ground, I think. Manuel's trees blend into the ground, creating a hazy, murky appearance. Your ground plane is lighter, giving a more airy appearance with higher contrast. Also, the solid-color sky and the distance fade in Manuel's pictures help create the appearance of an obscuring fog or haze. (Mind you, I rather like the different look your pictures have.) – Ilmari Karonen – 2014-08-09T10:43:56.283

Yup, this is an orchard to Manuel's deep woods. – shadowtalker – 2014-08-09T13:50:40.260

23

Context Free Art 3 (1133)

CF is a vector graphics rendering language, so I cannot avoid anti-alising. I worked that around by drawing square at the same place several (variable N) times. Fog is done by drawing small squares on random places.

startshape main

W = 80*0.6
H = 60*0.6

N = 3

CF::Background = [ b -1 ]
CF::Size = [ x 0 y -20 s W H ]
CF::Color = 0
CF::ColorDepth = 16
CF::MinimumSize = 0.6

shape main {
  transform [ z 0 y (H/2) b 1 ]
  loop 30 [ z -1 y -2 ] {
    loop 200000 []
      SQUARE1 [ s (0.1/3) x -W..W y -H..H z -0.5..0.5 ]
  }

  transform [ b -1 z 3 ]
  loop 14 [[ s 1.1 y -0.8..-1 s 0.6 z -3 ]] {
    loop 14 [] tree [ x (-30..-20) z (-3..3) ]
    loop 14 [] tree [ x (20..30) z (-3..3) ]
  }
}

shape tree {
  branch [ ]
}

shape branch
rule 7 {
  transform [ s (1..2) 1]
  SQUARE1 [ ]

  branch [ y (0.2..0.3) x (-0.05..0.05) s 0.994 r (-6..6) z (-0.3..0.3)  ]
  branch1 [ b 0.001 z -2 r -20..20 ]
}
rule 0.001 { }
rule 0.3 { branch [ r 4..20 ] }
rule 0.3 { branch [ r -4..-20 ] }

shape branch1
rule 90 { }
rule { branch [ r -22..22 s 0.8..1 ] }

path SQUARE1 {
  MOVETO( 0.5,  0.5)
  LINETO(-0.5,  0.5)
  LINETO(-0.5, -0.5)
  LINETO( 0.5, -0.5)
  CLOSEPOLY()
  loop N [] FILL()[]
}

shape S {
  SQUARE [ a -1 ]
  loop 1000 [ ] SQUARE [ x (-0.5..0.5) y (-0.5..0.5) s 0.01..0.001 ]
}

enter image description here

More renders using different numbers enter image description here enter image description here enter image description here

Ming-Tang

Posted 2014-08-06T16:48:37.783

Reputation: 5 383

1I get that there's no dithering option, but the output should still be black/white only. – Geobits – 2014-08-11T13:04:41.273

1Can't you find a way to dither? As it is, it's not valid answer. – edc65 – 2014-08-11T13:32:37.420

1You don't have to dither, but you do have to use only 2 colours - black and white. Use of greyscale does not match the rules. The question has been edited to remove the ambiguity. I'd recommend using some method to achieve this, whether dithering or not, so that you can edit your answer before too many more downvotes arrive. – trichoplax – 2014-08-11T15:04:06.777

Just as a suggestion in case it helps: You can use your current approach of transparent rectangles to achieve the black and white image, just instead of partial transparency, use full transparency in a grid pattern of some sort, so that only every other grid cell is transparent. – trichoplax – 2014-08-11T15:09:03.523

Vector graphic rendering has anti-alising, but I can find a way to bypass it by drawing single-pixel rectangles and stacking same shapes 10 times. I'm going to try that later. – Ming-Tang – 2014-08-11T17:35:06.247

1This is now much closer to being in the spirit of the question. – trichoplax – 2014-08-12T20:31:09.170

19

C: 301

This program creates a simple, abstract image in the PGM format. You can open it with GIMP.

int x,y,i,d,w;srand(time(NULL));unsigned char p[480000];FILE *f=fopen("a.pgm","w");fprintf(f,"P5\n800 600\n1\n");i=480000;while(i--)p[i]=i>240000|(i%800+i/800&3)!=0;i=100;while(i--){d=(11000-i*i)/99;y=300+1100/d;x=rand()%800;while(y--){w=300/d;while(w--)p[y*800+w+x]=0;}}fwrite(p, 1, 480000, f);fclose(f);

Here is an example run:Generated Image

Shujal

Posted 2014-08-06T16:48:37.783

Reputation: 687

34This looks more like a barcode than a forest :) – Sylwester – 2014-08-07T15:52:35.460

6bartree forest upside down – Fabricio – 2014-08-07T16:05:36.650

5When scrolling down I thought my browser failed to render the image correctly. I'd give you a ton of bonus points if your bartrees scanned with a barcode scanner yield the URL to this challange. Except that it wouldn't be a random forest. – nwp – 2014-08-08T08:00:46.877

6I scanned it and my barcode scanner cursed at me. thank you. – PlasmaHH – 2014-08-08T11:03:57.383

1

@nwp You could grab a random code from a UPC database, though. Scannable yet random :)

– Geobits – 2014-08-08T12:49:32.637

18

IFS with JAVA

This solution uses an Iterated Function System (IFS) to describe one (proto) tree. The IFS is applied 100 times (=forest). Before each tree is painted (planted into the forest) the IFS is altered slightly in place (random walk style). So each tree looks slightly different.

Pictures are from random seeds:

  • -824737443
  • -1220897877
  • -644492215
  • 1133984583

No dithering is needed.

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.*;

public class IFS {
    static Random random=new Random();
    static int BLACK = 0xff000000;
    static int treeCount = 100;
    static Random rand = new Random();
    static int Height = 600;
    static int Width = 800;
    static BufferedImage img = new BufferedImage(Width, Height, BufferedImage.TYPE_INT_ARGB);

    static double[][] ifs=new double[][] {//Tree 3 {; Paul Bourke  http://ecademy.agnesscott.edu/~lriddle/ifskit/gallery/bourke/bourke.ifs
       {0.050000,  0.000000,  0.000000,  0.600000,  0.000000,  0.000000,  0.028000},
       {0.050000,  0.000000,  0.000000, -0.500000,  0.000000,  1.000000,  0.023256},
       {0.459627, -0.321394,  0.385673,  0.383022,  0.000000,  0.600000,  0.279070},
       {0.469846, -0.153909,  0.171010,  0.422862,  0.000000,  1.100000,  0.209302},
       {0.433013,  0.275000, -0.250000,  0.476314,  0.000000,  1.000000,  0.555814 /*Paul Bourke has: 0.255814*/},
       {0.421325,  0.257115, -0.353533,  0.306418,  0.000000,  0.700000,  0.304651 /*Paul Bourke has: 0.204651*/},
    };

    public static void main(String[] args) {
        int seed=random.nextInt();
        //seed=-1220897877;
        random=new Random(seed);
        for (int t = 0; t < treeCount; t++) {
            for (int i = 0; i < ifs.length; i++) {
                for (int j = 0; j < ifs[0].length; j++) {
                    ifs[i][j]=R(ifs[i][j]);
                }
            }
            tree(random.nextDouble(), 0.1*random.nextDouble());
        }
        JFrame frame = new JFrame(""+seed) {
            public void paint(Graphics g) {
                setSize(800,600);
                g.drawImage(img,0,0,null);
            }
        };
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    private static void tree(double x0, double dist) {
        double y0=Math.atan(dist+0.01);
        double scale=Math.atan(0.01)/y0;
        double x=0;
        double y=0;
        for (int n = 0; n < 200000/Math.pow(20*dist+1, 8); n++) {
            int k = select(ifs);
            double newx=ifs[k][0]*x + ifs[k][1]*y + ifs[k][2];
            double newy=ifs[k][3]*x + ifs[k][4]*y + ifs[k][5];
            x=newx;
            y=newy;
            newx= Width*(0.5*scale*newx+x0);
            newy= Height*((1-0.5*scale*newy)-y0-0.1);
            if (0<=newx && newx<Width && 0<=newy && newy<Height) {
                img.setRGB((int)newx, (int)newy, BLACK);
            }
        }
    }

    private static double R(double x) {
        return (1+ 0.01*random.nextGaussian())*x;
    }

    private static int select(double[][] ifs) {
        int k;
        double sum=0;
        for(k=0; k<ifs.length; k++) {
            sum+=ifs[k][6];
        }
        double r=sum*random.nextDouble();
        sum=ifs[0][6];
        for(k=0; k<ifs.length-1 && r>sum; k++) {
            sum+=ifs[k+1][6];
        }
        return k;
    }
}

enter image description here enter image description here enter image description here enter image description here

Bob Genom

Posted 2014-08-06T16:48:37.783

Reputation: 846

Last images look best. However I think you should try fewer trees, because now there is always black unrecognizable area, but on edges it looks better. – Somnium – 2014-08-14T19:02:14.573

"Beauty is in the eye of the beholder." I tested a lot of variations. At the end, I came up with treeCount=100. Everybody else is welcome to copy and change my solution. – Bob Genom – 2014-08-18T20:43:29.757

15

I noticed a distinct lack of conifers here, so I hacked something together in Python.

from PIL import Image
import random

#Generates the seed for a tree
def makeSeed(y):
    random.seed()
    seed_x = random.randint(10, 590)
    seed_y = y
    width = random.randint(5, 10)
    height = random.randint(width*5, width*30)

    return (seed_x, seed_y, width, height)

#Grows the vertical components
def growStems(seed_data, pixel_field):
    seed_x = seed_data[0]
    seed_y = seed_data[1]
    width = seed_data[2]
    height = seed_data[3]
    for x in range(seed_x, seed_x+width):
        for y in range(seed_y-height, seed_y):
            pixel_field[x][y] = (0, 0, 0)
            #Dithering
            if seed_y > 300 and seed_y < 320:
                if (x+y)%2==0:
                    pixel_field[x][y] = (255, 255, 255)
            elif seed_y >= 320 and seed_y < 340:
                if (x+y)%4==0:
                    pixel_field[x][y] = (255, 255, 255)
            elif seed_y >= 340 and seed_y < 360:
                if (x+y)%8==0:
                    pixel_field[x][y] = (255, 255, 255)

    return pixel_field

#Grows the horizontal components
def growBranches(seed_data, pixel_field):
    seed_x = seed_data[0]
    seed_y = seed_data[1]
    width = seed_data[2]
    height = seed_data[3]
    branch_height = seed_y-height
    branch_width = width
    branch_length = 2
    max_prev = branch_length
    branches = []
    while(branch_height >= seed_y-height and branch_height < seed_y-(3*width) and branch_length < height/3):
        branches.append((branch_height, branch_width, branch_length))
        branch_height+= 4
        branch_length+=2
        #Gives the conifer unevenness to make it look more organic
        if random.randint(0,110) > 100 and branch_length > max_prev:
            max_prev = branch_length
            branch_length -= branch_length/4
    max_length = height/3


    for x in range(seed_x-max_length, seed_x+max_length):
        for y in range(seed_y-height, seed_y):
            for branch in branches:
                bh = branch[0]
                bw = branch[1]
                bl = branch[2]
                #Establishing whether a point is "in" a branch
                if x >= seed_x-bl+(width/2) and x <= seed_x+bl+(width/2):
                    if x > 1 and x < 599:
                        if y >= bh-(bw/2) and y <= bh+(bw/2):
                            if y < 400 and y > 0:
                                pixel_field[x][y] = (0, 0, 0)
                                #Dithering
                                if seed_y > 300 and seed_y < 320:
                                    if (x+y)%2==0:
                                        pixel_field[x][y] = (255, 255, 255)
                                elif seed_y >= 320 and seed_y < 340:
                                    if (x+y)%4==0:
                                        pixel_field[x][y] = (255, 255, 255)
                                elif seed_y >= 340 and seed_y < 360:
                                    if (x+y)%8==0:
                                        pixel_field[x][y] = (255, 255, 255)

    return pixel_field


def growTrees(n):
    pixel_field = [[(255, 255, 255) for y in range(400)] for x in range(600)]
    #Create the ground
    for i in range(600):    
        for j in range(400):
            if pixel_field[i][j]==(255,255,255) and j > 300:
                if (i+j)%2 == 0:
                    pixel_field[i][j]=(0,0,0)
    seed_ys=[]
    #Generates seeds for the trees and orders them back to front to make the dithering work
    for t in range(n):
        seed_ys.append(random.randint(300,390))
    seed_ys.sort()

    for s in range(len(seed_ys)):
        seed= makeSeed(seed_ys[s])
        pixel_field = growStems(seed, pixel_field)
        pixel_field = growBranches(seed, pixel_field)
    return pixel_field

def makeForest():
    forest = growTrees(25)
    img = Image.new( 'RGB', (600,400), "white") # create a new black image
    pixels = img.load() # create the pixel map
    for i in range(img.size[0]):    # for every pixel:
        for j in range(img.size[1]):
            if pixels[i,j]==(255,255,255) and j > 300:
                if (i+j)%2 == 0:
                    pixels[i,j]=(0,0,0)
            pixels[i,j] = forest[i][j] # set the colour accordingly

    img.save("Forest25.jpg")

if __name__ == '__main__':
    makeForest()

Forest with 5 trees Forest with 10 trees Forest with 25 trees

This was my first Code Golf, it was a lot of fun!

TApicella

Posted 2014-08-06T16:48:37.783

Reputation: 197

1Looks good! I like it. – TonySniper – 2014-08-14T22:04:33.320

5

This answer isn't as pretty as I hoped, but it's a stepping stone to a more 3D idea I'm working on, and I really like the idea of actually simulating which trees get resourcesenter image description here

package forest;

import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Forest extends Canvas{
    private int[] heights = new int[800];
    private BufferedImage buffered_image;
    File outputFile = new File("saved.png");
    Random r = new Random();
    public Forest() {
        buffered_image = new BufferedImage(800, 600,
                BufferedImage.TYPE_INT_RGB);
        for( int j = 0; j < 800; j++){
            heights[j] = -10000;
            for(int k = 0; k < 600; k++){
                buffered_image.setRGB(j, k, 0xFFFFFF);
            }
        }
        for(int i = 0; i < 7; i ++){
            heights[r.nextInt(800)] = 0;
        }

        this.setPreferredSize(new Dimension(800, 600));
        this.setSize(new Dimension(800, 600));
        for( int i = 0; i < 200000; i++){
            int x = r.nextInt(798) + 1;
            heights[x] =  Math.min(599, heights[x - 1] == heights[x + 1] ? heights[x] : Math.max(Math.max(heights[x - 1], heights[x]),heights[x + 1]) + 1);
            buffered_image.setRGB(x, Math.min(599, 600 - heights[x]), 0);
        } 

        try {

            ImageIO.write(buffered_image, "png", outputFile);
        } catch (IOException e) {

        }
        update();
    }
    public void repaint(){
        if(this.getGraphics() != null)
        paint(this.getGraphics());
    }


    public void paint(Graphics g) {
        g.drawImage(buffered_image, 0, 0, this);
    }

    public void update() {  
        repaint();
    }

    public static void main(String[] args) throws IOException {
        JFrame main_frame = new JFrame();
        main_frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel top_panel = new JPanel();
        top_panel.setLayout(new BorderLayout());
        Forest s = new Forest();
        top_panel.add(s, BorderLayout.CENTER);
        main_frame.setContentPane(top_panel);

        main_frame.pack();
        main_frame.setVisible(true);
    }

}

QuadmasterXLII

Posted 2014-08-06T16:48:37.783

Reputation: 881

6Maybe flip it vertically to make it similar to fir-trees? – Somnium – 2014-08-18T19:24:43.830