// ==UserScript== // @name 4chan Image Resizer // @namespace https://greasyfork.org/en/users/393416 // @version 2.3.1 // @description Automatically downscales images based on custom presets, image cropping and more. Requires 4chan X. // @author greenronia // @match *://boards.4chan.org/* // @match *://boards.4channel.org/* // @require https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.js // @require https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.11/cropper.js // @resource cropper_css https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.11/cropper.css // @grant GM_getResourceText // @grant GM_addStyle // @icon https://i.imgur.com/hQp5BTf.png // @downloadURL none // ==/UserScript== // //Using SparkMD5 to generate image hashes - https://github.com/satazor/js-spark-md5 //Using Cropper.js to crop images - https://github.com/fengyuanchen/cropperjs // //----------DEBUG MODE-------------// var DEBUG = false;//console // //---------------------------------// const version = "2.3.1"; //---------------------------------// if(DEBUG) console.log("[ImageResizer] Initialized"); //CSS var cssTxt = GM_getResourceText ("cropper_css"); GM_addStyle (cssTxt); var style = document.createElement("style"); style.innerHTML = '' + '.centerImg { margin: 0; position: absolute; top: 50%; left: 50%; -ms-transform: translate(-50%, -50%); transform: translate(-50%, -50%); max-width: 100%; max-height: 100vh; height: auto; cursor: pointer; }\n' + '.settingsOverlay { background: rgba(0,0,0,0.8); display: none; height: 100%; left: 0; position: fixed; top: 0; width: 100%; z-index: 777; } \n' + '#pvOverlay { background: rgba(0,0,0,0.9); height: 100%; left: 0; position: fixed; top: 0; width: 100%; z-index: 777; text-align: center;} \n' + '#pvHeader { position: fixed; height: 35px; width: 100%; opacity: 0; -webkit-transition: opacity 0.5s ease-in-out;}\n' + '#pvHeader:hover { opacity: 0.8; -webkit-transition: none; }\n' + '.pvOpct { opacity: 0.7 !important; } \n' + '#imgResizeMenu { position: fixed; top: 20%; left: 35%; width: 30%; min-width: 620px; padding: 2em; overflow: hidden; z-index: 8;}\n' + '#imgResizeMenu h3 { text-align: center; }\n' + '#imgResizeMenu a { cursor: pointer; }\n' + '#imgResizeMenu label { text-decoration-line: underline; }\n' + '#heplDiv summary { cursor: pointer; }\n' + '.settingsOverlay input[type=number] { -moz-appearance: textfield; text-align: right; }\n' + '.resizer-settings { padding-bottom: 5px }\n' + '#errMsg { color: red; text-align: center; }\n' + '#ruleTable { border-collapse: collapse; }\n' + '#ruleTable td, th { padding: 8px; text-align: left; border-bottom: 1pt solid; }\n' + '#QCTable { border-collapse: collapse; }\n' + '#QCTable td, th { padding: 8px; text-align: center; border-bottom: 1pt solid; }\n' + '#QCTable p { margin: auto; max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n' + '#inputContainer { text-align: center; padding-top: 1em; }\n' + '#inputContainer button { margin-top: 20px; }\n' + '.menuBtns { margin-left: 1em; }\n' + '#sideMenu { position: absolute; display: none; padding: 5px 0px 5px 0px; width: 101px; margin-left: -106px; margin-top: -2px;}\n' + '.sideMenuElement { background: inherit; display: block; cursor: pointer; padding: 2px 10px 2px 10px; text-align: left;}\n' + '.downscale-menu-off { display: none; }\n' + '.downscale-menu-on { display: block !important; }'; var styleRef = document.querySelector("script"); styleRef.parentNode.insertBefore(style, styleRef); //Load settings getSettings(); getPresets(); getQCList(); //Update downscale-settings object v2.3 (function () { if (getSettings().cropOutput == null || getSettings().smoothing == null) { var settings = getSettings(); settings.cropOutput = "image/jpeg"; settings.smoothing = false; localStorage.setItem("downscale-settings", JSON.stringify(settings)); var info = '4chan Image Resizer updated to version ' + version + '.\nMore info in Settings > About tab.'; var msgDetail = {type: 'info', content: info, lifetime: 10}; var msgEvent = new CustomEvent('CreateNotification', {bubbles: true, detail: msgDetail}); document.dispatchEvent(msgEvent); } })(); function getSettings() { if (JSON.parse(localStorage.getItem("downscale-settings"))) { var settings = JSON.parse(localStorage.getItem("downscale-settings")); } else { settings = { enabled:true, notify:true, convert:false, jpegQuality:0.92, shortcut:true , cropOutput:"image/jpeg", smoothing:false}; localStorage.setItem("downscale-settings", JSON.stringify(settings)); } return settings; } function getPresets() { if (JSON.parse(localStorage.getItem("downscale-presets"))) { var presets = JSON.parse(localStorage.getItem("downscale-presets")); } else { presets = []; } return presets; } function getQCList() { if (JSON.parse(localStorage.getItem("downscale-qclist"))) { var QCList = JSON.parse(localStorage.getItem("downscale-qclist")); } else { QCList = []; } return QCList; } //Checking if QuickReply dialog is open. document.addEventListener('QRDialogCreation', function(listenForQRDC) { var checkBox = document.getElementById("imgResize"); var sideMenu = document.getElementById("sideMenuArrow"); //Checking if the "resize" check box and "side menu" already exist if (!sideMenu) { appendSideMenu(); } if (!checkBox) { appendCheckBox(); } //Listening for clicks on check box document.getElementById("imgResize").addEventListener("click", checkState); checkState(1); if(DEBUG) console.log("[QRFile] Listening..."); //QRFile | Listening for QRFile, in response to: QRGetFile | Request File document.addEventListener('QRFile', function(GetFile) { if(DEBUG) console.log("[QRFile] File served: " + GetFile.detail); //Remove "Remember" option upon adding a (new) file. removeRemOption(); const file = GetFile.detail; //Initialize an instance of a FileReader const reader = new FileReader(); //Checking if the file is JPG or PNG if (file.type == "image/jpeg" || file.type == "image/png") { if(DEBUG) console.log("Acceptable File type: " + file.type); //add
to sideMenu var smHR = document.getElementById("sm-hr"); if (!smHR) { appendHR(); } //Check if resizer already completed its task (to determine priority) var complete = false; var presets = getPresets(); var QCList = getQCList(); reader.onload = function(f) { var img = new Image(); img.src = reader.result; img.onload = function() { //Base64 MD5 hash of an image var imgMD5 = SparkMD5.hash(img.src); if(DEBUG) console.log(""); if(DEBUG) if(getSettings().convert) console.log("[PNGConverter] Enabled"); else console.log("[PNGConverter] Disabled"); if(DEBUG) console.log("INPUT Dimensions: " + img.width + "x" + img.height); if(DEBUG) console.log("INPUT File size: " + formatBytes(file.size)); //THE priority list if (getQCList().length > 0) checkMD5(img, imgMD5); if (presets.length > 0 && !complete) checkPresets(img); if (getSettings().convert && !complete) checkPNG(img); if (!complete) { //Reset QC and Crop buttons removeQCOption(); removeCropOption(); quickConvert(img, file, imgMD5); crop(img); //Reset preview button removePreviewOption(); appendPreviewBtn(img.src, file.size, img.width, img.height, file.name); } return; } return; } function checkMD5(img, imgMD5) { if(DEBUG) console.log("[quickConvert] Checking for matching MD5: " + imgMD5); var filterCount = QCList.length; var matchFound = false; for (var i = 0; i < filterCount; i++) { //unpack md5 hash var filterMD5 = QCList[i].split(":").pop(); if (filterMD5 == imgMD5) { if(DEBUG) console.log("[quickConvert] Match found."); matchFound = true; resizer(img.width, img.height, img); break; } } if(DEBUG) if (!matchFound)console.log("[quickConvert] No match found."); return; } function checkPresets(img) { var matchCount = 0; var rule = []; var presetCount = presets.length; for (var i = 0; i < presetCount; i++) { //unpack rules rule[i] = presets[i].split(":"); //check for matching file type if (rule[i][0] != 0) { switch (parseInt(rule[i][0])) { case 1: rule[i][0] = "image/png"; break; case 2: rule[i][0] = "image/jpeg"; } if (rule[i][0] != file.type) continue; } //check for matching dimensions if (rule[i][1] == img.width && rule[i][2] == img.height) { var MAX_WIDTH = parseInt(rule[i][3]); var MAX_HEIGHT = parseInt(rule[i][4]); matchCount++; if(DEBUG) console.log("Preset '" + i + "' matched: " + rule[i]); break; } } //failsafe if (matchCount == 0 || matchCount > 1) { if(DEBUG) console.log("Image didn't match any presets.\n------------"); return; } else { resizer(MAX_WIDTH, MAX_HEIGHT, img); return; } } //PNG -> JPEG function checkPNG(img) { if (file.type == "image/png") { var MAX_WIDTH = img.width; var MAX_HEIGHT = img.height; if(DEBUG) console.log("[PNGConverter] Converting PNG to JPEG"); resizer(MAX_WIDTH, MAX_HEIGHT, img); } else { if(DEBUG) console.log("[PNGConverter] Image format isn't PNG.\n------------"); return; } } //The main resize function function resizer(MAX_WIDTH, MAX_HEIGHT, img, imgMD5) { if(DEBUG && !imgMD5) console.log(""); removePreviewOption(); var canvas = document.createElement("canvas"); //Input dimensions var width = img.width; var height = img.height; //Calculating dimensions/aspect ratio if (width > height) { if (width > MAX_WIDTH) { height *= MAX_WIDTH / width; width = MAX_WIDTH; } } else { if (height > MAX_HEIGHT) { width *= MAX_HEIGHT / height; height = MAX_HEIGHT; } } // resize the canvas to the new dimensions canvas.width = width; canvas.height = height; // scale & draw the image onto the canvas var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, width, height); //canvas to dataURL | JPEG quality (0-1) var dataURL; if (imgMD5) dataURL = canvas.toDataURL('image/jpeg', 92); else dataURL = canvas.toDataURL('image/jpeg', parseFloat(getSettings().jpegQuality)); //dataURL to blob var blob = dataURItoBlob(dataURL); //Stop classObserver | prevent trigger loop classObserver.disconnect(); if(DEBUG) console.log("[classObserver] Stopping..."); setFile(blob, img, width, height, imgMD5); //add crop option after conversion - v2.3.1 var imgForCrop = new Image; imgForCrop.src = dataURL; crop(imgForCrop); //add preview button after conversion appendPreviewBtn(dataURL, blob.size, width, height, file.name); } //Set the new file to QR form function setFile(blob, img, width, height, imgMD5) { var new_filename = constructFilename(blob.type, file.name) var detail = { file: blob, name: new_filename }; var event = new CustomEvent('QRSetFile', { bubbles: true, detail: detail }); document.dispatchEvent(event); if (imgMD5) rememberQC(img, file, imgMD5, blob.size); if(DEBUG) console.log("[QRSetFile] File Sent"); if(DEBUG) console.log("OUTPUT Dimesnions: " + Math.round(width) + "x" + Math.round(height)); if(DEBUG) console.log("OUTPUT Filesize: " + formatBytes(blob.size)); if(DEBUG) console.log("JPEG Quality: " + getSettings().jpegQuality); //Notification var FSInfo = "Original size: (" + formatBytes(file.size) + ", " + img.width + "x" + img.height + ") \n New size: (" + formatBytes(blob.size)+ ", " + Math.round(width) + "x" + Math.round(height) +")"; if (getSettings().notify) { var msgDetail = {type: 'info', content: FSInfo, lifetime: 5}; var msgEvent = new CustomEvent('CreateNotification', {bubbles: true, detail: msgDetail}); document.dispatchEvent(msgEvent); } //Remove Quick Convert option after conversion removeQCOption(); removeCropOption(); //Restart classObserver classObserver.observe(targetNode, observerOptions); //Preset priority complete = true; if(DEBUG) console.log("------------\n[classObserver] Restarting..."); } //Quick Convert (QC) image, from Side Menu function quickConvert(img, file, imgMD5) { //Convert options container (future use) var container = document.createElement("div"); container.id = "qcDiv"; //Convert button var convert = document.createElement("a"); convert.id = "quickConvert"; convert.classList.add("sideMenuElement"); convert.classList.add("entry"); convert.innerHTML = "Quick Convert"; convert.title = "Convert image to JPEG"; //CSS on hover convert.onmouseover = function(){this.classList.toggle("focused")}; convert.onmouseout = function(){this.classList.toggle("focused")}; //Call resizer convert.addEventListener('click', function(){ if(DEBUG) console.log("[quickConvert] Manually calling Resizer..."); resizer(img.width, img.height, img, imgMD5); },); var parent = document.getElementById("sideMenu"); parent.appendChild(container); container.appendChild(convert); } //Crop Image button, from Side Menu function crop(img) { //Convert options container (future use) var container = document.createElement("div"); container.id = "cropDiv"; //Convert button var crop = document.createElement("a"); crop.id = "crop"; crop.classList.add("sideMenuElement"); crop.classList.add("entry"); crop.innerHTML = "Crop Image"; //crop.title = "Crop Image"; //CSS on hover crop.onmouseover = function(){this.classList.toggle("focused")}; crop.onmouseout = function(){this.classList.toggle("focused")}; //Call cropper crop.addEventListener('click', function(){ if(DEBUG) console.log("[cropper] Calling cropper..."); cropImage(img); },); var parent = document.getElementById("sideMenu"); parent.appendChild(container); container.appendChild(crop); } //Image Cropper function cropImage(img) { var overlay = document.createElement("div"); overlay.id = "pvOverlay"; //----------------------------------------------- var header = document.createElement("div"); header.id = "pvHeader"; header.className = "dialog"; header.classList.add("pvOpct"); //header.style = "line-height: 35px;" //Set Image button------------------------------- var setBtn = document.createElement("a"); setBtn.id = "setCrop"; setBtn.style.cursor = "pointer"; setBtn.innerHTML = "Set Image"; //Undo button------------------------------------ var undoBtn = document.createElement("a"); undoBtn.id = "undoCrop"; undoBtn.style.cursor = "pointer"; undoBtn.innerHTML = "Undo"; //Close button----------------------------------- var closeBtn = document.createElement("a"); closeBtn.id = "closeCrop"; closeBtn.className = "close fa fa-times"; closeBtn.style = "float: right; cursor: pointer; margin-right: 20px; margin-top: 7px; transform: scale(1.5);"; closeBtn.title = "Close"; //Cropped ---------------------------------- var cropImg = document.createElement("img"); cropImg.id = "cropImg"; cropImg.classList.add("centerImg"); cropImg.src = img.src; cropImg.title = "LMB: Set\nMMB: Close\nRMB: Undo\nShift+RMB: Context Menu"; cropImg.oncontextmenu = function (){ return false; }; //----------------------------------------------- document.body.appendChild(overlay); overlay.appendChild(cropImg); //Cropper---------------------------------------- let cropper; const image = document.getElementById('cropImg'); //Do stuff when Cropper is ready image.addEventListener('ready', function () { //Scale image to 100%, if it's smaller than overlay/viewport (prevent initial zoom-in/stretching) if (overlay.clientWidth > img.width && overlay.clientHeight > img.height) { cropper.zoomTo(1); if(DEBUG) console.log("[cropper] Scaling image to 100%"); } var fired = false; //Cropper function Keybinds document.body.onkeydown = function (event) { var e = event || window.event; if (e.target !== this || !cropper) { return; } if (!fired) { switch (e.keyCode) { //Esc or Delete - close Cropper case 46: case 27: e.preventDefault(); cropper.destroy(); overlay.remove(); fired = true; break; //Backspace - clear crop selection / undo crop case 8: e.preventDefault(); if (document.getElementById('undoCrop')) { document.getElementById('undoCrop').click(); fired = true; } else { cropper.clear(); } break; //Enter or Space - crop image / set image case 32: case 13: e.preventDefault(); if (document.getElementById('setCrop')) { document.getElementById('setCrop').click(); fired = true; } else { //get cropped canvas if(DEBUG) console.log("[cropper] Smoothing: " + getSettings().smoothing); var croppedCanvas = cropper.getCroppedCanvas({ imageSmoothingEnabled: getSettings().smoothing, imageSmoothingQuality: 'high', }); //convert canvas to blob ('image/jpeg', 1) if(DEBUG) console.log("[cropper] Output format: " + getSettings().cropOutput); var dataURL = croppedCanvas.toDataURL(getSettings().cropOutput, parseFloat(getSettings().jpegQuality)); var blob = dataURItoBlob(dataURL); //get croping data (dimensions) [rounded] var cropData = cropper.getData(true); //kill cropper instance cropper.destroy(); //show cropped image cropImg.src = dataURL; cropImg.addEventListener('mouseup', logMouseButton); //show header when done overlay.appendChild(header); header.appendChild(closeBtn); header.innerHTML += "Cropped Image (" + formatBytes(blob.size)+ ", " + cropData.width + "x" + cropData.height + ")
"; header.appendChild(setBtn); header.innerHTML += " | "; header.appendChild(undoBtn); setTimeout(function() { header.classList.toggle("pvOpct"); }, 2000); document.getElementById('closeCrop').onclick = function() { cropper.destroy(); overlay.remove(); fired = true; }; document.getElementById('setCrop').onclick = function() { setImage(); }; document.getElementById('undoCrop').onclick = function() { overlay.remove(); cropImage(img); }; } break; } function setImage() { //Stop classObserver | prevent trigger loop classObserver.disconnect(); fired = true; if(DEBUG) console.log("[classObserver] Stopping..."); overlay.remove(); removePreviewOption(); var new_filename = constructFilename(blob.type, file.name) appendPreviewBtn(dataURL, blob.size, cropImg.width, cropImg.height, new_filename); setFile(blob, img, cropImg.width, cropImg.height) } //Mouse controls function logMouseButton(e) { if (typeof e === 'object') { switch (e.button) { case 0: setImage(); break; case 1: cropper.destroy(); overlay.remove(); fired = true; break; case 2: if (!e.shiftKey){ overlay.remove(); cropImage(img); } break; } } } } } }); //call Cropper with settings cropper = new Cropper(image, { aspectRatio: NaN, background: false, guides: false, viewMode: 1, autoCrop: false, scalable: false, }); } //Remember button function rememberQC (img, file, imgMD5, newSize) { var container = document.createElement("div"); container.id = "remDiv"; var remember = document.createElement("a"); remember.id = "rememberMD5"; remember.classList.add("sideMenuElement"); remember.classList.add("entry"); remember.innerHTML = "Remember"; remember.style.fontWeight = "bold"; remember.title = "Always convert this image." //CSS on hover remember.onmouseover = function(){this.classList.toggle("focused")}; remember.onmouseout = function(){this.classList.toggle("focused")}; remember.onclick = function(){ saveImgMD5(img, file, imgMD5, newSize) }; var parent = document.getElementById("sideMenu"); parent.appendChild(container); container.appendChild(remember); } //Preview Image button function appendPreviewBtn(img, pvSize, pvWidth, pvHeight, pvName) { var existCheck = document.getElementById("previewImg"); if (!existCheck) { var preview = document.createElement("a"); preview.id = "previewImg"; preview.classList.add("sideMenuElement"); preview.classList.add("entry"); preview.innerHTML = "Preview Image"; //CSS on hover preview.onmouseover = function(){this.classList.toggle("focused")}; preview.onmouseout = function(){this.classList.toggle("focused")}; preview.onclick = function(){ showImage(img, pvSize, pvWidth, pvHeight, pvName) }; var parent = document.getElementById("sideMenu"); parent.appendChild(preview); } else { existCheck.onclick = function(){ showImage(img, pvSize, pvWidth, pvHeight, pvName) }; } return; } //Read the file reader.readAsDataURL(file); } else { removeHR(); removeCropOption(); removeQCOption(); removePreviewOption(); if(DEBUG) console.log("[Error] Invalid FileType: " + file.type + "\n------------"); } }, false); //Observing if a file was uploaded or not | checking if div (with id: "file-n-submit") has class named: "has-file" function callback(mutationList, observer) { if (document.getElementById("file-n-submit").classList.contains("has-file") === true && checkState(2) === true) { if(DEBUG) console.log("------------\n[classObserver] File detected") //QRGetFile | Request File if(DEBUG) console.log("[QRGetFile] Requesting file..."); document.dispatchEvent(new CustomEvent('QRGetFile')); } else if (checkState(2) === false) { if(DEBUG) console.log("[classObserver] ImageResizer is disabled"); return; } else { //Remove Side menu options upon removing a file. removeHR(); removeCropOption(); removeQCOption(); removeRemOption(); removePreviewOption(); if(DEBUG) console.log("[classObserver] No file"); } } //MutationObserver. Checks if div (with id "file-n-submit") has its class attribute changed const targetNode = document.getElementById("file-n-submit"); var observerOptions = { attributes: true }; var classObserver = new MutationObserver(callback); if(DEBUG) console.log("[classObserver] Starting..."); classObserver.observe(targetNode, observerOptions); }, false); //*************************************************************************************// //END OF THE MAIN PROCESS //*************************************************************************************// //Add a label with a check box for ImageResize + Setting button in Side Menu function appendCheckBox() { var settingsButton = document.createElement("a"); var label = document.createElement("label"); var input = document.createElement("input"); input.type = "checkbox"; input.id = "imgResize"; label.id = "imgResizeLabel"; input.title = "Enable Image Resizer"; input.style = "margin-left: 0"; settingsButton.classList.add("sideMenuElement"); settingsButton.classList.add("entry"); label.classList.add("sideMenuElement"); //CSS on hover label.classList.add("entry"); var parent = document.getElementById("sideMenu"); parent.appendChild(label); label.appendChild(input); label.title = "Enable Image Resizer"; label.innerHTML += " Enabled"; settingsButton.title = "Image Resizer Settings"; settingsButton.innerHTML = "Settings"; parent.appendChild(settingsButton); //CSS on hover label.onmouseover = function(){this.classList.toggle("focused")}; label.onmouseout = function(){this.classList.toggle("focused")}; settingsButton.onmouseover = function(){this.classList.toggle("focused")}; settingsButton.onmouseout = function(){this.classList.toggle("focused")}; //Open settings menu settingsButton.onclick = function(){ document.getElementById("imgResizeOverlay").style.display = "block" }; //Checked by default document.getElementById("imgResize").checked = getSettings().enabled; } //Check box state function checkState(caller) { var state = document.getElementById("imgResize").checked; if (state === true) { if (caller != 2) if(DEBUG) console.log("[ImageResizer] Enabled"); return true; } else { if (caller != 2) if(DEBUG) console.log("[ImageResizer] Disabled"); //remove side menu options upon disabling ImageResizer removeHR(); removeCropOption(); removeQCOption(); removeRemOption(); removePreviewOption(); return false; } } //Clears error messages

