Skip to content

Commit 4c209cd

Browse files
committed
Make the Frosted Glass effect deterministic
- Extracted out some shared code into the RandomSeed class, for computing a random seed for each tile being processed.
1 parent c428b52 commit 4c209cd

File tree

6 files changed

+57
-53
lines changed

6 files changed

+57
-53
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Thanks to the following contributors who worked on this release:
2121

2222
### Changed
2323
- When building Pinta using the Makefile, 'dotnet publish' is now run during the build step rather than the install step.
24-
- Added a "Reseed" button for the Add Noise Effect, which generates a new noise pattern. The noise pattern will otherwise remain the same while other parameters are adjusted.
24+
- Added a "Reseed" button for the random noise used by several effects ("Add Noise" and "Frosted Glass"). Previously, the noise pattern changed every time the effect was computed (including when other parameters were changed).
2525

2626
### Fixed
2727
- Fixed an issue where the Pan tool's cursor could show up as a missing icon ([#2013047](https://bugs.launchpad.net/pinta/+bug/2013047))

Pinta.Core/Effects/RandomSeed.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Numerics;
3+
using System.Runtime.CompilerServices;
24

35
namespace Pinta.Core;
46

@@ -27,4 +29,45 @@ public override readonly bool Equals (object? obj)
2729
public override readonly int GetHashCode () => Value.GetHashCode ();
2830
public static bool operator == (RandomSeed left, RandomSeed right) => left.Equals (right);
2931
public static bool operator != (RandomSeed left, RandomSeed right) => !left.Equals (right);
32+
33+
/// <summary>
34+
/// Produces a new random seed based on <see cref="Value"/> and the specified region.
35+
/// This can be useful for tiled effects.
36+
/// </summary>
37+
public int GetValueForRegion (in RectangleI region)
38+
{
39+
// Note that HashCode.Combine() can't be used because it is random per-process and would
40+
// produce inconsistent results for unit tests.
41+
// This is the same implementation from HashCode.cs, but without the randomization.
42+
const uint Prime2 = 2246822519U;
43+
const uint Prime3 = 3266489917U;
44+
const uint Prime4 = 668265263U;
45+
46+
[MethodImpl (MethodImplOptions.AggressiveInlining)]
47+
static uint MixFinal (uint hash)
48+
{
49+
hash ^= hash >> 15;
50+
hash *= Prime2;
51+
hash ^= hash >> 13;
52+
hash *= Prime3;
53+
hash ^= hash >> 16;
54+
return hash;
55+
}
56+
57+
[MethodImpl (MethodImplOptions.AggressiveInlining)]
58+
static uint QueueRound (uint hash, uint queuedValue)
59+
{
60+
return BitOperations.RotateLeft (hash + queuedValue * Prime3, 17) * Prime4;
61+
}
62+
63+
uint hash = 374761393U;
64+
hash += 12;
65+
66+
hash = QueueRound (hash, (uint) Value);
67+
hash = QueueRound (hash, (uint) region.Left);
68+
hash = QueueRound (hash, (uint) region.Top);
69+
70+
hash = MixFinal (hash);
71+
return (int) hash;
72+
}
3073
}

Pinta.Effects/Effects/AddNoiseEffect.cs

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99

1010
using System;
1111
using System.Collections.Immutable;
12-
using System.Numerics;
13-
using System.Runtime.CompilerServices;
1412
using Cairo;
1513
using Pinta.Core;
1614
using Pinta.Gui.Widgets;
@@ -101,49 +99,11 @@ private static ImmutableArray<int> CreateLookup ()
10199
return result.ToImmutable ();
102100
}
103101

