Skip to content

Commit 0795cb5

Browse files
ayutazclaude
andcommitted
fix: 境界線のつぶれを改善
- ピクセル化処理を直接的な平均化アルゴリズムに変更 - エッジ検出(Sobelフィルタ)を追加して境界線を強調 - nearest-neighbor補間を使用してシャープなエッジを保持 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent df11fa8 commit 0795cb5

1 file changed

Lines changed: 103 additions & 13 deletions

File tree

src/js/app-v3.js

Lines changed: 103 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -479,10 +479,14 @@
479479
canvas.width = settings.outputWidth;
480480
canvas.height = outputHeight;
481481

482-
ctx.imageSmoothingEnabled = true;
482+
// Use nearest-neighbor scaling for sharper edges
483+
ctx.imageSmoothingEnabled = false;
483484
ctx.drawImage(img, 0, 0, settings.outputWidth, outputHeight);
484485

485-
const pixelatedCanvas = this.pixelate(canvas, settings.pixelSize);
486+
// Apply edge enhancement before pixelation
487+
const enhancedCanvas = this.enhanceEdges(canvas);
488+
489+
const pixelatedCanvas = this.pixelate(enhancedCanvas, settings.pixelSize);
486490

487491
// Apply custom palette or auto quantization
488492
const quantizedCanvas = settings.customPalette
@@ -539,30 +543,116 @@
539543
return nearestColor;
540544
}
541545

542-
pixelate(canvas, pixelSize) {
546+
enhanceEdges(canvas) {
543547
const ctx = canvas.getContext('2d');
548+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
549+
const data = imageData.data;
544550
const width = canvas.width;
545551
const height = canvas.height;
546552

547-
const scaledWidth = Math.ceil(width / pixelSize);
548-
const scaledHeight = Math.ceil(height / pixelSize);
553+
// Create a copy for edge detection
554+
const edgeData = new Uint8ClampedArray(data);
549555

550-
const smallCanvas = document.createElement('canvas');
551-
smallCanvas.width = scaledWidth;
552-
smallCanvas.height = scaledHeight;
553-
const smallCtx = smallCanvas.getContext('2d');
556+
// Sobel operator for edge detection
557+
const sobelX = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
558+
const sobelY = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
554559

555-
smallCtx.imageSmoothingEnabled = true;
556-
smallCtx.drawImage(canvas, 0, 0, scaledWidth, scaledHeight);
560+
for (let y = 1; y < height - 1; y++) {
561+
for (let x = 1; x < width - 1; x++) {
562+
let pixelX = 0;
563+
let pixelY = 0;
564+
565+
// Apply Sobel operator
566+
for (let j = -1; j <= 1; j++) {
567+
for (let i = -1; i <= 1; i++) {
568+
const idx = ((y + j) * width + (x + i)) * 4;
569+
const gray = data[idx] * 0.299 + data[idx + 1] * 0.587 + data[idx + 2] * 0.114;
570+
const kernelIdx = (j + 1) * 3 + (i + 1);
571+
pixelX += gray * sobelX[kernelIdx];
572+
pixelY += gray * sobelY[kernelIdx];
573+
}
574+
}
575+
576+
const magnitude = Math.sqrt(pixelX * pixelX + pixelY * pixelY);
577+
const idx = (y * width + x) * 4;
578+
579+
// Enhance edges by darkening them slightly
580+
if (magnitude > 30) {
581+
const factor = Math.min(1.2, 1 + magnitude / 255);
582+
edgeData[idx] = Math.max(0, data[idx] * 0.8);
583+
edgeData[idx + 1] = Math.max(0, data[idx + 1] * 0.8);
584+
edgeData[idx + 2] = Math.max(0, data[idx + 2] * 0.8);
585+
}
586+
}
587+
}
557588

589+
// Create result canvas
558590
const resultCanvas = document.createElement('canvas');
559591
resultCanvas.width = width;
560592
resultCanvas.height = height;
561593
const resultCtx = resultCanvas.getContext('2d');
594+
const resultImageData = resultCtx.createImageData(width, height);
595+
resultImageData.data.set(edgeData);
596+
resultCtx.putImageData(resultImageData, 0, 0);
562597

563-
resultCtx.imageSmoothingEnabled = false;
564-
resultCtx.drawImage(smallCanvas, 0, 0, width, height);
598+
return resultCanvas;
599+
}
600+
601+
pixelate(canvas, pixelSize) {
602+
const ctx = canvas.getContext('2d');
603+
const width = canvas.width;
604+
const height = canvas.height;
605+
const imageData = ctx.getImageData(0, 0, width, height);
606+
const data = imageData.data;
607+
608+
// Create result canvas
609+
const resultCanvas = document.createElement('canvas');
610+
resultCanvas.width = width;
611+
resultCanvas.height = height;
612+
const resultCtx = resultCanvas.getContext('2d');
613+
const resultImageData = resultCtx.createImageData(width, height);
614+
const resultData = resultImageData.data;
615+
616+
// Process each pixel block
617+
for (let y = 0; y < height; y += pixelSize) {
618+
for (let x = 0; x < width; x += pixelSize) {
619+
const blockWidth = Math.min(pixelSize, width - x);
620+
const blockHeight = Math.min(pixelSize, height - y);
621+
622+
// Calculate average color for this block
623+
let r = 0, g = 0, b = 0, a = 0;
624+
let count = 0;
625+
626+
for (let dy = 0; dy < blockHeight; dy++) {
627+
for (let dx = 0; dx < blockWidth; dx++) {
628+
const index = ((y + dy) * width + (x + dx)) * 4;
629+
r += data[index];
630+
g += data[index + 1];
631+
b += data[index + 2];
632+
a += data[index + 3];
633+
count++;
634+
}
635+
}
636+
637+
// Apply average color to entire block
638+
const avgR = Math.round(r / count);
639+
const avgG = Math.round(g / count);
640+
const avgB = Math.round(b / count);
641+
const avgA = Math.round(a / count);
642+
643+
for (let dy = 0; dy < blockHeight; dy++) {
644+
for (let dx = 0; dx < blockWidth; dx++) {
645+
const index = ((y + dy) * width + (x + dx)) * 4;
646+
resultData[index] = avgR;
647+
resultData[index + 1] = avgG;
648+
resultData[index + 2] = avgB;
649+
resultData[index + 3] = avgA;
650+
}
651+
}
652+
}
653+
}
565654

655+
resultCtx.putImageData(resultImageData, 0, 0);
566656
return resultCanvas;
567657
}
568658

0 commit comments

Comments
 (0)