Computer Generated Textured Wall Paint

48

9

The paint on the walls in my room has a random, almost fractal-like, 3-dimensional texture:

Image A

In this challenge you will write a program that generates random images that look like they could be part of my walls.

Below I've collected 10 images of different spots on my walls. All have roughly the same lighting and all were taken with the camera one foot away from the wall. The borders were evenly cropped to make them 2048 by 2048 pixels, then they were scaled to 512 by 512. The image above is image A.

These are only thumbnails, click images to view at full size!

A: Image A B: Image B C: Image C D: Image D E: Image E

F: Image F G: Image G H: Image H I: Image I J: Image J

Your task is to write a program that takes in a positive integer from 1 to 216 as a random seed, and for each value generates a distinct image that looks like it could have been the "eleventh image" of my wall. If someone looking at my 10 images and a few of yours can't tell which were computer generated then you've done very well!

Please show off a few of your generated images so viewers can see them without having to run the code.

I realize that the lighting in my images is not perfectly uniform in intensity or color. I'm sorry for this but it's the best I could do without better lighting equipment. Your images do not need to have variable lighting (though they could). The texture is the more important thing to focus on.

Details

  • You may use image processing tools and libraries.
  • Take the input in any common way you desire (command line, stdin, obvious variable, etc).
  • The output image can be in any common lossless image file format, or it can just be displayed in a window/bowser.
  • You may programmatically analyze my 10 images but don't assume that everyone running your code has access to them.
  • You must generate the images programmatically. You may not hard-code a slight variant of one of my images or some other stock image. (People would vote you down for this anyway.)
  • You may use built-in pseudorandom number generators and assume the period is 216 or more.

Scoring

This is a popularity contest so the highest-voted answer wins.

Calvin's Hobbies

Posted 2015-01-06T20:35:54.717

Reputation: 84 000

PerlinNoise + truncation + shading – Octopus – 2015-01-07T06:55:17.510

21

I can't make wall images, so instead have a comic!

– Sp3000 – 2015-01-07T07:42:36.440

8

@Sp3000 That's more or less how it happened. Though If I has been looking up I'd have probably chosen my ceiling, which could work as well...

– Calvin's Hobbies – 2015-01-07T08:07:39.690

Answers

65

GLSL (+ JavaScript + WebGL)

enter image description here

Live demo | GitHub repository

How to use

Reload the page for a new random image. If you want to feed in a particular seed, open your browser's console and call drawScreen(seed). The console should display the seed used on load.

I haven't really tested this on a lot of platforms, so let me know if it doesn't work for you. Of course, your browser needs to support WebGL. Errors are displayed either in the column on the left, or in the browser's console (depending on the type of error).

New: You can now bring the walls to life a bit, by ticking the "movable light source" checkbox.

What is this sorcery?

I've got this WebGL boilerplate code floating around my GitHub account, which I use every now and then to quickly prototype some 2D graphics things in WebGL. With some shader magic, we can also make it look slightly 3D, so I thought that was the quickest way to get some nice effects going. Most of the setup is from that boilerplate code, and I'm considering that a library for this submission and won't include it in this post. If you're interested, have a look at the main.js on GitHub (and the other files in that folder).

All the JavaScript does is to set up a WebGL context, store the seed in a uniform for the shader, and then render a single quad over the entire context. The vertex shader is a simple passthrough shader, so all the magic happens in the fragment shader. That's why I called this a GLSL submission.

The largest part of the code is actually to generate the Simplex noise, which I found on GitHub. So I'm omitting that as well in the code listing below. The important part is, it defines a function snoise(vec2 coords) which returns simplex noise without using a texture or array lookup. It's not seeded at all, so the trick to getting different noise is to use the seed in determining where to do the lookup.

So here goes:

#ifdef GL_ES
precision mediump float;
#endif
#extension GL_OES_standard_derivatives : enable

uniform float uSeed;
uniform vec2 uLightPos;

varying vec4 vColor;
varying vec4 vPos;

/* ... functions to define snoise(vec2 v) ... */

float tanh(float x)
{
    return (exp(x)-exp(-x))/(exp(x)+exp(-x));
}

