dopairb 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,818 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dopairb
4
+ module Gallery
5
+ # Impressionists and Post-Impressionists. Painters live in Paint as
6
+ # imp_* methods so they can use early returns.
7
+ module Paint
8
+ module_function
9
+
10
+ # Distance from (u, v) to the segment a-b (sx scales the u axis).
11
+ def imp_seg(u, v, ax, ay, bx, by, sx = 1.0)
12
+ dx = (bx - ax) * sx
13
+ dy = by - ay
14
+ px = (u - ax) * sx
15
+ py = v - ay
16
+ t = ((px * dx + py * dy) / (dx * dx + dy * dy + 1e-12)).clamp(0.0, 1.0)
17
+ Math.hypot(px - dx * t, py - dy * t)
18
+ end
19
+
20
+ # A standing figure: :head, :body or nil. top = top of the head, h = height, w = hem width.
21
+ def imp_figure(u, v, cx, top, h, w)
22
+ hr = h * 0.09
23
+ return :head if ellipse(u, v, cx, top + hr, hr * 0.8, hr) < 1
24
+ t = (v - top - hr * 1.8) / (h - hr * 1.8)
25
+ return nil unless t.between?(0.0, 1.0)
26
+ (u - cx).abs < w * (0.3 + 0.7 * t) * 0.5 ? :body : nil
27
+ end
28
+
29
+ # Thin dark line just outside a polygon.
30
+ def imp_outline?(u, v, pts, e = 0.014)
31
+ inside?(u + e, v, pts) || inside?(u - e, v, pts) || inside?(u, v + e, pts) || inside?(u, v - e, pts)
32
+ end
33
+
34
+ # Monet, Impression, Sunrise
35
+ def imp_impression_sunrise(u, v)
36
+ sx = 0.44
37
+ sy = 0.34
38
+ horizon = 0.50
39
+ ds = dist(u, v, sx, sy, 1.31)
40
+ return rgb(0xff6020) if ds < 0.030
41
+ # boats: hull + standing rower
42
+ [[0.36, 0.76, 0.075, 0.022, 0.0], [0.20, 0.64, 0.045, 0.015, 0.35], [0.53, 0.62, 0.04, 0.013, 0.45]].each do |bx, by, rx, ry, fade|
43
+ dark = mix([25, 35, 50], [90, 110, 125], fade)
44
+ return dark if ellipse(u, v, bx, by, rx, ry) < 1 && v > by - ry * 0.3
45
+ return dark if (u - bx - rx * 0.2).abs < rx * 0.16 && v < by && v > by - ry * 3.2
46
+ end
47
+ if v > horizon
48
+ # broken orange reflection streaks
49
+ row = (v * 42).floor
50
+ frac = v * 42 - row
51
+ off = (noise(row, 3) - 0.5) * 0.05
52
+ w = (0.02 + (v - horizon) * 0.12) * (noise(row, 7) > 0.2 ? 1 : 0)
53
+ if (u - sx - off).abs < w && frac.between?(0.15, 0.8) && v < 0.97
54
+ return mix([255, 120, 40], [230, 80, 30], noise(row, 9))
55
+ end
56
+ base = mix([95, 125, 135], [60, 90, 105], (v - horizon) * 2)
57
+ stroke = brush(u, v, angle: 0.05, freq: 30.0, wobble: 0.6)
58
+ base = mix(base, [140, 160, 160], 0.35) if stroke > 0.6
59
+ base = mix(base, [50, 75, 90], 0.3) if stroke < -0.7
60
+ return jitter(base, u, v, 10)
61
+ end
62
+ # cranes, masts and chimneys, faint in the haze
63
+ [[0.06, 0.12, 0.004], [0.10, 0.22, 0.004], [0.62, 0.18, 0.005], [0.70, 0.10, 0.004], [0.83, 0.22, 0.004], [0.90, 0.16, 0.004]].each do |mx, top, half|
64
+ return mix([110, 125, 135], [140, 150, 155], 0.3) if (u - mx).abs < half + 0.003 && v > top
65
+ end
66
+ return mix([110, 125, 135], [135, 145, 150], 0.4) if v > 0.40 && (u < 0.30 || u > 0.66) && vnoise(u, v, 10) > 0.4
67
+ sky = mix([125, 145, 155], [160, 165, 160], v * 1.5)
68
+ glow = Math.exp(-ds * 7)
69
+ sky = mix(sky, [235, 150, 100], glow * 0.8)
70
+ sky = mix(sky, [210, 150, 125], 0.25) if v < 0.25 && brush(u, v, angle: -0.15, freq: 12.0) > 0.6 && u > 0.3
71
+ jitter(sky, u, v, 10)
72
+ end
73
+
74
+ # Monet, Water Lilies
75
+ def imp_water_lilies(u, v)
76
+ clusters = [[0.22, 0.10, 0.20, 0.035], [0.75, 0.16, 0.22, 0.04], [0.48, 0.36, 0.28, 0.055],
77
+ [0.14, 0.58, 0.20, 0.07], [0.78, 0.62, 0.24, 0.08], [0.42, 0.88, 0.32, 0.09]]
78
+ clusters.each_with_index do |(cx, cy, rx, ry), i|
79
+ e = ellipse(u, v, cx, cy, rx, ry) + (vnoise(u, v, 14.0) - 0.5) * 0.9
80
+ next unless e < 1
81
+ # flowers
82
+ return [250, 245, 245] if dist(u, v, cx + rx * 0.25, cy - ry * 0.1, 1.1) < 0.022 + ry * 0.15 && i.even?
83
+ return [240, 140, 170] if dist(u, v, cx - rx * 0.35, cy, 1.1) < 0.02 + ry * 0.2
84
+ return [250, 220, 230] if dist(u, v, cx + rx * 0.05, cy + ry * 0.2, 1.1) < 0.012 + ry * 0.15 && i.odd?
85
+ pad = brush(u, v, angle: 0.0, freq: 70.0, wobble: 0.8) > 0 ? [90, 150, 70] : [130, 175, 80]
86
+ pad = mix(pad, [200, 200, 90], 0.4) if vnoise(u, v, 25.0) > 0.7
87
+ return pad
88
+ end
89
+ # pond: blue-violet sky reflections with green vertical willow reflections
90
+ base = ramp(v, [90, 110, 170], [120, 140, 200], [100, 120, 180], [70, 90, 150])
91
+ refl = Math.sin(u * 22 + vnoise(u, v, 4.0) * 4)
92
+ base = mix(base, [60, 110, 90], 0.55) if refl > 0.5
93
+ base = mix(base, [190, 190, 220], 0.4) if vnoise(u * 0.3, v * 3, 6.0) > 0.68
94
+ base = mix(base, [150, 120, 190], 0.3) if brush(u, v, angle: 0.0, freq: 45.0) > 0.7
95
+ jitter(base, u, v, 12)
96
+ end
97
+
98
+ # Monet, Woman with a Parasol
99
+ def imp_woman_with_a_parasol(u, v)
100
+ ground = 0.83 + 0.04 * Math.sin(u * 4 + 0.5)
101
+ # grass in the foreground
102
+ if v > ground
103
+ g = brush(u, v, angle: 1.1, freq: 40.0, wobble: 0.8) > 0.2 ? [120, 160, 60] : [70, 120, 50]
104
+ g = mix(g, [210, 200, 90], 0.5) if vnoise(u, v, 18.0) > 0.65
105
+ return g
106
+ end
107
+ # the boy at right, half hidden by the slope
108
+ return [235, 230, 200] if ellipse(u, v, 0.82, 0.715, 0.03, 0.018) < 1
109
+ return [200, 160, 130] if ellipse(u, v, 0.82, 0.745, 0.022, 0.025) < 1
110
+ return [220, 210, 180] if (u - 0.82).abs < 0.035 && v > 0.77
111
+ # parasol, tilted (its right side lower)
112
+ pv = v - (u - 0.42) * 0.22
113
+ if pv < 0.245 && ellipse(u, pv, 0.42, 0.245, 0.27, 0.17) < 1
114
+ shade = smooth(0.15, 0.68, u)
115
+ return mix([110, 170, 90], [40, 90, 60], shade) if brush(u, v, angle: 0.8, freq: 30.0) > -0.3
116
+ return [70, 120, 70]
117
+ end
118
+ return mix([200, 180, 90], [100, 110, 60], u) if pv >= 0.245 && ellipse(u, pv, 0.42, 0.245, 0.27, 0.045) < 1
119
+ # handle
120
+ return [70, 60, 50] if imp_seg(u, v, 0.43, 0.25, 0.47, 0.44) < 0.008
121
+ # head with hat and blowing veil
122
+ return [190, 160, 110] if ellipse(u, v, 0.48, 0.355, 0.035, 0.035) < 1
123
+ return [235, 240, 245] if ellipse(u, v, 0.40, 0.38, 0.08, 0.018 + (0.48 - u).abs * 0.05) < 1 && u < 0.47
124
+ # white dress, blown to the left
125
+ dress = [[0.45, 0.385], [0.52, 0.39], [0.57, 0.52], [0.61, 0.90], [0.36, 0.90], [0.33, 0.70], [0.40, 0.52]]
126
+ if inside?(u, v, dress)
127
+ side = smooth(0.38, 0.58, u)
128
+ c = mix([170, 190, 225], [250, 245, 220], side)
129
+ c = mix(c, [235, 225, 160], 0.4) if brush(u, v, angle: 0.4, freq: 35.0) > 0.6 && u > 0.5
130
+ return c
131
+ end
132
+ # sky with wind-blown clouds
133
+ sky = mix([90, 145, 210], [175, 205, 235], smooth(0.0, 0.85, v))
134
+ cloud = vnoise(u * 1.4 + v * 0.6, v, 4.0) + brush(u, v, angle: -0.6, freq: 18.0) * 0.12
135
+ sky = mix(sky, [245, 245, 240], smooth(0.56, 0.74, cloud))
136
+ jitter(sky, u, v, 8)
137
+ end
138
+
139
+ # Monet, The Water Lily Pond (Japanese Footbridge)
140
+ def imp_japanese_footbridge(u, v)
141
+ deck = 0.44 - 0.12 * Math.sin(Math::PI * u)
142
+ # the arched green bridge with its railing
143
+ return mix([160, 190, 110], [100, 150, 80], u) if v > deck && v < deck + 0.04
144
+ return [80, 130, 70] if v > deck + 0.04 && v < deck + 0.065
145
+ top = deck - 0.12
146
+ return [190, 210, 140] if (v - top).abs < 0.013
147
+ return [150, 185, 110] if (v - (deck - 0.06)).abs < 0.01
148
+ return [130, 175, 100] if v > top && v < deck && ((u * 12.5) % 1.0) < 0.18
149
+ if v > 0.50 + 0.04 * Math.sin(u * 6)
150
+ # pond with rows of lily pads
151
+ row = ((v - 0.5) * 22).floor
152
+ rh = (v - 0.5) * 22 - row
153
+ cw = 0.10 + row * 0.01
154
+ cell = ((u + noise(row, 1) * 0.3) / cw).floor
155
+ cu = (u + noise(row, 1) * 0.3) / cw - cell
156
+ if noise(cell, row) > 0.3 && ((cu - 0.5) / 0.45)**2 + ((rh - 0.5) / 0.4)**2 < 1
157
+ return [235, 150, 170] if noise(cell, row + 50) > 0.85 && (cu - 0.5).abs < 0.15
158
+ return [250, 245, 240] if noise(cell, row + 70) > 0.85 && (cu - 0.5).abs < 0.15
159
+ return mix([140, 180, 70], [90, 150, 60], noise(cell, row + 9))
160
+ end
161
+ water = mix([40, 80, 60], [70, 110, 90], vnoise(u, v, 6.0))
162
+ water = mix(water, [150, 170, 120], 0.4) if Math.sin(u * 40 + vnoise(u, v, 5.0) * 3) > 0.75
163
+ return water
164
+ end
165
+ # dense willow and bamboo foliage
166
+ f = vnoise(u, v, 16.0)
167
+ stroke = brush(u, v, angle: 1.5, freq: 50.0, wobble: 0.6)
168
+ c = ramp(f, [30, 60, 35], [60, 110, 50], [110, 160, 70], [180, 200, 90])
169
+ c = mix(c, [40, 80, 70], 0.4) if stroke > 0.6
170
+ jitter(c, u, v, 14)
171
+ end
172
+
173
+ # Monet, Poppy Field
174
+ def imp_poppy_field(u, v)
175
+ horizon = 0.30 + 0.20 * u
176
+ # foreground pair: woman with blue parasol and child
177
+ return [60, 80, 130] if ellipse(u, v, 0.70, 0.50, 0.065, 0.045) < 1 && v < 0.52
178
+ case imp_figure(u, v, 0.71, 0.53, 0.30, 0.13)
179
+ when :head then return [200, 160, 130]
180
+ when :body then return [45, 45, 60]
181
+ end
182
+ case imp_figure(u, v, 0.79, 0.66, 0.18, 0.08)
183
+ when :head then return [220, 200, 140]
184
+ when :body then return [230, 225, 205]
185
+ end
186
+ # background pair high on the hill
187
+ return [70, 100, 160] if ellipse(u, v, 0.30, 0.37, 0.035, 0.025) < 1 && v < 0.38
188
+ case imp_figure(u, v, 0.30, 0.38, 0.14, 0.06)
189
+ when :head then return [200, 170, 140]
190
+ when :body then return [50, 50, 60]
191
+ end
192
+ case imp_figure(u, v, 0.36, 0.44, 0.08, 0.04)
193
+ when :head, :body then return [210, 200, 180]
194
+ end
195
+ if v > horizon
196
+ ground = mix([140, 170, 80], [90, 140, 60], (v - horizon) * 1.5)
197
+ ground = mix(ground, [190, 190, 110], 0.35) if vnoise(u, v, 8.0) > 0.65
198
+ poppy_zone = u < 0.70 - (v - 0.4) * 0.6 + (vnoise(u, v, 5.0) - 0.5) * 0.2
199
+ cell = noise((u * 64).floor, (v * 48).floor)
200
+ return mix([230, 60, 30], [190, 30, 25], noise((u * 64).floor + 7, (v * 48).floor)) if poppy_zone && cell > 0.6
201
+ return [220, 60, 35] if cell > 0.95
202
+ return jitter(ground, u, v, 12)
203
+ end
204
+ # row of trees and a red-roofed house on the horizon
205
+ tree_top = horizon - 0.07 - vnoise(u, 0.0, 12.0) * 0.08
206
+ if u > 0.35 && u < 0.97 && v > tree_top
207
+ return [210, 120, 80] if (u - 0.62).abs < 0.025 && v > horizon - 0.04
208
+ return mix([40, 70, 50], [80, 110, 70], vnoise(u, v, 20.0))
209
+ end
210
+ sky = mix([110, 150, 210], [190, 210, 230], v * 2)
211
+ sky = mix(sky, [245, 245, 240], smooth(0.5, 0.7, vnoise(u * 1.3, v, 5.0)))
212
+ jitter(sky, u, v, 8)
213
+ end
214
+
215
+ # Renoir, Bal du moulin de la Galette
216
+ def imp_bal_du_moulin(u, v)
217
+ light = vnoise(u, v, 13.0) > 0.66
218
+ dapple = ->(c) { light ? mix(c, [250, 235, 200], 0.45) : c }
219
+ # foreground: woman in pink-and-blue stripes
220
+ return [60, 40, 30] if ellipse(u, v, 0.30, 0.45, 0.035, 0.045) < 1 && v < 0.44
221
+ return [235, 195, 170] if ellipse(u, v, 0.30, 0.47, 0.03, 0.04) < 1
222
+ stripe_dress = [[0.27, 0.51], [0.33, 0.51], [0.37, 0.70], [0.42, 1.0], [0.18, 1.0], [0.24, 0.70]]
223
+ if inside?(u, v, stripe_dress)
224
+ return dapple.(Math.sin(u * 160 + v * 20) > 0 ? [235, 190, 200] : [80, 100, 170])
225
+ end
226
+ # seated group at right: a man in dark coat, a striped woman, the table
227
+ return [225, 210, 120] if ellipse(u, v, 0.78, 0.53, 0.06, 0.02) < 1
228
+ return [225, 190, 160] if ellipse(u, v, 0.78, 0.58, 0.03, 0.04) < 1
229
+ return dapple.([30, 30, 45]) if inside?(u, v, [[0.72, 0.62], [0.84, 0.62], [0.92, 1.0], [0.66, 1.0]])
230
+ return [235, 195, 175] if ellipse(u, v, 0.58, 0.60, 0.028, 0.036) < 1
231
+ if inside?(u, v, [[0.55, 0.64], [0.62, 0.64], [0.66, 1.0], [0.50, 1.0]])
232
+ return dapple.(Math.sin(u * 160) > 0 ? [240, 220, 225] : [90, 110, 180])
233
+ end
234
+ return [235, 235, 220] if ellipse(u, v, 0.95, 0.80, 0.08, 0.03) < 1
235
+ # lamps on posts under the trees
236
+ [[0.16, 0.10], [0.40, 0.06], [0.62, 0.12], [0.86, 0.07]].each do |lx, ly|
237
+ d = dist(u, v, lx, ly, 1.33)
238
+ return [250, 245, 220] if d < 0.025
239
+ return [235, 220, 170] if d < 0.035
240
+ end
241
+ # the dancing crowd
242
+ if v > 0.20 && v < 0.80
243
+ # two staggered rows of dancers: nearer (bigger) first
244
+ [[11.0, 0.34, 0.032, 0.37], [19.0, 0.23, 0.02, 0.0]].each_with_index do |(cols, base, hr, shift), row|
245
+ col = (u * cols + shift).floor
246
+ next if noise(col, row + 20) < 0.25
247
+ cx = (col + 0.5 - shift) / cols + (noise(col, row + 30) - 0.5) * 0.3 / cols
248
+ hy = base + noise(col, row + 1) * 0.06
249
+ return [230, 190, 160] if ellipse(u, v, cx, hy, hr * 0.65, hr) < 1
250
+ return [225, 205, 120] if noise(col, row + 4) > 0.6 && ellipse(u, v, cx, hy - hr * 0.9, hr * 1.2, hr * 0.45) < 1
251
+ return [35, 30, 30] if ellipse(u, v, cx, hy - hr * 0.7, hr * 0.7, hr * 0.5) < 1
252
+ next unless v > hy + hr && (u - cx).abs < hr * 1.3 + (v - hy) * 0.12
253
+ n = noise(col, row + 2)
254
+ cloth = if n < 0.45 then [30, 30, 45]
255
+ elsif n < 0.65 then [60, 80, 150]
256
+ elsif n < 0.8 then [230, 200, 210]
257
+ else [40, 60, 90]
258
+ end
259
+ return dapple.(cloth)
260
+ end
261
+ end
262
+ if v < 0.30
263
+ leaves = mix([40, 70, 60], [90, 120, 90], vnoise(u, v, 18.0))
264
+ return dapple.(leaves)
265
+ end
266
+ jitter(dapple.(mix([150, 145, 150], [175, 155, 140], v)), u, v, 14)
267
+ end
268
+
269
+ DANCERS = [
270
+ [0.06, 0.28, 1.0, [60, 150, 90]], [0.15, 0.30, 1.0, [70, 110, 200]], [0.24, 0.28, 0.95, [230, 190, 60]],
271
+ [0.70, 0.28, 0.9, [210, 60, 50]], [0.80, 0.30, 0.9, [80, 120, 200]], [0.90, 0.27, 0.9, [230, 150, 170]],
272
+ [0.84, 0.56, 1.5, [70, 160, 110]], [0.14, 0.58, 1.45, [230, 110, 50]]
273
+ ].freeze
274
+
275
+ # Degas, The Dance Class
276
+ def imp_the_dance_class(u, v)
277
+ DANCERS.reverse_each do |cx, hy, s, sash|
278
+ return [70, 45, 30] if ellipse(u, v, cx, hy, 0.022 * s, 0.022 * s) < 1 && v < hy - 0.005
279
+ return [235, 200, 175] if ellipse(u, v, cx, hy + 0.01 * s, 0.02 * s, 0.026 * s) < 1
280
+ return sash if (v - hy - 0.085 * s).abs < 0.012 * s && (u - cx).abs < 0.03 * s
281
+ return [245, 245, 240] if (u - cx).abs < 0.02 * s && v > hy + 0.03 * s && v < hy + 0.09 * s
282
+ return mix([250, 250, 245], [210, 215, 220], ((u - cx) / (0.07 * s)).abs) if ellipse(u, v, cx, hy + 0.115 * s, 0.07 * s, 0.035 * s) < 1
283
+ return [235, 190, 180] if ((u - cx).abs - 0.012 * s).abs < 0.006 * s && v > hy + 0.12 * s && v < hy + 0.23 * s
284
+ end
285
+ # the dance master leaning on his cane
286
+ return [235, 235, 230] if ellipse(u, v, 0.55, 0.30, 0.025, 0.03) < 1 && v < 0.29
287
+ return [225, 185, 155] if ellipse(u, v, 0.55, 0.315, 0.023, 0.03) < 1
288
+ return [70, 55, 45] if imp_seg(u, v, 0.585, 0.45, 0.62, 0.74) < 0.006
289
+ case imp_figure(u, v, 0.555, 0.33, 0.40, 0.11)
290
+ when :body then return mix([110, 95, 80], [80, 70, 60], (u - 0.5) * 8)
291
+ end
292
+ floor_line = 0.47
293
+ if v > floor_line
294
+ a = Math.atan2(v - 0.20, u - 0.62)
295
+ base = mix([205, 165, 120], [175, 125, 85], (v - floor_line) * 1.6)
296
+ return Color.scale(base, 0.85) if Math.sin(a * 46) > 0.8
297
+ return jitter(base, u, v, 8, 60.0)
298
+ end
299
+ # pilaster and doorway with mirror
300
+ return [205, 205, 185] if u > 0.31 && u < 0.37
301
+ return [215, 215, 195] if u > 0.29 && u < 0.39 && v < 0.05
302
+ return mix([130, 145, 140], [160, 170, 160], vnoise(u, v, 10.0)) if u > 0.77 && u < 0.97 && v > 0.05
303
+ jitter(mix([180, 190, 170], [160, 170, 150], v * 2), u, v, 10)
304
+ end
305
+
306
+ # Seurat, A Sunday on La Grande Jatte (scene before pointillist dots)
307
+ def imp_grande_jatte_scene(u, v)
308
+ dark = [35, 40, 50]
309
+ # couple in profile at right: woman with bustle and parasol, man in top hat
310
+ return [150, 60, 50] if ellipse(u, v, 0.79, 0.28, 0.06, 0.035) < 1 && v < 0.29
311
+ return [60, 40, 40] if imp_seg(u, v, 0.79, 0.28, 0.80, 0.40) < 0.006
312
+ return [230, 190, 160] if ellipse(u, v, 0.80, 0.36, 0.012, 0.02) < 1
313
+ return dark if inside?(u, v, [[0.79, 0.39], [0.82, 0.39], [0.83, 0.55], [0.88, 0.60], [0.86, 0.96], [0.74, 0.96], [0.77, 0.55]])
314
+ return [20, 20, 25] if (u - 0.905).abs < 0.014 && v > 0.22 && v < 0.29
315
+ return [20, 20, 25] if (u - 0.905).abs < 0.025 && (v - 0.29).abs < 0.006
316
+ return [230, 190, 160] if ellipse(u, v, 0.905, 0.32, 0.013, 0.025) < 1
317
+ return [30, 30, 40] if inside?(u, v, [[0.89, 0.35], [0.93, 0.35], [0.95, 0.96], [0.87, 0.96]])
318
+ # woman with child in white, center
319
+ return [245, 245, 240] if (case imp_figure(u, v, 0.52, 0.48, 0.20, 0.07) when :head, :body then true end)
320
+ case imp_figure(u, v, 0.47, 0.38, 0.34, 0.11)
321
+ when :head then return [230, 190, 160]
322
+ when :body then return [120, 50, 70]
323
+ end
324
+ # seated woman with orange parasol, reclining man at lower left
325
+ return [230, 120, 50] if ellipse(u, v, 0.22, 0.52, 0.06, 0.035) < 1 && v < 0.53
326
+ return dark if ellipse(u, v, 0.23, 0.63, 0.05, 0.08) < 1
327
+ return [45, 35, 30] if ellipse(u, v, 0.11, 0.80, 0.10, 0.045) < 1
328
+ return [235, 200, 170] if ellipse(u, v, 0.04, 0.76, 0.02, 0.03) < 1
329
+ # small distant strollers
330
+ [[0.35, 0.30, 0.12], [0.62, 0.26, 0.11], [0.68, 0.33, 0.13], [0.42, 0.25, 0.08], [0.58, 0.40, 0.12]].each do |fx, fy, fh|
331
+ return dark if imp_figure(u, v, fx, fy, fh, fh * 0.35)
332
+ end
333
+ # trees: canopy at top, trunks
334
+ return [40, 70, 40] if v < 0.10 + 0.05 * vnoise(u, 0.0, 9.0)
335
+ return [70, 60, 45] if ((u - 0.30).abs < 0.012 || (u - 0.70).abs < 0.014) && v < 0.42
336
+ # the river at top left
337
+ if v < 0.42 - u * 0.75
338
+ return [250, 250, 245] if ellipse(u, v, 0.12, 0.20, 0.03, 0.03) < 1 && v < 0.21
339
+ return [140, 175, 210]
340
+ end
341
+ # grass: shade in front, sun behind
342
+ shade = v > 0.70 - 0.08 * u + 0.04 * Math.sin(u * 7)
343
+ shade ? [40, 95, 55] : [160, 200, 80]
344
+ end
345
+
346
+ def imp_grande_jatte(u, v)
347
+ c = imp_grande_jatte_scene(u, v)
348
+ cx = (u * 100).floor
349
+ cy = (v * 68).floor
350
+ n = noise(cx, cy)
351
+ if n < 0.2 then mix(c, [240, 150, 60], 0.3)
352
+ elsif n > 0.82 then mix(c, [70, 110, 210], 0.3)
353
+ elsif n > 0.65 then mix(c, [255, 250, 220], 0.2)
354
+ else jitter(c, u, v, 12, 90.0)
355
+ end
356
+ end
357
+
358
+ # Manet, A Bar at the Folies-Bergere
359
+ def imp_bar_folies(u, v)
360
+ skin = [240, 215, 195]
361
+ # bottles on the marble counter
362
+ [[0.05, [40, 70, 40]], [0.09, [120, 40, 30]], [0.13, [40, 70, 40]], [0.18, [200, 120, 40]], [0.84, [40, 70, 40]], [0.89, [120, 40, 30]]].each do |bx, bc|
363
+ next unless v > 0.52 && v < 0.72
364
+ return [230, 190, 80] if (u - bx).abs < 0.009 && v < 0.56
365
+ return bc if (u - bx).abs < (v < 0.60 ? 0.008 : 0.016)
366
+ end
367
+ return [240, 140, 40] if ellipse(u, v, 0.67, 0.69, 0.06, 0.03) < 1 && v < 0.70
368
+ # barmaid: hair, face, dark bodice with lace, hands on the counter
369
+ return [190, 140, 80] if ellipse(u, v, 0.50, 0.26, 0.05, 0.05) < 1 && v < 0.27
370
+ return skin if ellipse(u, v, 0.50, 0.30, 0.04, 0.06) < 1
371
+ return skin if (u - 0.5).abs < 0.022 && v > 0.34 && v < 0.40
372
+ body = [[0.42, 0.40], [0.58, 0.40], [0.62, 0.68], [0.38, 0.68]]
373
+ if inside?(u, v, body)
374
+ return [240, 150, 160] if ellipse(u, v, 0.53, 0.46, 0.02, 0.02) < 1
375
+ return [235, 230, 225] if v < 0.47 && (u - 0.5).abs < (0.47 - v) * 0.9
376
+ return [35, 35, 55]
377
+ end
378
+ return skin if ellipse(u, v, 0.39, 0.68, 0.025, 0.02) < 1 || ellipse(u, v, 0.61, 0.68, 0.025, 0.02) < 1
379
+ # marble counter
380
+ if v > 0.68
381
+ return [120, 110, 100] if v > 0.73 && v < 0.745
382
+ return mix([235, 225, 210], [200, 190, 180], v > 0.745 ? 0.6 : 0.0)
383
+ end
384
+ # in the mirror: her back and the man in a top hat
385
+ return [190, 150, 100] if ellipse(u, v, 0.80, 0.33, 0.035, 0.045) < 1
386
+ return [55, 50, 65] if inside?(u, v, [[0.76, 0.37], [0.84, 0.37], [0.87, 0.68], [0.73, 0.68]])
387
+ return [25, 25, 30] if (u - 0.93).abs < 0.03 && v > 0.17 && v < 0.27
388
+ return [210, 180, 150] if ellipse(u, v, 0.93, 0.31, 0.03, 0.045) < 1
389
+ return [40, 40, 45] if u > 0.89 && v > 0.35 && v < 0.68
390
+ # chandeliers and lamps
391
+ [[0.27, 0.13, 0.06], [0.63, 0.10, 0.05], [0.08, 0.30, 0.025], [0.40, 0.24, 0.025]].each do |lx, ly, r|
392
+ d = dist(u, v, lx, ly, 1.35)
393
+ return [255, 250, 230] if d < r * 0.5
394
+ return mix([255, 240, 190], [200, 180, 140], (d - r * 0.5) / (r * 0.5)) if d < r
395
+ end
396
+ return [80, 160, 70] if dist(u, v, 0.03, 0.06, 1.35) < 0.02
397
+ return [190, 160, 90] if (v - 0.22).abs < 0.01
398
+ crowd = ramp(vnoise(u, v, 14.0), [120, 105, 90], [175, 160, 140], [215, 205, 185], [235, 225, 200])
399
+ crowd = mix(crowd, [60, 55, 60], 0.5) if v > 0.30 && noise((u * 30).floor, (v * 20).floor) > 0.7
400
+ jitter(crowd, u, v, 12)
401
+ end
402
+
403
+ # Manet, Luncheon on the Grass
404
+ def imp_luncheon(u, v)
405
+ pale = [235, 220, 195]
406
+ suit = [30, 30, 35]
407
+ # still life at lower left: fruit, basket, blue dress
408
+ return [210, 60, 40] if dist(u, v, 0.15, 0.80, 1.27) < 0.018
409
+ return [235, 160, 50] if dist(u, v, 0.20, 0.81, 1.27) < 0.018
410
+ return [150, 110, 50] if ellipse(u, v, 0.17, 0.86, 0.07, 0.035) < 1
411
+ return [110, 140, 190] if ellipse(u, v, 0.07, 0.86, 0.08, 0.06) < 1
412
+ return [230, 200, 110] if ellipse(u, v, 0.08, 0.76, 0.05, 0.02) < 1
413
+ # reclining man at right with black cap
414
+ return [20, 20, 25] if ellipse(u, v, 0.64, 0.42, 0.035, 0.022) < 1 && v < 0.43
415
+ return [220, 180, 150] if ellipse(u, v, 0.64, 0.46, 0.028, 0.04) < 1
416
+ return suit if ellipse(u, v, 0.68, 0.60, 0.09, 0.10) < 1
417
+ return suit if imp_seg(u, v, 0.70, 0.66, 0.92, 0.78, 1.27) < 0.04
418
+ return [230, 190, 160] if ellipse(u, v, 0.57, 0.58, 0.02, 0.02) < 1
419
+ # middle man
420
+ return [120, 90, 60] if ellipse(u, v, 0.47, 0.40, 0.03, 0.025) < 1 && v < 0.40
421
+ return [220, 180, 150] if ellipse(u, v, 0.47, 0.43, 0.027, 0.038) < 1
422
+ return suit if ellipse(u, v, 0.48, 0.58, 0.07, 0.11) < 1
423
+ return suit if imp_seg(u, v, 0.48, 0.66, 0.44, 0.86, 1.27) < 0.035
424
+ # pale seated figure at left
425
+ return [40, 30, 25] if ellipse(u, v, 0.33, 0.42, 0.035, 0.03) < 1 && v < 0.43
426
+ return pale if ellipse(u, v, 0.33, 0.45, 0.03, 0.04) < 1
427
+ return pale if ellipse(u, v, 0.31, 0.60, 0.06, 0.11) < 1
428
+ return pale if ellipse(u, v, 0.39, 0.72, 0.09, 0.045) < 1
429
+ # bathing woman in the clearing
430
+ return [235, 235, 225] if ellipse(u, v, 0.50, 0.30, 0.03, 0.05) < 1
431
+ if ellipse(u, v, 0.50, 0.26, 0.16, 0.26) + (vnoise(u, v, 9.0) - 0.5) * 0.9 < 1 && v > 0.04
432
+ return mix([100, 130, 100], [140, 160, 120], vnoise(u, v, 8.0)) if v > 0.30
433
+ return mix([120, 150, 90], [170, 185, 130], vnoise(u, v, 9.0))
434
+ end
435
+ if v > 0.70 + 0.04 * Math.sin(u * 9)
436
+ return jitter(mix([60, 80, 40], [40, 55, 30], v), u, v, 14)
437
+ end
438
+ woods = ramp(vnoise(u, v, 10.0), [15, 30, 20], [35, 60, 35], [60, 90, 50], [90, 110, 60])
439
+ jitter(woods, u, v, 10)
440
+ end
441
+
442
+ SUNFLOWERS = [
443
+ [0.36, 0.56, 0.07, 1], [0.58, 0.55, 0.075, 0], [0.18, 0.58, 0.07, 1], [0.78, 0.62, 0.07, 0],
444
+ [0.44, 0.36, 0.10, 0], [0.24, 0.40, 0.085, 0], [0.64, 0.40, 0.09, 1], [0.84, 0.42, 0.08, 0],
445
+ [0.30, 0.20, 0.09, 0], [0.52, 0.14, 0.085, 1], [0.72, 0.22, 0.09, 0], [0.12, 0.24, 0.07, 1],
446
+ [0.88, 0.25, 0.065, 1], [0.64, 0.06, 0.06, 0]
447
+ ].freeze
448
+
449
+ # van Gogh, Sunflowers
450
+ def imp_sunflowers(u, v)
451
+ # vase with the blue line
452
+ if v > 0.62 && v < 0.93
453
+ half = 0.11 + 0.035 * Math.sin(Math::PI * (v - 0.62) / 0.31)
454
+ if (u - 0.48).abs < half
455
+ return [60, 90, 170] if (v - 0.72).abs < 0.009
456
+ return v < 0.72 ? mix([245, 210, 90], [215, 170, 60], (u - 0.48) / half) : mix([230, 175, 60], [190, 130, 40], (u - 0.48) / half)
457
+ end
458
+ end
459
+ SUNFLOWERS.each do |fx, fy, r, kind|
460
+ d = dist(u, v, fx, fy, 0.79)
461
+ next if d > r
462
+ a = Math.atan2(v - fy, (u - fx) * 0.79)
463
+ return (kind == 1 ? [190, 110, 30] : [150, 80, 25]) if d < r * 0.45 && noise((u * 90).floor, (v * 70).floor) > 0.25
464
+ return [110, 60, 20] if d < r * 0.45
465
+ return (Math.sin(a * 13) > 0 ? [245, 185, 30] : [230, 150, 30]) if d < r * (0.78 + 0.22 * Math.sin(a * 13))
466
+ end
467
+ # stems
468
+ return [110, 130, 40] if [[0.36, 0.56], [0.58, 0.55], [0.44, 0.36], [0.64, 0.40]].any? { |sx, sy| imp_seg(u, v, sx, sy, 0.48, 0.63) < 0.007 } && v < 0.63
469
+ # table and wall
470
+ return [195, 130, 40] if (v - 0.78).abs < 0.007
471
+ if v > 0.78
472
+ return jitter(mix([225, 170, 60], [210, 150, 50], brush(u, v, angle: 0.1, freq: 40.0) * 0.5 + 0.5), u, v, 8)
473
+ end
474
+ wall = brush(u, v, angle: 0.9, freq: 30.0, wobble: 0.5) > 0.3 ? [240, 215, 110] : [230, 200, 90]
475
+ jitter(wall, u, v, 8)
476
+ end
477
+
478
+ # van Gogh, Cafe Terrace at Night
479
+ def imp_cafe_terrace(u, v)
480
+ # yellow awning in perspective
481
+ awn_top = 0.02 + u * 0.45
482
+ awn_bot = 0.36 - u * 0.05
483
+ if u < 0.48 && v > awn_top && v < awn_bot
484
+ return Math.sin(u * 50) > 0.7 ? [235, 160, 40] : [250, 210, 60]
485
+ end
486
+ # building above the awning
487
+ return mix([50, 70, 90], [80, 90, 100], vnoise(u, v, 10.0)) if u < 0.48 && v <= awn_top
488
+ terrace_edge = 0.86 - u * 0.5
489
+ if u < 0.50 && v >= awn_bot && v < terrace_edge
490
+ # tables and patrons on the lit terrace
491
+ [[0.10, 0.62, 0.06], [0.24, 0.58, 0.05], [0.36, 0.53, 0.04], [0.20, 0.72, 0.07], [0.42, 0.48, 0.03]].each do |tx, ty, r|
492
+ return [235, 235, 200] if ellipse(u, v, tx, ty, r, r * 0.35) < 1
493
+ return [60, 50, 40] if (u - tx).abs < 0.006 && v > ty && v < ty + r * 1.2
494
+ end
495
+ return [50, 50, 60] if imp_figure(u, v, 0.30, 0.44, 0.12, 0.05)
496
+ return mix([240, 180, 60], [220, 140, 40], vnoise(u, v, 12.0)) if v < 0.48
497
+ return mix([240, 190, 90], [225, 160, 60], vnoise(u, v, 14.0))
498
+ end
499
+ # dark buildings at right
500
+ if u > 0.80 - v * 0.05 && v < 0.82
501
+ return [240, 190, 80] if ((u * 18) % 1.0) < 0.35 && ((v * 14) % 1.0) < 0.3 && v > 0.25 && noise((u * 18).floor, (v * 14).floor) > 0.5
502
+ return mix([30, 55, 70], [55, 75, 70], vnoise(u, v, 9.0))
503
+ end
504
+ # starry corridor of sky
505
+ if v < 0.38 + 0.02 * Math.sin(u * 30)
506
+ [[0.55, 0.08], [0.67, 0.05], [0.76, 0.14], [0.60, 0.21], [0.71, 0.27], [0.53, 0.30], [0.66, 0.35]].each do |sx, sy|
507
+ d = dist(u, v, sx, sy, 0.81)
508
+ return [255, 250, 220] if d < 0.014
509
+ return [220, 220, 170] if d < 0.024
510
+ end
511
+ return mix([20, 40, 120], [40, 70, 160], v * 2.2 + (brush(u, v, angle: 0.5, freq: 25.0) * 0.1))
512
+ end
513
+ # distant street and walkers
514
+ return [40, 40, 50] if imp_figure(u, v, 0.62, 0.42, 0.10, 0.04) || imp_figure(u, v, 0.70, 0.44, 0.09, 0.035)
515
+ return [45, 60, 90] if v < 0.48
516
+ # cobblestones
517
+ sv = 1.0 / (v - 0.38)
518
+ su = (u - 0.62) * sv
519
+ cell_x = (su * 4).floor
520
+ cell_y = (sv * 5).floor
521
+ fx = su * 4 - cell_x
522
+ fy = sv * 5 - cell_y
523
+ return [40, 40, 70] if fx < 0.15 || fy < 0.15
524
+ stone = noise(cell_x, cell_y)
525
+ return [230, 160, 120] if stone > 0.75 && u < 0.7
526
+ mix([90, 100, 160], [130, 120, 170], stone)
527
+ end
528
+
529
+ CROWS = [[0.30, 0.18, 0.035], [0.42, 0.10, 0.03], [0.55, 0.22, 0.04], [0.64, 0.12, 0.03], [0.72, 0.28, 0.035],
530
+ [0.80, 0.16, 0.03], [0.48, 0.35, 0.04], [0.60, 0.42, 0.035], [0.86, 0.33, 0.03], [0.22, 0.30, 0.03],
531
+ [0.38, 0.44, 0.03]].freeze
532
+
533
+ # van Gogh, Wheatfield with Crows
534
+ def imp_wheatfield_with_crows(u, v)
535
+ aspect = 2.04
536
+ CROWS.each do |cx, cy, s|
537
+ du = (u - cx) * aspect
538
+ next if du.abs > s
539
+ return [15, 15, 20] if (v - (cy - du.abs * 0.6)).abs < 0.022
540
+ end
541
+ horizon = 0.42 + 0.03 * Math.sin(u * 5)
542
+ if v < horizon
543
+ sky = mix([15, 30, 90], [40, 80, 160], vnoise(u, v, 6.0) + v)
544
+ sky = mix(sky, [80, 130, 200], 0.6) if brush(u, v, angle: 0.3 + vnoise(u, v, 3.0), freq: 25.0, wobble: 0.8) > 0.5
545
+ [[0.25, 0.20], [0.78, 0.14]].each do |cx, cy|
546
+ sky = mix(sky, [170, 205, 215], 0.85) if dist(u, v, cx, cy, aspect) < 0.08 + 0.03 * brush(u, v, freq: 30.0)
547
+ end
548
+ return sky
549
+ end
550
+ # the red-brown paths with green verges
551
+ if v > horizon + 0.03
552
+ t = (v - horizon) / (1.0 - horizon)
553
+ center = 0.52 - t * 0.02
554
+ w = 0.01 + t * 0.10
555
+ off = (u - center).abs
556
+ return mix([190, 80, 40], [150, 60, 30], vnoise(u, v, 20.0)) if off < w
557
+ return [60, 140, 60] if off < w + 0.015 + t * 0.03
558
+ [[0.0, 0.70, 0.26, 0.50], [1.0, 0.66, 0.76, 0.48]].each do |ax, ay, bx, by|
559
+ d = imp_seg(u, v, ax, ay, bx, by, aspect)
560
+ return [180, 80, 40] if d < 0.03 && v > by
561
+ return [70, 140, 60] if d < 0.055 && v > by
562
+ end
563
+ end
564
+ # agitated wheat
565
+ ang = 1.2 + (vnoise(u, v, 5.0) - 0.5) * 2.0
566
+ st = brush(u, v, angle: ang, freq: 35.0, wobble: 0.8)
567
+ wheat = if st > 0.5 then [245, 205, 80]
568
+ elsif st < -0.6 then [170, 110, 20]
569
+ else [220, 165, 40]
570
+ end
571
+ wheat = mix(wheat, [100, 140, 50], 0.5) if v < horizon + 0.05 && vnoise(u, v, 20.0) > 0.55
572
+ wheat
573
+ end
574
+
575
+ # van Gogh, Bedroom in Arles
576
+ def imp_bedroom_in_arles(u, v)
577
+ bed_yellow = [230, 170, 60]
578
+ # footboard at front right
579
+ return mix(bed_yellow, [190, 130, 40], (u - 0.70) * 3) if u > 0.70 && v > 0.66 && v < 0.94 && v > 0.66 + (u - 0.70) * 0.05
580
+ # mattress, red blanket, white pillows
581
+ mattress = [[0.60, 0.44], [0.86, 0.44], [1.0, 0.66], [0.70, 0.68]]
582
+ if inside?(u, v, mattress)
583
+ return [245, 240, 205] if v < 0.50
584
+ return mix([220, 60, 40], [180, 40, 30], vnoise(u, v, 12.0))
585
+ end
586
+ return bed_yellow if u > 0.60 && u < 0.86 && v > 0.27 && v < 0.46
587
+ # chair by the bed and chair at front left
588
+ [[0.52, 0.60, 0.8], [0.15, 0.80, 1.3]].each do |cx, cy, s|
589
+ return [215, 175, 90] if ellipse(u, v, cx, cy, 0.06 * s, 0.025 * s) < 1
590
+ return [200, 140, 50] if ((u - cx).abs - 0.05 * s).abs < 0.008 * s && v < cy && v > cy - 0.14 * s
591
+ return [200, 140, 50] if (v - (cy - 0.12 * s)).abs < 0.008 * s && (u - cx).abs < 0.05 * s
592
+ return [180, 120, 40] if ((u - cx).abs - 0.05 * s).abs < 0.008 * s && v > cy && v < cy + 0.09 * s
593
+ end
594
+ # small table with jug
595
+ return [80, 110, 170] if ellipse(u, v, 0.28, 0.47, 0.012, 0.025) < 1
596
+ return [210, 170, 100] if u > 0.20 && u < 0.36 && (v - 0.50).abs < 0.015
597
+ return [180, 130, 70] if ((u - 0.22).abs < 0.006 || (u - 0.34).abs < 0.006) && v > 0.50 && v < 0.66
598
+ # floor in tilted perspective
599
+ floor_v = u < 0.22 ? 0.82 - u * 1.2 : 0.556 + (u > 0.72 ? (u - 0.72) * 0.6 : 0.0)
600
+ if v > floor_v
601
+ a = Math.atan2(v - 0.20, u - 0.45)
602
+ base = mix([200, 120, 90], [170, 100, 80], v)
603
+ return Math.sin(a * 40) > 0.75 ? [150, 150, 100] : base
604
+ end
605
+ # pictures on the walls
606
+ [[0.66, 0.08, 0.04, 0.06], [0.78, 0.12, 0.04, 0.06], [0.90, 0.10, 0.05, 0.07], [0.25, 0.14, 0.03, 0.06]].each do |px, py, rx, ry|
607
+ next unless (u - px).abs < rx && (v - py).abs < ry
608
+ return [190, 150, 60] if (u - px).abs > rx * 0.7 || (v - py).abs > ry * 0.75
609
+ return [200, 160, 110] if dist(u, v, px, py - ry * 0.2, 1.25) < rx * 0.5
610
+ return [90, 140, 120]
611
+ end
612
+ # window with green shutters
613
+ if u > 0.34 && u < 0.50 && v > 0.05 && v < 0.40
614
+ return [70, 120, 60] if (u - 0.42).abs < 0.008 || (v - 0.20).abs < 0.008
615
+ return [150, 190, 110]
616
+ end
617
+ return [70, 130, 80] if u > 0.30 && u < 0.54 && v > 0.03 && v < 0.42
618
+ # left wall door, back wall, right wall
619
+ return [100, 110, 170] if u < 0.13 && u > 0.03 && v > 0.12 && v < 0.75
620
+ return [140, 150, 210] if u < 0.22
621
+ return [120, 130, 195] if u > 0.72
622
+ jitter([150, 165, 215], u, v, 6)
623
+ end
624
+
625
+ ALMOND_BRANCHES = [
626
+ [0.00, 0.98, 0.28, 0.58, 0.035], [0.28, 0.58, 0.45, 0.36, 0.028], [0.45, 0.36, 0.63, 0.20, 0.022],
627
+ [0.63, 0.20, 0.86, 0.04, 0.016], [0.28, 0.58, 0.14, 0.26, 0.018], [0.14, 0.26, 0.08, 0.02, 0.012],
628
+ [0.14, 0.26, 0.32, 0.10, 0.011], [0.45, 0.36, 0.72, 0.46, 0.016], [0.72, 0.46, 0.97, 0.38, 0.011],
629
+ [0.72, 0.46, 0.84, 0.78, 0.012], [0.84, 0.78, 0.92, 0.98, 0.010], [0.63, 0.20, 0.56, 0.02, 0.011]
630
+ ].freeze
631
+ ALMOND_BLOSSOMS = ALMOND_BRANCHES.each_with_index.flat_map do |(ax, ay, bx, by, _), i|
632
+ [0.25, 0.55, 0.85].each_with_index.map do |t, j|
633
+ side = (i + j).even? ? 1 : -1
634
+ nx = -(by - ay)
635
+ ny = bx - ax
636
+ len = Math.hypot(nx, ny)
637
+ [ax + (bx - ax) * t + side * nx / len * 0.035, ay + (by - ay) * t + side * ny / len * 0.035, (i * 3 + j) % 4]
638
+ end
639
+ end.freeze
640
+
641
+ # van Gogh, Almond Blossoms
642
+ def imp_almond_blossoms(u, v)
643
+ aspect = 1.25
644
+ ALMOND_BLOSSOMS.each do |bx, by, kind|
645
+ d = dist(u, v, bx, by, aspect)
646
+ next if d > 0.05
647
+ a = Math.atan2(v - by, (u - bx) * aspect)
648
+ r = (kind.zero? ? 0.025 : 0.04) * (0.75 + 0.25 * Math.cos(a * 5))
649
+ next if d > r
650
+ return [200, 70, 90] if d < r * 0.3
651
+ return kind == 3 ? [245, 215, 220] : mix([255, 255, 250], [225, 235, 230], d / r)
652
+ end
653
+ ALMOND_BRANCHES.each do |ax, ay, bx, by, w|
654
+ d = imp_seg(u, v, ax, ay, bx, by, aspect)
655
+ return d < w * 0.4 ? [70, 80, 50] : [40, 50, 35] if d < w
656
+ end
657
+ sky = brush(u, v, angle: 0.6 + vnoise(u, v, 3.0), freq: 30.0) > 0.4 ? [140, 205, 210] : [100, 175, 195]
658
+ jitter(sky, u, v, 8)
659
+ end
660
+
661
+ # van Gogh, Self-Portrait (1889)
662
+ def imp_van_gogh_self_portrait(u, v)
663
+ hair = [205, 110, 45]
664
+ # blue suit with swirling strokes, white collar
665
+ coat = [[0.12, 1.0], [0.16, 0.72], [0.32, 0.58], [0.50, 0.55], [0.68, 0.58], [0.84, 0.70], [0.90, 1.0]]
666
+ if inside?(u, v, coat)
667
+ return [235, 235, 220] if v < 0.66 && (u - 0.50).abs < (0.66 - v) * 0.7 && v > 0.55
668
+ return [40, 60, 100] if ((u - 0.5).abs - (v - 0.55) * 0.3).abs < 0.012
669
+ return brush(u, v, angle: 0.8 + vnoise(u, v, 4.0) * 2, freq: 35.0) > 0.3 ? [90, 120, 170] : [55, 80, 130]
670
+ end
671
+ return [200, 160, 110] if (u - 0.50).abs < 0.06 && v > 0.47 && v < 0.58
672
+ # face in three-quarter view, beard and hair
673
+ face = ellipse(u, v, 0.48, 0.34, 0.14, 0.17)
674
+ if face < 1
675
+ return [70, 90, 60] if ((u - 0.43).abs < 0.025 || (u - 0.55).abs < 0.022) && (v - 0.31).abs < 0.012
676
+ return mix(hair, [170, 80, 35], vnoise(u, v, 30.0)) if v < 0.24 + (u - 0.48).abs * 0.2
677
+ return mix(hair, [230, 140, 60], vnoise(u, v, 30.0)) if v > 0.41 - (u - 0.48).abs * 0.15
678
+ return [170, 120, 80] if (u - 0.47).abs < 0.008 && v > 0.32 && v < 0.38
679
+ return mix([230, 190, 130], [170, 150, 100], smooth(0.40, 0.62, u))
680
+ end
681
+ return [205, 160, 110] if ellipse(u, v, 0.625, 0.34, 0.02, 0.04) < 1
682
+ # swirling pale blue background
683
+ r = dist(u, v, 0.5, 0.35, 0.83)
684
+ a = Math.atan2(v - 0.35, (u - 0.5) * 0.83)
685
+ swirl = Math.sin(r * 60 + a * 4 + vnoise(u, v, 5.0) * 3)
686
+ base = swirl > 0.3 ? [175, 205, 210] : [130, 175, 190]
687
+ jitter(base, u, v, 8)
688
+ end
689
+
690
+ VICTOIRE_RIDGE = [[0.0, 0.45], [0.25, 0.38], [0.45, 0.30], [0.60, 0.20], [0.66, 0.22], [0.78, 0.36], [0.90, 0.40], [1.0, 0.43]].freeze
691
+
692
+ # Cezanne, Mont Sainte-Victoire
693
+ def imp_mont_sainte_victoire(u, v)
694
+ ridge = VICTOIRE_RIDGE.each_cons(2).each do |(x0, y0), (x1, y1)|
695
+ break y0 + (y1 - y0) * (u - x0) / (x1 - x0) if u <= x1
696
+ end
697
+ ridge = 0.43 unless ridge.is_a?(Float)
698
+ px = u * 13 + v * 4
699
+ py = v * 15 - u * 2
700
+ n = noise(px.floor, py.floor)
701
+ if v < ridge
702
+ pal = [[120, 170, 175], [175, 205, 200], [100, 145, 180], [150, 190, 170], [200, 215, 210]]
703
+ return pal[(n * pal.size).floor]
704
+ end
705
+ return [80, 90, 140] if v < ridge + 0.015
706
+ if v < ridge + 0.13 - u * 0.04 && u > 0.35
707
+ pal = [[130, 140, 175], [170, 160, 175], [190, 175, 170], [115, 125, 160]]
708
+ return pal[(n * pal.size).floor]
709
+ end
710
+ # houses in the valley
711
+ if v > 0.55 && v < 0.78 && noise(px.floor + 11, py.floor) > 0.82
712
+ return py - py.floor < 0.35 ? [190, 90, 50] : [230, 190, 120]
713
+ end
714
+ pal = if v > 0.86 then [[40, 80, 50], [60, 100, 60], [90, 120, 70], [50, 70, 60]]
715
+ else [[90, 140, 80], [130, 160, 80], [200, 165, 95], [110, 150, 110], [175, 150, 80], [70, 120, 80]]
716
+ end
717
+ pal[(n * pal.size).floor]
718
+ end
719
+
720
+ # Cezanne, The Card Players
721
+ def imp_the_card_players(u, v)
722
+ skin = [210, 150, 100]
723
+ # bottle in the middle
724
+ if (u - 0.5).abs < (v < 0.40 ? 0.012 : 0.03) && v > 0.28 && v < 0.58
725
+ return [230, 230, 210] if (u - 0.49).abs < 0.006 && v > 0.40
726
+ return [40, 50, 40]
727
+ end
728
+ # cards and hands
729
+ return [240, 235, 215] if ellipse(u, v, 0.38, 0.54, 0.025, 0.025) < 1 || ellipse(u, v, 0.63, 0.54, 0.025, 0.025) < 1
730
+ return skin if ellipse(u, v, 0.40, 0.58, 0.03, 0.022) < 1 || ellipse(u, v, 0.61, 0.58, 0.03, 0.022) < 1
731
+ # table top and front
732
+ if u > 0.30 && u < 0.70 && v > 0.58
733
+ return [205, 155, 90] if v < 0.64
734
+ return mix([170, 80, 40], [130, 60, 35], vnoise(u, v, 10.0))
735
+ end
736
+ # left man with pipe, blue-violet jacket
737
+ return [245, 245, 235] if imp_seg(u, v, 0.31, 0.31, 0.37, 0.34) < 0.008
738
+ return [55, 50, 50] if ellipse(u, v, 0.26, 0.17, 0.07, 0.05) < 1 && v < 0.19
739
+ return [55, 50, 50] if (v - 0.185).abs < 0.012 && (u - 0.26).abs < 0.10
740
+ return mix(skin, [160, 100, 60], (u - 0.20) * 6) if ellipse(u, v, 0.27, 0.26, 0.055, 0.075) < 1
741
+ if inside?(u, v, [[0.13, 0.34], [0.33, 0.34], [0.42, 0.56], [0.40, 1.0], [0.04, 1.0], [0.08, 0.50]])
742
+ return brush(u, v, angle: 1.2, freq: 30.0) > 0.4 ? [110, 105, 160] : [75, 70, 120]
743
+ end
744
+ # right man, hat and ochre jacket
745
+ return [110, 95, 75] if ellipse(u, v, 0.75, 0.15, 0.06, 0.06) < 1 && v < 0.18
746
+ return [110, 95, 75] if (v - 0.175).abs < 0.012 && (u - 0.75).abs < 0.09
747
+ return mix([160, 100, 60], skin, (u - 0.70) * 6) if ellipse(u, v, 0.74, 0.25, 0.055, 0.075) < 1
748
+ return [210, 200, 180] if (v - 0.32).abs < 0.012 && (u - 0.75).abs < 0.06
749
+ if inside?(u, v, [[0.66, 0.33], [0.88, 0.33], [0.95, 0.50], [0.98, 1.0], [0.60, 1.0], [0.58, 0.56]])
750
+ return brush(u, v, angle: 1.0, freq: 30.0) > 0.4 ? [200, 165, 100] : [165, 130, 80]
751
+ end
752
+ # warm brown wall
753
+ wall = mix([160, 115, 70], [110, 95, 80], u)
754
+ wall = mix(wall, [90, 70, 50], 0.35) if brush(u, v, angle: 0.9, freq: 25.0) > 0.6
755
+ jitter(wall, u, v, 8)
756
+ end
757
+
758
+ TAHITI_LEFT = [[0.18, 0.40], [0.38, 0.40], [0.46, 0.66], [0.48, 0.86], [0.12, 0.88], [0.14, 0.62]].freeze
759
+ TAHITI_RIGHT = [[0.60, 0.40], [0.80, 0.40], [0.88, 0.62], [0.96, 0.96], [0.52, 0.96], [0.56, 0.62]].freeze
760
+
761
+ # Gauguin, Tahitian Women on the Beach
762
+ def imp_tahitian_women(u, v)
763
+ skin = [190, 120, 70]
764
+ outline = [50, 35, 30]
765
+ # left woman in profile: black hair, white top, red pareo with white flowers
766
+ return [20, 15, 15] if ellipse(u, v, 0.29, 0.25, 0.07, 0.08) < 1 && u > 0.25
767
+ return skin if ellipse(u, v, 0.26, 0.29, 0.045, 0.06) < 1
768
+ return [250, 250, 240] if dist(u, v, 0.30, 0.20, 1.32) < 0.02
769
+ return skin if imp_seg(u, v, 0.18, 0.45, 0.10, 0.70, 1.32) < 0.025
770
+ if inside?(u, v, TAHITI_LEFT)
771
+ return [240, 235, 220] if v < 0.56
772
+ return [250, 245, 230] if noise((u * 30).floor, (v * 24).floor) > 0.72
773
+ return [205, 40, 40]
774
+ end
775
+ return outline if imp_outline?(u, v, TAHITI_LEFT)
776
+ # right woman, frontal, pink dress, weaving palm fronds
777
+ return [20, 15, 15] if ellipse(u, v, 0.70, 0.24, 0.075, 0.08) < 1 && v < 0.27
778
+ return skin if ellipse(u, v, 0.70, 0.29, 0.05, 0.07) < 1
779
+ return skin if (u - 0.70).abs < 0.025 && v > 0.34 && v < 0.42
780
+ if inside?(u, v, TAHITI_RIGHT)
781
+ return [240, 210, 120] if ellipse(u, v, 0.70, 0.70, 0.10, 0.04) < 1
782
+ return skin if ellipse(u, v, 0.70, 0.64, 0.07, 0.025) < 1
783
+ return [250, 240, 230] if v < 0.45 && (u - 0.70).abs < 0.05
784
+ return Math.sin(u * 50 + v * 10) > 0.6 ? [200, 100, 130] : [230, 140, 160]
785
+ end
786
+ return outline if imp_outline?(u, v, TAHITI_RIGHT)
787
+ # flat sea band with a white wave line, then pink-ochre sand
788
+ wave = 0.30 + 0.015 * Math.sin(u * 18)
789
+ return [30, 70, 80] if v < 0.12
790
+ return [60, 140, 130] if v < wave - 0.02
791
+ return [225, 235, 225] if v < wave + 0.01
792
+ sand = mix([225, 170, 140], [200, 130, 110], vnoise(u, v, 5.0))
793
+ sand = mix(sand, [170, 100, 90], 0.4) if v > 0.80 && vnoise(u, v, 8.0) > 0.6
794
+ sand
795
+ end
796
+ end
797
+
798
+ piece(:impression_sunrise, "Impression, Sunrise", "Claude Monet", "1872", aspect: 1.31) { |u, v| imp_impression_sunrise(u, v) }
799
+ piece(:water_lilies, "Water Lilies", "Claude Monet", "1906", aspect: 1.1) { |u, v| imp_water_lilies(u, v) }
800
+ piece(:woman_with_a_parasol, "Woman with a Parasol", "Claude Monet", "1875", aspect: 0.81) { |u, v| imp_woman_with_a_parasol(u, v) }
801
+ piece(:japanese_footbridge, "The Water Lily Pond (Japanese Footbridge)", "Claude Monet", "1899", aspect: 1.0) { |u, v| imp_japanese_footbridge(u, v) }
802
+ piece(:poppy_field, "Poppy Field", "Claude Monet", "1873", aspect: 1.3) { |u, v| imp_poppy_field(u, v) }
803
+ piece(:bal_du_moulin_de_la_galette, "Bal du moulin de la Galette", "Pierre-Auguste Renoir", "1876", aspect: 1.33) { |u, v| imp_bal_du_moulin(u, v) }
804
+ piece(:the_dance_class, "The Dance Class", "Edgar Degas", "1874", aspect: 0.88) { |u, v| imp_the_dance_class(u, v) }
805
+ piece(:sunday_on_la_grande_jatte, "A Sunday on La Grande Jatte", "Georges Seurat", "1886", aspect: 1.49) { |u, v| imp_grande_jatte(u, v) }
806
+ piece(:bar_at_the_folies_bergere, "A Bar at the Folies-Bergère", "Édouard Manet", "1882", aspect: 1.35) { |u, v| imp_bar_folies(u, v) }
807
+ piece(:luncheon_on_the_grass, "Luncheon on the Grass", "Édouard Manet", "1863", aspect: 1.27) { |u, v| imp_luncheon(u, v) }
808
+ piece(:sunflowers, "Sunflowers", "Vincent van Gogh", "1888", aspect: 0.79) { |u, v| imp_sunflowers(u, v) }
809
+ piece(:cafe_terrace_at_night, "Café Terrace at Night", "Vincent van Gogh", "1888", aspect: 0.81) { |u, v| imp_cafe_terrace(u, v) }
810
+ piece(:wheatfield_with_crows, "Wheatfield with Crows", "Vincent van Gogh", "1890", aspect: 2.04) { |u, v| imp_wheatfield_with_crows(u, v) }
811
+ piece(:bedroom_in_arles, "Bedroom in Arles", "Vincent van Gogh", "1888", aspect: 1.25) { |u, v| imp_bedroom_in_arles(u, v) }
812
+ piece(:almond_blossoms, "Almond Blossoms", "Vincent van Gogh", "1890", aspect: 1.25) { |u, v| imp_almond_blossoms(u, v) }
813
+ piece(:van_gogh_self_portrait, "Self-Portrait", "Vincent van Gogh", "1889", aspect: 0.83) { |u, v| imp_van_gogh_self_portrait(u, v) }
814
+ piece(:mont_sainte_victoire, "Mont Sainte-Victoire", "Paul Cézanne", "c. 1904", aspect: 1.26) { |u, v| imp_mont_sainte_victoire(u, v) }
815
+ piece(:the_card_players, "The Card Players", "Paul Cézanne", "c. 1893", aspect: 1.21) { |u, v| imp_the_card_players(u, v) }
816
+ piece(:tahitian_women_on_the_beach, "Tahitian Women on the Beach", "Paul Gauguin", "1891", aspect: 1.32) { |u, v| imp_tahitian_women(u, v) }
817
+ end
818
+ end