44
55 Captures the Studio viewport using CaptureService, loads the result into
66 an EditableImage to read raw RGBA pixels, encodes as PNG via png-luau,
7- base64-encodes the PNG, and sends it over the WebSocket.
7+ base64-encodes the PNG via EncodingService , and sends it over the WebSocket.
88
99 Protocol:
1010 Request: { type: "captureScreenshot", payload: { format?: "png" } }
@@ -18,70 +18,6 @@ local _disposed = false
1818
1919local CaptureScreenshotAction = {}
2020
21- -- ---------------------------------------------------------------------------
22- -- Base64 encoder for buffer data
23- -- ---------------------------------------------------------------------------
24-
25- -- Pre-compute lookup: index 0..63 → ASCII byte
26- local ENCODE_LUT : buffer = buffer.create (64 )
27- do
28- local chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
29- for i = 0 , 63 do
30- buffer.writeu8 (ENCODE_LUT , i , string.byte (chars , i + 1 ))
31- end
32- end
33-
34- -- Encode a buffer as a base64 string. Uses buffer I/O throughout for speed.
35- local function base64Encode (buf : buffer ): string
36- local len = buffer.len (buf )
37- local outLen = math.ceil (len / 3 ) * 4
38- local out = buffer.create (outLen )
39- local outIdx = 0
40-
41- -- Localize for tight loop performance
42- local readu8 = buffer.readu8
43- local writeu8 = buffer.writeu8
44- local lut = ENCODE_LUT
45- local rshift = bit32.rshift
46- local band = bit32.band
47-
48- -- Process full 3-byte groups
49- local fullLen = len - (len % 3 )
50- for i = 0 , fullLen - 1 , 3 do
51- local b0 = readu8 (buf , i )
52- local b1 = readu8 (buf , i + 1 )
53- local b2 = readu8 (buf , i + 2 )
54- local n = b0 * 65536 + b1 * 256 + b2
55-
56- writeu8 (out , outIdx , readu8 (lut , rshift (n , 18 )))
57- writeu8 (out , outIdx + 1 , readu8 (lut , band (rshift (n , 12 ), 63 )))
58- writeu8 (out , outIdx + 2 , readu8 (lut , band (rshift (n , 6 ), 63 )))
59- writeu8 (out , outIdx + 3 , readu8 (lut , band (n , 63 )))
60- outIdx += 4
61- end
62-
63- -- Handle remaining 1 or 2 bytes
64- local rem = len % 3
65- if rem == 1 then
66- local b0 = readu8 (buf , fullLen )
67- local n = b0 * 65536
68- writeu8 (out , outIdx , readu8 (lut , rshift (n , 18 )))
69- writeu8 (out , outIdx + 1 , readu8 (lut , band (rshift (n , 12 ), 63 )))
70- writeu8 (out , outIdx + 2 , 61 ) -- '='
71- writeu8 (out , outIdx + 3 , 61 ) -- '='
72- elseif rem == 2 then
73- local b0 = readu8 (buf , fullLen )
74- local b1 = readu8 (buf , fullLen + 1 )
75- local n = b0 * 65536 + b1 * 256
76- writeu8 (out , outIdx , readu8 (lut , rshift (n , 18 )))
77- writeu8 (out , outIdx + 1 , readu8 (lut , band (rshift (n , 12 ), 63 )))
78- writeu8 (out , outIdx + 2 , readu8 (lut , band (rshift (n , 6 ), 63 )))
79- writeu8 (out , outIdx + 3 , 61 ) -- '='
80- end
81-
82- return buffer.tostring (out )
83- end
84-
8521-- ---------------------------------------------------------------------------
8622-- Helpers
8723-- ---------------------------------------------------------------------------
11652
11753local function captureAsync (sendMessage : (msg : { [string ]: any }) -> (), requestId : string ? , sessionId : string )
11854 -- Resolve services (deferred so the module loads in Lune test context)
119- local getServiceOk , captureServiceOrErr , assetServiceOrErr = pcall (function ()
120- return game :GetService ("CaptureService" ), game :GetService ("AssetService" )
55+ local getServiceOk , captureServiceOrErr , assetServiceOrErr , encodingServiceOrErr = pcall (function ()
56+ return game :GetService ("CaptureService" ), game :GetService ("AssetService" ), game : GetService ( "EncodingService" )
12157 end )
12258 if not getServiceOk then
12359 sendMessage (
@@ -127,6 +63,7 @@ local function captureAsync(sendMessage: (msg: { [string]: any }) -> (), request
12763 end
12864 local CaptureService = captureServiceOrErr
12965 local AssetService = assetServiceOrErr
66+ local EncodingService = encodingServiceOrErr
13067
13168 -- Step 1: Capture the viewport via CaptureService
13269 local callerThread = coroutine.running ()
@@ -246,7 +183,9 @@ local function captureAsync(sendMessage: (msg: { [string]: any }) -> (), request
246183 end
247184 end
248185
249- local encodeOk , encoded = pcall (base64Encode , dataBuf )
186+ local encodeOk , encoded = pcall (function ()
187+ return EncodingService :Base64Encode (dataBuf )
188+ end )
250189 if not encodeOk or not encoded then
251190 sendMessage (
252191 buildError (
0 commit comments