void main() {
    float seed = uSeed * 1.61803398875;
    // Light position based on seed passed in from JavaScript.
    vec3 light = vec3(uLightPos, 2.5);
    float x = vPos.x;
    float y = vPos.y;

    // Add a handful of octaves of simplex noise
    float noise = 0.0;
    for ( int i=4; i>0; i-- )
    {
        float oct = pow(2.0,float(i));
        noise += snoise(vec2(mod(seed,13.0)+x*oct,mod(seed*seed,11.0)+y*oct))/oct*4.0;
    }
    // Level off the noise with tanh
    noise = tanh(noise*noise)*2.0;
    // Add two smaller octaves to the top for extra graininess
    noise += sqrt(abs(noise))*snoise(vec2(mod(seed,13.0)+x*32.0,mod(seed*seed,11.0)+y*32.0))/32.0*3.0;
    noise += sqrt(abs(noise))*snoise(vec2(mod(seed,13.0)+x*64.0,mod(seed*seed,11.0)+y*64.0))/64.0*3.0;

    // And now, the lighting
    float dhdx = dFdx(noise);
    float dhdy = dFdy(noise);
    vec3 N = normalize(vec3(-dhdx, -dhdy, 1.0)); // surface normal
    vec3 L = normalize(light - vec3(vPos.x, vPos.y, 0.0)); // direction towards light source
    vec3 V = vec3(0.0, 0.0, 1.0); // direction towards viewpoint (straight up)
    float Rs = dot(2.0*N*dot(N,L) - L, V); // reflection coefficient of specular light, this is actually the dot product of V and and the direction of reflected light
    float k = 1.0; // specular exponent

    vec4 specularColor = vec4(0.4*pow(Rs,k));
    vec4 diffuseColor = vec4(0.508/4.0, 0.457/4.0, 0.417/4.0, 1.0)*dot(N,L);
    vec4 ambientColor = vec4(0.414/3.0, 0.379/3.0, 0.344/3.0, 1.0);

    gl_FragColor = specularColor + diffuseColor + ambientColor;
    gl_FragColor.a = 1.0;
}

That's it. I might add some more explanation tomorrow, but the basic idea is:

  • Choose a random light position.
  • Add up a few octaves of noise, to generate the fractal pattern.
  • Square the noise to keep the bottom rough.
  • Feed the noise through tanh to level off the top.
  • Add two more octaves for a little bit more texture on the top layer.
  • Compute the normals of the resulting surface.
  • Run a simple Phong shading over that surface, with specular and diffuse lights. The colours are chosen based on some random colours I picked from the first example image.

Martin Ender

Posted 2015-01-06T20:35:54.717

Reputation: 184 808

17This is more realistic than the wall itself :o – Quentin – 2015-01-07T17:09:22.750

1Some more "veins" / "snakes" / "worms" would make this picture more fitting for the "wall". But still nice. – Nova – 2015-01-07T23:28:29.407

33

Mathematica Spackling

The app below applies speckling to a random image. Clicking on "new patch" generates a new random image to work with, and then applies the effects according to current settings. The effects are oil painting, Gaussian filter, posterization, and embossing. Each effect can be independently tweaked. The seed for the random number generator can be any integer from 1 to 2^16.

Update: The Gaussian filter, which softens the edges, is now the last image effect applied. With this modification, the posterization effect was no longer needed and thus removed.

Manipulate[
 GaussianFilter[ImageEffect[ImageEffect[r, {"OilPainting", o}], {"Embossing", e, 1.8}], g],
 Button["new patch", (SeedRandom[seed] r = RandomImage[1, {400, 400}])], 
 {{o, 15, "oil painting"}, 1, 20, 1, ContinuousAction -> False, Appearance -> "Labeled"}, 
 {{e, 1.64, "embossing"}, 0, 5, ContinuousAction -> False, Appearance -> "Labeled"},
 {{g, 5, "Gaussian filter"}, 1, 12, 1, ContinuousAction -> False, Appearance -> "Labeled"},
 {{seed, 1}, 1, 2^16, 1, ContinuousAction -> False, Appearance -> "Labeled"}, 
 Initialization :> (SeedRandom[seed]; r = RandomImage[1, {400, 400}])]

end result


Explanation

The explanation is based on a slightly different version, in which posterization was employed and GaussianFilter was applied early on. But it still serves to clarify how each image effect alters an image. The end result is a paint texture with sharper edges. When the Gaussian filter is only applied at the end, the result will be smoother, as the above picture shows.