function clearErr() { document.getElementById("errMsg").innerHTML = ""; } //Checks for any logic errors (upscaling) function basicCheck(edit, rulePos) { var inWidth = parseInt(document.getElementById("inWidth").value); var inHeight = parseInt(document.getElementById("inHeight").value); var outWidth = parseInt(document.getElementById("outWidth").value); var outHeight = parseInt(document.getElementById("outHeight").value); var imgType = parseInt(document.getElementById("imgType").value); if (outWidth <= 0 || outHeight <= 0) { document.getElementById("errMsg").innerHTML = "Invalid output dimensions"; return} else if (inWidth < outWidth || inHeight < outHeight) { document.getElementById("errMsg").innerHTML = "Cannot upscale images"; return} else finalCheck(edit, imgType, inWidth, inHeight, outWidth, outHeight, rulePos); return; } //Checks for any rule overlaps // ([0] - Image type, [1] - Input width, [2] - Input height, [3] - Output width, [4] - Output height) function finalCheck(edit, imgType, inWidth, inHeight, outWidth, outHeight, rulePos) { var e = document.getElementById("imgType"); var format = e.options[e.selectedIndex].text; var presetString = imgType + ":" + inWidth + ":" + inHeight + ":" + outWidth + ":" + outHeight; var presets = getPresets(); if (presets.length > 0) { var rule = []; var presetCount = presets.length; for (var i = 0; i < presetCount; i++) { if (edit && i === rulePos) continue; rule[i] = presets[i].split(":"); if (presetString == presets[i]) { document.getElementById("errMsg").innerHTML = "Exact preset already exists"; return } else if ((inWidth == rule[i][1] && inHeight == rule[i][2]) && (imgType == rule[i][0] || rule[i][0] == 0)) { document.getElementById("errMsg").innerHTML = "Preset with the same input dimensions for " + format + " format already exists"; return } } } //save preset clearErr(); if (edit) presets[rulePos] = presetString; else presets.push(presetString); localStorage.setItem("downscale-presets", JSON.stringify(presets)); //rebuild list document.getElementById("ruleTable").tBodies.item(0).innerHTML = ""; printList(); //hide / display document.getElementById("ruleInput").remove(); document.getElementById("addRule").style.display = "inline"; return; } //Check if possible to calculate output WIDTH function aspectCheckH() { var inWidth = document.getElementById("inWidth").value; var inHeight = document.getElementById("inHeight").value; var outWidth = document.getElementById("outWidth").value; var outHeight = document.getElementById("outHeight").value; if (outHeight > 0) { if (parseInt(inHeight) >= parseInt(outHeight)) { calcAspect("width", inWidth, inHeight, outHeight); clearErr(); } else { document.getElementById("errMsg").innerHTML = "Cannot upscale images"; } } } //Check if possible to calculate output HEIGHT function aspectCheckW() { var inWidth = document.getElementById("inWidth").value; var inHeight = document.getElementById("inHeight").value; var outWidth = document.getElementById("outWidth").value; var outHeight = document.getElementById("outHeight").value; if (outWidth > 0) { if (parseInt(inWidth) >= parseInt(outWidth)) { calcAspect("height", inWidth, inHeight, outWidth); clearErr(); } else { document.getElementById("errMsg").innerHTML = "Cannot upscale images"; } } } //Aspect ratio calculation (finds the other output dimension based on given exact input dimensions) function calcAspect(dimension, w, h, output) { if (dimension == "width") { var width = output / h * w; document.getElementById("outWidth").value = Math.round(width); } if (dimension == "height") { var height = output / w * h; document.getElementById("outHeight").value = Math.round(height); } } //Populate Presets list function printList() { var presets = getPresets(); var list = document.getElementById("imgResizeList"); var table = document.getElementById("ruleTable"); if (presets.length > 0) { var rule = []; var presetCount = presets.length; for (let i = 0; i < presetCount; i++) { rule[i] = presets[i].split(":"); switch (parseInt(rule[i][0])) { case 0: rule[i][0] = "PNG/JPEG"; break; case 1: rule[i][0] = "PNG"; break; case 2: rule[i][0] = "JPEG"; } let delRow = document.createElement("a"); let editRow = document.createElement("a"); delRow.innerHTML = "delete"; editRow.innerHTML = "edit"; //delete a rule and rebuild the list delRow.onclick = function() { if (document.getElementById("inputContainer")) document.getElementById("inputContainer").innerHTML = ""; presets.splice(delRow.parentElement.parentElement.sectionRowIndex, 1); localStorage.setItem("downscale-presets", JSON.stringify(presets)); table.tBodies.item(0).innerHTML = ""; printList(); clearErr(); document.getElementById("addRule").style.display = "inline"; }; editRow.onclick = function() { inputUI(true, rule[i], i); clearErr(); }; //Array contents: [0] - Image type, [1] - Input width, [2] - Input height, [3] - Output width, [4] - Output height var row = table.tBodies.item(0).insertRow(-1); row.insertCell(0).innerHTML = rule[i][0]; row.insertCell(1).innerHTML = '[ ' + rule[i][1] + ' x ' + rule[i][2] + ' ]'; row.insertCell(2).innerHTML = '→'; row.insertCell(3).innerHTML = '[ ' + rule[i][3] + ' x ' + rule[i][4] + ' ]'; row.insertCell(4).appendChild(editRow); row.insertCell(5).appendChild(delRow); } } } //Input field function inputUI(edit, rule, rulePos) { if (document.getElementById("inputContainer")) document.getElementById("inputContainer").innerHTML = ""; document.getElementById("addRule").style.display = "none"; var inputDiv = document.getElementById("inputContainer"); var input = document.createElement("div"); var discardRuleBtn = document.createElement("button"); discardRuleBtn.innerHTML = "Cancel"; var saveRuleBtn = document.createElement("button"); saveRuleBtn.innerHTML = "Save"; input.id = "ruleInput"; //Rules form input.innerHTML = '' + '' + ' ' + '' + ' x ' + '' + ' ' + '  →   x ' + '
'; inputDiv.appendChild(input); var inWidth = document.getElementById("inWidth"); var inHeight = document.getElementById("inHeight"); var outWidth = document.getElementById("outWidth"); var outHeight = document.getElementById("outHeight"); if (edit) { switch (rule[0]) { case "PNG/JPEG": document.getElementById("imgType").selectedIndex = 0; break; case "PNG": document.getElementById("imgType").selectedIndex = 1; break; case "JPEG": document.getElementById("imgType").selectedIndex = 2; } inWidth.value = rule[1]; inHeight.value = rule[2]; outWidth.value = rule[3]; outHeight.value = rule[4]; } //Listen for user input on target dimension input fields to automatically calculate aspect ratio outWidth.addEventListener("input", aspectCheckW); outHeight.addEventListener("input", aspectCheckH); inWidth.onkeypress = function() { outHeight.value = 0; outWidth.value = 0; return isNumber(event); }; inHeight.onkeypress = function() { outHeight.value = 0; outWidth.value = 0; return isNumber(event); }; outWidth.onkeypress = function() { return isNumber(event); }; outHeight.onkeypress = function() { return isNumber(event); }; input.appendChild(saveRuleBtn); input.appendChild(discardRuleBtn); discardRuleBtn.onclick = function(){ document.getElementById(input.id).remove(); document.getElementById("addRule").style.display = "inline"; clearErr();}; saveRuleBtn.onclick = function() { if (edit) basicCheck(true, rulePos); else basicCheck(false); }; } //Populate Quick Convert List table function printQCList() { var QCList = getQCList(); var list = document.getElementById("QCList"); var table = document.getElementById("QCTable"); var filterCount = QCList.length; if (filterCount > 0) { var QCFilter = []; for (let i = 0; i < filterCount; i++) { QCFilter[i] = QCList[i].split(":"); let delRow = document.createElement("a"); delRow.innerHTML = "delete"; delRow.onclick = function() { QCList.splice(delRow.parentElement.parentElement.sectionRowIndex, 1); localStorage.setItem("downscale-qclist", JSON.stringify(QCList)); table.tBodies.item(0).innerHTML = ""; printQCList(); }; //QCList Array: [0] - Filetype, [1] - Image Width, [2] - Image Height, [3] - Original Filesize, [4] - New Filesize, [5] - Filename, [6] - Image Base64 MD5 Hash var row = table.tBodies.item(0).insertRow(-1); row.insertCell(0).innerHTML = QCFilter[i][0]; row.insertCell(1).innerHTML = '[ ' + QCFilter[i][1] + ' x ' + QCFilter[i][2] + ' ]'; row.insertCell(2).innerHTML = QCFilter[i][3]; row.insertCell(3).innerHTML = '→'; row.insertCell(4).innerHTML = QCFilter[i][4]; row.insertCell(5).innerHTML = '

