Great idea there!
So to create a GIF, I think it would be convenient to introduce the total number of frames:
With[{frames = 120}
, With[{
(* time step of the animation: *) Δt = 1/frames
(* ...... *)
To ensure the GIF is periodical, ω should be constrained, so:
(* ...... *)
Module[{cx, cy, Δx, Δy, color},
{
(* θ0: *) RandomReal[{0, Pi / 2}, n],
(* ω: *)(* instead of RandomReal[{0.3, 1}, n]: *)
2 Pi RandomInteger[{1, 5}, n] ,
(* ...... *)
Finally, instead of:
Function[{θ0, ω, expr},
DynamicModule[{θ = θ0},
DynamicWrapper[
Deploy @ Activate @ expr,
θ = Mod[θ + ω * Δt, 2 * Pi]
]]]
a static generator is needed:
Function[{θ0, ω, expr},
Block[{θ = θ0 + k ω Δt,
Dynamic = Identity},
expr // Deploy // Inactive[Function][k, #] & // Activate
]
]
So assemble them together:
frameGen = With[{frames = 120},#]& @ With[
{
(* time step of the animation: *) Δt = 1./frames,
(* number of stars: *) n = 50,
(* maximal size of stars: *) radius = 1.5,
(* color theme: *) baseColorFunc = ColorData @ "StarryNightColors",
(* geometric properties of the water region: *)
waterBase = -2, waterWidth = 5,
(* geometric properties of the final drawing: *)
height = 20, imageHeight = 700, aspRatio = 1 / GoldenRatio
},
With[{width = (height + (-waterBase) + waterWidth) / aspRatio},
Apply[
Function[{θ0, ω, expr},
(* DynamicModule[{θ = θ0},DynamicWrapper[Deploy @ Activate @ expr,θ = Mod[θ + ω * Δt, 2 * Pi]]]] *)
Block[{θ = θ0 + k ω Δt, Dynamic = Identity},
expr // Deploy // Inactive[Function][k, #] & // Activate
]
]
][
Module[{cx, cy, Δx, Δy, color},
{
RandomReal[{0, Pi / 2}, n],
RandomReal[{0.3, 1}, n],
Inactive[Graphics][
{
(* background: *)
Module[
{
h = height + (-waterBase) + waterWidth + 10,
w = width + 5,
m = 10, Δh, cf = baseColorFunc /* (Darker[#, 0.5]&)
},
Δh = h / m;
MapThread[
Function[{y, c1, c2}, {EdgeForm[], Polygon[{{-5, y},{w, y},{w, y + Δh},{-5, y + Δh}},VertexColors -> Map[cf, {c1, c1, c2, c2}]]}],
{
Function[
Rescale[#, {1, m}, {(waterBase + -waterWidth) - 5, height + 5 + -Δh}]
][Range @ m],
Most[Function[0.4 * (1 + -#) ^ 5][Rescale[Range[m + 1]]]],
Rest[Function[0.4 * (1 + -#) ^ 5][Rescale[Range[m + 1]]]]
}
]
],
(* foreground: *)
MapThread[
Function[
{cpos, r, idx, shineShift},
{cx, cy} = cpos;
{Δx, Δy} = cpos + -({width, height} / 2);
(* base color: *)
color = baseColorFunc[
1 + -Norm[{Δx, Δy} / {width, height}, 1]
];
{
(* one star: *)
{
FaceForm @ {Append[(* transparency: *) 0.7][color]},
EdgeForm[],
Polygon[Map[Function[cpos + r * #],{{0, 1},{Cos[Dynamic[θ[[idx]]]], 0},{0, -1},{-Cos[Dynamic[θ[[idx]]]], 0}}]]
},
(* and its reflection: *)
{
RightComposition[
ColorConvert[#, "LAB"]&,
(* adjust luminance according to shineShift (i.e. y-coords): *)
ReplacePart[
1 -> RightComposition[
Function[Cos[2 * #]],
Function[((# + 1) / 2) ^ 0.5],
Function[
Rescale[#, {0, 1},
Plus[
(* mean luminance, lower the brighter: *)
Rescale[shineShift, {-1, 1}, {0.4, 0.7}],
(* luminance variation range, lower the more active: *)
{-1, 1} * Rescale[shineShift, {-1, 1}, {0.3, 0.03}]
]]
]
][Dynamic[θ[[idx]]]]
],
(* transparency: *)
Append[Function[Rescale[#, {-1, 1}, {0.2, 0.6}]][shineShift]]
][color],
(* abstract blur: *)
AbsoluteThickness[Function[Rescale[#, {-1, 1}, {10, 1}]][shineShift]],
Line[
Function[
{
{cx - 1/2 r (#3 (1 - #2 Cos[Dynamic[θ[[idx]]]])), #1},
{cx + 1/2 r (#3 (1 + #2 Sin[Dynamic[θ[[idx]]]])), #1}
}
][
(* y-coords: *)
Plus[
waterBase,
waterWidth * ((Rescale[shineShift, {-1, 1}] ^ 0.5) - 1)
],
(* variance: *)
Rescale[shineShift, {-1, 1}, {1, 0.2}],
(* mean radius: *)
Rescale[shineShift, {-1, 1}, {2, 4}]
]]}}],
{
Join[
ScalingTransform[{3 / 4, 3 / 4}, {width, height} / 2][
RandomPoint[Rectangle[{0, 0}, {width, height}], Ceiling[n / 4]]
],
RandomPoint[Rectangle[{0, 0}, {width, height}], (n + -Ceiling[n / 4]) - 1],
{{width, height} / 2}
],
radius * RandomReal[{1 / height, 1}, n],
Range @ n,
RandomReal[{-1, 1}, n]
}
]},
PlotRange -> {{0, width}, {waterBase + -waterWidth, height}},
PlotRangePadding -> {{2, 2}, {1, 2}},
Background -> None,
ImageSize -> {Automatic, imageHeight}
]}]]]];
Generating frames:
frames = With[{frames = 120}, RightComposition[frameGen, Rasterize] /@ Range[frames]];
Exporting to GIF:
Export["starry_night_over_water.gif", frames, "GIF", "DisplayDurations" -> .1]