PageSourceSearch

https://www.simpleimageresizer.com/static/js/sharedFunctions.js?v=173

js simpleimageresizer.com collected 2026-10-01 11:25:37 UTC 39,462 bytes, 1,177 lines download raw bytes

1const SQUARE_ASPECT_RATIO = '1'
2const EXIF_VIEWER_PATH = '/exif-metadata-viewer'
3const EXIF_REMOVER_PATH = '/remove-exif'
4const IMAGE_DETAILS_PATH = '/image-details'
5const PDF_COMPRESS_PATH_ARRAY = ['/reduce-pdf-file-size', '/compress-pdf']
6const VERIFIED_STATUS = 'verified';
7const VERIFY_USER_API_URL = '/api-check-is-premium-user-verified'
8const VERIFY_ONE_TIME_PURCHASE_API_URL = '/verify-one-time-purchase'
9// error message
10const setErrorMsg = (uploadedImagesArray) => {
11    if (uploadedImagesArray.length === 0) {
12        return 'Please upload your images first\n' +
13            '(Tap/click \'Select images\' above or drag and drop a file)'
14    }
15    return ''
16}
17
18const showPOSTRequestServerError = () => {
19    hideElement(uploadImageAnimationContainer)
20    hideElement(loadingBtn)
21    showElement(submitBtn)
22    showElement(serverErrorMessage)
23}
24
25const clearErrorMsg = () => {
26    if (uploadedImages.length > 0) {
27        hideElement(errorMsg)
28    }
29}
30
31const showErrorMsg = () => {
32    showElement(errorMsg)
33    errorMsg.innerText = setErrorMsg(uploadedImages)
34}
35
36const reloadPageWithNewURL = (base_path, param) => {
37    let path = `${base_path}/${param}`;
38    history.replaceState(null, null, path)
39    location.reload()
40}
41
42// POST Request
43const fetchPOSTRequest = async (url, body) => {
44    try {
45
46        const response = await fetch(url, {
47            method: "POST",
48            body: JSON.stringify(body),
49            headers: {
50                'Content-Type': 'application/json;charset=UTF-8'
51            }
52        });
53
54        return await response.json();
55
56    } catch (error) {
57        console.error('❌ fetchPOSTRequest error:', error)
58        throw error
59    }
60}
61
62const fetchGETRequest = async (url, params = {}) => {
63    // Build query string from params
64    const queryString = new URLSearchParams(params).toString();
65    const requestUrl = queryString ? `${url}?${queryString}` : url;
66
67    try {
68        const response = await fetch(requestUrl, {
69            method: "GET",
70            headers: {
71                'Content-Type': 'application/json;charset=UTF-8'
72            }
73        });
74
75        if (!response.ok) {
76            // Handle HTTP errors
77            const errorMessage = await response.text();
78            throw new Error(`Request failed with status ${response.status}: ${errorMessage}`);
79        }
80
81        // Parse and return the JSON response
82        return await response.json();
83    } catch (error) {
84        console.error('Error fetching data:', error.message);
85        throw error;
86    }
87};
88
89const isAPIResponseVerified = async (apiUrl) => {
90    try {
91        const response = await fetchGETRequest(apiUrl, {});
92        if (response.status === VERIFIED_STATUS) {
93            return true
94        } else {
95            console.warn('API validation failed:', response.message);
96            return false
97        }
98    } catch (error) {
99        console.error('Error during API validation:', error);
100        return false
101    }
102}
103
104const updateInputsValue = (width, widthInput, height, heightInput) => {
105    if (widthInput && heightInput) {
106        widthInput.value = width;
107        heightInput.value = height;
108    }
109}
110// display uploaded image thumbnail
111const displayDefaultImage = (imageClass) => {
112    const imageThumbnail = document.getElementsByClassName(imageClass)
113    if (imageThumbnail) {
114        for (let i = 0; i < imageThumbnail.length; i++) {
115            imageThumbnail[i].addEventListener('error', (e) => {
116                e.target.src = '/static/images/default-image.png';
117                e.onerror = null;
118            })
119        }
120    }
121}
122
123const isUploadedFileHEICFormat = (file) => {
124    return file.type.toLowerCase() === "image/heic" ||
125        file.name.toLowerCase().includes(".heic");
126}
127
128const displayHEICImageThumbnail = (file, thumbnail) => {
129    if (isUploadedFileHEICFormat(file)) {
130        heic2any({blob: file, toType: "image/png", quality: 1}).then((file) => {
131            thumbnail.src = URL.createObjectURL(file)
132        })
133    }
134}
135
136// display uploaded images shorten file names
137const shortenFileName = (fileName, maxCharacters) => {
138    // Extract the file extension
139    const fileExtension = fileName.slice(fileName.lastIndexOf('.'));
140    const baseFileName = fileName.slice(0, fileName.lastIndexOf('.'));
141
142    // Determine the maximum characters based on the screen size
143    const screenSize = window.innerWidth;
144    let maxChars = maxCharacters;
145    if (screenSize <= 576) {
146        maxChars = Math.min(maxChars, 28);
147    } else if (screenSize <= 768) {
148        maxChars = Math.min(40);
149    } else if (screenSize <= 992) {
150        maxChars = Math.min(50);
151    } else {
152        maxChars = Math.min(60)
153    }
154
155    if (fileName.length <= maxChars) {
156        return fileName; // No need to shorten
157    }
158
159    // Calculate the parts of the file name to show
160    const startLength = Math.floor((maxChars - 3 - fileExtension.length) / 2);
161    const endLength = maxChars - 3 - fileExtension.length - startLength;
162    return baseFileName.substring(0, startLength) + "..." +
163        baseFileName.substring(baseFileName.length - endLength) +
164        fileExtension;
165}
166
167const shortenUploadedFileNames = (filesArray, maxCharNum) => {
168    let MAX_CHAR_NUM = maxCharNum
169    for (let i = 0; i < filesArray.length; i++) {
170        let fileName = filesArray[i].name;
171
172        if (fileName.length > 5) {
173            if (fileName.length <= MAX_CHAR_NUM) {
174                shortName = fileName;
175            }
176            if (xs && fileName.length > MAX_CHAR_NUM) {
177                shortName = fileName.slice(0, 5) + '...' + fileName.slice(-12);
178            }
179            if (s && fileName.length > MAX_CHAR_NUM) {
180                shortName = fileName.slice(0, 8) + '...' + fileName.slice(-10);
181            }
182            if (md && fileName.length > MAX_CHAR_NUM) {
183                shortName = fileName.slice(0, 5) + '...' + fileName.slice(-12);
184            }
185            if (lg && fileName.length > MAX_CHAR_NUM) {
186                shortName = fileName.slice(0, 5) + '...' + fileName.slice(-10);
187            }
188            if (xl && fileName.length > MAX_CHAR_NUM) {
189                shortName = fileName.slice(0, 5) + '...' + fileName.slice(-12);
190            }
191            return shortName
192        }
193    }
194}
195
196const displayShortenedFileNames = (name) => {
197    if (xs && name.length >= MAX_CHAR_NUM_MOBILE) {
198        shortName = name.slice(0, 5) + '...' + name.slice(-18);
199    }
200    if (s && name.length >= MAX_CHAR_NUM_MOBILE) {
201        shortName = name.slice(0, 8) + '...' + name.slice(-20);
202    }
203    if (md && name.length <= MAX_CHAR_NUM) {
204        shortName = name
205    } else if (md && name.length >= MAX_CHAR_NUM) {
206        shortName = name.slice(0, 5) + '...' + name.slice(-40);
207    }
208    if (lg && name.length <= MAX_CHAR_NUM) {
209        shortName = name
210    } else if (lg && name.length >= MAX_CHAR_NUM) {
211        shortName = name.slice(0, 5) + '...' + name.slice(-40);
212    }
213}
214
215const createResizerParametersCookie = () => {
216    if (percentRadioInput.checked) {
217        SAVE_RESIZER_PARAMETERS_COOKIE_VALUE = rangeSlider.value
218    }
219    if (dimensionRadioInput.checked) {
220        isPercentMethodSelected = false
221        SAVE_RESIZER_PARAMETERS_COOKIE_VALUE = `${resizerWidthInput.value}_${resizerHeightInput.value}`
222    }
223
224    if (resizerWidthInput.value === '') {
225        resizerWidthInput.value = null
226    }
227    if (resizerHeightInput.value === '') {
228        resizerHeightInput.value = null
229    }
230    createCookie(SAVE_RESIZER_PARAMETERS_COOKIE, SAVE_RESIZER_PARAMETERS_COOKIE_VALUE, 1)
231}
232
233const updateResizerInputValues = (cookieValue) => {
234    if (cookieValue.includes('_')) {
235        resizerWidthInput.value = cookieValue.split('_')[0]
236        resizerHeightInput.value = cookieValue.split('_')[1]
237        dimensionMethodSelected()
238        dimensionRadioInput.checked = true
239    } else {
240        percentRadioInput.checked = true
241        rangeSlider.value = cookieValue
242        percentValue.innerText = rangeSlider.value
243    }
244}
245
246const getUploadedImageFileSize = (uploadedImage) => {
247    const sizeInMB = uploadedImage.size / 1000000;
248    if (sizeInMB >= 1000) {
249        return `${(sizeInMB / 1000).toFixed(2)} GB`
250    } else if (sizeInMB >= 1) {
251        return `${sizeInMB.toFixed(1)} MB`
252    } else {
253        return `${Math.round(uploadedImage.size / 1000)} KB`
254    }
255}
256
257const getUploadedImageResolution = (imageEl, displayEl, imageWidthEl, imageHeightEl, resolutionsArray) => {
258    const reader = new FileReader();
259    reader.readAsDataURL(imageEl)
260    reader.onload = (e) => {
261        const image = new Image();
262        image.src = e.target.result;
263        image.onload = () => {
264            imageWidthEl = image.width
265            imageHeightEl = image.height
266            let imageResolution = `${imageWidthEl} x ${imageHeightEl}`
267            resolutionsArray.push(imageResolution)
268            displayEl.innerText = `Resolution: ${imageResolution}`
269        }
270    }
271}
272
273const displayUploadedImagesResolutionAndSize = (uploadedImage, displayElement, widthInput, heightInput) => {
274    const reader = new FileReader();
275    reader.readAsDataURL(uploadedImage)
276    reader.onload = (e) => {
277        const image = new Image();
278        if (isUploadedFileHEICFormat(uploadedImage)) {
279            heic2any({blob: uploadedImage, toType: "image/png", quality: 1}).then((uploadedImage) => {
280                image.src = URL.createObjectURL(uploadedImage);
281            })
282        } else {
283            image.src = e.target.result;
284        }
285        image.onload = () => {
286            imageWidth = image.width
287            imageHeight = image.height
288            imageResolution = `${imageWidth}x${imageHeight}`
289            imageSize = getUploadedImageFileSize(uploadedImage)
290            uploadedResolutions.push(imageResolution)
291            displayElement.innerText = `${imageResolution} px (${imageSize})`
292            updateInputsValue(imageWidth, widthInput, imageHeight, heightInput)
293        }
294
295        if (PDF_COMPRESS_PATH_ARRAY.includes(pageUrl)) {
296            displayElement.innerText = `File size: ${getUploadedImageFileSize(uploadedImage)}`
297        }
298    }
299}
300
301const displayPaidActionPopup = () => {
302    const paidOptionPopup = document.getElementById('paid-option-popup-wrapper-id')
303    const paidOptionPopupBg = document.getElementById('popup-paid-option-dark-bgc-id')
304    showElement(paidOptionPopup)
305    showElement(paidOptionPopupBg)
306}
307
308// image crop
309// const uploadImageToCrop = (e, baseStringArray) => {
310//     const reader = new FileReader();
311//     if (e.target.files.length) {
312//         uploadAndShowImage(reader)
313//         reader.readAsDataURL(e.target.files[0]);
314//         addFiles(e.target.files)
315//         convertUploadedImageToBytes(e.target.files, baseStringArray)
316//     }
317// }
318
319const cropImageAnimation = (imageContainer, loadingBtn, submitBtn, selectImageBtn, cropperContainer, animationContainer, uploadedImages, animationTxtEl, animationTxt, progressBarEl, progressPercentEl) => {
320    hideElement(imageContainer)
321    hideElement(selectImageBtn)
322    hideElement(cropperContainer)
323    showElement(animationContainer)
324    showResizeProgressBarAnimation(180, uploadedImages, animationTxtEl, animationTxt, progressBarEl, progressPercentEl)
325    showFlexElement(loadingBtn)
326    hideElement(submitBtn)
327}
328
329const scrollToTopOfPage = () => {
330    window.scrollTo({
331        top: 0,
332        behavior: "smooth"
333    });
334}
335
336// After initial images optimization, when success screen is displayed
337// Optimize uploaded images again with new params
338const optimizeImageWithNewParams = () => {
339    removeSuccessColorClassFromDragAndDrop(dropArea)
340    hideElement(uploadImageAnimationContainer)
341    hideElement(resizedImageContainer)
342    resetProgressBar()
343    submitPOSTRequest()
344}
345
346const unsetUploadedImageTemplateDisplay = () => {
347    const uploadedImageTemplates = document.getElementsByClassName('uploaded-image-template')
348    Object.values(uploadedImageTemplates).forEach(template => {
349        addClassToElement(template, 'unset')
350    })
351}
352// display success screen
353const displaySuccessScreen = (dropArea) => {
354    const instagramToolPaths = ['/resize-image-for-instagram', '/whole-image-for-instagram']
355    hideElement(loadingBtn)
356    hideElement(inputContainer)
357    hideElement(uploadImageAnimationContainer)
358    hideElement(fileInputBtn)
359    hideElement(dragAndDropSpan)
360    hideElement(trustTxtWrapper)
361    hideElement(uploadedImageContainer)
362    showElement(submitBtn)
363
364    if (pageUrl !== '/image-rotate') {
365        showElement(inputContainer)
366    }
367
368    showElement(resizedImageContainer)
369    addSuccessColorClassToDragAndDrop(dropArea)
370
371    if (instagramToolPaths.includes(pageUrl)) {
372        unsetUploadedImageTemplateDisplay()
373    }
374
375    if (getMobileOperatingSystem() === iOS) {
376        showElement(resizeWithAppPopup)
377    }
378}
379
380const resetSuccessScreen = () => {
381    removeSuccessColorClassFromDragAndDrop(dropArea)
382    hideElement(uploadImageAnimationContainer)
383    hideElement(resizedImageContainer)
384    resetProgressBar()
385}
386
387// update value of download merged images in ZIP
388const updateDownloadButton = (mergedFilURL, mergedFileName) => {
389    const downloadBtn = buttonsPanel.querySelector('.btn-merged-download');
390    if (downloadBtn) {
391        downloadBtn.setAttribute('href', mergedFilURL);
392        downloadBtn.setAttribute('download', mergedFileName);
393        downloadBtn.innerText = `Download Images in ${mergedFileName.split('.')[1].toUpperCase()}`
394    }
395}
396// display buttons to: perform optimization with new images / same uploaded images but new parameters /
397// button to download images in ZIP file
398// if buttons already exist - update their value or display them for the first time
399const displayOrUpdateButtonsPanelElements = (mergedFilURL, mergedFileName) => {
400    if (!buttonsPanel.querySelector('.btn-new-action')) {
401        displayNewActionButtons(mergedFilURL, mergedFileName)
402    } else {
403        updateDownloadButton(mergedFilURL, mergedFileName);
404    }
405}
406const displayNewActionButtons = (mergedFilURL, mergedFileName) => {
407    if (!buttonsPanel.querySelector('.btn-new-action')) {
408        appendNewActionButton(buttonsPanel, NEW_ACTION_BTN_TXT);
409    }
410    if (!buttonsPanel.querySelector('.btn-merged-download')) {
411        appendDownloadMergedFilesButton(uploadedImages, buttonsPanel, mergedFilURL, mergedFileName);
412    }
413}
414
415
416const handleResponse = (response) => {
417    setTimeout(displaySuccessScreen, 4000, dropArea)
418    showResizedImages(resizedImageContainer, response)
419
420    displayOrUpdateButtonsPanelElements(response.mergedFileUrl, response.mergedFileName)
421
422    // if (response.surveyDisplay && socialMediaSurveyWrapper) {
423    //     setTimeout(() => showElement(socialMediaSurveyWrapper), 4000)
424    // }
425}
426
427const onSuccessResponse = (response) => {
428    if (!response) return;
429
430    try {
431        handleResponse(response)
432    } catch (error) {
433        console.error("Response Error:", error);
434    }
435}
436
437
438const getGreatestCommonDivisor = (a, b) => {
439    if (a === 0 || b === 0) return Math.max(a, b);
440
441    a = Math.abs(a);
442    b = Math.abs(b);
443
444    while (b !== 0) {
445        let t = b;
446        b = a % b;
447        a = t;
448    }
449    return a;
450}
451
452const calculateAspectRatioBasedOnGCD = (width, height) => {
453    const divisor = getGreatestCommonDivisor(width, height);
454    const firstDivider = width / divisor
455    const secondDivider = height / divisor
456    return {first: firstDivider, second: secondDivider}
457}
458
459const areAspectRatiosEqual = (ratio1, ratio2, tolerance = 0.001) => {
460    const decimal1 = ratio1.first / ratio1.second;
461    const decimal2 = ratio2.first / ratio2.second;
462    return Math.abs(decimal1 - decimal2) < tolerance;
463}
464
465const isTool = (path) => {
466    return pageUrl === path
467}
468
469// upload & image action animation (progress bar)
470const showUploadedImageContainer = () => {
471    if (isTool(EXIF_VIEWER_PATH) || isTool(EXIF_REMOVER_PATH) || isTool(IMAGE_DETAILS_PATH)) {
472        showFlexElement(uploadedImageContainer)
473    } else {
474        showElement(uploadedImageContainer)
475    }
476}
477
478const hideUploadImageAnimation = () => {
479    hideElement(uploadImageAnimationContainer)
480    resetProgressBar();
481}
482
483// uploading image animation progress bar (compressor)
484const showUploadProgressBarAnimation = (delay, imagesArray, txtElement, txt, progressBar, progressBarPercent, hideAnimation, showImages) => {
485    removeSuccessColorClassFromDragAndDrop(dropArea)
486    let progress = 0;
487    let id = setInterval(loadingAnimation, delay);
488
489    function loadingAnimation() {
490        IS_IMAGE_UPLOADED = false
491        if (imagesArray.length > 1) {
492            txtElement.innerText = txt
493        } else {
494            txtElement.innerText = txt
495        }
496        if (progress >= 60) {
497            progressBarPercent.style.color = '#fff';
498        }
499
500        if (progress >= 100) {
501            clearInterval(id);
502            setTimeout(hideAnimation, 1000)
503            setTimeout(showImages, 1000);
504            IS_IMAGE_UPLOADED = true
505        } else {
506            progress += 5;
507            progressBar.style.width = progress + '%';
508            progressBarPercent.innerText = progress + '%';
509        }
510    }
511}
512
513// resize animation progress bar (compressor)
514const showResizeProgressBarAnimation = (delay, imagesArray, txtElement, txt, progressBar, progressBarPercent) => {
515    let progress = 0;
516    let id = setInterval(loadingAnimation, delay);
517
518    function loadingAnimation() {
519        if (imagesArray.length > 1) {
520            txtElement.innerText = txt
521        } else {
522            txtElement.innerText = txt
523        }
524        if (progress >= 70) {
525            progressBarPercent.style.color = '#fff';
526        }
527
528        if (progress >= 95) {
529            clearInterval(id);
530        } else {
531            progress += 5;
532            progressBar.style.width = progress + '%';
533            progressBarPercent.innerText = progress + '%';
534        }
535    }
536}
537
538
539const uploadImageAnimation = (text) => {
540    if (resizerJpUrls.includes(pageUrl)) {
541        text = '画像をアップロード中...'
542    }
543    showElement(uploadImageAnimationContainer)
544
545    showUploadProgressBarAnimation(
546        100,
547        uploadedImages,
548        uploadImageAnimationTxt,
549        text,
550        progressBar,
551        progressBarPercent,
552        hideUploadImageAnimation,
553        showUploadedImageContainer
554    )
555    showUploadProgressBarAnimation(
556        100,
557        uploadedImages,
558        uploadImageAnimationTxt,
559        text,
560        progressBar,
561        progressBarPercent,
562        hideUploadImageAnimation,
563        showUploadedImageContainer
564    )
565    hideElement(inputContainer)
566    hideElement(uploadedImageContainer)
567}
568
569const returnAppropriateAnimationTime = () => {
570    let _2_5_MB = 2621440
571    let _5_MB = 5242880
572    let _10_MB = 10485760
573    let _20_MB = 20971520
574    // get number & file size of uploaded images
575    let uploadedImagesNumber = uploadedImages.length
576    // get sum of uploaded files in bytes
577    let fileSizeSumInBytes = uploadedImages.reduce((total, image) => total += image.size, 0)
578
579    // default animation time in milliseconds
580    let defaultAnimationTime = 200
581    let animationTime = defaultAnimationTime
582
583    // increase animation time based on file size sum & number of uploaded images
584    if (fileSizeSumInBytes > _2_5_MB || uploadedImagesNumber >= 3) {
585        animationTime = defaultAnimationTime * 2
586    }
587    if (fileSizeSumInBytes > _5_MB || uploadedImagesNumber >= 3) {
588        animationTime = defaultAnimationTime * 4
589    }
590    if (fileSizeSumInBytes > _10_MB || uploadedImagesNumber >= 3) {
591        animationTime = defaultAnimationTime * 6
592    }
593    if (fileSizeSumInBytes > _20_MB || uploadedImagesNumber >= 3) {
594        animationTime = defaultAnimationTime * 8
595    }
596    return animationTime
597}
598
599const imageActionAnimation = () => {
600    if (resizerJpUrls.includes(pageUrl)) {
601        ACTION_TEXT = '画像をリサイズ中...'
602    }
603    showElement(uploadImageAnimationContainer)
604    showResizeProgressBarAnimation(
605        returnAppropriateAnimationTime(),
606        uploadedImages,
607        uploadImageAnimationTxt,
608        ACTION_TEXT,
609        progressBar,
610        progressBarPercent
611    )
612    hideElement(inputContainer)
613    hideElement(uploadedImageContainer)
614    hideElement(submitBtn)
615    showElement(loadingBtn)
616}
617
618// aspect ratio related code (image resizer to sm & whole image for instagram)
619
620const calculateWidthAspectRatio = (width, height, newParam) => {
621    let quotient = height / width
622    return Math.round(quotient * newParam)
623}
624
625const calculateHeightAspectRatio = (width, height, newParam) => {
626    let quotient = width / height
627    return Math.round(quotient * newParam)
628}
629
630const updateInputWithNewWidthBasedOnAspectRatio = (updatedInput, listenerInput, aspectRatioCheckbox, width, height) => {
631    if (isCheckboxChecked(aspectRatioCheckbox)) {
632        updatedInput.value = calculateWidthAspectRatio(width, height, Math.round(listenerInput.value))
633    }
634}
635
636const updateInputWithNewHeightBasedOnAspectRatio = (updatedInput, listenerInput, aspectRatioCheckbox, width, height) => {
637    if (isCheckboxChecked(aspectRatioCheckbox)) {
638        updatedInput.value = calculateHeightAspectRatio(width, height, Math.round(listenerInput.value))
639    }
640}
641const resizeAndDisplayImagesInModal = (file) => {
642    const imgStretched = document.getElementById('stretched-img-id')
643    const imgAutoCrop = document.getElementById('auto-crop-img-id')
644    const imgManualCrop = document.getElementById('manual-crop-img-id')
645    const imgFitSize = document.getElementById('fit-size-img-id')
646    const blurredBgc = document.getElementById('blurred-img-bgc-id')
647
648    if (file) {
649        if (backgroundColor === DEFAULT_BACKGROUND) {
650            let blurredUrl = URL.createObjectURL(file)
651            blurredBgc.style.backgroundImage = `url(${blurredUrl})`
652
653            resizeImageInCss(imgStretched, file)
654            resizeImageInCss(imgAutoCrop, file)
655            resizeImageInCss(imgManualCrop, file)
656            resizeImageInCss(imgFitSize, file)
657        } else {
658            resizeImageInCss(imgStretched, file)
659            resizeImageInCss(imgAutoCrop, file)
660            resizeImageInCss(imgManualCrop, file)
661            resizeImageInCss(imgFitSize, file)
662            blurredBgc.style.backgroundImage = ``
663        }
664    }
665}
666
667const displayResizeModal = () => {
668    if (uploadedImages.length > 1) {
669        const manualBox = document.getElementById('manual-option-box-id')
670        if (manualBox) {
671            hideElement(manualBox)
672        }
673    }
674    showElement(aspectRatioPopup)
675    if (dpiSelectWrapper) {
676        hideElement(dpiSelectWrapper)
677    }
678    if (imageCropWindow) {
679        hideElement(imageCropWindow)
680    }
681}
682
683const setCropperFrameAspectRatio = () => {
684    if (width === height) {
685        cropperFrameAspectRatio = SQUARE_ASPECT_RATIO
686    } else {
687        cropperFrameAspectRatio = calculateAspectRatioBasedOnGCD(width, height)
688    }
689    return cropperFrameAspectRatio
690}
691
692const isAspectRatioCompatible = () => {
693    if (imageWidth === undefined || imageHeight === undefined) {
694        imageWidth = 360
695        imageHeight = 640
696    }
697    if (width === imageWidth && height === imageHeight) {
698        if (newWidthInPx && newHeightInPx) {
699            width = newWidthInPx
700            height = newHeightInPx
701        }
702    }
703
704    let imgRatio = calculateAspectRatioBasedOnGCD(Number(imageWidth), Number(imageHeight))
705    let resolutionRatio = calculateAspectRatioBasedOnGCD(Number(width), Number(height))
706    if (!areAspectRatiosEqual(imgRatio, resolutionRatio)) {
707        setCropperFrameAspectRatio()
708        displayResizeModal()
709        return false
710    }
711    return true
712}
713
714const updateAndDisplayAspectRatioModal = () => {
715    updateTxtContentOfAspectRatioPopup()
716    setCropperFrameAspectRatio()
717    displayResizeModal()
718}
719
720// image crop in Aspect ratio popup
721const hideCropUploadImageAnimation = () => {
722    hideElement(cropUploadImageAnimationContainer)
723    resetProgressBar();
724}
725
726const uploadAndShowImage = () => {
727    let withRatio;
728    let heightRatio;
729    let file = uploadedImages[0]
730    let imgSource = URL.createObjectURL(file)
731
732    if (isUploadedFileHEICFormat(file)) {
733        heic2any({blob: file, toType: "image/png", quality: 1}).then((file) => {
734            imgSource = URL.createObjectURL(file)
735            const uploadedImage = uploadedImageToCropComponent(cropperImageContainer, URL.createObjectURL(file))
736            displayImageInCrop(uploadedImage, withRatio, heightRatio)
737        })
738    }
739
740    const uploadedImage = uploadedImageToCropComponent(cropperImageContainer, URL.createObjectURL(file))
741    let value = setCropperFrameAspectRatio()
742
743    if (value === SQUARE_ASPECT_RATIO) {
744        withRatio = 1
745        heightRatio = 1
746    } else {
747        withRatio = value.first
748        heightRatio = value.second
749    }
750    displayImageInCrop(uploadedImage, withRatio, heightRatio)
751}
752
753const displayImageInCrop = (uploadedImage, withRatio, heightRatio) => {
754    cropper = new Cropper(uploadedImage, {
755        aspectRatio: withRatio / heightRatio,
756        crop: (event) => {
757            if (areInputsAdjustable) {
758                let maxHeight = 600
759                cropperDetailsInput.value = JSON.stringify(event.detail)
760                let containerData = cropper.getContainerData()
761                if (containerData.height > maxHeight) {
762                    popupWrapper.style.overflow = 'scroll'
763                }
764            }
765        }
766    })
767}
768
769const submitCropPOSTRequest = () => {
770    if (uploadedImages.length === 0) {
771        showErrorMsg()
772    } else {
773        cropPOSTRequest()
774    }
775}
776
777const displayCropSuccessScreen = () => {
778    hideElement(document.getElementById('crop-upload-image-animation-container-id'))
779    hideElement(selectImageBtn)
780    hideElement(document.getElementById('btn-loading-crop-id'))
781    showElement(cropperImageContainer)
782    addSuccessColorClassToDragAndDrop(document.getElementById('drop-area-crop-id'))
783}
784
785const handleCropResponse = (response) => {
786    setTimeout(displayCropSuccessScreen, 4000, dropArea)
787    showResizedImages(cropperImageContainer, response)
788    displayOrUpdateButtonsPanelElements(response.mergedFileUrl, response.mergedFileName)
789}
790
791const onCropSuccessResponse = (response) => {
792    if (!response) return;
793    try {
794        handleCropResponse(response)
795    } catch (error) {
796        console.error("Error:", error);
797    }
798}
799
800
801// Loop through passed radio inputs and check one with appropriate passed value
802const checkRadioInputWithPassedValue = (radioInputs, inputValue) => {
803    let found = false;
804    for (let i = 0; i < radioInputs.length; i++) {
805        if (radioInputs[i].value === inputValue) {
806            radioInputs[i].checked = true;
807            found = true;
808            break;
809        }
810    }
811}
812
813// check passed select option with right value and inner text description
814checkSelectOptionWithPassedValue = (options, value, desc) => {
815    let foundOption = null
816    options.forEach(option => {
817        if (option.value === value && option.innerHTML === desc) {
818            foundOption = option
819        }
820    })
821
822    if (foundOption) {
823        foundOption.selected = true
824    }
825}
826// return file extension in lower case
827const getFileExtension = (file_name) => file_name.split('.').pop().toLowerCase()
828
829const resizeImageInCss = (imgEl, file) => {
830    let size = (width === height && width > 300) || width > height
831        ? {width: 300, height: width > height ? 200 : 300}
832        : {width: 250, height: 250};
833
834    imgEl.style.width = `${size.width}px`;
835    imgEl.style.height = `${size.height}px`;
836    displayHEICImageThumbnail(file, imgEl)
837    imgEl.src = URL.createObjectURL(file);
838}
839
840// update select options based on passed obj with values
841// selectOptionsObj = [{'key': value}]
842const excludeOptionsFromSelectAndUpdate = (select, selectOptionsObj, extensionToExclude) => {
843    selectOptionsObj.unshift({value: NO_CHANGE, txt: `No change (${extensionToExclude})`})
844    select.innerHTML = '';
845    selectOptionsObj.forEach(format => {
846        if (format.value.toLowerCase() !== extensionToExclude) {
847            const option = document.createElement('option')
848            option.value = format.value;
849            option.innerText = format.txt.toUpperCase()
850            appendToElement(select, option)
851        }
852    })
853}
854// Unit calculators (cm,mm,inch,px)
855const calculatePixelsFromCentimeters = (cm, dpi) => {
856    let onCmInPx = (dpi / ONE_INCH_IN_CM).toFixed(2)
857    let countCm = Math.floor(cm)
858    return (countCm * onCmInPx).toFixed(2)
859}
860
861const calculatePixelsFromMillimeters = (mm, dpi) => {
862    let mmToCm = (mm / 10).toFixed(2)
863    return calculatePixelsFromCentimeters(mmToCm, dpi)
864}
865
866const calculatePixelsFromInch = (inch, dpi) => {
867    let countCm = (inch * ONE_INCH_IN_CM).toFixed(2)
868    return calculatePixelsFromCentimeters(countCm, dpi)
869}
870
871const calculateMillimetersFromPixels = (px, dpi) => {
872    let onePx = ((ONE_INCH_IN_CM * 10) / dpi)
873    return (px * onePx).toFixed(2)
874}
875
876const calculateCentimetersFromPixels = (px, dpi) => {
877    let onePx = (ONE_INCH_IN_CM / dpi)
878    return (px * onePx).toFixed(2)
879}
880
881const calculateInchFromPixels = (px, dpi) => {
882    let onePx = (1.0 / dpi)
883    return (px * onePx).toFixed(2)
884}
885
886// shared function for resize for printing & resize to units
887const calculateNewResolutionParameter = (width, height, newParam, unit) => {
888    let quotient = height / width
889    let newSizeParamInPixels = calculateNewSizeInPixels(unit, newParam)
890    let result = (quotient * newParam).toFixed(2)
891    updateNewPictureResolutionInPx(Math.round(newSizeParamInPixels), Math.round(quotient * newSizeParamInPixels))
892    return result
893}
894
895const updateNewPictureResolutionInPx = (width, height) => {
896    newWidthInPx = Math.round(width)
897    newHeightInPx = Math.round(height)
898}
899
900const calculateNewResolutionInPxWithoutAspectRatio = () => {
901    let newWidth = calculateNewSizeInPixels(unitValue, widthResizerInput.value)
902    let newHeight = calculateNewSizeInPixels(unitValue, heightResizerInput.value)
903    return {width: newWidth, height: newHeight}
904}
905
906const convertPxToUnits = (unit, width, height) => {
907    let convertedWidth;
908    let convertedHeight;
909
910    if (unit === CM_UNIT) {
911        convertedWidth = calculateCentimetersFromPixels(width, dpiValue)
912        convertedHeight = calculateCentimetersFromPixels(height, dpiValue)
913    }
914    if (unit === MM_UNIT) {
915        convertedWidth = calculateMillimetersFromPixels(width, dpiValue)
916        convertedHeight = calculateMillimetersFromPixels(height, dpiValue)
917    }
918    if (unit === INCH_UNIT) {
919        convertedWidth = calculateInchFromPixels(width, dpiValue)
920        convertedHeight = calculateInchFromPixels(height, dpiValue)
921    }
922    if (unit === PX_UNIT) {
923        convertedWidth = width
924        convertedHeight = height
925    }
926    return {width: convertedWidth, height: convertedHeight}
927}
928
929const displayConvertedImageSize = (width, height) => {
930    const convertedSize = convertPxToUnits(unitValue, width, height)
931    updateInputsValue(convertedSize.width, widthResizerInput, convertedSize.height, heightResizerInput)
932}
933
934const updateImageSizeByUnit = (unit) => {
935    const convertedSize = convertPxToUnits(unit, width, height)
936    updateInputsValue(convertedSize.width, widthResizerInput, convertedSize.height, heightResizerInput)
937
938}
939
940const calculateNewSizeInPixels = (unit, size) => {
941    let convertedSizeFromUnitValue;
942    if (unit === CM_UNIT) {
943        convertedSizeFromUnitValue = calculatePixelsFromCentimeters(size, dpiValue)
944    }
945    if (unit === MM_UNIT) {
946        convertedSizeFromUnitValue = calculatePixelsFromMillimeters(size, dpiValue)
947    }
948    if (unit === INCH_UNIT) {
949        convertedSizeFromUnitValue = calculatePixelsFromInch(size, dpiValue)
950    }
951    if (unit === PX_UNIT) {
952        convertedSizeFromUnitValue = calculateInchFromPixels(size, dpiValue) * dpiValue
953    }
954    return convertedSizeFromUnitValue
955}
956
957// returns chosen value from select element
958const getSelectedValue = (select) => select.options[select.selectedIndex].value
959// returns text of chosen option from select element
960const getSelectedOptionText = (select) => select.options[select.selectedIndex].text
961
962const geValueOfSelectedUnits = (selectEl) => {
963    unitValue = selectEl.value
964    updateUnitValues(unitValue)
965    updateImageSizeByUnit(unitValue)
966}
967
968const updateUnitValues = (value) => {
969    unitWidthSelect.value = value
970    unitHeightSelect.value = value
971}
972
973// Limit number of characters in input type number
974const limitNumberInputLength = (input) => {
975    if (input.value.length > MAX_LENGTH) {
976        input.value = input.value.slice(0, MAX_LENGTH)
977    }
978}
979
980const showInputErrorMessage = (inputElement, errorMsgElement) => {
981    errorMsgElement.innerText = setTxtInputErrorMessage()
982    inputElement.classList.add('error-msg')
983}
984
985const clearInputErrorMessage = (inputElement, errorMsgElement) => {
986    errorMsgElement.innerText = setTxtInputErrorMessage()
987    inputElement.classList.remove('error-msg')
988}
989
990const isTxtInputValidated = (inputElement, errorMsgElement, maxCharNum) => {
991    if (inputElement.value === '' || inputElement.value.length > maxCharNum) {
992        showInputErrorMessage(inputElement, errorMsgElement)
993        return false
994    } else {
995        clearInputErrorMessage(inputElement, errorMsgElement)
996        return true
997    }
998}
999
1000const getAndDisplayUploadedImagesResolution = (uploadedImage, convertFromFormat, imageWidth, imageHeight) => {
1001    const reader = new FileReader();
1002    reader.readAsDataURL(uploadedImage)
1003    reader.onload = (e) => {
1004        const image = new Image();
1005        image.src = e.target.result;
1006        image.onload = () => {
1007            imageWidth = image.width
1008            imageHeight = image.height
1009            let imageResolution = `${imageWidth} x ${imageHeight}`
1010            uploadedResolutions.push(imageResolution)
1011            imageSize = getUploadedImageFileSize(uploadedImage)
1012            convertFromFormat.innerHTML = `<strong>${imageResolution} px (${imageSize})</strong>`
1013
1014            width = imageWidth
1015            height = imageHeight
1016            displayConvertedImageSize(imageWidth, imageHeight)
1017            convertPxToUnits(unitValue, imageWidth, imageHeight)
1018        }
1019    }
1020}
1021
1022// create URL for compare images page
1023const createComparePageURL = (compareURLObj) => {
1024    return `/compare-images?orImg=${encodeURIComponent(compareURLObj.originImg)}&orName=${compareURLObj.originName}
1025    &orRes=${compareURLObj.originResolution}
1026    &orS=${compareURLObj.originSize}&optImg=${encodeURIComponent(compareURLObj.optimizedImg)}&optName=
1027    ${compareURLObj.optimizedName}&optRes=${compareURLObj.optimizedResolution}
1028    &optS=${compareURLObj.optimizedSize}`
1029}
1030
1031const createPreviewPdfPageURL = (demoPdfUrl) => {
1032    return `/preview-pdf?demoPdfUrl=${encodeURIComponent(demoPdfUrl)}`
1033}
1034
1035// create Google Analytics event
1036const createGtagEvent = (eventName, eventParametersObj) => {
1037    gtag('event', eventName, eventParametersObj)
1038}
1039
1040const toggleFaqActiveClass = (clickedElement) => {
1041    const wrapper = clickedElement.closest('.question-and-answer-wrapper')
1042    const showIcon = wrapper.querySelector('img[alt="display answer"]')
1043    const hideIcon = wrapper.querySelector('img[alt="hide answer"]')
1044    const answer = wrapper.querySelector('.single-answer');
1045    const question = wrapper.querySelector('.single-question');
1046
1047    if (clickedElement === showIcon || (clickedElement === question && !answer.classList.contains('active'))) {
1048        // Show the answer
1049        showIcon.classList.remove('active');
1050        hideIcon.classList.add('active');
1051        answer.classList.add('active');
1052    } else if (clickedElement === hideIcon) {
1053        // Hide the answer when hideIcon is clicked
1054        showIcon.classList.add('active');
1055        hideIcon.classList.remove('active');
1056        answer.classList.remove('active');
1057    } else if (clickedElement === question && answer.classList.contains('active')) {
1058        // Hide the answer when question is clicked and the answer is currently active
1059        showIcon.classList.add('active');
1060        hideIcon.classList.remove('active');
1061        answer.classList.remove('active');
1062    }
1063}
1064
1065const toDecimal = (coord, ref) => {
1066    if (!coord || coord.length !== 3) {
1067        console.warn("Wrong coordinates EXIF:", coord);
1068        return NaN;
1069    }
1070
1071    const d = coord[0][0] / coord[0][1];
1072    const m = coord[1][0] / coord[1][1];
1073    const s = coord[2][0] / coord[2][1];
1074
1075    let decimal = d + m / 60 + s / 3600;
1076    return (ref === "S" || ref === "W") ? -decimal : decimal;
1077}
1078
1079const getDirection = (ref) => {
1080    if (ref.includes("North")) return "N";
1081    if (ref.includes("South")) return "S";
1082    if (ref.includes("East")) return "E";
1083    if (ref.includes("West")) return "W";
1084    return null;
1085}
1086
1087const getCoordinates = (tags) => {
1088    const latTag = tags["GPSLatitude"];
1089    const lonTag = tags["GPSLongitude"];
1090    let lat
1091    let lon
1092    if (!latTag || !lonTag) {
1093        console.warn("There's no GPS data in EXIF");
1094        lat = DEFAULT_LATITUDE
1095        lon = DEFAULT_LONGITUDE
1096    } else {
1097        const latRef = tags["GPSLatitudeRef"] ? getDirection(tags["GPSLatitudeRef"].description) : null;
1098        const lonRef = tags["GPSLongitudeRef"] ? getDirection(tags["GPSLongitudeRef"].description) : null;
1099
1100        lat = toDecimal(latTag.value, latRef);
1101        lon = toDecimal(lonTag.value, lonRef);
1102    }
1103
1104    return {'latitude': lat, 'longitude': lon}
1105}
1106
1107const initializeMap = (lat, lon, index) => {
1108    if (!lat || !lon) {
1109        console.warn("There's no GPS data");
1110        return;
1111    }
1112    setTimeout(() => {
1113        const map = L.map('map-' + index).setView([lat, lon], 15);
1114
1115        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
1116            attribution: '&copy; OpenStreetMap contributors'
1117        }).addTo(map);
1118
1119        L.marker([lat, lon]).addTo(map)
1120            .openPopup();
1121
1122        setTimeout(() => {
1123            map.invalidateSize();
1124        }, 1000)
1125
1126    }, 3000);
1127}
1128
1129const createTable = () => {
1130    const table = document.createElement('table');
1131    table.className = 'table table-nowrap table-vertical-border-striped';
1132    return table
1133}
1134
1135const createSectionHeader = (groupName) => {
1136    const sectionHeader = document.createElement('tr');
1137    sectionHeader.innerHTML = `<th colspan="3" class="exif-group-header p-3 text-center"><strong>${groupName}</strong></th>`;
1138    return sectionHeader
1139}
1140
1141const createMapElement = (index) => {
1142    const mapElement = document.createElement('div')
1143    mapElement.id = `map-${index}`
1144    mapElement.className = 'map-container'
1145    return mapElement
1146}
1147
1148const createMapCell = (index) => {
1149    const mapCell = document.createElement('td');
1150    mapCell.setAttribute('colspan', '3');
1151    const map = createMapElement(index)
1152
1153    appendToElement(mapCell, map)
1154    return mapCell
1155}
1156
1157const isLocationData = (coordinatesObj) => {
1158    return coordinatesObj.latitude !== DEFAULT_LATITUDE && coordinatesObj.longitude !== DEFAULT_LONGITUDE
1159}
1160
1161const limitCharNumber = (value) => {
1162    if (value.length >= 120) {
1163        let limitedValue = value.substring(0, 120)
1164        value = `${limitedValue} ...`
1165    }
1166    return value
1167}
1168
1169const displayInfoAlert = (image) => {
1170    const alert = document.createElement('div')
1171    alert.innerHTML = `<span class="alert alert-success text-center mb-0">This image doesn't have any EXIF data.</span>`
1172
1173    appendUploadedImagesEXIFComponent(image, alert);
1174
1175    const exifDataContainer = document.querySelector('.exif-data-container')
1176    exifDataContainer.classList.add('no-data')
1177}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.