104-
private static int CreateSeedForRegion (int global_seed, int rect_left, int rect_top)
105-
{
106-
// Note that HashCode.Combine() can't be used because it is random per-process and would
107-
// produce inconsistent results for unit tests.
108-
// This is the same implementation from HashCode.cs, but without the randomization.
109-
const uint Prime2 = 2246822519U;
110-
const uint Prime3 = 3266489917U;
111-
const uint Prime4 = 668265263U;
112-
113-
[MethodImpl (MethodImplOptions.AggressiveInlining)]
114-
static uint MixFinal (uint hash)
115-
{
116-
hash ^= hash >> 15;
117-
hash *= Prime2;
118-
hash ^= hash >> 13;
119-
hash *= Prime3;
120-
hash ^= hash >> 16;
121-
return hash;
122-
}
123-
124-
[MethodImpl (MethodImplOptions.AggressiveInlining)]
125-
static uint QueueRound (uint hash, uint queuedValue)
126-
{
127-
return BitOperations.RotateLeft (hash + queuedValue * Prime3, 17) * Prime4;
128-
}
129-
130-
uint hash = 374761393U;
131-
hash += 12;
132-
133-
hash = QueueRound (hash, (uint) global_seed);
134-
hash = QueueRound (hash, (uint) rect_left);
135-
hash = QueueRound (hash, (uint) rect_top);
136-
137-
hash = MixFinal (hash);
138-
return (int) hash;
139-
}
140-
141102
public override void Render (ImageSurface src, ImageSurface dst, ReadOnlySpan<RectangleI> rois)
142103
{
143104
int intensity = Data.Intensity;
144105
int color_saturation = Data.ColorSaturation;
145106
double coverage = 0.01 * Data.Coverage;
146-
int global_seed = Data.Seed.Value;
147107

148108
int dev = intensity * intensity / 4;
149109
int sat = color_saturation * 4096 / 100;
@@ -158,7 +118,7 @@ public override void Render (ImageSurface src, ImageSurface dst, ReadOnlySpan<Re
158118
// Reseed the random number generator for each rectangle being rendered.
159119
// This should produce consistent results regardless of the number of threads
160120
// being used to render the effect, but will change if the effect is tiled differently.
161-
var rand = new Random (CreateSeedForRegion (global_seed, rect.Left, rect.Top));
121+
var rand = new Random (Data.Seed.GetValueForRegion (rect));
162122

163123
int right = rect.Right;
164124
for (int y = rect.Top; y <= rect.Bottom; ++y) {

Pinta.Effects/Effects/FrostedGlassEffect.cs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ public sealed class FrostedGlassEffect : BaseEffect
2828

2929
public FrostedGlassData Data => (FrostedGlassData) EffectData!;
3030

31-
private readonly Random random = new ();
32-
3331
public FrostedGlassEffect ()
3432
{
3533
EffectData = new FrostedGlassData ();
@@ -63,6 +61,8 @@ public override void Render (ImageSurface src, ImageSurface dst, ReadOnlySpan<Re
6361
Span<ColorBgra> dst_data = dst.GetPixelData ();
6462

6563
foreach (var rect in rois) {
64+
var random = new Random (Data.Seed.GetValueForRegion (rect));
65+
6666
for (int y = rect.Top; y <= rect.Bottom; ++y) {
6767

6868
var dst_row = dst_data.Slice (y * settings.src_width, settings.src_width);
@@ -76,12 +76,12 @@ public override void Render (ImageSurface src, ImageSurface dst, ReadOnlySpan<Re
7676
bottom = settings.src_height;
7777

7878
for (int x = rect.Left; x <= rect.Right; ++x)
79-
dst_row[x] = GetFinalPixelColor (settings, src_data, top, bottom, x);
79+
dst_row[x] = GetFinalPixelColor (settings, random, src_data, top, bottom, x);
8080
}
8181
}
8282
}
8383

84-
private ColorBgra GetFinalPixelColor (FrostedGlassSettings settings, ReadOnlySpan<ColorBgra> src_data, int top, int bottom, int x)
84+
private static ColorBgra GetFinalPixelColor (FrostedGlassSettings settings, Random random, ReadOnlySpan<ColorBgra> src_data, int top, int bottom, int x)
8585
{
8686
int intensityChoicesIndex = 0;
8787

@@ -133,11 +133,7 @@ private ColorBgra GetFinalPixelColor (FrostedGlassSettings settings, ReadOnlySpa
133133
}
134134
}
135135

136-
int randNum;
137-
lock (random) {
138-
randNum = random.Next (intensityChoicesIndex);
139-
}
140-
136+
int randNum = random.Next (intensityChoicesIndex);
141137
byte chosenIntensity = intensityChoices[randNum];
142138

143139
return ColorBgra.FromBgra (
@@ -153,5 +149,8 @@ public sealed class FrostedGlassData : EffectData
153149
{
154150
[Caption ("Amount"), MinimumValue (1), MaximumValue (10)]
155151
public int Amount { get; set; } = 1;
152+
153+
[Caption ("Random Noise")]
154+
public RandomSeed Seed { get; set; } = new (0);
156155
}
157156
}
180 KB
Loading

tests/Pinta.Effects.Tests/EffectsTest.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,12 @@ public void Fragment2 ()
102102
}
103103

104104
[Test]
105-
[Ignore ("Produces non-deterministic results because the random seed is not fixed")]
106105
public void FrostedGlass ()
107106
{
108-
// TODO
107+
var effect = new FrostedGlassEffect ();
108+
effect.Data.Amount = 7;
109+
effect.Data.Seed = new (42);
110+
Utilities.TestEffect (effect, "frostedglass1.png");
109111
}
110112

111113
[Test]

0 commit comments

Comments
 (0)