Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitca867ce

Browse files
authored
Mergec92e506 into2f9e80e
2 parents2f9e80e +c92e506 commitca867ce

File tree

3 files changed

+237
-27
lines changed

3 files changed

+237
-27
lines changed

‎libs/zzz/disc-scanner/src/lib/consts.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,9 @@ export const blackColor: Color = {
1212
g:0,
1313
b:0,
1414
}
15+
16+
exportconstgreyBorderColor:Color={
17+
r:55,
18+
g:55,
19+
b:55,
20+
}

‎libs/zzz/disc-scanner/src/lib/processImg.ts‎

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import type { DiscSlotKey } from '@genshin-optimizer/zzz/consts'
1717
import{discSlotToMainStatKeys}from'@genshin-optimizer/zzz/consts'
1818
importtype{IDisc}from'@genshin-optimizer/zzz/zood'
1919
importtype{ReactNode}from'react'
20-
import{blackColor}from'./consts'
20+
import{blackColor,greyBorderColor}from'./consts'
2121
import{statMapEngMap}from'./enStringMap'
2222
import{
2323
parseLvlRarity,
@@ -171,32 +171,47 @@ function cropDiscCard(
171171
imageData:ImageData,
172172
debugImgs?:Record<string,string>
173173
){
174+
// Check if the image is within 90% of 16:9 ratio and crop to right 1/3
175+
constaspectRatio=imageData.width/imageData.height
176+
consttargetRatio=16/9
177+
constratioTolerance=0.1// 10% tolerance
178+
constisNear16to9=
179+
Math.abs(aspectRatio-targetRatio)<=targetRatio*ratioTolerance
180+
181+
letprocessedImageData=imageData
182+
if(isNear16to9){
183+
// Crop to keep only the right 1/3 of the image
184+
constsourceX=Math.floor((imageData.width*2)/3)// Start from 2/3 of the width
185+
constsourceWidth=Math.floor(imageData.width/3)// Take only 1/3 of the width
186+
187+
// Use the existing crop function
188+
processedImageData=crop(imageDataToCanvas(imageData),{
189+
x1:sourceX,
190+
x2:sourceX+sourceWidth,
191+
y1:0,
192+
y2:imageData.height,
193+
})
194+
}
195+
174196
consthistogram=histogramContAnalysis(
175-
imageData,
176-
darkerColor(blackColor),
177-
lighterColor(blackColor)
197+
processedImageData,
198+
darkerColor(greyBorderColor,20),
199+
lighterColor(greyBorderColor,20)
178200
)
179-
letskipCrop=imageData.width<500
180-
leta=0
181-
letb=imageData.width
182-
if(!skipCrop){
183-
// look for the black line outside the card outline. This will likely be only a pixel wide
184-
// eslint-disable-next-line @typescript-eslint/no-extra-semi
185-
;[a,b]=findHistogramRange(histogram,0.9,1)
186-
}
201+
202+
// look for the grey outline of the card.
203+
// eslint-disable-next-line @typescript-eslint/no-extra-semi
204+
let[a,b]=findHistogramRange(histogram,0.3,4)
187205

188206
if(b-a<100){
189-
skipCrop=true
190207
a=0
191-
b=imageData.width
208+
b=processedImageData.width
192209
}
193210

194-
constcropped=skipCrop
195-
?imageData
196-
:crop(imageDataToCanvas(imageData),{x1:a,x2:b})
211+
constcropped=crop(imageDataToCanvas(processedImageData),{x1:a,x2:b})
197212

198213
if(debugImgs){
199-
constcanvas=imageDataToCanvas(imageData)
214+
constcanvas=imageDataToCanvas(processedImageData)
200215

201216
drawHistogram(canvas,histogram,{
202217
r:255,
@@ -215,17 +230,12 @@ function cropDiscCard(
215230
lighterColor(blackColor),
216231
false
217232
)
218-
letbot=0
219-
lettop=cropped.height
233+
220234
// look for the black line outside the card outline. This will likely be only a pixel wide
221-
if(!skipCrop){
222-
// eslint-disable-next-line @typescript-eslint/no-extra-semi
223-
;[bot,top]=findHistogramRange(horihistogram,0.9,1)
224-
}
235+
// eslint-disable-next-line @typescript-eslint/no-extra-semi
236+
const[bot,top]=findHistogramRange(horihistogram,0.9,1)
225237

226-
constcropped2=skipCrop
227-
?cropped
228-
:crop(imageDataToCanvas(cropped),{y1:bot,y2:top})
238+
constcropped2=crop(imageDataToCanvas(cropped),{y1:bot,y2:top})
229239

230240
if(debugImgs){
231241
constcanvas=imageDataToCanvas(cropped)

‎libs/zzz/ui/src/Disc/DiscEditor/DiscEditor.tsx‎

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import LockIcon from '@mui/icons-material/Lock'
3434
importLockOpenIconfrom'@mui/icons-material/LockOpen'
3535
importPhotoCameraIconfrom'@mui/icons-material/PhotoCamera'
3636
importReplayIconfrom'@mui/icons-material/Replay'
37+
importScreenShareIconfrom'@mui/icons-material/ScreenShare'
38+
importStopIconfrom'@mui/icons-material/Stop'
3739
importUpdateIconfrom'@mui/icons-material/Update'
3840
import{
3941
Alert,
@@ -235,6 +237,15 @@ export function DiscEditor({
235237
undefinedasundefined|Omit<Processed,'disc'>
236238
)
237239

240+
// Screen capture state
241+
const[captureStream,setCaptureStream]=useState<MediaStream|null>(null)
242+
const[captureInterval,setCaptureInterval]=useState<NodeJS.Timeout|null>(
243+
null
244+
)
245+
246+
// Use captureStream existence as isCapturing indicator
247+
constisCapturing=!!captureStream
248+
238249
const{ fileName, imageURL, debugImgs, texts}=scannedData??{}
239250
constqueueTotal=processedNum+outstandingNum+scanningNum
240251

@@ -250,6 +261,150 @@ export function DiscEditor({
250261
queue.clearQueue()
251262
},[queue])
252263

264+
// Screen capture functions
265+
constcaptureScreenshot=useCallback(
266+
(stream:MediaStream)=>{
267+
try{
268+
// Create video element to capture frame
269+
constvideo=document.createElement('video')
270+
video.srcObject=stream
271+
video.muted=true
272+
video.playsInline=true
273+
274+
consthandleLoadedMetadata=()=>{
275+
// Create canvas to capture the frame
276+
constcanvas=document.createElement('canvas')
277+
constctx=canvas.getContext('2d')
278+
279+
if(!ctx){
280+
video.removeEventListener('loadedmetadata',handleLoadedMetadata)
281+
return
282+
}
283+
284+
constoriginalWidth=video.videoWidth
285+
constoriginalHeight=video.videoHeight
286+
287+
// Check if the capture is within 90% of 16:9 ratio
288+
constaspectRatio=originalWidth/originalHeight
289+
consttargetRatio=16/9
290+
constratioTolerance=0.1// 10% tolerance
291+
constisNear16to9=
292+
Math.abs(aspectRatio-targetRatio)<=targetRatio*ratioTolerance
293+
294+
letcanvasWidth=originalWidth
295+
letcanvasHeight=originalHeight
296+
letsourceX=0
297+
constsourceY=0
298+
letsourceWidth=originalWidth
299+
constsourceHeight=originalHeight
300+
301+
// If it's close to 16:9 ratio, crop to keep only the right 1/3
302+
if(isNear16to9){
303+
sourceX=Math.floor((originalWidth*2)/3)// Start from 2/3 of the width
304+
sourceWidth=Math.floor(originalWidth/3)// Take only 1/3 of the width
305+
canvasWidth=sourceWidth
306+
canvasHeight=originalHeight
307+
}
308+
309+
canvas.width=canvasWidth
310+
canvas.height=canvasHeight
311+
312+
// Draw the video frame to canvas with cropping
313+
ctx.drawImage(
314+
video,
315+
sourceX,
316+
sourceY,
317+
sourceWidth,
318+
sourceHeight,
319+
0,
320+
0,
321+
canvasWidth,
322+
canvasHeight
323+
)
324+
325+
// Convert canvas to blob and create file
326+
canvas.toBlob((blob)=>{
327+
if(blob){
328+
consttimestamp=newDate().toISOString().replace(/[:.]/g,'-')
329+
constfile=newFile([blob],`screen-capture-${timestamp}.png`,{
330+
type:'image/png',
331+
})
332+
333+
// Add to scanning queue
334+
queue.addFiles([{f:file,fName:file.name}])
335+
}
336+
},'image/png')
337+
338+
// Clean up
339+
video.removeEventListener('loadedmetadata',handleLoadedMetadata)
340+
video.srcObject=null
341+
}
342+
343+
video.addEventListener('loadedmetadata',handleLoadedMetadata)
344+
video.play().catch(console.error)
345+
}catch(error){
346+
console.error('Failed to capture screenshot:',error)
347+
}
348+
},
349+
[queue]
350+
)
351+
352+
conststopScreenCapture=useCallback(()=>{
353+
if(captureInterval){
354+
clearInterval(captureInterval)
355+
setCaptureInterval(null)
356+
}
357+
358+
if(captureStream){
359+
captureStream.getTracks().forEach((track)=>track.stop())
360+
setCaptureStream(null)
361+
}
362+
},[captureInterval,captureStream])
363+
364+
conststartScreenCapture=async()=>{
365+
try{
366+
// Check if getDisplayMedia is supported
367+
if(!navigator.mediaDevices||!navigator.mediaDevices.getDisplayMedia){
368+
alert(
369+
'Screen capture is not supported in this browser. Please use a modern browser like Chrome, Firefox, or Edge.'
370+
)
371+
return
372+
}
373+
374+
// Request screen capture permission
375+
conststream=awaitnavigator.mediaDevices.getDisplayMedia({
376+
video:true,
377+
})
378+
379+
setCaptureStream(stream)
380+
onShow()
381+
382+
// Set up interval to capture screenshots every 5 second
383+
constinterval=setInterval(()=>{
384+
if(processedNum||outstandingNum||scanningNum||scannedData)return
385+
captureScreenshot(stream)
386+
},5000)
387+
388+
setCaptureInterval(interval)
389+
390+
// Handle stream end (user stops sharing)
391+
stream.getVideoTracks()[0].addEventListener('ended',()=>{
392+
stopScreenCapture()
393+
})
394+
}catch(error){
395+
console.error('Failed to start screen capture:',error)
396+
if(errorinstanceofError&&error.name==='NotAllowedError'){
397+
alert(
398+
'Screen capture permission was denied. Please allow screen sharing to use this feature.'
399+
)
400+
}else{
401+
alert(
402+
'Failed to start screen capture. Please ensure you grant permission to share your screen.'
403+
)
404+
}
405+
}
406+
}
407+
253408
constonUpload=useCallback(
254409
(e:ChangeEvent<HTMLInputElement>)=>{
255410
if(!e.target)return
@@ -269,6 +424,16 @@ export function DiscEditor({
269424
setDisc((scannedDisc??{})asPartial<ICachedDisc>)
270425
},[queue,processedNum,scannedData,setDisc])
271426

427+
// Auto-reset when duplicate is detected during capture mode
428+
if(
429+
isCapturing&&
430+
prevEditType==='duplicate'&&
431+
disc&&
432+
Object.keys(disc).length>0
433+
){
434+
reset()
435+
}
436+
272437
useEffect(()=>{
273438
constpasteFunc=(e:Event)=>{
274439
// Don't handle paste if targetting the edit team modal
@@ -297,6 +462,13 @@ export function DiscEditor({
297462
}
298463
},[queue])
299464

465+
// Cleanup screen capture on unmount
466+
useEffect(()=>{
467+
return()=>{
468+
stopScreenCapture()
469+
}
470+
},[stopScreenCapture])
471+
300472
return(
301473
<Suspensefallback={false}>
302474
<ModalWrapperopen={show}onClose={onCloseModal}>
@@ -458,6 +630,22 @@ export function DiscEditor({
458630
</Button>
459631
</label>
460632
</Grid>
633+
<Griditem>
634+
<Button
635+
onClick={
636+
isCapturing
637+
?stopScreenCapture
638+
:startScreenCapture
639+
}
640+
startIcon={
641+
isCapturing ?<StopIcon/> :<ScreenShareIcon/>
642+
}
643+
color={isCapturing ?'error' :'primary'}
644+
variant={'contained'}
645+
>
646+
{isCapturing ?'Stop Capture' :'Capture Screen'}
647+
</Button>
648+
</Grid>
461649
{shouldShowDevComponents&&debugImgs&&(
462650
<Griditem>
463651
<DebugModalimgs={debugImgs}/>
@@ -502,6 +690,12 @@ export function DiscEditor({
502690
}
503691
/>
504692
)}
693+
{isCapturing&&(
694+
<Alertseverity="info"sx={{mt:1}}>
695+
Screen capture is active. Screenshots will be taken
696+
every 5 seconds.
697+
</Alert>
698+
)}
505699
{!!queueTotal&&(
506700
<CardThemedsx={{pl:2}}>
507701
<Boxdisplay="flex"alignItems="center">

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp