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,829 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dopairb
4
+ module Gallery
5
+ # Renaissance and Golden Age painters (helpers and painters use an rn_ prefix).
6
+ module Paint
7
+ module_function
8
+
9
+ # Distance from (u, v) to the segment (x1, y1)-(x2, y2); sx scales the u axis.
10
+ def rn_seg(u, v, x1, y1, x2, y2, sx = 1.0)
11
+ dx = (x2 - x1) * sx
12
+ dy = y2 - y1
13
+ px = (u - x1) * sx
14
+ py = v - y1
15
+ t = ((px * dx + py * dy) / (dx * dx + dy * dy + 1e-12)).clamp(0.0, 1.0)
16
+ Math.hypot(px - dx * t, py - dy * t)
17
+ end
18
+
19
+ # A simple standing (or seated) figure: :head, :body or nil.
20
+ # x: center u, top: v of the top of the head, h: height and w: width in v units,
21
+ # sx: the picture's aspect so heads stay round.
22
+ def rn_fig(u, v, x, top, h, w, sx)
23
+ hr = h * 0.08
24
+ return :head if (((u - x) * sx) / (hr * 0.85))**2 + ((v - top - hr) / hr)**2 < 1
25
+ t = (v - top - hr * 1.8) / (h - hr * 1.8)
26
+ return nil if t < 0 || t > 1
27
+ (u - x).abs * sx < w * 0.5 * (0.7 + 0.5 * t) ? :body : nil
28
+ end
29
+
30
+ # Botticelli, The Birth of Venus
31
+ def rn_birth_of_venus(u, v)
32
+ a = 1.58
33
+ skin = [238, 214, 190]
34
+ hair = [196, 128, 62]
35
+ # orange trees at the right edge
36
+ edge = 0.91 + 0.015 * Math.sin(v * 11)
37
+ if u > edge
38
+ return [92, 70, 45] if (u - 0.95).abs < 0.008 && v > 0.15
39
+ return noise((u * 70).floor, (v * 50).floor) > 0.9 ? [210, 150, 70] : jitter([45, 72, 50], u, v, 14)
40
+ end
41
+ # the green shore under the attendant
42
+ return jitter([96, 120, 70], u, v, 12) if v > 0.84 - (u - 0.7) * 0.35 && u > 0.68
43
+ # the scallop shell
44
+ su = 0.5
45
+ sv = 0.97
46
+ if ellipse(u, v, su, sv, 0.17, 0.2) < 1 && v < 0.94 && v > 0.78
47
+ ang = Math.atan2(v - sv, (u - su) * a)
48
+ return [170, 130, 85] if ellipse(u, v, su, sv, 0.17, 0.2) > 0.85
49
+ return Math.sin(ang * 26) > 0.3 ? [200, 165, 115] : [240, 220, 178]
50
+ end
51
+ # Venus: long golden-red hair over a pale S-shaped figure
52
+ return mix(hair, [150, 85, 40], 0.2) if ellipse(u, v, 0.5, 0.115, 0.03, 0.055) < 1 && v < 0.11
53
+ return skin if ellipse(u, v, 0.5, 0.135, 0.022, 0.05) < 1
54
+ # the long hair: down her left side and drawn across the hips, a few strands blowing right
55
+ lock = 0.47 - 0.012 * Math.sin(v * 14)
56
+ hair_px = (v > 0.1 && v < 0.6 && (u - lock).abs < 0.016 + (v - 0.1) * 0.03) ||
57
+ rn_seg(u, v, 0.465, 0.55, 0.505, 0.63, a) < 0.035 ||
58
+ rn_seg(u, v, 0.52, 0.1, 0.565, 0.24, a) < 0.016 || rn_seg(u, v, 0.53, 0.14, 0.555, 0.32, a) < 0.012
59
+ return Math.sin(v * 70 + u * 40) > 0.5 ? [228, 170, 92] : hair if hair_px
60
+ body = [[0.482, 0.18], [0.52, 0.18], [0.538, 0.3], [0.532, 0.46], [0.522, 0.6], [0.518, 0.8], [0.488, 0.8], [0.48, 0.6], [0.47, 0.46], [0.472, 0.3]]
61
+ return mix(skin, [205, 170, 140], smooth(0.49, 0.54, u)) if inside?(u, v, body)
62
+ # the attendant (Hora) with the pink cloak held out toward Venus
63
+ fig = rn_fig(u, v, 0.77, 0.16, 0.66, 0.11, a)
64
+ return [232, 200, 170] if fig == :head
65
+ cloak = [[0.735, 0.28], [0.65, 0.3], [0.6, 0.42], [0.61, 0.66], [0.64, 0.76], [0.71, 0.64], [0.74, 0.45]]
66
+ if inside?(u, v, cloak)
67
+ return noise((u * 60).floor, (v * 40).floor) > 0.88 ? [180, 60, 60] : jitter([230, 160, 160], u, v, 14)
68
+ end
69
+ if fig == :body
70
+ return noise((u * 70).floor, (v * 50).floor) > 0.85 ? [80, 110, 170] : jitter([236, 232, 214], u, v, 10)
71
+ end
72
+ # the winds flying in from the left
73
+ return [100, 140, 125] if rn_seg(u, v, 0.03, 0.15, 0.27, 0.27, a) < 0.07 # Zephyr's blue-green cloak
74
+ return skin if rn_seg(u, v, 0.05, 0.3, 0.27, 0.33, a) < 0.045 # Aura
75
+ return [232, 205, 180] if dist(u, v, 0.29, 0.27, a) < 0.04
76
+ return [215, 195, 170] if dist(u, v, 0.29, 0.34, a) < 0.035
77
+ return [120, 160, 140] if rn_seg(u, v, 0.08, 0.38, 0.22, 0.42, a) < 0.03 # trailing drapery
78
+ # falling roses
79
+ return [235, 150, 160] if u > 0.18 && u < 0.42 && v < 0.6 && noise((u * 50).floor, (v * 35).floor) > 0.94
80
+ horizon = 0.46
81
+ if v > horizon
82
+ chevron = Math.sin(v * 110 + Math.sin(u * 70).abs * 5)
83
+ sea = mix([150, 185, 170], [110, 150, 140], (v - horizon) / 0.5)
84
+ return chevron > 0.85 ? mix(sea, [240, 245, 235], 0.6) : sea
85
+ end
86
+ jitter(mix([150, 180, 175], [205, 218, 205], v / horizon), u, v, 8)
87
+ end
88
+
89
+ # Botticelli, Primavera
90
+ def rn_primavera(u, v)
91
+ a = 1.55
92
+ # the figures, left to right: [u, top, height, width, gown color]
93
+ figs = [
94
+ [0.08, 0.17, 0.74, 0.12, [176, 40, 30]], # Mercury
95
+ [0.18, 0.18, 0.72, 0.1, [236, 232, 222]], # the three Graces
96
+ [0.245, 0.17, 0.73, 0.1, [228, 224, 214]],
97
+ [0.31, 0.19, 0.71, 0.1, [236, 230, 218]],
98
+ [0.5, 0.25, 0.64, 0.12, [190, 50, 40]], # Venus, red mantle
99
+ [0.655, 0.2, 0.7, 0.13, [240, 236, 222]], # Flora
100
+ [0.775, 0.19, 0.71, 0.1, [222, 222, 210]], # Chloris
101
+ [0.9, 0.08, 0.7, 0.12, [80, 115, 120]] # Zephyr
102
+ ]
103
+ # Cupid hovering above Venus
104
+ return [200, 90, 70] if dist(u, v, 0.5, 0.11, a) < 0.035
105
+ return [230, 200, 170] if dist(u, v, 0.5, 0.07, a) < 0.022
106
+ figs.each_with_index do |(fx, ft, fh, fw, col), i|
107
+ fx2 = fx + (i == 7 ? (v - ft) * -0.12 : 0) # Zephyr leans in
108
+ part = rn_fig(u, v, fx2, ft, fh, fw, a)
109
+ next unless part
110
+ return [234, 208, 180] if part == :head
111
+ if i == 4 # Venus: white gown under the red mantle
112
+ return (u - 0.5).abs < 0.012 && v > 0.36 ? [236, 228, 214] : jitter(col, u, v, 12)
113
+ end
114
+ if i == 5 && noise((u * 70).floor + 7, (v * 50).floor) > 0.72 # Flora's flowered dress
115
+ return [[220, 70, 80], [90, 140, 200], [230, 200, 80]][(noise((u * 90).floor, (v * 60).floor) * 3).floor % 3]
116
+ end
117
+ return mix(col, [150, 140, 125], (u - fx2).abs * a * 6) # sheer gowns, shaded at the edges
118
+ end
119
+ # the flowery meadow
120
+ if v > 0.84
121
+ n = noise((u * 90).floor, (v * 70).floor)
122
+ return [235, 230, 210] if n > 0.9
123
+ return [210, 80, 80] if n > 0.85
124
+ return [90, 120, 190] if n > 0.82
125
+ return jitter([35, 55, 35], u, v, 10)
126
+ end
127
+ # the arch of trees around Venus opening onto pale sky
128
+ ae = ellipse(u, v, 0.5, 0.42, 0.075, 0.36)
129
+ return jitter([165, 175, 160], u, v, 14) if ae < 1 && ae > 0.55
130
+ # the dark orange grove
131
+ return [55, 40, 28] if Math.sin(u * 55) > 0.93 # trunks
132
+ return [228, 140, 50] if v < 0.62 && noise((u * 48).floor, (v * 34).floor) > 0.88 # oranges
133
+ return [135, 150, 140] if v < 0.55 && vnoise(u, v, 14) > 0.74 # sky between the leaves
134
+ jitter([30, 44, 30], u, v, 12, 50)
135
+ end
136
+
137
+ # Leonardo da Vinci, The Last Supper
138
+ def rn_last_supper(u, v)
139
+ a = 1.9
140
+ vp_u = 0.5
141
+ vp_v = 0.4
142
+ # the long table
143
+ if v > 0.62 && v < 0.82 && u > 0.03 && u < 0.97
144
+ if v < 0.67
145
+ return [150, 120, 80] if v < 0.645 && noise((u * 70).floor, 3) > 0.8 # bread and plates
146
+ return [238, 235, 225]
147
+ end
148
+ return Math.sin(u * 120) > 0.85 ? [200, 200, 195] : [222, 220, 210] # cloth hanging down
149
+ end
150
+ # thirteen figures behind the table, in groups of three around Jesus
151
+ if v > 0.36 && v < 0.67
152
+ if inside?(u, v, [[0.5, 0.39], [0.565, 0.67], [0.435, 0.67]])
153
+ return [70, 50, 35] if dist(u, v, 0.5, 0.44, a) < 0.04 && v < 0.43
154
+ return [225, 185, 150] if dist(u, v, 0.5, 0.45, a) < 0.04
155
+ return u > 0.5 + (v - 0.45) * 0.1 ? [45, 70, 140] : [180, 40, 35]
156
+ end
157
+ cols = [[90, 120, 80], [180, 120, 60], [70, 90, 140], [160, 60, 50], [120, 110, 100], [200, 160, 90],
158
+ [80, 110, 150], [150, 70, 60], [110, 130, 90], [180, 100, 70], [90, 90, 120], [170, 150, 110]]
159
+ [0.08, 0.145, 0.205, 0.285, 0.345, 0.405, 0.595, 0.655, 0.72, 0.795, 0.855, 0.92].each_with_index do |fx, i|
160
+ top = 0.4 + noise(i, 1) * 0.06
161
+ lean = (noise(i, 2) - 0.5) * 0.03
162
+ part = rn_fig(u, v, fx + lean * (0.6 - v) * 4, top - 0.02, 0.5, 0.14, a)
163
+ next unless part
164
+ return part == :head ? [215, 170, 130] : cols[i]
165
+ end
166
+ end
167
+ # the room in one-point perspective
168
+ bw_r = 0.64
169
+ bw_t = 0.14
170
+ bw_b = 0.62
171
+ if (u - vp_u).abs < bw_r - vp_u && v > bw_t && v < bw_b
172
+ # three windows on the back wall, the middle one framing Jesus
173
+ [[0.5, 0.045, 0.2], [0.405, 0.022, 0.25], [0.595, 0.022, 0.25]].each do |wx, hw, wt|
174
+ next unless (u - wx).abs < hw && v > wt && v < 0.5
175
+ return v < 0.38 ? mix([215, 228, 230], [180, 200, 205], v * 2) : [120, 140, 110]
176
+ end
177
+ return [150, 130, 100] if (u - 0.5).abs < 0.06 && v > 0.17 && v < 0.2 # pediment
178
+ return jitter([150, 135, 105], u, v, 10)
179
+ end
180
+ s = (u - vp_u).abs / (bw_r - vp_u)
181
+ ceil_v = vp_v + (bw_t - vp_v) * s
182
+ floor_v = vp_v + (bw_b - vp_v) * s
183
+ if v < ceil_v
184
+ # coffered ceiling
185
+ depth = 1.0 / [(vp_v - v) / (vp_v - bw_t), 1.0].max
186
+ across = (u - vp_u) * depth
187
+ return Math.sin(depth * 22) > 0.6 || Math.sin(across * 40) > 0.8 ? [90, 70, 50] : [130, 105, 75]
188
+ end
189
+ if v < floor_v
190
+ # side walls with dark tapestries
191
+ depth = 1.0 / s
192
+ t = (v - ceil_v) / (floor_v - ceil_v)
193
+ panel = [[0.3, 0.42], [0.5, 0.62], [0.7, 0.8], [0.88, 0.96]].any? { |d0, d1| depth.between?(d0, d1) }
194
+ return Math.sin(t * 30) > 0.5 ? [55, 40, 30] : [35, 28, 22] if panel && t > 0.1 && t < 0.75
195
+ return jitter([130, 115, 90], u, v, 10)
196
+ end
197
+ jitter([75, 60, 45], u, v, 10)
198
+ end
199
+
200
+ # Leonardo da Vinci, Lady with an Ermine
201
+ def rn_lady_with_ermine(u, v)
202
+ a = 0.74
203
+ skin = [238, 214, 188]
204
+ # her long hand stroking the ermine
205
+ return mix(skin, [195, 160, 130], (u - 0.5) * 4) if rn_seg(u, v, 0.7, 0.8, 0.52, 0.67, a) < 0.03
206
+ # the ermine, white, curled diagonally in her arms with its head to the right
207
+ return [40, 30, 25] if dist(u, v, 0.535, 0.515, a) < 0.011 # eye
208
+ ermine = [[0.3, 0.95], [0.27, 0.82], [0.33, 0.68], [0.41, 0.58], [0.47, 0.51], [0.5, 0.48], [0.535, 0.49], [0.6, 0.52],
209
+ [0.54, 0.55], [0.51, 0.61], [0.49, 0.72], [0.45, 0.86], [0.41, 0.97]]
210
+ if inside?(u, v, ermine)
211
+ back = rn_seg(u, v, 0.3, 0.9, 0.48, 0.52, a)
212
+ return mix([248, 244, 232], [175, 165, 150], smooth(0.02, 0.07, back) * 0.6 + smooth(0.5, 0.9, v) * 0.3)
213
+ end
214
+ # face, turned three-quarters toward the right
215
+ if ellipse(u, v, 0.565, 0.3, 0.115, 0.105) < 1 && v > 0.215
216
+ return [30, 22, 18] if (v - 0.23).abs < 0.008 # the fine band across the forehead
217
+ return [70, 45, 35] if ((v - 0.29).abs < 0.012) && ((u - 0.58).abs < 0.018 || (u - 0.65).abs < 0.016) # eyes
218
+ return [185, 105, 95] if (v - 0.365).abs < 0.007 && (u - 0.61).abs < 0.022 # lips
219
+ return mix(skin, [205, 170, 140], smooth(0.56, 0.68, u) * 0.6 + smooth(0.48, 0.45, u) * 0.4)
220
+ end
221
+ # smooth dark hair drawn down along the cheeks and under the chin
222
+ return [50, 36, 28] if ellipse(u, v, 0.55, 0.29, 0.14, 0.125) < 1 && v < 0.41
223
+ # neck and the black bead necklace
224
+ if (u - 0.56).abs < 0.06 && v > 0.36 && v < 0.52
225
+ return [30, 25, 22] if (v - (0.47 + (u - 0.56)**2 * 4)).abs < 0.01 && Math.sin(u * 200) > -0.3
226
+ return mix(skin, [205, 170, 140], (v - 0.36) * 4)
227
+ end
228
+ # dress: square neckline, blue mantle on her right shoulder, red sleeve on the left
229
+ shoulders = 0.52 + (u - 0.56).abs * 0.35
230
+ if v > shoulders && u > 0.1
231
+ return mix(skin, [210, 175, 145], 0.3) if (u - 0.56).abs < 0.11 && v < 0.61 && v > 0.52
232
+ return [170, 140, 70] if (u - 0.56).abs < 0.13 && v < 0.64
233
+ return jitter([60, 80, 120], u, v, 14) if u < 0.4
234
+ return jitter([170, 70, 50], u, v, 16) if u > 0.64
235
+ return jitter([150, 110, 70], u, v, 12)
236
+ end
237
+ jitter([22, 18, 16], u, v, 6)
238
+ end
239
+
240
+ # Leonardo da Vinci, Vitruvian Man
241
+ def rn_vitruvian_man(u, v)
242
+ a = 0.73
243
+ paper = mix([232, 212, 170], [210, 185, 140], vnoise(u, v, 5) * 0.8)
244
+ ink = [95, 65, 40]
245
+ lw = 0.013
246
+ cx = 0.5
247
+ cy = 0.53
248
+ r = 0.27
249
+ # circle and square
250
+ return ink if (dist(u, v, cx, cy, a) - r).abs < lw
251
+ sq_b = cy + r
252
+ sq_t = sq_b - 0.47
253
+ sq_h = 0.235 / a
254
+ in_sq = (u - cx).abs < sq_h + lw / a && v > sq_t - lw && v < sq_b + lw
255
+ return ink if in_sq && (((u - cx).abs - sq_h).abs < lw / a || (v - sq_t).abs < lw || (v - sq_b).abs < lw)
256
+ # the man: arms and legs in two positions
257
+ body = mix(paper, ink, 0.55)
258
+ sh_v = sq_t + 0.11
259
+ hip_v = cy + 0.06
260
+ limbs = [
261
+ [cx, sh_v, cx - sq_h, sh_v], [cx, sh_v, cx + sq_h, sh_v],
262
+ [cx, sh_v, cx - r * 0.77 / a, cy - r * 0.64], [cx, sh_v, cx + r * 0.77 / a, cy - r * 0.64],
263
+ [cx - 0.02, hip_v, cx - 0.04, sq_b], [cx + 0.02, hip_v, cx + 0.04, sq_b],
264
+ [cx, hip_v, cx - r * 0.5 / a, cy + r * 0.87], [cx, hip_v, cx + r * 0.5 / a, cy + r * 0.87]
265
+ ]
266
+ dh = dist(u, v, cx, sq_t + 0.05, a)
267
+ return ink if dh < 0.045 && dh > 0.045 - lw * 1.5
268
+ return body if dh < 0.045
269
+ return body if rn_seg(u, v, cx, sh_v, cx, hip_v, a) < 0.045
270
+ return body if limbs.any? { |x1, y1, x2, y2| rn_seg(u, v, x1, y1, x2, y2, a) < 0.016 }
271
+ # lines of mirror-script text above and below
272
+ if (v > 0.05 && v < 0.2) || (v > 0.86 && v < 0.96)
273
+ row = v * 34
274
+ return mix(paper, ink, 0.45) if (row - row.floor).between?(0.3, 0.75) && u > 0.07 && u < 0.93 && noise((u * 45).floor, row.floor) > 0.3
275
+ end
276
+ paper
277
+ end
278
+
279
+ # Michelangelo, The Creation of Adam
280
+ def rn_creation_of_adam(u, v)
281
+ a = 2.2
282
+ skin = [215, 168, 120]
283
+ # God's hand and Adam's hand, almost touching
284
+ return mix(skin, [240, 210, 170], 0.3) if rn_seg(u, v, 0.515, 0.44, 0.63, 0.41, a) < 0.032
285
+ return skin if rn_seg(u, v, 0.3, 0.5, 0.44, 0.455, a) < 0.03 || rn_seg(u, v, 0.44, 0.455, 0.455, 0.475, a) < 0.022
286
+ # Adam reclining on the hill
287
+ return [80, 55, 35] if dist(u, v, 0.105, 0.42, a) < 0.07 && v < 0.42 + (u - 0.1) * 1.5
288
+ return skin if dist(u, v, 0.11, 0.44, a) < 0.07
289
+ adam = [
290
+ [0.14, 0.53, 0.26, 0.64, 0.1], # torso
291
+ [0.26, 0.64, 0.33, 0.52, 0.055], # raised thigh
292
+ [0.33, 0.52, 0.36, 0.76, 0.045], # shin
293
+ [0.26, 0.65, 0.44, 0.82, 0.05], # extended leg
294
+ [0.15, 0.52, 0.3, 0.5, 0.042], # upper arm resting on the knee
295
+ [0.12, 0.55, 0.07, 0.7, 0.035] # the other arm propping him up
296
+ ]
297
+ adam.each do |x1, y1, x2, y2, w|
298
+ d = rn_seg(u, v, x1, y1, x2, y2, a)
299
+ return mix(skin, [150, 105, 70], d / w * 0.6 + (v - 0.5)) if d < w
300
+ end
301
+ # God borne in the red cloak with angels
302
+ return [240, 236, 232] if dist(u, v, 0.61, 0.35, a) < 0.075 # white hair and beard
303
+ return [215, 190, 200] if rn_seg(u, v, 0.64, 0.41, 0.85, 0.5, a) < 0.11 # pale robe
304
+ return skin if rn_seg(u, v, 0.85, 0.5, 0.96, 0.58, a) < 0.05 # legs
305
+ god = ellipse(u, v, 0.75, 0.43, 0.18, 0.4)
306
+ if god < 1 && v < 0.82
307
+ return [205, 85, 60] if god > 0.8
308
+ [[0.7, 0.18], [0.77, 0.22], [0.84, 0.28], [0.72, 0.64], [0.8, 0.68], [0.68, 0.58], [0.87, 0.62]].each do |ax, ay|
309
+ d = dist(u, v, ax, ay, a)
310
+ return mix(skin, [180, 130, 90], d * 12) if d < 0.055
311
+ end
312
+ return jitter([140, 50, 40], u, v, 14)
313
+ end
314
+ return [90, 120, 90] if rn_seg(u, v, 0.86, 0.75, 0.99, 0.7, a) < 0.03 # green scarf
315
+ # the green-brown hill
316
+ hill = 0.62 + u * 0.55
317
+ return jitter(mix([110, 115, 80], [70, 75, 50], (v - hill) * 3), u, v, 12) if v > hill || v > 0.94
318
+ jitter(mix([205, 205, 195], [225, 220, 205], v), u, v, 8, 10)
319
+ end
320
+
321
+ # Raphael, The School of Athens
322
+ def rn_school_of_athens(u, v)
323
+ a = 1.54
324
+ x = (u - 0.5) * a
325
+ # the lunette frame
326
+ return [60, 50, 40] if v < 0.77 && Math.hypot(x, v - 0.77) > 0.765
327
+ # Plato (red) and Aristotle (blue) at the center of the steps
328
+ [[0.475, [190, 60, 50]], [0.525, [60, 90, 160]]].each do |fx, col|
329
+ part = rn_fig(u, v, fx, 0.42, 0.2, 0.05, a)
330
+ return part == :head ? [220, 190, 160] : col if part
331
+ end
332
+ # crowds on both sides
333
+ if v > 0.4 && v < 0.88
334
+ 16.times do |i|
335
+ fx = 0.07 + i * 0.058
336
+ next if (fx - 0.5).abs < 0.06
337
+ [[0.43, 0.18], [0.62, 0.23]].each_with_index do |(top, h), row|
338
+ jx = fx + (noise(i, row) - 0.5) * 0.04 + row * 0.02
339
+ next if row == 1 && (jx - 0.5).abs < 0.12
340
+ part = rn_fig(u, v, jx, top + noise(i, row + 5) * 0.03, h, 0.05, a)
341
+ next unless part
342
+ return [215, 180, 145] if part == :head
343
+ return hsv(noise(i, row + 9) * 360, 0.45, 0.75)
344
+ end
345
+ end
346
+ end
347
+ # steps and floor
348
+ if v > 0.6
349
+ return Math.sin(v * 200) > 0.5 ? [180, 170, 150] : [210, 200, 180] if v < 0.7
350
+ return (((u - 0.5) * 12 / (v + 0.1)).floor + (v * 20).floor).even? ? [205, 190, 160] : [160, 140, 115]
351
+ end
352
+ # receding barrel-vaulted arches
353
+ arches = [[0.48, 0.5], [0.34, 0.47], [0.23, 0.45], [0.15, 0.44]]
354
+ arches.each_with_index do |(r, c), k|
355
+ open = v < c ? Math.hypot(x, v - c) < r : x.abs < r
356
+ next if open
357
+ if v < c
358
+ d = Math.hypot(x, v - c)
359
+ return [225, 215, 195] if d < r * 1.08
360
+ ang = Math.atan2(v - c, x)
361
+ return Math.sin(ang * 24) > 0.4 || Math.sin(d * 120) > 0.6 ? [150, 135, 110] : [185, 170, 145]
362
+ end
363
+ # piers, with dark statue niches on the outermost
364
+ return [70, 60, 50] if k.zero? && (x.abs - 0.62).abs < 0.04 && v > 0.3 && v < 0.55
365
+ prev_r = k.zero? ? 0.77 : arches[k - 1][0]
366
+ return mix([200, 185, 160], [150, 135, 115], (x.abs - r) / (prev_r - r))
367
+ end
368
+ # bright sky through the last arch
369
+ mix([150, 185, 215], [245, 245, 235], smooth(0.2, 0.55, v))
370
+ end
371
+
372
+ # Jan van Eyck, The Arnolfini Portrait
373
+ def rn_arnolfini_portrait(u, v)
374
+ a = 0.73
375
+ # the little dog
376
+ return [130, 95, 60] if ellipse(u, v, 0.5, 0.92, 0.07, 0.035) < 1
377
+ # the man: huge dark hat, pale face, dark fur coat
378
+ return [25, 18, 15] if ellipse(u, v, 0.33, 0.17, 0.11, 0.06) < 1 && v < 0.2
379
+ return [225, 190, 160] if ellipse(u, v, 0.33, 0.25, 0.05, 0.055) < 1
380
+ return [225, 190, 160] if dist(u, v, 0.43, 0.42, a) < 0.03 # raised hand
381
+ coat = [[0.27, 0.3], [0.39, 0.3], [0.47, 0.55], [0.47, 0.95], [0.18, 0.95], [0.2, 0.55]]
382
+ if inside?(u, v, coat)
383
+ return [95, 70, 45] if ((u - 0.18).abs < 0.03 || (u - 0.47).abs < 0.03) && v > 0.5 # fur trim
384
+ return jitter([55, 35, 45], u, v, 10)
385
+ end
386
+ # the woman in her green gown with white headdress
387
+ return [240, 238, 230] if ellipse(u, v, 0.67, 0.25, 0.075, 0.06) < 1 && v < 0.27
388
+ return [235, 205, 180] if ellipse(u, v, 0.66, 0.29, 0.045, 0.05) < 1
389
+ gown = [[0.6, 0.34], [0.72, 0.34], [0.78, 0.55], [0.86, 0.95], [0.5, 0.95], [0.55, 0.6]]
390
+ if inside?(u, v, gown)
391
+ return [60, 90, 160] if v > 0.4 && v < 0.5 && (u - 0.6).abs < 0.03 # blue sleeve
392
+ return mix([40, 110, 50], [20, 60, 30], Math.sin(u * 70 + v * 8) * 0.5 + 0.5)
393
+ end
394
+ return [235, 205, 180] if dist(u, v, 0.53, 0.43, a) < 0.03 # joined hands
395
+ # the chandelier
396
+ return [190, 150, 60] if v < 0.12 && ((u - 0.5).abs < 0.008 || (v > 0.06 && (u - 0.5).abs < 0.06 && Math.sin(u * 160) > 0.2))
397
+ # the convex mirror on the back wall
398
+ dm = dist(u, v, 0.5, 0.45, a)
399
+ return [60, 40, 25] if dm < 0.07 && dm > 0.055
400
+ return mix([170, 175, 165], [110, 100, 80], dm / 0.055) if dm < 0.055
401
+ # the red bed at the right
402
+ return jitter(Math.sin(u * 60) > 0.6 ? [130, 20, 20] : [170, 30, 30], u, v, 10) if u > 0.78 && v > 0.22
403
+ # the window at the left
404
+ if u < 0.15 && v > 0.08 && v < 0.55
405
+ return [70, 50, 30] if (u - 0.07).abs < 0.01 || (v - 0.3).abs < 0.01
406
+ return mix([220, 220, 200], [170, 175, 150], v)
407
+ end
408
+ return jitter([120, 85, 50], u, v, 12) if v > 0.82 # wooden floor
409
+ jitter(mix([110, 85, 55], [80, 60, 40], (u - 0.5).abs * 2), u, v, 8)
410
+ end
411
+
412
+ # fur for the hare: brown with fine light and dark hairs
413
+ def rn_fur(u, v, shade)
414
+ n = brush(u, v, angle: 1.1, freq: 70.0, wobble: 0.6)
415
+ base = mix([150, 115, 75], [85, 60, 40], shade)
416
+ return mix(base, [215, 195, 160], 0.35) if n > 0.5
417
+ n < -0.6 ? Color.scale(base, 0.7) : base
418
+ end
419
+
420
+ # Albrecht Dürer, Young Hare
421
+ def rn_young_hare(u, v)
422
+ a = 0.88
423
+ # ears up and slightly back
424
+ [[0.4, 0.37, 0.48, 0.08], [0.46, 0.38, 0.6, 0.1]].each_with_index do |(x1, y1, x2, y2), i|
425
+ d = rn_seg(u, v, x1, y1, x2, y2, a)
426
+ return rn_fur(u, v, 0.3 + i * 0.3) if d < 0.045 - 0.025 * Fx.clamp01((y1 - v) / (y1 - y2))
427
+ end
428
+ # head facing left
429
+ if ellipse(u, v, 0.37, 0.47, 0.13, 0.11) < 1
430
+ de = dist(u, v, 0.33, 0.44, a)
431
+ return [240, 240, 235] if de < 0.025 && dist(u, v, 0.325, 0.435, a) < 0.008
432
+ return [25, 20, 15] if de < 0.025
433
+ return [220, 205, 175] if ellipse(u, v, 0.26, 0.53, 0.04, 0.03) < 1 # muzzle
434
+ return rn_fur(u, v, 0.3)
435
+ end
436
+ # crouching body
437
+ if ellipse(u, v, 0.57, 0.66, 0.28, 0.23) < 1 || ellipse(u, v, 0.34, 0.8, 0.09, 0.1) < 1
438
+ return [225, 210, 180] if v > 0.78 && u < 0.45 && Math.sin(u * 90) > 0.6 # paws
439
+ return rn_fur(u, v, Fx.clamp01(0.2 + (u - 0.3) * 0.9 + (v - 0.5) * 0.3))
440
+ end
441
+ # soft shadow on the cream ground
442
+ return [205, 190, 160] if ellipse(u, v, 0.55, 0.9, 0.36, 0.05) < 1
443
+ jitter([238, 228, 205], u, v, 6, 6)
444
+ end
445
+
446
+ # Albrecht Dürer, Self-Portrait at Twenty-Eight
447
+ def rn_durer_self_portrait(u, v)
448
+ a = 0.73
449
+ skin = [220, 175, 135]
450
+ cx = 0.5
451
+ # face, frontal and symmetric
452
+ if ellipse(u, v, cx, 0.33, 0.15, 0.15) < 1 && v > 0.21
453
+ mx = (u - cx).abs
454
+ return [40, 30, 20] if (v - 0.3).abs < 0.012 && (mx - 0.06).abs < 0.03 # eyes
455
+ return [110, 70, 40] if v > 0.4 && mx < 0.11 - (v - 0.4) * 0.6 # beard and moustache
456
+ return mix(skin, [160, 115, 80], mx * 3)
457
+ end
458
+ # long curly hair to the shoulders
459
+ if ellipse(u, v, cx, 0.36, 0.3, 0.28) < 1 && v < 0.6
460
+ curl = Math.sin(dist(u, v, cx + (u > cx ? 0.2 : -0.2), 0.45, a) * 90 + v * 20)
461
+ return curl > 0.3 ? [175, 115, 60] : [120, 75, 40]
462
+ end
463
+ return [100, 65, 40] if v > 0.42 && v < 0.5 && (u - cx).abs < 0.08 # beard below the chin
464
+ # fur collar and dark coat
465
+ if v > 0.5 && (u - cx).abs < 0.15 + (v - 0.5) * 1.2
466
+ return skin if ellipse(u, v, 0.47, 0.88, 0.06, 0.07) < 1 # hand at the fur
467
+ if (u - cx).abs < 0.12 + (v - 0.5) * 0.3 || v < 0.6
468
+ return mix([130, 90, 55], [80, 55, 35], brush(u, v, angle: 1.2, freq: 50.0) * 0.5 + 0.5)
469
+ end
470
+ return jitter([50, 38, 28], u, v, 8)
471
+ end
472
+ # the gold inscriptions on the dark ground
473
+ return [190, 150, 70] if v.between?(0.3, 0.36) && u.between?(0.08, 0.16) && Math.sin(u * 200) > 0
474
+ if u.between?(0.82, 0.95) && v.between?(0.26, 0.4) && (v * 40 - (v * 40).floor) < 0.4 && noise((u * 60).floor, (v * 40).floor) > 0.3
475
+ return [180, 140, 65]
476
+ end
477
+ jitter([30, 22, 16], u, v, 6)
478
+ end
479
+
480
+ # Pieter Bruegel the Elder, Hunters in the Snow
481
+ def rn_hunters_in_the_snow(u, v)
482
+ a = 1.39
483
+ dark = [40, 30, 25]
484
+ # hunters with spears and their dogs trudging along the hill
485
+ [[0.14, 0.66], [0.22, 0.69], [0.3, 0.72]].each do |hx, hy|
486
+ return dark if (u - hx).abs < 0.016 && v > hy - 0.12 && v < hy
487
+ return dark if dist(u, v, hx, hy - 0.13, a) < 0.02
488
+ return dark if rn_seg(u, v, hx + 0.01, hy - 0.08, hx - 0.05, hy - 0.2, a) < 0.007 # spears
489
+ end
490
+ [[0.18, 0.72], [0.25, 0.75], [0.34, 0.77], [0.1, 0.7]].each do |dx, dy|
491
+ return dark if ellipse(u, v, dx, dy, 0.025, 0.015) < 1 # dogs
492
+ end
493
+ # tall bare trees in a row down the slope
494
+ hill = 0.5 + u * 0.6 + 0.015 * Math.sin(u * 25)
495
+ [0.06, 0.19, 0.31, 0.43, 0.54].each_with_index do |tx, i|
496
+ base = 0.55 + tx * 0.6
497
+ next unless v < base
498
+ x = u - tx - (base - v) * 0.03 * (i.even? ? 1 : -1)
499
+ return dark if x.abs < 0.005 + (v / base) * 0.005
500
+ return dark if v < base - 0.12 && Math.sin(v * 45 + x * 40 + i) > 0.93 && x.abs < 0.08 * (base - v)
501
+ end
502
+ if v > hill
503
+ return [200, 80, 40] if dist(u, v, 0.04, 0.6, a) < 0.03 # the inn's fire
504
+ return jitter(mix([190, 198, 200], [240, 240, 235], smooth(0.0, 0.08, v - hill)), u, v, 8)
505
+ end
506
+ # the valley: snow, frozen ponds with skaters, houses
507
+ if v > 0.42 + 0.03 * Math.sin(u * 10)
508
+ [[0.62, 0.75, 0.2, 0.1], [0.82, 0.62, 0.15, 0.06], [0.55, 0.6, 0.08, 0.04]].each do |px, py, rx, ry|
509
+ next unless ellipse(u, v, px, py, rx, ry) < 1
510
+ return dark if noise((u * 90).floor, (v * 70).floor) > 0.9
511
+ return [140, 160, 150]
512
+ end
513
+ return [110, 60, 45] if noise((u * 40).floor, (v * 30).floor) > 0.86 # houses
514
+ return mix([235, 238, 232], [200, 205, 200], v)
515
+ end
516
+ # jagged mountains at the right
517
+ peaks = 0.33 + 0.08 * Math.sin(u * 30).abs - (u - 0.5) * 0.25
518
+ return mix([235, 240, 238], [150, 165, 160], (v - peaks) * 8) if u > 0.5 && v > peaks
519
+ return dark if dist(u, v, 0.62, 0.22, a) < 0.012 # the crow
520
+ mix([160, 185, 175], [200, 210, 200], v * 2)
521
+ end
522
+
523
+ # Pieter Bruegel the Elder, The Tower of Babel
524
+ def rn_tower_of_babel(u, v)
525
+ cx = 0.47
526
+ base_v = 0.78
527
+ top_v = 0.12
528
+ if v > top_v && v < base_v
529
+ t = (base_v - v) / (base_v - top_v) # 0 at the bottom, 1 at the top
530
+ hw = 0.36 * (1 - t * 0.78)
531
+ # the unfinished top: a ragged crown with red brick exposed on the right
532
+ hw *= 0.85 + 0.15 * Math.sin(u * 40) if t > 0.85
533
+ if (u - cx).abs < hw
534
+ band = t * 7.0
535
+ f = band - band.floor
536
+ sx = (u - cx) / hw # -1..1 across the cone
537
+ light = 1.0 - 0.45 * Fx.clamp01(sx + 0.3)
538
+ return Color.scale([160, 80, 60], light) if t > 0.7 && sx > 0.2 # raw brick
539
+ return Color.scale([225, 210, 175], light) if f > 0.82 # ledge
540
+ arch = Math.sin(Math.asin(sx.clamp(-1.0, 1.0)) * (22 - band * 2))
541
+ return Color.scale([70, 55, 45], light) if f < 0.55 && f > 0.15 && arch > 0.2
542
+ return jitter(Color.scale([200, 180, 140], light), u, v, 10)
543
+ end
544
+ end
545
+ # the harbor with ships at the right
546
+ if u > 0.72 && v > 0.62
547
+ return [230, 225, 210] if Math.sin(u * 40) > 0.85 && v < 0.75 && v > 0.64 # sails
548
+ return mix([120, 150, 160], [70, 100, 110], (v - 0.62) * 3)
549
+ end
550
+ # the city around the base
551
+ if v > 0.66
552
+ return [160, 85, 60] if noise((u * 60).floor, (v * 40).floor) > 0.8
553
+ return jitter([170, 160, 130], u, v, 14)
554
+ end
555
+ # countryside to the horizon
556
+ return jitter([120, 140, 110], u, v, 12) if v > 0.55 + 0.03 * Math.sin(u * 8)
557
+ # clouds drifting around the upper tower
558
+ return [235, 238, 240] if (v - 0.2 - u * 0.1).abs < 0.03 && Math.sin(u * 18) > 0
559
+ mix([150, 180, 205], [215, 225, 225], v * 1.6)
560
+ end
561
+
562
+ # Hieronymus Bosch, The Garden of Earthly Delights
563
+ def rn_garden_of_earthly_delights(u, v)
564
+ a = 1.77
565
+ # dark frames between the panels
566
+ return [30, 22, 18] if u < 0.01 || u > 0.99 || (u - 0.255).abs < 0.008 || (u - 0.745).abs < 0.008
567
+ if u < 0.255
568
+ # Eden: green land, the pink fountain, a pond
569
+ lu = u / 0.255
570
+ return mix([200, 215, 220], [150, 180, 190], 1 - v * 3) if v < 0.28
571
+ return [235, 160, 170] if (lu - 0.5).abs < 0.06 + (v > 0.48 ? 0.05 : 0) && v > 0.3 && v < 0.55
572
+ return [90, 130, 150] if ellipse(lu, v, 0.5, 0.6, 0.3, 0.06) < 1
573
+ return [230, 210, 190] if noise((u * 90).floor, (v * 60).floor) > 0.95 && v > 0.7
574
+ return jitter(mix([110, 150, 80], [60, 100, 50], v), u, v, 14)
575
+ end
576
+ if u < 0.745
577
+ # the garden: pale sky, pink and blue fantastical towers, crowds of tiny figures
578
+ cu = (u - 0.255) / 0.49
579
+ return mix([200, 215, 225], [170, 195, 210], 1 - v * 4) if v < 0.15
580
+ if v < 0.32
581
+ [[0.15, 0.04], [0.35, 0.03], [0.5, 0.05], [0.68, 0.03], [0.86, 0.04]].each_with_index do |(tx, tw), i|
582
+ next unless (cu - tx).abs < tw + (v - 0.15) * 0.15
583
+ return dist(cu, v, tx, 0.24, a * 0.5) < 0.04 ? [90, 120, 190] : (i.even? ? [230, 160, 175] : [120, 150, 200])
584
+ end
585
+ return mix([150, 190, 170], [120, 160, 130], (v - 0.15) * 4)
586
+ end
587
+ # the round pool with its cavalcade
588
+ pe = ellipse(cu, v, 0.5, 0.42, 0.22, 0.07)
589
+ return [100, 140, 170] if pe < 0.5
590
+ return noise((u * 80).floor, (v * 50).floor) > 0.5 ? [210, 180, 160] : [150, 110, 80] if pe < 1
591
+ [[0.2, 0.62], [0.78, 0.7], [0.45, 0.82]].each do |bx, by|
592
+ return [110, 150, 210] if dist(cu, v, bx, by, a * 0.5) < 0.04 # blue spheres
593
+ end
594
+ n = noise((u * 100).floor, (v * 70).floor)
595
+ return [240, 215, 200] if n > 0.86 # tiny pale figures
596
+ return [200, 50, 50] if n > 0.83 # giant berries
597
+ return jitter([150, 180, 120], u, v, 14)
598
+ end
599
+ # hell: darkness, fire glows, the tree-man, ice
600
+ ru = (u - 0.745) / 0.255
601
+ if v < 0.3
602
+ glow = Math.sin(ru * 18) * 0.04 + 0.18
603
+ return mix([250, 170, 60], [140, 30, 10], (glow - v).abs * 12) if (v - glow).abs < 0.06
604
+ return [20, 14, 12]
605
+ end
606
+ return [230, 220, 210] if ellipse(ru, v, 0.5, 0.45, 0.18, 0.06) < 1 # tree-man's broken shell
607
+ return [180, 175, 165] if ((ru - 0.42).abs < 0.03 && v > 0.48 && v < 0.65) || ((ru - 0.6).abs < 0.03 && v > 0.48 && v < 0.62)
608
+ return [110, 130, 150] if v > 0.6 && v < 0.68 # frozen lake
609
+ return [230, 120, 40] if noise((u * 80).floor, (v * 60).floor) > 0.94
610
+ jitter([35, 28, 30], u, v, 10)
611
+ end
612
+
613
+ # Johannes Vermeer, Girl with a Pearl Earring
614
+ def rn_girl_with_pearl_earring(u, v)
615
+ a = 0.88
616
+ skin = [235, 210, 180]
617
+ # the pearl
618
+ dp = dist(u, v, 0.4, 0.62, a)
619
+ return [255, 255, 250] if dp < 0.012
620
+ return [200, 205, 205] if dp < 0.028
621
+ # blue turban and the yellow cloth hanging behind
622
+ if ellipse(u, v, 0.48, 0.24, 0.17, 0.12) < 1 && v < 0.33 - (u - 0.48) * 0.2
623
+ return mix([60, 90, 170], [30, 45, 100], smooth(0.4, 0.6, u))
624
+ end
625
+ if inside?(u, v, [[0.6, 0.14], [0.66, 0.16], [0.7, 0.5], [0.66, 0.6], [0.6, 0.45]])
626
+ return mix([230, 200, 110], [170, 140, 60], (u - 0.58) * 6)
627
+ end
628
+ # face, lit from the left
629
+ if ellipse(u, v, 0.5, 0.47, 0.145, 0.17) < 1 && v > 0.3
630
+ return [60, 45, 35] if dist(u, v, 0.44, 0.44, a) < 0.022 || dist(u, v, 0.57, 0.44, a) < 0.02 # eyes
631
+ return [195, 85, 80] if ellipse(u, v, 0.48, 0.59, 0.035, 0.018) < 1 # parted lips
632
+ return mix(skin, [180, 145, 110], 0.5) if (u - 0.5).abs < 0.012 && v > 0.46 && v < 0.53 # nose shadow
633
+ return mix(skin, [130, 100, 75], smooth(0.5, 0.64, u))
634
+ end
635
+ # the ochre jacket with a white collar
636
+ shoulder = 0.68 + (u - 0.5).abs * 0.25
637
+ if v > shoulder
638
+ return [235, 232, 220] if v < shoulder + 0.05 && u > 0.33 && u < 0.62
639
+ return mix([170, 140, 70], [90, 70, 40], smooth(0.3, 0.8, u))
640
+ end
641
+ return [40, 32, 25] if (u - 0.45).abs < 0.06 && v > 0.6 # shadowed neck
642
+ jitter([22, 24, 18], u, v, 5)
643
+ end
644
+
645
+ # Johannes Vermeer, The Milkmaid
646
+ def rn_milkmaid(u, v)
647
+ a = 0.9
648
+ skin = [225, 185, 145]
649
+ # the milk stream into the bowl
650
+ return [250, 245, 230] if (u - 0.425).abs < 0.008 && v > 0.58 && v < 0.66
651
+ return [140, 85, 45] if ellipse(u, v, 0.42, 0.67, 0.08, 0.035) < 1 # bowl
652
+ return [115, 72, 40] if ellipse(u, v, 0.45, 0.545, 0.05, 0.055) < 1 # jug
653
+ # the maid
654
+ return [240, 238, 225] if ellipse(u, v, 0.57, 0.17, 0.075, 0.07) < 1 && v < 0.19 # white cap
655
+ return mix(skin, [190, 150, 115], smooth(0.55, 0.62, u)) if ellipse(u, v, 0.56, 0.23, 0.06, 0.065) < 1
656
+ return [100, 125, 115] if rn_seg(u, v, 0.48, 0.33, 0.43, 0.48, a) < 0.05 || rn_seg(u, v, 0.67, 0.35, 0.56, 0.5, a) < 0.05 # sleeves
657
+ return skin if rn_seg(u, v, 0.43, 0.48, 0.44, 0.54, a) < 0.035 || rn_seg(u, v, 0.56, 0.5, 0.49, 0.53, a) < 0.035
658
+ return mix([245, 210, 80], [180, 140, 50], (u - 0.45) * 4) if inside?(u, v, [[0.46, 0.29], [0.67, 0.29], [0.7, 0.52], [0.47, 0.52]]) # yellow bodice
659
+ if inside?(u, v, [[0.45, 0.5], [0.7, 0.5], [0.8, 0.98], [0.44, 0.98]])
660
+ return [55, 85, 150] if u > 0.48 && u < 0.72 + (v - 0.5) * 0.1 && v < 0.88 # blue apron
661
+ return [150, 50, 40]
662
+ end
663
+ # the table with bread and the green-blue cloth
664
+ if v > 0.62 && u < 0.46 - (v - 0.62) * 0.2
665
+ return [180, 120, 60] if ellipse(u, v, 0.18, 0.68, 0.1, 0.05) < 1 || ellipse(u, v, 0.3, 0.69, 0.05, 0.03) < 1 # bread
666
+ return jitter([70, 100, 110], u, v, 14)
667
+ end
668
+ return [120, 90, 60] if u > 0.82 && v > 0.88 # foot warmer
669
+ # the window at the left
670
+ if u < 0.2 && v > 0.08 && v < 0.5
671
+ return [80, 75, 60] if (u * 20 - (u * 20).floor) < 0.15 || (v * 20 - (v * 20).floor) < 0.15
672
+ return mix([230, 235, 225], [180, 190, 180], v)
673
+ end
674
+ return [70, 60, 50] if dist(u, v, 0.1, 0.56, a) < 0.05 # basket on the wall
675
+ jitter(mix([215, 210, 190], [170, 160, 140], smooth(0.2, 1.0, u)), u, v, 8, 12)
676
+ end
677
+
678
+ # Johannes Vermeer, View of Delft
679
+ def rn_view_of_delft(u, v)
680
+ # the low skyline: roofs, towers, the gate
681
+ roof = 0.56 - 0.02 * Math.sin(u * 37).abs - (Math.sin(u * 91) > 0.5 ? 0.015 : 0)
682
+ tower = ((u - 0.66).abs < 0.015 && v > 0.36) || ((u - 0.34).abs < 0.02 && v > 0.47) || ((u - 0.28).abs < 0.012 && v > 0.45)
683
+ if v < 0.64 && (v > roof || tower)
684
+ return [235, 210, 120] if (u - 0.66).abs < 0.015 # sunlit church tower
685
+ return [60, 75, 90] if v < roof + 0.02 && u > 0.36 && u < 0.6 && !tower
686
+ return noise((u * 70).floor, (v * 50).floor) > 0.6 ? [150, 70, 45] : [110, 75, 55]
687
+ end
688
+ # the bridge
689
+ return [100, 80, 60] if v > 0.6 && v < 0.64 && u > 0.38 && u < 0.5
690
+ # water with reflections
691
+ if v > 0.64 && v < 0.86 - 0.03 * Math.sin(u * 6)
692
+ refl = Math.sin(v * 160 + u * 4) > 0.3 ? 0.9 : 1.0
693
+ base = u < 0.45 ? [65, 60, 55] : [70, 85, 95]
694
+ return Color.scale(mix(base, [120, 140, 145], (v - 0.64) * 3), refl)
695
+ end
696
+ # the sandy bank with figures and boats
697
+ if v >= 0.64
698
+ return [40, 50, 70] if ((u - 0.1).abs < 0.008 || (u - 0.14).abs < 0.008) && v > 0.85 && v < 0.9
699
+ return [60, 50, 40] if u > 0.8 && v < 0.88 && ellipse(u, v, 0.88, 0.86, 0.08, 0.025) < 1
700
+ return jitter([200, 175, 130], u, v, 12)
701
+ end
702
+ # the big cloudy sky
703
+ cloud = vnoise(u * 1.5, v * 3, 4)
704
+ base = mix([90, 125, 175], [190, 205, 215], v * 1.8)
705
+ return mix(base, [80, 85, 95], 0.7) if v < 0.15 && cloud > 0.45
706
+ return mix(base, [245, 245, 240], (cloud - 0.5) * 3) if cloud > 0.5
707
+ base
708
+ end
709
+
710
+ # Rembrandt, The Night Watch
711
+ def rn_night_watch(u, v)
712
+ a = 1.2
713
+ # the captain in black with a red sash, and the lieutenant in gold
714
+ return [230, 200, 170] if dist(u, v, 0.48, 0.42, a) < 0.035
715
+ return [240, 240, 235] if ellipse(u, v, 0.48, 0.47, 0.04, 0.015) < 1 # ruff
716
+ return [20, 15, 12] if dist(u, v, 0.48, 0.385, a) < 0.05 && v < 0.4 # hat
717
+ if inside?(u, v, [[0.44, 0.48], [0.52, 0.48], [0.55, 0.95], [0.42, 0.95]])
718
+ return [200, 40, 30] if (v - 0.52 - (u - 0.44) * 0.8).abs < 0.025 # sash
719
+ return [20, 16, 14]
720
+ end
721
+ return [225, 195, 160] if dist(u, v, 0.6, 0.44, a) < 0.03
722
+ return [235, 225, 200] if dist(u, v, 0.6, 0.41, a) < 0.04 && v < 0.42 # white plume hat
723
+ return mix([250, 225, 130], [180, 140, 60], (u - 0.57) * 8) if inside?(u, v, [[0.57, 0.48], [0.64, 0.48], [0.66, 0.95], [0.56, 0.95]])
724
+ # the glowing girl in gold at the left
725
+ return [245, 220, 150] if dist(u, v, 0.33, 0.52, a) < 0.03
726
+ return mix([255, 225, 120], [200, 160, 70], dist(u, v, 0.33, 0.62, a) * 10) if ellipse(u, v, 0.33, 0.64, 0.05, 0.1) < 1
727
+ # pikes and the musketeers in the gloom
728
+ if rn_seg(u, v, 0.15, 0.7, 0.3, 0.1, a) < 0.006 || rn_seg(u, v, 0.7, 0.6, 0.85, 0.05, a) < 0.006 || rn_seg(u, v, 0.62, 0.55, 0.55, 0.05, a) < 0.006
729
+ return [90, 70, 45]
730
+ end
731
+ [[0.12, 0.45], [0.2, 0.38], [0.26, 0.5], [0.38, 0.3], [0.55, 0.28], [0.7, 0.35], [0.78, 0.42], [0.86, 0.38], [0.92, 0.5], [0.67, 0.25]].each_with_index do |(fx, fy), i|
732
+ return mix([200, 160, 110], [110, 80, 50], i * 0.08) if dist(u, v, fx, fy, a) < 0.025
733
+ return mix([70, 50, 30], [30, 22, 16], noise(i, 3)) if (u - fx).abs < 0.04 && v > fy + 0.02 && v < 0.95
734
+ end
735
+ return [55, 45, 40] if v > 0.92 # floor
736
+ glow = Fx.clamp01(1 - dist(u, v, 0.5, 0.55, a) * 1.6)
737
+ jitter(mix([22, 16, 10], [95, 70, 40], glow), u, v, 6)
738
+ end
739
+
740
+ # Diego Velázquez, Las Meninas
741
+ def rn_las_meninas(u, v)
742
+ a = 0.87
743
+ skin = [230, 200, 170]
744
+ # the Infanta in her wide pale gown
745
+ return [235, 210, 150] if dist(u, v, 0.5, 0.58, a) < 0.04 && v < 0.58 # blond hair
746
+ return skin if dist(u, v, 0.5, 0.6, a) < 0.035
747
+ return mix([235, 230, 215], [190, 185, 170], (u - 0.42) * 5) if v > 0.64 && v < 0.92 && (u - 0.5).abs < 0.03 + (v - 0.64) * 0.55
748
+ # the maids of honor
749
+ [[0.38, 0.63, [150, 140, 110]], [0.62, 0.62, [120, 110, 90]]].each do |mx, my, col|
750
+ return skin if dist(u, v, mx, my, a) < 0.03
751
+ return col if v > my + 0.03 && v < 0.92 && (u - mx).abs < 0.03 + (v - my) * 0.3
752
+ end
753
+ # the dwarf and the dog at the right
754
+ return skin if dist(u, v, 0.76, 0.7, a) < 0.03
755
+ return [50, 55, 70] if v > 0.73 && v < 0.9 && (u - 0.76).abs < 0.05
756
+ return [150, 120, 80] if ellipse(u, v, 0.78, 0.92, 0.09, 0.03) < 1
757
+ # the painter before his canvas
758
+ return skin if dist(u, v, 0.3, 0.42, a) < 0.03
759
+ return [30, 25, 22] if v > 0.45 && v < 0.85 && (u - 0.3).abs < 0.04
760
+ # the back of the huge canvas
761
+ if u < 0.22 && v > 0.05 && v < 0.9
762
+ return [70, 50, 35] if u > 0.2 || (v - 0.1).abs < 0.01
763
+ return jitter([95, 70, 45], u, v, 10)
764
+ end
765
+ # the bright open doorway with a figure, and the mirror
766
+ if (u - 0.62).abs < 0.035 && v > 0.36 && v < 0.55
767
+ return [30, 25, 20] if (u - 0.62).abs < 0.01 && v > 0.42
768
+ return [240, 220, 170]
769
+ end
770
+ return [170, 175, 170] if (u - 0.5).abs < 0.02 && v > 0.4 && v < 0.46 # the royal couple in the mirror
771
+ return [40, 30, 22] if (u - 0.5).abs < 0.03 && v > 0.38 && v < 0.48
772
+ # paintings hung high on the back wall
773
+ return [45, 35, 25] if v > 0.12 && v < 0.27 && ((u - 0.4).abs < 0.08 || (u - 0.62).abs < 0.08)
774
+ # light from the windows at the right
775
+ return jitter(mix([80, 70, 55], [140, 120, 90], u), u, v, 8) if v > 0.9
776
+ jitter(mix([60, 50, 38], [110, 95, 75], smooth(0.3, 1.0, u) * (1 - v * 0.5)), u, v, 8)
777
+ end
778
+
779
+ # Caravaggio, The Calling of Saint Matthew
780
+ def rn_calling_of_saint_matthew(u, v)
781
+ a = 1.06
782
+ skin = [215, 170, 125]
783
+ # Christ at the right edge pointing across the room
784
+ return [200, 150, 110] if rn_seg(u, v, 0.82, 0.44, 0.66, 0.42, a) < 0.016
785
+ return skin if dist(u, v, 0.86, 0.37, a) < 0.04
786
+ return [130, 50, 40] if inside?(u, v, [[0.82, 0.42], [0.92, 0.42], [0.98, 0.95], [0.84, 0.95]])
787
+ return [120, 100, 70] if inside?(u, v, [[0.7, 0.47], [0.8, 0.47], [0.84, 0.95], [0.68, 0.95]]) # Peter
788
+ return skin if dist(u, v, 0.75, 0.43, a) < 0.035
789
+ # the group at the table on the left, lit by the beam
790
+ [[0.12, 0.38, [200, 170, 60]], [0.25, 0.4, [190, 70, 50]], [0.37, 0.36, [60, 80, 60]], [0.48, 0.4, [220, 200, 170]]].each do |fx, fy, col|
791
+ return mix(skin, [255, 230, 190], 0.3) if dist(u, v, fx, fy, a) < 0.04
792
+ return [30, 25, 20] if dist(u, v, fx, fy - 0.035, a) < 0.045 && v < fy - 0.01 # hats
793
+ return col if (u - fx).abs < 0.06 && v > fy + 0.04 && v < 0.66
794
+ end
795
+ return [200, 160, 110] if v > 0.62 && v < 0.7 && u < 0.6 # table top
796
+ return [70, 45, 35] if v > 0.7 && v < 0.95 && u < 0.6 && (u < 0.03 || (u - 0.55).abs < 0.02 || Math.sin(u * 30) > 0.8)
797
+ # the window, dark
798
+ if u > 0.38 && u < 0.62 && v > 0.08 && v < 0.32
799
+ return [150, 130, 90] if (u - 0.5).abs < 0.008 || (v - 0.2).abs < 0.008 || u < 0.39 || u > 0.61
800
+ return [40, 35, 28]
801
+ end
802
+ # the diagonal beam of light from the upper right
803
+ beam = v - (0.05 + (1 - u) * 0.55)
804
+ return jitter(mix([150, 125, 85], [95, 80, 55], -beam * 3), u, v, 8) if beam < 0.02 && beam > -0.28 && v < 0.62
805
+ jitter([30, 26, 22], u, v, 6)
806
+ end
807
+ end
808
+
809
+ piece(:birth_of_venus, "The Birth of Venus", "Sandro Botticelli", "c. 1485", aspect: 1.58) { |u, v| rn_birth_of_venus(u, v) }
810
+ piece(:primavera, "Primavera", "Sandro Botticelli", "c. 1480", aspect: 1.55) { |u, v| rn_primavera(u, v) }
811
+ piece(:last_supper, "The Last Supper", "Leonardo da Vinci", "1498", aspect: 1.9) { |u, v| rn_last_supper(u, v) }
812
+ piece(:lady_with_ermine, "Lady with an Ermine", "Leonardo da Vinci", "c. 1490", aspect: 0.74) { |u, v| rn_lady_with_ermine(u, v) }
813
+ piece(:vitruvian_man, "Vitruvian Man", "Leonardo da Vinci", "c. 1490", aspect: 0.73) { |u, v| rn_vitruvian_man(u, v) }
814
+ piece(:creation_of_adam, "The Creation of Adam", "Michelangelo", "c. 1512", aspect: 2.2) { |u, v| rn_creation_of_adam(u, v) }
815
+ piece(:school_of_athens, "The School of Athens", "Raphael", "1511", aspect: 1.54) { |u, v| rn_school_of_athens(u, v) }
816
+ piece(:arnolfini_portrait, "The Arnolfini Portrait", "Jan van Eyck", "1434", aspect: 0.73) { |u, v| rn_arnolfini_portrait(u, v) }
817
+ piece(:young_hare, "Young Hare", "Albrecht Dürer", "1502", aspect: 0.88) { |u, v| rn_young_hare(u, v) }
818
+ piece(:durer_self_portrait, "Self-Portrait at Twenty-Eight", "Albrecht Dürer", "1500", aspect: 0.73) { |u, v| rn_durer_self_portrait(u, v) }
819
+ piece(:hunters_in_the_snow, "Hunters in the Snow", "Pieter Bruegel the Elder", "1565", aspect: 1.39) { |u, v| rn_hunters_in_the_snow(u, v) }
820
+ piece(:tower_of_babel, "The Tower of Babel", "Pieter Bruegel the Elder", "1563", aspect: 1.36) { |u, v| rn_tower_of_babel(u, v) }
821
+ piece(:garden_of_earthly_delights, "The Garden of Earthly Delights", "Hieronymus Bosch", "c. 1500", aspect: 1.77) { |u, v| rn_garden_of_earthly_delights(u, v) }
822
+ piece(:girl_with_pearl_earring, "Girl with a Pearl Earring", "Johannes Vermeer", "c. 1665", aspect: 0.88) { |u, v| rn_girl_with_pearl_earring(u, v) }
823
+ piece(:milkmaid, "The Milkmaid", "Johannes Vermeer", "c. 1658", aspect: 0.9) { |u, v| rn_milkmaid(u, v) }
824
+ piece(:view_of_delft, "View of Delft", "Johannes Vermeer", "c. 1661", aspect: 1.2) { |u, v| rn_view_of_delft(u, v) }
825
+ piece(:night_watch, "The Night Watch", "Rembrandt", "1642", aspect: 1.2) { |u, v| rn_night_watch(u, v) }
826
+ piece(:las_meninas, "Las Meninas", "Diego Velázquez", "1656", aspect: 0.87) { |u, v| rn_las_meninas(u, v) }
827
+ piece(:calling_of_saint_matthew, "The Calling of Saint Matthew", "Caravaggio", "1600", aspect: 1.06) { |u, v| rn_calling_of_saint_matthew(u, v) }
828
+ end
829
+ end