Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add burn-in mitigation #53

Merged
merged 7 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions DesktopClock.Tests/PixelShifterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using DesktopClock.Utilities;

namespace DesktopClock.Tests;

public class PixelShifterTests
{
[Theory]
[InlineData(5, 10)] // Evenly divisible.
[InlineData(3, 10)] // Not evenly divisible.
[InlineData(10, 5)] // Amount is larger than total.
public void ShiftX_ShouldNotExceedMaxTotalShift(int shiftAmount, int maxTotalShift)
{
var shifter = new PixelShifter
{
PixelsPerShift = shiftAmount,
MaxPixelOffset = maxTotalShift,
};

double totalShiftX = 0;

// Test 100 times because it's random.
for (var i = 0; i < 100; i++)
{
var shift = shifter.ShiftX();
totalShiftX += shift;

Assert.InRange(Math.Abs(totalShiftX), 0, maxTotalShift);
}
}

[Theory]
[InlineData(5, 10)] // Evenly divisible.
[InlineData(3, 10)] // Not evenly divisible.
[InlineData(10, 5)] // Amount is larger than total.
public void ShiftY_ShouldNotExceedMaxTotalShift(int shiftAmount, int maxTotalShift)
{
var shifter = new PixelShifter
{
PixelsPerShift = shiftAmount,
MaxPixelOffset = maxTotalShift,
};

double totalShiftY = 0;

// Test 100 times because it's random.
for (var i = 0; i < 100; i++)
{
var shift = shifter.ShiftY();
totalShiftY += shift;

Assert.InRange(Math.Abs(totalShiftY), 0, maxTotalShift);
}
}
}
24 changes: 24 additions & 0 deletions DesktopClock/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DesktopClock.Properties;
using DesktopClock.Utilities;
using H.NotifyIcon;
using H.NotifyIcon.EfficiencyMode;
using Humanizer;
Expand All @@ -27,6 +28,7 @@ public partial class MainWindow : Window
private TaskbarIcon _trayIcon;
private TimeZoneInfo _timeZone;
private SoundPlayer _soundPlayer;
private PixelShifter _pixelShifter;

/// <summary>
/// The date and time to countdown to, or <c>null</c> if regular clock is desired.
Expand All @@ -40,6 +42,12 @@ public partial class MainWindow : Window
[ObservableProperty]
private string _currentTimeOrCountdownString;

/// <summary>
/// The amount of margin applied in order to shift the clock's pixels and help prevent burn-in.
/// </summary>
[ObservableProperty]
private Thickness _pixelShift;

public MainWindow()
{
InitializeComponent();
Expand Down Expand Up @@ -202,6 +210,8 @@ private void SystemClockTimer_SecondChanged(object sender, EventArgs e)
{
UpdateTimeString();

TryShiftPixels();

TryPlaySound();
}

Expand Down Expand Up @@ -258,6 +268,20 @@ private void TryPlaySound()
}
}

private void TryShiftPixels()
{
if (!Settings.Default.BurnInMitigation || DateTimeOffset.Now.Second != 0)
return;

_pixelShifter ??= new();

Dispatcher.Invoke(() =>
{
Left += _pixelShifter.ShiftX();
Top += _pixelShifter.ShiftY();
});
}

private void UpdateTimeString()
{
string GetTimeString()
Expand Down
5 changes: 5 additions & 0 deletions DesktopClock/Properties/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ private Settings()
/// </remarks>
public bool RightAligned { get; set; } = false;

/// <summary>
/// Experimental: Shifts the clock periodically in order to reduce screen burn-in.
/// </summary>
public bool BurnInMitigation { get; set; } = false;

/// <summary>
/// Path to a WAV file to be played on a specified interval.
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions DesktopClock/SettingsWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,18 @@
FontSize="10"
Margin="0,0,0,12" />

<CheckBox Content="Right Aligned" IsChecked="{Binding Settings.RightAligned, Mode=TwoWay}" />
<TextBlock Text="Experimental: Keeps the clock window aligned to the right when the size changes."
FontStyle="Italic"
FontSize="10"
Margin="0,0,0,12" />

<CheckBox Content="Burn-in Mitigation" IsChecked="{Binding Settings.BurnInMitigation, Mode=TwoWay}" />
<TextBlock Text="Experimental: Shifts the clock periodically in order to reduce screen burn-in."
FontStyle="Italic"
FontSize="10"
Margin="0,0,0,12" />

<TextBlock Text="WAV File Path:" />
<Grid>
<Grid.ColumnDefinitions>
Expand Down
71 changes: 71 additions & 0 deletions DesktopClock/Utilities/PixelShifter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;

namespace DesktopClock.Utilities;

public class PixelShifter
{
private readonly Random _random = new();
private double _totalShiftX;
private double _totalShiftY;

/// <summary>
/// The number of pixels that will be shifted each time.
/// </summary>
public int PixelsPerShift { get; set; } = 1;

/// <summary>
/// The maximum amount of drift that can occur in each direction.
/// </summary>
public int MaxPixelOffset { get; set; } = 4;

/// <summary>
/// Returns an amount to shift horizontally by while staying within the specified bounds.
/// </summary>
public double ShiftX()
{
double pixelsToMoveBy = GetRandomShift();
pixelsToMoveBy = GetFinalShiftAmount(_totalShiftX, pixelsToMoveBy, MaxPixelOffset);
_totalShiftX += pixelsToMoveBy;
return pixelsToMoveBy;
}

/// <summary>
/// Returns an amount to shift vertically by while staying within the specified bounds.
/// </summary>
public double ShiftY()
{
double pixelsToMoveBy = GetRandomShift();
pixelsToMoveBy = GetFinalShiftAmount(_totalShiftY, pixelsToMoveBy, MaxPixelOffset);
_totalShiftY += pixelsToMoveBy;
return pixelsToMoveBy;
}

/// <summary>
/// Returns a random amount to shift by within the specified amount.
/// </summary>
private int GetRandomShift() => _random.Next(-PixelsPerShift, PixelsPerShift + 1);

/// <summary>
/// Returns a capped amount to shift by.
/// </summary>
/// <param name="current">The current total amount of shift that has occurred.</param>
/// <param name="offset">The proposed amount to shift by this time.</param>
/// <param name="max">The bounds to stay within in respect to the total shift.</param>
private double GetFinalShiftAmount(double current, double offset, double max)
{
var newTotal = current + offset;

if (newTotal > max)
{
return max - current;
}
else if (newTotal < -max)
{
return -max - current;
}
else
{
return offset;
}
}
}