' + QCFilter[i][5] + '

'; row.insertCell(6).appendChild(delRow); } } } //*************************************************************************************// // MENUS // //*************************************************************************************// function appendSettings() { //Button-------------------------------------------------------- var span = document.createElement("span"); var button = document.createElement("a"); button.id = "imgResizeSettings"; button.className += "fa fa-cog"; button.style = "cursor: pointer;"; button.title = "Image Resizer Settings"; var ref = document.getElementById('shortcut-settings'); ref.insertBefore(span, parent.nextSibling); span.appendChild(button); //Overlay | imgResizeOverlay------------------------------------ var overlay = document.createElement("div"); overlay.id = "imgResizeOverlay"; overlay.classList.add("settingsOverlay"); document.body.appendChild(overlay); //Settings menu links | imgResizeMenu--------------------------- var menu = document.createElement("div"); menu.id = "imgResizeMenu"; menu.classList.add("dialog"); overlay.appendChild(menu); var close = document.createElement("a"); close.className += "close fa fa-times"; close.style = "float: right;"; close.title = "Close"; menu.insertAdjacentElement('afterbegin', close); //Settings var settingsBtn = document.createElement("a"); settingsBtn.innerHTML += "Settings"; settingsBtn.classList.add("menuBtns"); settingsBtn.style = "font-weight: bold;"; settingsBtn.onclick = function() { settingsDiv.className = "downscale-menu-on"; presetsDiv.className = "downscale-menu-off"; QCListDiv.className = "downscale-menu-off"; helpDiv.className = "downscale-menu-off"; settingsBtn.style = "font-weight: bold;"; presetsBtn.style = ""; QCListBtn.style = ""; helpBtn.style = ""; }; menu.appendChild(settingsBtn); //Presets var presetsBtn = document.createElement("a"); presetsBtn.innerHTML += "Presets"; presetsBtn.classList.add("menuBtns"); presetsBtn.onclick = function() { settingsDiv.className = "downscale-menu-off"; presetsDiv.className = "downscale-menu-on"; QCListDiv.className = "downscale-menu-off"; helpDiv.className = "downscale-menu-off"; settingsBtn.style = ""; presetsBtn.style = "font-weight: bold;"; QCListBtn.style = ""; helpBtn.style = ""; }; menu.appendChild(presetsBtn); //Quick Convert List var QCListBtn = document.createElement("a"); QCListBtn.innerHTML += "Quick Convert"; QCListBtn.classList.add("menuBtns"); QCListBtn.onclick = function() { settingsDiv.className = "downscale-menu-off"; presetsDiv.className = "downscale-menu-off"; QCListDiv.className = "downscale-menu-on"; helpDiv.className = "downscale-menu-off"; settingsBtn.style = ""; presetsBtn.style = ""; QCListBtn.style = "font-weight: bold;"; helpBtn.style = ""; }; menu.appendChild(QCListBtn); //Help var helpBtn = document.createElement("a"); helpBtn.innerHTML += "About"; helpBtn.classList.add("menuBtns"); helpBtn.onclick = function() { settingsDiv.className = "downscale-menu-off"; presetsDiv.className = "downscale-menu-off"; QCListDiv.className = "downscale-menu-off"; helpDiv.className = "downscale-menu-on"; settingsBtn.style = ""; presetsBtn.style = ""; QCListBtn.style = ""; helpBtn.style = "font-weight: bold;"; }; menu.appendChild(helpBtn); var hr = document.createElement("hr"); hr.style.borderColor = getHRColor(); menu.appendChild(hr); //Content divs| imgResizeContent--------------------------------- var content = document.createElement("div"); content.id = "imgResizeContent"; menu.appendChild(content); content.innerHTML = ""; var errMsg = document.createElement("p"); errMsg.id = "errMsg"; //Settings var settingsDiv = document.createElement("div"); settingsDiv.id = "settingsDiv"; settingsDiv.classList.add("downscale-menu-on"); content.appendChild(settingsDiv); //Presets var presetsDiv = document.createElement("div"); presetsDiv.id = "presetsDiv"; presetsDiv.classList.add("downscale-menu-off"); presetsDiv.style.textAlign = "center"; content.appendChild(presetsDiv); //Quick Convert List var QCListDiv = document.createElement("div"); QCListDiv.id = "QCListDiv"; QCListDiv.classList.add("downscale-menu-off"); content.appendChild(QCListDiv); //Help var helpDiv = document.createElement("div"); helpDiv.id = "heplDiv"; helpDiv.classList.add("downscale-menu-off"); content.appendChild(helpDiv); //-------------------------------------------------------------- var title = document.createElement("h3"); title.innerHTML = "Image Resizer Settings"; settingsDiv.appendChild(title); //Enable Resizer------------------------------------------------ var enableDiv = document.createElement("div"); enableDiv.classList.add("resizer-settings"); enableDiv.innerHTML = '' + '' + ': ' + 'Enable 4chan Image Resizer by default.'; settingsDiv.appendChild(enableDiv); var enableSet = document.getElementById("enableSet"); enableSet.checked = getSettings().enabled; enableSet.oninput = function() { //remove side menu options upon disabling ImageResizer if (!enableSet.checked) { removeCropOption(); removeQCOption(); removeRemOption(); removePreviewOption(); } var settings = getSettings(); settings.enabled = enableSet.checked; document.getElementById("imgResize").checked = enableSet.checked; localStorage.setItem("downscale-settings", JSON.stringify(settings)); }; //Enable Shortcut----------------------------------------------- var shortcutDiv = document.createElement("div"); shortcutDiv.classList.add("resizer-settings"); shortcutDiv.innerHTML = '' + '' + ': ' + 'Enable "Quick Convert" shortcut. Ctrl + Q'; settingsDiv.appendChild(shortcutDiv); var shortcutSet = document.getElementById("shortcutSet"); shortcutSet.checked = getSettings().shortcut; shortcutSet.oninput = function() { var settings = getSettings(); settings.shortcut = shortcutSet.checked; localStorage.setItem("downscale-settings", JSON.stringify(settings)); }; //Display notifications----------------------------------------- var notifySetDiv = document.createElement("div"); notifySetDiv.classList.add("resizer-settings"); notifySetDiv.innerHTML = '' + '' + ': ' + 'Display a notification when an image is downscaled.'; settingsDiv.appendChild(notifySetDiv); var notifySet = document.getElementById('displaySet'); notifySet.checked = getSettings().notify; notifySet.oninput = function() { var settings = getSettings(); settings.notify = notifySet.checked; localStorage.setItem("downscale-settings", JSON.stringify(settings)); }; //Convert all PNGs to JPEGs------------------------------------- var convertSetDiv = document.createElement("div"); convertSetDiv.classList.add("resizer-settings"); convertSetDiv.innerHTML = '' + '' + ': ' + 'Automatically convert all added PNGs to JPEGs. Presets apply as normal.'; settingsDiv.appendChild(convertSetDiv); var convertSet = document.getElementById('convertSet'); convertSet.checked = getSettings().convert; convertSet.oninput = function() { var settings = getSettings(); settings.convert = convertSet.checked; localStorage.setItem("downscale-settings", JSON.stringify(settings)); }; //Set JPEG quality---------------------------------------------- //RegExp ^(0(\.\d{1,2})?|1(\.0+)?)$ //Only one number (0 or 1) before decimal, and up tp 2 numbers after decimal, if there is a 0 before decimal (between 0 and 9) //e.g. 0.92 true, 1.92 false var qualitySetDiv = document.createElement("div"); qualitySetDiv.classList.add("resizer-settings"); qualitySetDiv.innerHTML = '' + '' + ': ' + 'A number between 0 and 1 indicating the output image quality.'; settingsDiv.appendChild(qualitySetDiv); var inputField = document.getElementById('imgQuality'); inputField.value = getSettings().jpegQuality; inputField.onkeypress = function() { return isDecimalNumber(event); }; //Check input field validity inputField.oninput = function() { var inputField = document.getElementById('imgQuality'); var r = new RegExp(/^(0(\.\d{1,2})?|1(\.0+)?)$/); if(r.test(document.getElementById('imgQuality').value)) { inputField.setCustomValidity(""); var settings = getSettings(); settings.jpegQuality = inputField.value; localStorage.setItem("downscale-settings", JSON.stringify(settings)); } else inputField.setCustomValidity("Set the value between 1 and 0 up to 2 numbers after the decimal point."); }; //Cropper Settings----------------------------------------------- var title2 = document.createElement("h3"); title2.innerHTML = "Image Cropper Settings"; settingsDiv.appendChild(title2); //Crop output format--------------------------------------------- var cropOutDiv = document.createElement("div"); cropOutDiv.classList.add("resizer-settings"); cropOutDiv.innerHTML = '' + ' ' + ': ' + 'Set the desired output format for cropped images.'; settingsDiv.appendChild(cropOutDiv); var cropOutSet = document.getElementById('cropOut'); cropOutSet.value = getSettings().cropOutput; cropOutSet.oninput = function() { var settings = getSettings(); settings.cropOutput = cropOutSet.value; localStorage.setItem("downscale-settings", JSON.stringify(settings)); }; //Crop image smoothing------------------------------------------ var cropSmoothDiv = document.createElement("div"); cropSmoothDiv.classList.add("resizer-settings"); cropSmoothDiv.innerHTML = '' + '' + ': ' + 'Enables image smoothing for cropped images.'; settingsDiv.appendChild(cropSmoothDiv); var smoothSet = document.getElementById('cropSmooth'); smoothSet.checked = getSettings().smoothing; smoothSet.oninput = function() { var settings = getSettings(); settings.smoothing = smoothSet.checked; localStorage.setItem("downscale-settings", JSON.stringify(settings)); }; //Preset table | ruleTable---------------------------------------- var tableWrapper = document.createElement("div"); tableWrapper.style.overflowY = "auto"; tableWrapper.style.maxHeight = "220px"; var table = document.createElement("table"); var thead = document.createElement("thead"); var tbody = document.createElement("tbody"); var presetsTitle = document.createElement("h3"); presetsTitle.innerHTML = "Presets"; presetsDiv.appendChild(presetsTitle); table.appendChild(thead); table.appendChild(tbody); table.id = "ruleTable"; var row = thead.insertRow(0); row.insertCell(0).outerHTML = "Format"; row.insertCell(1).outerHTML = "Input"; row.insertCell(2).outerHTML = ""; row.insertCell(3).outerHTML = "Output"; row.insertCell(4).outerHTML = ""; row.insertCell(5).outerHTML = ""; presetsDiv.appendChild(tableWrapper); tableWrapper.appendChild(table); //Input container | inputContainer------------------------------ var inputDiv = document.createElement("div"); inputDiv.id = "inputContainer"; presetsDiv.appendChild(inputDiv); var addRuleBtn = document.createElement("button"); addRuleBtn.id = "addRule"; addRuleBtn.innerHTML = "New Preset"; printList(); presetsDiv.appendChild(addRuleBtn); presetsDiv.appendChild(errMsg); button.onclick = function(){ overlay.style.display = "block"; }; close.onclick = function(){ overlay.style.display = "none"; }; window.addEventListener('click', function(closeSettingsMenu) { if (closeSettingsMenu.target == overlay) overlay.style.display = "none"; }); addRuleBtn.onclick = function(){ inputUI(false); }; //import/export buttons var bottomPresets = document.createElement("div"); bottomPresets.style = "float: left;"; var separator1 = document.createElement("span"); separator1.innerHTML = " | "; var importPresets = document.createElement("a"); var exportPresets = document.createElement("a"); importPresets.innerHTML = "Import"; exportPresets.innerHTML = "Export"; importPresets.classList.add("menuBtns"); bottomPresets.innerHTML += ''; //file-input importPresets.onclick = function(){ document.getElementById('importPresetsFile-input').click(); }; exportPresets.onclick = function(){ downloadObjectAsJson(getPresets(), "4chan Image Resizer v" + version + " Presets List - " + Date.now()); }; //call file exporter bottomPresets.appendChild(importPresets); bottomPresets.appendChild(separator1); bottomPresets.appendChild(exportPresets); presetsDiv.appendChild(bottomPresets); //import document.getElementById('importPresetsFile-input').addEventListener('change', function() { var jsonPresetsFile = new FileReader(); jsonPresetsFile.onload = function() { var originalPresets = getPresets(); var duplicateCount1 = 0; var tempDuplicateCount1 = 0; //parse raw text var importedPresets = JSON.parse(jsonPresetsFile.result); //check if array if (Array.isArray(importedPresets)) { for (let i = 0; i < importedPresets.length; i++) { var line1 = importedPresets[i].split(':'); if (line1.length != 5) { if(DEBUG) console.log("[Error] Imported array does not match the required length (5)"); if(DEBUG) console.log(line1); alert("Error: Array length mismatch.\nThis file is either outdated or invalid."); return; } else { //check for duplicate entries for (let j = 0; j < originalPresets.length; j++) { var tempLine = line1[0] + ":" + line1[1] + ":" + line1[2] + ":" + line1[3] + ":" + line1[4]; if (tempLine == originalPresets[j]) { tempDuplicateCount1++; break; } } //if not a dupe, push to the original array if (tempDuplicateCount1 == 0) { originalPresets.push(importedPresets[i]); } //count all duplicate entries else { duplicateCount1 += tempDuplicateCount1; tempDuplicateCount1 = 0; } } } //add the final result to local storage localStorage.setItem("downscale-presets", JSON.stringify(originalPresets)); //rebuild list document.getElementById("ruleTable").tBodies.item(0).innerHTML = ""; printList(); var newEntries1 = importedPresets.length - duplicateCount1; alert("Succesfully imported " + importedPresets.length + " entries.\nDuplicate entries skipped: " + duplicateCount1 + "\nNew entries added: " + newEntries1); } else { alert("Error: Invalid data type."); if(DEBUG) console.log("[Error] Imported data object is not an array.") } } jsonPresetsFile.readAsText(this.files[0]); }); //Quick Convert table | QCTable---------------------------------- var QCTableWrapper = document.createElement("div"); QCTableWrapper.style.overflowY = "auto"; QCTableWrapper.style.maxHeight = "220px"; var QCTable = document.createElement("table"); var QCThead = document.createElement("thead"); var QCTbody = document.createElement("tbody"); var QCTitle = document.createElement("h3"); QCTitle.innerHTML = "Quick Convert List"; QCListDiv.appendChild(QCTitle); QCListDiv.innerHTML += "

