備忘錄_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>