4chan Image Resizer

Automatically downscales images based on custom presets and more. Requires 4chan X.

当前为 2020-10-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name 4chan Image Resizer
  3. // @namespace https://greasyfork.org/en/users/393416
  4. // @version 2.1
  5. // @description Automatically downscales images based on custom presets and more. Requires 4chan X.
  6. // @author greenronia
  7. // @match *://boards.4chan.org/*
  8. // @match *://boards.4channel.org/*
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.js
  10. // @grant none
  11. // @icon https://i.imgur.com/hQp5BTf.png
  12. // ==/UserScript==
  13. //
  14. //Using SparkMD5 to generate image hashes - https://github.com/satazor/js-spark-md5
  15. //*********************************//
  16. // //
  17. // "it just werks" //
  18. // //
  19. //*********************************//
  20. // //
  21. //----------DEBUG MODE-------------//
  22. var DEBUG = false;//console //
  23. //---------------------------------//
  24. if(DEBUG) console.log("[ImageResizer] Initialized");
  25. //CSS
  26. var style = document.createElement("style");
  27. style.innerHTML = '' +
  28. '.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' +
  29. '.settingsOverlay { background: rgba(0,0,0,0.8); display: none; height: 100%; left: 0; position: fixed; top: 0; width: 100%; z-index: 777; } \n' +
  30. '#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' +
  31. '#pvHeader { position: fixed; height: 35px; width: 100%; opacity: 0; -webkit-transition: opacity 0.5s ease-in-out;}\n' +
  32. '#pvHeader:hover { opacity: 0.8; -webkit-transition: none; }\n' +
  33. '.pvOpct { opacity: 0.7 !important; } \n' +
  34. '#imgResizeMenu { position: fixed; top: 20%; left: 35%; width: 35%; min-width: 670px; padding: 2em; overflow: hidden; z-index: 8;}\n' +
  35. '#imgResizeMenu h3 { text-align: center; }\n' +
  36. '#imgResizeMenu a { cursor: pointer; }\n' +
  37. '#imgResizeMenu label { text-decoration-line: underline; }\n' +
  38. '.settingsOverlay input[type=number] { -moz-appearance: textfield; text-align: right; }\n' +
  39. '.resizer-settings { padding-bottom: 5px }\n' +
  40. '#errMsg { color: red; text-align: center; }\n' +
  41. '#ruleTable { border-collapse: collapse; }\n' +
  42. '#ruleTable td, th { padding: 8px; text-align: left; border-bottom: 1pt solid; }\n' +
  43. '#QCTable { border-collapse: collapse; }\n' +
  44. '#QCTable td, th { padding: 8px; text-align: center; border-bottom: 1pt solid; }\n' +
  45. '#QCTable p { margin: auto; max-width: 100px; overflow: hidden; text-overflow: ellipsis; }\n' +
  46. '#inputContainer { text-align: center; padding-top: 30px; }\n' +
  47. '#inputContainer button { margin-top: 20px; }\n' +
  48. '.menuBtns { margin-left: 1em; }\n' +
  49. '#sideMenu { position: absolute; display: none; padding: 5px 0px 5px 0px; width: 101px; margin-left: -106px; margin-top: -2px;}\n' +
  50. '.sideMenuElement { background: inherit; display: block; cursor: pointer; padding: 2px 10px 2px 10px; text-align: left;}\n' +
  51. '.downscale-menu-off { display: none; }\n' +
  52. '.downscale-menu-on { display: block !important; }';
  53. var styleRef = document.querySelector("script");
  54. styleRef.parentNode.insertBefore(style, styleRef);
  55. //Load settings
  56. getSettings();
  57. getPresets();
  58. getQCList();
  59. function getSettings() {
  60. if (JSON.parse(localStorage.getItem("downscale-settings"))) {
  61. var settings = JSON.parse(localStorage.getItem("downscale-settings"));
  62. }
  63. else {
  64. settings = { enabled:true, notify:true, convert:false, jpegQuality:0.92 };
  65. }
  66. return settings;
  67. }
  68. function getPresets() {
  69. if (JSON.parse(localStorage.getItem("downscale-presets"))) {
  70. var presets = JSON.parse(localStorage.getItem("downscale-presets"));
  71. }
  72. else {
  73. presets = [];
  74. }
  75. return presets;
  76. }
  77. function getQCList() {
  78. if (JSON.parse(localStorage.getItem("downscale-qclist"))) {
  79. var QCList = JSON.parse(localStorage.getItem("downscale-qclist"));
  80. }
  81. else {
  82. QCList = [];
  83. }
  84. return QCList;
  85. }
  86. //Checking if QuickReply dialog is open.
  87. document.addEventListener('QRDialogCreation', function(listenForQRDC) {
  88. var checkBox = document.getElementById("imgResize");
  89. var sideMenu = document.getElementById("sideMenuArrow");
  90. //Checking if the "resize" check box and "side menu" already exist
  91. if (!sideMenu) {
  92. appendSideMenu();
  93. }
  94. if (!checkBox) {
  95. appendCheckBox();
  96. }
  97. //Listening for clicks on check box
  98. document.getElementById("imgResize").addEventListener("click", checkState);
  99. checkState(1);
  100. if(DEBUG) console.log("[QRFile] Listening...");
  101. //QRFile | Listening for QRFile, in response to: QRGetFile | Request File
  102. document.addEventListener('QRFile', function(GetFile) {
  103. if(DEBUG) console.log("[QRFile] File served: " + GetFile.detail);
  104. //Remove Remember option upon adding a (new) file.
  105. removeRemOption();
  106. const file = GetFile.detail;
  107. //Initialize an instance of a FileReader
  108. const reader = new FileReader();
  109. //Checking if the file is JPG or PNG
  110. if (file.type == "image/jpeg" || file.type == "image/png") {
  111. if(DEBUG) console.log("Acceptable File type: " + file.type);
  112. //Check if resizer already completed its task (to determine priority)
  113. var complete = false;
  114. var presets = getPresets();
  115. var QCList = getQCList();
  116.  
  117. reader.onload = function(f) {
  118. var img = new Image();
  119. img.src = reader.result;
  120.  
  121. img.onload = function() {
  122. //Base64 MD5 hash of an image
  123. var imgMD5 = SparkMD5.hash(img.src);
  124. if(DEBUG) console.log("<FILTER START>");
  125. if(DEBUG) if(getSettings().convert) console.log("[PNGConverter] Enabled"); else console.log("[PNGConverter] Disabled");
  126. if(DEBUG) console.log("INPUT Dimensions: " + img.width + "x" + img.height);
  127. if(DEBUG) console.log("INPUT File size: " + formatBytes(file.size));
  128. //Add QC button
  129. quickConvert(img, file, imgMD5);
  130. //Remove/Add preview buton
  131. removePreviewOption();
  132. appendPreviewBtn(img.src, file.size, img.width, img.height, file.name);
  133. //THE priority list
  134. if (getQCList().length > 0) checkMD5(img, imgMD5);
  135. if (presets.length > 0 && !complete) checkPresets(img);
  136. if (getSettings().convert && !complete) checkPNG(img);
  137. return;
  138. }
  139. return;
  140. }
  141.  
  142. function checkMD5(img, imgMD5) {
  143. if(DEBUG) console.log("[quickConvert] Checking for matching MD5: " + imgMD5);
  144. var filterCount = QCList.length;
  145. var matchFound = false;
  146. for (var i = 0; i < filterCount; i++) {
  147. //unpack md5 hash
  148. var filterMD5 = QCList[i].split(":").pop();
  149. if (filterMD5 == imgMD5) {
  150. if(DEBUG) console.log("[quickConvert] Match found.");
  151. matchFound = true;
  152. resizer(img.width, img.height, img);
  153. break;
  154. }
  155. }
  156. if(DEBUG) if (!matchFound)console.log("[quickConvert] No matching MD5 found.");
  157. return;
  158. }
  159. function checkPresets(img) {
  160. var matchCount = 0;
  161. var rule = [];
  162. var presetCount = presets.length;
  163. for (var i = 0; i < presetCount; i++) {
  164. //unpack rules
  165. rule[i] = presets[i].split(":");
  166. //check for matching file type
  167. if (rule[i][0] != 0) {
  168. switch (parseInt(rule[i][0])) {
  169. case 1:
  170. rule[i][0] = "image/png";
  171. break;
  172. case 2:
  173. rule[i][0] = "image/jpeg";
  174. }
  175. if (rule[i][0] != file.type) continue;
  176. }
  177. //check for matching dimensions
  178. if (rule[i][1] == img.width && rule[i][2] == img.height) {
  179. var MAX_WIDTH = parseInt(rule[i][3]);
  180. var MAX_HEIGHT = parseInt(rule[i][4]);
  181. matchCount++;
  182. if(DEBUG) console.log("Preset '" + i + "' matched: " + rule[i]);
  183. break;
  184. }
  185. }
  186. //failsafe
  187. if (matchCount == 0 || matchCount > 1) {
  188. if(DEBUG) console.log("Image didn't match any presets.\n------<END>------");
  189. return;
  190. }
  191. else {
  192. resizer(MAX_WIDTH, MAX_HEIGHT, img);
  193. return;
  194. }
  195. }
  196. //PNG -> JPEG
  197. function checkPNG(img) {
  198. if (file.type == "image/png") {
  199. var MAX_WIDTH = img.width;
  200. var MAX_HEIGHT = img.height;
  201. if(DEBUG) console.log("[PNGConverter] Converting PNG to JPEG");
  202. resizer(MAX_WIDTH, MAX_HEIGHT, img);
  203. }
  204. else {
  205. if(DEBUG) console.log("[PNGConverter] Image format isn't PNG.\n------<END>------");
  206. return;
  207. }
  208. }
  209. //The main resize function
  210. function resizer(MAX_WIDTH, MAX_HEIGHT, img, imgMD5) {
  211. if(DEBUG && !imgMD5) console.log("<FILTER END>");
  212. removePreviewOption();
  213. var canvas = document.createElement("canvas");
  214. //Input dimensions
  215. var width = img.width;
  216. var height = img.height;
  217. //Calculating dimensions/aspect ratio
  218. if (width > height) {
  219. if (width > MAX_WIDTH) {
  220. height *= MAX_WIDTH / width;
  221. width = MAX_WIDTH;
  222. }
  223. } else {
  224. if (height > MAX_HEIGHT) {
  225. width *= MAX_HEIGHT / height;
  226. height = MAX_HEIGHT;
  227. }
  228. }
  229. // resize the canvas to the new dimensions
  230. canvas.width = width;
  231. canvas.height = height;
  232. // scale & draw the image onto the canvas
  233. var ctx = canvas.getContext("2d");
  234. ctx.drawImage(img, 0, 0, width, height);
  235.  
  236. //Converts dataURI to blob
  237. function dataURItoBlob(dataURI) {
  238. //convert base64/URLEncoded data component to raw binary data held in a string
  239. var byteString;
  240. if (dataURI.split(',')[0].indexOf('base64') >= 0) { byteString = atob(dataURI.split(',')[1]); }
  241. else { byteString = unescape(dataURI.split(',')[1]); }
  242. //separate out the mime component
  243. var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
  244. //write the bytes of the string to a typed array
  245. var ia = new Uint8Array(byteString.length);
  246. for (var i = 0; i < byteString.length; i++) {
  247. ia[i] = byteString.charCodeAt(i);
  248. }
  249. return new Blob([ia], {
  250. type: mimeString
  251. });
  252. }
  253. //canvas to dataURL | JPEG quality (0-1)
  254. var dataURL;
  255. if (imgMD5) dataURL = canvas.toDataURL('image/jpeg', 92);
  256. else dataURL = canvas.toDataURL('image/jpeg', parseFloat(getSettings().jpegQuality));
  257. //dataURL to blob
  258. var blob = dataURItoBlob(dataURL);
  259. //Stop classObserver | prevent trigger loop
  260. classObserver.disconnect();
  261. if(DEBUG) console.log("[classObserver] Stopping...");
  262. setFile(blob, img, width, height, imgMD5);
  263. appendPreviewBtn(dataURL, blob.size, width, height, file.name);
  264. }
  265. //Set the new file to QR form
  266. function setFile(blob, img, width, height, imgMD5) {
  267. var detail = {
  268. file: blob,
  269. name: file.name
  270. };
  271. var event = new CustomEvent('QRSetFile', {
  272. bubbles: true,
  273. detail: detail
  274. });
  275. document.dispatchEvent(event);
  276. if (imgMD5) rememberQC(img, file, imgMD5, blob.size);
  277. if(DEBUG) console.log("[QRSetFile] File Sent");
  278. if(DEBUG) console.log("OUTPUT Dimesnions: " + Math.round(width) + "x" + Math.round(height));
  279. if(DEBUG) console.log("OUTPUT Filesize: " + formatBytes(blob.size));
  280. if(DEBUG) console.log("JPEG Quality: " + getSettings().jpegQuality);
  281. //Notification
  282. 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) +")";
  283. if (getSettings().notify) {
  284. var msgDetail = {type: 'info', content: FSInfo, lifetime: 5};
  285. var msgEvent = new CustomEvent('CreateNotification', {bubbles: true, detail: msgDetail});
  286. document.dispatchEvent(msgEvent);
  287. }
  288. //Remove Quick Convert option after conversion
  289. removeQCOption();
  290. //Restart classObserver
  291. classObserver.observe(targetNode, observerOptions);
  292. //Preset priority
  293. complete = true;
  294. if(DEBUG) console.log("------<END>------\n[classObserver] Restarting...");
  295. }
  296. //Quick Convert (QC) image from Side Menu
  297. function quickConvert(img, file, imgMD5) {
  298. var existCheck = document.getElementById("quickConvert");
  299. //create QC button if it doesn't exist
  300. if (!existCheck) {
  301. //Convert options container (future use)
  302. var container = document.createElement("div");
  303. container.id = "qcDiv";
  304. //Convert button
  305. var convert = document.createElement("a");
  306. convert.id = "quickConvert";
  307. convert.classList.add("sideMenuElement");
  308. convert.classList.add("entry");
  309. convert.innerHTML = "Quick Convert";
  310. convert.title = "Convert image to JPEG";
  311. //CSS on hover
  312. convert.onmouseover = function(){this.classList.toggle("focused")};
  313. convert.onmouseout = function(){this.classList.toggle("focused")};
  314. var hr = document.createElement("hr");
  315. hr.style.borderColor = getHRColor();
  316. //Call resizer
  317. convert.onclick = function(){
  318. if(DEBUG) console.log("[quickConvert] Manually calling Resizer...");
  319. resizer(img.width, img.height, img, imgMD5);
  320. };
  321. var parent = document.getElementById("sideMenu");
  322. parent.appendChild(container);
  323. container.appendChild(hr);
  324. container.appendChild(convert);
  325.  
  326. }
  327. //if QC button already exists
  328. else {
  329. existCheck.onclick = function(){
  330. if(DEBUG) console.log("[quickConvert] Manually calling Resizer...");
  331. resizer(img.width, img.height, img, imgMD5);
  332. };
  333. }
  334. return;
  335. }
  336. //Remember button
  337. function rememberQC (img, file, imgMD5, newSize) {
  338. var container = document.createElement("div");
  339. container.id = "remDiv";
  340. var remember = document.createElement("a");
  341. remember.id = "rememberMD5";
  342. remember.classList.add("sideMenuElement");
  343. remember.classList.add("entry");
  344. remember.innerHTML = "Remember";
  345. remember.style.fontWeight = "bold";
  346. remember.title = "Always convert this image."
  347. //CSS on hover
  348. remember.onmouseover = function(){this.classList.toggle("focused")};
  349. remember.onmouseout = function(){this.classList.toggle("focused")};
  350. var hr = document.createElement("hr");
  351. hr.style.borderColor = getHRColor();
  352. remember.onclick = function(){ saveImgMD5(img, file, imgMD5, newSize) };
  353. var parent = document.getElementById("sideMenu");
  354. parent.appendChild(container);
  355. container.appendChild(hr);
  356. container.appendChild(remember);
  357. }
  358. //Preview Image button
  359. function appendPreviewBtn(img, pvSize, pvWidth, pvHeight, pvName) {
  360. var existCheck = document.getElementById("previewImg");
  361. if (!existCheck) {
  362. var preview = document.createElement("a");
  363. preview.id = "previewImg";
  364. preview.classList.add("sideMenuElement");
  365. preview.classList.add("entry");
  366. preview.innerHTML = "Preview Image";
  367. //CSS on hover
  368. preview.onmouseover = function(){this.classList.toggle("focused")};
  369. preview.onmouseout = function(){this.classList.toggle("focused")};
  370. preview.onclick = function(){ showImage(img, pvSize, pvWidth, pvHeight, pvName) };
  371. var parent = document.getElementById("sideMenu");
  372. parent.appendChild(preview);
  373. }
  374. else {
  375. existCheck.onclick = function(){ showImage(img, pvSize, pvWidth, pvHeight, pvName) };
  376. }
  377. return;
  378. }
  379. //Read the file
  380. reader.readAsDataURL(file);
  381. } else {
  382. removeQCOption();
  383. if(DEBUG) console.log("[Error] Invalid FileType: " + file.type + "\n------<END>------");
  384. }
  385. }, false);
  386. //Observing if a file was uploaded or not | checking if div (with id: "file-n-submit") has class named: "has-file"
  387. function callback(mutationList, observer) {
  388. if (document.getElementById("file-n-submit").classList.contains("has-file") === true && checkState(2) === true) {
  389. if(DEBUG) console.log("------<START>------\n[classObserver] File detected")
  390. //QRGetFile | Request File
  391. if(DEBUG) console.log("[QRGetFile] Requesting file...");
  392. document.dispatchEvent(new CustomEvent('QRGetFile'));
  393.  
  394. } else if (checkState(2) === false) {
  395. if(DEBUG) console.log("[classObserver] ImageResizer is disabled");
  396. return;
  397. }
  398. else {
  399. //Remove Side menu options upon removing a file.
  400. removeQCOption();
  401. removeRemOption();
  402. removePreviewOption();
  403. if(DEBUG) console.log("[classObserver] No file");
  404. }
  405. }
  406. //MutationObserver. Checks if div (with id "file-n-submit") has its class attribute changed
  407. const targetNode = document.getElementById("file-n-submit");
  408. var observerOptions = {
  409. attributes: true
  410. };
  411. var classObserver = new MutationObserver(callback);
  412. if(DEBUG) console.log("[classObserver] Starting...");
  413. classObserver.observe(targetNode, observerOptions);
  414. }, false);
  415. //*************************************************************************************//
  416. //END OF THE MAIN PROCESS
  417. //*************************************************************************************//
  418. //Add a label with a check box for ImageResize + Setting button in Side Menu
  419. function appendCheckBox() {
  420. var settingsButton = document.createElement("a");
  421. var label = document.createElement("label");
  422. var input = document.createElement("input");
  423. input.type = "checkbox";
  424. input.id = "imgResize";
  425. label.id = "imgResizeLabel";
  426. input.title = "Enable Image Resizer";
  427. input.style = "margin-left: 0";
  428. settingsButton.classList.add("sideMenuElement");
  429. settingsButton.classList.add("entry");
  430. label.classList.add("sideMenuElement");
  431. //CSS on hover
  432. label.classList.add("entry");
  433. var parent = document.getElementById("sideMenu");
  434. parent.appendChild(label);
  435. label.appendChild(input);
  436. label.title = "Enable Image Resizer";
  437. label.innerHTML += " Enabled";
  438. settingsButton.title = "Image Resizer Settings";
  439. settingsButton.innerHTML = "Settings";
  440. parent.appendChild(settingsButton);
  441. //CSS on hover
  442. label.onmouseover = function(){this.classList.toggle("focused")};
  443. label.onmouseout = function(){this.classList.toggle("focused")};
  444. settingsButton.onmouseover = function(){this.classList.toggle("focused")};
  445. settingsButton.onmouseout = function(){this.classList.toggle("focused")};
  446. //Open settings menu
  447. settingsButton.onclick = function(){ document.getElementById("imgResizeOverlay").style.display = "block" };
  448. //Checked by default
  449. document.getElementById("imgResize").checked = getSettings().enabled;
  450. }
  451. //Check box state
  452. function checkState(caller) {
  453. var state = document.getElementById("imgResize").checked;
  454. if (state === true) {
  455. if (caller != 2) if(DEBUG) console.log("[ImageResizer] Enabled");
  456. return true;
  457. } else {
  458. if (caller != 2) if(DEBUG) console.log("[ImageResizer] Disabled");
  459. return false;
  460. }
  461. }
  462. //Clears error messages <p>
  463. function clearErr() { document.getElementById("errMsg").innerHTML = ""; }
  464. //Checks for any logic errors (upscaling)
  465. function basicCheck(edit, rulePos) {
  466. var inWidth = parseInt(document.getElementById("inWidth").value);
  467. var inHeight = parseInt(document.getElementById("inHeight").value);
  468. var outWidth = parseInt(document.getElementById("outWidth").value);
  469. var outHeight = parseInt(document.getElementById("outHeight").value);
  470. var imgType = parseInt(document.getElementById("imgType").value);
  471. if (outWidth <= 0 || outHeight <= 0) { document.getElementById("errMsg").innerHTML = "Invalid output dimensions"; return}
  472. else if (inWidth < outWidth || inHeight < outHeight) { document.getElementById("errMsg").innerHTML = "Cannot upscale images"; return}
  473. else finalCheck(edit, imgType, inWidth, inHeight, outWidth, outHeight, rulePos);
  474. return;
  475. }
  476. //Checks for any rule overlaps
  477. // ([0] - Image type, [1] - Input width, [2] - Input height, [3] - Output width, [4] - Output height)
  478. function finalCheck(edit, imgType, inWidth, inHeight, outWidth, outHeight, rulePos) {
  479. var e = document.getElementById("imgType");
  480. var format = e.options[e.selectedIndex].text;
  481. var presetString = imgType + ":" + inWidth + ":" + inHeight + ":" + outWidth + ":" + outHeight;
  482. var presets = getPresets();
  483. if (presets.length > 0) {
  484. var rule = [];
  485. var presetCount = presets.length;
  486. for (var i = 0; i < presetCount; i++) {
  487. if (edit && i === rulePos) continue;
  488. rule[i] = presets[i].split(":");
  489. if (presetString == presets[i]) { document.getElementById("errMsg").innerHTML = "Exact preset already exists"; return }
  490. 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 }
  491. }
  492. }
  493. //save preset
  494. clearErr();
  495. if (edit) presets[rulePos] = presetString;
  496. else presets.push(presetString);
  497. localStorage.setItem("downscale-presets", JSON.stringify(presets));
  498. //rebuild list
  499. document.getElementById("ruleTable").tBodies.item(0).innerHTML = "";
  500. printList();
  501. //hide / display
  502. document.getElementById("ruleInput").remove();
  503. document.getElementById("addRule").style.display = "inline";
  504. return;
  505. }
  506. //Check if possible to calculate output WIDTH
  507. function aspectCheckH() {
  508. var inWidth = document.getElementById("inWidth").value;
  509. var inHeight = document.getElementById("inHeight").value;
  510. var outWidth = document.getElementById("outWidth").value;
  511. var outHeight = document.getElementById("outHeight").value;
  512. if (outHeight > 0) {
  513. if (parseInt(inHeight) >= parseInt(outHeight)) {
  514. calcAspect("width", inWidth, inHeight, outHeight);
  515. clearErr();
  516. }
  517. else {
  518. document.getElementById("errMsg").innerHTML = "Cannot upscale images";
  519. }
  520. }
  521. }
  522. //Check if possible to calculate output HEIGHT
  523. function aspectCheckW() {
  524. var inWidth = document.getElementById("inWidth").value;
  525. var inHeight = document.getElementById("inHeight").value;
  526. var outWidth = document.getElementById("outWidth").value;
  527. var outHeight = document.getElementById("outHeight").value;
  528. if (outWidth > 0) {
  529. if (parseInt(inWidth) >= parseInt(outWidth)) {
  530. calcAspect("height", inWidth, inHeight, outWidth);
  531. clearErr();
  532. }
  533. else {
  534. document.getElementById("errMsg").innerHTML = "Cannot upscale images";
  535. }
  536. }
  537. }
  538. //Aspect ratio calculation (finds the other output dimension based on given exact input dimensions)
  539. function calcAspect(dimension, w, h, output) {
  540. if (dimension == "width") {
  541. var width = output / h * w;
  542. document.getElementById("outWidth").value = Math.round(width);
  543. }
  544. if (dimension == "height") {
  545. var height = output / w * h;
  546. document.getElementById("outHeight").value = Math.round(height);
  547. }
  548. }
  549. //Populate Presets list
  550. function printList() {
  551. var presets = getPresets();
  552. var list = document.getElementById("imgResizeList");
  553. var table = document.getElementById("ruleTable");
  554. if (presets.length > 0) {
  555. var rule = [];
  556. var presetCount = presets.length;
  557. for (let i = 0; i < presetCount; i++) {
  558. rule[i] = presets[i].split(":");
  559. switch (parseInt(rule[i][0])) {
  560. case 0:
  561. rule[i][0] = "PNG/JPEG";
  562. break;
  563. case 1:
  564. rule[i][0] = "PNG";
  565. break;
  566. case 2:
  567. rule[i][0] = "JPEG";
  568. }
  569. let delRow = document.createElement("a");
  570. let editRow = document.createElement("a");
  571. delRow.innerHTML = "delete";
  572. editRow.innerHTML = "edit";
  573. //delete a rule and rebuild the list
  574. delRow.onclick = function() {
  575. if (document.getElementById("inputContainer")) document.getElementById("inputContainer").innerHTML = "";
  576. presets.splice(delRow.parentElement.parentElement.sectionRowIndex, 1);
  577. localStorage.setItem("downscale-presets", JSON.stringify(presets));
  578. table.tBodies.item(0).innerHTML = "";
  579. printList();
  580. clearErr();
  581. document.getElementById("addRule").style.display = "inline";
  582. };
  583. editRow.onclick = function() { inputUI(true, rule[i], i); clearErr(); };
  584. //Array contents: [0] - Image type, [1] - Input width, [2] - Input height, [3] - Output width, [4] - Output height
  585. var row = table.tBodies.item(0).insertRow(-1);
  586. row.insertCell(0).innerHTML = rule[i][0];
  587. row.insertCell(1).innerHTML = '[ ' + rule[i][1] + ' x ' + rule[i][2] + ' ]';
  588. row.insertCell(2).innerHTML = '&#8594;';
  589. row.insertCell(3).innerHTML = '[ ' + rule[i][3] + ' x ' + rule[i][4] + ' ]';
  590. row.insertCell(4).appendChild(editRow);
  591. row.insertCell(5).appendChild(delRow);
  592. }
  593. }
  594. }
  595. //Input field
  596. function inputUI(edit, rule, rulePos) {
  597. if (document.getElementById("inputContainer")) document.getElementById("inputContainer").innerHTML = "";
  598. document.getElementById("addRule").style.display = "none";
  599. var inputDiv = document.getElementById("inputContainer");
  600. var input = document.createElement("div");
  601. var discardRuleBtn = document.createElement("button");
  602. discardRuleBtn.innerHTML = "Cancel";
  603. var saveRuleBtn = document.createElement("button");
  604. saveRuleBtn.innerHTML = "Save";
  605. input.id = "ruleInput";
  606. //Rules form
  607. input.innerHTML = '' +
  608. '' +
  609. '<select id="imgType" name="imgType" title="Input Format">' +
  610. '<option value="0">PNG/JPEG</option>' +
  611. '<option value="1">PNG</option>' +
  612. '<option value="2">JPEG</option>' +
  613. '</select>&ensp;' +
  614. '' +
  615. '<input type="number" id="inWidth" title="Input Width" size="2" min="0" value="0" onfocus="this.select();"></input> x ' +
  616. '' +
  617. '<input type="number" id="inHeight" title="Input Height" size="2" min="0" value="0" onfocus="this.select();"></input> ' +
  618. '&ensp; &#8594; &ensp; <input type="number" id="outWidth" title="Output Width" size="2" min="0" value="0" onfocus="this.select();"></input> x ' +
  619. '<input type="number" id="outHeight" title="Output Height" size="2" min="0" value="0" onfocus="this.select();"></input><br>';
  620. inputDiv.appendChild(input);
  621. var inWidth = document.getElementById("inWidth");
  622. var inHeight = document.getElementById("inHeight");
  623. var outWidth = document.getElementById("outWidth");
  624. var outHeight = document.getElementById("outHeight");
  625. if (edit) {
  626. switch (rule[0]) {
  627. case "PNG/JPEG":
  628. document.getElementById("imgType").selectedIndex = 0;
  629. break;
  630. case "PNG":
  631. document.getElementById("imgType").selectedIndex = 1;
  632. break;
  633. case "JPEG":
  634. document.getElementById("imgType").selectedIndex = 2;
  635. }
  636. inWidth.value = rule[1];
  637. inHeight.value = rule[2];
  638. outWidth.value = rule[3];
  639. outHeight.value = rule[4];
  640. }
  641. //Listen for user input on target dimension input fields to automatically calculate aspect ratio
  642. outWidth.addEventListener("input", aspectCheckW);
  643. outHeight.addEventListener("input", aspectCheckH);
  644. inWidth.onkeypress = function() { outHeight.value = 0; outWidth.value = 0; return isNumber(event); };
  645. inHeight.onkeypress = function() { outHeight.value = 0; outWidth.value = 0; return isNumber(event); };
  646. outWidth.onkeypress = function() { return isNumber(event); };
  647. outHeight.onkeypress = function() { return isNumber(event); };
  648.  
  649. input.appendChild(saveRuleBtn);
  650. input.appendChild(discardRuleBtn);
  651. discardRuleBtn.onclick = function(){ document.getElementById(input.id).remove(); document.getElementById("addRule").style.display = "inline"; clearErr();};
  652. saveRuleBtn.onclick = function() { if (edit) basicCheck(true, rulePos); else basicCheck(false); };
  653. }
  654. //Populate Quick Convert List table
  655. function printQCList() {
  656. var QCList = getQCList();
  657. var list = document.getElementById("QCList");
  658. var table = document.getElementById("QCTable");
  659. var filterCount = QCList.length;
  660. if (filterCount > 0) {
  661. var QCFilter = [];
  662. for (let i = 0; i < filterCount; i++) {
  663. QCFilter[i] = QCList[i].split(":");
  664. let delRow = document.createElement("a");
  665. delRow.innerHTML = "delete";
  666. delRow.onclick = function() {
  667. QCList.splice(delRow.parentElement.parentElement.sectionRowIndex, 1);
  668. localStorage.setItem("downscale-qclist", JSON.stringify(QCList));
  669. table.tBodies.item(0).innerHTML = "";
  670. printQCList();
  671. };
  672. //QCList Array: [0] - Filetype, [1] - Image Width, [2] - Image Height, [3] - Original Filesize, [4] - New Filesize, [5] - Filename, [6] - Image Base64 MD5 Hash
  673. var row = table.tBodies.item(0).insertRow(-1);
  674. row.insertCell(0).innerHTML = QCFilter[i][0];
  675. row.insertCell(1).innerHTML = '[ ' + QCFilter[i][1] + ' x ' + QCFilter[i][2] + ' ]';
  676. row.insertCell(2).innerHTML = QCFilter[i][3];
  677. row.insertCell(3).innerHTML = '&#8594;';
  678. row.insertCell(4).innerHTML = QCFilter[i][4];
  679. row.insertCell(5).innerHTML = '<p title = "' + QCFilter[i][5] +'">' + QCFilter[i][5] + '</p>';
  680. row.insertCell(6).innerHTML = '<p title = "' + QCFilter[i][6] +'">' + QCFilter[i][6] + '</p>';
  681. row.insertCell(7).appendChild(delRow);
  682. }
  683. }
  684. }
  685. //*************************************************************************************//
  686. // /!\ very shitty menu ahead /!\ //
  687. // I'm too lazy to fix this mess //
  688. //*************************************************************************************//
  689. function appendSettings() {
  690. //Button--------------------------------------------------------
  691. var span = document.createElement("span");
  692. var button = document.createElement("a");
  693. button.id = "imgResizeSettings";
  694. button.className += "fa fa-cog";
  695. button.style = "cursor: pointer;";
  696. button.title = "Image Resizer Settings";
  697. var ref = document.getElementById('shortcut-settings');
  698. ref.insertBefore(span, parent.nextSibling);
  699. span.appendChild(button);
  700. //Overlay | imgResizeOverlay------------------------------------
  701. var overlay = document.createElement("div");
  702. overlay.id = "imgResizeOverlay";
  703. overlay.classList.add("settingsOverlay");
  704. document.body.appendChild(overlay);
  705. //Settings menu links | imgResizeMenu---------------------------
  706. var menu = document.createElement("div");
  707. menu.id = "imgResizeMenu";
  708. menu.classList.add("dialog");
  709. overlay.appendChild(menu);
  710. var close = document.createElement("a");
  711. close.className += "close fa fa-times";
  712. close.style = "float: right;";
  713. close.title = "Close";
  714. menu.insertAdjacentElement('afterbegin', close);
  715. //Settings
  716. var settingsBtn = document.createElement("a");
  717. settingsBtn.innerHTML += "Settings";
  718. settingsBtn.classList.add("menuBtns");
  719. settingsBtn.style = "font-weight: bold;";
  720. settingsBtn.onclick = function() {
  721. settingsDiv.className = "downscale-menu-on";
  722. presetsDiv.className = "downscale-menu-off";
  723. QCListDiv.className = "downscale-menu-off";
  724. helpDiv.className = "downscale-menu-off";
  725. settingsBtn.style = "font-weight: bold;";
  726. presetsBtn.style = "";
  727. QCListBtn.style = "";
  728. helpBtn.style = "";
  729. };
  730. menu.appendChild(settingsBtn);
  731. //Presets
  732. var presetsBtn = document.createElement("a");
  733. presetsBtn.innerHTML += "Presets";
  734. presetsBtn.classList.add("menuBtns");
  735. presetsBtn.onclick = function() {
  736. settingsDiv.className = "downscale-menu-off";
  737. presetsDiv.className = "downscale-menu-on";
  738. QCListDiv.className = "downscale-menu-off";
  739. helpDiv.className = "downscale-menu-off";
  740. settingsBtn.style = "";
  741. presetsBtn.style = "font-weight: bold;";
  742. QCListBtn.style = "";
  743. helpBtn.style = "";
  744. };
  745. menu.appendChild(presetsBtn);
  746. //Quick Convert List
  747. var QCListBtn = document.createElement("a");
  748. QCListBtn.innerHTML += "Quick Convert";
  749. QCListBtn.classList.add("menuBtns");
  750. QCListBtn.onclick = function() {
  751. settingsDiv.className = "downscale-menu-off";
  752. presetsDiv.className = "downscale-menu-off";
  753. QCListDiv.className = "downscale-menu-on";
  754. helpDiv.className = "downscale-menu-off";
  755. settingsBtn.style = "";
  756. presetsBtn.style = "";
  757. QCListBtn.style = "font-weight: bold;";
  758. helpBtn.style = "";
  759. };
  760. menu.appendChild(QCListBtn);
  761. //Help
  762. var helpBtn = document.createElement("a");
  763. helpBtn.innerHTML += "About";
  764. helpBtn.classList.add("menuBtns");
  765. helpBtn.onclick = function() {
  766. settingsDiv.className = "downscale-menu-off";
  767. presetsDiv.className = "downscale-menu-off";
  768. QCListDiv.className = "downscale-menu-off";
  769. helpDiv.className = "downscale-menu-on";
  770. settingsBtn.style = "";
  771. presetsBtn.style = "";
  772. QCListBtn.style = "";
  773. helpBtn.style = "font-weight: bold;";
  774. };
  775. menu.appendChild(helpBtn);
  776. var hr = document.createElement("hr");
  777. hr.style.borderColor = getHRColor();
  778. menu.appendChild(hr);
  779. //Content divs| imgResizeContent------------------------------------
  780. var content = document.createElement("div");
  781. content.id = "imgResizeContent";
  782. menu.appendChild(content);
  783. content.innerHTML = "";
  784. var errMsg = document.createElement("p");
  785. errMsg.id = "errMsg";
  786. //Settings
  787. var settingsDiv = document.createElement("div");
  788. settingsDiv.id = "settingsDiv";
  789. settingsDiv.classList.add("downscale-menu-on");
  790. content.appendChild(settingsDiv);
  791. //Presets
  792. var presetsDiv = document.createElement("div");
  793. presetsDiv.id = "presetsDiv";
  794. presetsDiv.classList.add("downscale-menu-off");
  795. presetsDiv.style.textAlign = "center";
  796. content.appendChild(presetsDiv);
  797. //Quick Convert List
  798. var QCListDiv = document.createElement("div");
  799. QCListDiv.id = "QCListDiv";
  800. QCListDiv.classList.add("downscale-menu-off");
  801. content.appendChild(QCListDiv);
  802. //Help
  803. var helpDiv = document.createElement("div");
  804. helpDiv.id = "heplDiv";
  805. helpDiv.classList.add("downscale-menu-off");
  806. content.appendChild(helpDiv);
  807. //Enable Resizer------------------------------------------------
  808. var title = document.createElement("h3");
  809. title.innerHTML = "Image Resizer Settings";
  810. settingsDiv.appendChild(title);
  811. var enableDiv = document.createElement("div");
  812. enableDiv.classList.add("resizer-settings");
  813. enableDiv.innerHTML = '' +
  814. '<input type="checkbox" id="enableSet" title="" size="1"></input>' +
  815. '<label for="enableSet">Enable Resizer</label>:&ensp;' +
  816. 'Enable / disable 4chan Image Resizer.';
  817. settingsDiv.appendChild(enableDiv);
  818. var enableSet = document.getElementById('enableSet');
  819. enableSet.checked = getSettings().enabled;
  820. enableSet.oninput = function() {
  821. var settings = getSettings();
  822. settings.enabled = enableSet.checked;
  823. document.getElementById("imgResize").checked = enableSet.checked;
  824. localStorage.setItem("downscale-settings", JSON.stringify(settings));
  825. };
  826. //Display notifications-----------------------------------------
  827. var notifySetDiv = document.createElement("div");
  828. notifySetDiv.classList.add("resizer-settings");
  829. notifySetDiv.innerHTML = '' +
  830. '<input type="checkbox" id="displaySet" title="" size="1"></input>' +
  831. '<label for="displaySet">Display Notifications</label>:&ensp;' +
  832. 'Displays a notification when an image is downscaled.';
  833. settingsDiv.appendChild(notifySetDiv);
  834. var notifySet = document.getElementById('displaySet');
  835. notifySet.checked = getSettings().notify;
  836. notifySet.oninput = function() {
  837. var settings = getSettings();
  838. settings.notify = notifySet.checked;
  839. localStorage.setItem("downscale-settings", JSON.stringify(settings));
  840. };
  841. //Convert all PNGs to JPEGs-------------------------------------
  842. var convertSetDiv = document.createElement("div");
  843. convertSetDiv.classList.add("resizer-settings");
  844. convertSetDiv.innerHTML = '' +
  845. '<input type="checkbox" id="convertSet" title="" size="1"></input>' +
  846. '<label for="convertSet">Convert All PNGs</label>:&ensp;' +
  847. 'Automatically converts all added PNGs to JPEGs. Presets apply as normal.';
  848. settingsDiv.appendChild(convertSetDiv);
  849. var convertSet = document.getElementById('convertSet');
  850. convertSet.checked = getSettings().convert;
  851. convertSet.oninput = function() {
  852. var settings = getSettings();
  853. settings.convert = convertSet.checked;
  854. localStorage.setItem("downscale-settings", JSON.stringify(settings));
  855. };
  856. //Set JPEG quality----------------------------------------------
  857. //RegExp ^(0(\.\d{1,2})?|1(\.0+)?)$
  858. //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)
  859. //e.g. 0.92 true, 1.92 false
  860. var qualitySetDiv = document.createElement("div");
  861. qualitySetDiv.classList.add("resizer-settings");
  862. qualitySetDiv.innerHTML = '' +
  863. '<input type="text" id="imgQuality" title="JPEG Quality" size="1"></input>' +
  864. '<label for="imgQuality">JPEG Quality</label>:&ensp;' +
  865. 'A number between 0 and 1 indicating the output image quality. Recommended 0.92.';
  866. settingsDiv.appendChild(qualitySetDiv);
  867. var inputField = document.getElementById('imgQuality');
  868. inputField.value = getSettings().jpegQuality;
  869. inputField.onkeypress = function() { return isDecimalNumber(event); };
  870. //Check input field validity
  871. inputField.oninput = function() {
  872. var inputField = document.getElementById('imgQuality');
  873. var r = new RegExp(/^(0(\.\d{1,2})?|1(\.0+)?)$/);
  874. if(r.test(document.getElementById('imgQuality').value)) {
  875. inputField.setCustomValidity("");
  876. var settings = getSettings();
  877. settings.jpegQuality = inputField.value;
  878. localStorage.setItem("downscale-settings", JSON.stringify(settings));
  879. }
  880. else inputField.setCustomValidity("Set the value between 1 and 0 up to 2 numbers after the decimal point.");
  881. };
  882. //Preset table | ruleTable----------------------------------------
  883. var tableWrapper = document.createElement("div");
  884. tableWrapper.style.overflowY = "auto";
  885. tableWrapper.style.maxHeight = "200px";
  886. var table = document.createElement("table");
  887. var thead = document.createElement("thead");
  888. var tbody = document.createElement("tbody");
  889. var presetsTitle = document.createElement("h3");
  890. presetsTitle.innerHTML = "Presets";
  891. presetsDiv.appendChild(presetsTitle);
  892. table.appendChild(thead);
  893. table.appendChild(tbody);
  894. table.id = "ruleTable";
  895. var row = thead.insertRow(0);
  896. row.insertCell(0).outerHTML = "<th>Format</th>";
  897. row.insertCell(1).outerHTML = "<th>Input</th>";
  898. row.insertCell(2).outerHTML = "<th></th>";
  899. row.insertCell(3).outerHTML = "<th>Output</th>";
  900. row.insertCell(4).outerHTML = "<th></th>";
  901. row.insertCell(5).outerHTML = "<th></th>";
  902. presetsDiv.appendChild(tableWrapper);
  903. tableWrapper.appendChild(table);
  904. //Input container | inputContainer------------------------------
  905. var inputDiv = document.createElement("div");
  906. inputDiv.id = "inputContainer";
  907. presetsDiv.appendChild(inputDiv);
  908. var addRuleBtn = document.createElement("button");
  909. addRuleBtn.id = "addRule";
  910. addRuleBtn.innerHTML = "New Preset";
  911. printList();
  912. presetsDiv.appendChild(addRuleBtn);
  913. presetsDiv.appendChild(errMsg);
  914. button.onclick = function(){ overlay.style.display = "block"; };
  915. close.onclick = function(){ overlay.style.display = "none"; };
  916. window.addEventListener('click', function(closeSettingsMenu) {
  917. if (closeSettingsMenu.target == overlay) overlay.style.display = "none";
  918. });
  919. addRuleBtn.onclick = function(){ inputUI(false); };
  920. //Quick Convert table | QCTable----------------------------------
  921. var QCTableWrapper = document.createElement("div");
  922. QCTableWrapper.style.overflowY = "auto";
  923. QCTableWrapper.style.maxHeight = "210px";
  924. var QCTable = document.createElement("table");
  925. var QCThead = document.createElement("thead");
  926. var QCTbody = document.createElement("tbody");
  927. var QCTitle = document.createElement("h3");
  928. QCTitle.innerHTML = "Quick Convert List";
  929. QCListDiv.appendChild(QCTitle);
  930. QCListDiv.innerHTML += "<p style='text-align: center;'>Images on this list will be automatically converted to JPEG with a quality setting of 92.</p>";
  931. QCTable.appendChild(QCThead);
  932. QCTable.appendChild(QCTbody);
  933. QCTable.id = "QCTable";
  934. var QCRow = QCThead.insertRow(0);
  935. QCRow.insertCell(0).outerHTML = "<th>Format</th>";
  936. QCRow.insertCell(1).outerHTML = "<th>Dimensions</th>";
  937. QCRow.insertCell(2).outerHTML = "<th>Original Size</th>";
  938. QCRow.insertCell(3).outerHTML = "<th></th>";
  939. QCRow.insertCell(4).outerHTML = "<th>New Size</th>";
  940. QCRow.insertCell(5).outerHTML = "<th>Filename</th>";
  941. QCRow.insertCell(6).outerHTML = "<th>MD5 Hash</th>";
  942. QCRow.insertCell(7).outerHTML = "<th></th>";
  943. QCListDiv.appendChild(QCTableWrapper);
  944. QCTableWrapper.appendChild(QCTable);
  945. printQCList();
  946. //Help----------------------------------------------------------
  947. var helpTitle = document.createElement("h3");
  948. helpTitle.innerHTML = "About";
  949. helpDiv.appendChild(helpTitle);
  950. var rant = document.createElement("p");
  951. rant.innerHTML = '<span style="font-weight:bold;">4chan Image <span style="text-decoration-line: line-through;">Resizer</span></span> <s>can\'t upscale</s> automatically downscales images based on custom presets. Originally developed to downscale anime/vidya screenshots "on the fly".<br><br>' +
  952. 'To get started, you first have to create a preset by choosing an input image format and entering input and output dimensions (pixels). Then just add (drag & drop) an image to a quick reply form. ' +
  953. '<br>If it meets any of the presets input requirements, the image will be automatically downscaled to specified dimensions as a <span style="font-weight:bold;">JPEG</span>. ' +
  954. '<br><br><span style="font-weight:bold;">Note</span> that output dimensions are constrained by input dimensions <span style="font-weight:bold;">aspect ratio</span>. ' +
  955. '<br><span style="font-weight:bold;">Also note</span> that <span style="font-weight:bold;">setting JPEG output quality to 1</span> may result in filesizes larger than that of the original image, and should be considered as a placebo.' +
  956. '<br><br><span style="font-weight:bold; color: red;">NEW</span><br><span style="font-weight:bold;"> "Quick Convert"</span> allows you to quickly convert images (PNG/JPEG) to JPEG at a quality of 92 <s>for now</s>.' +
  957. '<br>This is very useful when an image exceeds 4chan image size limit of <span style="font-weight:bold;">4 MB</span> (higher on some other boards) and you do not want to, or cannot be bothered to lower the resolution manually.' +
  958. '<br>It works well on super high resolution images (+3000px), sometimes drastically cutting the filesize without any noticeble quality loss.' +
  959. ' However, <span style="font-weight:bold;">it is not recommended to use it on grayscale PNG images</span>, i.e. manga pages, because most of the time <span style="font-weight:bold;">it will result in larger than original filesizes</span>.' +
  960. '<br>Once you are satisfied with the "Quick Convert" results, you can click "Remember" on the "Side Menu" to add the image MD5 hash <s>not the actual image MD5 hash, the Image->Base64->MD5 hash</s> to "Quick Convert List", which allows you to always automatically convert this image before posting.' +
  961. '<br><br> Also added a simple <span style="font-weight:bold;">"Preview Image"</span> feature for JPEGs/PNGs. Click on the image to close it.' +
  962. '<br><a style="float: right;" href="https://greasyfork.org/en/scripts/391758-4chan-image-resizer" target="_blank">[version 2.1]</a>';
  963. helpDiv.appendChild(rant);
  964. }
  965. //Only when QR form is open.
  966. function appendSideMenu() {
  967. //Arrow | sideMenuArrow----------------------------------------------------------
  968. var arrow = document.createElement("a");
  969. arrow.id = "sideMenuArrow";
  970. arrow.title = "Image Settings";
  971. arrow.style.cursor = "pointer";
  972. arrow.innerHTML = "&#9664;";
  973. var arrowRef = document.getElementById("autohide");
  974. arrowRef.parentNode.insertAdjacentElement("beforebegin", arrow);
  975. arrow.onclick = function(){ sideMenu.classList.toggle("downscale-menu-on"); };
  976. //Side Menu | sideMenu----------------------------------------------------------
  977. var sideMenu = document.createElement("div");
  978. sideMenu.id = "sideMenu";
  979. sideMenu.classList.add("dialog");
  980. var sideMenuRef = document.getElementById("qr");
  981. sideMenuRef.insertAdjacentElement("afterbegin", sideMenu);
  982. //Close side menu dialog by clicking anywhere but here:
  983. window.addEventListener('click', function(event) {
  984. var getSideMenu = document.getElementById("sideMenu");
  985. if (!event.target.matches('#sideMenuArrow') &&
  986. !event.target.matches('#sideMenu') &&
  987. !event.target.matches('#imgResize') &&
  988. !event.target.matches('#quickConvert') &&
  989. !event.target.matches('#imgResizeLabel')) {
  990. if (getSideMenu.classList.contains('downscale-menu-on')) getSideMenu.classList.remove('downscale-menu-on');
  991. }
  992. });
  993. }
  994. //*************************************************************************************//
  995. //END OF MENUs //
  996. //*************************************************************************************//
  997. //Saves image details to local storage
  998. function saveImgMD5 (img, file, imgMD5, newSize) {
  999. removeRemOption();
  1000. var QCList = getQCList();
  1001. //"file/jpeg" -> "JPEG"
  1002. var filetype = file.type.split("/").pop().toUpperCase();
  1003. //remove filetype
  1004. var filename = file.name.split(".").slice(0,-1).join(".");
  1005. var orig_filesize = formatBytes(file.size);
  1006. var new_filesize = formatBytes(newSize);
  1007. //QCList Array [0] - Filetype, [1] - Image Width, [2] - Image Height, [3] - Original Filesize, [4] - New Filesize, [5] - Filename, [6] - Image Base64 MD5 Hash
  1008. var QCString = filetype + ":" + img.width + ":" + img.height + ":" + orig_filesize + ":" + new_filesize + ":" + filename + ":" + imgMD5;
  1009. QCList.push(QCString);
  1010. localStorage.setItem("downscale-qclist", JSON.stringify(QCList));
  1011. //Show notification
  1012. var info = 'Added to the "Quick Convert List"';
  1013. var msgDetail = {type: 'info', content: info, lifetime: 5};
  1014. var msgEvent = new CustomEvent('CreateNotification', {bubbles: true, detail: msgDetail});
  1015. document.dispatchEvent(msgEvent);
  1016. //Print list
  1017. document.getElementById("QCTable").tBodies.item(0).innerHTML = "";
  1018. printQCList();
  1019. }
  1020. //Removes these Side Menu options
  1021. function removeQCOption() {
  1022. var checkQC = document.getElementById("qcDiv");
  1023. if (checkQC) checkQC.remove();
  1024. }
  1025. function removeRemOption() {
  1026. var checkRem = document.getElementById("remDiv");
  1027. if (checkRem) checkRem.remove();
  1028. }
  1029. function removePreviewOption() {
  1030. var checkPreview = document.getElementById("previewImg");
  1031. if (checkPreview) checkPreview.remove();
  1032. }
  1033. //Get border color for <hr> hack
  1034. function getHRColor () {
  1035. var sample = document.getElementById("imgResizeMenu");
  1036. return window.getComputedStyle(sample, null).getPropertyValue("border-bottom-color");
  1037. }
  1038. function showImage(img, size, width, height, filename) {
  1039. var overlay = document.createElement("div");
  1040. overlay.id = "pvOverlay";
  1041. //-----------------------------------------------
  1042. var pvHeader = document.createElement("div");
  1043. pvHeader.id = "pvHeader";
  1044. pvHeader.className = "dialog";
  1045. //opacity hack
  1046. pvHeader.classList.add("pvOpct");
  1047. pvHeader.innerHTML = filename + "<br>(" + formatBytes(size)+ ", " + Math.round(width) + "x" + Math.round(height) + ")";
  1048. //-----------------------------------------------
  1049. var closePv = document.createElement("a");
  1050. closePv.className = "close fa fa-times";
  1051. closePv.style = "float: right;";
  1052. closePv.onclick = function(){ overlay.remove(); };
  1053. //-----------------------------------------------
  1054. var pvImg = document.createElement("img");
  1055. pvImg.id = "pvImg";
  1056. pvImg.classList.add("centerImg");
  1057. pvImg.title = "Click to close";
  1058. pvImg.src = img;
  1059. pvImg.onclick = function(){ overlay.remove(); };
  1060. //-----------------------------------------------
  1061. document.body.appendChild(overlay);
  1062. //overlay.appendChild(closePv);
  1063. overlay.appendChild(pvImg);
  1064. overlay.appendChild(pvHeader);
  1065. //opacity hack
  1066. setTimeout(function() { pvHeader.classList.toggle("pvOpct"); }, 2000);
  1067. }
  1068. appendSettings();
  1069. //Bloat
  1070. function isDecimalNumber(e){var h=e.which?e.which:e.keyCode;return!(46!=h&&h>31&&(h<48||h>57));}
  1071. function isNumber(e){var i=(e=e||window.event).which?e.which:e.keyCode;return!(i>31&&(i<48||i>57));}
  1072. 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];}