Images on this list will be automatically converted to JPEG with a quality setting of 92.

"; QCTable.appendChild(QCThead); QCTable.appendChild(QCTbody); QCTable.id = "QCTable"; var QCRow = QCThead.insertRow(0); QCRow.insertCell(0).outerHTML = "Format"; QCRow.insertCell(1).outerHTML = "Dimensions"; QCRow.insertCell(2).outerHTML = "Original Size"; QCRow.insertCell(3).outerHTML = ""; QCRow.insertCell(4).outerHTML = "New Size"; QCRow.insertCell(5).outerHTML = "Filename"; QCRow.insertCell(6).outerHTML = ""; QCListDiv.appendChild(QCTableWrapper); QCTableWrapper.appendChild(QCTable); //import/export buttons var bottomQCL = document.createElement("div"); bottomQCL.style = "padding-top: 1em;"; var separator2 = document.createElement("span"); separator2.innerHTML = " | "; var importQCList = document.createElement("a"); var exportQCList = document.createElement("a"); importQCList.innerHTML = "Import"; exportQCList.innerHTML = "Export"; importQCList.classList.add("menuBtns"); bottomQCL.innerHTML += ''; //file-input importQCList.onclick = function(){ document.getElementById('importQCLFile-input').click(); }; exportQCList.onclick = function(){ downloadObjectAsJson(getQCList(), "4chan Image Resizer v" + version + " Quick Convert List - " + Date.now()); }; //call file exporter bottomQCL.appendChild(importQCList); bottomQCL.appendChild(separator2); bottomQCL.appendChild(exportQCList); QCListDiv.appendChild(bottomQCL); //import document.getElementById('importQCLFile-input').addEventListener('change', function() { var jsonFile = new FileReader(); jsonFile.onload = function() { var originalQCL = getQCList(); var duplicateCount2 = 0; var tempDuplicateCount2 = 0; //parse raw text var importedQCL = JSON.parse(jsonFile.result); //check if array if (Array.isArray(importedQCL)) { for (let i = 0; i < importedQCL.length; i++) { var line = importedQCL[i].split(':'); if (line.length != 7 || line[6].length != 32) { if(DEBUG) console.log("[Error] Imported array does not match the required length (7) or contains an invalid MD5 hash."); if(DEBUG) console.log(line); alert("Error: Array length mismatch.\nThis file is either outdated or invalid."); return; } else { //check for duplicate MD5 hashes for (let j = 0; j < originalQCL.length; j++) { var originalLine2 = originalQCL[j].split(':'); if (line[6] == originalLine2[6]) { tempDuplicateCount2++; break; } } //if not a dupe, push to the original array if (tempDuplicateCount2 == 0) { originalQCL.push(importedQCL[i]); } //count all duplicate entries else { duplicateCount2 += tempDuplicateCount2; tempDuplicateCount2 = 0; } } } //add the final result to local storage localStorage.setItem("downscale-qclist", JSON.stringify(originalQCL)); //rebuild list document.getElementById("QCTable").tBodies.item(0).innerHTML = ""; printQCList(); var newEntries2 = importedQCL.length - duplicateCount2; alert("Succesfully imported " + importedQCL.length + " entries.\nDuplicate entries skipped: " + duplicateCount2 + "\nNew entries added: " + newEntries2); } else { alert("Error: Invalid data type."); if(DEBUG) console.log("[Error] Imported data object is not an array.") } } jsonFile.readAsText(this.files[0]); }); //delete all QCL entries var delAll = document.createElement("a"); var emptyArray = []; delAll.innerHTML = "Delete All"; delAll.style = "float: right; margin-right: 1em;"; delAll.onclick = function(){ if (confirm(" WARNING!\nAre you sure you want to DELETE ALL entries from the \"Quick Convert List\"?")) { localStorage.setItem("downscale-qclist", JSON.stringify(emptyArray)); document.getElementById("QCTable").tBodies.item(0).innerHTML = ""; } }; bottomQCL.appendChild(delAll); //INITIAL PRINT OF QUICK CONVERT LIST printQCList(); //Help---------------------------------------------------------- var helpTitle = document.createElement("h3"); helpTitle.innerHTML = "About"; helpDiv.appendChild(helpTitle); var rant = document.createElement("div"); rant.innerHTML = '4chan Image Resizer Downscaler automatically downscales images based on custom presets. Originally developed to downscale anime/vidya screenshots "on the fly". Now supports image cropping!