Let's look at some image effects, one at a time.

Generate a starting image.

 r = RandomImage[1, {200, 200}]

random image


Lena will show us how each image effect transforms a life-like picture.

Lena = ExampleData[{"TestImage", "Lena"}]

Lena


An oil painting effect applied to Lena.

ImageEffect[Lena, {"OilPainting", 8}]

lena oil

An oil painting effect applied to our random image. The effect was intensified (16 instead of 8).

 r1 = ImageEffect[r, {"OilPainting", 16}]

oil


A Gaussian filter effect applied to Lena (not to the oil painting effect version of Lena). The radius is 10 pixels. (In the final version, at the top of this entry, GaussianFilter is applied as the final effect.)

 GaussianFilter[Lena, 10]

lena gaussian.


A somewhat milder Gaussian filter effect applied to r1. The radius is 5 pixels.

 r2 = GaussianFilter[r1, 5]

gauss


An intense posterization effect applied to Lena. (In the final version of the app, I removed posterization. But we'll leave it in the analysis, since the examples in the analysis were based on an earlier version with posterization.)

 ImageEffect[Lena, {"Posterization", 2}]

lena posterize


A posterization effect applied to r2.

r3 = ImageEffect[r2, {"Posterization", 4}]

posterize


Embossing Lena

 ImageEffect[Lena, {"Embossing", 1.2, 1.8}]

lena embossing


Embossing r3 completes the image processing. This is intended to look something like the OP's ceiling.

 ceilingSample = ImageEffect[r3, {"Embossing", 1.2, 1.8}]

emboss


For the curious, here is Lena with the same image effects applied.

lena4

DavidC

Posted 2015-01-06T20:35:54.717

Reputation: 24 524

... there's a built-in to get the Lena image? LOL. – user253751 – 2015-01-07T03:14:10.780

7Yes. There are roughly 30 built-in pictures in Mathematica used for image processing tests. – DavidC – 2015-01-07T03:17:04.650

With a slight modification, one could feed RandomInteger a seed, thereby guaranteeing a particular output. Or do you mean something else, like a starting, non-Random image to which effects are applied? – DavidC – 2015-01-07T13:37:31.797

1It now accepts a seed from 1 to 2^16. – DavidC – 2015-01-07T19:10:39.173

1+1 because Lena will show us how each image effect transforms a life-like picture made me LOL. The weird thing is, the final Lena image appears to have an Aztec or Inca facing left, wearing a headdress and weilding a twig as if it were a gun. – Level River St – 2016-01-05T23:25:16.853

13

POV-Ray

A lot of golfing potential, run with povray /RENDER wall.pov -h512 -w512 -K234543 enter image description here

First it creates a random texture, but instead of stopping there it transforms the texture into a 3D-heightfield to make the radial shadows from the camera flash more realistic. And for good measure it adds another texture of little bumps on top.
The only way apart from hardcoding the random seed is to use the clock variable meant for animations, this is passed with the -K{number} flag

#default{ finish{ ambient 0.1 diffuse 0.9 }} 

camera {location y look_at 0 right x}
light_source {5*y color 1}

#declare R1 = seed (clock); // <= change this

#declare HF_Function  =
 function{
   pigment{
     crackle turbulence 0.6
     color_map{
       [0.00, color 0.01]
       [0.10, color 0.05]
       [0.30, color 0.20]
       [0.50, color 0.31]
       [0.70, color 0.28]
       [1.00, color 0.26]
     }// end color_map
    scale <0.25,0.005,0.25>*0.7 
    translate <500*rand(R1),0,500*rand(R1)>
   } // end pigment
 } // end function

height_field{
  function  512, 512
  { HF_Function(x,0,y).gray * .04 }
  smooth 
  texture { pigment{ color rgb<0.6,0.55,0.5>}
            normal { bumps 0.1 scale 0.005}
            finish { phong .1 phong_size 400}
          } // end of texture  
  translate< -0.5,0.0,-0.5>
}

DenDenDo

Posted 2015-01-06T20:35:54.717

Reputation: 2 811

There is no need to golf this at all. Nice results! :) – Martin Ender – 2015-01-14T15:54:28.670