Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
6455c26
Initial plan
Copilot Dec 22, 2025
a565086
Add HDR to SDR conversion for screenshots
Copilot Dec 22, 2025
efd3437
Add tests for HDR conversion utilities
Copilot Dec 22, 2025
cd669dc
Address code review feedback - refactor and improve documentation
Copilot Dec 22, 2025
6f51282
Initial plan
Copilot Dec 27, 2025
c66cb19
Add translation feature to GrabFrame with Windows AI
Copilot Dec 27, 2025
5f6576a
Update translation implementation with TextRewriter workaround
Copilot Dec 27, 2025
737f443
Improve translation feature with auto-freeze and parallel translation
Copilot Dec 27, 2025
69eae14
Add UI improvements for translation feature
Copilot Dec 27, 2025
d97c153
Address code review feedback
Copilot Dec 27, 2025
7dd6cc3
Improve translation with controlled concurrency and better documentation
Copilot Dec 27, 2025
2c14bf7
Extract translation prompt to constant for maintainability
Copilot Dec 27, 2025
c123008
Add translation to Local AI menu in Edit Text Window
Copilot Dec 27, 2025
8d440dc
tweak translation button text
TheJoeFin Dec 27, 2025
7a9c7fe
Add error handling to Edit Text Window translation
Copilot Dec 27, 2025
e6314da
Simplify error handling to match existing AI menu pattern
Copilot Dec 27, 2025
2e766c3
Disambiguate MessageBox reference in error handling
TheJoeFin Dec 27, 2025
9845891
Improve translation prompt and clean up TranslateText docs
TheJoeFin Dec 27, 2025
33949bc
Add word-by-word translation with progress and cancel UI
TheJoeFin Dec 28, 2025
dacbd05
Add Windows AI translation option to post-capture actions
TheJoeFin Dec 28, 2025
064de12
Add "Translate to System Language" menu option
TheJoeFin Jan 2, 2026
5562b04
Handle null Lines in WinAiOcrLinesWords constructor
TheJoeFin Jan 2, 2026
85b23f8
Address code review feedback: fix indentation, resource leaks, and co…
Copilot Jan 2, 2026
0b13fad
Address remaining code review feedback on WindowsAiUtilities.cs
Copilot Jan 2, 2026
79a910e
Update FUNDING.yml with new funding platforms
TheJoeFin Jan 9, 2026
2dfcdd0
Hide "post capture translation"
TheJoeFin Jan 10, 2026
5a4d409
Add customizable post-capture actions for Fullscreen Grab
TheJoeFin Jan 11, 2026
39c09f6
Add option to keep post-grab menu open after action
TheJoeFin Jan 11, 2026
b9ee3db
Refactor post-grab action input gesture handling
TheJoeFin Jan 11, 2026
aeea8c4
Initial plan
Copilot Jan 11, 2026
0b19ccd
Address PR review comments: fix hash code, improve string concatenati…
Copilot Jan 11, 2026
fb86ccc
Fix TrimEachLine logic and use Tag-based menu item identification
Copilot Jan 11, 2026
c14fc37
Remove unused import and extract magic strings to constants
Copilot Jan 11, 2026
f899ed3
Use explicit types instead of var to respect editor config style
Copilot Jan 11, 2026
f4c85a4
Adjust UI layout and remove translate action button
TheJoeFin Jan 11, 2026
d0af2e9
Update tests for removal of "Translate to system language"
TheJoeFin Jan 11, 2026
a988063
Update dependencies and improve settings menu UI
TheJoeFin Jan 23, 2026
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
4 changes: 3 additions & 1 deletion .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# These are supported funding model platforms

custom: ['paypal.me/JosephFinney']
github: [TheJoeFin]
buy_me_a_coffee: thejoefin
custom: ["paypal.me/JosephFinney"]
160 changes: 160 additions & 0 deletions Tests/HdrTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
using System.Drawing;
using System.Drawing.Imaging;
using Text_Grab.Utilities;
using Xunit;

namespace Tests;