' + '
Presets
To automate image resizing, you have to create a preset by choosing an input image format and entering input and output dimensions (pixels). Then just add an image to a quick reply form. ' + 'If it meets any of the created input requirements, the image will be automatically downscaled to your specified dimensions as a JPEG. ' + '

Note that output dimensions are constrained by input dimensions aspect ratio. ' + '
Also note that setting JPEG output quality to 1 may result in filesizes larger than that of the original image, and should be considered as a placebo.
' + // '
Quick Convert
Allows you to quickly convert images (PNG/JPEG) to JPEG.' + '
This is very useful when an image exceeds 4chan image size limit of 4 MB.' + '

It works well on super high resolution images (+3000px), sometimes drastically cutting the filesize without any noticeble quality loss.' + ' However, it is not recommended to use it on grayscale PNG images, i.e. manga pages, because most of the time it will result in larger than original filesizes.' + '
Once you are satisfied with the "Quick Convert" results, you can click "Remember" on the side menu to add the image MD5 hash to the "Quick Convert List", which will always automatically convert the image for you in the future.' + '

You can also use Ctrl + Q keyboard shortcut to perform "Quick Convert" faster. Press again to "Remember" the image.
' + // '
Import/Export
Allows you to seperatly backup both, "Presets" and "Quick Convert" lists as .json files.' + '
Import works by merging list entries instead of overwriting them, so you can export/import items between domains without any worry.
' + // '
Image Cropping*
A basic image cropping tool that uses Cropper.js libraby. ' + 'You can change cropped image output format in the settings tab.
PNG is lossless and preserves transperancy, but results in larger filesizes.
JPEG is lossy, but results in smaller filesizes (the "JPEG quality" setting also applies here).' + '

