2023-05-01 22:57:48 +02:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
|
|
|
|
|
|
|
<th:block th:insert="~{fragments/common :: head(title=#{extractImages.title})}"></th:block>
|
|
|
|
|
|
|
|
|
|
|
|
<body>
|
|
|
|
<div id="page-container">
|
|
|
|
<div id="content-wrap">
|
|
|
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
|
|
|
<br> <br>
|
|
|
|
<div class="container">
|
|
|
|
<div class="row justify-content-center">
|
|
|
|
<div class="col-md-6">
|
|
|
|
<h2 th:text="#{extractImages.header}"></h2>
|
2023-07-05 23:21:43 +02:00
|
|
|
<input type="file" id="pdf-file" accept="application/pdf" />
|
|
|
|
<canvas id="pdf-canvas"></canvas>
|
2023-05-01 22:57:48 +02:00
|
|
|
|
2023-07-05 23:21:43 +02:00
|
|
|
<h4>Contrast: <span id="contrast-val">100</span>%</h4>
|
|
|
|
<input type="range" min="0" max="200" value="100" id="contrast-slider" />
|
|
|
|
|
|
|
|
<h4>Brightness: <span id="brightness-val">100</span>%</h4>
|
|
|
|
<input type="range" min="0" max="200" value="100" id="brightness-slider" />
|
|
|
|
|
|
|
|
<h4>Saturation: <span id="saturation-val">100</span>%</h4>
|
|
|
|
<input type="range" min="0" max="200" value="100" id="saturation-slider" />
|
|
|
|
|
|
|
|
<button id="download-button">Download</button>
|
|
|
|
|
|
|
|
<script src="pdfjs/pdf.js"></script>
|
|
|
|
<script>
|
|
|
|
var canvas = document.getElementById('pdf-canvas');
|
|
|
|
var context = canvas.getContext('2d');
|
|
|
|
var originalImageData = null;
|
|
|
|
|
|
|
|
function renderPDFAndSaveOriginalImageData(file) {
|
|
|
|
var fileReader = new FileReader();
|
|
|
|
fileReader.onload = function() {
|
|
|
|
var data = new Uint8Array(this.result);
|
|
|
|
pdfjsLib.getDocument({data: data}).promise.then(function(pdf) {
|
|
|
|
pdf.getPage(1).then(function(page) {
|
|
|
|
var scale = 1.5;
|
|
|
|
var viewport = page.getViewport({ scale: scale });
|
|
|
|
|
|
|
|
canvas.height = viewport.height;
|
|
|
|
canvas.width = viewport.width;
|
|
|
|
|
|
|
|
var renderContext = {
|
|
|
|
canvasContext: context,
|
|
|
|
viewport: viewport
|
|
|
|
};
|
|
|
|
|
|
|
|
var renderTask = page.render(renderContext);
|
|
|
|
renderTask.promise.then(function () {
|
|
|
|
originalImageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
fileReader.readAsArrayBuffer(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
function adjustImageProperties() {
|
|
|
|
var contrast = parseFloat(document.getElementById('contrast-slider').value);
|
|
|
|
var brightness = parseFloat(document.getElementById('brightness-slider').value);
|
|
|
|
var saturation = parseFloat(document.getElementById('saturation-slider').value);
|
|
|
|
|
|
|
|
contrast /= 100; // normalize to range [0, 2]
|
|
|
|
brightness /= 100; // normalize to range [0, 2]
|
|
|
|
saturation /= 100; // normalize to range [0, 2]
|
|
|
|
|
|
|
|
if (originalImageData) {
|
|
|
|
var newImageData = context.createImageData(originalImageData.width, originalImageData.height);
|
|
|
|
newImageData.data.set(originalImageData.data);
|
|
|
|
|
|
|
|
for(var i=0; i<newImageData.data.length; i+=4)
|
|
|
|
{
|
|
|
|
var r = newImageData.data[i];
|
|
|
|
var g = newImageData.data[i+1];
|
|
|
|
var b = newImageData.data[i+2];
|
|
|
|
// Adjust contrast
|
|
|
|
r = adjustContrastForPixel(r, contrast);
|
|
|
|
g = adjustContrastForPixel(g, contrast);
|
|
|
|
b = adjustContrastForPixel(b, contrast);
|
|
|
|
// Adjust brightness
|
|
|
|
r = adjustBrightnessForPixel(r, brightness);
|
|
|
|
g = adjustBrightnessForPixel(g, brightness);
|
|
|
|
b = adjustBrightnessForPixel(b, brightness);
|
|
|
|
// Adjust saturation
|
|
|
|
if(i==100){
|
|
|
|
console.log('r',r)
|
|
|
|
console.log('g',g)
|
|
|
|
console.log('b',b)
|
|
|
|
console.log('saturation',saturation)
|
|
|
|
}
|
|
|
|
var rgb = adjustSaturationForPixel(r, g, b, saturation);
|
|
|
|
if(i==100){
|
|
|
|
console.log('rgb',rgb)
|
|
|
|
}
|
|
|
|
newImageData.data[i] = rgb[0];
|
|
|
|
newImageData.data[i+1] = rgb[1];
|
|
|
|
newImageData.data[i+2] = rgb[2];
|
|
|
|
}
|
|
|
|
|
|
|
|
context.putImageData(newImageData, 0, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function rgbToHsl(r, g, b) {
|
|
|
|
r /= 255, g /= 255, b /= 255;
|
|
|
|
|
|
|
|
var max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
|
|
var h, s, l = (max + min) / 2;
|
|
|
|
|
|
|
|
if (max === min) {
|
|
|
|
h = s = 0; // achromatic
|
|
|
|
} else {
|
|
|
|
var d = max - min;
|
|
|
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
|
|
|
|
|
|
switch (max) {
|
|
|
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
|
|
|
case g: h = (b - r) / d + 2; break;
|
|
|
|
case b: h = (r - g) / d + 4; break;
|
|
|
|
}
|
|
|
|
|
|
|
|
h /= 6;
|
|
|
|
}
|
|
|
|
|
|
|
|
return [h, s, l];
|
|
|
|
}
|
|
|
|
|
|
|
|
function hslToRgb(h, s, l) {
|
|
|
|
var r, g, b;
|
|
|
|
|
|
|
|
if (s === 0) {
|
|
|
|
r = g = b = l; // achromatic
|
|
|
|
} else {
|
|
|
|
var hue2rgb = function hue2rgb(p, q, t) {
|
|
|
|
if (t < 0) t += 1;
|
|
|
|
if (t > 1) t -= 1;
|
|
|
|
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
|
|
if (t < 1 / 2) return q;
|
|
|
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
|
|
return p;
|
|
|
|
};
|
|
|
|
|
|
|
|
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
|
|
var p = 2 * l - q;
|
|
|
|
|
|
|
|
r = hue2rgb(p, q, h + 1 / 3);
|
|
|
|
g = hue2rgb(p, q, h);
|
|
|
|
b = hue2rgb(p, q, h - 1 / 3);
|
|
|
|
}
|
|
|
|
|
|
|
|
return [r * 255, g * 255, b * 255];
|
|
|
|
}
|
|
|
|
|
|
|
|
function adjustContrastForPixel(pixel, contrast) {
|
|
|
|
// Normalize to range [-0.5, 0.5]
|
|
|
|
var normalized = pixel / 255 - 0.5;
|
|
|
|
|
|
|
|
// Apply contrast
|
|
|
|
normalized *= contrast;
|
|
|
|
|
|
|
|
// Denormalize back to [0, 255]
|
|
|
|
return (normalized + 0.5) * 255;
|
|
|
|
}
|
|
|
|
function clamp(value, min, max) {
|
|
|
|
return Math.min(Math.max(value, min), max);
|
|
|
|
}
|
|
|
|
|
|
|
|
function adjustSaturationForPixel(r, g, b, saturation) {
|
|
|
|
var hsl = rgbToHsl(r, g, b);
|
|
|
|
|
|
|
|
// Adjust saturation
|
|
|
|
hsl[1] = clamp(hsl[1] * saturation, 0, 1);
|
|
|
|
|
|
|
|
// Convert back to RGB
|
|
|
|
var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
|
|
|
|
|
|
|
|
// Return adjusted RGB values
|
|
|
|
return rgb;
|
|
|
|
}
|
|
|
|
|
|
|
|
function adjustBrightnessForPixel(pixel, brightness) {
|
|
|
|
return Math.max(0, Math.min(255, pixel * brightness));
|
|
|
|
}
|
|
|
|
|
|
|
|
function downloadImage() {
|
|
|
|
var downloadLink = document.createElement('a');
|
|
|
|
downloadLink.href = canvas.toDataURL('image/png');
|
|
|
|
downloadLink.download = 'download.png';
|
|
|
|
downloadLink.click();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Event listeners
|
|
|
|
document.getElementById('pdf-file').addEventListener('change', function(e) {
|
|
|
|
if (e.target.files.length > 0) {
|
|
|
|
renderPDFAndSaveOriginalImageData(e.target.files[0]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
document.getElementById('contrast-slider').addEventListener('input', function() {
|
|
|
|
document.getElementById('contrast-val').textContent = this.value;
|
|
|
|
adjustImageProperties();
|
|
|
|
});
|
|
|
|
|
|
|
|
document.getElementById('brightness-slider').addEventListener('input', function() {
|
|
|
|
document.getElementById('brightness-val').textContent = this.value;
|
|
|
|
adjustImageProperties();
|
|
|
|
});
|
|
|
|
|
|
|
|
document.getElementById('saturation-slider').addEventListener('input', function() {
|
|
|
|
document.getElementById('saturation-val').textContent = this.value;
|
|
|
|
adjustImageProperties();
|
|
|
|
});
|
|
|
|
|
|
|
|
document.getElementById('download-button').addEventListener('click', function() {
|
|
|
|
downloadImage();
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
2023-05-01 22:57:48 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
|
|
|
</div>
|
|
|
|
</body>
|
|
|
|
</html>
|