mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-29 00:24:42 +01:00
add mouse wheel to adjust pixel tolerance + per channel detection algorithm setting
This commit is contained in:
@@ -52,7 +52,7 @@ inline int _mm_cvtsi128_si32(__m128i a)
|
||||
|
||||
#endif
|
||||
|
||||
inline __m128i distance_epi8(const __m128i a, __m128i b)
|
||||
inline __m128i distance_epu8(const __m128i a, __m128i b)
|
||||
{
|
||||
return _mm_or_si128(_mm_subs_epu8(a, b),
|
||||
_mm_subs_epu8(b, a));
|
||||
@@ -80,18 +80,25 @@ struct BGRATextureView
|
||||
return pixels[x + width * y];
|
||||
}
|
||||
|
||||
static inline bool PixelsClose(const uint32_t pixel1, const uint32_t pixel2, const uint8_t tolerance)
|
||||
template<bool perChannel>
|
||||
static inline bool PixelsClose(const uint32_t pixel1, const uint32_t pixel2, uint8_t tolerance)
|
||||
{
|
||||
const __m128i rgba1 = _mm_cvtsi32_si128(pixel1);
|
||||
const __m128i rgba2 = _mm_cvtsi32_si128(pixel2);
|
||||
const __m128i distances = distance_epi8(rgba1, rgba2);
|
||||
const __m128i distances = distance_epu8(rgba1, rgba2);
|
||||
|
||||
// Method 1: Test whether each channel distance is not great than tolerance
|
||||
//const __m128i tolerances = _mm_set1_epi8(tolerance);
|
||||
//return _mm_cvtsi128_si32(_mm_cmpgt_epi8(distances, tolerances)) == 0;
|
||||
|
||||
// Method 2: Test whether sum of all channel differences is smaller than tolerance
|
||||
return _mm_cvtsi128_si32(_mm_sad_epu8(distances, _mm_setzero_si128())) <= tolerance;
|
||||
// Method 1: Test whether each channel distance is not greater than tolerance
|
||||
if constexpr (perChannel)
|
||||
{
|
||||
const __m128i tolerances = _mm_set1_epi16(tolerance);
|
||||
const int gtResults = _mm_cvtsi128_si32(_mm_cmpgt_epi16(_mm_cvtepu8_epi16(distances), tolerances));
|
||||
return gtResults == 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Method 2: Test whether sum of all channel differences is smaller than tolerance
|
||||
return _mm_cvtsi128_si32(_mm_sad_epu8(distances, _mm_setzero_si128())) <= tolerance;
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
#include "constants.h"
|
||||
#include "EdgeDetection.h"
|
||||
template<bool continuousCapture, bool IsX, bool Increment>
|
||||
template<bool PerChannel,
|
||||
bool ContinuousCapture,
|
||||
bool IsX,
|
||||
bool Increment>
|
||||
inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, const uint8_t tolerance)
|
||||
{
|
||||
using namespace consts;
|
||||
@@ -11,7 +14,7 @@ inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, co
|
||||
long yOffset = 0;
|
||||
|
||||
// For continuous capture, we'll be a bit off center from the cursor so the cross we draw won't interfere with the measurement.
|
||||
if constexpr (continuousCapture)
|
||||
if constexpr (ContinuousCapture)
|
||||
{
|
||||
if constexpr (IsX)
|
||||
{
|
||||
@@ -63,7 +66,7 @@ inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, co
|
||||
}
|
||||
|
||||
const uint32_t nextPixel = texture.GetPixel(x, y);
|
||||
if (!texture.PixelsClose(startPixel, nextPixel, tolerance))
|
||||
if (!texture.PixelsClose<PerChannel>(startPixel, nextPixel, tolerance))
|
||||
{
|
||||
return IsX ? oldX : oldY;
|
||||
}
|
||||
@@ -72,16 +75,36 @@ inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, co
|
||||
return Increment ? static_cast<long>(IsX ? texture.width : texture.height) - 1 : 0;
|
||||
}
|
||||
|
||||
template<bool continuousCapture>
|
||||
template<bool PerChannel, bool ContinuousCapture>
|
||||
inline RECT DetectEdgesInternal(const BGRATextureView& texture, const POINT centerPoint, const uint8_t tolerance)
|
||||
{
|
||||
return RECT{ .left = FindEdge<continuousCapture, true, false>(texture, centerPoint, tolerance),
|
||||
.top = FindEdge<continuousCapture, false, false>(texture, centerPoint, tolerance),
|
||||
.right = FindEdge<continuousCapture, true, true>(texture, centerPoint, tolerance),
|
||||
.bottom = FindEdge<continuousCapture, false, true>(texture, centerPoint, tolerance) };
|
||||
return RECT{ .left = FindEdge<PerChannel,
|
||||
ContinuousCapture,
|
||||
true,
|
||||
false>(texture, centerPoint, tolerance),
|
||||
.top = FindEdge<PerChannel,
|
||||
ContinuousCapture,
|
||||
false,
|
||||
false>(texture, centerPoint, tolerance),
|
||||
.right = FindEdge<PerChannel,
|
||||
ContinuousCapture,
|
||||
true,
|
||||
true>(texture, centerPoint, tolerance),
|
||||
.bottom = FindEdge<PerChannel,
|
||||
ContinuousCapture,
|
||||
false,
|
||||
true>(texture, centerPoint, tolerance) };
|
||||
}
|
||||
|
||||
RECT DetectEdges(const BGRATextureView& texture, const POINT centerPoint, const uint8_t tolerance, const bool continuousCapture)
|
||||
RECT DetectEdges(const BGRATextureView& texture,
|
||||
const POINT centerPoint,
|
||||
const bool perChannel,
|
||||
const uint8_t tolerance,
|
||||
const bool continuousCapture)
|
||||
{
|
||||
return (continuousCapture ? &DetectEdgesInternal<true> : &DetectEdgesInternal<false>)(texture, centerPoint, tolerance);
|
||||
auto function = perChannel ? &DetectEdgesInternal<true, false> : DetectEdgesInternal<false, false>;
|
||||
if (continuousCapture)
|
||||
function = perChannel ? &DetectEdgesInternal<true, true> : &DetectEdgesInternal<false, true>;
|
||||
|
||||
return function(texture, centerPoint, tolerance);
|
||||
}
|
||||
@@ -2,4 +2,8 @@
|
||||
|
||||
#include "BGRATextureView.h"
|
||||
|
||||
RECT DetectEdges(const BGRATextureView& texture, const POINT centerPoint, const uint8_t tolerance, const bool continuousCapture);
|
||||
RECT DetectEdges(const BGRATextureView& texture,
|
||||
const POINT centerPoint,
|
||||
const bool perChannel,
|
||||
const uint8_t tolerance,
|
||||
const bool continuousCapture);
|
||||
@@ -49,8 +49,8 @@ LRESULT CALLBACK measureToolWndProc(HWND window, UINT message, WPARAM wparam, LP
|
||||
{
|
||||
case WM_CREATE:
|
||||
{
|
||||
auto commonState = GetWindowCreateParam<const CommonState*>(lparam);
|
||||
StoreWindowParam(window, commonState);
|
||||
auto state = GetWindowCreateParam<Serialized<MeasureToolState>*>(lparam);
|
||||
StoreWindowParam(window, state);
|
||||
|
||||
#if !defined(DEBUG_OVERLAY)
|
||||
for (; ShowCursor(false) > 0;)
|
||||
@@ -58,6 +58,8 @@ LRESULT CALLBACK measureToolWndProc(HWND window, UINT message, WPARAM wparam, LP
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case WM_ERASEBKGND:
|
||||
return 1;
|
||||
case WM_CLOSE:
|
||||
DestroyWindow(window);
|
||||
break;
|
||||
@@ -71,15 +73,24 @@ LRESULT CALLBACK measureToolWndProc(HWND window, UINT message, WPARAM wparam, LP
|
||||
PostMessageW(window, WM_CLOSE, {}, {});
|
||||
break;
|
||||
case WM_LBUTTONUP:
|
||||
if (auto commonState = GetWindowParam<const CommonState*>(window))
|
||||
if (auto state = GetWindowParam<Serialized<MeasureToolState>*>(window))
|
||||
{
|
||||
commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
|
||||
SetClipBoardToText(text.buffer);
|
||||
state->Read([](const MeasureToolState& s) { s.commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
|
||||
SetClipBoardToText(text.buffer);
|
||||
}); });
|
||||
}
|
||||
break;
|
||||
case WM_MOUSEWHEEL:
|
||||
if (auto state = GetWindowParam<Serialized<MeasureToolState>*>(window))
|
||||
{
|
||||
const int8_t step = static_cast<short>(HIWORD(wparam)) < 0 ? -15: 15;
|
||||
state->Access([step](MeasureToolState& s) {
|
||||
int wideVal = s.pixelTolerance;
|
||||
wideVal += step;
|
||||
s.pixelTolerance = static_cast<uint8_t>(std::clamp(wideVal, 1, 254));
|
||||
});
|
||||
}
|
||||
break;
|
||||
case WM_ERASEBKGND:
|
||||
return 1;
|
||||
}
|
||||
|
||||
return DefWindowProcW(window, message, wparam, lparam);
|
||||
@@ -89,15 +100,17 @@ LRESULT CALLBACK boundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case WM_CLOSE:
|
||||
DestroyWindow(window);
|
||||
break;
|
||||
case WM_CREATE:
|
||||
{
|
||||
auto toolState = GetWindowCreateParam<BoundsToolState*>(lparam);
|
||||
StoreWindowParam(window, toolState);
|
||||
break;
|
||||
}
|
||||
case WM_ERASEBKGND:
|
||||
return 1;
|
||||
case WM_CLOSE:
|
||||
DestroyWindow(window);
|
||||
break;
|
||||
case WM_KEYUP:
|
||||
if (wparam == VK_ESCAPE)
|
||||
{
|
||||
@@ -107,6 +120,8 @@ LRESULT CALLBACK boundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
|
||||
case WM_LBUTTONDOWN:
|
||||
{
|
||||
auto toolState = GetWindowParam<BoundsToolState*>(window);
|
||||
if (!toolState)
|
||||
break;
|
||||
POINT cursorPos = toolState->commonState->cursorPos;
|
||||
ScreenToClient(window, &cursorPos);
|
||||
|
||||
@@ -118,26 +133,28 @@ LRESULT CALLBACK boundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
|
||||
case WM_USER:
|
||||
{
|
||||
auto toolState = GetWindowParam<BoundsToolState*>(window);
|
||||
if (!toolState)
|
||||
break;
|
||||
toolState->currentRegionStart = std::nullopt;
|
||||
break;
|
||||
}
|
||||
case WM_LBUTTONUP:
|
||||
if (auto toolState = GetWindowParam<BoundsToolState*>(window); toolState->currentRegionStart.has_value())
|
||||
{
|
||||
const auto cursorPos = toolState->commonState->cursorPos;
|
||||
D2D_POINT_2F newRegionEnd = { .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(cursorPos.y) };
|
||||
toolState->currentRegionStart = std::nullopt;
|
||||
{
|
||||
auto toolState = GetWindowParam<BoundsToolState*>(window);
|
||||
if (!toolState)
|
||||
break;
|
||||
const auto cursorPos = toolState->commonState->cursorPos;
|
||||
D2D_POINT_2F newRegionEnd = { .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(cursorPos.y) };
|
||||
toolState->currentRegionStart = std::nullopt;
|
||||
|
||||
toolState->commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
|
||||
SetClipBoardToText(text.buffer);
|
||||
});
|
||||
}
|
||||
toolState->commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
|
||||
SetClipBoardToText(text.buffer);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case WM_RBUTTONUP:
|
||||
PostMessageW(window, WM_CLOSE, {}, {});
|
||||
break;
|
||||
case WM_ERASEBKGND:
|
||||
return 1;
|
||||
}
|
||||
|
||||
return DefWindowProcW(window, message, wparam, lparam);
|
||||
@@ -228,7 +245,7 @@ std::vector<D2D1::ColorF> AppendCommonOverlayUIColors(const D2D1::ColorF lineCol
|
||||
|
||||
void OverlayUIState::RunUILoop()
|
||||
{
|
||||
while (IsWindow(_window))
|
||||
while (IsWindow(_window) && !_commonState.closeOnOtherMonitors)
|
||||
{
|
||||
_d2dState.rt->BeginDraw();
|
||||
_d2dState.rt->Clear(D2D1::ColorF(1.f, 1.f, 1.f, 0.f));
|
||||
@@ -272,13 +289,13 @@ OverlayUIState::OverlayUIState(StateT& toolState,
|
||||
{
|
||||
}
|
||||
|
||||
OverlayUIState::~OverlayUIState() noexcept
|
||||
OverlayUIState::~OverlayUIState()
|
||||
{
|
||||
PostMessageW(_window, WM_CLOSE, {}, {});
|
||||
// Extra cautious to not trigger termination due to noexcept
|
||||
try
|
||||
{
|
||||
_uiThread.join();
|
||||
if (_uiThread.joinable())
|
||||
_uiThread.join();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -289,7 +306,7 @@ OverlayUIState::~OverlayUIState() noexcept
|
||||
template<typename ToolT, typename TickFuncT>
|
||||
inline std::unique_ptr<OverlayUIState> OverlayUIState::CreateInternal(ToolT& toolState,
|
||||
TickFuncT tickFunc,
|
||||
const CommonState& commonState,
|
||||
CommonState& commonState,
|
||||
const wchar_t* toolWindowClassName,
|
||||
void* windowParam,
|
||||
const MonitorInfo& monitor)
|
||||
@@ -306,6 +323,7 @@ inline std::unique_ptr<OverlayUIState> OverlayUIState::CreateInternal(ToolT& too
|
||||
uiCreatedEvent.SetEvent();
|
||||
|
||||
state->RunUILoop();
|
||||
commonState.closeOnOtherMonitors = true;
|
||||
commonState.sessionCompletedCallback();
|
||||
});
|
||||
|
||||
@@ -315,20 +333,19 @@ inline std::unique_ptr<OverlayUIState> OverlayUIState::CreateInternal(ToolT& too
|
||||
}
|
||||
|
||||
std::unique_ptr<OverlayUIState> OverlayUIState::Create(Serialized<MeasureToolState>& toolState,
|
||||
const CommonState& commonState,
|
||||
CommonState& commonState,
|
||||
const MonitorInfo& monitor)
|
||||
{
|
||||
return OverlayUIState::CreateInternal(toolState,
|
||||
DrawMeasureToolTick,
|
||||
commonState,
|
||||
NonLocalizable::MeasureToolOverlayWindowName,
|
||||
// ok to cast away const, since member access is serialized
|
||||
(void*)(&commonState),
|
||||
&toolState,
|
||||
monitor);
|
||||
}
|
||||
|
||||
std::unique_ptr<OverlayUIState> OverlayUIState::Create(BoundsToolState& toolState,
|
||||
const CommonState& commonState,
|
||||
CommonState& commonState,
|
||||
const MonitorInfo& monitor)
|
||||
{
|
||||
return OverlayUIState::CreateInternal(toolState,
|
||||
|
||||
@@ -25,20 +25,20 @@ class OverlayUIState final
|
||||
template<typename ToolT, typename TickFuncT>
|
||||
static std::unique_ptr<OverlayUIState> CreateInternal(ToolT& toolState,
|
||||
TickFuncT tickFunc,
|
||||
const CommonState& commonState,
|
||||
CommonState& commonState,
|
||||
const wchar_t* toolWindowClassName,
|
||||
void* windowParam,
|
||||
const MonitorInfo& monitor);
|
||||
|
||||
public:
|
||||
OverlayUIState(OverlayUIState&&) noexcept = default;
|
||||
~OverlayUIState() noexcept;
|
||||
~OverlayUIState();
|
||||
|
||||
static std::unique_ptr<OverlayUIState> Create(BoundsToolState& toolState,
|
||||
const CommonState& commonState,
|
||||
CommonState& commonState,
|
||||
const MonitorInfo& monitor);
|
||||
static std::unique_ptr<OverlayUIState> Create(Serialized<MeasureToolState>& toolState,
|
||||
const CommonState& commonState,
|
||||
CommonState& commonState,
|
||||
const MonitorInfo& monitor);
|
||||
inline HWND overlayWindowHandle() const
|
||||
{
|
||||
|
||||
@@ -44,7 +44,6 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
|
||||
{
|
||||
_overlayUIStates.clear();
|
||||
_boundsToolState = { .commonState = &_commonState };
|
||||
_measureToolState.Reset();
|
||||
for (auto& thread : _screenCaptureThreads)
|
||||
{
|
||||
if (thread.joinable())
|
||||
@@ -53,12 +52,17 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
|
||||
}
|
||||
}
|
||||
_screenCaptureThreads.clear();
|
||||
_measureToolState.Reset();
|
||||
_measureToolState.Access([&](MeasureToolState& s) {
|
||||
s.commonState = &_commonState;
|
||||
});
|
||||
|
||||
_settings = Settings::LoadFromFile();
|
||||
|
||||
_commonState.lineColor.r = _settings.lineColor[0] / 255.f;
|
||||
_commonState.lineColor.g = _settings.lineColor[1] / 255.f;
|
||||
_commonState.lineColor.b = _settings.lineColor[2] / 255.f;
|
||||
_commonState.closeOnOtherMonitors = false;
|
||||
}
|
||||
|
||||
void Core::StartBoundsTool()
|
||||
@@ -87,6 +91,7 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
|
||||
state.continuousCapture = _settings.continuousCapture;
|
||||
state.drawFeetOnCross = _settings.drawFeetOnCross;
|
||||
state.pixelTolerance = _settings.pixelTolerance;
|
||||
state.perColorChannelEdgeDetection = _settings.perColorChannelEdgeDetection;
|
||||
});
|
||||
|
||||
for (const auto& monitorInfo : MonitorInfo::GetMonitors(true))
|
||||
|
||||
@@ -314,7 +314,6 @@ void D3DCaptureState::StopCapture()
|
||||
void UpdateCaptureState(const CommonState& commonState,
|
||||
Serialized<MeasureToolState>& state,
|
||||
HWND targetWindow,
|
||||
const uint8_t pixelTolerance,
|
||||
const OwnedTextureView& textureView,
|
||||
const bool continuousCapture)
|
||||
{
|
||||
@@ -322,13 +321,25 @@ void UpdateCaptureState(const CommonState& commonState,
|
||||
ScreenToClient(targetWindow, &cursorPos);
|
||||
const bool cursorInLeftScreenHalf = cursorPos.x < textureView.view.width / 2;
|
||||
const bool cursorInTopScreenHalf = cursorPos.y < textureView.view.height / 2;
|
||||
uint8_t pixelTolerance = {};
|
||||
bool perColorChannelEdgeDetection = {};
|
||||
state.Access([&](MeasureToolState& state) {
|
||||
state.cursorInLeftScreenHalf = cursorInLeftScreenHalf;
|
||||
state.cursorInTopScreenHalf = cursorInTopScreenHalf;
|
||||
pixelTolerance = state.pixelTolerance;
|
||||
perColorChannelEdgeDetection = state.perColorChannelEdgeDetection;
|
||||
});
|
||||
|
||||
// Every one of 4 edges is a coordinate of the last similar pixel in a row
|
||||
// Example: given a 5x5 green square on a blue background with its top-left pixel
|
||||
// at 20x100, bounds should be [20,100]-[24,104]. We don't include [25,105] or
|
||||
// [19,99], since those pixels are blue. Thus, square dims are equal to
|
||||
// [24-20+1,104-100+1]=[5,5].
|
||||
const RECT bounds = DetectEdges(textureView.view, cursorPos, pixelTolerance, continuousCapture);
|
||||
const RECT bounds = DetectEdges(textureView.view,
|
||||
cursorPos,
|
||||
perColorChannelEdgeDetection,
|
||||
pixelTolerance,
|
||||
continuousCapture);
|
||||
|
||||
#if defined(DEBUG_EDGES)
|
||||
char buffer[256];
|
||||
@@ -344,8 +355,6 @@ void UpdateCaptureState(const CommonState& commonState,
|
||||
#endif
|
||||
state.Access([&](MeasureToolState& state) {
|
||||
state.measuredEdges = bounds;
|
||||
state.cursorInLeftScreenHalf = cursorInLeftScreenHalf;
|
||||
state.cursorInTopScreenHalf = cursorInTopScreenHalf;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -366,10 +375,8 @@ std::thread StartCapturingThread(const CommonState& commonState,
|
||||
winrt::guid_of<winrt::GraphicsCaptureItem>(),
|
||||
winrt::put_abi(item)));
|
||||
|
||||
uint8_t pixelTolerance = {};
|
||||
bool continuousCapture = {};
|
||||
state.Access([&](MeasureToolState& state) {
|
||||
pixelTolerance = state.pixelTolerance;
|
||||
state.Read([&](const MeasureToolState& state) {
|
||||
continuousCapture = state.continuousCapture;
|
||||
});
|
||||
|
||||
@@ -380,8 +387,8 @@ std::thread StartCapturingThread(const CommonState& commonState,
|
||||
!continuousCapture);
|
||||
if (continuousCapture)
|
||||
{
|
||||
captureState->StartCapture([&, targetWindow, pixelTolerance](OwnedTextureView textureView) {
|
||||
UpdateCaptureState(commonState, state, targetWindow, pixelTolerance, textureView, continuousCapture);
|
||||
captureState->StartCapture([&, targetWindow](OwnedTextureView textureView) {
|
||||
UpdateCaptureState(commonState, state, targetWindow, textureView, continuousCapture);
|
||||
});
|
||||
|
||||
while (IsWindow(targetWindow))
|
||||
@@ -398,7 +405,7 @@ std::thread StartCapturingThread(const CommonState& commonState,
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
if (monitorArea.inside(commonState.cursorPos))
|
||||
{
|
||||
UpdateCaptureState(commonState, state, targetWindow, pixelTolerance, textureView, continuousCapture);
|
||||
UpdateCaptureState(commonState, state, targetWindow, textureView, continuousCapture);
|
||||
}
|
||||
const auto frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - now);
|
||||
if (frameTime < consts::TARGET_FRAME_DURATION)
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace
|
||||
const wchar_t JSON_KEY_CONTINUOUS_CAPTURE[] = L"ContinuousCapture";
|
||||
const wchar_t JSON_KEY_DRAW_FEET_ON_CROSS[] = L"DrawFeetOnCross";
|
||||
const wchar_t JSON_KEY_PIXEL_TOLERANCE[] = L"PixelTolerance";
|
||||
const wchar_t JSON_KEY_PER_COLOR_CHANNEL_EDGE_DETECTION[] = L"PerColorChannelEdgeDetection";
|
||||
const wchar_t JSON_KEY_MEASURE_CROSS_COLOR[] = L"MeasureCrossColor";
|
||||
}
|
||||
|
||||
@@ -56,6 +57,14 @@ Settings Settings::LoadFromFile()
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
result.perColorChannelEdgeDetection = props.GetNamedObject(JSON_KEY_PER_COLOR_CHANNEL_EDGE_DETECTION).GetNamedBoolean(JSON_KEY_VALUE);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ struct Settings
|
||||
uint8_t pixelTolerance = 5;
|
||||
bool continuousCapture = false;
|
||||
bool drawFeetOnCross = true;
|
||||
bool perColorChannelEdgeDetection = false;
|
||||
std::array<uint8_t, 3> lineColor = {255, 69, 0};
|
||||
|
||||
static Settings LoadFromFile();
|
||||
|
||||
@@ -26,6 +26,7 @@ struct CommonState
|
||||
|
||||
mutable Serialized<OverlayBoxText> overlayBoxText;
|
||||
POINT cursorPos = {}; // updated atomically
|
||||
std::atomic_bool closeOnOtherMonitors = false;
|
||||
};
|
||||
|
||||
struct BoundsToolState
|
||||
@@ -48,5 +49,7 @@ struct MeasureToolState
|
||||
RECT measuredEdges = {};
|
||||
bool cursorInLeftScreenHalf = false;
|
||||
bool cursorInTopScreenHalf = false;
|
||||
bool perColorChannelEdgeDetection = false;
|
||||
Mode mode = Mode::Cross;
|
||||
CommonState* commonState = nullptr; // required for WndProc
|
||||
};
|
||||
@@ -17,6 +17,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
PixelTolerance = new IntProperty(5);
|
||||
ContinuousCapture = false;
|
||||
DrawFeetOnCross = true;
|
||||
PerColorChannelEdgeDetection = false;
|
||||
MeasureCrossColor = new StringProperty("#FF4500");
|
||||
}
|
||||
|
||||
@@ -28,6 +29,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool DrawFeetOnCross { get; set; }
|
||||
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool PerColorChannelEdgeDetection { get; set; }
|
||||
|
||||
public IntProperty PixelTolerance { get; set; }
|
||||
|
||||
public StringProperty MeasureCrossColor { get; set; }
|
||||
|
||||
@@ -108,6 +108,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool PerColorChannelEdgeDetection
|
||||
{
|
||||
get
|
||||
{
|
||||
return Settings.Properties.PerColorChannelEdgeDetection;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (Settings.Properties.PerColorChannelEdgeDetection != value)
|
||||
{
|
||||
Settings.Properties.PerColorChannelEdgeDetection = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int PixelTolerance
|
||||
{
|
||||
get
|
||||
|
||||
@@ -134,6 +134,9 @@ Inspired by Roolr.</value>
|
||||
<value>Screen Ruler</value>
|
||||
<comment>"Screen Ruler" is the name of the utility</comment>
|
||||
</data>
|
||||
<data name="MeasureTool_ActivationSettings.Header" xml:space="preserve">
|
||||
<value>Activation</value>
|
||||
</data>
|
||||
<data name="MeasureTool_Settings.Header" xml:space="preserve">
|
||||
<value>Behavior</value>
|
||||
<comment>"Screen Ruler" is the name of the utility</comment>
|
||||
@@ -157,6 +160,12 @@ Inspired by Roolr.</value>
|
||||
<data name="MeasureTool_ContinuousCapture.Description" xml:space="preserve">
|
||||
<value>For measuring animated graphics. Measurement is less precise.</value>
|
||||
</data>
|
||||
<data name="MeasureTool_PerColorChannelEdgeDetection.Header" xml:space="preserve">
|
||||
<value>Per color channel edge detection</value>
|
||||
</data>
|
||||
<data name="MeasureTool_PerColorChannelEdgeDetection.Description" xml:space="preserve">
|
||||
<value>If enabled, test that all color channels are within a tolerance distance from each other. Otherwise, check that the sum of all color channels differences is smaller than the tolerance.</value>
|
||||
</data>
|
||||
<data name="MeasureTool_DrawFeetOnCross.Header" xml:space="preserve">
|
||||
<value>Draw feet on cross</value>
|
||||
</data>
|
||||
|
||||
@@ -22,14 +22,29 @@
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
|
||||
<controls:SettingsGroup x:Uid="MeasureTool_Settings" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}">
|
||||
|
||||
<controls:SettingsGroup x:Uid="MeasureTool_ActivationSettings" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}">
|
||||
<controls:Setting x:Uid="MeasureTool_ActivationShortcut" Icon="">
|
||||
<controls:Setting.ActionContent>
|
||||
<controls:ShortcutControl HotkeySettings="{x:Bind Path=ViewModel.ActivationShortcut, Mode=TwoWay}"
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"/>
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="MeasureTool_Settings" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}">
|
||||
|
||||
<controls:Setting x:Uid="MeasureTool_ContinuousCapture" Icon="">
|
||||
<controls:Setting.ActionContent>
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.ContinuousCapture, Mode=TwoWay}" x:Uid="MeasureTool_ContinuousCapture_ToggleSwitch" />
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
<controls:Setting x:Uid="MeasureTool_PerColorChannelEdgeDetection" Icon="">
|
||||
<controls:Setting.ActionContent>
|
||||
<ToggleSwitch
|
||||
IsOn="{x:Bind ViewModel.PerColorChannelEdgeDetection, Mode=TwoWay}"
|
||||
x:Uid="MeasureTool_PerColorChannelEdgeDetection_ToggleSwitch" />
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
|
||||
<controls:Setting x:Uid="MeasureTool_PixelTolerance">
|
||||
<controls:Setting.ActionContent>
|
||||
@@ -39,22 +54,16 @@
|
||||
Value="{x:Bind Mode=TwoWay, Path=ViewModel.PixelTolerance}"/>
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
|
||||
<controls:Setting x:Uid="MeasureTool_MeasureCrossColor">
|
||||
<controls:Setting.ActionContent>
|
||||
<controls:ColorPickerButton SelectedColor="{x:Bind Path=ViewModel.CrossColor, Mode=TwoWay}" />
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
|
||||
|
||||
<controls:Setting x:Uid="MeasureTool_DrawFeetOnCross">
|
||||
<controls:Setting.ActionContent>
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.DrawFeetOnCross, Mode=TwoWay}" x:Uid="MeasureTool_DrawFeetOnCross_ToggleSwitch" />
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
|
||||
<controls:Setting x:Uid="MeasureTool_ContinuousCapture" Icon="">
|
||||
<controls:Setting x:Uid="MeasureTool_MeasureCrossColor">
|
||||
<controls:Setting.ActionContent>
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.ContinuousCapture, Mode=TwoWay}" x:Uid="MeasureTool_ContinuousCapture_ToggleSwitch" />
|
||||
<controls:ColorPickerButton SelectedColor="{x:Bind Path=ViewModel.CrossColor, Mode=TwoWay}" />
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user