mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-10-25 11:17:28 +02:00 
			
		
		
		
	Search bar and adjust contrast
This commit is contained in:
		
							parent
							
								
									4e28bf03bd
								
							
						
					
					
						commit
						a3c7f5aa46
					
				| @ -77,7 +77,7 @@ public class AutoRenameController { | ||||
|     private static final Logger logger = LoggerFactory.getLogger(AutoRenameController.class); | ||||
| 
 | ||||
|     private static final float TITLE_FONT_SIZE_THRESHOLD = 20.0f; | ||||
|     private static final int LINE_LIMIT = 7; | ||||
|     private static final int LINE_LIMIT = 11; | ||||
| 
 | ||||
|     @PostMapping(consumes = "multipart/form-data", value = "/auto-rename") | ||||
|     @Operation(summary = "Extract header from PDF file", description = "This endpoint accepts a PDF file and attempts to extract its title or header based on heuristics. Input:PDF Output:PDF Type:SISO") | ||||
| @ -136,12 +136,25 @@ public class AutoRenameController { | ||||
|     	                super.getText(doc); | ||||
|     	                processLine(); // Process the last line | ||||
| 
 | ||||
|     	                // Sort lines by font size in descending order and get the first one | ||||
|     	                lineInfos.sort(Comparator.comparing((LineInfo li) -> li.fontSize).reversed()); | ||||
|     	                String title = lineInfos.isEmpty() ? null : lineInfos.get(0).text; | ||||
|     	                // Merge lines with same font size | ||||
|     	                List<LineInfo> mergedLineInfos = new ArrayList<>(); | ||||
|     	                for (int i = 0; i < lineInfos.size(); i++) { | ||||
|     	                    String mergedText = lineInfos.get(i).text; | ||||
|     	                    float fontSize = lineInfos.get(i).fontSize; | ||||
|     	                    while (i + 1 < lineInfos.size() && lineInfos.get(i + 1).fontSize == fontSize) { | ||||
|     	                        mergedText += " " + lineInfos.get(i + 1).text; | ||||
|     	                        i++; | ||||
|     	                    } | ||||
|     	                    mergedLineInfos.add(new LineInfo(mergedText, fontSize)); | ||||
|     	                } | ||||
| 
 | ||||
|     	                return title != null ? title : (useFirstTextAsFallback ? (lineInfos.isEmpty() ? null : lineInfos.get(lineInfos.size() - 1).text) : null); | ||||
|     	                // Sort lines by font size in descending order and get the first one | ||||
|     	                mergedLineInfos.sort(Comparator.comparing((LineInfo li) -> li.fontSize).reversed()); | ||||
|     	                String title = mergedLineInfos.isEmpty() ? null : mergedLineInfos.get(0).text; | ||||
| 
 | ||||
|     	                return title != null ? title : (useFirstTextAsFallback ? (mergedLineInfos.isEmpty() ? null : mergedLineInfos.get(mergedLineInfos.size() - 1).text) : null); | ||||
|     	            } | ||||
| 
 | ||||
|     	        }; | ||||
| 
 | ||||
|     	        String header = reader.getText(document); | ||||
|  | ||||
| @ -147,4 +147,6 @@ public class OtherWebController { | ||||
|         return "other/auto-rename"; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|      | ||||
| } | ||||
|  | ||||
| @ -35,9 +35,11 @@ navbar.pageOps=Page Operations | ||||
| 
 | ||||
| home.multiTool.title=PDF Multi Tool | ||||
| home.multiTool.desc=Merge, Rotate, Rearrange, and Remove pages | ||||
| multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side | ||||
| 
 | ||||
| home.merge.title=Merge | ||||
| home.merge.desc=Easily merge multiple PDFs into one. | ||||
| merge.tags=merge,Page operations,Back end,server side | ||||
| 
 | ||||
| home.split.title=Split | ||||
| home.split.desc=Split PDFs into multiple documents | ||||
|  | ||||
| @ -1,3 +1,17 @@ | ||||
| #searchBar {  | ||||
| 	background-image: url('/images/search.svg');  | ||||
| 	background-position: 16px 16px;  | ||||
| 	background-repeat: no-repeat;  | ||||
| 	width: 100%;  | ||||
| 	font-size: 16px;  | ||||
| 	margin-bottom: 12px; | ||||
| 	padding: 12px 20px 12px 40px;  | ||||
| 	border: 1px solid #ddd; | ||||
| 	 | ||||
| 	 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| .features-container { | ||||
|     display: grid; | ||||
|     grid-template-columns: repeat(auto-fill, minmax(21rem, 3fr)); | ||||
|  | ||||
| @ -1,3 +1,41 @@ | ||||
| 
 | ||||
| 
 | ||||
| #navbarSearch { | ||||
|     top: 100%; | ||||
|     right: 0; | ||||
| } | ||||
| 
 | ||||
