@bitefoundry/retrieviewer 0.0.18 → 0.0.20
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/NATIVE-RUNTIME.md +15 -9
- package/PUPPY-MEMORY.md +25 -0
- package/README.ko.md +13 -0
- package/README.md +15 -0
- package/RELEASING.md +34 -1
- package/SPARSE-PAINT.md +119 -0
- package/integrations/native-windows/bin/win32-x64/retrieviewer-native.exe +0 -0
- package/integrations/native-windows/desktop.mjs +4 -1
- package/integrations/native-windows/host.cpp +2 -0
- package/integrations/native-windows/manifest.json +2 -2
- package/integrations/native-windows/native-paint.hpp +116 -0
- package/integrations/native-windows/native-window.hpp +15 -36
- package/integrations/native-windows/paint-regions.hpp +28 -0
- package/package.json +5 -2
package/NATIVE-RUNTIME.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Lightweight desktop runtime · 0.0.
|
|
1
|
+
# Lightweight desktop runtime · 0.0.20
|
|
2
2
|
|
|
3
3
|
Windows x64 basic desktop mode runs the existing JavaScript pet behavior in
|
|
4
4
|
QuickJS-NG, inside the same native process that paints the transparent window.
|
|
@@ -41,15 +41,14 @@ play/treat/attention or tower/wheel actions. No forced GC or working-set trimmin
|
|
|
41
41
|
|
|
42
42
|
| Pet | Median RSS | Sampled maximum | OS lifetime peak |
|
|
43
43
|
|---|---:|---:|---:|
|
|
44
|
-
| Retriever |
|
|
45
|
-
| Ginger cat |44.
|
|
44
|
+
| Retriever |22.9MiB|37.9MiB|38.7MiB|
|
|
45
|
+
| Ginger cat with tower/wheel |44.4MiB|64.6MiB|65.5MiB|
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
rounded; memory remains dependent on scene, window dimensions and OS behavior.
|
|
47
|
+
Version 0.0.20 uses one cropped layered window for the complete scene. This
|
|
48
|
+
fixes flicker from the 0.0.19 multi-window painter; its lower memory figures are
|
|
49
|
+
superseded. Buffer shrinking and hidden-state cleanup remain. See the
|
|
50
|
+
[presentation fix](SPARSE-PAINT.md). These finite measurements do not establish
|
|
51
|
+
a universal 50 MiB memory cap. Raw evidence: dist/coherent-paint-memory/.
|
|
53
52
|
|
|
54
53
|
## Scope
|
|
55
54
|
|
|
@@ -87,3 +86,10 @@ off-screen diagnostic windows, preserving an already-running user companion.
|
|
|
87
86
|
The executable embeds the Windows UTF-8 process code-page manifest (Windows10
|
|
88
87
|
1903+ /11) so QuickJS stdio also reads Korean/non-ASCII installation paths.
|
|
89
88
|
A regression test launches both species from Korean bundle, art and state paths.
|
|
89
|
+
|
|
90
|
+
## Coherent scene presentation
|
|
91
|
+
|
|
92
|
+
[One complete-scene upload](SPARSE-PAINT.md) keeps the final pet, props and
|
|
93
|
+
furniture in the same HWND. That window stays click-through and non-activating.
|
|
94
|
+
The 128-frame presentation regression checks window identity, full-frame pixel
|
|
95
|
+
parity and hidden-resource release separately from pet-core pixel parity.
|
package/PUPPY-MEMORY.md
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
# 0.0.20 coherent scene painter
|
|
2
|
+
|
|
3
|
+
The complete scene now updates through one native layered window to fix the
|
|
4
|
+
0.0.19 flicker regression. Buffer shrinking, hidden-state release, stamp atlas
|
|
5
|
+
and indexed art loading remain; no art or pet behavior changed. Distant cat
|
|
6
|
+
furniture can enlarge the one bounding surface. The 0.0.19 figures below are
|
|
7
|
+
historical and must not describe the current version. See
|
|
8
|
+
[presentation evidence](SPARSE-PAINT.md) and [current measurements](NATIVE-RUNTIME.md).
|
|
9
|
+
|
|
10
|
+
Real 4K 150s measurements: dog median22.9MiB / OS peak38.7MiB; cat with
|
|
11
|
+
tower/wheel44.4 /65.5MiB. Watch/brain off, no forced GC/trim; not a hard cap.
|
|
12
|
+
|
|
13
|
+
## Superseded 0.0.19 result (presentation regression)
|
|
14
|
+
|
|
15
|
+
# 0.0.19 sparse painter result
|
|
16
|
+
|
|
17
|
+
Windows x64 basic mode retains one native process and splits sparse content
|
|
18
|
+
into bounded cropped surfaces. In the same real4K150s schedule, dog median RSS
|
|
19
|
+
was22.6MiB (OS peak36.5MiB); cat with tower/wheel29.5MiB (peak39.1MiB).
|
|
20
|
+
Final masks and behavior are unchanged. No forced GC/trim; optional watching,
|
|
21
|
+
other OS hosts and browser tabs are outside this memory claim. See
|
|
22
|
+
[sparse paint evidence](SPARSE-PAINT.md) and [runtime scope](NATIVE-RUNTIME.md).
|
|
23
|
+
|
|
24
|
+
## Previous0.0.18 native result (historical)
|
|
25
|
+
|
|
1
26
|
# 0.0.18 native desktop result
|
|
2
27
|
|
|
3
28
|
Windows x64 basic mode now uses a single QuickJS/Win32 process. In real4K
|
package/README.ko.md
CHANGED
|
@@ -148,6 +148,19 @@ JS 플러그인과 JSONL 브리지를 제공해요. 화면·입력·시간 제
|
|
|
148
148
|
AI로 생성한 그림에 자세 정렬·접지·프레임 처리를 적용한 애니메이션입니다.
|
|
149
149
|
실제 모션 캡처나 자의식이 있는 강아지라고 주장하지 않아요.
|
|
150
150
|
|
|
151
|
+
## 0.0.20
|
|
152
|
+
|
|
153
|
+
Windows에서 화면 일부가 깜빡이던 문제를 고쳤어요. 장면 전체를 창 하나로
|
|
154
|
+
갱신하고, 버퍼 축소와 숨김 상태 정리는 유지합니다. 그림과 동작은 그대로예요.
|
|
155
|
+
[수정과 측정 기록](SPARSE-PAINT.md)을 참고하세요.
|
|
156
|
+
|
|
157
|
+
## 0.0.19 (이전 버전 · 깜빡임 수정으로 대체)
|
|
158
|
+
|
|
159
|
+
Windows 기본 실행에서 표시 영역을 나누고 불필요하게 커진 버퍼를 회수해요.
|
|
160
|
+
그림과 동작은 그대로예요. 실제4K150초 측정에서 강아지는 중앙값22.6MiB·최고36.5MiB,
|
|
161
|
+
가구가 있는 고양이는29.5MiB·39.1MiB였어요. 선택형 감시·다른OS·브라우저는
|
|
162
|
+
별도 범위예요. [측정과 검증 기록](SPARSE-PAINT.md)을 참고하세요.
|
|
163
|
+
|
|
151
164
|
## 0.0.18
|
|
152
165
|
|
|
153
166
|
Windows x64 기본 실행은 네이티브 프로세스 하나만 남고 Node는 종료돼요.
|
package/README.md
CHANGED
|
@@ -189,6 +189,21 @@ The puppy artwork was developed with AI-generated imagery and programmatic pose,
|
|
|
189
189
|
grounding and frame processing. It is authored animation, not motion capture or
|
|
190
190
|
a claim of sentience.
|
|
191
191
|
|
|
192
|
+
## 0.0.20
|
|
193
|
+
|
|
194
|
+
Fixes Windows flicker introduced by the split-window painter. The whole scene
|
|
195
|
+
updates through one stable transparent window again; buffer shrinking and
|
|
196
|
+
hidden-state cleanup remain. Artwork and behavior are unchanged.
|
|
197
|
+
See [presentation fix and measurements](SPARSE-PAINT.md).
|
|
198
|
+
|
|
199
|
+
## 0.0.19 (historical; superseded by flicker fix)
|
|
200
|
+
|
|
201
|
+
Windows basic mode now splits distant content into small paint regions and
|
|
202
|
+
reclaims oversized/hidden surfaces. Same pets, artwork and behavior. In real4K
|
|
203
|
+
150s tests: dog median22.6MiB / OS peak36.5MiB; cat with tower/wheel29.5 /39.1MiB.
|
|
204
|
+
Optional watching and other OS/browser hosts keep their separate memory scope.
|
|
205
|
+
See [measurements and verification](SPARSE-PAINT.md).
|
|
206
|
+
|
|
192
207
|
## 0.0.18
|
|
193
208
|
|
|
194
209
|
Windows x64 basic mode now runs one native process; Node exits after launch.
|
package/RELEASING.md
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
|
-
# Retrieviewer 0.0.
|
|
1
|
+
# Retrieviewer 0.0.20 release process
|
|
2
|
+
|
|
3
|
+
Current target: `@bitefoundry/retrieviewer@0.0.20`, public `latest`.
|
|
4
|
+
Hotfix for the reported flicker in the previously authorized npm/site release.
|
|
5
|
+
Replace the dynamically repartitioned HWNDs with one complete-scene layered
|
|
6
|
+
window and one pixel/position/size upload. Keep stamp caching, indexed art,
|
|
7
|
+
buffer shrinking and hidden-state release. Artwork and pet behavior are unchanged.
|
|
8
|
+
|
|
9
|
+
Verify moving/disjoint masks keep one stable window, exact native paint pixels,
|
|
10
|
+
resize/hide/restore/switch/teardown, full suite and fresh installed artifact.
|
|
11
|
+
Publish that same tested tarball after CI, verify registry integrity and a fresh
|
|
12
|
+
consumer, then confirm the matching bilingual website. Memory figures must
|
|
13
|
+
come from this version, not the regressed sparse painter. Existing code/art
|
|
14
|
+
licenses, optional integrations, user pet and privacy setup are preserved.
|
|
15
|
+
|
|
16
|
+
## Previous 0.0.19 scope (historical; presentation regression)
|
|
17
|
+
|
|
18
|
+
### Retrieviewer 0.0.19 release process
|
|
19
|
+
|
|
20
|
+
Current target: `@bitefoundry/retrieviewer@0.0.19`, public `latest`.
|
|
21
|
+
The owner explicitly authorized npm and website deployment on2026-10-25.
|
|
22
|
+
Ship the verified sparse Windows painter: bounded disjoint final-mask regions,
|
|
23
|
+
reclaimable buffers, preserved artwork/behavior and existing compatibility modes.
|
|
24
|
+
Keep code MIT and both artworks CC BY-SA4.0; no new generation inputs or assets.
|
|
25
|
+
|
|
26
|
+
Existing evidence covers300 exact native paint frames,56 partition cases,
|
|
27
|
+
real4K150s operating memory and owned-window shrink/hide/restore/switch/teardown.
|
|
28
|
+
Run the suite/release gate, build and install a fresh exact tarball, exercise its
|
|
29
|
+
actual native resources/picker, then publish that same artifact after CI.
|
|
30
|
+
Verify registry integrity, fresh registry installation and production website.
|
|
31
|
+
Keep website figures scoped to Windows basic mode; update EN/KO copy and cache
|
|
32
|
+
keys together. Preserve old releases, the user's running pet and privacy setup.
|
|
33
|
+
|
|
34
|
+
## Previous0.0.18 scope (historical)
|
|
2
35
|
|
|
3
36
|
Current target: `@bitefoundry/retrieviewer@0.0.18`, public `latest`.
|
|
4
37
|
The owner authorized immediate npm and website deployment of the lightweight
|
package/SPARSE-PAINT.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Coherent native painting · 0.0.20
|
|
2
|
+
|
|
3
|
+
The 0.0.19 sparse-window optimization is superseded. Per-frame pixel comparison
|
|
4
|
+
passed, but a moving scene could change its partition and destroy/reposition
|
|
5
|
+
multiple HWNDs at different compositor moments. The owner reported visible
|
|
6
|
+
flicker. Preparing all DIBs first did not make those window updates atomic.
|
|
7
|
+
|
|
8
|
+
Version 0.0.20 paints the complete final mask into one cropped layered window.
|
|
9
|
+
`UpdateLayeredWindow` uploads all pixels, position and size together. The same
|
|
10
|
+
HWND survives movement and crop changes. Visibility is changed only when first
|
|
11
|
+
shown; there is no extra per-frame `SetWindowPos` after upload. No animal assets,
|
|
12
|
+
anatomy, motion, Braille masks or optional integrations changed.
|
|
13
|
+
|
|
14
|
+
The 256-mask stamp atlas and one-process QuickJS host remain. Capacity still
|
|
15
|
+
shrinks when it exceeds twice the rounded target area; hidden/minimized scenes
|
|
16
|
+
release their surface and window. The tradeoff is one bounding bitmap spanning
|
|
17
|
+
distant furniture: memory can exceed the regressed split-painter measurements.
|
|
18
|
+
Those old numbers below must not be presented as current measurements.
|
|
19
|
+
|
|
20
|
+
## Verification
|
|
21
|
+
|
|
22
|
+
- Real Win32 presentation regression: 128 changing/disjoint scenes retain one
|
|
23
|
+
visible HWND, with exact full-scene BGRA pixels; hidden resources release.
|
|
24
|
+
- Native mask/capacity tests: 56 grids across seven cell sizes.
|
|
25
|
+
- Real moving dog/cream, cat/gold and cat/dark: 301 exact paint comparisons.
|
|
26
|
+
- Owned 4K -> small -> 4K, minimize/restore and same-process species switching.
|
|
27
|
+
DIB capacity shrank from 12.22 to 1.50 MiB and reached zero when minimized.
|
|
28
|
+
|
|
29
|
+
Reproduce with `node --test native-desktop.test.mjs`,
|
|
30
|
+
`node tools/check-native-paint.mjs` and `node tools/check-native-resources.mjs`.
|
|
31
|
+
This checks actual window lifetime and upload coherence as well as image bytes;
|
|
32
|
+
finite pixel/lifetime checks are not a claim that every display was observed.
|
|
33
|
+
|
|
34
|
+
## Current memory measurement
|
|
35
|
+
|
|
36
|
+
Same Windows x64 host, owned off-screen 3840x2160 target, real native painter,
|
|
37
|
+
seed731 and 150s action schedule, 300 roughly half-second samples per pet
|
|
38
|
+
(157.5/156.9s wall time). Watch/brain off; no forced GC or working-set trim.
|
|
39
|
+
Harness and external DWM/GPU allocations are excluded.
|
|
40
|
+
|
|
41
|
+
| Pet | Median RSS | Sampled maximum | OS lifetime peak |
|
|
42
|
+
|---|---:|---:|---:|
|
|
43
|
+
| Retriever |22.9MiB|37.9MiB|38.7MiB|
|
|
44
|
+
| Cat with tower/wheel |44.4MiB|64.6MiB|65.5MiB|
|
|
45
|
+
|
|
46
|
+
Cat furniture spans a large bounding bitmap again. This restores coherent
|
|
47
|
+
presentation at a higher RAM cost than 0.0.19; it is not a universal 50MiB cap.
|
|
48
|
+
Both owned processes stopped and removed their sessions. Raw local evidence:
|
|
49
|
+
`dist/coherent-paint-memory/{dog,cat,report}.json`. Full suite:244 passed,
|
|
50
|
+
two macOS-only tests skipped on Windows. No artwork or motion change.
|
|
51
|
+
|
|
52
|
+
## Superseded 0.0.19 experiment (historical)
|
|
53
|
+
|
|
54
|
+
# Sparse Windows paint surfaces · 0.0.19
|
|
55
|
+
|
|
56
|
+
Version0.0.19 builds on0.0.18. JavaScript behavior, Q5 art, world/contact math, colors and
|
|
57
|
+
Braille masks are unchanged; the native painter allocates less empty space.
|
|
58
|
+
|
|
59
|
+
## Change
|
|
60
|
+
|
|
61
|
+
The old painter kept one bitmap spanning every nonempty cell. The cat, tower
|
|
62
|
+
and wheel therefore retained a large transparent rectangle between objects.
|
|
63
|
+
Its width and height only grew until species switch/exit. A held-pose ablation
|
|
64
|
+
at3840×2160 measured cat+furniture38.6MiB versus cat alone17.6MiB; the equivalent
|
|
65
|
+
single DIB capacities were12.22MiB and1.63MiB respectively.
|
|
66
|
+
|
|
67
|
+
The new painter partitions the FINAL cell mask into up to8 disjoint cropped
|
|
68
|
+
regions. Greedy splits account for64px allocation rounding and must save at
|
|
69
|
+
least256KiB of bitmap space. No glyph is resampled, dropped, drawn twice or
|
|
70
|
+
recolored; geometry and palette still share the original stamp atlas. A dense
|
|
71
|
+
scene can remain a single region. This is a bounded native composition change,
|
|
72
|
+
not extra animal simulations or one process per region.
|
|
73
|
+
|
|
74
|
+
Each surface reuses capacity while it is within2× its current rounded target
|
|
75
|
+
area. Oversized capacity is reclaimed on repaint; unused regions are destroyed.
|
|
76
|
+
Hidden/minimized/retargeted scenes release their surfaces and overlay windows.
|
|
77
|
+
Resume repaints from the current simulation state. Windows system/compositor
|
|
78
|
+
allocations remain separate from the reported `paintSurfaceBytes` DIB counter.
|
|
79
|
+
|
|
80
|
+
## Measurement
|
|
81
|
+
|
|
82
|
+
Same Windows11 x64 host, real native painter and an owned off-screen physical
|
|
83
|
+
3840×2160 scene. Seed731; the existing150s diagnostic action schedule and300
|
|
84
|
+
roughly0.5s samples per pet. No forced GC/working-set trimming. Watch/brain off;
|
|
85
|
+
harness and external DWM/GPU memory excluded. Baseline is the tested0.0.18
|
|
86
|
+
installed-artifact run. These finite runs do not establish a universal RAM cap.
|
|
87
|
+
|
|
88
|
+
| Scene |0.0.18 median RSS|0.0.19 median RSS|0.0.18 OS peak|0.0.19 OS peak|
|
|
89
|
+
|---|---:|---:|---:|---:|
|
|
90
|
+
|Dog|29.35MiB|22.63MiB|40.34MiB|36.45MiB|
|
|
91
|
+
|Cat with tower/wheel|44.22MiB|29.46MiB|65.45MiB|39.13MiB|
|
|
92
|
+
|
|
93
|
+
The short fixed-pose cat+furniture comparison fell from38.6 to27.3MiB. This is
|
|
94
|
+
a different workload from the moving150s runs above, not a replacement for them.
|
|
95
|
+
Actual resize testing reduced allocated DIBs from6.52 to1.66MiB. Minimizing
|
|
96
|
+
reported0 DIB bytes and0 overlay HWNDs; restore and dog/cat switching recovered.
|
|
97
|
+
|
|
98
|
+
A paired16s cat timing check had92 paints/340 rendered calls in both variants.
|
|
99
|
+
Mean render time changed3.06→3.94ms, P95 12.42→16.68ms. Partitioning trades some
|
|
100
|
+
CPU for memory; these timings are not a constant-FPS guarantee.
|
|
101
|
+
|
|
102
|
+
## Verification
|
|
103
|
+
|
|
104
|
+
- Native self-test:56 grids across7 cell sizes. Occupied masks covered once,
|
|
105
|
+
all regions disjoint/in bounds, partition plans no larger than one crop,
|
|
106
|
+
at most8 regions, and real DIB shrink/release verified.
|
|
107
|
+
- Real painter:300 moving frames across dog/cream and cat/gold/dark compared
|
|
108
|
+
against the old monolithic GDI+ ellipse output, byte-for-byte including alpha.
|
|
109
|
+
This tests per-frame paint pixels, not atomicity of compositor presentation.
|
|
110
|
+
- Real owned windows: pick/reselect,4K→small→4K, minimize/restore, species
|
|
111
|
+
switching, bootstrap detach, startup EOF, stop and Korean paths passed.
|
|
112
|
+
- Local full suite:243 passed,2 macOS-only tests skipped on this Windows host.
|
|
113
|
+
|
|
114
|
+
Reproduce with `node --test native-desktop.test.mjs`,
|
|
115
|
+
`node tools/check-native-paint.mjs`, `node tools/check-native-resources.mjs`,
|
|
116
|
+
and `node tools/measure-native-pet.mjs 150` after `npm run build:native`.
|
|
117
|
+
Local evidence is under dist/sparse-paint-memory, dist/paint-parity-HlzSTl,
|
|
118
|
+
dist/paint-resource-m3dMB1 and dist/sparse-timing-REFNLe. QA output is not
|
|
119
|
+
packaged. Linux/macOS/browser and optional Node/CLR watching paths are unchanged.
|
|
Binary file
|
|
@@ -25,7 +25,10 @@ export async function startNativeDesktop(options={}){
|
|
|
25
25
|
if(process.platform!=='win32'||process.arch!=='x64')throw Error('Native desktop currently requires Windows x64');
|
|
26
26
|
await verifyNativeArtifact();const paths=await prepareRoot(options);await noLink(paths.session);
|
|
27
27
|
const pipe=controlAddress({uuid:randomUUID()}),token=randomBytes(32).toString('hex');
|
|
28
|
-
const
|
|
28
|
+
const args=[fileURLToPath(bundle),fileURLToPath(new URL('../../',root)),'--production'];
|
|
29
|
+
if(options.nativeDiagnostic&&options.nativeVerifyPaint)args.push('--verify-regions');
|
|
30
|
+
if(options.nativeDiagnostic&&options.nativeProofPath)args.push('--proof='+options.nativeProofPath);
|
|
31
|
+
const child=spawn(fileURLToPath(binary),args,{detached:true,windowsHide:true,stdio:['pipe','pipe','pipe'],env:{...process.env,OPENROUTER_API_KEY:undefined}});
|
|
29
32
|
let owns=false,complete=false,stderr='';const session={owner:'bitefoundry-retrieviewer-overlay-v1',pid:child.pid,pipe,token};
|
|
30
33
|
child.stderr.on('data',b=>stderr=(stderr+b.toString('utf8')).slice(-2048));child.stdin.on('error',()=>{});
|
|
31
34
|
try{
|
|
@@ -74,6 +74,8 @@ static JSValue blit(JSContext* c,JSValueConst,int argc,JSValueConst* a){try{
|
|
|
74
74
|
}catch(const std::exception& e){return failure(c,e);}}
|
|
75
75
|
int wmain(int argc,wchar_t** wideArgs){
|
|
76
76
|
std::vector<std::string> argText;for(int i=0;i<argc;i++){int n=WideCharToMultiByte(CP_UTF8,0,wideArgs[i],-1,nullptr,0,nullptr,nullptr);std::string value(n,0);WideCharToMultiByte(CP_UTF8,0,wideArgs[i],-1,value.data(),n,nullptr,nullptr);value.pop_back();argText.push_back(value);}std::vector<char*> args;for(auto& text:argText)args.push_back(text.data());char** argv=args.data();
|
|
77
|
+
if(argc==2&&std::string(argv[1])=="--self-test-regions")try{return NativePet::selfTestPaintRegions();}catch(const std::exception& e){fprintf(stderr,"%s\n",e.what());return 1;}
|
|
78
|
+
if(argc==2&&std::string(argv[1])=="--self-test-presentation")try{return NativePet::selfTestPaintPresentation();}catch(const std::exception& e){fprintf(stderr,"%s\n",e.what());return 1;}
|
|
77
79
|
bool production=false;for(int i=1;i<argc;i++)if(std::string(argv[i])=="--production")production=true;std::string configuration;
|
|
78
80
|
if(production){char ch;while(std::cin.get(ch)&&ch!='\n'){if(configuration.size()>=8192)return 2;configuration+=ch;}if(configuration.empty())return 2;}
|
|
79
81
|
if(argc<2)return 2;JSRuntime* rt=JS_NewRuntime();js_std_init_handlers(rt);JSContext* ctx=JS_NewContext(rt);JS_SetModuleLoaderFunc2(rt,nullptr,js_module_loader,js_module_check_attributes,nullptr);js_init_module_std(ctx,"qjs:std");js_init_module_os(ctx,"qjs:os");js_std_add_helpers(ctx,argc-1,argv+1);
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"sha256": "e9da942dbe57ac778c44ea8f300e2e868face5e73b00ffcd4399aeb7e94ee09c"
|
|
8
8
|
},
|
|
9
9
|
"bin/win32-x64/retrieviewer-native.exe": {
|
|
10
|
-
"bytes":
|
|
11
|
-
"sha256": "
|
|
10
|
+
"bytes": 1562624,
|
|
11
|
+
"sha256": "0844d0e5303938e11d3e57acb457d7f6df2f138ee84246ecdf006395055f0fba"
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Included inside NativePet. Shared final-mask stamps preserve original pixels.
|
|
2
|
+
struct Surface {
|
|
3
|
+
HDC dc=nullptr;HBITMAP bitmap=nullptr;HGDIOBJ old=nullptr;uint8_t* bits=nullptr;int width=0,height=0;
|
|
4
|
+
~Surface(){release();}
|
|
5
|
+
uint64_t bytes() const {return (uint64_t)width*height*4;}
|
|
6
|
+
void release(){if(dc&&old)SelectObject(dc,old);if(bitmap)DeleteObject(bitmap);if(dc)DeleteDC(dc);dc=nullptr;bitmap=nullptr;old=nullptr;bits=nullptr;width=height=0;}
|
|
7
|
+
void ensure(int w,int h){
|
|
8
|
+
const int rw=PetPaint::rounded(w),rh=PetPaint::rounded(h);
|
|
9
|
+
if(w<=width&&h<=height&&(uint64_t)width*height<=(uint64_t)rw*rh*2)return;
|
|
10
|
+
int nw=std::max(width,rw),nh=std::max(height,rh);
|
|
11
|
+
if((uint64_t)nw*nh>(uint64_t)rw*rh*2){nw=rw;nh=rh;}
|
|
12
|
+
release();width=nw;height=nh;BITMAPINFO info{};info.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);info.bmiHeader.biWidth=nw;info.bmiHeader.biHeight=-nh;info.bmiHeader.biPlanes=1;info.bmiHeader.biBitCount=32;info.bmiHeader.biCompression=BI_RGB;
|
|
13
|
+
HDC screen=GetDC(nullptr);dc=CreateCompatibleDC(screen);bitmap=CreateDIBSection(screen,&info,DIB_RGB_COLORS,(void**)&bits,nullptr,0);ReleaseDC(nullptr,screen);
|
|
14
|
+
if(!bitmap||!dc)throw std::runtime_error("Native surface allocation");old=SelectObject(dc,bitmap);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
struct Panel {
|
|
18
|
+
HWND window=nullptr;Surface surface;PetPaint::Rect crop;
|
|
19
|
+
void release(){if(window)DestroyWindow(window);window=nullptr;surface.release();crop={};}
|
|
20
|
+
};
|
|
21
|
+
static std::array<Panel,PetPaint::MaxRegions> panels;
|
|
22
|
+
static void releasePanels(){for(auto& p:panels)p.release();}
|
|
23
|
+
static void hidePanels(){releasePanels();}
|
|
24
|
+
static uint64_t paintBytes(){uint64_t total=0;for(const auto& p:panels)total+=p.surface.bytes();return total;}
|
|
25
|
+
static size_t paintCount(){size_t n=0;for(const auto& p:panels)if(p.window)n++;return n;}
|
|
26
|
+
struct DotAtlas {
|
|
27
|
+
int cell=0;uint32_t color=0;std::vector<uint8_t> pixels;
|
|
28
|
+
void ensure(int value){
|
|
29
|
+
if(cell==value&&color==ink().GetValue())return;cell=value;color=ink().GetValue();size_t length=(size_t)cell*cell*2*4;pixels.assign(length*256,0);
|
|
30
|
+
static const int dx[]={0,0,0,1,1,1,0,1},dy[]={0,1,2,0,1,2,3,3};float step=cell/2.f,diameter=std::max(1.1f,step*.48f);
|
|
31
|
+
for(int mask=0;mask<256;mask++){Gdiplus::Bitmap bitmap(cell,cell*2,cell*4,PixelFormat32bppPARGB,pixels.data()+mask*length);Gdiplus::Graphics g(&bitmap);g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);Gdiplus::SolidBrush brush(ink());for(int bit=0;bit<8;bit++)if(mask&(1<<bit)){float x=(dx[bit]+.5f)*step,y=(dy[bit]+.5f)*step;g.FillEllipse(&brush,x-diameter/2,y-diameter/2,diameter,diameter);}}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
static DotAtlas atlas;
|
|
35
|
+
static uint64_t verifiedPaints=0;
|
|
36
|
+
static std::vector<uint8_t> assemble(const PetPaint::Rect& all,int cell){
|
|
37
|
+
const int width=all.width()*cell,height=all.height()*cell*2;std::vector<uint8_t> image((size_t)width*height*4,0);
|
|
38
|
+
for(const auto& p:panels)if(p.window){const auto& r=p.crop;const int w=r.width()*cell,h=r.height()*cell*2;
|
|
39
|
+
for(int y=0;y<h;y++)memcpy(image.data()+((size_t)((r.top-all.top)*cell*2+y)*width+(r.left-all.left)*cell)*4,p.surface.bits+(size_t)y*p.surface.width*4,(size_t)w*4);
|
|
40
|
+
}
|
|
41
|
+
return image;
|
|
42
|
+
}
|
|
43
|
+
static void verifyPaint(const std::vector<uint8_t>& masks,int columns,const PetPaint::Rect& all,int cell){
|
|
44
|
+
const int w=all.width()*cell,h=all.height()*cell*2;std::vector<uint8_t> reference((size_t)w*h*4,0);
|
|
45
|
+
{Gdiplus::Bitmap bitmap(w,h,w*4,PixelFormat32bppPARGB,reference.data());Gdiplus::Graphics g(&bitmap);g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);Gdiplus::SolidBrush brush(ink());
|
|
46
|
+
static const int dx[]={0,0,0,1,1,1,0,1},dy[]={0,1,2,0,1,2,3,3};float step=cell/2.f,diameter=std::max(1.1f,step*.48f);
|
|
47
|
+
for(int y=all.top;y<all.bottom;y++)for(int x=all.left;x<all.right;x++){int mask=masks[(size_t)y*columns+x];for(int bit=0;bit<8;bit++)if(mask&(1<<bit)){float px=(x-all.left)*cell+(dx[bit]+.5f)*step,py=(y-all.top)*cell*2+(dy[bit]+.5f)*step;g.FillEllipse(&brush,px-diameter/2,py-diameter/2,diameter,diameter);}}g.Flush(Gdiplus::FlushIntentionSync);
|
|
48
|
+
}
|
|
49
|
+
if(reference!=assemble(all,cell))throw std::runtime_error("Native dots changed pixels");verifiedPaints++;
|
|
50
|
+
if(!atlasVerified){printf("{\"stage\":\"dot-atlas-verified\",\"pixelExact\":true,\"regions\":%zu}\n",paintCount());fflush(stdout);}atlasVerified=true;
|
|
51
|
+
}
|
|
52
|
+
static void paint(const std::string& text,int columns,int rows,int cell,POINT canvas){
|
|
53
|
+
std::vector<uint8_t> masks((size_t)columns*rows);size_t at=0;
|
|
54
|
+
for(int y=0;y<rows;y++){if(y){if(at>=text.size()||text[at++]!='\n')throw std::runtime_error("Native row separator");}for(int x=0;x<columns;x++){if(at+3>text.size())throw std::runtime_error("Native text length");const auto* p=(const unsigned char*)text.data()+at;uint32_t ch=((p[0]&15)<<12)|((p[1]&63)<<6)|(p[2]&63);at+=3;if(ch<0x2800||ch>0x28ff)throw std::runtime_error("Native glyph");masks[(size_t)y*columns+x]=(uint8_t)(ch-0x2800);}}
|
|
55
|
+
if(at!=text.size())throw std::runtime_error("Native trailing text");
|
|
56
|
+
const auto regions=PetPaint::partition(masks,columns,rows,cell);if(regions.empty()){releasePanels();return;}
|
|
57
|
+
PetPaint::Rect all;for(const auto& r:regions)all.include(r);
|
|
58
|
+
if((int64_t)all.width()*cell*all.height()*cell*2>8000000){hidePanels();visible=false;return;}
|
|
59
|
+
atlas.ensure(cell);const size_t stampSize=(size_t)cell*cell*2*4;
|
|
60
|
+
for(size_t i=0;i<regions.size();i++){
|
|
61
|
+
auto& panel=panels[i];panel.crop=regions[i];const auto& r=panel.crop;auto& surface=panel.surface;
|
|
62
|
+
if(!panel.window){panel.window=CreateWindowEx(WS_EX_LAYERED|WS_EX_TRANSPARENT|WS_EX_TOOLWINDOW|WS_EX_NOACTIVATE|WS_EX_TOPMOST,L"RetrieviewerNativeDots",L"",WS_POPUP,0,0,1,1,nullptr,nullptr,GetModuleHandle(nullptr),nullptr);if(!panel.window)throw std::runtime_error("Native overlay region");}
|
|
63
|
+
surface.ensure(r.width()*cell,r.height()*cell*2);std::fill(surface.bits,surface.bits+(size_t)surface.width*surface.height*4,0);
|
|
64
|
+
for(int y=r.top;y<r.bottom;y++)for(int x=r.left;x<r.right;x++){int mask=masks[(size_t)y*columns+x];if(!mask)continue;const uint8_t* stamp=atlas.pixels.data()+mask*stampSize;for(int sy=0;sy<cell*2;sy++)memcpy(surface.bits+((size_t)((y-r.top)*cell*2+sy)*surface.width+(x-r.left)*cell)*4,stamp+(size_t)sy*cell*4,cell*4);}
|
|
65
|
+
}
|
|
66
|
+
for(size_t i=regions.size();i<panels.size();i++)panels[i].release();
|
|
67
|
+
if(verifyAtlas&&(!atlasVerified||verifyEveryPaint))verifyPaint(masks,columns,all,cell);
|
|
68
|
+
if(saveProof&&!proofSaved){const int w=all.width()*cell,h=all.height()*cell*2;auto image=assemble(all,cell);Gdiplus::Bitmap source(w,h,w*4,PixelFormat32bppPARGB,image.data()),proof(w,h,PixelFormat32bppARGB);Gdiplus::Graphics pg(&proof);pg.Clear(Gdiplus::Color(255,0,0,0));pg.DrawImage(&source,Gdiplus::Rect(0,0,w,h),0,0,w,h,Gdiplus::UnitPixel);pg.Flush(Gdiplus::FlushIntentionSync);CLSID png={0x557cf406,0x1a04,0x11d3,{0x9a,0x73,0x00,0x00,0xf8,0x1e,0xf3,0x2e}};if(proof.Save(wide(proofPath).c_str(),&png,nullptr)!=Gdiplus::Ok)throw std::runtime_error("PNG proof failed");proofSaved=true;}
|
|
69
|
+
// The complete scene (pet, ball and furniture) has exactly one HWND.
|
|
70
|
+
// UpdateLayeredWindow changes its pixels, size and position in one call.
|
|
71
|
+
// Do not add another per-frame SetWindowPos/show: it exposes separate state.
|
|
72
|
+
HDC screen=GetDC(nullptr);BLENDFUNCTION blend={AC_SRC_OVER,0,255,AC_SRC_ALPHA};POINT origin={0,0};
|
|
73
|
+
for(size_t i=0;i<regions.size();i++){auto& p=panels[i];POINT destination={canvas.x+p.crop.left*cell,canvas.y+p.crop.top*cell*2};SIZE size={p.crop.width()*cell,p.crop.height()*cell*2};BOOL ok=UpdateLayeredWindow(p.window,screen,&destination,&size,p.surface.dc,&origin,0,&blend,ULW_ALPHA);if(!ok){ReleaseDC(nullptr,screen);throw std::runtime_error("Native layered update");}if(!IsWindowVisible(p.window))ShowWindow(p.window,SW_SHOWNOACTIVATE);}
|
|
74
|
+
ReleaseDC(nullptr,screen);painted++;
|
|
75
|
+
}
|
|
76
|
+
static int selfTestPaintRegions(){
|
|
77
|
+
uint32_t random=731;auto next=[&](){random^=random<<13;random^=random>>17;random^=random<<5;return random;};size_t checks=0;
|
|
78
|
+
for(int cell:{6,8,10,12,16,24,32})for(int type=0;type<8;type++){
|
|
79
|
+
const int cols=type==7?1000:240,rows=type==7?125:90;std::vector<uint8_t> masks((size_t)cols*rows);
|
|
80
|
+
for(int y=0;y<rows;y++)for(int x=0;x<cols;x++){bool on=type==1||type==2&&x%61==0||type==3&&y%31==0||type==4&&(x<20||x>cols-30)&&y>10||type==5&&(next()%100<4)||type==6&&((x-cols/2)*(x-cols/2)+(y-rows/2)*(y-rows/2)<900)||type==7&&x%97<3&&y%17<2;if(on)masks[(size_t)y*cols+x]=(uint8_t)(1+next()%255);}
|
|
81
|
+
const auto regions=PetPaint::partition(masks,cols,rows,cell);if(regions.size()>PetPaint::MaxRegions)throw std::runtime_error("Non-atomic scene partition");std::vector<uint8_t> seen(masks.size(),0);PetPaint::Rect all;uint64_t bytes=0;
|
|
82
|
+
for(const auto& r:regions){if(r.empty()||r.left<0||r.top<0||r.right>cols||r.bottom>rows)throw std::runtime_error("Region outside grid");all.include(r);bytes+=PetPaint::bytes(r,cell);for(int y=r.top;y<r.bottom;y++)for(int x=r.left;x<r.right;x++)if(++seen[(size_t)y*cols+x]!=1)throw std::runtime_error("Overlapping paint regions");}
|
|
83
|
+
for(size_t i=0;i<masks.size();i++)if(masks[i]&&seen[i]!=1)throw std::runtime_error("Missing mask cell");if(bytes>PetPaint::bytes(all,cell))throw std::runtime_error("Partition grew allocation");checks++;
|
|
84
|
+
}
|
|
85
|
+
Surface s;s.ensure(2048,1024);const auto big=s.bytes();s.ensure(1800,900);if(s.bytes()!=big)throw std::runtime_error("Surface churn on small size change");s.ensure(96,64);if(s.bytes()>(uint64_t)128*64*4*2)throw std::runtime_error("Surface did not shrink");s.release();if(s.bytes()!=0)throw std::runtime_error("Surface did not release");
|
|
86
|
+
printf("{\"passed\":true,\"partitionCases\":%zu,\"coverageAndDisjointness\":true,\"surfaceShrink\":true}\n",checks);return 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Real Win32 regression: moving/disjoint masks must retain one display window.
|
|
90
|
+
// Byte equality alone did not catch the multi-HWND presentation regression.
|
|
91
|
+
static int selfTestPaintPresentation(){
|
|
92
|
+
Gdiplus::GdiplusStartupInput input;ULONG_PTR token=0;
|
|
93
|
+
if(Gdiplus::GdiplusStartup(&token,&input,nullptr)!=Gdiplus::Ok)throw std::runtime_error("Presentation GDI+ startup");
|
|
94
|
+
WNDCLASS wc{};wc.lpfnWndProc=DefWindowProc;wc.hInstance=GetModuleHandle(nullptr);wc.lpszClassName=L"RetrieviewerNativeDots";RegisterClass(&wc);
|
|
95
|
+
const int columns=240,rows=90,cell=8;HWND first=nullptr;size_t checked=0;
|
|
96
|
+
verifyAtlas=verifyEveryPaint=atlasVerified=true;
|
|
97
|
+
try{
|
|
98
|
+
for(int frame=0;frame<128;frame++){
|
|
99
|
+
std::string text;
|
|
100
|
+
for(int y=0;y<rows;y++){if(y)text+='\n';for(int x=0;x<columns;x++){
|
|
101
|
+
bool pet=x>=20+frame%81&&x<55+frame%81&&y>=20&&y<65;
|
|
102
|
+
bool object=frame%3!=0&&x>=200-frame%13&&x<210-frame%13&&y>=5&&y<30;
|
|
103
|
+
unsigned ch=0x2800+(pet||object?1+(x+y+frame)%255:0);
|
|
104
|
+
text+=(char)(0xe0|(ch>>12));text+=(char)(0x80|((ch>>6)&63));text+=(char)(0x80|(ch&63));
|
|
105
|
+
}}
|
|
106
|
+
paint(text,columns,rows,cell,POINT{-12000,-12000});
|
|
107
|
+
if(paintCount()!=1||!IsWindowVisible(panels[0].window))throw std::runtime_error("Scene lost coherent visible window");
|
|
108
|
+
if(!first)first=panels[0].window;
|
|
109
|
+
if(panels[0].window!=first)throw std::runtime_error("Moving scene recreated its display window");
|
|
110
|
+
checked++;
|
|
111
|
+
}
|
|
112
|
+
hidePanels();if(paintCount()!=0||paintBytes()!=0)throw std::runtime_error("Hidden scene retained paint resources");
|
|
113
|
+
}catch(...){releasePanels();atlas.pixels.clear();Gdiplus::GdiplusShutdown(token);throw;}
|
|
114
|
+
atlas.pixels.clear();Gdiplus::GdiplusShutdown(token);
|
|
115
|
+
printf("{\"passed\":true,\"frames\":%zu,\"singleWindow\":true,\"stableWindow\":true,\"pixelExact\":true,\"hiddenResourcesReleased\":true}\n",checked);return 0;
|
|
116
|
+
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
#include <windows.h>
|
|
2
2
|
#include <gdiplus.h>
|
|
3
3
|
#include <memory>
|
|
4
|
+
#include <array>
|
|
5
|
+
#include "paint-regions.hpp"
|
|
4
6
|
#include "native-control.hpp"
|
|
5
7
|
namespace NativePet {
|
|
6
8
|
static JSContext* context;
|
|
7
|
-
static HWND target=nullptr
|
|
9
|
+
static HWND target=nullptr;
|
|
8
10
|
static bool showing=false,paused=false,failed=false;
|
|
9
11
|
static ULONGLONG startTime=0,lastTick=0,durationMs=0;
|
|
10
12
|
static int stage=0,painted=0;
|
|
11
13
|
static std::string previous;
|
|
12
14
|
static std::vector<double> timings;
|
|
13
|
-
static bool saveProof=false,verifyAtlas=false,proofSaved=false,atlasVerified=false;
|
|
15
|
+
static bool saveProof=false,verifyAtlas=false,verifyEveryPaint=false,proofSaved=false,atlasVerified=false;
|
|
16
|
+
static std::string proofPath="dist/native-memory/native-window-proof.png";
|
|
14
17
|
static bool production=false,diagnostic=false,testPicker=false,visible=false,inkExplicit=false;
|
|
15
18
|
static HWND followed=nullptr,picker=nullptr,windowList=nullptr;
|
|
16
19
|
static DWORD followedPid=0;
|
|
@@ -27,39 +30,15 @@ static Gdiplus::Color ink(){return inkName=="dark"?Gdiplus::Color(245,67,48,24):
|
|
|
27
30
|
static void updateStatus();static void chooseWindow();static void layoutControls();
|
|
28
31
|
static JSValue call(const char* name,int argc=0,JSValue* args=nullptr){JSValue global=JS_GetGlobalObject(context),fn=JS_GetPropertyStr(context,global,name),out=JS_Call(context,fn,global,argc,args);JS_FreeValue(context,fn);JS_FreeValue(context,global);if(JS_IsException(out)){js_std_dump_error(context);failed=true;if(target)PostMessage(target,WM_CLOSE,0,0);}return out;}
|
|
29
32
|
static void interact(const char* kind){JSValue arg=JS_NewString(context,kind),result=call("__desktopInteract",1,&arg);bool accepted=!JS_IsException(result)&&JS_ToBool(context,result);JS_FreeValue(context,result);JS_FreeValue(context,arg);lastKind=kind;lastAccepted=accepted;if(!production||diagnostic){printf("{\"interaction\":\"%s\",\"accepted\":%s}\n",kind,accepted?"true":"false");fflush(stdout);}previous.clear();updateStatus();}
|
|
30
|
-
|
|
31
|
-
HDC dc=nullptr;HBITMAP bitmap=nullptr;HGDIOBJ old=nullptr;uint8_t* bits=nullptr;int width=0,height=0;
|
|
32
|
-
~Surface(){release();}
|
|
33
|
-
void release(){if(dc){if(old)SelectObject(dc,old);if(bitmap)DeleteObject(bitmap);DeleteDC(dc);}dc=nullptr;bitmap=nullptr;old=nullptr;bits=nullptr;width=height=0;}
|
|
34
|
-
void ensure(int w,int h){if(w<=width&&h<=height)return;w=std::max(width,(w+63)/64*64);h=std::max(height,(h+63)/64*64);release();width=w;height=h;BITMAPINFO info{};info.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);info.bmiHeader.biWidth=w;info.bmiHeader.biHeight=-h;info.bmiHeader.biPlanes=1;info.bmiHeader.biBitCount=32;info.bmiHeader.biCompression=BI_RGB;HDC screen=GetDC(nullptr);dc=CreateCompatibleDC(screen);bitmap=CreateDIBSection(screen,&info,DIB_RGB_COLORS,(void**)&bits,nullptr,0);ReleaseDC(nullptr,screen);if(!bitmap||!dc)throw std::runtime_error("Native surface allocation");old=SelectObject(dc,bitmap);}
|
|
35
|
-
};
|
|
36
|
-
static Surface surface;
|
|
37
|
-
struct DotAtlas {
|
|
38
|
-
int cell=0;uint32_t color=0;std::vector<uint8_t> pixels;
|
|
39
|
-
void ensure(int value){if(cell==value&&color==ink().GetValue())return;cell=value;color=ink().GetValue();size_t length=(size_t)cell*cell*2*4;pixels.assign(length*256,0);static const int dx[]={0,0,0,1,1,1,0,1},dy[]={0,1,2,0,1,2,3,3};float step=cell/2.f,diameter=std::max(1.1f,step*.48f);
|
|
40
|
-
for(int mask=0;mask<256;mask++){Gdiplus::Bitmap bitmap(cell,cell*2,cell*4,PixelFormat32bppPARGB,pixels.data()+mask*length);Gdiplus::Graphics g(&bitmap);g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);Gdiplus::SolidBrush brush(ink());for(int bit=0;bit<8;bit++)if(mask&(1<<bit)){float x=(dx[bit]+.5f)*step,y=(dy[bit]+.5f)*step;g.FillEllipse(&brush,x-diameter/2,y-diameter/2,diameter,diameter);}}
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
static DotAtlas atlas;
|
|
44
|
-
static void paint(const std::string& text,int columns,int rows,int cell,POINT canvas){
|
|
45
|
-
std::vector<uint8_t> masks((size_t)columns*rows);size_t at=0;int left=columns,top=rows,right=-1,bottom=-1;
|
|
46
|
-
for(int y=0;y<rows;y++){if(y){if(at>=text.size()||text[at++]!='\n')throw std::runtime_error("Native row separator");}for(int x=0;x<columns;x++){if(at+3>text.size())throw std::runtime_error("Native text length");const auto* p=(const unsigned char*)text.data()+at;uint32_t ch=((p[0]&15)<<12)|((p[1]&63)<<6)|(p[2]&63);at+=3;if(ch<0x2800||ch>0x28ff)throw std::runtime_error("Native glyph");uint8_t mask=(uint8_t)(ch-0x2800);masks[y*columns+x]=mask;if(mask){left=std::min(left,x);right=std::max(right,x);top=std::min(top,y);bottom=std::max(bottom,y);}}}
|
|
47
|
-
if(at!=text.size())throw std::runtime_error("Native trailing text");if(right<left){ShowWindow(overlay,SW_HIDE);return;}
|
|
48
|
-
int w=(right-left+1)*cell,h=(bottom-top+1)*cell*2;if((int64_t)w*h>8000000){ShowWindow(overlay,SW_HIDE);visible=false;return;}surface.ensure(w,h);std::fill(surface.bits,surface.bits+(size_t)surface.width*surface.height*4,0);
|
|
49
|
-
atlas.ensure(cell);size_t stampSize=(size_t)cell*cell*2*4;
|
|
50
|
-
for(int y=top;y<=bottom;y++)for(int x=left;x<=right;x++){int mask=masks[y*columns+x];if(!mask)continue;const uint8_t* stamp=atlas.pixels.data()+mask*stampSize;for(int sy=0;sy<cell*2;sy++)memcpy(surface.bits+((size_t)((y-top)*cell*2+sy)*surface.width+(x-left)*cell)*4,stamp+(size_t)sy*cell*4,cell*4);}
|
|
51
|
-
if(verifyAtlas&&!atlasVerified){std::vector<uint8_t> reference((size_t)w*h*4,0);Gdiplus::Bitmap bitmap(w,h,w*4,PixelFormat32bppPARGB,reference.data());Gdiplus::Graphics g(&bitmap);g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);Gdiplus::SolidBrush ink(NativePet::ink());static const int dx[]={0,0,0,1,1,1,0,1},dy[]={0,1,2,0,1,2,3,3};float step=cell/2.f,diameter=std::max(1.1f,step*.48f);for(int y=top;y<=bottom;y++)for(int x=left;x<=right;x++){int mask=masks[y*columns+x];for(int bit=0;bit<8;bit++)if(mask&(1<<bit)){float px=(x-left)*cell+(dx[bit]+.5f)*step,py=(y-top)*cell*2+(dy[bit]+.5f)*step;g.FillEllipse(&ink,px-diameter/2,py-diameter/2,diameter,diameter);}}g.Flush(Gdiplus::FlushIntentionSync);for(int y=0;y<h;y++)if(memcmp(reference.data()+(size_t)y*w*4,surface.bits+(size_t)y*surface.width*4,w*4)!=0)throw std::runtime_error("Dot atlas changed pixels");atlasVerified=true;printf("{\"stage\":\"dot-atlas-verified\",\"pixelExact\":true}\n");fflush(stdout);}
|
|
52
|
-
if(saveProof&&!proofSaved){Gdiplus::Bitmap source(surface.width,surface.height,surface.width*4,PixelFormat32bppPARGB,surface.bits),proof(w,h,PixelFormat32bppARGB);Gdiplus::Graphics pg(&proof);pg.Clear(Gdiplus::Color(255,0,0,0));pg.DrawImage(&source,Gdiplus::Rect(0,0,w,h),0,0,w,h,Gdiplus::UnitPixel);pg.Flush(Gdiplus::FlushIntentionSync);CLSID png={0x557cf406,0x1a04,0x11d3,{0x9a,0x73,0x00,0x00,0xf8,0x1e,0xf3,0x2e}};if(proof.Save(L"dist/native-memory/native-window-proof.png",&png,nullptr)!=Gdiplus::Ok)throw std::runtime_error("PNG proof failed");proofSaved=true;}
|
|
53
|
-
POINT destination={canvas.x+left*cell,canvas.y+top*cell*2},origin={0,0};SIZE size={w,h};BLENDFUNCTION blend={AC_SRC_OVER,0,255,AC_SRC_ALPHA};HDC screen=GetDC(nullptr);BOOL result=UpdateLayeredWindow(overlay,screen,&destination,&size,surface.dc,&origin,0,&blend,ULW_ALPHA);ReleaseDC(nullptr,screen);if(!result)throw std::runtime_error("Native layered update");SetWindowPos(overlay,HWND_TOPMOST,destination.x,destination.y,w,h,SWP_NOACTIVATE|SWP_SHOWWINDOW);painted++;
|
|
54
|
-
}
|
|
33
|
+
#include "native-paint.hpp"
|
|
55
34
|
static void updateStatus(){
|
|
56
35
|
if(!production)return;
|
|
57
36
|
PROCESS_MEMORY_COUNTERS_EX memory{};GetProcessMemoryInfo(GetCurrentProcess(),(PROCESS_MEMORY_COUNTERS*)&memory,sizeof(memory));
|
|
58
|
-
std::ostringstream out;out<<"{\"running\":true,\"reachable\":true,\"engine\":\"quickjs-native\",\"pet\":\""<<petKind<<"\",\"ink\":\""<<inkName<<"\",\"returnStyle\":\""<<returnStyle<<"\",\"selected\":"<<(followed?"true":"false")<<",\"visible\":"<<(visible?"true":"false")<<",\"pickerVisible\":"<<(picker?"true":"false")<<",\"paused\":"<<(paused?"true":"false")<<",\"frames\":"<<frames<<",\"paintedFrames\":"<<painted<<",\"pid\":"<<GetCurrentProcessId()<<",\"painterPid\":"<<GetCurrentProcessId()<<",\"rssBytes\":"<<memory.WorkingSetSize<<",\"peakWorkingSetBytes\":"<<memory.PeakWorkingSetSize<<",\"privateBytes\":"<<memory.PrivateUsage<<",\"watch\":{\"source\":\"off\",\"state\":\"off\",\"pendingCount\":0},\"brain\":{\"provider\":\"off\",\"state\":\"off\"},\"lastInteraction\":";
|
|
37
|
+
std::ostringstream out;out<<"{\"running\":true,\"reachable\":true,\"engine\":\"quickjs-native\",\"pet\":\""<<petKind<<"\",\"ink\":\""<<inkName<<"\",\"returnStyle\":\""<<returnStyle<<"\",\"selected\":"<<(followed?"true":"false")<<",\"visible\":"<<(visible?"true":"false")<<",\"pickerVisible\":"<<(picker?"true":"false")<<",\"paused\":"<<(paused?"true":"false")<<",\"frames\":"<<frames<<",\"paintedFrames\":"<<painted<<",\"pid\":"<<GetCurrentProcessId()<<",\"painterPid\":"<<GetCurrentProcessId()<<",\"rssBytes\":"<<memory.WorkingSetSize<<",\"peakWorkingSetBytes\":"<<memory.PeakWorkingSetSize<<",\"privateBytes\":"<<memory.PrivateUsage<<",\"paintSurfaceBytes\":"<<paintBytes()<<",\"paintRegionCount\":"<<paintCount()<<",\"paintVerificationCount\":"<<verifiedPaints<<",\"watch\":{\"source\":\"off\",\"state\":\"off\",\"pendingCount\":0},\"brain\":{\"provider\":\"off\",\"state\":\"off\"},\"lastInteraction\":";
|
|
59
38
|
if(lastKind.empty())out<<"null";else out<<"{\"kind\":\""<<lastKind<<"\",\"accepted\":"<<(lastAccepted?"true":"false")<<"}";out<<"}";control.update(out.str());
|
|
60
39
|
}
|
|
61
40
|
static void switchPet(const std::string& next){
|
|
62
|
-
if(next==petKind)return;JSValue arg=JS_NewString(context,next.c_str()),result=call("__desktopSwitchPet",1,&arg);bool ok=!JS_IsException(result)&&JS_ToBool(context,result);JS_FreeValue(context,arg);JS_FreeValue(context,result);if(!ok)return;petKind=next;if(!inkExplicit)inkName=next=="cat"?"gold":"cream";previous.clear();
|
|
41
|
+
if(next==petKind)return;JSValue arg=JS_NewString(context,next.c_str()),result=call("__desktopSwitchPet",1,&arg);bool ok=!JS_IsException(result)&&JS_ToBool(context,result);JS_FreeValue(context,arg);JS_FreeValue(context,result);if(!ok)return;petKind=next;if(!inkExplicit)inkName=next=="cat"?"gold":"cream";previous.clear();releasePanels();lastCanvas={INT_MIN,INT_MIN};layoutControls();updateStatus();
|
|
63
42
|
}
|
|
64
43
|
static BOOL CALLBACK enumerate(HWND window,LPARAM){
|
|
65
44
|
if(!IsWindowVisible(window)||GetWindow(window,GW_OWNER))return TRUE;DWORD pid=0;GetWindowThreadProcessId(window,&pid);if(!pid||pid==GetCurrentProcessId())return TRUE;wchar_t title[512];if(!GetWindowText(window,title,512))return TRUE;RECT rect;if(!GetClientRect(window,&rect)||rect.right<160||rect.bottom<120)return TRUE;LRESULT index=SendMessage(windowList,LB_ADDSTRING,0,(LPARAM)title);if(index!=LB_ERR&&index!=LB_ERRSPACE){SendMessage(windowList,LB_SETITEMDATA,index,(LPARAM)choices.size());choices.emplace_back(window,pid);}return TRUE;
|
|
@@ -73,7 +52,7 @@ static LRESULT CALLBACK pickerProcedure(HWND window,UINT message,WPARAM w,LPARAM
|
|
|
73
52
|
if(message==WM_DESTROY){if(picker==window)picker=nullptr;if(!followed&&target)PostMessage(target,WM_CLOSE,0,0);updateStatus();return 0;}return DefWindowProc(window,message,w,l);
|
|
74
53
|
}
|
|
75
54
|
static void chooseWindow(){
|
|
76
|
-
if(!production||(diagnostic&&!testPicker))return;if(picker){SetForegroundWindow(picker);return;}visible=false;
|
|
55
|
+
if(!production||(diagnostic&&!testPicker))return;if(picker){SetForegroundWindow(picker);return;}visible=false;hidePanels();ShowWindow(target,SW_HIDE);previous.clear();WNDCLASS wc{};wc.lpfnWndProc=pickerProcedure;wc.hInstance=GetModuleHandle(nullptr);wc.lpszClassName=L"RetrieviewerNativePicker";wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);RegisterClass(&wc);
|
|
77
56
|
picker=CreateWindowEx(WS_EX_TOOLWINDOW,wc.lpszClassName,L"Retrieviewer - choose an app window",WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU, std::max(0,(GetSystemMetrics(SM_CXSCREEN)-650)/2),std::max(0,(GetSystemMetrics(SM_CYSCREEN)-450)/2),650,450,nullptr,nullptr,wc.hInstance,nullptr);if(!picker)throw std::runtime_error("Cannot open window picker");
|
|
78
57
|
addPetCombo(picker);CreateWindow(L"STATIC",L"Choose the app window your companion should follow.",WS_CHILD|WS_VISIBLE,136,12,490,24,picker,nullptr,wc.hInstance,nullptr);windowList=CreateWindowEx(WS_EX_CLIENTEDGE,L"LISTBOX",L"",WS_CHILD|WS_VISIBLE|WS_VSCROLL|LBS_NOTIFY,12,44,610,310,picker,(HMENU)100,wc.hInstance,nullptr);CreateWindow(L"BUTTON",L"Attach",WS_CHILD|WS_VISIBLE|BS_DEFPUSHBUTTON,434,367,90,30,picker,(HMENU)101,wc.hInstance,nullptr);CreateWindow(L"BUTTON",L"Refresh",WS_CHILD|WS_VISIBLE,532,367,90,30,picker,(HMENU)102,wc.hInstance,nullptr);choices.clear();EnumWindows(enumerate,0);ShowWindow(picker,SW_SHOW);SetWindowPos(picker,HWND_TOP,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW);SetForegroundWindow(picker);updateStatus();
|
|
79
58
|
}
|
|
@@ -91,9 +70,9 @@ static void tick(){
|
|
|
91
70
|
DWORD scenePid=0;if(scene)GetWindowThreadProcessId(scene,&scenePid);
|
|
92
71
|
if(production&&(!diagnostic||testPicker)&&scene&&(!IsWindow(scene)||scenePid!=followedPid)){followed=nullptr;chooseWindow();scene=nullptr;}
|
|
93
72
|
if(picker||!scene||IsIconic(scene)||(production&&(!diagnostic||testPicker)&&GetForegroundWindow()!=scene&&GetForegroundWindow()!=target)||(showing&&GetForegroundWindow()!=target)){
|
|
94
|
-
visible=false;
|
|
73
|
+
visible=false;hidePanels();if(production&&(!diagnostic||testPicker))ShowWindow(target,SW_HIDE);previous.clear();updateStatus();return;
|
|
95
74
|
}
|
|
96
|
-
RECT rect;GetClientRect(scene,&rect);POINT canvas{0,0};ClientToScreen(scene,&canvas);int cell=8,columns=rect.right/cell,rows=rect.bottom/(cell*2);while(columns*rows>125000&&cell<32){cell+=2;columns=rect.right/cell;rows=rect.bottom/(cell*2);}if(columns<8||rows<4||columns*rows>125000){visible=false;
|
|
75
|
+
RECT rect;GetClientRect(scene,&rect);POINT canvas{0,0};ClientToScreen(scene,&canvas);int cell=8,columns=rect.right/cell,rows=rect.bottom/(cell*2);while(columns*rows>125000&&cell<32){cell+=2;columns=rect.right/cell;rows=rect.bottom/(cell*2);}if(columns<8||rows<4||columns*rows>125000){visible=false;hidePanels();previous.clear();updateStatus();return;}
|
|
97
76
|
visible=true;if(canvas.x!=lastCanvas.x||canvas.y!=lastCanvas.y||rect.right!=lastWidth||rect.bottom!=lastHeight){previous.clear();lastCanvas=canvas;lastWidth=rect.right;lastHeight=rect.bottom;}
|
|
98
77
|
if(production&&(!diagnostic||testPicker)){RECT bar;GetWindowRect(target,&bar);int x=canvas.x+std::max<LONG>(0,rect.right-(bar.right-bar.left)),y=canvas.y+8;if(bar.left!=x||bar.top!=y||!IsWindowVisible(target))SetWindowPos(target,HWND_TOPMOST,x,y,0,0,SWP_NOSIZE|SWP_NOACTIVATE|SWP_SHOWWINDOW);}
|
|
99
78
|
if(paused){updateStatus();return;}
|
|
@@ -105,24 +84,24 @@ static LRESULT CALLBACK procedure(HWND window,UINT message,WPARAM w,LPARAM l){
|
|
|
105
84
|
if(message==WM_APP+1){if(w==1||w==2)switchPet(w==2?"cat":"dog");return 0;}
|
|
106
85
|
if(message==WM_COMMAND){int id=LOWORD(w);if(id==9&&HIWORD(w)==CBN_SELCHANGE)switchPet(SendMessage((HWND)l,CB_GETCURSEL,0,0)==1?"cat":"dog");else if(id==1)interact(petKind=="cat"?"greet":"ball");else if(id==2)interact("treat");else if(id==3)interact(petKind=="cat"?"company":"tug");else if(id==4){paused=!paused;previous.clear();layoutControls();updateStatus();}else if(id==5){if(production)chooseWindow();else interact("paw");}else if(id==6)interact(production?"tower":"wall");else if(id==8)interact("wheel");else if(id==7)DestroyWindow(window);return 0;}
|
|
107
86
|
if(message==WM_SIZE||message==WM_MOVE){previous.clear();return 0;}
|
|
108
|
-
if(message==WM_DESTROY){KillTimer(window,1);followed=nullptr;if(picker){HWND old=picker;picker=nullptr;SetWindowLongPtr(old,GWLP_WNDPROC,(LONG_PTR)DefWindowProc);DestroyWindow(old);}
|
|
87
|
+
if(message==WM_DESTROY){KillTimer(window,1);followed=nullptr;if(picker){HWND old=picker;picker=nullptr;SetWindowLongPtr(old,GWLP_WNDPROC,(LONG_PTR)DefWindowProc);DestroyWindow(old);}releasePanels();PostQuitMessage(0);return 0;}
|
|
109
88
|
return DefWindowProc(window,message,w,l);
|
|
110
89
|
}
|
|
111
90
|
static int run(JSContext* ctx,int argc,char** argv){
|
|
112
|
-
context=ctx;showing=false;durationMs=150000;for(int i=1;i<argc;i++){std::string arg=argv[i];if(arg=="--production"){production=true;durationMs=0;}if(arg=="--show"){showing=true;durationMs=0;}if(arg=="--proof")saveProof=true;if(arg=="--verify-atlas")verifyAtlas=true;if(arg.rfind("--duration=",0)==0)durationMs=(ULONGLONG)(std::stod(arg.substr(11))*1000);}
|
|
91
|
+
context=ctx;showing=false;durationMs=150000;for(int i=1;i<argc;i++){std::string arg=argv[i];if(arg=="--production"){production=true;durationMs=0;}if(arg=="--show"){showing=true;durationMs=0;}if(arg=="--proof")saveProof=true;if(arg=="--verify-atlas")verifyAtlas=true;if(arg=="--verify-regions"){verifyAtlas=true;verifyEveryPaint=true;}if(arg.rfind("--proof=",0)==0){saveProof=true;proofPath=arg.substr(8);}if(arg.rfind("--duration=",0)==0)durationMs=(ULONGLONG)(std::stod(arg.substr(11))*1000);}
|
|
113
92
|
JSValue global=JS_GetGlobalObject(ctx),config=JS_GetPropertyStr(ctx,global,"__nativeConfig");if(JS_IsObject(config)){petKind=propertyString(ctx,config,"pet","dog");inkName=propertyString(ctx,config,"ink",petKind=="cat"?"gold":"cream");returnStyle=propertyString(ctx,config,"returnStyle","auto");inkExplicit=propertyBool(ctx,config,"inkExplicit");diagnostic=propertyBool(ctx,config,"diagnostic");testPicker=diagnostic&&propertyBool(ctx,config,"testPicker");}if(petKind!="dog"&&petKind!="cat")throw std::runtime_error("Invalid native pet");if(inkName!="cream"&&inkName!="gold"&&inkName!="dark")throw std::runtime_error("Invalid native ink");
|
|
114
93
|
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);Gdiplus::GdiplusStartupInput gdip;ULONG_PTR token;if(Gdiplus::GdiplusStartup(&token,&gdip,nullptr)!=Gdiplus::Ok)throw std::runtime_error("GDI+ startup");
|
|
115
94
|
if(production){std::wstring name=wide("Local\\BiteFoundry.Retrieviewer.Overlay."+userSid()+(diagnostic?".Diagnostics."+std::to_string(GetCurrentProcessId()):""));instanceMutex=CreateMutexW(nullptr,TRUE,name.c_str());if(!instanceMutex||GetLastError()==ERROR_ALREADY_EXISTS)throw std::runtime_error("A Retrieviewer companion is already running");}
|
|
116
95
|
HINSTANCE instance=GetModuleHandle(nullptr);WNDCLASS wc{};wc.lpfnWndProc=procedure;wc.hInstance=instance;wc.lpszClassName=L"RetrieviewerNativeHost";wc.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);RegisterClass(&wc);
|
|
117
96
|
const bool attach=production&&(!diagnostic||testPicker);target=CreateWindowEx(WS_EX_TOOLWINDOW|(attach?WS_EX_NOACTIVATE:0),wc.lpszClassName,attach?L"Retrieviewer controls":L"Retrieviewer diagnostic scene",showing?WS_OVERLAPPEDWINDOW:WS_POPUP,showing?80:-12000,showing?80:-12000,attach?552:showing?1280:3840,attach?40:showing?800:2160,nullptr,nullptr,instance,nullptr);if(!target)throw std::runtime_error("Native host window");if(!attach)followed=target;
|
|
118
|
-
WNDCLASS pet{};pet.lpfnWndProc=DefWindowProc;pet.hInstance=instance;pet.lpszClassName=L"RetrieviewerNativeDots";RegisterClass(&pet);
|
|
97
|
+
WNDCLASS pet{};pet.lpfnWndProc=DefWindowProc;pet.hInstance=instance;pet.lpszClassName=L"RetrieviewerNativeDots";RegisterClass(&pet);
|
|
119
98
|
if(attach){addPetCombo(target);for(int id=1;id<=8;id++)CreateWindow(L"BUTTON",L"",WS_CHILD|BS_PUSHBUTTON,0,0,1,1,target,(HMENU)(INT_PTR)id,instance,nullptr);layoutControls();chooseWindow();}
|
|
120
99
|
else if(showing){const wchar_t* names[]={L"Ball",L"Treat",L"Tug",L"Pause",L"Paw",L"Wall",L"Quit"};for(int i=0;i<7;i++)CreateWindow(L"BUTTON",names[i],WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,8+i*70,8,66,28,target,(HMENU)(INT_PTR)(i+1),instance,nullptr);}
|
|
121
100
|
if(!attach)ShowWindow(target,showing?SW_SHOW:SW_SHOWNOACTIVATE);startTime=GetTickCount64();lastTick=0;timings.reserve(1024);SetTimer(target,1,33,nullptr);
|
|
122
101
|
if(production){updateStatus();control.start(propertyString(ctx,config,"pipe"),propertyString(ctx,config,"token"),propertyString(ctx,config,"sessionFile"),[](const std::string& command){PostMessage(target,command=="stop"?WM_CLOSE:WM_APP+1,command=="pet-cat"?2:command=="pet-dog"?1:0,0);});printf("{\"ready\":true,\"pid\":%lu}\n",GetCurrentProcessId());fflush(stdout);startupGuard=std::thread([]{std::string ack;bool ok=std::getline(std::cin,ack)&&ack=="DETACH";if(ok){detached=true;printf("{\"detached\":true}\n");fflush(stdout);}else{DWORD pid=0;GetWindowThreadProcessId(target,&pid);if(pid==GetCurrentProcessId())PostMessage(target,WM_CLOSE,0,0);}});}
|
|
123
102
|
else{detached=true;printf("{\"stage\":\"native-window\",\"width\":%d,\"height\":%d,\"durationMs\":%llu,\"pid\":%lu}\n",showing?1280:3840,showing?800:2160,durationMs,GetCurrentProcessId());fflush(stdout);}
|
|
124
103
|
JS_FreeValue(ctx,config);JS_FreeValue(ctx,global);MSG msg;while(GetMessage(&msg,nullptr,0,0)>0){TranslateMessage(&msg);DispatchMessage(&msg);}
|
|
125
|
-
if(startupGuard.joinable()){CancelSynchronousIo(startupGuard.native_handle());startupGuard.join();}control.close();JSValue result=call("__desktopDispose");JS_FreeValue(ctx,result);
|
|
104
|
+
if(startupGuard.joinable()){CancelSynchronousIo(startupGuard.native_handle());startupGuard.join();}control.close();JSValue result=call("__desktopDispose");JS_FreeValue(ctx,result);releasePanels();if(instanceMutex){ReleaseMutex(instanceMutex);CloseHandle(instanceMutex);instanceMutex=nullptr;}Gdiplus::GdiplusShutdown(token);
|
|
126
105
|
std::sort(timings.begin(),timings.end());double sum=0;for(auto t:timings)sum+=t;if(!production||diagnostic){printf("{\"stage\":\"native-exit\",\"painted\":%d,\"renders\":%llu,\"meanMs\":%.3f,\"p95Ms\":%.3f,\"maxMs\":%.3f,\"failed\":%s}\n",painted,frames,timings.empty()?0:sum/timings.size(),timings.empty()?0:timings[(size_t)(timings.size()*.95)],timings.empty()?0:timings.back(),failed?"true":"false");fflush(stdout);}return failed?1:0;
|
|
127
106
|
}
|
|
128
107
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <algorithm>
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
#include <limits>
|
|
5
|
+
#include <stdexcept>
|
|
6
|
+
#include <vector>
|
|
7
|
+
|
|
8
|
+
// One final-mask crop and one layered-window upload per scene. Separate HWNDs
|
|
9
|
+
// cannot present a repartitioned frame atomically, even with identical pixels.
|
|
10
|
+
namespace PetPaint {
|
|
11
|
+
constexpr size_t MaxRegions=1;
|
|
12
|
+
struct Rect {
|
|
13
|
+
int left=std::numeric_limits<int>::max(),top=std::numeric_limits<int>::max();
|
|
14
|
+
int right=0,bottom=0;
|
|
15
|
+
bool empty() const {return left>=right||top>=bottom;}
|
|
16
|
+
int width() const {return empty()?0:right-left;}
|
|
17
|
+
int height() const {return empty()?0:bottom-top;}
|
|
18
|
+
void include(int x,int y){left=std::min(left,x);top=std::min(top,y);right=std::max(right,x+1);bottom=std::max(bottom,y+1);}
|
|
19
|
+
void include(const Rect& r){if(!r.empty()){left=std::min(left,r.left);top=std::min(top,r.top);right=std::max(right,r.right);bottom=std::max(bottom,r.bottom);}}
|
|
20
|
+
};
|
|
21
|
+
inline int rounded(int n){return (n+63)/64*64;}
|
|
22
|
+
inline uint64_t bytes(const Rect& r,int cell){return r.empty()?0:(uint64_t)rounded(r.width()*cell)*rounded(r.height()*cell*2)*4;}
|
|
23
|
+
inline std::vector<Rect> partition(const std::vector<uint8_t>& masks,int columns,int rows,int cell){
|
|
24
|
+
if(columns<1||rows<1||(uint64_t)columns*rows>125000||masks.size()!=(size_t)columns*rows||cell<6||cell>32)throw std::runtime_error("Invalid paint mask grid");
|
|
25
|
+
Rect all;for(int y=0;y<rows;y++)for(int x=0;x<columns;x++)if(masks[(size_t)y*columns+x])all.include(x,y);
|
|
26
|
+
return all.empty()?std::vector<Rect>{}:std::vector<Rect>{all};
|
|
27
|
+
}
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitefoundry/retrieviewer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"description": "A fluffy Braille desktop pet for Windows and Linux X11, with experimental macOS support and an embeddable TUI engine.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"homepage": "https://bitefoundry.github.io/retrieviewer/",
|
|
@@ -197,7 +197,10 @@
|
|
|
197
197
|
"integrations/purrviewer/vendor/retrieviewer/puppy-frame-store.mjs",
|
|
198
198
|
"integrations/purrviewer/vendor/retrieviewer/puppy-frame-file.mjs",
|
|
199
199
|
"integrations/purrviewer/vendor/retrieviewer/puppy-frame-indexed.mjs",
|
|
200
|
-
"integrations/native-windows/application.manifest"
|
|
200
|
+
"integrations/native-windows/application.manifest",
|
|
201
|
+
"integrations/native-windows/paint-regions.hpp",
|
|
202
|
+
"integrations/native-windows/native-paint.hpp",
|
|
203
|
+
"SPARSE-PAINT.md"
|
|
201
204
|
],
|
|
202
205
|
"scripts": {
|
|
203
206
|
"bake:motion": "node tools/bake-pet-motion.mjs",
|