備忘錄_20160105(定位)
修改
回首頁
程式 2026-06-25 17:08:09 1782378489 100
圖片檢視器ver1.16
圖片檢視器ver1.16
<!doctype html>
<html lang="zh-Hant-TW">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>圖片檢視器 ver1.16</title>
<style>
html, body
{
margin: 0;
border: 0;
padding: 0;
width: 100vw;
height: 100vh;
overflow: hidden;
background-color: #f5f7df;
}
fieldset.classControlPanel
{
display: inline-block;
position: fixed;
z-index: 4;
left: 0;
top: 0;
margin: 0.5em;
border: 0;
border-radius: 6px;
padding: 0.5em;
background-color: #ffe3c4;
}
fieldset.classControlPanel > legend
{
font-family: 微軟正黑體, 標楷體;
font-weight: bold;
}
#aPara
{
font-size: 0.7em;
}
canvas.classFinalCanvas
{
position: fixed;
z-index: 3;
left: 0;
top: 0;
margin: 0;
border: 0;
padding: 0;
width: 100vw;
height: 100vh;
}
fieldset.classFSTransform,
fieldset.classFSDraw,
fieldset.classFSFile,
fieldset.classFSHelp
{
display: inline-block;
border-radius: 6px;
}
fieldset.classFSTransform legend,
fieldset.classFSDraw legend,
fieldset.classFSFile legend,
fieldset.classFSHelp legend
{
cursor: pointer;
}
div.classRealHide
{
display: none;
}
legend.classOpen::before
{
content: "-";
}
legend.classClose::before
{
content: "+";
}
</style>
</head>
<body onkeydown="bodyKeyDown(event);">
<fieldset class="classControlPanel">
<legend>圖片檢視器<a id="aPara"></a></legend>
<div>
<fieldset class="classFSFile">
<legend class="classOpen" title="點我切換顯示與隱藏" onclick="showHideBlock();">檔案</legend>
<div class="classShowHide">
<button type="button" onclick="loadOneImage();" title="載入圖片">載入</button>
<select id="selLayer" title="選取圖層">
<option title="原始圖片+繪畫圖層">融合</option>
<option title="純粹繪畫圖層">繪畫</option>
<option title="原始圖片">原始</option>
<option title="視窗所見圖層">最終</option>
</select>
<button type="button" onclick="exportToAFile();" title="匯出圖檔">匯出</button>
</div>
</fieldset>
<fieldset class="classFSTransform">
<legend class="classClose" title="點我切換顯示與隱藏" onclick="showHideBlock();">轉換</legend>
<div class="classShowHide classRealHide">
<button type="button" onclick="zoomIn();" title="放大圖片 [*]">大</button>
<button type="button" onclick="zoomOut();" title="縮小圖片 [/]">小</button>
<select id="selStep" title="選取移動步幅">
<option>1</option>
<option>2</option>
<option>4</option>
<option>8</option>
<option>16</option>
<option selected="true">32</option>
<option>64</option>
<option>128</option>
<option>256</option>
<option>512</option>
<option>1024</option>
<option>2048</option>
<option>4096</option>
</select>
<button type="button" onclick="moveLeft();" title="看左邊 [←]">左</button>
<button type="button" onclick="moveRight();" title="看右邊 [→]">右</button>
<button type="button" onclick="moveUp();" title="看上邊 [↑]">上</button>
<button type="button" onclick="moveDown();" title="看下邊 [↓]">下</button>
</div>
</fieldset>
<fieldset class="classFSDraw">
<legend class="classClose" title="點我切換顯示與隱藏" onclick="showHideBlock();">繪畫</legend>
<div class="classShowHide classRealHide">
<button type="button" onclick="switchDrawMode();" title="切換繪圖模式 [Ctrl+Draw]">切</button>
<select id="selDrawStyle" title="繪圖樣式">
<option selected="true">直線</option>
<option>空心矩形</option>
<option>實心矩形</option>
<option>空心圓形</option>
<option>實心圓形</option>
</select>
<select id="selTool" title="繪圖工具">
<option value="source-over" selected="true">彩筆</option>
<option value="destination-out">擦子</option>
</select>
<input type="color" id="inpDrawColor" value="#d10700" title="選取繪圖顏色">
<select id="selDrawWidth" title="選取線條寬度">
<option>1</option>
<option>2</option>
<option selected="true">4</option>
<option>8</option>
<option>16</option>
<option>32</option>
</select>
<button type="button" onclick="clearDrawCanvas();" title="清除繪畫圖層 [Ctrl+cleaR]">清</button>
<button type="button" onclick="switchAssistMode();" title="切換輔助線條 [Ctrl+Assist]">輔</button>
</div>
</fieldset>
<fieldset class="classFSHelp">
<legend class="classClose" title="點我切換顯示與隱藏" onclick="showHideBlock();">說明</legend>
<div class="classShowHide classRealHide">
最底層為原始圖片,<br>
中間層為繪畫圖層,(寬高與原始圖片相同)<br>
最上層為最終圖層。(寬高是視窗最大範圍)<br>
<br>
融合圖層是繪畫圖層+原始圖片,僅用在匯出時。
</div>
</fieldset>
</div>
</fieldset>
<canvas class="classFinalCanvas" onclick="clickFinalCanvas(event);" onmousemove="getCursorAndShowAssistLines();"></canvas>
<script> // toast
function toast(strMessage, iInterval)
{
if(!iInterval) { iInterval=2500; }
let oToast=document.createElement("div");
oToast.style.display="block";
oToast.style.position="fixed";
oToast.style.zIndex="6";
oToast.style.visibility="visible";
oToast.style.width="100%";
oToast.style.textAlign="center";
oToast.style.bottom="30px";
oToast.style.fontSize="17px";
let straStyle=[];
straStyle.push("display: inline-block");
straStyle.push("color: #ffffff");
straStyle.push("background-color: #333");
straStyle.push("margin: 0 auto");
straStyle.push("border-radius: 2px");
straStyle.push("padding: 16px");
oToast.innerHTML
="<div style='"+straStyle.join("; ")+"'>"
+strMessage
+"</div>";
document.body.appendChild(oToast);
window.setTimeout(function() { oToast.parentNode.removeChild(oToast); }, iInterval);
}
</script>
<script> // global parameters
var oImage=null;
var oDrawCanvas=null;
var oDrawCtx=null;
var oFinalCanvas=null;
var oFinalCtx=null;
let oPara={};
</script>
<script> // body key down
function bodyKeyDown(event)
{
console.log(event.key);
while(true)
{
if(event.key=='*') { zoomIn(); }
else if(event.key=='/') { zoomOut(); }
else if(event.key=='ArrowUp') { moveUp(); }
else if(event.key=='ArrowDown') { moveDown(); }
else if(event.key=='ArrowLeft') { moveLeft(); }
else if(event.key=='ArrowRight') { moveRight(); }
else if(event.key=='ArrowRight') { moveRight(); }
else if(event.ctrlKey==true && event.shiftKey==false && event.altKey==false && event.code=='KeyR') { clearDrawCanvas(); }
else if(event.ctrlKey==true && event.shiftKey==false && event.altKey==false && event.code=='KeyD') { switchDrawMode(); }
else if(event.ctrlKey==true && event.shiftKey==false && event.altKey==false && event.code=='KeyA') { switchAssistMode(); }
else { break; }
event.preventDefault();
break;
}
}
</script>
<script> // final canvas
function clearAndResizeFinalCanvas()
{
if(oFinalCanvas==null) { return; }
oFinalCanvas.setAttribute("width", "0");
oFinalCanvas.setAttribute("height", "0");
oFinalCanvas.setAttribute("width", String(document.documentElement.clientWidth));
oFinalCanvas.setAttribute("height", String(document.documentElement.clientHeight));
}
window.addEventListener(
"load",
async function()
{
oFinalCanvas=document.getElementsByClassName("classFinalCanvas")[0];
oFinalCtx=oFinalCanvas.getContext("2d");
clearAndResizeFinalCanvas();
}
);
function refreshFinalCanvas()
{
if(oImage==null) { return; }
if(oDrawCanvas==null) { return; }
if(oFinalCanvas==null) { return; }
let fX=oPara.fX;
let fY=oPara.fY;
let fW=oImage.width*oPara.fScale;
let fH=oImage.height*oPara.fScale;
let str繪圖樣式=document.getElementById("selDrawStyle").value;
clearAndResizeFinalCanvas(); // 清空畫面
oFinalCtx.drawImage(oImage, fX, fY, fW, fH); // 原始圖片
oFinalCtx.drawImage(oDrawCanvas, fX, fY, fW, fH); // 繪畫內容
if(oPara.booDraw==true && oPara.oaPoint.length==1) // 線條第一個點
{
let oPointFinalCanvas=from_image_to_final_canvas({x:oPara.oaPoint[0][0], y:oPara.oaPoint[0][1]});
oFinalCtx.beginPath();
oFinalCtx.strokeStyle="#3994e3";
oFinalCtx.lineWidth=2;
oFinalCtx.moveTo(oPointFinalCanvas.x-3, oPointFinalCanvas.y-3);
oFinalCtx.lineTo(oPointFinalCanvas.x+3, oPointFinalCanvas.y+3);
oFinalCtx.stroke();
oFinalCtx.moveTo(oPointFinalCanvas.x-3, oPointFinalCanvas.y+3);
oFinalCtx.lineTo(oPointFinalCanvas.x+3, oPointFinalCanvas.y-3);
oFinalCtx.stroke();
drawing(
oFinalCtx,
'source-over',
str繪圖樣式,
"#3994e3",
2,
oPointFinalCanvas.x,
oPointFinalCanvas.y,
oPara.oAssistPoint[0],
oPara.oAssistPoint[1]);
}
if(oPara.booAssist==true && oPara.oAssistPoint) // 輔助線條
{
oFinalCtx.beginPath();
oFinalCtx.strokeStyle="#ff0000";
oFinalCtx.lineWidth=1;
oFinalCtx.moveTo(oPara.oAssistPoint[0],0);
oFinalCtx.lineTo(oPara.oAssistPoint[0],oFinalCanvas.height);
oFinalCtx.stroke();
oFinalCtx.moveTo(0,oPara.oAssistPoint[1]);
oFinalCtx.lineTo(oFinalCanvas.width, oPara.oAssistPoint[1]);
oFinalCtx.stroke();
}
}
</script>
<script> // 座標系統轉換
function from_image_to_final_canvas(oPointImage)
{
if(oFinalCanvas==null) { return; }
let oRect=oFinalCanvas.getBoundingClientRect();
return {
x:oRect.left+oPara.fX+oPointImage.x*oPara.fScale,
y:oRect.top+oPara.fY+oPointImage.y*oPara.fScale
};
}
function from_final_canvas_to_image(oPointFinalCanvas)
{
if(oFinalCanvas==null) { return; }
let oRect=oFinalCanvas.getBoundingClientRect();
return {
x:(oPointFinalCanvas.x-oRect.left-oPara.fX)/oPara.fScale,
y:(oPointFinalCanvas.y-oRect.top-oPara.fY)/oPara.fScale
};
}
</script>
<script> // load one image
async function loadOneImage()
{
let oElement=document.createElement("input");
oElement.setAttribute("type", "file");
oElement.setAttribute("accept", "image/*");
oElement.addEventListener("change", loadImage);
oElement.click();
}
async function loadImage(event)
{
if(event.target.files.length<1)
{
toast("沒有選取圖檔,無法進行載入!");
return;
}
let oFile=event.target.files[0];
if(oFile.type.indexOf("image/")!=0)
{
toast("抱歉,您選取的檔案,並非瀏覽器支援的圖片格式,無法載入。");
return;
}
const oFileReader=new FileReader();
oFileReader.addEventListener(
"load",
function(event)
{
oImage=document.createElement("img");
oImage.addEventListener(
"load",
async function()
{
// 初始化 oDrawCanvas
oDrawCanvas=document.createElement("canvas");
oDrawCanvas.setAttribute("width", oImage.width);
oDrawCanvas.setAttribute("height", oImage.height);
oDrawCtx=oDrawCanvas.getContext('2d');
// 參數初始化
let fScaleW=document.documentElement.clientWidth/oImage.width;
let fScaleH=document.documentElement.clientHeight/oImage.height;
oPara.fScale=Math.min(fScaleW, fScaleH);
oPara.fX=0;
oPara.fY=0;
oPara.booDraw=false;
oPara.oaPoint=[];
oPara.booAssist=false;
refreshParameters(); // 刷新參數
refreshFinalCanvas(); // 繪製結果
showHideBlock(document.getElementsByClassName("classFSFile")[0].getElementsByTagName("legend")[0]);
}
);
oImage.src=event.target.result;
toast("圖片讀取完成。");
}
);
oFileReader.readAsDataURL(oFile);
}
</script>
<script> // refresh parameters
function refreshParameters()
{
if(oImage==null) { return; }
var stra1=[];
stra1.push("倍率("+oPara.fScale.toFixed(4)+")");
stra1.push("位移("+String(oPara.fX)+","+String(oPara.fY)+")");
if(oPara.booDraw==true)
{
stra1.push("繪圖-"+String(oPara.oaPoint.length)+"點");
}
document.getElementById("aPara").textContent=" "+stra1.join(" ");
}
</script>
<script> // zoom in/out and move left/right/up/down
function zoomIn()
{
if(oImage==null) { toast("尚未載入圖片"); return; }
oPara.fScale+=0.02;
refreshParameters();
refreshFinalCanvas();
}
function zoomOut()
{
if(oImage==null) { toast("尚未載入圖片"); return; }
oPara.fScale-=0.02;
if(oPara.fScale<0.02) { oPara.fScale=0.02; }
refreshParameters();
refreshFinalCanvas();
}
function getFPixel()
{
return Number(document.getElementById("selStep").value);
}
function moveUp()
{
if(oImage==null) { toast("尚未載入圖片"); return; }
oPara.fY+=getFPixel();
refreshParameters();
refreshFinalCanvas();
}
function moveDown()
{
if(oImage==null) { toast("尚未載入圖片"); return; }
oPara.fY-=getFPixel();
refreshParameters();
refreshFinalCanvas();
}
function moveLeft()
{
if(oImage==null) { toast("尚未載入圖片"); return; }
oPara.fX+=getFPixel();
refreshParameters();
refreshFinalCanvas();
}
function moveRight()
{
if(oImage==null) { toast("尚未載入圖片"); return; }
oPara.fX-=getFPixel();
refreshParameters();
refreshFinalCanvas();
}
</script>
<script> // clear draw canvas
function clearDrawCanvas()
{
if(oImage==null) { toast("尚未載入圖片"); return; }
if(oDrawCanvas==null) { toast("繪畫圖層尚未初始化"); return; }
oDrawCanvas.setAttribute("width", "0");
oDrawCanvas.setAttribute("height", "0");
oDrawCanvas.setAttribute("width", String(oImage.width));
oDrawCanvas.setAttribute("height", String(oImage.height));
refreshFinalCanvas();
toast('已經清除繪畫圖層');
}
</script>
<script> // switch draw mode
function switchDrawMode()
{
oPara.booDraw=!oPara.booDraw;
oPara.oaPoint=[];
refreshParameters();
if(oPara.booDraw==true) { toast('進入繪畫模式', 2000); }
else { toast('離開繪畫模式', 2000); }
}
</script>
<script> // assist lines
function switchAssistMode()
{
oPara.booAssist=!oPara.booAssist;
if(oPara.booAssist==true) { toast('顯示輔助線條', 2000); }
else { toast('關閉輔助線條', 2000); }
refreshFinalCanvas();
}
function getCursorAndShowAssistLines()
{
oPara.oAssistPoint=[event.clientX, event.clientY];
if(oImage==null) { return; }
if(oPara.booDraw==false && oPara.booAssist==false) { return; }
refreshFinalCanvas();
}
</script>
<script> // 繪製圖形
function drawing(oCtx, strGlobalCompositeOperation, str繪圖樣式, strColor, fWidth, x1, y1, x2, y2)
{
if(str繪圖樣式=="直線")
{
oCtx.beginPath();
oCtx.globalCompositeOperation=strGlobalCompositeOperation;
oCtx.strokeStyle=strColor;
oCtx.lineWidth=fWidth;
oCtx.moveTo(x1, y1);
oCtx.lineTo(x2, y2);
oCtx.stroke();
oCtx.globalCompositeOperation='source-over';
}
else if(str繪圖樣式=="空心矩形")
{
oCtx.beginPath();
oCtx.globalCompositeOperation=strGlobalCompositeOperation;
oCtx.strokeStyle=strColor;
oCtx.lineWidth=fWidth;
oCtx.strokeRect(x1, y1, x2-x1, y2-y1);
oCtx.globalCompositeOperation='source-over';
}
else if(str繪圖樣式=="實心矩形")
{
oCtx.beginPath();
oCtx.globalCompositeOperation=strGlobalCompositeOperation;
oCtx.fillStyle=strColor;
oCtx.lineWidth=fWidth;
oCtx.fillRect(x1, y1, x2-x1, y2-y1);
oCtx.globalCompositeOperation='source-over';
}
else if(str繪圖樣式=="空心圓形")
{
oCtx.beginPath();
oCtx.globalCompositeOperation=strGlobalCompositeOperation;
oCtx.strokeStyle=strColor;
oCtx.lineWidth=fWidth;
let fRadius=Math.sqrt((x2-x1)**2+(y2-y1)**2);
oCtx.arc(x1, y1, fRadius, 0, 2 * Math.PI);
oCtx.stroke();
oCtx.globalCompositeOperation='source-over';
}
else if(str繪圖樣式=="實心圓形")
{
oCtx.beginPath();
oCtx.globalCompositeOperation=strGlobalCompositeOperation;
oCtx.strokeStyle=strColor;
oCtx.fillStyle=strColor;
oCtx.lineWidth=fWidth;
let fRadius=Math.sqrt((x2-x1)**2+(y2-y1)**2);
oCtx.arc(x1, y1, fRadius, 0, 2 * Math.PI);
oCtx.stroke();
oCtx.fill();
oCtx.globalCompositeOperation='source-over';
}
}
function clickFinalCanvas(event)
{
if(oImage==null) { return; }
if(oDrawCanvas==null) { return; }
if(oPara.booDraw==false) { return; }
let oPointImage=from_final_canvas_to_image({x:event.clientX, y:event.clientY});
oPara.oaPoint.push([oPointImage.x, oPointImage.y]);
console.log(oPointImage.x, oPointImage.y);
if(oPara.oaPoint.length==2)
{
let str繪圖樣式=document.getElementById("selDrawStyle").value;
let strColor=document.getElementById('inpDrawColor').value;
let fWidth=Number(document.getElementById('selDrawWidth').value);
let x1=oPara.oaPoint[0][0];
let y1=oPara.oaPoint[0][1];
let x2=oPara.oaPoint[1][0];
let y2=oPara.oaPoint[1][1];
drawing(oDrawCtx, document.getElementById("selTool").value, str繪圖樣式, strColor, fWidth, x1, y1, x2, y2);
oPara.oaPoint=[];
}
refreshParameters();
refreshFinalCanvas();
}
</script>
<script>
function showHideBlock(oTarget=null)
{
var oLegend=null;
if(oTarget!=null) { oLegend=oTarget; }
else { oLegend=event.target; }
let oFieldset=oLegend.parentNode;
let oDiv=oFieldset.getElementsByClassName("classShowHide")[0];
if(oDiv.classList.contains("classRealHide")==true)
{
oDiv.classList.remove("classRealHide");
oLegend.setAttribute("class", "classOpen");
}
else
{
oDiv.classList.add("classRealHide");
oLegend.setAttribute("class", "classClose");
}
}
</script>
<script>
function exportToAFileCore(oCanvas, strDefaultFilename)
{
oCanvas.toBlob(
async function tempfunc(oBlob)
{
const strUrl=URL.createObjectURL(oBlob);
const oAnchor=document.createElement("a");
oAnchor.href=strUrl;
oAnchor.download=strDefaultFilename;
oAnchor.click();
URL.revokeObjectURL(strUrl);
},
"image/png"
);
}
function exportToAFile()
{
if(oImage==null) { toast("尚未載入圖片"); return; }
let strTargetLayer=document.getElementById("selLayer").value;
if(strTargetLayer=="融合")
{
let oMergedCanvas=document.createElement("canvas");
oMergedCanvas.setAttribute("width", oImage.width);
oMergedCanvas.setAttribute("height", oImage.height);
let oMergedCtx=oMergedCanvas.getContext("2d");
oMergedCtx.drawImage(oImage, 0, 0, oImage.width, oImage.height); // 原始圖片
oMergedCtx.drawImage(oDrawCanvas, 0, 0, oImage.width, oImage.height); // 繪畫內容
exportToAFileCore(oMergedCanvas, "merged.png");
}
else if(strTargetLayer=="原始")
{
let oOrigionalCanvas=document.createElement("canvas");
oOrigionalCanvas.setAttribute("width", oImage.width);
oOrigionalCanvas.setAttribute("height", oImage.height);
let oOrigionalCtx=oOrigionalCanvas.getContext("2d");
oOrigionalCtx.drawImage(oImage, 0, 0, oImage.width, oImage.height); // 原始圖片
exportToAFileCore(oOrigionalCanvas, "original.png");
}
else if(strTargetLayer=="繪畫")
{
exportToAFileCore(oDrawCanvas, "draw.png");
}
else if(strTargetLayer=="最終")
{
exportToAFileCore(oFinalCanvas, "final.png");
}
else
{
alert("抱歉,遇到不知名的圖層("+strTargetLayer+"),無法匯出!");
}
}
</script>
</body>
</html>