備忘錄_20160105(定位)
修改
回首頁
程式 2022-05-29 12:44:56 1653799496 100
製作類似卡拉OK字幕逐字亮起的流程─03圖片檔標記製作
製作類似卡拉OK字幕逐字亮起的流程─03圖片檔標記製作
- 用js+input file載入單機圖片檔(jpg,png......)
- 攔截mouse在canvas中的正確座標
- 攔截鍵盤keycode
- 加入snap grid功能,加速定位
前往 20220520_loadimage.htm
●20220520_loadimage.htm
<!doctype html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
*
{
font-family: "WenQuanYi Zen Hei","文泉驛正黑","Heiti TC","黑體-繁","LiHei Pro","儷黑 Pro","PingFang TC","Droid Sans","Roboto","Microsoft JhengHei","微軟正黑體",sans-serif;
/* 【linux的字型】【ios字型】【android字型】【微軟正黑體】【無襯線字=黑體】 */
}
html, body
{
margin: 0;
border: 0;
padding: 0;
width: 100%;
height: 100%;
}
a:link, a:visited
{
color: blue;
text-decoration: none;
}
#divWrapper
{
padding: 1em;
}
.classHasCanvas
{
display: inline-block;
margin: 0;
border-style: solid;
border-color: red;
border-width: 1px;
padding: 0;
}
</style>
</head>
<body onkeydown="bodyKeydown(event);">
<div id="divWrapper">
<div>
<!-- <button type="button" onclick="drawImage();">from image to canvas</button> -->
<div>
(範例圖片來源取自今成文教基金會)
載入背景圖片檔案<input type="file" name="" id="" onchange="loadImageFile(this);">
</div>
<div style="display: inline-block; background-color: #efabcd; margin: 6px;">
<input type="checkbox" name="cbUsingSnap" id="cbUsingSnap" checked="true">使用SNAP
(用 [Ctrl]+[leftClick] 在右上角點,會盡可能去尋找並自動新增左下角點。)
</div>
<div style="display: inline-block; background-color: #efabcd; margin: 6px;">
<input type="checkbox" name="cbShowSnapLines" id="cbShowSnapLines">顯示SNAP線條
</div>
<div style="display: inline-block; background-color: #efabcd; margin: 6px;">
<input type="checkbox" name="cbOnlyBox" id="cbOnlyBox" checked="true">雙數座標點成矩形
</div>
<button type="button" onclick="recalcXY();">整理座標點到輸出窗格</button>
<button type="button" onclick="gebi('taXY').value='';">clear x y data</button>
</div>
<div>[Shift]+[s]=切換SNAP使用與否。[Shift]+[c]=清除snap資料。[Shift]+[x] | [Shift]+[y]=將現有座標點加入SNAP座標點(垂直|水平)</div>
<div>[Shift]+[u]=多等份切割最後兩點。下面是畫布,滑鼠左鍵 or [CapsLock] 點一下會抓取座標並畫線。</div>
<div class="classBlock">
【方框顯示】
<button type="button" onclick="doCmd('search4indexofboxblock');">抓取滑鼠位置區塊並取得索引[r]</button>
索引(base0)<input type="text" id="inpIdxOfBoxBlock" value="0">
<button type="button" onclick="showBoxByIndex();">顯示並往下一個移動[n]</button>
<span id="spanShowBoxByIndeStatus"></span>
</div>
<div class="classHasCanvas">
<canvas
id="myCanvas"
onmousemove="moving(event);"
onclick="saving(event);"></canvas>
</div>
<div>
<div style="float: left;">
<div>下面是 x,y 座標點</div>
<textarea id="taXY" rows="10" cols="20" onfocus="setTextareaFocus(true);" onfocusout="setTextareaFocus(false);"></textarea>
</div>
<div style="float: left;">
輸出窗格(準備貼到EXCEL):<br>
<textarea id="taFinalXY" rows="10" cols="120" onfocus="setTextareaFocus(true);" onfocusout="setTextareaFocus(false);"></textarea>
</div>
</div>
<div style="clear: both;"></div>
</div>
<hr>
<div>
<div>snap 的 x,y 座標點 (特別注意:x 和 y 是分別snap)</div>
<div>
<button type="button" onclick="readSnapXY();">讀取snap座標點</button>
</div>
<textarea id="taSnapXY" onfocus="setTextareaFocus(true);" onfocusout="setTextareaFocus(false);">1767,154;1676,154;1585,154;1494,154;1403,154;1312,154;1221,154;1130,154;808,154;717,154;626,154;535,154;444,154;353,154;262,154;171,154;808,154;808,203;808,252;808,301;808,350;808,399;808,448;808,497;808,546;808,595;808,644;808,693;808,742;808,791;808,840;808,889;808,938</textarea>
</div>
<hr>
<div style="display: ;">
下面是原始圖檔<br>
<img id="myImage" src="20220520_loadimage.jpg" strFN="20220520_loadimage.jpg">
</div>
<script>
function loadImageFile(oObj1)
{
console.log(oObj1);
gebi("myImage").src=URL.createObjectURL(oObj1.files[0]);
gebi("myImage").setAttribute("strFN",oObj1.files[0].name);
if(gebi("myImage").complete)
{
resizeCanvas();
drawImage();
drawSnapLines();
}
else
{
gebi("myImage").onload=function(){ resizeCanvas(); drawImage(); drawSnapLines(); }
}
}
function resizeCanvas()
{
var oImg=gebi("myImage");
oCanvas=gebi("myCanvas");
oCanvas.setAttribute("width", oImg.width);
oCanvas.setAttribute("height", oImg.height);
}
</script>
<script>
if(!String.prototype.ltrim) { String.prototype.ltrim = function() { return this.replace(/^\s+/,''); }; }
if(!String.prototype.rtrim) { String.prototype.rtrim = function() { return this.replace(/\s+$/,''); }; }
if(!String.prototype.trim ) { String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g,''); }; }
function gebi(strId)
{
return document.getElementById(strId);
}
var oCanvas=null, oCtx=null;
window.addEventListener(
"load",
function()
{
readSnapXY();
resizeCanvas();
oCtx=oCanvas.getContext("2d");
drawImage();
drawSnapLines();
}
);
</script>
<script>
var oaSnapXY=[];
function readSnapXY()
{
oaSnapXY=[];
var straBuffer=gebi("taSnapXY").value.split(" ").join("").split(";");
for(var i=0; i<straBuffer.length; i++)
{
var straItem=straBuffer[i].split(",");
if(straItem.length==2)
{
oaSnapXY.push([straItem[0]*1, straItem[1]*1]);
}
}
}
function snapped(oXY)
{
var fX=oXY.fX;
var fY=oXY.fY;
var fLastDeltaX=15;
var fLastDeltaY=15;
var fCurrentDeltaX, fCurrentDeltaY;
for(var i=0; i<oaSnapXY.length; i++)
{
fCurrentDeltaX=Math.abs(fX-oaSnapXY[i][0]);
fCurrentDeltaY=Math.abs(fY-oaSnapXY[i][1]);
if(fCurrentDeltaX<fLastDeltaX)
{
fLastDeltaX=fCurrentDeltaX;
fX=oaSnapXY[i][0];
}
if(fCurrentDeltaY<fLastDeltaY)
{
fLastDeltaY=fCurrentDeltaY;
fY=oaSnapXY[i][1];
}
}
return {fX:fX, fY:fY};
}
function debug4searchLeftBottom(oXY) { console.log(oXY); }
function searchLeftBottom(oXY)
{
debug4searchLeftBottom(oXY);
oXY.fX=oXY.fX*1;
oXY.fY=oXY.fY*1;
var fX=null;
var fY=null;
var fLastDeltaX=1000;
var fLastDeltaY=1000;
var fCurrentDeltaX, fCurrentDeltaY;
for(var i=0; i<oaSnapXY.length; i++)
{
fCurrentDeltaX=Math.abs(oXY.fX-oaSnapXY[i][0]);
fCurrentDeltaY=Math.abs(oXY.fY-oaSnapXY[i][1]);
if((oaSnapXY[i][0]<oXY.fX) && (fCurrentDeltaX<fLastDeltaX) && (fCurrentDeltaX>1))
{
fLastDeltaX=fCurrentDeltaX;
fX=oaSnapXY[i][0];
}
if((oaSnapXY[i][1]>oXY.fY) && (fCurrentDeltaY<fLastDeltaY) && (fCurrentDeltaY>1))
{
fLastDeltaY=fCurrentDeltaY;
fY=oaSnapXY[i][1];
}
}
if(fX==null) { return null; }
if(fY==null) { return null; }
return {fX:fX, fY:fY};
}
function drawImage()
{
oCtx.drawImage(gebi("myImage"),0,0);
}
function saving(oEvent)
{
var fX = oEvent.clientX-(oCanvas.getBoundingClientRect()).left;
var fY = oEvent.clientY-(oCanvas.getBoundingClientRect()).top;
var booCtrlKey=false;
if(oEvent.altKey==false && oEvent.shiftKey==false && oEvent.ctrlKey==true)
{
booCtrlKey=true;
}
savingReally(fX,fY,booCtrlKey);
}
function savingReally(fX,fY,booCtrlKey)
{
if(gebi("cbUsingSnap").checked==true)
{
var oSnappedXY=snapped({fX:fX, fY:fY});
fX=oSnappedXY.fX;
fY=oSnappedXY.fY;
}
gebi("taXY").value=gebi("taXY").value+fX+","+fY+"\r\n";
if(gebi("cbUsingSnap").checked==true && booCtrlKey==true)
{
var oLB=searchLeftBottom({fX:fX, fY:fY})
if(oLB!=null)
{
gebi("taXY").value=gebi("taXY").value+oLB.fX+","+oLB.fY+"\r\n";
}
}
drawImage();
drawSnapLines();
drawLines();
}
function drawLines()
{
var straLine=gebi("taXY").value.split("\r\n").join("\n").split("\r").join("\n").split("\n");
if(gebi("cbOnlyBox").checked==false)
{
for(var iLine=0; iLine<straLine.length; iLine++)
{
var straItem=straLine[iLine].split(",");
if(straItem.length==2)
{
var fX=straItem[0]*1;
var fY=straItem[1]*1;
oCtx.beginPath();
oCtx.strokeStyle="green";
oCtx.moveTo(fX,0);
oCtx.lineTo(fX,oCanvas.getAttribute("height"));
oCtx.stroke();
oCtx.strokeStyle="green";
oCtx.moveTo(0,fY);
oCtx.lineTo(oCanvas.getAttribute("width"),fY);
oCtx.stroke();
}
}
}
else
{
for(var iLine=0; iLine<straLine.length; iLine+=2)
{
var straItem1=straLine[iLine].split(",");
if((iLine+1)>=(straLine.length-1))
{
// 僅有一個點,就畫十字
var fX1=straItem1[0]*1;
var fY1=straItem1[1]*1;
oCtx.beginPath();
oCtx.strokeStyle="green";
oCtx.moveTo(fX1,fY1-10);
oCtx.lineTo(fX1,fY1+10);
oCtx.stroke();
oCtx.strokeStyle="green";
oCtx.moveTo(fX1-10,fY1);
oCtx.lineTo(fX1+10,fY1);
oCtx.stroke();
break;
}
var straItem2=straLine[iLine+1].split(",");
if(straItem1.length==2 && straItem2.length==2)
{
var fX1=straItem1[0]*1;
var fY1=straItem1[1]*1;
var fX2=straItem2[0]*1;
var fY2=straItem2[1]*1;
var fx,fy,fw,fh;
fw=Math.abs(fX1-fX2);
fh=Math.abs(fY1-fY2);
if(fX1<fX2) { fx=fX1; } else { fx=fX2; }
if(fY1<fY2) { fy=fY1; } else { fy=fY2; }
oCtx.beginPath();
oCtx.strokeStyle="green";
oCtx.rect(fx,fy,fw,fh);
oCtx.stroke();
}
}
}
}
var fLastCursorX=null;
var fLastCursorY=null;
function moving(oEvent)
{
var fX = oEvent.clientX-(oCanvas.getBoundingClientRect()).left;
var fY = oEvent.clientY-(oCanvas.getBoundingClientRect()).top;
if(gebi("cbUsingSnap").checked==true)
{
var oSnappedXY=snapped({fX:fX, fY:fY});
fX=oSnappedXY.fX;
fY=oSnappedXY.fY;
}
drawImage();
drawSnapLines();
drawLines();
oCtx.beginPath();
oCtx.strokeStyle="red";
oCtx.moveTo(fX,0);
oCtx.lineTo(fX,oCanvas.getAttribute("height"));
oCtx.stroke();
oCtx.strokeStyle="red";
oCtx.moveTo(0,fY);
oCtx.lineTo(oCanvas.getAttribute("width"),fY);
oCtx.stroke();
fLastCursorX=fX;
fLastCursorY=fY;
}
</script>
<script>
function recalcXY()
{
var strImageFilename=gebi("myImage").getAttribute("strFN").replace(/^.*[\\\/]/, '');
if(strImageFilename.substring(0,2)=="fg")
{
strImageFilename="bg"+strImageFilename.substring(2);
}
var straSource=gebi("taXY").value.split("\r\n").join("\n").split("\r").join("\n").split("\n");
var straBuffer=[];
var straFinal=[];
// 首先過濾空白行以及不符合規格的資料
for(var i=0; i<straSource.length; i++)
{
var straItem=straSource[i].split(",");
if(straItem.length==2)
{
straBuffer.push(straSource[i]);
}
}
if((straBuffer.length % 2)!=0)
{
alert("座標點的數量不是雙數,有問題!計算終止!");
return;
}
var leftx, topy, rightx, bottomy;
for(var i=0; i<straBuffer.length; i+=2)
{
var oaPoint1=straBuffer[i].split(",");
var oaPoint2=straBuffer[i+1].split(",");
var x1=oaPoint1[0]*1;
var y1=oaPoint1[1]*1;
var x2=oaPoint2[0]*1;
var y2=oaPoint2[1]*1;
if(x1<x2) { leftx=x1; rightx=x2; }
else { leftx=x2; rightx=x1; }
if(y1<y2) { topy=y1; bottomy=y2; }
else { topy=y2; bottomy=y1; }
// 因為都是由上而下,所以這樣安排
straFinal.push(([strImageFilename,leftx,topy,rightx,topy,leftx,bottomy,rightx,bottomy]).join("\t"));
}
gebi("taFinalXY").style.backgroundColor="rgb("+Math.floor(Math.random()*255)+","+Math.floor(Math.random()*255)+","+Math.floor(Math.random()*255)+")";
gebi("taFinalXY").value=straFinal.join("\r\n");
}
</script>
<script>
function bodyKeydown(oE)
{
var strCmd="";
console.log(oE);
if(booTextareaFocus==false)
{
if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==20) { strCmd="mimicClick"; } // [capslock]
else if(oE.ctrlKey==true && oE.altKey==false && oE.shiftKey==false && oE.keyCode==20) { strCmd="mimicClickAndCtrl"; }
else if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==true && oE.keyCode==67) { strCmd="clearSnapData"; } // [shift]+[c]
else if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==true && oE.keyCode==83) { strCmd="switchSnap"; } // [shift]+[s]
else if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==true && oE.keyCode==88) { strCmd="addSnapDataX"; } // [shift]+[x]
else if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==true && oE.keyCode==89) { strCmd="addSnapDataY"; } // [shift]+[y]
else if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==true && oE.keyCode==85) { strCmd="batchCutLastTwoPoints"; } // [shift]+[u]
else if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==82) { strCmd="search4indexofboxblock"; } // [r]
else if(oE.ctrlKey==false && oE.altKey==false && oE.shiftKey==false && oE.keyCode==78) { strCmd="showboxbyindex"; } // [n]
}
if(strCmd!="")
{
doCmd(strCmd);
}
}
function doCmd(strCmd)
{
if(strCmd=="mimicClick")
{
savingReally(fLastCursorX,fLastCursorY,false);
}
else if(strCmd=="mimicClickAndCtrl")
{
savingReally(fLastCursorX,fLastCursorY,true);
}
else if(strCmd=="clearSnapData")
{
oaSnapXY=[];
}
else if(strCmd=="switchSnap")
{
if(cbUsingSnap.checked==true) { cbUsingSnap.checked=false; }
else { cbUsingSnap.checked=true; }
}
else if(strCmd=="addSnapDataX")
{
addSnapDataXY(true);
}
else if(strCmd=="addSnapDataY")
{
addSnapDataXY(false);
}
else if(strCmd=="batchCutLastTwoPoints")
{
batchCutLastTwoPoints();
}
else if(strCmd=="search4indexofboxblock")
{
search4IndexOfBoxBlock();
}
else if(strCmd=="showboxbyindex")
{
showBoxByIndex();
}
}
</script>
<script>
function search4IndexOfBoxBlock()
{
var straLine=gebi("taXY").value.split("\r\n").join("\n").split("\r").join("\n").split("\n");
var iTotal=Math.floor(straLine.length/2);
if(iTotal<1)
{
alert("抱歉,未有足夠座標點(要有2的倍數)可供顯示!");
return;
}
var iFound=null;
for(var i=0; i<iTotal; i++)
{
var oaPoint1=straLine[i*2].split(",");
var oaPoint2=straLine[i*2+1].split(",");
var x1=oaPoint1[0]*1;
var y1=oaPoint1[1]*1;
var x2=oaPoint2[0]*1;
var y2=oaPoint2[1]*1;
var fChkValX=(fLastCursorX-x1)*(fLastCursorX-x2);
var fChkValY=(fLastCursorY-y1)*(fLastCursorY-y2);
if(fChkValX<=0 && fChkValY<=0)
{
iFound=i;
break;
}
}
if(iFound==null)
{
gebi("inpIdxOfBoxBlock").value="抱歉,沒有找到";
}
else
{
gebi("inpIdxOfBoxBlock").value=iFound;
}
}
function showBoxByIndex()
{
var straLine=gebi("taXY").value.split("\r\n").join("\n").split("\r").join("\n").split("\n");
var iTotal=Math.floor(straLine.length/2);
if(iTotal<1)
{
alert("抱歉,未有足夠座標點(要有2的倍數)可供顯示!");
return;
}
var iIdxOfBoxBlock=gebi("inpIdxOfBoxBlock").value*1;
if(iIdxOfBoxBlock<0) { iIdxOfBoxBlock=0; }
if(iIdxOfBoxBlock>(iTotal-1)) { iIdxOfBoxBlock=(iTotal-1); }
var oaPoint1=straLine[iIdxOfBoxBlock*2].split(",");
var oaPoint2=straLine[iIdxOfBoxBlock*2+1].split(",");
gebi("spanShowBoxByIndeStatus").innerHTML=straLine[iIdxOfBoxBlock*2]+" ~ "+straLine[iIdxOfBoxBlock*2+1];
var x1=oaPoint1[0]*1;
var y1=oaPoint1[1]*1;
var x2=oaPoint2[0]*1;
var y2=oaPoint2[1]*1;
var leftx=Math.min(x1,x2);
var topy=Math.min(y1,y2);
var thewidth=Math.abs(x1-x2);
var theheight=Math.abs(y1-y2);
drawImage();
drawSnapLines();
drawLines();
oCtx.beginPath();
oCtx.lineWidth=4;
oCtx.strokeStyle="#f542e9";
oCtx.rect(leftx,topy,thewidth,theheight);
oCtx.stroke();
oCtx.lineWidth=1;
// 移動到下一個區塊
iIdxOfBoxBlock=(iIdxOfBoxBlock+1) % iTotal;
gebi("inpIdxOfBoxBlock").value=iIdxOfBoxBlock;
}
</script>
<script>
function addSnapDataXY(booAddX)
{
var straLine=gebi("taXY").value.split("\r\n").join("\n").split("\r").join("\n").split("\n").filter(Boolean);
var oaBuffer=[];
for(var i=0; i<straLine.length; i++)
{
var straItem=straLine[i].split(",");
if(booAddX==true) { straItem[1]=0; }
else { straItem[0]=0; }
oaBuffer.push(straItem);
}
oaSnapXY=oaSnapXY.concat(oaBuffer);
var straFinal=[];
for(var i=0; i<oaSnapXY.length; i++)
{
straFinal.push(oaSnapXY[i].join(","));
}
gebi("taSnapXY").value=straFinal.join(";");
}
function drawSnapLines()
{
if(gebi("cbShowSnapLines").checked==false) { return; }
var width=oCanvas.getAttribute("width")*1;
var height=oCanvas.getAttribute("height")*1;
oCtx.strokeStyle="gray";
oCtx.setLineDash([15,3,3,3])
oCtx.beginPath(); //NMOMTF
for(var i=0; i<oaSnapXY.length; i++)
{
var x=oaSnapXY[i][0];
var y=oaSnapXY[i][1];
oCtx.moveTo(x,0);
oCtx.lineTo(x,height);
oCtx.moveTo(0,y);
oCtx.lineTo(width,y);
}
oCtx.stroke();
oCtx.setLineDash([]);
}
</script>
<script>
function batchCutLastTwoPoints()
{
var straLine=gebi("taXY").value.split("\r\n").join("\n").split("\r").join("\n").split("\n").filter(Boolean);
if(straLine.length<2)
{
alert("抱歉,最少要有兩個點,才能進行等份切割");
return;
}
var iSegments=prompt("請輸入要切成幾等份?",2);
if(iSegments===null) { return; }
iSegments=Number.parseInt(iSegments);
if(Number.isNaN(iSegments)==true)
{
alert("抱歉,您要輸入數字才能切割!");
return;
}
if(iSegments<2)
{
alert("抱歉,數字最少要2(含)以上才能進行切割!");
return;
}
var oaPoint1=straLine[straLine.length-2].split(",");
var oaPoint2=straLine[straLine.length-1].split(",");
straLine=straLine.slice(0,straLine.length-1);
for(var i=0; i<iSegments; i++)
{
var newX=oaPoint1[0]*1+(oaPoint2[0]*1-oaPoint1[0]*1)*(i+1)/iSegments;
var newY=oaPoint1[1]*1+(oaPoint2[1]*1-oaPoint1[1]*1)*(i+1)/iSegments;
straLine.push(newX+","+newY);
}
gebi("taXY").value=straLine.join("\r\n")+"\r\n";
}
</script>
<script>
var booTextareaFocus=false;
function setTextareaFocus(booFocus)
{
booTextareaFocus=booFocus;
}
</script>
</body>
</html>