3434 border-radius : 3px ;
3535 padding : 6px ;
3636 }
37+
38+ # inputSection {
39+ border : 2px dashed var (--main-border-color );
40+ color : var (--main-border-color );
41+ padding : 1em ;
42+ }
43+
44+ # inputSection : hover {
45+ border : 2px dashed var (--main-draw-color );
46+ color : var (--main-draw-color );
47+ }
3748
3849 @keyframes shake {
3950 0% { transform : translateX (0 ); }
93104 }
94105 }
95106
107+ # convertBtn {
108+ margin-top : 0.5em ;
109+ }
110+
111+ # advancedInput {
112+ * {
113+ margin-bottom : 0.5em ;
114+ }
115+ }
116+
117+ # actionSection {
118+ display : flex;
119+ flex-direction : column;
120+ align-items : center;
121+ }
122+
96123 </ style >
97124 </ head >
98125 < body >
@@ -103,78 +130,85 @@ <h1>Offline Image Converter</h1>
103130 < li > Everything stays on your device, private and secure</ li >
104131 </ ul >
105132
106- < p > Paste an image using CTLR-V or choose a image from:</ p >
107-
108- < input type ="file " id ="fileInput " accept ="image/bmp,image/x-bmp,image/vnd-ms.dds,image/x-direct-draw-surface,image/x-exr,image/ff,image/gif,image/vnd.radiance,image/x-icon,image/jpeg,image/png,image/x-portable-bitmap,image/x-portable-graymap,image/x-portable-pixmap,image/x-portable-anymap,image/qoi,image/x-tga,image/x-targa,image/tiff,image/tiff-fx,image/webp " />
133+ < hr style ="width: 35em; ">
109134
110- < div id ="filePasteSection " style ="display: none; ">
111- < p > Pasted file</ p >
112- < button onclick ="resetPaste() "> Reset</ button >
113- </ div >
135+ < p > Paste an image using CTLR-V or choose a image from:</ p >
114136
115- < div id ="outputSection ">
116- < label for ="outputType "> Output format:</ label >
117- < select id ="outputType ">
118-
119- < optgroup label ="Most used: ">
120- < option value ="image/jpeg "> JPEG</ option >
121- < option value ="image/png "> PNG</ option >
122- </ optgroup >
123-
124- < optgroup label ="More: ">
125- < option value ="image/avif "> AVIF</ option >
126- < option value ="image/bmp "> BMP</ option >
127- < option value ="image/x-exr "> OpenEXR</ option >
128- < option value ="image/ff "> Farbfeld</ option >
129- < option value ="image/gif "> GIF</ option >
130- < option value ="image/vnd.radiance "> HDR</ option >
131- < option value ="image/x-icon "> ICO</ option >
132- < option value ="image/x-portable-bitmap "> PNM</ option >
133- < option value ="image/qoi "> QOI</ option >
134- < option value ="image/x-tga "> TGA</ option >
135- < option value ="image/tiff "> TIFF</ option >
136- < option value ="image/webp "> WebP</ option >
137- </ optgroup >
138- </ select >
139- </ div >
137+ < div id ="inputSection ">
138+ < input type ="file " id ="fileInput " onchange ="fileInputChanged(event) " accept ="image/bmp,image/x-bmp,image/vnd-ms.dds,image/x-direct-draw-surface,image/x-exr,image/ff,image/gif,image/vnd.radiance,image/x-icon,image/jpeg,image/png,image/x-portable-bitmap,image/x-portable-graymap,image/x-portable-pixmap,image/x-portable-anymap,image/qoi,image/x-tga,image/x-targa,image/tiff,image/tiff-fx,image/webp " />
140139
141- < details id ="advancedInput ">
142- < summary >
143- Advanced options (click to expand)
144- </ summary >
145- < div >
146- < label for ="quality "> Quality (0-100, where 100 is best quality):</ label >
147- < input type ="number " id ="quality " name ="quality " min ="0 " max ="100 " />
140+ < div id ="filePasteSection " style ="display: none; ">
141+ < p > Pasted file</ p >
142+ < button onclick ="resetPaste() "> Reset</ button >
148143 </ div >
144+ </ div >
149145
150- < div >
151- < label for ="compression "> Compression method:</ label >
152- < select id ="compression " name ="compression ">
153- < option value ="fast "> Fast</ option >
154- < option value ="best "> Best</ option >
155- < option value ="default "> Default</ option >
146+ < div id ="actionSection " style ="display: none; ">
147+ < div id ="outputSection ">
148+ < label for ="outputType "> Output format:</ label >
149+ < select id ="outputType " onchange ="selectChanged(event) ">
150+
151+ < optgroup label ="Most used: ">
152+ < option value ="image/jpeg "> JPEG</ option >
153+ < option value ="image/png "> PNG</ option >
154+ </ optgroup >
155+
156+ < optgroup label ="More: ">
157+ < option value ="image/avif "> AVIF</ option >
158+ < option value ="image/bmp "> BMP</ option >
159+ < option value ="image/x-exr "> OpenEXR</ option >
160+ < option value ="image/ff "> Farbfeld</ option >
161+ < option value ="image/gif "> GIF</ option >
162+ < option value ="image/vnd.radiance "> HDR</ option >
163+ < option value ="image/x-icon "> ICO</ option >
164+ < option value ="image/x-portable-bitmap "> PNM</ option >
165+ < option value ="image/qoi "> QOI</ option >
166+ < option value ="image/x-tga "> TGA</ option >
167+ < option value ="image/tiff "> TIFF</ option >
168+ < option value ="image/webp "> WebP</ option >
169+ </ optgroup >
156170 </ select >
157171 </ div >
158172
159- < div >
160- < label for ="filter "> Filter type:</ label >
161- < select id ="filter " name ="filter ">
162- < option value ="adaptive "> Adaptive</ option >
163- < option value ="no_filter "> No filter</ option >
164- < option value ="sub "> Sub</ option >
165- < option value ="up "> Up</ option >
166- < option value ="avg "> Avg</ option >
167- < option value ="paeth "> Paeth</ option >
168- </ select >
173+ < details id ="advancedInput ">
174+ < summary >
175+ Advanced options (click to expand)
176+ </ summary >
177+ < div id ="advancedInput-image/png " style ="display: none; ">
178+ < label for ="quality "> Quality (0 worst, 100 best):</ label >
179+ < input type ="number " id ="quality " name ="quality " min ="0 " max ="100 " value ="100 " />
180+ </ div >
181+
182+ < div id ="advancedInput-image/jpeg ">
183+ < div >
184+ < label for ="compression "> Compression method:</ label >
185+ < select id ="compression " name ="compression ">
186+ < option value ="fast "> Fast</ option >
187+ < option value ="best "> Best</ option >
188+ < option value ="default "> Default</ option >
189+ </ select >
190+ </ div >
191+
192+ < div >
193+ < label for ="filter "> Filter type:</ label >
194+ < select id ="filter " name ="filter ">
195+ < option value ="adaptive "> Adaptive</ option >
196+ < option value ="no_filter "> No filter</ option >
197+ < option value ="sub "> Sub</ option >
198+ < option value ="up "> Up</ option >
199+ < option value ="avg "> Avg</ option >
200+ < option value ="paeth "> Paeth</ option >
201+ </ select >
202+ </ div >
203+ </ div >
204+ </ details >
205+
206+ < button id ="convertBtn " onclick ="convert() "> Convert</ button >
207+
208+ < div id ="loader " style ="display: none; ">
209+ < span class ="loader "> </ span >
210+ < p id ="progress "> Starting...</ p >
169211 </ div >
170-
171- </ details >
172-
173- < button id ="convertBtn " onclick ="convert() "> Convert</ button >
174-
175- < div id ="loader " style ="display: none; ">
176- < span class ="loader "> </ span >
177- < p id ="progress "> Starting...</ p >
178212 </ div >
179213
180214 < p id ="error-message " style ="display: none; ">
@@ -203,6 +237,14 @@ <h1>Offline Image Converter</h1>
203237 < script type ="module ">
204238 const worker = new Worker ( "./worker.js" , { type : "module" } ) ;
205239
240+ const actionSection = document . getElementById ( "actionSection" ) ;
241+ function hideActions ( ) {
242+ actionSection . style . display = 'none' ;
243+ }
244+ function showActions ( ) {
245+ actionSection . style . display = 'flex' ;
246+ }
247+
206248 function downloadImage ( imageData , fileName , outputType ) {
207249 const blob = new Blob ( [ imageData ] , { type : outputType } ) ;
208250 const url = URL . createObjectURL ( blob ) ;
@@ -242,6 +284,7 @@ <h1>Offline Image Converter</h1>
242284 }
243285
244286 if ( type === "done" ) {
287+ hideError ( ) ;
245288 downloadImage ( imageData , fileName , outputType ) ;
246289 } else if ( type === "error" ) {
247290 showError ( message ) ;
@@ -276,6 +319,30 @@ <h1>Offline Image Converter</h1>
276319 return withoutExtension + "." + extension ;
277320 }
278321
322+ const advancedQualityInput = document . getElementById ( "quality" ) ;
323+ const advancedCompressionInput = document . getElementById ( "compression" ) ;
324+ const advancedFilterInput = document . getElementById ( "filter" ) ;
325+ function getOptions ( ) {
326+ const isOpen = advancedInput . open ;
327+ if ( ! isOpen ) {
328+ return { } ;
329+ }
330+
331+ if ( outputTypeElement . value === "image/png" ) {
332+ const qualityInt = parseInt ( advancedQualityInput . value ) ;
333+ if ( ! isNaN ( qualityInt ) ) {
334+ const quality = Math . min ( 100 , Math . max ( 0 , qualityInt ) ) ;
335+ return { quality } ;
336+ }
337+ } else if ( outputTypeElement . value === "image/jpeg" ) {
338+ const compression = advancedCompressionInput . value ;
339+ const filter = advancedFilterInput . value ;
340+ return { compression, filter } ;
341+ }
342+
343+ return { } ;
344+ }
345+
279346 let pasted = false ;
280347
281348 const fileInput = document . getElementById ( 'fileInput' ) ;
@@ -301,13 +368,15 @@ <h1>Offline Image Converter</h1>
301368 showLoader ( ) ;
302369 hideError ( ) ;
303370
371+ const options = getOptions ( outputType ) ;
372+
304373 const fileName = newFileName ( file . name , outputType ) ;
305374 const inputType = file . type ;
306375 const imageDataBuffer = await file . arrayBuffer ( ) ;
307376 const imageData = new Uint8Array ( imageDataBuffer ) ;
308377 const imageTransfer = imageData . buffer ;
309-
310- worker . postMessage ( { imageData, fileName, inputType, outputType } , [ imageTransfer ] ) ;
378+
379+ worker . postMessage ( { imageData, fileName, inputType, outputType, options } , [ imageTransfer ] ) ;
311380 }
312381 window . convert = convert ;
313382
@@ -316,6 +385,8 @@ <h1>Offline Image Converter</h1>
316385 pasted = false ;
317386 filePasteSection . style . display = 'none' ;
318387 fileInput . style . display = 'block' ;
388+ hideActions ( ) ;
389+ hideError ( ) ;
319390 }
320391 window . resetPaste = resetPaste ;
321392
@@ -330,11 +401,55 @@ <h1>Offline Image Converter</h1>
330401 pasted = true ;
331402
332403 fileInput . style . display = 'none' ;
404+ fileInput . value = null ;
333405 filePasteSection . style . display = 'flex' ;
406+ fileInputChanged ( file . type ) ;
407+ showActions ( ) ;
408+ hideError ( ) ;
334409 }
335410 }
336411 } ) ;
337412
413+ const advancedInput = document . getElementById ( "advancedInput" ) ;
414+ function selectChanged ( event ) {
415+ const outputType = event . target . value ;
416+ const advancedInputDivs = document . querySelectorAll ( "#advancedInput > div" ) ;
417+ advancedInputDivs . forEach ( div => {
418+ div . style . display = 'none' ;
419+ } ) ;
420+
421+ let constainsAdvancedOptions = false ;
422+
423+ const specificDiv = document . getElementById ( "advancedInput-" + outputType ) ;
424+ if ( specificDiv ) {
425+ specificDiv . style . display = 'block' ;
426+ constainsAdvancedOptions = true ;
427+ }
428+
429+ advancedInput . style . display = constainsAdvancedOptions ? 'block' : 'none' ;
430+ hideError ( ) ;
431+ }
432+ window . selectChanged = selectChanged ;
433+
434+ function fileInputChangedEvent ( event ) {
435+ const fileType = event . target . files [ 0 ] ?. type ;
436+ fileInputChanged ( fileType ) ;
437+ }
438+ window . fileInputChanged = fileInputChangedEvent ;
439+
440+ function fileInputChanged ( fileType ) {
441+ const outputType = outputTypeElement . value ;
442+ if ( fileType === "image/png" && outputType === "image/png" ) {
443+ outputTypeElement . value = "image/jpeg" ;
444+ selectChanged ( { target : outputTypeElement } ) ;
445+ } else if ( fileType === "image/jpeg" && outputType === "image/jpeg" ) {
446+ outputTypeElement . value = "image/png" ;
447+ selectChanged ( { target : outputTypeElement } ) ;
448+ }
449+ showActions ( ) ;
450+ hideError ( ) ;
451+ }
452+
338453 </ script >
339454 </ body >
340455</ html >
0 commit comments