| #searchForm { | ||||
|     width: 200px;  /* Adjust this value as needed */ | ||||
| } | ||||
| 
 | ||||
| /* Style the search results to match the navbar */ | ||||
| #searchResults { | ||||
|     max-height: 200px;  /* Adjust this value as needed */ | ||||
|     overflow-y: auto; | ||||
|     width: 100%; | ||||
| } | ||||
| 
 | ||||
| #searchResults .dropdown-item { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     white-space: nowrap; | ||||
|     height: 50px;  /* Fixed height */ | ||||
|     overflow: hidden;  /* Hide overflow */ | ||||
| } | ||||
| 
 | ||||
| #searchResults .icon { | ||||
|     margin-right: 10px; | ||||
| } | ||||
| 
 | ||||
| #searchResults .icon-text { | ||||
|     display: inline; | ||||
|     overflow: hidden;  /* Hide overflow */ | ||||
|     text-overflow: ellipsis;  /* Add ellipsis for long text */ | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| .main-icon { | ||||
| 	width: 36px; | ||||
| 	height: 36px; | ||||
|  | ||||
							
								
								
									
										4
									
								
								src/main/resources/static/images/adjust-contrast.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/main/resources/static/images/adjust-contrast.svg
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-palette" viewBox="0 0 16 16"> | ||||
|   <path d="M8 5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm4 3a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zM5.5 7a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm.5 6a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/> | ||||
|   <path d="M16 8c0 3.15-1.866 2.585-3.567 2.07C11.42 9.763 10.465 9.473 10 10c-.603.683-.475 1.819-.351 2.92C9.826 14.495 9.996 16 8 16a8 8 0 1 1 8-8zm-8 7c.611 0 .654-.171.655-.176.078-.146.124-.464.07-1.119-.014-.168-.037-.37-.061-.591-.052-.464-.112-1.005-.118-1.462-.01-.707.083-1.61.704-2.314.369-.417.845-.578 1.272-.618.404-.038.812.026 1.16.104.343.077.702.186 1.025.284l.028.008c.346.105.658.199.953.266.653.148.904.083.991.024C14.717 9.38 15 9.161 15 8a7 7 0 1 0-7 7z"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 795 B | 
| @ -1,3 +1,24 @@ | ||||
| function filterCards() { | ||||
|     var input = document.getElementById('searchBar'); | ||||
|     var filter = input.value.toUpperCase(); | ||||
|     var cards = document.querySelectorAll('.feature-card'); | ||||
| 
 | ||||
|     for (var i = 0; i < cards.length; i++) { | ||||
|         var card = cards[i]; | ||||
|         var title = card.querySelector('h5.card-title').innerText; | ||||
|         var text = card.querySelector('p.card-text').innerText; | ||||
|         var tags = card.getAttribute('data-tags'); | ||||
|         var content = title + ' ' + text + ' ' + tags; | ||||
| 
 | ||||
|         if (content.toUpperCase().indexOf(filter) > -1) { | ||||
|             card.style.display = ""; | ||||
|         } else { | ||||
|             card.style.display = "none"; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| function toggleFavorite(element) { | ||||
| 	var img = element.querySelector('img'); | ||||
| 	var card = element.closest('.feature-card'); | ||||
| @ -13,6 +34,7 @@ function toggleFavorite(element) { | ||||
| 	} | ||||
| 	reorderCards(); | ||||
| 	updateFavoritesDropdown(); | ||||
| 	filterCards(); | ||||
| } | ||||
| 
 | ||||
| function reorderCards() { | ||||
| @ -45,5 +67,7 @@ function initializeCards() { | ||||
| 	}); | ||||
| 	reorderCards(); | ||||
| 	updateFavoritesDropdown(); | ||||
| 	filterCards(); | ||||
| } | ||||
| 
 | ||||
| window.onload = initializeCards; | ||||
							
								
								
									
										72
									
								
								src/main/resources/static/js/search.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/main/resources/static/js/search.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | ||||
| // Toggle search bar when the search icon is clicked
 | ||||
| document.querySelector('#search-icon').addEventListener('click', function(e) { | ||||
| 	e.preventDefault(); | ||||
| 	var searchBar = document.querySelector('#navbarSearch'); | ||||
| 	searchBar.classList.toggle('show'); | ||||
| }); | ||||
| window.onload = function() { | ||||
|     var items = document.querySelectorAll('.dropdown-item, .nav-link'); | ||||
|     var dummyContainer = document.createElement('div'); | ||||
|     dummyContainer.style.position = 'absolute'; | ||||
|     dummyContainer.style.visibility = 'hidden'; | ||||
|     dummyContainer.style.whiteSpace = 'nowrap';  // Ensure we measure full width
 | ||||
|     document.body.appendChild(dummyContainer); | ||||
| 
 | ||||
|     var maxWidth = 0; | ||||
| 
 | ||||
|     items.forEach(function(item) { | ||||
|         var clone = item.cloneNode(true); | ||||
|         dummyContainer.appendChild(clone); | ||||
|         var width = clone.offsetWidth; | ||||
|         if (width > maxWidth) { | ||||
|             maxWidth = width; | ||||
|         } | ||||
|         dummyContainer.removeChild(clone); | ||||
|     }); | ||||
| 
 | ||||
|     document.body.removeChild(dummyContainer); | ||||
| 
 | ||||
|     // Store max width for later use
 | ||||
|     window.navItemMaxWidth = maxWidth; | ||||
| }; | ||||
| 
 | ||||
| // Show search results as user types in search box
 | ||||
| document.querySelector('#navbarSearchInput').addEventListener('input', function(e) { | ||||
|     var searchText = e.target.value.toLowerCase(); | ||||
|     var items = document.querySelectorAll('.dropdown-item, .nav-link'); | ||||
|     var resultsBox = document.querySelector('#searchResults'); | ||||
| 
 | ||||
|     // Clear any previous results
 | ||||
|     resultsBox.innerHTML = ''; | ||||
| 
 | ||||
|     items.forEach(function(item) { | ||||
|         var titleElement = item.querySelector('.icon-text'); | ||||
|         var iconElement = item.querySelector('.icon'); | ||||
|         var itemHref = item.getAttribute('href'); | ||||
|         if (titleElement && iconElement && itemHref !== '#') { | ||||
|             var title = titleElement.innerText.toLowerCase(); | ||||
|             if (title.indexOf(searchText) !== -1 && !resultsBox.querySelector(`a[href="${item.getAttribute('href')}"]`)) { | ||||
|                 var result = document.createElement('a'); | ||||
|                 result.href = itemHref; | ||||
|                 result.classList.add('dropdown-item'); | ||||
| 
 | ||||
|                 var resultIcon = document.createElement('img'); | ||||
|                 resultIcon.src = iconElement.src; | ||||
|                 resultIcon.alt = 'icon'; | ||||
|                 resultIcon.classList.add('icon'); | ||||
|                 result.appendChild(resultIcon); | ||||
| 
 | ||||
|                 var resultText = document.createElement('span'); | ||||
|                 resultText.textContent = title; | ||||
|                 resultText.classList.add('icon-text'); | ||||
|                 result.appendChild(resultText); | ||||
| 
 | ||||
|                 resultsBox.appendChild(result); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     // Set the width of the search results box to the maximum width
 | ||||
|     resultsBox.style.width = window.navItemMaxWidth + 'px'; | ||||
| }); | ||||
| 
 | ||||
| @ -1,4 +1,4 @@ | ||||
| <div th:fragment="card" class="feature-card" th:id="${id}" th:if="${@endpointConfiguration.isEndpointEnabled(cardLink)}"> | ||||
| <div th:fragment="card" class="feature-card" th:id="${id}" th:if="${@endpointConfiguration.isEndpointEnabled(cardLink)}" data-tags="${tags}"> | ||||
|     <a th:href="${cardLink}"> | ||||
|         <div class="d-flex align-items-center"> <!-- Add a flex container to align the SVG and title --> | ||||
|             <img th:if="${svgPath}"  id="card-icon" class="home-card-icon home-card-icon-colour" th:src="${svgPath}" alt="Icon" width="30" height="30"> | ||||
|  | ||||
| @ -205,6 +205,23 @@ | ||||
|                         </a> | ||||
| 
 | ||||
|                     </li> | ||||
|                      | ||||
|              <!-- Search Button and Search Bar --> | ||||
| <li class="nav-item position-relative"> | ||||
|     <a href="#" class="nav-link" id="search-icon"> | ||||
|         <img class="navbar-icon" src="images/search.svg" alt="icon" width="24" height="24"> | ||||
|     </a> | ||||
|     <!-- Search Bar --> | ||||
|     <div class="collapse position-absolute" id="navbarSearch"> | ||||
|         <form class="d-flex p-2 bg-white border" id="searchForm"> | ||||
|             <input class="form-control" type="search" placeholder="Search" aria-label="Search" id="navbarSearchInput"> | ||||
|         </form> | ||||
|         <!-- Search Results --> | ||||
|         <div id="searchResults" class="border p-2 bg-white"></div> | ||||
|     </div> | ||||
| </li> | ||||
| 
 | ||||
| 
 | ||||
|                 </ul> | ||||
| 
 | ||||
|           | ||||
| @ -212,6 +229,7 @@ | ||||
| 
 | ||||
|         </div> | ||||
|         <script src="js/favourites.js"></script> | ||||
|         <script src="js/search.js"></script> | ||||
|     </nav> | ||||
|      | ||||
| 	<div th:insert="~{fragments/errorBannerPerPage.html :: errorBannerPerPage}"></div> | ||||
|  | ||||
| @ -20,8 +20,13 @@ | ||||
|             </div> | ||||
| 			<br class="d-md-none"> | ||||
|             <!-- Features --> | ||||
|             <div class="features-container container"> | ||||
|              | ||||
|             <script src="js/homecard.js"></script> | ||||
|             	 | ||||
|             <div class=" container"> | ||||
|             <input type="text" id="searchBar" onkeyup="filterCards()" placeholder="Search for features..."> | ||||
|             <div class="features-container "> | ||||
|             	 | ||||
|             	 | ||||
|                  <div th:replace="~{fragments/card :: card(id='pipeline', cardTitle=#{home.pipeline.title}, cardText=#{home.pipeline.desc}, cardLink='pipeline', svgPath='images/pipeline.svg')}"></div> | ||||
|                  | ||||
|                 <div th:replace="~{fragments/card :: card(id='multi-tool', cardTitle=#{home.multiTool.title}, cardText=#{home.multiTool.desc}, cardLink='multi-tool', svgPath='images/tools.svg')}"></div> | ||||
| @ -70,13 +75,13 @@ | ||||
|                  | ||||
|                 <div th:replace="~{fragments/card :: card(id='add-page-numbers', cardTitle=#{home.add-page-numbers.title}, cardText=#{home.add-page-numbers.desc}, cardLink='add-page-numbers', svgPath='images/add-page-numbers.svg')}"></div> | ||||
|                 <div th:replace="~{fragments/card :: card(id='auto-rename', cardTitle=#{home.auto-rename.title}, cardText=#{home.auto-rename.desc}, cardLink='auto-rename', svgPath='images/fonts.svg')}"></div> | ||||
|                  | ||||
|                 <div th:replace="~{fragments/card :: card(id='adjust-contrast', cardTitle=#{home.adjust-contrast.title}, cardText=#{home.adjust-contrast.desc}, cardLink='adjust-contrast', svgPath='images/adjust-contrast.svg')}"></div> | ||||
| 
 | ||||
|                  | ||||
| 		 | ||||
|                 <script src="js/homecard.js"></script> | ||||
|                  | ||||
|             </div> | ||||
|         </div> | ||||
|         </div> </div> | ||||
|         <div th:insert="~{fragments/footer.html :: footer}"></div> | ||||
|     </div> | ||||
| </body> | ||||
|  | ||||
| @ -13,15 +13,215 @@ | ||||
|         <div class="row justify-content-center"> | ||||
|           <div class="col-md-6"> | ||||
|             <h2 th:text="#{extractImages.header}"></h2> | ||||
| 			<input type="file" id="pdf-file" accept="application/pdf" /> | ||||
|   <canvas id="pdf-canvas"></canvas> | ||||
| 
 | ||||
|             <form id="multiPdfForm" th:action="@{adjust-contrast}" method="post" enctype="multipart/form-data"> | ||||
|               <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div> | ||||
|                  <div class="form-group"> | ||||
|                     <label for="contrastRange">Contrast</label> | ||||
|                     <input name="contrastRange" type="range" class="form-control-range" id="contrastRange" min="-100" max="100" value="0" step="1"> | ||||
|                 </div> | ||||
|               <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{extractImages.submit}"></button> | ||||
|             </form> | ||||
|   <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> | ||||
|        | ||||
|        | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user