Skip to content

Commit 4a261ee

Browse files
authored
Merge pull request #5 from ChrisPHP/procedural-textures
Adds procedural brick texture and more wang tile border options.
2 parents 7bfbd06 + 895caf9 commit 4a261ee

File tree

10 files changed

+707
-171
lines changed

10 files changed

+707
-171
lines changed

src/app.py

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from flask import Flask, render_template, request, send_file
2-
from werkzeug.utils import secure_filename
3-
from PIL import Image
2+
from PIL import Image, ImageColor
43
from io import BytesIO
54
import os
65
import json
@@ -22,7 +21,6 @@
2221

2322
pixel_gen = pixel_generator.PixelGenerator()
2423
proc_tex = procedural_textures.ProceduralTextures()
25-
wang_tile = wang_tile_generator.WangTilesGenerator()
2624

2725
@app.route('/')
2826
def home():
@@ -39,7 +37,55 @@ def wang_borders():
3937
border_size = int(request.form['border_size'])
4038
border_style = request.form['border_style']
4139

42-
new_img = wang_tile.generate_wang_borders(width, height, border_size, border_style, avg_colour)
40+
if border_style == 'brickborder':
41+
brick_border_width = int(request.form['brick_border_width'])
42+
brick_border_height = int(request.form['brick_border_height'])
43+
mortar_border = int(request.form['mortar_border'])
44+
45+
wang_tile = wang_tile_generator.WangTilesGenerator(border_dict={
46+
'brick_border_width': brick_border_width,
47+
'brick_border_height': brick_border_height,
48+
'mortar_border': mortar_border
49+
})
50+
new_img = wang_tile.generate_wang_borders(width, height, border_size, border_style, avg_colour)
51+
elif border_style == 'noise_mask':
52+
base_frequency = float(request.form['base_frequency'])
53+
cell_size =int(request.form['cell_size'])
54+
noise_octaves = int(request.form['noise_octaves'])
55+
noise_persistance = float(request.form['noise_persistance'])
56+
noise_lacunarity = float(request.form['noise_lacunarity'])
57+
58+
noise_2d = proc_tex.generate_noise([width,height],base_frequency,cell_size,noise_octaves,noise_persistance,noise_lacunarity)
59+
60+
#black_image = Image.new('RGBA', (width, height), color='black')
61+
wang_tile = wang_tile_generator.WangTilesGenerator(noise_img=noise_2d, border_dict={'border_size': border_size})
62+
new_img = wang_tile.generate_mask_border(img)
63+
elif border_style == 'noise':
64+
colour = request.form['colours']
65+
colours_json = json.loads(colour)
66+
noise_params = {
67+
"base_frequency": float(request.form['base_frequency']),
68+
"cell_size": int(request.form['cell_size']),
69+
"noise_octaves": int(request.form['noise_octaves']),
70+
"noise_persistance": float(request.form['noise_persistance']),
71+
"noise_lacunarity": float(request.form['noise_lacunarity'])
72+
}
73+
thresholds = [
74+
float(request.form['threshold_1']),
75+
float(request.form['threshold_2']),
76+
float(request.form['threshold_3']),
77+
float(request.form['threshold_4']),
78+
float(request.form['threshold_5'])
79+
]
80+
81+
new_img = proc_tex.noise_texture([width, height], colours_json, thresholds, noise_params)
82+
new_img = new_img.convert('RGBA')
83+
84+
wang_tile = wang_tile_generator.WangTilesGenerator(input_border_img=new_img.load())
85+
new_img = wang_tile.generate_wang_borders(width, height, border_size, border_style, avg_colour)
86+
else:
87+
wang_tile = wang_tile_generator.WangTilesGenerator()
88+
new_img = wang_tile.generate_wang_borders(width, height, border_size, border_style, avg_colour)
4389
img_io = BytesIO()
4490
new_img.save(img_io, 'PNG')
4591
img_io.seek(0)
@@ -50,7 +96,9 @@ def wang_borders():
5096
def wang_tiles():
5197
img = verify_file(request)
5298

53-
new_img = wang_tile.generate_wang_tile(img)
99+
wang_tile = wang_tile_generator.WangTilesGenerator()
100+
101+
new_img = wang_tile.generate_wang_tile(img, False)
54102
img_io = BytesIO()
55103
new_img.save(img_io, 'PNG')
56104
img_io.seek(0)
@@ -75,13 +123,45 @@ def noise_image():
75123

76124
@app.route('/procedural', methods=['POST'])
77125
def procedural_texture():
78-
base_frequency = float(request.form['base_frequency'])
79-
cell_size = int(request.form['cell_size'])
80-
noise_octaves = int(request.form['noise_octaves'])
81-
noise_persistance = float(request.form['noise_persistance'])
82-
noise_lacunarity = float(request.form['noise_lacunarity'])
126+
tile_width = int(request.form['tile_width'])
127+
tile_height = int(request.form['tile_height'])
128+
texture_type = request.form['texture_type']
129+
130+
colour = request.form['colours']
131+
colours_json = json.loads(colour)
83132

