Numex yayın: index.html
This commit is contained in:
parent
8756a5722e
commit
95222a74c7
598
index.html
Normal file
598
index.html
Normal file
|
|
@ -0,0 +1,598 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="tr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Numex Codex Resim Düzenleyici</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #f0f2f5;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||||
|
padding: 30px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: min(900px, 95vw); /* Geniş ekranda 900px, dar ekranda %95 — taşmaz */
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 25px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #1a202c;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.6em; /* mobil: küçük */
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
@media (min-width: 640px) { h1 { font-size: 1.9em; } } /* sm */
|
||||||
|
@media (min-width: 768px) { h1 { font-size: 2.2em; } } /* md */
|
||||||
|
.controls, .filters, .actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 15px 0;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
.controls label, .filters label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #555;
|
||||||
|
min-width: 80px;
|
||||||
|
min-height: 44px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.controls input[type="range"] {
|
||||||
|
width: 100px;
|
||||||
|
margin-top: 8px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
height: 44px; /* Dokunma/işaret hedefi büyük */
|
||||||
|
background: transparent;
|
||||||
|
outline: none;
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: opacity .2s;
|
||||||
|
background-image: linear-gradient(#ddd, #ddd);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100% 6px;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
.controls input[type="range"]::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #007bff;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
|
||||||
|
}
|
||||||
|
.controls input[type="range"]::-moz-range-thumb {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #007bff;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: #0b5ed7; /* Daha koyu → kontrast güçlü */
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
min-height: 44px; /* Dokunma hedefi ≥ 44px */
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1em;
|
||||||
|
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||||
|
box-shadow: 0 2px 10px rgba(11, 94, 215, 0.25);
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #084298;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
button:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
button#downloadBtn {
|
||||||
|
background-color: #198754;
|
||||||
|
}
|
||||||
|
button#downloadBtn:hover {
|
||||||
|
background-color: #157347;
|
||||||
|
}
|
||||||
|
button#resetBtn {
|
||||||
|
background-color: #bb2d3b;
|
||||||
|
}
|
||||||
|
button#resetBtn:hover {
|
||||||
|
background-color: #b02a37;
|
||||||
|
}
|
||||||
|
/* Aktif filtre durumu işareti (gri tonlama / sepia) */
|
||||||
|
button.active-filter {
|
||||||
|
outline: 3px solid #ffc107;
|
||||||
|
outline-offset: 2px;
|
||||||
|
box-shadow: 0 0 0 4px rgba(255, 193, 7, 0.4);
|
||||||
|
}
|
||||||
|
.canvas-area {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
min-height: 400px;
|
||||||
|
overflow: hidden; /* Canvas'ı taşma durumunda gizle */
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 60vh; /* Ekran yüksekliğinin %60'ı */
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
||||||
|
display: block; /* Alt boşluğu engellemek için */
|
||||||
|
}
|
||||||
|
input[type="file"] {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
clip-path: inset(50%);
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.file-upload-label {
|
||||||
|
background-color: #565e64;
|
||||||
|
color: #fff;
|
||||||
|
padding: 10px 20px;
|
||||||
|
min-height: 44px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1em;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
box-shadow: 0 2px 10px rgba(86, 94, 100, 0.25);
|
||||||
|
}
|
||||||
|
.file-upload-label:hover {
|
||||||
|
background-color: #4b5257;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
padding: 20px;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 1.8em;
|
||||||
|
}
|
||||||
|
.controls, .filters, .actions {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.controls label, .filters label {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.controls input[type="range"] {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Numex Codex Resim Düzenleyici</h1>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<input type="file" id="imageLoader" accept="image/*">
|
||||||
|
<label for="imageLoader" class="file-upload-label">Resim Yükle</label>
|
||||||
|
<button id="rotateLeftBtn">Sola Döndür</button>
|
||||||
|
<button id="rotateRightBtn">Sağa Döndür</button>
|
||||||
|
<button id="cropBtn">Kırp</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<label>Parlaklık: <input type="range" id="brightness" min="0" max="200" value="100"></label>
|
||||||
|
<label>Kontrast: <input type="range" id="contrast" min="0" max="200" value="100"></label>
|
||||||
|
<label>Doygunluk: <input type="range" id="saturation" min="0" max="200" value="100"></label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filters">
|
||||||
|
<button id="grayscaleBtn">Gri Tonlama</button>
|
||||||
|
<button id="sepiaBtn">Sepia</button>
|
||||||
|
<button id="resetFiltersBtn">Filtreleri Sıfırla</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="canvas-area">
|
||||||
|
<canvas id="imageCanvas"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button id="downloadBtn">Resmi İndir</button>
|
||||||
|
<button id="resetBtn">Sıfırla</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue
Block a user