public class HdrTests
{
[Fact]
public void ConvertHdrToSdr_WithNullBitmap_ReturnsNull()
{
// Arrange
Bitmap? nullBitmap = null;

// Act
Bitmap? result = HdrUtilities.ConvertHdrToSdr(nullBitmap);

// Assert
Assert.Null(result);
}

[Fact]
public void ConvertHdrToSdr_WithValidBitmap_ReturnsNewBitmap()
{
// Arrange
Bitmap testBitmap = new(100, 100, PixelFormat.Format32bppArgb);
using Graphics g = Graphics.FromImage(testBitmap);
// Fill with white color (simulating HDR bright pixels)
g.Clear(Color.White);

// Act
Bitmap result = HdrUtilities.ConvertHdrToSdr(testBitmap);

// Assert
Assert.NotNull(result);
Assert.NotSame(testBitmap, result);
Assert.Equal(testBitmap.Width, result.Width);
Assert.Equal(testBitmap.Height, result.Height);

// Cleanup
testBitmap.Dispose();
result.Dispose();
}

[Fact]
public void ConvertHdrToSdr_WithBrightPixels_ReducesBrightness()
{
// Arrange
Bitmap testBitmap = new(10, 10, PixelFormat.Format32bppArgb);
// Fill with very bright color
using (Graphics g = Graphics.FromImage(testBitmap))
{
g.Clear(Color.FromArgb(255, 255, 255, 255));
}

// Act
Bitmap result = HdrUtilities.ConvertHdrToSdr(testBitmap);

// Assert
// The conversion should tone map bright values
// In this case, pure white (255,255,255) should remain relatively close
// but with tone mapping applied
Color centerPixel = result.GetPixel(5, 5);

// After tone mapping, pixels should still be bright but potentially adjusted
Assert.True(centerPixel.R >= 200, "Red channel should remain bright");
Assert.True(centerPixel.G >= 200, "Green channel should remain bright");
Assert.True(centerPixel.B >= 200, "Blue channel should remain bright");

// Cleanup
testBitmap.Dispose();
result.Dispose();
}

[Fact]
public void ConvertHdrToSdr_WithMixedPixels_ProcessesCorrectly()
{
// Arrange
Bitmap testBitmap = new(10, 10, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(testBitmap))
{
// Fill with different colors to test tone mapping
using Brush darkBrush = new SolidBrush(Color.FromArgb(255, 50, 50, 50));
using Brush brightBrush = new SolidBrush(Color.FromArgb(255, 250, 250, 250));

g.FillRectangle(darkBrush, 0, 0, 5, 10);
g.FillRectangle(brightBrush, 5, 0, 5, 10);
}

// Act
Bitmap result = HdrUtilities.ConvertHdrToSdr(testBitmap);

// Assert
Assert.NotNull(result);
Color darkPixel = result.GetPixel(2, 5);
Color brightPixel = result.GetPixel(7, 5);

// Dark pixels should remain relatively dark
Assert.True(darkPixel.R < 100, "Dark pixel should remain dark");

// Bright pixels should be tone mapped
Assert.True(brightPixel.R > darkPixel.R, "Bright pixel should be brighter than dark pixel");

// Cleanup
testBitmap.Dispose();
result.Dispose();
}

[Fact]
public void ConvertHdrToSdr_PreservesAlphaChannel()
{
// Arrange
Bitmap testBitmap = new(10, 10, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(testBitmap))
{
using Brush semiTransparentBrush = new SolidBrush(Color.FromArgb(128, 255, 255, 255));
g.FillRectangle(semiTransparentBrush, 0, 0, 10, 10);
}

// Act
Bitmap result = HdrUtilities.ConvertHdrToSdr(testBitmap);

// Assert
Color pixel = result.GetPixel(5, 5);
Assert.Equal(128, pixel.A);

// Cleanup
testBitmap.Dispose();
result.Dispose();
}

[Fact]
public void IsHdrEnabledAtPoint_WithValidCoordinates_DoesNotThrow()
{
// Arrange
int x = 100;
int y = 100;

// Act & Assert
// Should not throw exception even if HDR is not available
var exception = Record.Exception(() => HdrUtilities.IsHdrEnabledAtPoint(x, y));
Assert.Null(exception);
}

[Fact]
public void IsHdrEnabledAtPoint_WithNegativeCoordinates_ReturnsFalse()
{
// Arrange
int x = -1;
int y = -1;

// Act
bool result = HdrUtilities.IsHdrEnabledAtPoint(x, y);

// Assert
// Should return false for invalid coordinates
Assert.False(result);
}
}
131 changes: 131 additions & 0 deletions Tests/PostGrabActionManagerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using Text_Grab.Models;
using Text_Grab.Utilities;

namespace Tests;