84-
new_img = proc_tex.noise_texture([300, 300], base_frequency, cell_size, noise_octaves, noise_persistance, noise_lacunarity)
133+
noise_params = {
134+
"base_frequency": float(request.form['base_frequency']),
135+
"cell_size": int(request.form['cell_size']),
136+
"noise_octaves": int(request.form['noise_octaves']),
137+
"noise_persistance": float(request.form['noise_persistance']),
138+
"noise_lacunarity": float(request.form['noise_lacunarity'])
139+
}
140+
if texture_type == 'noise':
141+
thresholds = [
142+
float(request.form['threshold_1']),
143+
float(request.form['threshold_2']),
144+
float(request.form['threshold_3']),
145+
float(request.form['threshold_4']),
146+
float(request.form['threshold_5'])
147+
]
148+
149+
new_img = proc_tex.noise_texture([tile_width, tile_height], colours_json, thresholds, noise_params)
150+
else:
151+
mortar_colour = request.form['mortar_colour']
152+
brick_width = int(request.form['brick_width'])
153+
brick_height = int(request.form['brick_height'])
154+
mortar_size = int(request.form['mortar_size'])
155+
threshold = float(request.form['threshold'])
156+
157+
new_img = proc_tex.generate_brick_texture([tile_width, tile_height],
158+
colours_json,
159+
noise_params,
160+
[brick_width, brick_height],
161+
mortar_size,
162+
ImageColor.getrgb(mortar_colour),
163+
threshold)
164+
85165
img_io = BytesIO()
86166
new_img.save(img_io, 'PNG')
87167
img_io.seek(0)

src/pixel_generator/__init__.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,27 +92,29 @@ def generate_seamless_texture(self, img):
9292
result_image = image_to_seamless(img, overlap=0.1)
9393
return result_image
9494

95-
def process_image(self, img, num_colours, pixel_size):
96-
img = img.convert('RGB')
97-
width, height = img.size
98-
99-
new_width = width - (width % pixel_size)
100-
new_height = height - (height % pixel_size)
101-
img = img.resize((new_width, new_height))
102-
103-
img_array = np.array(img)
104-
95+
def get_colour_palette(self, img_array, num_colours=6):
10596
pixels = img_array.reshape((-1,4))
106-
10797
rand_int = np.random.randint(0, 2**32)
10898
kmeans = KMeans(n_clusters=num_colours, random_state=rand_int)
10999
kmeans.fit(pixels)
110-
111100
colours = kmeans.cluster_centers_.astype(int)
112101

113102
labels = kmeans.predict(pixels)
114103
quantized = colours[labels]
115104

105+
return quantized, colours
106+
107+
def process_image(self, img, num_colours, pixel_size):
108+
img = img.convert('RGBA')
109+
width, height = img.size
110+
111+
new_width = width - (width % pixel_size)
112+
new_height = height - (height % pixel_size)
113+
img = img.resize((new_width, new_height))
114+
115+
img_array = np.array(img)
116+
quantized, _ = self.get_colour_palette(img_array, num_colours)
117+
116118
quantized = quantized.reshape(img_array.shape)
117119
for i in range(0, new_height, pixel_size):
118120
for j in range(0, new_width, pixel_size):

src/procedural_textures/__init__.py

Lines changed: 79 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
from PIL import Image
33
import numpy as np
44

5+
from . import brick
6+
7+
58
class ProceduralTextures:
69
def improved_noise(self, baseFrequency, cellSize, octaves, persistance, lacunarity, coords):
710
totalNoise = 0
@@ -29,7 +32,8 @@ def normalize(arr):
2932
scale = 1
3033
width = img_size[0]
3134
height = img_size[1]
32-
opensimplex.seed(100)
35+
rand_int = np.random.randint(0, 2**32)
36+
opensimplex.seed(rand_int)
3337
result = np.zeros((width, height))
3438

3539
for y in range(width):
@@ -45,38 +49,94 @@ def normalize(arr):
4549

4650
return noise_2d
4751

48-
def noise_texture(self, img_size, baseFrequency, cellSize, octaves, persistance, lacunarity):
49-
noise_array = self.generate_noise(img_size, baseFrequency, cellSize, octaves, persistance, lacunarity)
52+
def noise_texture(self, img_size, colours, thresholds, noise_params):
53+
noise_array = self.generate_noise(img_size,
54+
noise_params['base_frequency'],
55+
noise_params['cell_size'],
56+
noise_params['noise_octaves'],
57+
noise_params['noise_persistance'],
58+
noise_params['noise_lacunarity'])
5059