Note after a bit more testing, I noticed that Cropper.js can sometimes will cause strange, spike-like artifacts on images with sharp lines/edges(?) which have their dimensions end with an odd number (e.g. original image of 1533x737px, i.e. image dimensions before cropping matter), regardless of output format or quality setting. This can be "fixed" by enabling "Image Smoothing". ' + 'Side effects include smearing, blurriness and overall lower image quality.

I\'ll try to fix this in the next release.
' + // '
Cropper Controls' + '
While cropping:
  • Double click LMB - switch between cropping and zooming.
  • Hold Shift while cropping - draw a fixed rectangle.
  • MWheel - zoom in/out.
  • ' + '
  • Backspace - clear selection.
  • Enter - confirm selection.
  • Esc or Delete - close cropper.
' + 'After selection:
  • Enter or LMB - set image to QR form.
  • Backspace or RMB - undo selection.
  • MMB - close cropper.
  • Shift + RMB - open context menu.
' + // '
*NEW*
  • "Crop Image" option now appears in the "Side Menu" after any image conversion (except the cropping itself).
' + '

'; helpDiv.appendChild(rant); } //Only when QR form is open. function appendSideMenu() { //Arrow | sideMenuArrow---------------------------------------------------------- var arrow = document.createElement("a"); arrow.id = "sideMenuArrow"; arrow.title = "Side Menu"; arrow.style.cursor = "pointer"; arrow.innerHTML = "◀"; var arrowRef = document.getElementById("autohide"); arrowRef.parentNode.insertAdjacentElement("beforebegin", arrow); arrow.onclick = function(){ sideMenu.classList.toggle("downscale-menu-on"); }; //Side Menu | sideMenu---------------------------------------------------------- var sideMenu = document.createElement("div"); sideMenu.id = "sideMenu"; sideMenu.classList.add("dialog"); var sideMenuRef = document.getElementById("qr"); sideMenuRef.insertAdjacentElement("afterbegin", sideMenu); //Close side menu dialog by clicking anywhere but here: window.addEventListener('click', function(event) { var getSideMenu = document.getElementById("sideMenu"); if (!event.target.matches('#sideMenuArrow') && !event.target.matches('#sideMenu') && !event.target.matches('#imgResize') && !event.target.matches('#quickConvert') && !event.target.matches('#imgResizeLabel')) { if (getSideMenu.classList.contains('downscale-menu-on')) getSideMenu.classList.remove('downscale-menu-on'); } }); } appendSettings(); //*************************************************************************************// //END OF MENUs // //*************************************************************************************// //Saves image details to local storage function saveImgMD5 (img, file, imgMD5, newSize) { removeRemOption(); var QCList = getQCList(); //"file/jpeg" -> "JPEG" var filetype = file.type.split("/").pop().toUpperCase(); //remove filetype var filename = file.name.split(".").slice(0,-1).join("."); //replace seperators filename = filename.replace(/:/g,"_"); var orig_filesize = formatBytes(file.size); var new_filesize = formatBytes(newSize); //QCList Array [0] - Filetype, [1] - Image Width, [2] - Image Height, [3] - Original Filesize, [4] - New Filesize, [5] - Filename, [6] - Image Base64 MD5 Hash var QCString = filetype + ":" + img.width + ":" + img.height + ":" + orig_filesize + ":" + new_filesize + ":" + filename + ":" + imgMD5; QCList.push(QCString); localStorage.setItem("downscale-qclist", JSON.stringify(QCList)); //Show notification var info = file.name + '\nAdded to the "Quick Convert List"'; var msgDetail = {type: 'info', content: info, lifetime: 5}; var msgEvent = new CustomEvent('CreateNotification', {bubbles: true, detail: msgDetail}); document.dispatchEvent(msgEvent); //rebuild list document.getElementById("QCTable").tBodies.item(0).innerHTML = ""; printQCList(); } //Removes these Side Menu options function removeQCOption() { var checkQC = document.getElementById("qcDiv"); if (checkQC) checkQC.remove(); } function removeRemOption() { var checkRem = document.getElementById("remDiv"); if (checkRem) checkRem.remove(); } function removeCropOption() { var checkCrop = document.getElementById("cropDiv"); if (checkCrop) checkCrop.remove(); } function removePreviewOption() { var checkPreview = document.getElementById("previewImg"); if (checkPreview) checkPreview.remove(); } function removeHR() { var checkHR = document.getElementById("sm-hr"); if (checkHR) checkHR.remove(); } //Get border color for
hack function getHRColor () { var sample = document.getElementById("imgResizeMenu"); return window.getComputedStyle(sample, null).getPropertyValue("border-bottom-color"); } //Show/hide sidemenu
function appendHR() { var hr = document.createElement("hr"); hr.id = "sm-hr"; hr.style.borderColor = getHRColor(); document.getElementById("sideMenu").appendChild(hr); } //Image viewer function showImage(img, size, width, height, filename) { var overlay = document.createElement("div"); overlay.id = "pvOverlay"; //----------------------------------------------- var pvHeader = document.createElement("div"); pvHeader.id = "pvHeader"; pvHeader.className = "dialog"; //opacity hack pvHeader.classList.add("pvOpct"); pvHeader.innerHTML = filename + "
(" + formatBytes(size)+ ", " + Math.round(width) + "x" + Math.round(height) + ")"; //----------------------------------------------- var closePv = document.createElement("a"); closePv.className = "close fa fa-times"; closePv.style = "float: right; cursor: pointer; margin-right: 20px; margin-top: -9px; transform: scale(1.5);"; closePv.title = "Close"; closePv.onclick = function(){ overlay.remove(); }; //----------------------------------------------- var pvImg = document.createElement("img"); pvImg.id = "pvImg"; pvImg.classList.add("centerImg"); pvImg.title = "Click to close"; pvImg.src = img; pvImg.onclick = function(){ overlay.remove(); }; //----------------------------------------------- document.body.appendChild(overlay); //pvHeader.appendChild(closePv); overlay.appendChild(pvImg); overlay.appendChild(pvHeader); pvHeader.appendChild(closePv); //opacity hack setTimeout(function() { pvHeader.classList.toggle("pvOpct"); }, 2000); } //Converts dataURI to blob function dataURItoBlob(dataURI) { //convert base64/URLEncoded data component to raw binary data held in a string var byteString; if (dataURI.split(',')[0].indexOf('base64') >= 0) { byteString = atob(dataURI.split(',')[1]); } else { byteString = unescape(dataURI.split(',')[1]); } //separate out the mime component var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; //write the bytes of the string to a typed array var ia = new Uint8Array(byteString.length); for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } return new Blob([ia], { type: mimeString }); } //json file exporter function downloadObjectAsJson(exportObj, exportName) { var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj)); var downloadAnchorNode = document.createElement('a'); downloadAnchorNode.setAttribute("href", dataStr); downloadAnchorNode.setAttribute("download", exportName + ".json"); document.body.appendChild(downloadAnchorNode); downloadAnchorNode.click(); downloadAnchorNode.remove(); } //Prevent multiple event listeners var scListenerExists = false; //Quick Convert shortcut | Ctrl+Q if (getSettings().shortcut && !scListenerExists) { document.addEventListener('keyup', qCShortcut); scListenerExists = true ; } function qCShortcut(e) { var convertBtn = document.getElementById("quickConvert"); var rememberBtn = document.getElementById("rememberMD5"); //if shortcut is enabled, simulate clicks if (getSettings().shortcut) { if (e.ctrlKey && e.keyCode == 81 && convertBtn) { convertBtn.click(); } else if (e.ctrlKey && e.keyCode == 81 && rememberBtn) { rememberBtn.click(); } } } //Fix filetype in filename function constructFilename(mime, filename) { //"file/jpeg" -> "jpeg" var filetype = mime.split("/").pop(); //remove filetype from filename and add the correct one var new_filename = filename.split(".").slice(0,-1).join(".").concat("." + filetype); return new_filename; } //Bloat function isDecimalNumber(e){var h=e.which?e.which:e.keyCode;return!(46!=h&&h>31&&(h<48||h>57));} function isNumber(e){var i=(e=e||window.event).which?e.which:e.keyCode;return!(i>31&&(i<48||i>57));} function formatBytes(a,b){if(0==a)return"0 Bytes";var c=1024,d=b||2,e=["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"],f=Math.floor(Math.log(a)/Math.log(c));return parseFloat((a/Math.pow(c,f)).toFixed(d))+" "+e[f];}