| <!DOCTYPE html> |
| <html lang=ja> |
| <head> |
| <meta charset=UTF-8> |
| <meta name=viewport content="width=device-width,initial-scale=1"> |
| <title>画像変換ツール</title> |
| <style> |
| body{font-family:Arial,sans-serif;margin:0;padding:20px;background-color:#f5f5f5} |
| .container{max-width:900px;margin:0 auto;background-color:#fff;padding:20px;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,.1)} |
| h1,h2{color:#333} |
| .mode-selector{display:flex;margin-bottom:20px;flex-wrap:wrap} |
| .mode-selector button{flex:1;min-width:120px;padding:10px;border:none;background-color:#eee;cursor:pointer;margin:2px} |
| .mode-selector button.active{background-color:#4caf50;color:#fff} |
| .file-input{margin-bottom:20px} |
| .file-input input[type=file]{display:none} |
| .file-input label{display:inline-block;padding:10px 15px;background-color:#4caf50;color:#fff;border-radius:4px;cursor:pointer} |
| .settings{margin-bottom:20px;border:1px solid #ddd;padding:15px;border-radius:4px} |
| .settings-panel{display:none} |
| .settings-panel.active{display:block} |
| .setting-item{margin-bottom:15px} |
| .setting-item label{display:block;margin-bottom:5px;font-weight:700} |
| button{padding:10px 15px;background-color:#4caf50;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:16px} |
| button:disabled{background-color:#ccc;cursor:not-allowed} |
| .result-container{margin-top:30px} |
| .image-preview{display:flex;justify-content:space-between;margin:20px 0;flex-wrap:wrap} |
| .canvas-container{width:48%;margin-bottom:10px;position:relative} |
| canvas{max-width:100%;border:1px solid #ddd;box-shadow:0 2px 5px rgba(0,0,0,.1);background-color:#fff} |
| .image-info{font-size:14px;color:#666;margin-top:5px} |
| #downloadBtn{margin-top:10px} |
| .loading{display:none;margin:20px 0;padding:10px;background-color:#f8f8f8;border:1px solid #ddd;border-radius:4px;text-align:center} |
| .result-options{margin-top:10px} |
| .result-options button{margin-right:10px;background-color:#2196f3} |
| .result-options button.download-depth{background-color:#9c27b0} |
| .result-options button.download-semi{background-color:#ff9800} |
| .format-selector{margin-top:15px} |
| .depth-image-container{margin-top:20px;border-top:1px dashed #ddd;padding-top:20px} |
| </style> |
| </head> |
| <body> |
| <div class=container> |
| <h1>画像変換ツール</h1> |
| <div class=mode-selector> |
| <button id=lineDrawingMode class=active>線画モード</button> |
| <button id=blackWhiteMode>白黒画像モード</button> |
| <button id=bgRemovalMode>背景削除</button> |
| <button id=upscaleMode>高画質化</button> |
| </div> |
| <div class=file-input> |
| <input type=file id=imageUpload accept=image/*> |
| <label for=imageUpload>画像を選択</label> |
| </div> |
| <div class=settings> |
| <div id=lineDrawingSettings class="settings-panel active"> |
| <h2>線画設定</h2> |
| <div class=setting-item> |
| <label for=lineThickness>線の太さ</label> |
| <input type=range id=lineThickness min=1 max=20 value=2> |
| <span id=lineThicknessValue>2</span> |
| </div> |
| <div class=setting-item> |
| <label for=lineColor>線の色</label> |
| <input type=color id=lineColor value=#000000> |
| </div> |
| <div class=setting-item> |
| <label for=bgColor>背景色</label> |
| <input type=color id=bgColor value=#ffffff> |
| </div> |
| <div class=setting-item> |
| <label for=bgOpacity>背景の透明度</label> |
| <input type=range id=bgOpacity min=0 max=100 value=100> |
| <span id=bgOpacityValue>100%</span> |
| </div> |
| <div class=setting-item> |
| <label for=sensitivity>感度</label> |
| <input type=range id=sensitivity min=1 max=100 value=50> |
| <span id=sensitivityValue>50</span> |
| </div> |
| </div> |
| <div id=blackWhiteSettings class=settings-panel> |
| <h2>白黒画像設定</h2> |
| <div class=setting-item> |
| <label for=bwBgColor>背景色</label> |
| <input type=color id=bwBgColor value=#ffffff> |
| </div> |
| <div class=setting-item> |
| <label for=bwFgColor>前景色</label> |
| <input type=color id=bwFgColor value=#000000> |
| </div> |
| <div class=setting-item> |
| <label for=threshold>明るさの閾値</label> |
| <input type=range id=threshold min=0 max=100 value=50> |
| <span id=thresholdValue>50%</span> |
| </div> |
| </div> |
| <div id=bgRemovalSettings class=settings-panel> |
| <h2>背景削除設定</h2> |
| <div class=setting-item> |
| <p>背景を削除した画像が生成されます</p> |
| </div> |
| </div> |
| <div id=upscaleSettings class=settings-panel> |
| <h2>高画質化設定</h2> |
| <div class=setting-item> |
| <label for=version>拡大モデル</label> |
| <select id=version name=version> |
| <option value=v1.2>GFPGANv1.2(顔の拡大に特化)</option> |
| <option value=v1.3>GFPGANv1.3(顔の拡大に特化)</option> |
| <option value=v1.4>GFPGANv1.4(顔の拡大に特化)</option> |
| <option value=RestoreFormer selected>RestoreFormer</option> |
| <option value=CodeFormer>CodeFormer</option> |
| <option value=RealESR-General-x4v3>RealESR-General-x4v3</option> |
| </select> |
| </div> |
| <div class=setting-item> |
| <label for=scale>拡大率</label> |
| <input type=range id=scale min=1 max=4 value=2 step=1> |
| <span id=scaleValue>2x</span> |
| </div> |
| </div> |
| </div> |
| <div class=loading id=loadingIndicator> |
| <p>処理中です...しばらくお待ちください</p> |
| </div> |
| <div class=format-selector> |
| <label for=outputFormat>出力フォーマット: </label> |
| <select id=outputFormat> |
| <option value=png selected>PNG</option> |
| <option value=jpeg>JPEG</option> |
| <option value=webp>WebP</option> |
| </select> |
| <label for=jpegQuality style="display:none" id=jpegQualityLabel>品質: </label> |
| <input type=range id=jpegQuality min=1 max=100 value=90 style="display:none;width:100px"> |
| <span id=jpegQualityValue style="display:none">90%</span> |
| </div> |
| <button id=processBtn disabled>変換実行</button> |
| <div class=result-container> |
| <h2>結果</h2> |
| <div class=image-preview> |
| <div class=canvas-container> |
| <h3>元画像</h3> |
| <canvas id=originalCanvas></canvas> |
| <div class=image-info id=originalInfo></div> |
| </div> |
| <div class=canvas-container> |
| <h3>結果画像</h3> |
| <canvas id=resultCanvas></canvas> |
| <div class=image-info id=resultInfo></div> |
| </div> |
| </div> |
| <div class=depth-image-container id=depthImageContainer style=display:none> |
| <h3>深度画像</h3> |
| <canvas id=depthCanvas></canvas> |
| <div class=image-info id=depthInfo></div> |
| </div> |
| <div class=result-options id=resultOptions style=display:none> |
| <button id=downloadBtn>結果をダウンロード</button> |
| <button id=downloadDepthBtn class=download-depth>深度画像をダウンロード</button> |
| <button id=downloadSemiBtn class=download-semi>半透明画像をダウンロード</button> |
| </div> |
| </div> |
| </div> |
| <script> |
| document.addEventListener("DOMContentLoaded",(function(){ |
| const e=document.getElementById("imageUpload"), |
| t=document.getElementById("processBtn"), |
| n=document.getElementById("downloadBtn"), |
| a=document.getElementById("downloadDepthBtn"), |
| i=document.getElementById("downloadSemiBtn"), |
| o=document.getElementById("originalCanvas"), |
| d=document.getElementById("resultCanvas"), |
| depthCanvas=document.getElementById("depthCanvas"), |
| l=document.getElementById("loadingIndicator"), |
| c=document.getElementById("resultOptions"), |
| depthContainer=document.getElementById("depthImageContainer"), |
| s=document.getElementById("lineDrawingMode"), |
| g=document.getElementById("blackWhiteMode"), |
| r=document.getElementById("bgRemovalMode"), |
| m=document.getElementById("upscaleMode"), |
| h=document.getElementById("lineDrawingSettings"), |
| u=document.getElementById("blackWhiteSettings"), |
| f=document.getElementById("bgRemovalSettings"), |
| y=document.getElementById("upscaleSettings"), |
| p=document.getElementById("lineThickness"), |
| I=document.getElementById("lineThicknessValue"), |
| outputFormat=document.getElementById("outputFormat"), |
| jpegQuality=document.getElementById("jpegQuality"), |
| jpegQualityLabel=document.getElementById("jpegQualityLabel"), |
| jpegQualityValue=document.getElementById("jpegQualityValue"), |
| originalInfo=document.getElementById("originalInfo"), |
| resultInfo=document.getElementById("resultInfo"), |
| depthInfo=document.getElementById("depthInfo"); |
| |
| |
| outputFormat.addEventListener("change",function(){ |
| if(outputFormat.value === "jpeg"){ |
| jpegQuality.style.display = "inline-block"; |
| jpegQualityLabel.style.display = "inline-block"; |
| jpegQualityValue.style.display = "inline-block"; |
| }else{ |
| jpegQuality.style.display = "none"; |
| jpegQualityLabel.style.display = "none"; |
| jpegQualityValue.style.display = "none"; |
| } |
| }); |
| |
| jpegQuality.addEventListener("input",function(){ |
| jpegQualityValue.textContent = jpegQuality.value + "%"; |
| }); |
| |
| p.addEventListener("input",(()=>{I.textContent=p.value})); |
| const w=document.getElementById("bgOpacity"),E=document.getElementById("bgOpacityValue"); |
| w.addEventListener("input",(()=>{E.textContent=`${w.value}%`})); |
| const v=document.getElementById("sensitivity"),B=document.getElementById("sensitivityValue"); |
| v.addEventListener("input",(()=>{B.textContent=v.value})); |
| const b=document.getElementById("threshold"),L=document.getElementById("thresholdValue"); |
| b.addEventListener("input",(()=>{L.textContent=`${b.value}%`})); |
| const C=document.getElementById("scale"),k=document.getElementById("scaleValue"); |
| |
| function R(e,t){ |
| [s,g,r,m].forEach((e=>{e.classList.remove("active")})), |
| [h,u,f,y].forEach((e=>{e.classList.remove("active")})), |
| e.classList.add("active"), |
| t.classList.add("active") |
| } |
| |
| C.addEventListener("input",(()=>{k.textContent=`${C.value}x`})), |
| s.addEventListener("click",(()=>{R(s,h)})), |
| g.addEventListener("click",(()=>{R(g,u)})), |
| r.addEventListener("click",(()=>{R(r,f)})), |
| m.addEventListener("click",(()=>{R(m,y)})); |
| |
| let D=null,S=null,x=null,U=null; |
| |
| function $(e,t){return(e[t]+e[t+1]+e[t+2])/3} |
| |
| function O(e){return{r:parseInt(e.slice(1,3),16),g:parseInt(e.slice(3,5),16),b:parseInt(e.slice(5,7),16)}} |
| |
| function M(e,t,n){ |
| const a=document.createElement("a"); |
| a.download=t; |
| |
| let mimeType = "image/png"; |
| if(n === "jpeg") mimeType = "image/jpeg"; |
| if(n === "webp") mimeType = "image/webp"; |
| |
| |
| const img = new Image(); |
| img.onload = function(){ |
| const canvas = document.createElement("canvas"); |
| canvas.width = img.width; |
| canvas.height = img.height; |
| const ctx = canvas.getContext("2d"); |
| ctx.drawImage(img, 0, 0); |
| |
| |
| let dataUrl; |
| if(n === "jpeg"){ |
| dataUrl = canvas.toDataURL(mimeType, jpegQuality.value/100); |
| }else{ |
| dataUrl = canvas.toDataURL(mimeType); |
| } |
| |
| a.href = dataUrl; |
| document.body.appendChild(a); |
| a.click(); |
| document.body.removeChild(a); |
| }; |
| img.src = e; |
| } |
| |
| function updateImageInfo(canvas, infoElement, isOriginal=false){ |
| const format = outputFormat.value; |
| let mimeType = "image/png"; |
| if(format === "jpeg") mimeType = "image/jpeg"; |
| if(format === "webp") mimeType = "image/webp"; |
| |
| let dataUrl; |
| if(format === "jpeg"){ |
| dataUrl = canvas.toDataURL(mimeType, jpegQuality.value/100); |
| }else{ |
| dataUrl = canvas.toDataURL(mimeType); |
| } |
| |
| const size = Math.round((dataUrl.length - 'data:image/png;base64,'.length) * 3 / 4 / 1024); |
| infoElement.textContent = `${canvas.width}×${canvas.height}px | ${size}KB | ${format.toUpperCase()}`; |
| |
| if(isOriginal){ |
| const ctx = canvas.getContext("2d"); |
| const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data; |
| let colorCount = 0; |
| const colors = {}; |
| |
| for(let i=0; i<imageData.length; i+=4){ |
| const r = imageData[i]; |
| const g = imageData[i+1]; |
| const b = imageData[i+2]; |
| const a = imageData[i+3]; |
| const color = `${r},${g},${b},${a}`; |
| |
| if(!colors[color]){ |
| colors[color] = true; |
| colorCount++; |
| } |
| } |
| |
| infoElement.textContent += ` | ${colorCount}色`; |
| } |
| } |
| |
| e.addEventListener("change",(function(e){ |
| if(e.target.files&&e.target.files[0]){ |
| const n=new FileReader; |
| n.onload=function(e){ |
| D=new Image; |
| D.onload=function(){ |
| const e=o.getContext("2d"); |
| o.width=D.width; |
| o.height=D.height; |
| e.fillStyle="#ffffff"; |
| e.fillRect(0,0,o.width,o.height); |
| e.drawImage(D,0,0); |
| d.width=D.width; |
| d.height=D.height; |
| const n=d.getContext("2d"); |
| n.fillStyle="#ffffff"; |
| n.fillRect(0,0,d.width,d.height); |
| t.disabled=!1; |
| c.style.display="none"; |
| depthContainer.style.display="none"; |
| updateImageInfo(o, originalInfo, true); |
| }; |
| D.src=e.target.result; |
| }; |
| n.readAsDataURL(e.target.files[0]); |
| } |
| })); |
| |
| t.addEventListener("click",(async function(){ |
| if(D){ |
| l.style.display="block"; |
| t.disabled=!0; |
| try{ |
| const e=s.classList.contains("active"), |
| t=g.classList.contains("active"), |
| a=r.classList.contains("active"), |
| i=m.classList.contains("active"), |
| l=d.getContext("2d"); |
| |
| l.fillStyle="#ffffff"; |
| l.fillRect(0,0,d.width,d.height); |
| |
| if(e){ |
| |
| const ctx=d.getContext("2d"); |
| d.width=o.width; |
| d.height=o.height; |
| |
| |
| const bgColor=O(document.getElementById("bgColor").value); |
| const bgOpacity=parseInt(document.getElementById("bgOpacity").value)/100; |
| ctx.fillStyle=`rgba(${bgColor.r}, ${bgColor.g}, ${bgColor.b}, ${bgOpacity})`; |
| ctx.fillRect(0,0,d.width,d.height); |
| |
| |
| ctx.drawImage(D,0,0); |
| |
| |
| const imageData=ctx.getImageData(0,0,d.width,d.height); |
| const data=imageData.data; |
| const lineColor=O(document.getElementById("lineColor").value); |
| const lineThickness=parseInt(document.getElementById("lineThickness").value); |
| const sensitivity=parseInt(document.getElementById("sensitivity").value); |
| |
| |
| ctx.fillStyle=`rgba(${bgColor.r}, ${bgColor.g}, ${bgColor.b}, ${bgOpacity})`; |
| ctx.fillRect(0,0,d.width,d.height); |
| |
| |
| for(let y=0;y<d.height;y++){ |
| for(let x=0;x<d.width;x++){ |
| const idx=(y*d.width+x)*4; |
| if(x>0 && y>0 && x<d.width-1 && y<d.height-1){ |
| const brightness=$(data,idx); |
| const right=$(data,idx+4); |
| const bottom=$(data,idx+4*d.width); |
| const diffX=Math.abs(brightness-right); |
| const diffY=Math.abs(brightness-bottom); |
| |
| if(diffX>sensitivity/10 || diffY>sensitivity/10){ |
| ctx.fillStyle=`rgb(${lineColor.r}, ${lineColor.g}, ${lineColor.b})`; |
| ctx.fillRect(x-lineThickness/2, y-lineThickness/2, lineThickness, lineThickness); |
| } |
| } |
| } |
| } |
| |
| S=d.toDataURL("image/png"); |
| updateImageInfo(d, resultInfo); |
| }else if(t){ |
| |
| const ctx=d.getContext("2d"); |
| d.width=o.width; |
| d.height=o.height; |
| |
| const bgColor=O(document.getElementById("bwBgColor").value); |
| const fgColor=O(document.getElementById("bwFgColor").value); |
| const threshold=parseInt(document.getElementById("threshold").value)/100*255; |
| |
| |
| ctx.fillStyle=`rgb(${bgColor.r}, ${bgColor.g}, ${bgColor.b})`; |
| ctx.fillRect(0,0,d.width,d.height); |
| |
| |
| ctx.drawImage(D,0,0); |
| |
| |
| const imageData=ctx.getImageData(0,0,d.width,d.height); |
| const data=imageData.data; |
| |
| |
| for(let i=0;i<data.length;i+=4){ |
| const brightness=$(data,i); |
| if(brightness<=threshold){ |
| data[i]=fgColor.r; |
| data[i+1]=fgColor.g; |
| data[i+2]=fgColor.b; |
| data[i+3]=255; |
| }else{ |
| data[i]=bgColor.r; |
| data[i+1]=bgColor.g; |
| data[i+2]=bgColor.b; |
| data[i+3]=255; |
| } |
| } |
| |
| ctx.putImageData(imageData,0,0); |
| S=d.toDataURL("image/png"); |
| updateImageInfo(d, resultInfo); |
| }else if(a){ |
| |
| try{ |
| const e=o.toDataURL("image/png"); |
| const t=await fetch("https://soiz1-dis-background-removal-api-proxy.hf.space/remove_background",{ |
| method:"POST", |
| headers:{"Content-Type":"application/json"}, |
| body:JSON.stringify({image_url:e}) |
| }); |
| |
| if(!t.ok) throw new Error(`APIエラー: ${t.status}`); |
| |
| const n=await t.json(); |
| const resultImg=new Image; |
| resultImg.onload=function(){ |
| d.width=resultImg.width; |
| d.height=resultImg.height; |
| const e=d.getContext("2d"); |
| e.fillStyle="#ffffff"; |
| e.fillRect(0,0,d.width,d.height); |
| e.drawImage(resultImg,0,0); |
| S=d.toDataURL("image/png"); |
| updateImageInfo(d, resultInfo); |
| |
| |
| if(n.depth_image){ |
| const depthImg=new Image; |
| depthImg.onload=function(){ |
| depthCanvas.width=depthImg.width; |
| depthCanvas.height=depthImg.height; |
| const ctx=depthCanvas.getContext("2d"); |
| ctx.fillStyle="#ffffff"; |
| ctx.fillRect(0,0,depthCanvas.width,depthCanvas.height); |
| ctx.drawImage(depthImg,0,0); |
| x=depthCanvas.toDataURL("image/png"); |
| updateImageInfo(depthCanvas, depthInfo); |
| depthContainer.style.display="block"; |
| }; |
| depthImg.src=n.depth_image; |
| }else{ |
| depthContainer.style.display="none"; |
| } |
| |
| if(n.semi_transparent_image){ |
| U=n.semi_transparent_image; |
| } |
| }; |
| resultImg.src=n.semi_transparent_image; |
| }catch(e){ |
| console.error("背景削除処理中にエラーが発生しました:",e); |
| alert("背景削除処理中にエラーが発生しました: "+e.message); |
| } |
| }else if(i){ |
| |
| try{ |
| const e=document.getElementById("version").value, |
| t=parseInt(document.getElementById("scale").value), |
| n=new FormData; |
| n.append("file",function(e){ |
| const t=e.split(","), |
| n=t[0].match(/:(.*?);/)[1], |
| a=atob(t[1]); |
| let i=a.length; |
| const o=new Uint8Array(i); |
| for(;i--;)o[i]=a.charCodeAt(i); |
| return new Blob([o],{type:n}) |
| }(o.toDataURL("image/png"))); |
| n.append("version",e); |
| n.append("scale",t); |
| |
| const a=await fetch("https://soiz1-image-face-upscale-restoration-gfpgan-api.hf.space/api/restore",{ |
| method:"POST", |
| body:n |
| }); |
| |
| if(!a.ok) throw new Error(`APIエラー: ${a.status}`); |
| |
| const i=await a.blob(), |
| l=URL.createObjectURL(i), |
| c=new Image; |
| c.onload=function(){ |
| d.width=c.width; |
| d.height=c.height; |
| const e=d.getContext("2d"); |
| e.fillStyle="#ffffff"; |
| e.fillRect(0,0,d.width,d.height); |
| e.drawImage(c,0,0); |
| S=d.toDataURL("image/png"); |
| updateImageInfo(d, resultInfo); |
| URL.revokeObjectURL(l); |
| }; |
| c.src=l; |
| }catch(e){ |
| console.error("高画質化処理中にエラーが発生しました:",e); |
| alert("高画質化処理中にエラーが発生しました: "+e.message); |
| } |
| } |
| |
| t.disabled=!1; |
| c.style.display=a?"block":"none"; |
| }catch(e){ |
| console.error("処理中にエラーが発生しました:",e); |
| alert("処理中にエラーが発生しました: "+e.message); |
| }finally{ |
| l.style.display="none"; |
| t.disabled=!1; |
| } |
| } |
| })); |
| |
| n.addEventListener("click",(function(){ |
| S&&M(S,"converted-image."+outputFormat.value, outputFormat.value); |
| })); |
| |
| a.addEventListener("click",(function(){ |
| x&&M(x,"depth-image.png", "png"); |
| })); |
| |
| i.addEventListener("click",(function(){ |
| U&&M(U,"semi-transparent-image.png", "png"); |
| })); |
| })); |
| </script> |
| </body> |
| </html> |