5160
width = img_size[0]
5261
height = img_size[1]
5362

54-
brown = np.array([165, 42, 42]) / 255.0 # RGB for brown
55-
green = np.array([0, 255, 0]) / 255.0 # RGB for green
63+
new_colours = np.array(colours) / 255.0
64+
65+
thresholds = np.sort(np.array(thresholds))
5666

5767
color_noise = np.zeros((*(width, height), 3))
5868
for i in range(3):
59-
color_noise[:,:,i] = noise_array * (green[i] - brown[i]) + brown[i]
69+
color_noise[:,:,i] = np.interp(noise_array, thresholds, new_colours[:, i])
6070

61-
#pic = np.array(noise_2d)
62-
#pic = (pic - pic.min()) / (pic.max() - pic.min())
6371
pic = (color_noise * 255).astype(np.uint8)
6472

6573
return Image.fromarray(pic, mode="RGB")
74+
75+
def apply_linear_gradient(self, noise_array, stop_point_x, stop_point_y, direction='horizontal'):
76+
rows, cols = noise_array.shape
77+
if direction in ['horizontal', 'horizontal_rev']:
78+
if direction == 'horizontal':
79+
gradient = np.linspace(0, stop_point_x, cols)
80+
gradient = np.tile(gradient, (rows, 1))
81+
else:
82+
gradient = np.linspace(stop_point_x, 0, cols)
83+
gradient = np.tile(gradient, (rows, 1))
84+
elif direction in ['vertical', 'vertical_rev']:
85+
if direction == 'vertical':
86+
gradient = np.linspace(0, stop_point_y, rows)
87+
gradient = np.tile(gradient, (cols, 1)).T
88+
else:
89+
gradient = np.linspace(stop_point_y, 0, rows)
90+
gradient = np.tile(gradient, (cols, 1)).T
91+
elif direction in ['diagonal_tl', 'diagonal_tr', 'diagonal_bl', 'diagonal_br']:
92+
x = np.linspace(0, stop_point_x, cols)
93+
y = np.linspace(0, stop_point_y, rows)
94+
xx, yy = np.meshgrid(x, y)
95+
if direction == 'diagonal_tl':
96+
gradient = (xx + yy) / 2
97+
elif direction == 'diagonal_tr':
98+
gradient = (1 - xx + yy) / 2
99+
elif direction == 'diagonal_bl':
100+
gradient = (xx + 1 - yy) / 2
101+
else: # diagonal_br
102+
gradient = (2 - xx - yy) / 2
103+
else:
104+
raise ValueError("Direction must be 'horizontal' or 'vertical'")
105+
106+
return noise_array * gradient
107+
66108
def noiseify_image(self, img, baseFrequency, cellSize, octaves, persistance, lacunarity):
67-
img = img.convert('RGB')
68-
width, height = img.size
109+
img = img.convert('RGBA')
110+
img_array = np.array(img)
69111

70-
new_width = round(width / 8)
71-
new_height = round(height / 8)
112+
noise_array = self.generate_noise([200, 200], baseFrequency, cellSize, octaves, persistance, lacunarity)
113+
noise_array = self.apply_linear_gradient(noise_array)
72114

73-
noise_array = self.generate_noise([new_width, new_height], baseFrequency, cellSize, octaves, persistance, lacunarity)
74115

75-
img_array = np.array(img)
116+
scaled_noise = Image.fromarray((noise_array * 255).astype(np.uint8))
117+
scaled_noise = scaled_noise.resize(img.size, Image.LANCZOS)
118+
scaled_noise = np.array(scaled_noise) / 255.0
119+
120+
alpha_mask = (scaled_noise > 0.3).astype(float)
121+
img_array[:, :, 3] = img_array[:, :, 3] * alpha_mask
122+
123+
124+
return Image.fromarray(img_array.astype('uint8'))
76125

77-
for i in range(height-1):
78-
for j in range(width-1):
79-
if noise_array[round(i/8)][round(j/8)] < 0.5:
80-
img_array[i, j] = (0,0,0)
81126

82-
return Image.fromarray(img_array.astype('uint8'))
127+
def generate_brick_texture(self, img_size, colours, noise_params, brick_size, mortar_size, mortar_colour, threshold):
128+
colours = np.array(colours)
129+
colours = np.append(colours, np.full((colours.shape[0], 1), 255), axis=1)
130+
noise_array = self.generate_noise(img_size,
131+
noise_params['base_frequency'],
132+
noise_params['cell_size'],
133+
noise_params['noise_octaves'],
134+
noise_params['noise_persistance'],
135+
noise_params['noise_lacunarity'])
136+
return brick.create_brick_texture(img_size,
137+
colours,
138+
noise_array,
139+
brick_size,
140+
mortar_size,
141+
mortar_colour,
142+
threshold)

0 commit comments

Comments
 (0)