Created
September 21, 2023 12:41
-
-
Save motsu0/65ae3596123465925477509b2928ba12 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.sample-img { | |
width: 200px; | |
} | |
/* */ | |
.settings { | |
display: flex; | |
flex-direction: column; | |
row-gap: 12px; | |
align-items: center; | |
} | |
.text-input { | |
width: 100%; | |
max-width: 300px; | |
height: 2em; | |
padding: 0 2px; | |
} | |
#submit-bt { | |
width: 100px; | |
height: 44px; | |
cursor: pointer; | |
} | |
/* */ | |
.canvas-area { | |
margin: 12px 0; | |
text-align: center; | |
} | |
.canvas-container { | |
box-sizing: border-box; | |
display: inline-flex; | |
max-width: 90%; | |
position: relative; | |
border: 1px solid #ccc; | |
overflow: hidden; | |
} | |
#base-canvas { | |
width: 100%; | |
max-height: 80vh; | |
} | |
#letter-canvas { | |
width: 80%; | |
height: 100%; | |
position: absolute; | |
top: 0; | |
left: 50%; | |
transform: translateX(-50%); | |
} | |
/* */ | |
.output { | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
row-gap: 12px; | |
} | |
.image-type-setting { | |
display: flex; | |
flex-direction: column; | |
row-gap: 8px; | |
} | |
.image-type-title { | |
font-weight: bold; | |
border-bottom: 1px solid #333; | |
} | |
.image-type-setting__row { | |
display: flex; | |
column-gap: 12px; | |
} | |
#quality-input { | |
width: 60px; | |
} | |
#output-bt { | |
padding: 4px 8px; | |
cursor: pointer; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="settings"> | |
<div class="settings__row"> | |
<input type="file" id="file-input" /> | |
</div> | |
<div class="settings__row"> | |
肩書き<br /> | |
<input | |
type="text" | |
id="title-input" | |
class="text-input" | |
placeholder="肩書き" | |
/> | |
</div> | |
<div class="settings__row"> | |
名前<br /> | |
<input | |
type="text" | |
id="name-input" | |
class="text-input" | |
placeholder="名前" | |
/> | |
</div> | |
<div class="settngs__row"> | |
<button id="submit-bt" disabled>作成</button> | |
</div> | |
</div> | |
<div class="canvas-area"> | |
<div class="canvas-container"> | |
<canvas id="letter-canvas" width="0" height="0"></canvas> | |
<canvas id="base-canvas" width="0" height="0"></canvas> | |
</div> | |
</div> | |
<div class="output"> | |
<div class="image-type-setting"> | |
<div class="image-type-title">出力タイプ</div> | |
<div class="image-type-setting__row"> | |
<label class="settings-label"> | |
<input | |
type="radio" | |
value="jpg" | |
name="image-type" | |
id="jpg-radio" | |
checked | |
/> | |
JPG | |
</label> | |
<div> | |
(画質: | |
<input | |
type="text" | |
id="quality-input" | |
value="90" | |
min="0" | |
max="100" | |
/>) | |
</div> | |
</div> | |
<div class="image-type-setting__row"> | |
<label class="settings-label"> | |
<input | |
type="radio" | |
value="png" | |
id="png-radio" | |
name="image-type" | |
/> | |
PNG | |
</label> | |
</div> | |
</div> | |
<button id="output-bt">保存する</button> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const nowloading = new nowLoading(); | |
nowloading.start(); | |
const modal = new simpleImgModal(); | |
const dialog = new simpleDialog({ | |
str_head: 'エラー', | |
}); | |
const fileInput = document.getElementById('file-input'); | |
const titleInput = document.getElementById('title-input'); | |
const nameInput = document.getElementById('name-input'); | |
const submitBt = document.getElementById('submit-bt'); | |
const baseCanvas = document.getElementById('base-canvas'); | |
const baseCtx = baseCanvas.getContext('2d'); | |
const letterCanvas = document.getElementById('letter-canvas'); | |
const letterCtx = letterCanvas.getContext('2d'); | |
const jpgRadio = document.getElementById('jpg-radio'); | |
const pngRadio = document.getElementById('png-radio'); | |
const qualityInput = document.getElementById('quality-input'); | |
const outputBt = document.getElementById('output-bt'); | |
const fontName = 'aoyagireisyosimo'; | |
const fontPath = '/fonts/aoyagireisyosimo.otf'; | |
const loadFontPromise = loadLocalFont(fontName, fontPath); | |
fileInput.addEventListener('change', checkFile); | |
submitBt.addEventListener('click', drawLetter); | |
[jpgRadio, pngRadio].forEach((el) => { | |
el.addEventListener('change', () => { | |
if (!jpgRadio.checked) { | |
qualityInput.disabled = true; | |
} else { | |
qualityInput.disabled = false; | |
} | |
}); | |
}); | |
outputBt.addEventListener('click', downloadImage); | |
nowloading.stop(); | |
async function drawLetter() { | |
if (baseCanvas.width === '') return; | |
letterCtx.clearRect(0, 0, letterCanvas.width, letterCanvas.height); | |
letterCanvas.width = baseCanvas.width; | |
letterCanvas.height = baseCanvas.height; | |
await loadFontPromise; | |
letterCtx.textBaseline = 'middle'; | |
letterCtx.textAlign = 'center'; | |
// 肩書 サイズ | |
const titleFontSize = Math.round(letterCanvas.width / 16); | |
const titleY = letterCanvas.height - titleFontSize * 3.4; | |
letterCtx.font = `${titleFontSize}px '${fontName}'`; | |
// letterCtx.letterSpacing = `${-Math.round(titleFontSize / 6)}px`; | |
// 肩書 縁黒 | |
letterCtx.shadowBlur = Math.max(titleFontSize / 5, 2); | |
letterCtx.shadowColor = '#000000'; | |
letterCtx.lineWidth = Math.max(titleFontSize / 10, 2); | |
letterCtx.strokeStyle = '#000000'; | |
letterCtx.strokeText( | |
titleInput.value, | |
letterCanvas.width / 2, | |
titleY, | |
letterCanvas.width | |
); | |
letterCtx.shadowBlur = 0; | |
// 肩書 縁白 | |
letterCtx.lineWidth = Math.max(titleFontSize / 24, 1); | |
letterCtx.strokeStyle = '#ffffff'; | |
letterCtx.strokeText( | |
titleInput.value, | |
letterCanvas.width / 2, | |
titleY, | |
letterCanvas.width | |
); | |
// 肩書 赤 | |
(letterCtx.fillStyle = 'rgb(180 0 0)'), | |
letterCtx.fillText( | |
titleInput.value, | |
letterCanvas.width / 2, | |
titleY, | |
letterCanvas.width | |
); | |
// 名前 サイズ | |
const nameFontSize = Math.round(letterCanvas.width / 6); | |
const nameY = letterCanvas.height - nameFontSize * 0.6; | |
letterCtx.font = `${nameFontSize}px '${fontName}', serif`; | |
letterCtx.letterSpacing = `${Math.round(nameFontSize / 10)}px`; | |
// 名前 縁黒 | |
letterCtx.shadowBlur = Math.max(nameFontSize / 12, 2); | |
letterCtx.shadowColor = '#000000'; | |
letterCtx.lineWidth = Math.max(nameFontSize / 20, 2); | |
letterCtx.strokeStyle = '#000000'; | |
letterCtx.strokeText( | |
nameInput.value, | |
letterCanvas.width / 2, | |
nameY, | |
letterCanvas.width | |
); | |
letterCtx.shadowBlur = 0; | |
// 名前 縁白 | |
letterCtx.lineWidth = Math.max(nameFontSize / 40, 1); | |
letterCtx.strokeStyle = '#ffffff'; | |
letterCtx.strokeText( | |
nameInput.value, | |
letterCanvas.width / 2, | |
nameY, | |
letterCanvas.width | |
); | |
// 名前 赤 | |
letterCtx.fillStyle = 'rgb(180 0 0)'; | |
letterCtx.fillText( | |
nameInput.value, | |
letterCanvas.width / 2, | |
nameY, | |
letterCanvas.width | |
); | |
} | |
function drawBase(img) { | |
baseCanvas.width = img.naturalWidth; | |
baseCanvas.height = img.naturalHeight; | |
baseCtx.drawImage(img, 0, 0); | |
const imageData = baseCtx.getImageData( | |
0, | |
0, | |
baseCanvas.width, | |
baseCanvas.height | |
); | |
for (let i = 0; i < imageData.data.length; i += 4) { | |
const red = imageData.data[i]; | |
const green = imageData.data[i + 1]; | |
const blue = imageData.data[i + 2]; | |
const gray = Math.round((red + green + blue) / 3); | |
imageData.data[i] = gray; | |
imageData.data[i + 1] = gray; | |
imageData.data[i + 2] = gray; | |
} | |
baseCtx.putImageData(imageData, 0, 0); | |
// | |
const radius = { | |
min: Math.min(baseCanvas.width / 2, baseCanvas.height / 2), | |
max: Math.max(baseCanvas.width / 2, baseCanvas.height / 2), | |
}; | |
const gradient = baseCtx.createRadialGradient( | |
baseCanvas.width / 2, | |
baseCanvas.height / 2, | |
radius.min * 0.3, | |
baseCanvas.width / 2, | |
baseCanvas.height / 2, | |
radius.max | |
); | |
gradient.addColorStop(0.0, 'rgb(0 0 0 /0)'); | |
gradient.addColorStop(1.0, 'rgb(0 0 0 /.5)'); | |
baseCtx.fillStyle = gradient; | |
baseCtx.fillRect(0, 0, baseCanvas.width, baseCanvas.height); | |
// | |
submitBt.disabled = false; | |
drawLetter(); | |
} | |
function checkFile(e) { | |
nowloading.start(); | |
const files = e.target.files; | |
if (files.length === 0) { | |
nowloading.stop(); | |
return; | |
} | |
const file = files[0]; | |
if (!file.type.includes('image')) { | |
dialog.setStrBody('画像を選択してください。'); | |
dialog.display(); | |
nowloading.stop(); | |
return; | |
} | |
const reader = new FileReader(); | |
reader.onload = (ev) => { | |
const img = document.createElement('img'); | |
img.onload = () => { | |
if (img.naturalWidth > 10000 || img.naturalHeight > 10000) { | |
dialog.setStrBody('画像サイズが大きすぎます。'); | |
dialog.display(); | |
} else { | |
drawBase(img); | |
} | |
nowloading.stop(); | |
}; | |
img.src = ev.target.result; | |
}; | |
reader.readAsDataURL(file); | |
} | |
function downloadImage() { | |
nowloading.start(); | |
if (baseCanvas.width === 0 || baseCanvas.height === 0) { | |
dialog.setStrBody('画像が存在しません。'); | |
dialog.display(); | |
nowloading.stop(); | |
return; | |
} | |
const outputCanvas = document.createElement('canvas'); | |
outputCanvas.width = baseCanvas.width; | |
outputCanvas.height = baseCanvas.height; | |
const outputCtx = outputCanvas.getContext('2d'); | |
outputCtx.drawImage(baseCanvas, 0, 0); | |
const dWidth = letterCanvas.width * 0.8; | |
const dx = (letterCanvas.width - dWidth) / 2; | |
outputCtx.drawImage( | |
letterCanvas, | |
dx, | |
0, | |
dWidth, | |
letterCanvas.height | |
); | |
const imageType = jpgRadio.checked ? 'jpg' : 'png'; | |
const alink = document.createElement('a'); | |
const quality = (() => { | |
let temp = Number(qualityInput.value); | |
if (Number.isNaN(temp)) return 1; | |
if (temp < 0) return 0; | |
if (temp > 100) return 1; | |
return temp / 100; | |
})(); | |
if (imageType === 'jpg') { | |
outputCanvas.toBlob( | |
(blob) => { | |
alink.href = window.URL.createObjectURL(blob); | |
alink.download = 'output.jpg'; | |
alink.click(); | |
URL.revokeObjectURL(blob); | |
nowloading.stop(); | |
}, | |
'image/jpeg', | |
quality | |
); | |
} else { | |
outputCanvas.toBlob((blob) => { | |
alink.href = window.URL.createObjectURL(blob); | |
alink.download = 'output.png'; | |
alink.click(); | |
URL.revokeObjectURL(blob); | |
nowloading.stop(); | |
}, 'image/png'); | |
} | |
} | |
async function loadLocalFont(fontName, path, weight) { | |
const options = {}; | |
if (weight !== undefined) options.weight = weight; | |
const font = new FontFace(fontName, `url(${path})`, options); | |
await font.load(); | |
document.fonts.add(font); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment