resim-duzenleyici/_js_check.js

370 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Numex Codex Resim Düzenleyici
* Saf Canvas API — harici kütüphane yok.
* Modüler yaklaşım: her davranış (durum, canvas, görüntü, filtre,
* kırpma, döndürme, işlemler) kendi küçük nesnesinde toplanır.
*/
(function () {
'use strict';
/* ==========================================================
* 1. DURUM (State) — Uygulamanın tek gerçek kaynağı
* ========================================================== */
const State = {
originalImage: null, // Orijinal Image nesnesi (yüklenen)
displayImage: null, // Mevcut düzenlenmiş Image (kırp/döndür sonrası)
brightness: 100, // % (100 = nötr)
contrast: 100, // % (100 = nötr)
saturation: 100, // % (100 = nötr)
grayscale: false, // Gri tonlama aktif mi
sepia: false, // Sepia aktif mi
cropMode: false, // Kırpma modu açık mı
// Orijinal değerlere dönmek için saklanan yedek
backup() {
return {
displayImage: this.displayImage,
brightness: this.brightness,
contrast: this.contrast,
saturation: this.saturation,
grayscale: this.grayscale,
sepia: this.sepia
};
},
restore(snapshot) {
this.displayImage = snapshot.displayImage;
this.brightness = snapshot.brightness;
this.contrast = snapshot.contrast;
this.saturation = snapshot.saturation;
this.grayscale = snapshot.grayscale;
this.sepia = snapshot.sepia;
}
};
/* ==========================================================
* 2. DOM YARDIMCILARI — Element erişimini tek yerden yönet
* ========================================================== */
const Dom = {
fileInput: document.getElementById('imageLoader'),
canvas: document.getElementById('imageCanvas'),
ctx: null,
init() {
this.ctx = this.canvas.getContext('2d');
}
};
/* ==========================================================
* 3. GÖRÜNTÜ (Image) — Yükleme, ayarlama, sıfırlama
* ========================================================== */
const ImageManager = {
load(file) {
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
const img = new Image();
img.onload = () => {
State.originalImage = img;
State.displayImage = img;
resetAdjustments();
CanvasD.setup(img.width, img.height);
Renderer.render();
};
img.onerror = () => alert('Resim yüklenemedi! Lütfen geçerli bir resim dosyası seçin.');
img.src = event.target.result;
};
reader.readAsDataURL(file);
}
};
/* ==========================================================
* 4. CANVAS — Boyut ayarlama ve tuvale çizim
* ========================================================== */
const CanvasD = {
setup(width, height) {
const maxW = 800, maxH = 600;
let w = width, h = height;
if (w > maxW || h > maxH) {
const ratio = Math.min(maxW / w, maxH / h);
w = Math.round(w * ratio);
h = Math.round(h * ratio);
}
Dom.canvas.width = w;
Dom.canvas.height = h;
},
get ctx() { return Dom.ctx; }
};
/* ==========================================================
* 5. RENDER; — Tüm görsel ayarları uygulayıp tuvale çizer
* ========================================================== */
const Renderer = {
render() {
const img = State.displayImage;
if (!img) return;
const ctx = CanvasD.ctx;
// Mevcut kanvas boyutuna resmi sığdır (boyut korunsun)
CanvasD.setup(img.width, img.height);
ctx.clearRect(0, 0, Dom.canvas.width, Dom.canvas.height);
ctx.drawImage(img, 0, 0, Dom.canvas.width, Dom.canvas.height);
// CSS filtreleri tek seferde birleştirip uygula
let filterString = 'none';
if (State.brightness !== 100 || State.contrast !== 100 || State.saturation !== 100
|| State.grayscale || State.sepia) {
const parts = [];
parts.push('brightness(' + State.brightness + '%)');
parts.push('contrast(' + State.contrast + '%)');
parts.push('saturate(' + State.saturation + '%)');
if (State.grayscale) parts.push('grayscale(1)');
if (State.sepia) parts.push('sepia(1)');
filterString = parts.join(' ');
}
ctx.filter = filterString;
ctx.drawImage(img, 0, 0, Dom.canvas.width, Dom.canvas.height);
ctx.filter = 'none'; // Sonraki çizimleri etkilemesin
}
};
/* ==========================================================
* 6. AYARLAR (Filters) — Parlaklık/kontrast/doygunluk + renk filtreleri
* ========================================================== */
const Adjustments = {
brightness() { State.brightness = +DomBrightness.value; Renderer.render(); },
contrast() { State.contrast = +DomContrast.value; Renderer.render(); },
saturation() { State.saturation = +DomSaturation.value; Renderer.render(); },
toggleGrayscale() {
State.grayscale = !State.grayscale;
if (State.grayscale) State.sepia = false; // ikisi birden olmaz
updateFilterButtons();
Renderer.render();
},
toggleSepia() {
State.sepia = !State.sepia;
if (State.sepia) State.grayscale = false;
updateFilterButtons();
Renderer.render();
},
reset() {
State.brightness = 100;
State.contrast = 100;
State.saturation = 100;
State.grayscale = false;
State.sepia = false;
updateFilterButtons();
syncSliders();
Renderer.render();
}
};
/* ==========================================================
* 7. KIRPMA (Crop) — Sürükleme ile bölge seç, naklet
* ========================================================== */
const Crop = {
startX: 0, startY: 0, endX: 0, endY: 0,
active: false,
enterMode() {
if (!State.displayImage) { alert('Önce bir resim yükleyin!'); return; }
State.cropMode = true;
this.capture();
},
capture() {
State.cropMode = true;
const rect = Dom.canvas.getBoundingClientRect();
const scaleX = Dom.canvas.width / rect.width;
const scaleY = Dom.canvas.height / rect.height;
Dom.canvas.style.cursor = 'crosshair';
const onMouseDown = (e) => {
this.active = true;
this.startX = (e.clientX - rect.left) * scaleX;
this.startY = (e.clientY - rect.top) * scaleY;
this.endX = this.startX;
this.endY = this.startY;
};
const onMouseMove = (e) => {
if (!this.active) return;
this.endX = (e.clientX - rect.left) * scaleX;
this.endY = (e.clientY - rect.top) * scaleY;
this.drawOverlay();
};
const onMouseUp = () => {
if (!this.active) return;
this.active = false;
Dom.canvas.style.cursor = 'default';
this.commit();
Dom.canvas.removeEventListener('mousedown', onMouseDown);
Dom.canvas.removeEventListener('mousemove', onMouseMove);
Dom.canvas.removeEventListener('mouseup', onMouseUp);
};
Dom.canvas.addEventListener('mousedown', onMouseDown);
Dom.canvas.addEventListener('mousemove', onMouseMove);
Dom.canvas.addEventListener('mouseup', onMouseUp);
},
drawOverlay() {
const ctx = CanvasD.ctx;
ctx.clearRect(0, 0, Dom.canvas.width, Dom.canvas.height);
// Orijinal görüntüyü çiz, sonra seçimi vurgula
ctx.drawImage(State.displayImage, 0, 0, Dom.canvas.width, Dom.canvas.height);
const x = Math.min(this.startX, this.endX);
const y = Math.min(this.startY, this.endY);
const w = Math.abs(this.endX - this.startX);
const h = Math.abs(this.endY - this.startY);
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
// Dört kenar boyunca karart
ctx.fillRect(0, 0, Dom.canvas.width, y);
ctx.fillRect(0, y + h, Dom.canvas.width, Dom.canvas.height - (y + h));
ctx.fillRect(0, y, x, h);
ctx.fillRect(x + w, y, Dom.canvas.width - (x + w), h);
ctx.strokeStyle = 'rgba(0, 123, 255, 0.9)';
ctx.lineWidth = 2;
ctx.strokeRect(x, y, w, h);
},
commit() {
const x = Math.min(this.startX, this.endX);
const y = Math.min(this.startY, this.endY);
const w = Math.abs(this.endX - this.startX);
const h = Math.abs(this.endY - this.startY);
if (w < 5 || h < 5) {
Renderer.render(); // Çok küçük seçim → iptal
return;
}
// Yeni bir offscreen canvas'ta seçilen bölgeyi kırp
const cropCanvas = document.createElement('canvas');
cropCanvas.width = Math.round(w);
cropCanvas.height = Math.round(h);
const cropCtx = cropCanvas.getContext('2d');
cropCtx.drawImage(State.displayImage, Math.round(x), Math.round(y), Math.round(w), Math.round(h), 0, 0, Math.round(w), Math.round(h));
// Kırpılmış görüntüyü yeni bir Image olarak ayarla
const img = new Image();
img.onload = () => {
State.displayImage = img;
Renderer.render();
};
img.src = cropCanvas.toDataURL();
State.cropMode = false;
}
};
/* ==========================================================
* 8. DÖNDÜRME (Rotate) — 90° adımlarla, resim yeniden örneklenir
* ========================================================== */
const Rotator = {
rotate(angle) {
if (!State.displayImage) { alert('Önce bir resim yükleyin!'); return; }
const img = State.displayImage;
const rotated = document.createElement('canvas');
if (angle === 90 || angle === -90) {
rotated.width = img.height;
rotated.height = img.width;
} else {
rotated.width = img.width;
rotated.height = img.height;
}
const rctx = rotated.getContext('2d');
rctx.translate(rotated.width / 2, rotated.height / 2);
rctx.rotate(angle * Math.PI / 180);
rctx.drawImage(img, -img.width / 2, -img.height / 2);
const result = new Image();
result.onload = () => {
State.displayImage = result;
Renderer.render();
};
result.src = rotated.toDataURL();
},
left() { this.rotate(90); },
right() { this.rotate(-90); }
};
/* ==========================================================
* 9. İNDİRME (Export) — Canvas'ı PNG olarak kaydet
* ========================================================== */
const Export = {
download() {
if (!State.displayImage) { alert('Önce bir resim yükleyin!'); return; }
const link = document.createElement('a');
link.download = 'duzenlenmis-resim.png';
link.href = Dom.canvas.toDataURL('image/png');
link.click();
},
reset() {
if (!State.originalImage) { alert('Henüz resim yüklenmedi, sıfırlanacak bir şey yok.'); return; }
State.displayImage = State.originalImage;
resetAdjustments();
Renderer.render();
}
};
/* ==========================================================
* 10. SLAYT SENKRON VE SIFIRLAMA YARDIMCILARI
* ========================================================== */
const DomBrightness = document.getElementById('brightness');
const DomContrast = document.getElementById('contrast');
const DomSaturation = document.getElementById('saturation');
function resetAdjustments() {
State.brightness = 100;
State.contrast = 100;
State.saturation = 100;
State.grayscale = false;
State.sepia = false;
updateFilterButtons();
syncSliders();
}
function updateFilterButtons() {
const gBtn = document.getElementById('grayscaleBtn');
const sBtn = document.getElementById('sepiaBtn');
gBtn.classList.toggle('active-filter', State.grayscale);
sBtn.classList.toggle('active-filter', State.sepia);
}
function syncSliders() {
DomBrightness.value = State.brightness;
DomContrast.value = State.contrast;
DomSaturation.value = State.saturation;
}
/* ==========================================================
* 11. OLAY BAĞLANTILARI (Event Wiring)
* ========================================================== */
function wireEvents() {
Dom.fileInput.addEventListener('change', (e) => ImageManager.load(e.target.files[0]));
document.getElementById('brightness').addEventListener('input', Adjustments.brightness);
document.getElementById('contrast').addEventListener('input', Adjustments.contrast);
document.getElementById('saturation').addEventListener('input', Adjustments.saturation);
document.getElementById('grayscaleBtn').addEventListener('click', Adjustments.toggleGrayscale);
document.getElementById('sepiaBtn').addEventListener('click', Adjustments.toggleSepia);
document.getElementById('resetFiltersBtn').addEventListener('click', Adjustments.reset);
document.getElementById('rotateLeftBtn').addEventListener('click', () => Rotator.left());
document.getElementById('rotateRightBtn').addEventListener('click', () => Rotator.right());
document.getElementById('cropBtn').addEventListener('click', () => Crop.enterMode());
document.getElementById('downloadBtn').addEventListener('click', Export.download);
document.getElementById('resetBtn').addEventListener('click', Export.reset);
}
/* ==========================================================
* 12. UYGULAMA BAŞLATMA
* ========================================================== */
function init() {
Dom.init();
wireEvents();
}
document.addEventListener('DOMContentLoaded', init);
})();