Files
MouseHighlighter/include/DataStructures.h
digouyou 326306d76a Initial commit: MouseHighlighter project
A lightweight Windows mouse highlight overlay tool with halo circle
and click ripple animations, built with C++17 and Win32 API.
2026-05-27 16:46:07 +08:00

289 lines
8.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include <cstdint>
#include <wingdi.h>
#include <array>
#include <cmath>
/**
* @brief DIB (Device Independent Bitmap) 双缓冲管理
*
* 维护 ARGB32 位图,用于 UpdateLayeredWindow 输出
*/
class DIBBuffer {
public:
DIBBuffer() = default;
// 内存 DC & 位图
HDC hdcMem = nullptr;
HBITMAP hbmDIB = nullptr;
uint32_t* pBits = nullptr;
uint32_t width = 0;
uint32_t height = 0;
/**
* @brief 创建或重建 DIB 缓冲
* @param w 宽度 (像素)
* @param h 高度 (像素)
* @param hdcScreen 参考设备上下文,通常来自 GetDC(nullptr)
* @return true 成功false 失败
*/
bool Create(uint32_t w, uint32_t h, HDC hdcScreen) noexcept {
Release();
width = w;
height = h;
// 创建兼容 DC
hdcMem = CreateCompatibleDC(hdcScreen);
if (!hdcMem) {
return false;
}
// 创建 ARGB32 DIB
BITMAPINFO bmi{};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = static_cast<LONG>(w);
bmi.bmiHeader.biHeight = -static_cast<LONG>(h); // 负数: 顶部对齐
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32; // ARGB
bmi.bmiHeader.biCompression = BI_RGB;
hbmDIB = CreateDIBSection(hdcScreen, &bmi, DIB_RGB_COLORS,
reinterpret_cast<void**>(&pBits), nullptr, 0);
if (!hbmDIB || !pBits) {
DeleteDC(hdcMem);
hdcMem = nullptr;
return false;
}
// 选择 DIB 到 DC
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, hbmDIB);
if (hbmOld) {
// 如果旧位图非空,需跟踪以便后续删除 (此处可忽略,因为创建时通常为空)
}
return true;
}
/**
* @brief 释放所有资源
*/
void Release() noexcept {
if (hbmDIB) {
DeleteObject(hbmDIB);
hbmDIB = nullptr;
}
if (hdcMem) {
DeleteDC(hdcMem);
hdcMem = nullptr;
}
pBits = nullptr;
width = height = 0;
}
/**
* @brief 清除矩形区域为透明 (所有像素 alpha = 0)
*/
void ClearRect(const RECT& rc) noexcept {
if (!pBits || width == 0 || height == 0) return;
int left = std::max(0L, rc.left);
int top = std::max(0L, rc.top);
int right = std::min((LONG)width, rc.right);
int bottom = std::min((LONG)height, rc.bottom);
for (int y = top; y < bottom; ++y) {
for (int x = left; x < right; ++x) {
pBits[y * width + x] = 0; // ARGB: alpha=0 (完全透明)
}
}
}
/**
* @brief 获取指针到特定像素
*/
inline uint32_t* GetPixel(int x, int y) noexcept {
if (x < 0 || x >= (int)width || y < 0 || y >= (int)height) {
return nullptr;
}
return &pBits[y * width + x];
}
~DIBBuffer() {
Release();
}
// 禁用拷贝
DIBBuffer(const DIBBuffer&) = delete;
DIBBuffer& operator=(const DIBBuffer&) = delete;
};
/**
* @brief 单个波纹动画状态
*/
struct RippleState {
int32_t centerX = 0;
int32_t centerY = 0;
uint64_t startTime = 0; // QueryPerformanceCounter() 返回值
/**
* @brief 计算当前半径
* @param nowQPC 当前 QueryPerformanceCounter() 返回值
* @param freq QueryPerformanceFrequency() 返回值
* @return 半径 (像素),如果波纹已死亡返回 -1.0f
*/
float GetCurrentRadius(uint64_t nowQPC,
uint64_t freq,
float maxRadius,
uint32_t durationMS,
float radiusExponent = 1.0f) const noexcept {
if (freq == 0) return -1.0f;
uint64_t elapsedMS = (nowQPC - startTime) * 1000 / freq;
if (elapsedMS > durationMS) {
return -1.0f; // 标记已死亡
}
float t = elapsedMS / static_cast<float>(durationMS);
float exp = std::max(0.1f, radiusExponent);
return maxRadius * std::pow(t, exp);
}
/**
* @brief 计算当前 Alpha 值 (0-255)
* 使用立方衰减: alpha = (1 - t)^3
*/
uint8_t GetCurrentAlpha(uint64_t nowQPC,
uint64_t freq,
uint32_t durationMS,
float alphaExponent = 3.0f) const noexcept {
if (freq == 0) return 0;
uint64_t elapsedMS = (nowQPC - startTime) * 1000 / freq;
if (elapsedMS > durationMS) {
return 0;
}
float t = elapsedMS / static_cast<float>(durationMS);
float exp = std::max(0.1f, alphaExponent);
float alpha_norm = std::pow((1.0f - t), exp);
return static_cast<uint8_t>(255.0f * alpha_norm);
}
/**
* @brief 检查波纹是否已鼓励 (可回收)
*/
bool IsAlive(uint64_t nowQPC, uint64_t freq, uint32_t durationMS) const noexcept {
if (freq == 0) return false;
uint64_t elapsedMS = (nowQPC - startTime) * 1000 / freq;
return elapsedMS <= durationMS;
}
};
/**
* @brief 波纹对象池
*/
struct RipplePool {
static constexpr size_t MAX_RIPPLES = 12;
std::array<RippleState, MAX_RIPPLES> ripples{};
uint8_t activeCount = 0; // 当前活跃波纹数 (0 到 MAX_RIPPLES)
/**
* @brief 添加新波纹
* @return true 成功false 池已满
*/
bool AddRipple(int32_t x, int32_t y, uint64_t nowQPC) noexcept {
if (activeCount >= MAX_RIPPLES) {
return false;
}
ripples[activeCount].centerX = x;
ripples[activeCount].centerY = y;
ripples[activeCount].startTime = nowQPC;
activeCount++;
return true;
}
/**
* @brief 清理并压缩死亡波纹,更新 activeCount
*/
void CompactDeadRipples(uint64_t nowQPC, uint64_t freq, uint32_t durationMS) noexcept {
uint8_t writeIdx = 0;
for (uint8_t i = 0; i < activeCount; ++i) {
if (ripples[i].IsAlive(nowQPC, freq, durationMS)) {
ripples[writeIdx++] = ripples[i];
}
}
activeCount = writeIdx;
}
};
/**
* @brief 脏矩形追踪器
*
* 记录"上一帧圆圈的包围盒 + 当前帧圆圈的包围盒"
* 计算联合区域以确定必须重绘的区域
*/
class DirtyRectTracker {
public:
RECT currentDirty{0, 0, 0, 0}; // 当前帧脏区
RECT prevDirty{0, 0, 0, 0}; // 上一帧脏区
uint32_t screenWidth = 1920;
uint32_t screenHeight = 1080;
/**
* @brief 记下一个圆形的包围盒到脏区
*/
void UnionCircle(int x, int y, float radius) noexcept {
int l = std::max(0, static_cast<int>(x - radius - 2.0f));
int t = std::max(0, static_cast<int>(y - radius - 2.0f));
int r = std::min(static_cast<int>(screenWidth),
static_cast<int>(x + radius + 2.0f));
int b = std::min(static_cast<int>(screenHeight),
static_cast<int>(y + radius + 2.0f));
if (l >= r || t >= b) return; // 无效矩形
if (currentDirty.left == 0 && currentDirty.right == 0) {
currentDirty = {l, t, r, b};
} else {
currentDirty.left = std::min(currentDirty.left, (LONG)l);
currentDirty.top = std::min(currentDirty.top, (LONG)t);
currentDirty.right = std::max(currentDirty.right, (LONG)r);
currentDirty.bottom = std::max(currentDirty.bottom, (LONG)b);
}
}
/**
* @brief 获取需要更新的矩形 (当前 + 前一帧的并集)
*/
RECT GetUpdateRect() const noexcept {
RECT result = currentDirty;
result.left = std::min(result.left, prevDirty.left);
result.top = std::min(result.top, prevDirty.top);
result.right = std::max(result.right, prevDirty.right);
result.bottom = std::max(result.bottom, prevDirty.bottom);
return result;
}
/**
* @brief 循环开始 - 交换脏区,准备新一帧
*/
void BeginFrame() noexcept {
prevDirty = currentDirty;
currentDirty = {0, 0, 0, 0};
}
/**
* @brief 检查此帧是否有脏内容
*/
bool HasDirtyRects() const noexcept {
return !IsRectEmpty(&currentDirty);
}
};