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,703 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dopairb
4
+ module Gallery
5
+ module Paint
6
+ module_function
7
+
8
+ # Friedrich, Wanderer above the Sea of Fog
9
+ def wanderer_above_the_sea_of_fog(u, v)
10
+ # the wanderer: dark coat, seen from behind, on the summit
11
+ return [150, 95, 60] if ellipse(u, v, 0.50, 0.335, 0.035, 0.028) < 1 # windswept reddish hair
12
+ return [40, 30, 30] if ellipse(u, v, 0.50, 0.365, 0.03, 0.02) < 1 # collar / neck
13
+ coat_w = 0.06 - (v - 0.40).abs * 0.15
14
+ return mix([35, 45, 40], [20, 25, 25], (u - 0.44) * 8) if v > 0.37 && v < 0.56 && (u - 0.50).abs < coat_w + 0.012
15
+ if v >= 0.54 && v < 0.69
16
+ leg_l = 0.475 - (v - 0.54) * 0.25
17
+ leg_r = 0.525 + (v - 0.54) * 0.30
18
+ return [30, 30, 30] if (u - leg_l).abs < 0.014 || (u - leg_r).abs < 0.014
19
+ end
20
+ return [60, 45, 35] if (u - (0.585 + (v - 0.48) * 0.1)).abs < 0.006 && v > 0.48 && v < 0.70 # walking stick
21
+ # the dark rocky crag
22
+ crag = 0.68 + (u - 0.50).abs * 0.55 + 0.03 * Math.sin(u * 37) + 0.02 * vnoise(u, v, 20)
23
+ crag = [crag, 0.70].max
24
+ return jitter(mix([45, 40, 38], [20, 18, 18], (v - crag) * 4), u, v, 12) if v > crag && u > 0.08 && u < 0.97
25
+ # rocks poking through the fog
26
+ return jitter([95, 95, 100], u, v, 10) if inside?(u, v, [[0.0, 0.62], [0.06, 0.52], [0.13, 0.55], [0.22, 0.66], [0.0, 0.70]])
27
+ return jitter([90, 92, 100], u, v, 10) if inside?(u, v, [[0.66, 0.60], [0.74, 0.50], [0.80, 0.53], [0.90, 0.62], [0.78, 0.66]])
28
+ return jitter([110, 112, 120], u, v, 8) if inside?(u, v, [[0.20, 0.52], [0.28, 0.47], [0.36, 0.53], [0.30, 0.56]])
29
+ # distant mountains on the horizon
30
+ mtn = 0.47 - 0.10 * smooth(0.55, 0.85, u) * (1 - smooth(0.9, 1.0, u) * 0.3) - 0.05 * Math.exp(-((u - 0.12) / 0.10)**2)
31
+ return mix([150, 155, 170], [175, 180, 190], (v - mtn) * 5) if v > mtn && v < 0.50
32
+ # the sea of fog
33
+ if v > 0.46
34
+ f = vnoise(u, v, 9) * 0.6 + vnoise(u + 3, v, 22) * 0.4
35
+ return mix([205, 208, 210], [150, 155, 165], f * 1.2 - 0.2 + (v - 0.46) * 0.4)
36
+ end
37
+ # pale sky with soft clouds
38
+ sky = ramp(v / 0.47, [150, 165, 185], [200, 205, 210], [235, 228, 210])
39
+ c = vnoise(u, v, 6)
40
+ c > 0.6 ? mix(sky, [235, 235, 232], (c - 0.6) * 1.5) : sky
41
+ end
42
+
43
+ # Friedrich, The Monk by the Sea
44
+ def monk_by_the_sea(u, v)
45
+ dune = 0.80 - (1 - u) * 0.06 + 0.012 * Math.sin(u * 9)
46
+ if v > dune
47
+ return [30, 30, 35] if (u - 0.33).abs < 0.008 && v < dune + 0.07 && v > dune - 0.0 # the monk
48
+ return jitter(mix([215, 205, 175], [175, 160, 130], (v - dune) * 5), u, v, 8)
49
+ end
50
+ return [30, 30, 35] if (u - 0.33).abs < 0.008 && v > dune - 0.07 && v < dune + 0.01 # monk above the dune line
51
+ return [30, 30, 35] if ellipse(u, v, 0.33, dune - 0.08, 0.009, 0.015) < 1
52
+ if v > 0.71
53
+ return mix([20, 35, 45], [35, 55, 70], Math.sin(u * 60 + v * 30) * 0.5 + 0.5) # dark sea
54
+ end
55
+ # vast sky: dark at the top, hazy light band above the horizon
56
+ haze = smooth(0.30, 0.70, v)
57
+ sky = ramp(haze, [55, 65, 75], [105, 120, 140], [170, 190, 205])
58
+ c = vnoise(u, v, 5)
59
+ sky = mix(sky, [210, 210, 205], (c - 0.55) * 1.5) if v > 0.45 && c > 0.55
60
+ jitter(sky, u, v, 6, 12)
61
+ end
62
+
63
+ # Friedrich, The Sea of Ice
64
+ def sea_of_ice(u, v)
65
+ light = [225, 228, 225]
66
+ mid = [165, 175, 180]
67
+ shade = [105, 115, 125]
68
+ # foreground slabs
69
+ return jitter(mix([150, 140, 120], [110, 100, 85], (v - 0.85) * 6), u, v, 10) if v > 0.86 + 0.03 * Math.sin(u * 11)
70
+ return jitter([205, 200, 185], u, v, 8) if inside?(u, v, [[0.0, 0.80], [0.18, 0.66], [0.40, 0.76], [0.30, 0.90], [0.0, 0.92]])
71
+ return jitter([130, 130, 125], u, v, 8) if inside?(u, v, [[0.30, 0.90], [0.40, 0.76], [0.48, 0.80], [0.45, 0.92]])
72
+ return jitter([190, 190, 180], u, v, 8) if inside?(u, v, [[0.55, 0.92], [0.70, 0.70], [0.98, 0.80], [1.0, 0.95]])
73
+ # the crushed ship's stern, at the right of the pile
74
+ return [70, 50, 35] if inside?(u, v, [[0.72, 0.62], [0.78, 0.46], [0.83, 0.48], [0.80, 0.66]])
75
+ return [60, 45, 35] if (u - 0.80 + (v - 0.40) * 0.3).abs < 0.006 && v > 0.36 && v < 0.48 # broken mast
76
+ # the pyramid of ice slabs
77
+ if inside?(u, v, [[0.18, 0.80], [0.36, 0.45], [0.55, 0.10], [0.62, 0.18], [0.76, 0.48], [0.88, 0.74], [0.60, 0.82]])
78
+ # a sun-lit left face and a shaded right face, banded into slabs
79
+ side = u - (0.55 + (v - 0.10) * 0.15)
80
+ band = Math.sin((u * 0.6 + v) * 30)
81
+ base = side < 0 ? mix(light, mid, (v - 0.1) * 0.6) : mix(shade, mid, 0.2)
82
+ base = mix(base, side < 0 ? mid : [80, 90, 100], 0.6) if band > 0.75
83
+ return jitter(base, u, v, 8)
84
+ end
85
+ # low far ice field
86
+ return jitter(mix([175, 180, 180], [140, 150, 155], (v - 0.6) * 4), u, v, 8) if v > 0.62 + 0.02 * Math.sin(u * 23)
87
+ # cold sky
88
+ sky = ramp(v / 0.62, [120, 140, 160], [165, 180, 195], [210, 215, 210])
89
+ jitter(sky, u, v, 6, 8)
90
+ end
91
+
92
+ # Turner, The Fighting Temeraire
93
+ def fighting_temeraire(u, v)
94
+ horizon = 0.68
95
+ sun_glow = Math.exp(-((u - 0.80) / 0.18)**2 - ((v - 0.64) / 0.20)**2)
96
+ # background: sunset sky and the water reflecting it
97
+ if v > horizon
98
+ refl = sun_glow * 1.6 + Math.exp(-((u - 0.80) / 0.05)**2) * 0.7
99
+ ripple = Math.sin(v * 120 + Math.sin(u * 20) * 2) > 0.3 ? 0.9 : 1.0
100
+ bg = Color.scale(mix([95, 125, 140], [240, 140, 60], refl), ripple)
101
+ elsif dist(u, v, 0.80, 0.64, 1.3) < 0.04
102
+ bg = [255, 225, 140]
103
+ else
104
+ bg = mix([110, 150, 185], [185, 200, 205], v)
105
+ bg = mix(bg, [235, 115, 40], smooth(0.40, 0.95, u) * smooth(0.25, 0.65, v))
106
+ bg = mix(bg, [250, 200, 100], sun_glow)
107
+ streak = Math.sin(v * 40 + Math.sin(u * 8) * 2)
108
+ bg = mix(bg, [200, 60, 40], 0.5) if streak > 0.7 && u > 0.55 && v > 0.3
109
+ bg = jitter(bg, u, v, 8, 10)
110
+ end
111
+ # the dark tugboat ahead of the old ship
112
+ return [30, 28, 30] if inside?(u, v, [[0.34, 0.655], [0.52, 0.65], [0.50, 0.71], [0.36, 0.71]])
113
+ return [25, 22, 22] if (u - 0.44).abs < 0.012 && v > 0.56 && v < 0.66
114
+ # its red-orange smoke plume streaming up and back
115
+ cx = 0.44 - (0.57 - v) * 0.55
116
+ w = 0.02 + (0.57 - v) * 0.15
117
+ if v < 0.57 && v > 0.22 && (u - cx).abs < w
118
+ return mix([170, 60, 35], bg, (0.57 - v) * 1.4 + (u - cx).abs / w * 0.3)
119
+ end
120
+ # the ghostly old ship, pale against the sky
121
+ ghost = [235, 228, 205]
122
+ return mix(ghost, bg, 0.15 + (v - 0.58) * 2) if inside?(u, v, [[0.06, 0.58], [0.40, 0.57], [0.37, 0.69], [0.10, 0.70]])
123
+ [0.13, 0.22, 0.31].each_with_index do |mx, i|
124
+ top = [0.14, 0.08, 0.16][i]
125
+ return mix(ghost, bg, 0.3) if (u - mx).abs < 0.007 && v > top && v < 0.58
126
+ [0.22, 0.34, 0.46].each do |yy|
127
+ next if yy < top + 0.04
128
+ return mix(ghost, bg, 0.4) if (v - yy).abs < 0.009 && (u - mx).abs < 0.03 + (yy - 0.22) * 0.12
129
+ end
130
+ end
131
+ bg
132
+ end
133
+
134
+ # Turner, Rain, Steam and Speed - The Great Western Railway
135
+ def rain_steam_and_speed(u, v)
136
+ haze = vnoise(u, v, 4) * 0.6 + vnoise(u + 5, v, 10) * 0.4
137
+ # background: golden haze, blue patch top left, river and fields below
138
+ if v > 0.58
139
+ base = u < 0.55 ? mix([185, 175, 140], [140, 125, 95], (v - 0.58) * 3) : mix([140, 110, 65], [95, 70, 45], (v - 0.58) * 3)
140
+ bg = jitter(mix(base, [215, 200, 150], haze * 0.4), u, v, 10)
141
+ else
142
+ bg = mix([230, 210, 145], [185, 170, 145], haze)
143
+ bg = mix(bg, [125, 155, 185], smooth(0.40, 0.0, u + v * 0.7) * 0.8)
144
+ bg = mix(bg, [250, 238, 195], Math.exp(-((u - 0.50) / 0.18)**2 - ((v - 0.30) / 0.18)**2) * 0.7)
145
+ bg = jitter(bg, u, v, 8, 12)
146
+ end
147
+ # the old road bridge with arches, far left
148
+ if u < 0.32 && v > 0.53 && v < 0.60
149
+ return mix([160, 145, 115], bg, 0.4) if Math.sin(u * 55) < 0.3 || v < 0.545
150
+ end
151
+ # the railway bridge from the lower right, receding toward the left
152
+ t = smooth(0.25, 1.0, u)
153
+ deck_top = 0.50 + t * 0.16
154
+ deck_bot = deck_top + 0.015 + t * 0.20
155
+ fade = 0.80 - t * 0.75
156
+ # the locomotive charging toward the viewer
157
+ if ellipse(u, v, 0.78, 0.58, 0.085, 0.09) < 1 && v < deck_top + 0.06
158
+ return [255, 190, 100] if dist(u, v, 0.78, 0.62, 1.3) < 0.018 # firebox glow
159
+ return mix([35, 28, 25], bg, 0.1)
160
+ end
161
+ return [30, 25, 22] if (u - 0.78).abs < 0.016 && v > 0.43 && v < 0.52 # chimney
162
+ return mix([200, 195, 185], bg, 0.6) if ellipse(u, v, 0.66, 0.41, 0.14, 0.05) < 1 # steam
163
+ if u > 0.25 && v > deck_top && v < deck_bot
164
+ return mix([90, 60, 35], bg, fade)
165
+ end
166
+ # piers under the bridge
167
+ if u > 0.45 && v > deck_bot && Math.sin((u - 0.45) * 22) > 0.8
168
+ return mix([110, 80, 50], bg, fade + 0.2)
169
+ end
170
+ bg
171
+ end
172
+
173
+ # Constable, The Hay Wain
174
+ def hay_wain(u, v)
175
+ # the hay wain and horses in the shallows
176
+ return [150, 50, 35] if ellipse(u, v, 0.50, 0.69, 0.025, 0.035) < 1 && ellipse(u, v, 0.50, 0.69, 0.012, 0.018) > 1 # red wheel
177
+ return [160, 115, 60] if inside?(u, v, [[0.40, 0.57], [0.60, 0.59], [0.59, 0.68], [0.42, 0.68]])
178
+ return [190, 175, 140] if ellipse(u, v, 0.52, 0.66, 0.02, 0.025) < 1 # driver
179
+ return [60, 45, 32] if inside?(u, v, [[0.29, 0.62], [0.41, 0.61], [0.41, 0.68], [0.30, 0.68]])
180
+ return [60, 45, 32] if ((u - 0.31).abs < 0.009 || (u - 0.39).abs < 0.009) && v > 0.67 && v < 0.73
181
+ return [55, 40, 30] if ellipse(u, v, 0.285, 0.605, 0.02, 0.03) < 1
182
+ # Willy Lott's cottage on the left
183
+ return [150, 65, 40] if inside?(u, v, [[0.0, 0.47], [0.07, 0.40], [0.18, 0.49], [0.0, 0.49]])
184
+ if u < 0.17 && v >= 0.49 && v < 0.66
185
+ return [70, 60, 50] if noise((u * 40).floor, (v * 30).floor) > 0.8
186
+ return [205, 190, 160]
187
+ end
188
+ # the great trees over the cottage and along the left half
189
+ tree_l = v > 0.06 + 0.35 * smooth(0.15, 0.55, u) + 0.04 * Math.sin(u * 30) && v < 0.64 && u < 0.58
190
+ tree_r = u > 0.87 + 0.04 * Math.sin(v * 13) && v > 0.30 && v < 0.62
191
+ if tree_l || tree_r
192
+ f = vnoise(u, v, 14)
193
+ return mix([35, 50, 28], [100, 115, 55], f * f * 1.4)
194
+ end
195
+ # far trees and the sunlit meadow on the right
196
+ return mix([60, 85, 50], [90, 110, 60], vnoise(u, v, 20)) if v > 0.51 - 0.025 * Math.sin(u * 20).abs && v < 0.56
197
+ return jitter(mix([185, 180, 95], [125, 145, 60], (v - 0.56) * 10), u, v, 10) if v >= 0.56 && v < 0.63 && u > 0.36
198
+ # the river, reflecting the trees and sky
199
+ if v >= 0.62 && v < 0.88 - 0.10 * u
200
+ ripple = u > 0.45 && Math.sin(v * 120 + Math.sin(u * 15) * 3) > 0.75
201
+ base = u < 0.5 ? mix([85, 85, 60], [60, 60, 45], (v - 0.62) * 4) : mix([150, 160, 160], [95, 100, 85], (v - 0.62) * 4)
202
+ return ripple ? mix(base, [210, 210, 200], 0.3) : base
203
+ end
204
+ # banks
205
+ return jitter(u < 0.45 ? [110, 95, 55] : [85, 110, 50], u, v, 14) if v >= 0.62
206
+ # summer sky with big cumulus clouds
207
+ sky = mix([75, 115, 180], [150, 175, 205], v * 1.6)
208
+ c = vnoise(u, v, 4) * 0.7 + vnoise(u, v, 11) * 0.3
209
+ return mix([250, 245, 235], [150, 150, 150], (c - 0.5) * 2.5 + v * 0.5) if c > 0.48
210
+ sky
211
+ end
212
+
213
+ # Delacroix, Liberty Leading the People
214
+ def liberty_leading_the_people(u, v)
215
+ # the tricolor flag on its pole, raised high
216
+ wave = 0.015 * Math.sin(v * 25 + u * 10)
217
+ if v > 0.04 + wave && v < 0.25 + wave && u > 0.47 && u < 0.66
218
+ return [35, 55, 150] if u < 0.53
219
+ return [240, 235, 220] if u < 0.59
220
+ return [205, 35, 35]
221
+ end
222
+ return [70, 50, 35] if (u - 0.47 + (v - 0.04) * 0.08).abs < 0.007 && v > 0.03 && v < 0.30
223
+ # Liberty: raised arm, head, golden dress
224
+ return [230, 195, 155] if (u - 0.462 + (v - 0.22) * 0.25).abs < 0.014 && v > 0.21 && v < 0.33 # arm
225
+ return [40, 30, 25] if ellipse(u, v, 0.425, 0.315, 0.03, 0.035) < 1 && u < 0.42 # hair
226
+ return [230, 195, 155] if ellipse(u, v, 0.43, 0.32, 0.026, 0.035) < 1
227
+ return [40, 30, 25] if (v - 0.42 - (0.40 - u) * 1.0).abs < 0.01 && u > 0.31 && u < 0.41 # musket
228
+ if inside?(u, v, [[0.40, 0.35], [0.47, 0.35], [0.52, 0.55], [0.58, 0.80], [0.36, 0.80], [0.39, 0.55]])
229
+ return jitter(mix([240, 220, 160], [175, 150, 90], (v - 0.35) * 1.5 + (u - 0.44) * 2.5), u, v, 10)
230
+ end
231
+ # the man in a top hat at left, the boy with pistols at right
232
+ return [25, 22, 22] if inside?(u, v, [[0.25, 0.32], [0.31, 0.32], [0.31, 0.40], [0.25, 0.40]])
233
+ return [24, 22, 22] if inside?(u, v, [[0.23, 0.40], [0.33, 0.40], [0.33, 0.415], [0.23, 0.415]])
234
+ return [215, 180, 140] if ellipse(u, v, 0.28, 0.44, 0.022, 0.03) < 1
235
+ return [35, 30, 30] if inside?(u, v, [[0.22, 0.47], [0.34, 0.47], [0.37, 0.80], [0.18, 0.80]])
236
+ return [60, 45, 40] if ellipse(u, v, 0.62, 0.38, 0.022, 0.022) < 1 # beret
237
+ return [215, 180, 140] if ellipse(u, v, 0.62, 0.42, 0.02, 0.03) < 1
238
+ return [45, 50, 80] if inside?(u, v, [[0.58, 0.45], [0.66, 0.45], [0.69, 0.78], [0.57, 0.78]])
239
+ return [40, 35, 30] if (v - 0.36 - (u - 0.66) * 1.2).abs < 0.012 && u > 0.65 && u < 0.71 # raised pistol arm
240
+ # the crowd rising toward her in a pyramid
241
+ if inside?(u, v, [[0.0, 0.70], [0.12, 0.55], [0.30, 0.48], [0.45, 0.42], [0.62, 0.50], [0.85, 0.55], [1.0, 0.68], [1.0, 0.85], [0.0, 0.85]])
242
+ return jitter(mix([75, 65, 55], [40, 35, 30], vnoise(u, v, 14)), u, v, 10)
243
+ end
244
+ # the barricade and fallen figures at the bottom
245
+ if v > 0.76 - 0.06 * Math.sin(u * 3.2)
246
+ return jitter([175, 165, 145], u, v, 12) if ellipse(u, v, 0.24, 0.90, 0.12, 0.035) < 1
247
+ return jitter([160, 150, 135], u, v, 12) if ellipse(u, v, 0.76, 0.89, 0.09, 0.03) < 1
248
+ return jitter(mix([95, 75, 50], [50, 40, 30], vnoise(u, v, 10)), u, v, 12)
249
+ end
250
+ # Notre-Dame's towers in the haze
251
+ return [150, 135, 110] if v > 0.38 && (u > 0.80 && u < 0.84 || u > 0.88 && u < 0.92)
252
+ # smoke-filled sky, brightest behind Liberty
253
+ glow = Math.exp(-((u - 0.47) / 0.22)**2 - ((v - 0.35) / 0.35)**2)
254
+ smoke = vnoise(u, v, 5)
255
+ sky = mix([105, 90, 65], [215, 200, 150], smoke * 0.6 + glow * 0.6)
256
+ jitter(sky, u, v, 10, 14)
257
+ end
258
+
259
+ # Géricault, The Raft of the Medusa
260
+ def raft_of_the_medusa(u, v)
261
+ # the man at the apex waving a cloth
262
+ return [215, 190, 160] if ellipse(u, v, 0.82, 0.20, 0.035, 0.03) < 1 && ((u * 30 + v * 20).floor.even?)
263
+ return [210, 150, 110] if ellipse(u, v, 0.80, 0.27, 0.02, 0.03) < 1
264
+ return [190, 140, 100] if (u - 0.80 - (0.28 - v) * 0.3).abs < 0.008 && v > 0.22 && v < 0.30
265
+ # the pyramid of figures rising to the right
266
+ pyramid = [[0.30, 0.84], [0.42, 0.66], [0.55, 0.55], [0.66, 0.44], [0.76, 0.32], [0.82, 0.30], [0.86, 0.84]]
267
+ if inside?(u, v, pyramid)
268
+ f = vnoise(u, v, 18)
269
+ return f > 0.55 ? mix([200, 150, 100], [150, 100, 65], v) : mix([90, 60, 40], [50, 35, 25], v)
270
+ end
271
+ # the sail and mast at left
272
+ return [50, 40, 30] if (u - 0.30).abs < 0.007 && v > 0.12 && v < 0.84
273
+ return [55, 45, 35] if (v - 0.12 - (u - 0.30) * 1.4).abs < 0.005 && u > 0.30 && u < 0.55 # rope
274
+ return [55, 45, 35] if (v - 0.12 - (0.30 - u) * 2.2).abs < 0.005 && u < 0.30 && u > 0.12 # rope
275
+ if inside?(u, v, [[0.31, 0.16], [0.44, 0.24], [0.42, 0.62], [0.31, 0.64]])
276
+ return jitter(mix([175, 150, 110], [120, 100, 75], (u - 0.31) * 6), u, v, 10)
277
+ end
278
+ # the tilting raft
279
+ raft_v = 0.86 - (u - 0.2) * 0.08
280
+ if v > raft_v && v < raft_v + 0.06 && u > 0.12 && u < 0.92
281
+ return [70, 50, 30]
282
+ end
283
+ # stormy sea
284
+ sea = 0.62 - 0.12 * Math.exp(-((u - 0.10) / 0.12)**2)
285
+ if v > sea
286
+ wave = Math.sin(u * 25 + v * 40 + Math.sin(u * 6) * 3)
287
+ return mix([22, 30, 28], [65, 75, 60], wave * 0.3 + 0.4 - (v - sea))
288
+ end
289
+ # dark sky with a light patch at the horizon on the right
290
+ light = Math.exp(-((u - 0.85) / 0.18)**2 - ((v - 0.55) / 0.15)**2)
291
+ sky = mix([40, 40, 35], [85, 80, 60], vnoise(u, v, 5))
292
+ sky = mix(sky, [235, 210, 140], light)
293
+ jitter(sky, u, v, 8, 12)
294
+ end
295
+
296
+ # Goya, The Third of May 1808
297
+ def third_of_may(u, v)
298
+ lamp = Math.exp(-((u - 0.45) / 0.25)**2 - ((v - 0.75) / 0.30)**2)
299
+ # the man with raised arms, white shirt and yellow trousers
300
+ return [240, 235, 220] if ellipse(u, v, 0.34, 0.50, 0.045, 0.08) < 1
301
+ return [240, 235, 220] if (u - 0.30 + (0.45 - v) * 0.35).abs < 0.012 && v > 0.32 && v < 0.46 # left arm
302
+ return [240, 235, 220] if (u - 0.38 - (0.45 - v) * 0.35).abs < 0.012 && v > 0.32 && v < 0.46 # right arm
303
+ return [80, 60, 40] if ellipse(u, v, 0.34, 0.415, 0.022, 0.03) < 1 # head
304
+ return [225, 190, 60] if ellipse(u, v, 0.34, 0.63, 0.05, 0.07) < 1 # trousers, kneeling
305
+ # the lantern on the ground
306
+ return [255, 245, 200] if inside?(u, v, [[0.46, 0.74], [0.56, 0.74], [0.57, 0.86], [0.45, 0.86]])
307
+ return [60, 45, 30] if inside?(u, v, [[0.44, 0.86], [0.58, 0.86], [0.58, 0.88], [0.44, 0.88]])
308
+ # other victims huddled around him (dark)
309
+ return [60, 45, 35] if ellipse(u, v, 0.22, 0.58, 0.06, 0.13) < 1
310
+ return [70, 55, 40] if ellipse(u, v, 0.43, 0.60, 0.04, 0.12) < 1
311
+ return [55, 45, 35] if ellipse(u, v, 0.20, 0.86, 0.12, 0.04) < 1
312
+ # the firing squad: a dark row seen from behind, rifles aimed left
313
+ return [40, 35, 30] if v > 0.54 && v < 0.565 && u > 0.47 && u < 0.62 && ((u * 60).floor % 3 != 0)
314
+ [0.62, 0.70, 0.78, 0.86, 0.94].each do |sx|
315
+ return [25, 25, 25] if ellipse(u, v, sx, 0.41, 0.03, 0.035) < 1 # shako
316
+ if ellipse(u, v, sx + 0.01, 0.62, 0.055, 0.20) < 1
317
+ return mix([90, 80, 60], [45, 40, 32], (u - sx + 0.05) * 8)
318
+ end
319
+ end
320
+ return [45, 40, 32] if v > 0.55 && v < 0.85 && u > 0.6
321
+ # lit ground
322
+ ground = 0.70 - 0.25 * smooth(0.6, 0.0, u) * 0 # flat
323
+ if v > ground
324
+ return mix([60, 45, 30], [210, 175, 110], lamp)
325
+ end
326
+ # the dark hill at left, the town and the night sky
327
+ hill = 0.20 + u * 0.8
328
+ return mix([40, 35, 25], [110, 90, 60], lamp * 0.8) if v > hill
329
+ return [55, 50, 50] if v > 0.30 && (u > 0.66 && u < 0.72 || u > 0.74 && u < 0.85 && v > 0.36) # town buildings
330
+ return [50, 45, 45] if (u - 0.69).abs < 0.012 && v > 0.22 # church tower
331
+ mix([15, 15, 20], [35, 35, 40], v)
332
+ end
333
+
334
+ # Millet, The Gleaners
335
+ def the_gleaners(u, v)
336
+ # three peasant women in the foreground: two bent low, one half-rising
337
+ women = [
338
+ [0.22, 0.68, [55, 85, 150], [150, 125, 95], [80, 85, 105], false],
339
+ [0.48, 0.70, [195, 45, 40], [95, 105, 130], [140, 105, 70], false],
340
+ [0.75, 0.60, [105, 65, 40], [120, 105, 90], [100, 85, 70], true],
341
+ ]
342
+ women.each do |cx, cy, scarf, top, skirt, upright|
343
+ if upright
344
+ return scarf if ellipse(u, v, cx - 0.04, cy - 0.17, 0.03, 0.04) < 1
345
+ return top if ellipse(u, v, cx, cy - 0.06, 0.05, 0.10) < 1
346
+ return [170, 130, 95] if (u - (cx - 0.05 + (v - 0.55) * 0.1)).abs < 0.01 && v > 0.55 && v < 0.76
347
+ return skirt if inside?(u, v, [[cx - 0.05, cy - 0.02], [cx + 0.05, cy - 0.02], [cx + 0.07, 0.90], [cx - 0.05, 0.90]])
348
+ else
349
+ # the back slopes down toward the head near the ground
350
+ x = u - cx
351
+ y = v - cy
352
+ a = -0.3
353
+ lx = x * Math.cos(a) + y * Math.sin(a)
354
+ ly = -x * Math.sin(a) + y * Math.cos(a)
355
+ return scarf if ellipse(u, v, cx - 0.14, cy + 0.07, 0.035, 0.05) < 1
356
+ return top if (lx / 0.12)**2 + (ly / 0.075)**2 < 1
357
+ return skirt if inside?(u, v, [[cx + 0.0, cy - 0.02], [cx + 0.10, cy + 0.02], [cx + 0.11, 0.93], [cx - 0.01, 0.93]])
358
+ return [170, 130, 95] if (u - (cx - 0.10)).abs < 0.014 && v > cy + 0.05 && v < 0.92 # arm to the stubble
359
+ end
360
+ end
361
+ # distant haystacks and the harvest
362
+ stack = lambda do |sx, w, h|
363
+ ellipse(u, v, sx, 0.46, w, h) < 1 && v < 0.46
364
+ end
365
+ return mix([200, 165, 100], [150, 115, 65], (u - 0.80) * 8) if stack.call(0.86, 0.06, 0.17)
366
+ return mix([200, 165, 100], [150, 115, 65], (u - 0.66) * 10) if stack.call(0.70, 0.04, 0.11)
367
+ return [175, 140, 85] if stack.call(0.58, 0.025, 0.06)
368
+ return [110, 85, 60] if v > 0.41 && v < 0.46 && noise((u * 70).floor, 1) > 0.85 # tiny workers
369
+ if v > 0.38
370
+ base = mix([225, 205, 145], [180, 145, 90], smooth(0.38, 0.52, v))
371
+ base = mix(base, [150, 115, 75], smooth(0.5, 0.95, v))
372
+ stub = Math.sin(u * 160 + v * 40) > 0.6 && v > 0.55 ? 0.85 : 1.0
373
+ return Color.scale(jitter(base, u, v, 10), stub)
374
+ end
375
+ # pale hazy sky
376
+ jitter(mix([215, 215, 200], [235, 225, 195], v * 3), u, v, 6, 8)
377
+ end
378
+
379
+ # Millet, The Angelus
380
+ def the_angelus(u, v)
381
+ horizon = 0.52
382
+ # the man, head bowed, hat held at his chest
383
+ return [50, 40, 32] if ellipse(u, v, 0.31, 0.27, 0.032, 0.04) < 1
384
+ return [65, 55, 50] if inside?(u, v, [[0.26, 0.29], [0.35, 0.30], [0.38, 0.60], [0.24, 0.60]])
385
+ return [40, 32, 25] if ellipse(u, v, 0.34, 0.40, 0.03, 0.035) < 1 # hat in hands
386
+ return [55, 42, 35] if inside?(u, v, [[0.25, 0.60], [0.37, 0.60], [0.36, 0.86], [0.26, 0.86]])
387
+ # the woman, hands clasped, head bowed under a pale cap
388
+ return [175, 165, 145] if ellipse(u, v, 0.645, 0.30, 0.03, 0.03) < 1
389
+ return [95, 70, 60] if inside?(u, v, [[0.61, 0.32], [0.69, 0.32], [0.71, 0.54], [0.60, 0.54]])
390
+ return [110, 60, 45] if inside?(u, v, [[0.60, 0.54], [0.71, 0.54], [0.75, 0.86], [0.57, 0.86]])
391
+ # pitchfork stuck in the ground at left
392
+ return [55, 42, 30] if (u - 0.18 + (v - 0.5) * 0.06).abs < 0.007 && v > 0.36 && v < 0.86
393
+ # basket between them, wheelbarrow with sacks at right
394
+ return [115, 85, 50] if ellipse(u, v, 0.47, 0.84, 0.04, 0.03) < 1
395
+ return [140, 120, 85] if ellipse(u, v, 0.87, 0.63, 0.06, 0.045) < 1
396
+ return [70, 55, 40] if (v - 0.67).abs < 0.01 && u > 0.78 && u < 0.94
397
+ # church steeple on the horizon
398
+ return [85, 75, 80] if (u - 0.84).abs < 0.008 && v > 0.44 && v < horizon || (u - 0.84).abs < 0.03 && v > 0.49 && v < horizon
399
+ if v > horizon
400
+ furrow = Math.sin(v * 200 / (v - 0.4)) > 0.5 ? 0.85 : 1.0
401
+ base = mix([125, 105, 70], [60, 48, 32], (v - horizon) * 2.2)
402
+ return Color.scale(jitter(base, u, v, 10), furrow)
403
+ end
404
+ # warm evening sky, glowing at the horizon
405
+ sky = ramp(v / horizon, [125, 125, 115], [195, 175, 135], [245, 210, 140])
406
+ jitter(sky, u, v, 6, 8)
407
+ end
408
+
409
+ # Whistler, Arrangement in Grey and Black No. 1
410
+ def whistlers_mother(u, v)
411
+ # the mother, seated in profile facing left
412
+ return [235, 235, 230] if ellipse(u, v, 0.61, 0.36, 0.045, 0.05) < 1 && v < 0.38 || ellipse(u, v, 0.63, 0.40, 0.035, 0.05) < 1 && u > 0.62 # white cap
413
+ return [225, 200, 180] if ellipse(u, v, 0.59, 0.41, 0.03, 0.04) < 1 # face
414
+ return [230, 230, 225] if ellipse(u, v, 0.53, 0.66, 0.035, 0.025) < 1 # handkerchief in the lap
415
+ body = inside?(u, v, [[0.57, 0.44], [0.66, 0.44], [0.72, 0.60], [0.86, 0.92], [0.44, 0.92], [0.48, 0.70], [0.56, 0.60]])
416
+ return [25, 25, 28] if body
417
+ return [40, 35, 30] if inside?(u, v, [[0.66, 0.50], [0.80, 0.48], [0.82, 0.70], [0.72, 0.70]]) # chair back
418
+ return [60, 50, 40] if inside?(u, v, [[0.38, 0.88], [0.50, 0.88], [0.50, 0.94], [0.38, 0.94]]) # footstool
419
+ # the curtain at left, dark with a pale pattern
420
+ if u < 0.22 + 0.01 * Math.sin(v * 20)
421
+ return [175, 170, 155] if noise((u * 60).floor, (v * 45).floor) > 0.82
422
+ return Math.sin(u * 80) > 0.6 ? [45, 42, 40] : [30, 28, 27]
423
+ end
424
+ # the framed picture on the wall
425
+ if u > 0.28 && u < 0.50 && v > 0.16 && v < 0.40
426
+ return [20, 20, 20] if u < 0.30 || u > 0.48 || v < 0.18 || v > 0.38
427
+ return [225, 222, 210] if u < 0.32 || u > 0.46 || v < 0.21 || v > 0.35
428
+ return mix([110, 110, 105], [170, 168, 160], vnoise(u, v, 30))
429
+ end
430
+ # floor and wall
431
+ return jitter([90, 85, 75], u, v, 8) if v > 0.88
432
+ return [60, 58, 55] if (v - 0.76).abs < 0.008
433
+ jitter([150, 148, 140], u, v, 5, 8)
434
+ end
435
+
436
+ # Aivazovsky, The Ninth Wave
437
+ def ninth_wave(u, v)
438
+ sun_u = 0.55
439
+ sun_v = 0.38
440
+ glow = Math.exp(-(dist(u, v, sun_u, sun_v, 1.4) / 0.25)**2)
441
+ # survivors on the broken mast, lower left
442
+ mast_v = 0.80 - (u - 0.10) * 0.25
443
+ if u > 0.08 && u < 0.45 && (v - mast_v).abs < 0.018
444
+ return [45, 30, 20]
445
+ end
446
+ [0.16, 0.23, 0.31, 0.38].each do |fx|
447
+ mv = 0.80 - (fx - 0.10) * 0.25
448
+ return [60, 40, 35] if ellipse(u, v, fx, mv - 0.04, 0.018, 0.04) < 1
449
+ return [120, 50, 40] if ellipse(u, v, fx, mv - 0.085, 0.012, 0.015) < 1
450
+ end
451
+ # the great wave rising at left, lit through by the sun
452
+ crest = 0.55 - 0.25 * Math.exp(-((u - 0.25) / 0.17)**2)
453
+ sea = 0.58 + 0.03 * Math.sin(u * 18)
454
+ wave_top = [crest, sea].min
455
+ if v > wave_top
456
+ if v < wave_top + 0.035 && Math.sin(u * 60) > -0.3
457
+ return mix([250, 240, 200], [210, 220, 190], (v - wave_top) * 20) # foam crest
458
+ end
459
+ translucent = Math.exp(-((v - wave_top - 0.08) / 0.10)**2) * (0.5 + glow)
460
+ streak = Math.sin(u * 30 + v * 70 + Math.sin(u * 7) * 3)
461
+ base = mix([30, 70, 60], [140, 200, 120], translucent)
462
+ base = mix(base, [20, 40, 40], smooth(0.75, 1.0, v))
463
+ base = mix(base, [235, 230, 190], 0.5) if streak > 0.85
464
+ return jitter(base, u, v, 8)
465
+ end
466
+ # sky: sun glowing through stormy air
467
+ return [255, 245, 180] if dist(u, v, sun_u, sun_v, 1.4) < 0.045
468
+ sky = ramp(v / 0.6, [80, 60, 60], [200, 120, 80], [240, 180, 90])
469
+ sky = mix(sky, [255, 220, 120], glow)
470
+ cl = vnoise(u, v, 5)
471
+ sky = mix(sky, [110, 80, 80], (cl - 0.5) * 1.2) if cl > 0.5 && v < 0.35
472
+ jitter(sky, u, v, 8, 10)
473
+ end
474
+
475
+ # Fragonard, The Swing
476
+ def the_swing(u, v)
477
+ # the woman in a billowing pink dress, the focus of the scene
478
+ dress = ellipse(u, v, 0.58, 0.52, 0.17, 0.10)
479
+ if dress < 1
480
+ frill = Math.sin(u * 50 + v * 40) > 0.6
481
+ return frill ? [255, 200, 205] : mix([235, 120, 140], [250, 160, 175], 1 - dress)
482
+ end
483
+ return [150, 115, 85] if ellipse(u, v, 0.66, 0.35, 0.04, 0.035) < 1 && u > 0.65 # hair
484
+ return [245, 210, 190] if ellipse(u, v, 0.65, 0.36, 0.035, 0.035) < 1 # head
485
+ return [225, 110, 130] if ellipse(u, v, 0.63, 0.42, 0.045, 0.04) < 1 # bodice
486
+ return [245, 215, 195] if (v - 0.52 - (u - 0.41) * 0.5).abs < 0.018 && u > 0.33 && u < 0.42 # leg kicked up-left
487
+ return [250, 130, 150] if ellipse(u, v, 0.27, 0.25, 0.035, 0.022) < 1 # the flying shoe
488
+ # swing ropes running up to the upper left
489
+ return [215, 190, 130] if (u - 0.50 + (0.47 - v) * 0.45).abs < 0.014 && v < 0.47
490
+ return [215, 190, 130] if (u - 0.66 + (0.44 - v) * 0.45).abs < 0.014 && v < 0.44
491
+ # the hidden admirer in the bushes at lower left, reaching up
492
+ return [245, 210, 185] if ellipse(u, v, 0.18, 0.76, 0.035, 0.03) < 1
493
+ return [225, 215, 180] if (v - 0.73 + (u - 0.22) * 1.1).abs < 0.018 && u > 0.21 && u < 0.33 # raised arm
494
+ return [190, 185, 150] if ellipse(u, v, 0.10, 0.82, 0.10, 0.04) < 1
495
+ # pale sky gap at top right
496
+ gap = ellipse(u, v, 0.92, 0.10, 0.18, 0.16)
497
+ return mix([225, 230, 215], [185, 200, 190], gap) if gap < 1 && vnoise(u, v, 12) < 0.7
498
+ # pool of warm light behind her, dark lush trees framing everything
499
+ light = Math.exp(-((u - 0.62) / 0.30)**2 - ((v - 0.48) / 0.28)**2)
500
+ leaf = vnoise(u, v, 16) * 0.6 + vnoise(u, v, 5) * 0.4
501
+ green = mix([15, 30, 15], [80, 105, 45], leaf * leaf)
502
+ return mix(green, [215, 190, 120], light * 1.1 - 0.2 + leaf * 0.2) if light > 0.3
503
+ jitter(mix(green, [120, 130, 70], light * 0.8), u, v, 8)
504
+ end
505
+
506
+ # David, Napoleon Crossing the Alps
507
+ def napoleon_crossing_the_alps(u, v)
508
+ # the billowing gold cloak streaming up and to the right
509
+ cloak = inside?(u, v, [[0.50, 0.32], [0.62, 0.15], [0.80, 0.08], [0.92, 0.18], [0.80, 0.28], [0.70, 0.40], [0.56, 0.48]])
510
+ return mix([240, 170, 50], [180, 90, 30], vnoise(u, v, 12) * 1.2) if cloak
511
+ # the pointing arm
512
+ return [40, 45, 80] if (v - 0.30 + (0.45 - u) * 1.1).abs < 0.018 && u > 0.33 && u < 0.47
513
+ return [230, 200, 170] if ellipse(u, v, 0.32, 0.165, 0.015, 0.015) < 1
514
+ # bicorne hat and face
515
+ return [25, 25, 25] if ellipse(u, v, 0.49, 0.24, 0.06, 0.022) < 1
516
+ return [230, 195, 160] if ellipse(u, v, 0.49, 0.285, 0.025, 0.03) < 1
517
+ # rider's body
518
+ return [45, 50, 90] if inside?(u, v, [[0.45, 0.31], [0.54, 0.31], [0.57, 0.48], [0.45, 0.50]])
519
+ return [210, 200, 170] if inside?(u, v, [[0.47, 0.48], [0.57, 0.48], [0.62, 0.58], [0.52, 0.58]]) # breeches
520
+ return [30, 25, 20] if ellipse(u, v, 0.62, 0.62, 0.025, 0.05) < 1 # boot
521
+ # the rearing white horse on a diagonal
522
+ return [70, 60, 50] if ellipse(u, v, 0.24, 0.40, 0.04, 0.10) < 1 && u < 0.26 # mane
523
+ horse = ellipse(u, v, 0.47, 0.56, 0.20, 0.09) < 1 ||
524
+ inside?(u, v, [[0.18, 0.36], [0.26, 0.30], [0.34, 0.48], [0.30, 0.56], [0.20, 0.44]]) || # neck and head
525
+ ellipse(u, v, 0.15, 0.40, 0.05, 0.04) < 1 ||
526
+ (u - 0.26 - (v - 0.56) * 0.6).abs < 0.02 && v > 0.56 && v < 0.70 || # forelegs raised
527
+ (u - 0.62 - (v - 0.62) * 0.3).abs < 0.025 && v > 0.62 && v < 0.92 ||
528
+ (u - 0.70 - (v - 0.62) * 0.1).abs < 0.025 && v > 0.60 && v < 0.94
529
+ if horse
530
+ return mix([245, 240, 230], [170, 165, 160], (v - 0.35) * 1.5)
531
+ end
532
+ return [80, 65, 55] if ellipse(u, v, 0.74, 0.60, 0.05, 0.10) < 1 && u > 0.66 # tail
533
+ # rocks at the bottom
534
+ return jitter(mix([90, 80, 65], [50, 45, 40], (v - 0.85) * 5), u, v, 12) if v > 0.88 - 0.05 * Math.sin(u * 4)
535
+ # mountains and dark stormy sky
536
+ return mix([110, 105, 100], [70, 70, 75], vnoise(u, v, 8)) if v > 0.70 - 0.25 * u.clamp(0, 1) * (u < 0.5 ? 1 : -1) + 0.0 && v > 0.55
537
+ sky = mix([60, 55, 55], [140, 130, 115], vnoise(u, v, 4) * 0.8 + (1 - v) * 0.2)
538
+ jitter(sky, u, v, 10, 10)
539
+ end
540
+
541
+ # Gainsborough, The Blue Boy
542
+ def the_blue_boy(u, v)
543
+ blue = lambda do
544
+ sheen = Math.sin(u * 50 + v * 20 + vnoise(u, v, 10) * 6)
545
+ mix([40, 80, 140], [160, 200, 230], sheen * 0.5 + 0.5)
546
+ end
547
+ # head and hair
548
+ return [230, 200, 175] if ellipse(u, v, 0.50, 0.18, 0.045, 0.045) < 1
549
+ return [110, 75, 45] if ellipse(u, v, 0.50, 0.17, 0.065, 0.06) < 1
550
+ # lace collar
551
+ return [240, 238, 230] if ellipse(u, v, 0.50, 0.245, 0.07, 0.025) < 1
552
+ # the blue satin suit
553
+ return blue.call if inside?(u, v, [[0.42, 0.25], [0.58, 0.25], [0.63, 0.40], [0.62, 0.62], [0.38, 0.62], [0.37, 0.40]])
554
+ # cape on his left side (viewer's right) and the arm on the hip
555
+ return Color.scale(blue.call, 0.75) if inside?(u, v, [[0.58, 0.25], [0.68, 0.32], [0.70, 0.56], [0.62, 0.55]])
556
+ # the hat in his hand at the viewer's left
557
+ return blue.call if (u - 0.36).abs < 0.025 && v > 0.27 && v < 0.50 # arm
558
+ return [45, 35, 30] if ellipse(u, v, 0.33, 0.55, 0.05, 0.035) < 1
559
+ # legs in blue stockings and shoes
560
+ if v >= 0.62 && v < 0.90
561
+ return blue.call if (u - 0.45).abs < 0.03 || (u - 0.555).abs < 0.03
562
+ end
563
+ return [70, 55, 45] if v >= 0.90 && v < 0.93 && ((u - 0.45).abs < 0.04 || (u - 0.56).abs < 0.04)
564
+ # brownish landscape and cloudy sky
565
+ ground = 0.75 + 0.05 * Math.sin(u * 5)
566
+ return jitter(mix([95, 75, 50], [55, 45, 30], (v - ground) * 3), u, v, 12) if v > ground
567
+ return jitter(mix([60, 60, 45], [100, 90, 65], vnoise(u, v, 10)), u, v, 10) if v > 0.55 + 0.1 * Math.sin(u * 4) || u < 0.2 && v > 0.3
568
+ sky = mix([90, 110, 125], [180, 175, 155], vnoise(u, v, 5) + v * 0.3)
569
+ jitter(sky, u, v, 10, 10)
570
+ end
571
+
572
+ # Millais, Ophelia
573
+ def ophelia(u, v)
574
+ # Ophelia floating on her back, face upward at left, dress spread to the right
575
+ return [240, 220, 205] if ellipse(u, v, 0.33, 0.58, 0.035, 0.035) < 1 # face
576
+ return [130, 70, 40] if ellipse(u, v, 0.30, 0.58, 0.07, 0.05) < 1 # auburn hair in the water
577
+ return [235, 215, 200] if ellipse(u, v, 0.40, 0.51, 0.015, 0.02) < 1 || ellipse(u, v, 0.43, 0.66, 0.015, 0.02) < 1 # open hands
578
+ dress = ellipse(u, v, 0.55, 0.59, 0.17, 0.07)
579
+ if dress < 1
580
+ sheen = Math.sin(u * 60 + v * 30) > 0.3
581
+ return sheen ? [200, 200, 185] : [150, 155, 140]
582
+ end
583
+ # scattered flowers on the water
584
+ [[0.47, 0.46, [200, 40, 40]], [0.62, 0.70, [230, 220, 120]], [0.75, 0.52, [210, 90, 150]], [0.25, 0.67, [220, 230, 240]], [0.82, 0.66, [200, 40, 40]]].each do |fx, fy, col|
585
+ return col if dist(u, v, fx, fy, 1.4) < 0.018
586
+ end
587
+ # the dark stream
588
+ bank_top = 0.40 + 0.05 * Math.sin(u * 7)
589
+ bank_bot = 0.80 - 0.04 * Math.sin(u * 5 + 1)
590
+ if v > bank_top && v < bank_bot
591
+ return mix([25, 40, 30], [55, 75, 55], vnoise(u, v, 12) * 0.8)
592
+ end
593
+ # dense green riverbank vegetation
594
+ leaf = vnoise(u, v, 18) * 0.6 + vnoise(u, v, 6) * 0.4
595
+ g = mix([25, 50, 20], [130, 160, 60], leaf * leaf * 1.5)
596
+ return [200, 60, 60] if v < bank_top && noise((u * 60).floor, (v * 40).floor) > 0.988
597
+ return [225, 225, 205] if noise((u * 60).floor + 7, (v * 40).floor) > 0.985
598
+ g
599
+ end
600
+
601
+ # Waterhouse, The Lady of Shalott
602
+ def the_lady_of_shalott(u, v)
603
+ # the lady in white, sitting upright with red hair
604
+ return [235, 205, 185] if ellipse(u, v, 0.40, 0.37, 0.025, 0.035) < 1
605
+ return [190, 90, 40] if ellipse(u, v, 0.41, 0.40, 0.04, 0.08) < 1 && u > 0.40
606
+ return [190, 90, 40] if ellipse(u, v, 0.40, 0.35, 0.035, 0.035) < 1
607
+ return jitter([235, 235, 225], u, v, 10) if inside?(u, v, [[0.37, 0.40], [0.45, 0.40], [0.50, 0.62], [0.33, 0.62]])
608
+ # candles and lantern at the prow (left)
609
+ [0.14, 0.17, 0.20].each do |cx|
610
+ return [255, 220, 120] if (u - cx).abs < 0.006 && v > 0.53 && v < 0.55
611
+ return [230, 225, 200] if (u - cx).abs < 0.006 && v > 0.55 && v < 0.61
612
+ end
613
+ return [80, 60, 30] if (u - 0.10).abs < 0.006 && v > 0.40 && v < 0.62
614
+ return [240, 180, 70] if ellipse(u, v, 0.10, 0.42, 0.015, 0.02) < 1
615
+ # the wooden boat with a patterned tapestry draped over the side
616
+ if inside?(u, v, [[0.06, 0.60], [0.85, 0.62], [0.80, 0.70], [0.12, 0.70]])
617
+ if u > 0.25 && u < 0.62
618
+ pat = (u * 40).floor.even? ^ (v * 40).floor.even?
619
+ return pat ? [150, 50, 40] : [190, 150, 70]
620
+ end
621
+ return [90, 60, 35]
622
+ end
623
+ return [150, 50, 40] if u > 0.28 && u < 0.58 && v > 0.70 && v < 0.78 && ((u * 40).floor.even? ^ (v * 40).floor.even?) # tapestry trailing in water
624
+ # reeds in the foreground
625
+ if v > 0.62 && (u < 0.10 || u > 0.88)
626
+ return [110, 115, 60] if Math.sin(u * 140) > 0.2
627
+ end
628
+ # dark water
629
+ if v > 0.58
630
+ refl = Math.sin(v * 100 + Math.sin(u * 13) * 2) > 0.7
631
+ return refl ? [70, 85, 85] : [25, 35, 35]
632
+ end
633
+ # far bank and autumn trees
634
+ return mix([70, 80, 40], [100, 110, 50], vnoise(u, v, 20)) if v > 0.48
635
+ leaf = vnoise(u, v, 10)
636
+ return mix([110, 90, 40], [190, 140, 60], leaf) if v > 0.12 + 0.05 * Math.sin(u * 9) || leaf > 0.6
637
+ jitter([175, 185, 180], u, v, 8)
638
+ end
639
+
640
+ # Leutze, Washington Crossing the Delaware
641
+ def washington_crossing_the_delaware(u, v)
642
+ # the flag held up behind Washington, slanting back
643
+ if inside?(u, v, [[0.53, 0.10], [0.68, 0.15], [0.66, 0.38], [0.55, 0.34]])
644
+ return [40, 50, 115] if u < 0.585 && v < 0.21
645
+ return ((v * 32).floor.even? ? [200, 40, 45] : [240, 235, 225])
646
+ end
647
+ return [70, 55, 40] if (u - 0.53 - (v - 0.10) * 0.15).abs < 0.007 && v > 0.08 && v < 0.62
648
+ # Washington standing at the bow
649
+ return [40, 35, 30] if ellipse(u, v, 0.42, 0.175, 0.032, 0.016) < 1 # tricorn hat
650
+ return [225, 195, 165] if ellipse(u, v, 0.42, 0.215, 0.018, 0.032) < 1
651
+ return [75, 70, 85] if inside?(u, v, [[0.39, 0.25], [0.46, 0.25], [0.48, 0.40], [0.46, 0.58], [0.38, 0.58], [0.37, 0.40]])
652
+ return [230, 215, 180] if inside?(u, v, [[0.40, 0.42], [0.45, 0.42], [0.45, 0.58], [0.40, 0.58]]) # breeches
653
+ # the men rowing, crowded into the boat
654
+ [0.18, 0.25, 0.32, 0.51, 0.58, 0.65, 0.72].each_with_index do |mx, i|
655
+ top = 0.42 + (i.even? ? 0.03 : 0.0)
656
+ return [150, 45, 40] if i == 2 && ellipse(u, v, mx, top + 0.07, 0.03, 0.07) < 1
657
+ return [200, 170, 140] if ellipse(u, v, mx, top - 0.01, 0.015, 0.025) < 1
658
+ return [35, 35, 40] if ellipse(u, v, mx, top - 0.035, 0.022, 0.012) < 1
659
+ return [60, 55, 55] if ellipse(u, v, mx, top + 0.09, 0.03, 0.10) < 1
660
+ end
661
+ # oars and poles
662
+ return [90, 70, 45] if (v - 0.60 - (u - 0.12) * 0.6).abs < 0.008 && u > 0.08 && u < 0.24
663
+ return [90, 70, 45] if (v - 0.64 + (u - 0.82) * 0.6).abs < 0.008 && u > 0.76 && u < 0.88
664
+ # the boat
665
+ return [65, 48, 35] if inside?(u, v, [[0.10, 0.58], [0.82, 0.57], [0.78, 0.70], [0.16, 0.70]])
666
+ # ice floes on the river
667
+ if v > 0.54
668
+ f = vnoise(u * 1.5, v * 3, 7)
669
+ return mix([230, 235, 240], [185, 195, 205], (v - 0.54) * 2) if f > 0.6
670
+ return mix([75, 90, 105], [40, 55, 70], (v - 0.54) * 2)
671
+ end
672
+ # far shore with other boats
673
+ return [45, 50, 55] if v > 0.51 - 0.01 * Math.sin(u * 20)
674
+ return [55, 55, 60] if v > 0.47 && (u - 0.88).abs < 0.06 && v > 0.49 - (0.06 - (u - 0.88).abs) * 0.3
675
+ # dawn sky
676
+ sky = ramp(v / 0.52, [80, 90, 110], [150, 150, 150], [250, 225, 160])
677
+ c = vnoise(u, v, 6)
678
+ sky = mix(sky, [60, 65, 80], (c - 0.55) * 1.8) if c > 0.55 && v < 0.35
679
+ jitter(sky, u, v, 6, 10)
680
+ end
681
+ end
682
+
683
+ piece(:wanderer_above_the_sea_of_fog, "Wanderer above the Sea of Fog", "Caspar David Friedrich", "c. 1818", aspect: 0.79) { |u, v| wanderer_above_the_sea_of_fog(u, v) }
684
+ piece(:monk_by_the_sea, "The Monk by the Sea", "Caspar David Friedrich", "c. 1810", aspect: 1.56) { |u, v| monk_by_the_sea(u, v) }
685
+ piece(:sea_of_ice, "The Sea of Ice", "Caspar David Friedrich", "1824", aspect: 1.31) { |u, v| sea_of_ice(u, v) }
686
+ piece(:fighting_temeraire, "The Fighting Temeraire", "J. M. W. Turner", "1839", aspect: 1.33) { |u, v| fighting_temeraire(u, v) }
687
+ piece(:rain_steam_and_speed, "Rain, Steam and Speed", "J. M. W. Turner", "1844", aspect: 1.34) { |u, v| rain_steam_and_speed(u, v) }
688
+ piece(:hay_wain, "The Hay Wain", "John Constable", "1821", aspect: 1.42) { |u, v| hay_wain(u, v) }
689
+ piece(:liberty_leading_the_people, "Liberty Leading the People", "Eugène Delacroix", "1830", aspect: 1.25) { |u, v| liberty_leading_the_people(u, v) }
690
+ piece(:raft_of_the_medusa, "The Raft of the Medusa", "Théodore Géricault", "1819", aspect: 1.46) { |u, v| raft_of_the_medusa(u, v) }
691
+ piece(:third_of_may, "The Third of May 1808", "Francisco Goya", "1814", aspect: 1.30) { |u, v| third_of_may(u, v) }
692
+ piece(:the_gleaners, "The Gleaners", "Jean-François Millet", "1857", aspect: 1.33) { |u, v| the_gleaners(u, v) }
693
+ piece(:the_angelus, "The Angelus", "Jean-François Millet", "1859", aspect: 1.19) { |u, v| the_angelus(u, v) }
694
+ piece(:whistlers_mother, "Whistler's Mother", "James McNeill Whistler", "1871", aspect: 1.13) { |u, v| whistlers_mother(u, v) }
695
+ piece(:ninth_wave, "The Ninth Wave", "Ivan Aivazovsky", "1850", aspect: 1.50) { |u, v| ninth_wave(u, v) }
696
+ piece(:the_swing, "The Swing", "Jean-Honoré Fragonard", "1767", aspect: 0.79) { |u, v| the_swing(u, v) }
697
+ piece(:napoleon_crossing_the_alps, "Napoleon Crossing the Alps", "Jacques-Louis David", "1801", aspect: 0.85) { |u, v| napoleon_crossing_the_alps(u, v) }
698
+ piece(:the_blue_boy, "The Blue Boy", "Thomas Gainsborough", "c. 1770", aspect: 0.68) { |u, v| the_blue_boy(u, v) }
699
+ piece(:ophelia, "Ophelia", "John Everett Millais", "1852", aspect: 1.47) { |u, v| ophelia(u, v) }
700
+ piece(:the_lady_of_shalott, "The Lady of Shalott", "John William Waterhouse", "1888", aspect: 1.31) { |u, v| the_lady_of_shalott(u, v) }
701
+ piece(:washington_crossing_the_delaware, "Washington Crossing the Delaware", "Emanuel Leutze", "1851", aspect: 1.71) { |u, v| washington_crossing_the_delaware(u, v) }
702
+ end
703
+ end