xw-devtool-cli 1.0.33 → 1.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/crosshair.js +47 -39
- package/src/commands/imgCompress.js +29 -4
- package/src/commands/pixelDistance.js +15 -3
- package/src/locales/en.js +2 -1
- package/src/locales/zh.js +2 -1
package/package.json
CHANGED
|
@@ -16,40 +16,45 @@ using System;
|
|
|
16
16
|
using System.Drawing;
|
|
17
17
|
using System.Windows.Forms;
|
|
18
18
|
using System.Runtime.InteropServices;
|
|
19
|
-
public class OverlayForm : Form {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
19
|
+
public class OverlayForm : Form {
|
|
20
|
+
const int WS_EX_TRANSPARENT=0x20;
|
|
21
|
+
const int WS_EX_LAYERED=0x80000;
|
|
22
|
+
const int GWL_EXSTYLE=-20;
|
|
23
|
+
[DllImport("user32.dll")] static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
|
24
|
+
[DllImport("user32.dll")] static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
|
25
|
+
Timer t;
|
|
26
|
+
public OverlayForm(){
|
|
27
|
+
this.FormBorderStyle=FormBorderStyle.None;
|
|
28
|
+
this.TopMost=true;
|
|
29
|
+
this.ShowInTaskbar=false;
|
|
30
|
+
this.BackColor=Color.Fuchsia;
|
|
31
|
+
this.TransparencyKey=this.BackColor;
|
|
32
|
+
this.DoubleBuffered=true;
|
|
33
|
+
this.Cursor = Cursors.Default;
|
|
34
|
+
Rectangle totalBounds = Rectangle.Empty;
|
|
35
|
+
foreach(Screen s in Screen.AllScreens) {
|
|
36
|
+
totalBounds = Rectangle.Union(totalBounds, s.Bounds);
|
|
37
|
+
}
|
|
38
|
+
this.StartPosition = FormStartPosition.Manual;
|
|
39
|
+
this.Bounds = totalBounds;
|
|
40
|
+
}
|
|
41
|
+
protected override void OnShown(EventArgs e){
|
|
42
|
+
base.OnShown(e);
|
|
43
|
+
int ex=GetWindowLong(this.Handle,GWL_EXSTYLE);
|
|
44
|
+
SetWindowLong(this.Handle,GWL_EXSTYLE,ex|WS_EX_LAYERED|WS_EX_TRANSPARENT);
|
|
45
|
+
t=new Timer();
|
|
46
|
+
t.Interval=16;
|
|
47
|
+
t.Tick+=(s,ev)=>{ this.Invalidate(); };
|
|
48
|
+
t.Start();
|
|
49
|
+
}
|
|
50
|
+
protected override void OnPaint(PaintEventArgs e){
|
|
51
|
+
var p=this.PointToClient(Cursor.Position);
|
|
47
52
|
var g=e.Graphics;
|
|
48
53
|
using(var pen=new Pen(Color.Red,1)){
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
g.DrawLine(pen,0,p.Y,this.Width,p.Y);
|
|
55
|
+
g.DrawLine(pen,p.X,0,p.X,this.Height);
|
|
56
|
+
}
|
|
51
57
|
}
|
|
52
|
-
}
|
|
53
58
|
}
|
|
54
59
|
public static class Entry {
|
|
55
60
|
public static void Main(){ Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new OverlayForm()); }
|
|
@@ -81,14 +86,17 @@ public static class Entry {
|
|
|
81
86
|
try { stdin.setRawMode(true); } catch {}
|
|
82
87
|
stdin.resume();
|
|
83
88
|
const onData = (data) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
const s = data.toString('utf8');
|
|
90
|
+
if (s === '\x1b') {
|
|
91
|
+
stdin.removeListener('data', onData);
|
|
92
|
+
try { stdin.setRawMode(false); } catch {}
|
|
93
|
+
stdin.pause();
|
|
94
|
+
process.removeListener('exit', exitHandler);
|
|
95
|
+
process.removeListener('SIGINT', exitHandler);
|
|
96
|
+
cleanup();
|
|
97
|
+
resolve();
|
|
98
|
+
}
|
|
91
99
|
};
|
|
92
|
-
stdin.
|
|
100
|
+
stdin.on('data', onData);
|
|
93
101
|
});
|
|
94
102
|
}
|
|
@@ -124,7 +124,29 @@ async function pickPreserveStructure() {
|
|
|
124
124
|
return choice;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
async function
|
|
127
|
+
async function pickTimestampSuffix() {
|
|
128
|
+
const choice = await selectFromMenu(i18next.t('imgCompress.timestampPrompt'), [
|
|
129
|
+
{ name: i18next.t('imgCompress.yes'), value: true },
|
|
130
|
+
{ name: i18next.t('imgCompress.no'), value: false }
|
|
131
|
+
], true, i18next.t('common.back'));
|
|
132
|
+
if (choice === '__BACK__') return '__BACK__';
|
|
133
|
+
return choice;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function uniqueOutPath(dir, baseName) {
|
|
137
|
+
let candidate = path.join(dir, baseName);
|
|
138
|
+
if (!fs.existsSync(candidate)) return candidate;
|
|
139
|
+
let idx = 1;
|
|
140
|
+
const ext = path.extname(baseName);
|
|
141
|
+
const name = path.basename(baseName, ext);
|
|
142
|
+
while (true) {
|
|
143
|
+
candidate = path.join(dir, `${name}_${idx}${ext}`);
|
|
144
|
+
if (!fs.existsSync(candidate)) return candidate;
|
|
145
|
+
idx++;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function compressOne(inputPath, quality, outDir, baseDir, preserveStructure, addTimestamp) {
|
|
128
150
|
const ext = path.extname(inputPath).toLowerCase();
|
|
129
151
|
const name = path.basename(inputPath, ext);
|
|
130
152
|
const ts = dayjs().format('YYYYMMDD_HHmmss');
|
|
@@ -134,12 +156,11 @@ async function compressOne(inputPath, quality, outDir, baseDir, preserveStructur
|
|
|
134
156
|
targetDir = path.join(outDir, rel);
|
|
135
157
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
136
158
|
}
|
|
137
|
-
|
|
159
|
+
let outPath = addTimestamp ? path.join(targetDir, `${name}_compressed_${ts}${ext}`) : uniqueOutPath(targetDir, `${name}_compressed${ext}`);
|
|
138
160
|
const image = sharp(inputPath, { failOnError: false });
|
|
139
161
|
if (ext === '.jpg' || ext === '.jpeg') {
|
|
140
162
|
await image.jpeg({ quality, mozjpeg: true }).toFile(outPath);
|
|
141
163
|
} else if (ext === '.png') {
|
|
142
|
-
// Map quality (1-100) to compressionLevel (0-9) roughly
|
|
143
164
|
const level = Math.max(0, Math.min(9, Math.round((100 - quality) / 10)));
|
|
144
165
|
await image.png({ quality, compressionLevel: level }).toFile(outPath);
|
|
145
166
|
} else if (ext === '.webp') {
|
|
@@ -179,15 +200,19 @@ export async function imgCompressHandler() {
|
|
|
179
200
|
const outDir = path.resolve(baseDir, `compressed_${dayjs().format('YYYYMMDD_HHmmss')}`);
|
|
180
201
|
fs.mkdirSync(outDir, { recursive: true });
|
|
181
202
|
let preserveStructure = false;
|
|
203
|
+
let addTimestamp = true;
|
|
182
204
|
if (input.type === 'folder') {
|
|
183
205
|
const preserveChoice = await pickPreserveStructure();
|
|
184
206
|
if (preserveChoice === '__BACK__') return;
|
|
185
207
|
preserveStructure = preserveChoice;
|
|
208
|
+
const tsChoice = await pickTimestampSuffix();
|
|
209
|
+
if (tsChoice === '__BACK__') return;
|
|
210
|
+
addTimestamp = tsChoice;
|
|
186
211
|
}
|
|
187
212
|
const results = [];
|
|
188
213
|
for (const f of files) {
|
|
189
214
|
try {
|
|
190
|
-
const out = await compressOne(f, quality, outDir, baseDir, preserveStructure);
|
|
215
|
+
const out = await compressOne(f, quality, outDir, baseDir, preserveStructure, addTimestamp);
|
|
191
216
|
if (out) results.push(out);
|
|
192
217
|
} catch (e) {
|
|
193
218
|
console.error(`${i18next.t('imgCompress.fail')}: ${f} -> ${e.message}`);
|
|
@@ -25,6 +25,7 @@ public class DistanceForm : Form {
|
|
|
25
25
|
private bool p1Set = false;
|
|
26
26
|
private bool p2Set = false;
|
|
27
27
|
private Bitmap screenCapture;
|
|
28
|
+
private Timer t;
|
|
28
29
|
|
|
29
30
|
public DistanceForm() {
|
|
30
31
|
this.FormBorderStyle = FormBorderStyle.None;
|
|
@@ -47,6 +48,11 @@ public class DistanceForm : Form {
|
|
|
47
48
|
g.CopyFromScreen(totalBounds.Location, Point.Empty, totalBounds.Size);
|
|
48
49
|
}
|
|
49
50
|
this.BackgroundImage = screenCapture;
|
|
51
|
+
|
|
52
|
+
t = new Timer();
|
|
53
|
+
t.Interval = 33;
|
|
54
|
+
t.Tick += (s, e) => { this.Invalidate(); };
|
|
55
|
+
t.Start();
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
private Point GetSnappedPoint(Point current) {
|
|
@@ -95,9 +101,7 @@ public class DistanceForm : Form {
|
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
protected override void OnMouseMove(MouseEventArgs e) {
|
|
98
|
-
|
|
99
|
-
this.Invalidate();
|
|
100
|
-
}
|
|
104
|
+
this.Invalidate();
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
protected override void OnPaint(PaintEventArgs e) {
|
|
@@ -105,10 +109,18 @@ public class DistanceForm : Form {
|
|
|
105
109
|
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
|
|
106
110
|
|
|
107
111
|
Pen pen = new Pen(Color.Red, 2);
|
|
112
|
+
Pen crossPen = new Pen(Color.Red, 1);
|
|
108
113
|
Brush brush = Brushes.Red;
|
|
109
114
|
Font font = new Font("Segoe UI", 12, FontStyle.Bold);
|
|
110
115
|
Brush textBg = new SolidBrush(Color.FromArgb(180, 0, 0, 0));
|
|
111
116
|
|
|
117
|
+
Point cp = this.PointToClient(Cursor.Position);
|
|
118
|
+
int gap = 24;
|
|
119
|
+
g.DrawLine(crossPen, 0, cp.Y, Math.Max(0, cp.X - gap), cp.Y);
|
|
120
|
+
g.DrawLine(crossPen, Math.Min(this.ClientSize.Width, cp.X + gap), cp.Y, this.ClientSize.Width, cp.Y);
|
|
121
|
+
g.DrawLine(crossPen, cp.X, 0, cp.X, Math.Max(0, cp.Y - gap));
|
|
122
|
+
g.DrawLine(crossPen, cp.X, Math.Min(this.ClientSize.Height, cp.Y + gap), cp.X, this.ClientSize.Height);
|
|
123
|
+
|
|
112
124
|
if (p1Set) {
|
|
113
125
|
Point target = p2Set ? p2 : GetSnappedPoint(this.PointToClient(Cursor.Position));
|
|
114
126
|
|
package/src/locales/en.js
CHANGED
|
@@ -155,6 +155,7 @@ export default {
|
|
|
155
155
|
openFail: 'Failed to open directory',
|
|
156
156
|
opened: 'Opened output directory',
|
|
157
157
|
scanResult: 'Images found',
|
|
158
|
-
preservePrompt: 'Preserve original directory structure in output?'
|
|
158
|
+
preservePrompt: 'Preserve original directory structure in output?',
|
|
159
|
+
timestampPrompt: 'Append timestamp suffix to filename for folder compression?'
|
|
159
160
|
}
|
|
160
161
|
};
|
package/src/locales/zh.js
CHANGED