public class PostGrabActionManagerTests
{
[Fact]
public void GetDefaultPostGrabActions_ReturnsExpectedCount()
{
// Arrange & Act
List<ButtonInfo> actions = PostGrabActionManager.GetDefaultPostGrabActions();

// Assert
Assert.NotNull(actions);
Assert.Equal(5, actions.Count);
}

[Fact]
public void GetDefaultPostGrabActions_ContainsExpectedActions()
{
// Arrange & Act
List<ButtonInfo> actions = PostGrabActionManager.GetDefaultPostGrabActions();

// Assert
Assert.Contains(actions, a => a.ButtonText == "Fix GUIDs");
Assert.Contains(actions, a => a.ButtonText == "Trim each line");
Assert.Contains(actions, a => a.ButtonText == "Remove duplicate lines");
Assert.Contains(actions, a => a.ButtonText == "Web Search");
Assert.Contains(actions, a => a.ButtonText == "Try to insert text");
//Assert.Contains(actions, a => a.ButtonText == "Translate to system language");
}

[Fact]
public void GetDefaultPostGrabActions_AllHaveClickEvents()
{
// Arrange & Act
List<ButtonInfo> actions = PostGrabActionManager.GetDefaultPostGrabActions();

// Assert
Assert.All(actions, action =>
{
Assert.False(string.IsNullOrEmpty(action.ClickEvent));
});
}

[Fact]
public void GetDefaultPostGrabActions_AllHaveSymbols()
{
// Arrange & Act
List<ButtonInfo> actions = PostGrabActionManager.GetDefaultPostGrabActions();

// Assert
Assert.All(actions, action =>
{
Assert.True(action.IsSymbol);
});
}

[Fact]
public void GetDefaultPostGrabActions_AllMarkedForFullscreenGrab()
{
// Arrange & Act
List<ButtonInfo> actions = PostGrabActionManager.GetDefaultPostGrabActions();

// Assert
Assert.All(actions, action =>
{
Assert.True(action.IsRelevantForFullscreenGrab);
Assert.False(action.IsRelevantForEditWindow);
});
}

[Fact]
public async Task ExecutePostGrabAction_CorrectGuid_TransformsText()
{
// Arrange
ButtonInfo action = PostGrabActionManager.GetDefaultPostGrabActions()
.First(a => a.ClickEvent == "CorrectGuid_Click");
string input = "123e4567-e89b-12d3-a456-426614174OOO"; // Has O's instead of 0's

// Act
string result = await PostGrabActionManager.ExecutePostGrabAction(action, input);

// Assert
Assert.Contains("000", result); // Should have corrected O's to 0's
}

[Fact]
public async System.Threading.Tasks.Task ExecutePostGrabAction_RemoveDuplicateLines_RemovesDuplicates()
{
// Arrange
ButtonInfo action = PostGrabActionManager.GetDefaultPostGrabActions()
.First(a => a.ClickEvent == "RemoveDuplicateLines_Click");
string input = $"Line 1{Environment.NewLine}Line 2{Environment.NewLine}Line 1{Environment.NewLine}Line 3";

// Act
string result = await PostGrabActionManager.ExecutePostGrabAction(action, input);

// Assert
string[] lines = result.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
Assert.Equal(3, lines.Length);
Assert.Single(lines, l => l == "Line 1");
}

[Fact]
public void GetCheckState_DefaultOff_ReturnsFalse()
{
// Arrange
ButtonInfo action = new("Test", "Test_Click", Wpf.Ui.Controls.SymbolRegular.Apps24, DefaultCheckState.Off);

// Act
bool result = PostGrabActionManager.GetCheckState(action);

// Assert
Assert.False(result);
}

[Fact]
public void GetCheckState_DefaultOn_ReturnsTrue()
{
// Arrange
ButtonInfo action = new("Test", "Test_Click", Wpf.Ui.Controls.SymbolRegular.Apps24, DefaultCheckState.On);

// Act
bool result = PostGrabActionManager.GetCheckState(action);

// Assert
Assert.True(result);
}
}
6 changes: 3 additions & 3 deletions Tests/Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="NCalcAsync" Version="5.9.0" />
<PackageReference Include="NCalcAsync" Version="5.11.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Xunit.StaFact" Version="3.0.13" />
<PackageReference Include="xunit.v3" Version="3.2.1" />
<PackageReference Include="xunit.v3" Version="3.2.2" />
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
<PackageReference Include="Microsoft.VisualStudio.DiagnosticsHub.BenchmarkDotNetDiagnosers" Version="18.3.36726.2" />
<PackageReference Include="Microsoft.VisualStudio.DiagnosticsHub.BenchmarkDotNetDiagnosers" Version="18.3.36812.1" />
</ItemGroup>

<ItemGroup>
Expand Down
15 changes: 15 additions & 0 deletions Text-Grab/App.config
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,21 @@
<setting name="OverrideAiArchCheck" serializeAs="String">
<value>False</value>
</setting>
<setting name="GrabFrameTranslationEnabled" serializeAs="String">
<value>False</value>
</setting>
<setting name="GrabFrameTranslationLanguage" serializeAs="String">
<value>English</value>
</setting>
<setting name="PostGrabJSON" serializeAs="String">
<value />
</setting>
<setting name="PostGrabCheckStates" serializeAs="String">
<value />
</setting>
<setting name="PostGrabStayOpen" serializeAs="String">
<value>False</value>
</setting>
</Text_Grab.Properties.Settings>
</userSettings>
</configuration>
Loading