Logo Xingxin on Bug

Boost Productivity Using PowerToys Day 2 - Screen Ruler

December 26, 2024
4 min read

This is the day 2 I am using the PowerToys following the post. ⏮️ Day 1: Shortcut Guide ⏸️ Day 3: Registry Preview ⏭️

📝Description

The Screen Ruler is a measurement tool that you can measure the elements on your screen with pixels / centimeters / millimeters.

📢Name

In the codebase, this utility was initially called “Measure Tool”:

private void ModuleButton_Click(object sender, RoutedEventArgs e)
{
    switch ((ModuleType)selectedModuleBtn.Tag)
    {
        case ModuleType.MeasureTool: // 👈Launch Screen Ruler
        case //...
    }
}

I personally think this is a very smart move! The comparison is straightforward:

Measure ToolScreenRulermany otheroptionsbefore:after✅:
  • Measure Tool:
    • ❌ Broad term
    • ❌ Unclear what it measures
    • ❌ Unclear what the tool is
  • Screen Ruler:
    • ✅ Screen: Provides context
    • ✅ Ruler: Clearly indicates the tool and its purpose - measuring length 📏

By renaming it to “Screen Ruler,” the tool’s functionality becomes immediately clear to users, enhancing usability and user experience.

This reminds me of Uncle Bob’s book Clean Code > Chapter 2 Meaningful Names > Section 2.2 where he suggests we should use intention-revealing names. I think this rule not only applies to programming but also marketing. This is an example of Good Name Gives Good Product.

⌨️Shortcut

+Ctrl+Shift+M

Remark

Hum… A shortcut requires 2 hands 🤣…

🖱️Code

Screen Capture

The utility captures the screen in 2 modes:

  1. live(real-time update)
  2. static(single frame)

There is a background std::thread continuously capturing the screen using the DirectX api:

//src\modules\MeasureTool\MeasureToolCore\ScreenCapturing.cpp
std::thread StartCapturingThread(DxgiAPI* dxgiAPI,
                                 const CommonState& commonState,
                                 Serialized<MeasureToolState>& state,
                                 HWND window,
                                 MonitorInfo monitor)
{
    return SpawnLoggedThread({});
}

Edge Detection

The core idea is to find where a color changes significantly in each direction (left, right, up, down) from a starting point. Here’s how it works:

1️⃣ Starting Point

const uint32_t startPixel = texture.GetPixel(x, y);

Let’s say you click on a red square on a white background and if you click at (3,2) - a red pixel in the middle.

⬜⬜⬜⬜⬜⬜⬜ ⬜⬜🟥🟥🟥⬜⬜ ⬜⬜🟥🟥🟥⬜⬜ ⬜⬜🟥🟥🟥⬜⬜ ⬜⬜⬜⬜⬜⬜⬜

Note

Note that in Computer Graphics , the coordinate starts with 0-index. Also the origin (0,0) is the top left corner.

2️⃣ Find Edge The FindEdge template function searches in one direction at a time:

template<bool PerChannel,
         bool IsX,
         bool Increment>
inline long FindEdge(/*...*/)
  • PerChannel: Whether to check RGB channels separately or together
  • IsX: Search horizontally (true) or vertically (false)
  • Increment: Search forward/right/down (true) or backward/left/up (false)

3️⃣ Search Right Suppose we gonna search right (IsX=true, Increment=true):

⬜⬜⬜⬜⬜⬜⬜ ⬜⬜🟥🟥🟥⬜⬜
⬜⬜🟥❌🟥⬜⬜ Start at (3,2) ⬜⬜🟥🟥🟥⬜⬜ ⬜⬜⬜⬜⬜⬜⬜

⬜⬜⬜⬜⬜⬜⬜ ⬜⬜🟥🟥🟥⬜⬜ ⬜⬜🟥🟥❌⬜⬜ Check: (4,2) - still red ⬜⬜🟥🟥🟥⬜⬜ ⬜⬜⬜⬜⬜⬜⬜

⬜⬜⬜⬜⬜⬜⬜ ⬜⬜🟥🟥🟥⬜⬜ ⬜⬜🟥🟥🟥✖️⬜ Check: (5,2) - white! Stop! ⬜⬜🟥🟥🟥⬜⬜ ⬜⬜⬜⬜⬜⬜⬜

while (true) {
    // Move to next pixel
    if constexpr (Increment) {
        if (++x == maxDim)
            break;
    }
    
    // Check if color changed
    const uint32_t nextPixel = texture.GetPixel(x, y);
    if (!texture.PixelsClose<PerChannel>(startPixel, nextPixel, tolerance)) {
        return oldX;  // Return last position of similar color
    }
}

4️⃣ Complete Detection The DetectEdges function calls FindEdge in all 4 directions:

RECT DetectEdges(/*...*/) {
    return RECT{ 
        .left = FindEdge</*left*/>,
        .top = FindEdge</*up*/>,
        .right = FindEdge</*right*/>,
        .bottom = FindEdge</*down*/>
    };
}

5️⃣Result ⬜⬜⬜⬜⬜⬜⬜ After clicking (3,2): ⬜⬜🟥🟥🟥⬜⬜ left is x=2 (last red pixel) ⬜⬜🟥🟥🟥⬜⬜ right is x=4 (last red pixel) ⬜⬜🟥🟥🟥⬜⬜ top = y=1 (last red pixel) ⬜⬜⬜⬜⬜⬜⬜ bottom = y=3 (last red pixel)