@@ -619,8 +619,40 @@ class LVGLImage {
619619 // Simple wrapper to call pngquant if available (uses pngquant-bin if installed)
620620 classPngQuant ( ncolors = 256 , dither = true , execPath = '' , forceJS = false , forceBin = false ) {
621621 let pngquantPath = null ;
622+
623+ // Try to find pngquant binary by searching up the directory tree for node_modules
624+ const findPngquantBinary = ( ) => {
625+ let searchDir = __dirname ;
626+ const maxLevels = 20 ;
627+ const binaryName = process . platform === 'win32' ? 'pngquant.exe' : 'pngquant' ;
628+
629+ // First, check if we're in an Electron app and look for unpacked asar path
630+ // This takes priority because executables can't run from inside .asar archive
631+ for ( let i = 0 ; i < maxLevels ; i ++ ) {
632+ // Check for unpacked asar path first
633+ const asarUnpackedPath = path . join ( searchDir , '..' , 'app.asar.unpacked' , 'node_modules' , 'pngquant-bin' , 'vendor' ) ;
634+ const asarPngquantExe = path . join ( asarUnpackedPath , binaryName ) ;
635+ if ( fs . existsSync ( asarPngquantExe ) ) {
636+ return asarPngquantExe ;
637+ }
638+
639+ // Then check regular node_modules, but skip if inside asar archive
640+ const vendorPath = path . join ( searchDir , 'node_modules' , 'pngquant-bin' , 'vendor' ) ;
641+ const pngquantExe = path . join ( vendorPath , binaryName ) ;
642+ if ( fs . existsSync ( pngquantExe ) && ! pngquantExe . includes ( '.asar' ) ) {
643+ return pngquantExe ;
644+ }
645+
646+ const parentDir = path . dirname ( searchDir ) ;
647+ if ( parentDir === searchDir ) break ; // reached filesystem root
648+ searchDir = parentDir ;
649+ }
650+
651+ return null ;
652+ } ;
653+
622654 try {
623- pngquantPath = require ( 'pngquant-bin' ) ;
655+ pngquantPath = findPngquantBinary ( ) ;
624656 } catch ( e ) {
625657 pngquantPath = null ;
626658 }
@@ -720,8 +752,17 @@ class LVGLImage {
720752 }
721753
722754 // resolve binary path if pngquant-bin returned an object
723- let bin = pngquantPath || path . join ( execPath , 'pngquant' ) ;
724- if ( bin && typeof bin === 'object' && bin . default ) bin = bin . default ;
755+ let bin = pngquantPath ;
756+ if ( ! bin && execPath ) bin = path . join ( execPath , process . platform === 'win32' ? 'pngquant.exe' : 'pngquant' ) ;
757+ if ( bin && typeof bin === 'object' ) {
758+ bin = bin . default || bin . path || null ;
759+ }
760+
761+ if ( ! bin ) {
762+ console . warn ( 'pngquant binary path could not be resolved; falling back to JS quantization' ) ;
763+ // Fall back to JS quantization by returning null to trigger the fallback condition
764+ return null ;
765+ }
725766
726767 // Handle data URIs and buffers by writing to a temporary file
727768 let inputFile = filename ;
@@ -746,12 +787,21 @@ class LVGLImage {
746787 args . push ( String ( ncolors ) ) ;
747788 args . push ( '--force' , '--output' , '-' , '--' , inputFile ) ;
748789
790+ console . debug ( `pngquant binary: ${ bin } ` ) ;
791+ console . debug ( `pngquant args: ${ args . join ( ' ' ) } ` ) ;
792+
749793 const res = child_process . spawnSync ( bin , args ) ;
750794 if ( res . status !== 0 ) {
751- const err = res . stderr ? res . stderr . toString ( ) : 'unknown error' ;
752- throw new Error ( `pngquant conversion failed: ${ err } ` ) ;
795+ const err = res . stderr ? res . stderr . toString ( ) : ( res . error ? res . error . message : 'unknown error' ) ;
796+ console . warn ( `pngquant conversion failed (status: ${ res . status } , error: ${ err } ); falling back to JS quantization` ) ;
797+ // Return null to trigger fallback to JS quantization
798+ return null ;
753799 }
754800 return res . stdout ;
801+ } catch ( e ) {
802+ console . warn ( `pngquant error (${ e . message } ); falling back to JS quantization` ) ;
803+ // Return null to trigger fallback to JS quantization
804+ return null ;
755805 } finally {
756806 // Clean up temporary file
757807 if ( tempFile && fs . existsSync ( tempFile ) ) {
@@ -818,12 +868,22 @@ class LVGLImage {
818868 let quantResult = null ;
819869 const palette_len = png . palette ? png . palette . length : 0 ;
820870 if ( ! png . palette || ( ! auto_cf && palette_len !== ncolors ( cf ) ) ) {
821- const q = this . classPngQuant ( ncolors ( cf ) , true , '' , this . forceJSQuant , this . forceUsePngquant ) ;
822- const res = q . convert ( filename ) ;
871+ let q = this . classPngQuant ( ncolors ( cf ) , true , '' , this . forceJSQuant , this . forceUsePngquant ) ;
872+ let res = q . convert ( filename ) ;
873+
874+ // If pngquant failed (returned null), fall back to JS quantization
875+ if ( res === null && ! this . forceJSQuant ) {
876+ console . warn ( 'pngquant unavailable or failed; using JS quantization as fallback' ) ;
877+ q = this . classPngQuant ( ncolors ( cf ) , true , '' , true , false ) ; // Force JS quantization
878+ res = q . convert ( filename ) ;
879+ }
880+
823881 if ( Buffer . isBuffer ( res ) ) {
824882 png = PNG . sync . read ( res , { inputHasAlpha :true } ) ;
825883 } else if ( res && res . palette && res . rows ) {
826884 quantResult = res ; // { palette: [[r,g,b,a],..], rows: [ [idx,...], ... ], width, height }
885+ } else if ( res === null ) {
886+ throw new Error ( 'both pngquant and JS quantization failed' ) ;
827887 } else {
828888 throw new Error ( 'pngquant conversion returned unexpected result' ) ;
829889 }
0 commit comments