@@ -813,6 +813,23 @@ function setupSettings() {
813813 document . getElementById ( 'maxImageWidthNum' ) ,
814814 'maxImageWidthValue' ) ;
815815
816+ // Toggle dependent image controls when processImages changes
817+ var optProcessImages = document . getElementById ( 'optProcessImages' ) ;
818+ var imageSubControls = [
819+ document . getElementById ( 'optGrayscale' ) ,
820+ document . getElementById ( 'optRemoveUnsupported' ) ,
821+ document . getElementById ( 'maxImageWidth' ) ,
822+ document . getElementById ( 'maxImageWidthNum' )
823+ ] ;
824+ function updateImageSubControls ( ) {
825+ var enabled = optProcessImages . checked ;
826+ for ( var i = 0 ; i < imageSubControls . length ; i ++ ) {
827+ imageSubControls [ i ] . disabled = ! enabled ;
828+ }
829+ }
830+ optProcessImages . addEventListener ( 'change' , updateImageSubControls ) ;
831+ updateImageSubControls ( ) ;
832+
816833 // Status bar sliders (render-only, no applySettings needed)
817834 syncInputsRenderOnly ( statusFontSize , statusFontSizeNum , 'statusFontSizeValue' ) ;
818835 syncInputsRenderOnly ( statusEdgeMargin , statusEdgeMarginNum , 'statusEdgeMarginValue' ) ;
@@ -1744,6 +1761,8 @@ async function optimizeEpub(file) {
17441761 var settings = {
17451762 removeCss : document . getElementById ( 'optRemoveCss' ) . checked ,
17461763 stripFonts : document . getElementById ( 'optStripFonts' ) . checked ,
1764+ processImages : document . getElementById ( 'optProcessImages' ) . checked ,
1765+ removeUnsupportedImages : document . getElementById ( 'optRemoveUnsupported' ) . checked ,
17471766 grayscale : document . getElementById ( 'optGrayscale' ) . checked ,
17481767 maxWidth : parseInt ( document . getElementById ( 'maxImageWidth' ) . value ) ,
17491768 injectCss : document . getElementById ( 'optInjectCss' ) . checked
@@ -1785,11 +1804,17 @@ async function optimizeEpub(file) {
17851804 epubZip . file ( path , html ) ;
17861805 }
17871806
1807+ // Remove unsupported image formats (can't reliably process in browser, won't display on device)
1808+ if ( settings . processImages && settings . removeUnsupportedImages && / \. ( s v g | w e b p | t i f f ? ) $ / i. test ( path ) ) {
1809+ epubZip . remove ( path ) ;
1810+ continue ;
1811+ }
1812+
17881813 // Process images
1789- if ( settings . grayscale && / \. ( j p g | j p e g | p n g | g i f ) $ / i. test ( path ) ) {
1814+ if ( settings . processImages && / \. ( j p g | j p e g | p n g | b m p | g i f ) $ / i. test ( path ) ) {
17901815 var imgData = await zipFile . async ( 'arraybuffer' ) ;
17911816 var processedImg = await processImage ( imgData , settings . maxWidth , settings . grayscale ) ;
1792- if ( processedImg ) {
1817+ if ( processedImg && processedImg . byteLength < imgData . byteLength ) {
17931818 epubZip . file ( path , processedImg ) ;
17941819 }
17951820 }
@@ -1847,6 +1872,9 @@ async function processImage(imgData, maxWidth, toGrayscale) {
18471872 var blob = new Blob ( [ imgData ] ) ;
18481873 var img = new Image ( ) ;
18491874 img . onload = function ( ) {
1875+ // Skip tiny decorative images
1876+ if ( img . width < 20 || img . height < 20 ) { resolve ( null ) ; return ; }
1877+
18501878 var canvas = document . createElement ( 'canvas' ) ;
18511879 var ctx = canvas . getContext ( '2d' ) ;
18521880
@@ -1859,8 +1887,20 @@ async function processImage(imgData, maxWidth, toGrayscale) {
18591887 width = maxWidth ;
18601888 }
18611889
1890+ // Constrain height to device decode limit
1891+ var maxHeight = 3072 ;
1892+ if ( height > maxHeight ) {
1893+ width = Math . round ( width * ( maxHeight / height ) ) ;
1894+ height = maxHeight ;
1895+ }
1896+
18621897 canvas . width = width ;
18631898 canvas . height = height ;
1899+
1900+ // Flatten alpha to white background (matches CLI behavior)
1901+ ctx . fillStyle = '#ffffff' ;
1902+ ctx . fillRect ( 0 , 0 , width , height ) ;
1903+
18641904 ctx . drawImage ( img , 0 , 0 , width , height ) ;
18651905
18661906 // Convert to grayscale if enabled
0 commit comments