@@ -27,6 +27,9 @@ class FileSystem
2727 /** @var string */
2828 public const SCAN_DIRECTORIES = "SCAN_DIRECTORIES " ;
2929
30+ /** @var string[] */
31+ private static $ imageFunctionSuffixes = ["jpeg " , "webp " , "png " , "gif " , "bmp " , "gd " , "wbmp " , "xbm " , "xpn " , "gd2 " , "gd2part " ];
32+
3033 /**
3134 * @param string $source
3235 * @param string $destination
@@ -507,7 +510,7 @@ public static function zip(string $source, ?string $destination = null, bool $ov
507510 throw new RuntimeException ("You have to enable zip extension to use this function. " );
508511 }
509512 if (!$ overwrite && $ destination !== null && self ::exists ($ destination )) {
510- throw new RuntimeException (sprintf ("Destination %s already exists. " , $ destination ));
513+ throw new InvalidArgumentException (sprintf ("Destination %s already exists. " , $ destination ));
511514 }
512515 if (!self ::exists ($ source )) {
513516 throw new InvalidArgumentException (sprintf ("Source %s does not exist. " , $ source ));
@@ -563,7 +566,7 @@ public static function unzip(string $source, ?string $destination = null, bool $
563566 throw new RuntimeException ("You have to enable zip extension to use this function. " );
564567 }
565568 if (!$ overwrite && $ destination !== null && self ::exists ($ destination )) {
566- throw new RuntimeException (sprintf ("Destination %s already exists. " , $ destination ));
569+ throw new InvalidArgumentException (sprintf ("Destination %s already exists. " , $ destination ));
567570 }
568571 if (!self ::exists ($ source )) {
569572 throw new InvalidArgumentException (sprintf ("Source %s does not exist. " , $ source ));
@@ -587,13 +590,13 @@ public static function unzip(string $source, ?string $destination = null, bool $
587590 return $ destination ;
588591 }
589592
590- public static function compressFile (string $ source , ?string $ destination = null , bool $ overwrite = false , int $ compressionLevel = 9 , int $ bufferSize = 512 ): string
593+ public static function compressFile (string $ source , ?string $ destination = null , bool $ overwrite = false , int $ compressionLevel = 9 , int $ bufferSize = 1024 * 512 ): string
591594 {
592595 if (!extension_loaded ("zlib " )) {
593596 throw new RuntimeException ("You have to enable zlib extension to use this function. " );
594597 }
595598 if (!$ overwrite && $ destination !== null && self ::exists ($ destination )) {
596- throw new RuntimeException (sprintf ("Destination %s already exists. " , $ destination ));
599+ throw new InvalidArgumentException (sprintf ("Destination %s already exists. " , $ destination ));
597600 }
598601 if (!self ::isFile ($ source )) {
599602 throw new InvalidArgumentException (sprintf ("Source %s does not exits. " , $ source ));
@@ -602,7 +605,7 @@ public static function compressFile(string $source, ?string $destination = null,
602605 throw new InvalidArgumentException (sprintf ('Parameter $compressionLevel has to be between 1 and 9. ' ));
603606 }
604607 if ($ bufferSize < 1 ) {
605- throw new RuntimeException (sprintf ('Parameter $bufferSize has to be greater than 1. ' ));
608+ throw new InvalidArgumentException (sprintf ('Parameter $bufferSize has to be greater than 1. ' ));
606609 }
607610 if ($ destination !== null && self ::exists ($ destination )) {
608611 self ::delete ($ destination );
@@ -616,7 +619,7 @@ public static function compressFile(string $source, ?string $destination = null,
616619 throw new RuntimeException (sprintf ("Function fopen('%s', '%s') has failed. " , $ source , "rb " ));
617620 }
618621 while (!feof ($ sourceResource )) {
619- $ chunk = fread ($ sourceResource , 1024 * $ bufferSize );
622+ $ chunk = fread ($ sourceResource , $ bufferSize );
620623 if ($ chunk === false ) {
621624 throw new RuntimeException ("Could not read chunk from file {$ source }. " );
622625 }
@@ -631,7 +634,7 @@ public static function compressFile(string $source, ?string $destination = null,
631634 return $ destination ;
632635 }
633636
634- public static function decompressFile (string $ source , ?string $ destination = null , bool $ overwrite = false , int $ bufferSize = 512 )
637+ public static function decompressFile (string $ source , ?string $ destination = null , bool $ overwrite = false , int $ bufferSize = 1024 * 512 )
635638 {
636639 if (!extension_loaded ("zlib " )) {
637640 throw new RuntimeException ("You have to enable zlib extension to use this function. " );
@@ -643,7 +646,7 @@ public static function decompressFile(string $source, ?string $destination = nul
643646 throw new InvalidArgumentException (sprintf ("Source %s does not exits. " , $ source ));
644647 }
645648 if ($ bufferSize < 1 ) {
646- throw new RuntimeException (sprintf ('Parameter $bufferSize has to be greater than 1. ' ));
649+ throw new InvalidArgumentException (sprintf ('Parameter $bufferSize has to be greater than 1. ' ));
647650 }
648651 if ($ overwrite && $ destination !== null && self ::exists ($ destination )) {
649652 self ::delete ($ destination );
@@ -658,7 +661,7 @@ public static function decompressFile(string $source, ?string $destination = nul
658661 throw new RuntimeException (sprintf ("Function fopen('%s', 'wb') has failed. " , $ destination ));
659662 }
660663 while (!gzeof ($ sourceResource )) {
661- fwrite ($ destinationResource , gzread ($ sourceResource , 1024 * $ bufferSize ));
664+ fwrite ($ destinationResource , gzread ($ sourceResource , $ bufferSize ));
662665 }
663666 if (gzclose ($ sourceResource ) === false ) {
664667 throw new RuntimeException ("Could not close file {$ source }. " );
@@ -668,4 +671,161 @@ public static function decompressFile(string $source, ?string $destination = nul
668671 }
669672 return $ destination ;
670673 }
674+
675+ public static function compressImageToSize (string $ source , int $ targetSize , ?string $ destination = null , bool $ overwrite = false , string $ inputFormat = null , bool $ outputFormat = null ): string
676+ {
677+ if (!$ overwrite && $ destination !== null && self ::exists ($ destination )) {
678+ throw new RuntimeException (sprintf ("Destination %s already exists. " , $ destination ));
679+ }
680+ if (!self ::isFile ($ source )) {
681+ throw new InvalidArgumentException (sprintf ("Source %s does not exits. " , $ source ));
682+ }
683+ if ($ targetSize < 1 ) {
684+ throw new RuntimeException (sprintf ('Parameter $targetSize has to be greater than 1. ' ));
685+ }
686+ if ($ inputFormat !== null && !function_exists ("imagecreatefrom {$ inputFormat }" )) {
687+ throw new InvalidArgumentException (sprintf ("Unknown format %s. This function tried to use imagecreatefrom%s function. " , $ inputFormat , $ inputFormat ));
688+ }
689+ if ($ outputFormat !== null && !function_exists ("image {$ outputFormat }" )) {
690+ throw new InvalidArgumentException (sprintf ("Unknown format %s. This function tried to use image%s function. " , $ outputFormat , $ outputFormat ));
691+ }
692+ if ($ overwrite && $ destination !== null && self ::exists ($ destination )) {
693+ self ::delete ($ destination );
694+ }
695+ if ($ destination === null ) {
696+ $ destination = self ::generateFilename ();
697+ }
698+ if (self ::size ($ source ) <= $ targetSize ) {
699+ self ::copy ($ source , $ destination );
700+ return $ destination ;
701+ }
702+ if ($ inputFormat !== null ) {
703+ $ function = "imagecreatefrom {$ inputFormat }" ;
704+ $ sourceImage = $ function ($ source );
705+ if ($ sourceImage === false ) {
706+ throw new RuntimeException (sprintf ("Function %s('%s') has failed. " , $ function , $ source ));
707+ }
708+ } else {
709+ $ sourceImage = null ;
710+ foreach (self ::$ imageFunctionSuffixes as $ suffix ) {
711+ $ function = "imagecreatefrom {$ suffix }" ;
712+ $ sourceImage = $ function ($ source );
713+ if ($ sourceImage !== false ) {
714+ break ;
715+ }
716+ }
717+ if ($ sourceImage === false ) {
718+ throw new RuntimeException (sprintf ("Could not autodetect function which could open %s. " , $ source ));
719+ }
720+ }
721+ if ($ outputFormat === null ) {
722+ $ outputFormat = function_exists ("imagewebp " ) ? "webp " : "jpeg " ;
723+ }
724+ $ outputFunction = "image {$ outputFormat }" ;
725+ $ cleanup = function ($ cleanDestination = false ) use (&$ sourceImage , $ destination ) {
726+ if (function_exists ("imagedestroy " )) {
727+ imagedestroy ($ sourceImage );
728+ }
729+ unset($ sourceImage );
730+ if ($ cleanDestination && self ::exists ($ destination )) {
731+ self ::delete ($ destination );
732+ }
733+ };
734+ $ min = 0 ;
735+ $ max = 100 ;
736+ $ acceptableQualities = [];
737+ while ($ min <= $ max ) {
738+ $ mid = floor (($ min + $ max ) / 2 );
739+ if ($ outputFunction ($ sourceImage , $ destination , $ mid ) === false ) {
740+ $ cleanup (true );
741+ throw new RuntimeException (sprintf ("Function %s('%s', '%s', %s) has failed. " , $ outputFunction , $ source , $ destination , $ mid ));
742+ }
743+ $ size = filesize ($ destination );
744+ if ($ size === false ) {
745+ $ cleanup (true );
746+ throw new RuntimeException (sprintf ("Could not read file size of %s. " , $ destination ));
747+ }
748+ clearstatcache (true , $ destination );
749+ if ($ size <= $ targetSize ) {
750+ $ acceptableQualities [$ size ] = $ mid ;
751+ $ min = $ mid + 1 ;
752+ } else {
753+ $ max = $ mid - 1 ;
754+ }
755+ }
756+ if (empty ($ acceptableQualities )) {
757+ $ cleanup (true );
758+ throw new RuntimeException (sprintf ("Could not compress image to target size. " ));
759+ }
760+ $ quality = $ acceptableQualities [max (array_keys ($ acceptableQualities ))];
761+ if ($ outputFunction ($ sourceImage , $ destination , $ quality ) === false ) {
762+ $ cleanup (true );
763+ throw new RuntimeException (sprintf ("Function %s('%s', '%s', %s) has failed. " , $ outputFunction , $ source , $ destination , $ quality ));
764+ }
765+ $ cleanup (false );
766+ return $ destination ;
767+ }
768+
769+ public static function compressImageToQuality (string $ source , int $ quality , ?string $ destination = null , bool $ overwrite = false , string $ inputFormat = null , bool $ outputFormat = null ): string
770+ {
771+ if (!$ overwrite && $ destination !== null && self ::exists ($ destination )) {
772+ throw new RuntimeException (sprintf ("Destination %s already exists. " , $ destination ));
773+ }
774+ if (!self ::isFile ($ source )) {
775+ throw new InvalidArgumentException (sprintf ("Source %s does not exits. " , $ source ));
776+ }
777+ if ($ quality < 0 || $ quality > 100 ) {
778+ throw new InvalidArgumentException (sprintf ('Parameter $quality is expected to be between 0 and 100. ' ));
779+ }
780+ if ($ inputFormat !== null && !function_exists ("imagecreatefrom {$ inputFormat }" )) {
781+ throw new InvalidArgumentException (sprintf ("Unknown format %s. This function tried to use imagecreatefrom%s function. " , $ inputFormat , $ inputFormat ));
782+ }
783+ if ($ outputFormat !== null && !function_exists ("image {$ outputFormat }" )) {
784+ throw new InvalidArgumentException (sprintf ("Unknown format %s. This function tried to use image%s function. " , $ outputFormat , $ outputFormat ));
785+ }
786+ if ($ overwrite && $ destination !== null && self ::exists ($ destination )) {
787+ self ::delete ($ destination );
788+ }
789+ if ($ destination === null ) {
790+ $ destination = self ::generateFilename ();
791+ }
792+ if ($ inputFormat !== null ) {
793+ $ function = "imagecreatefrom {$ inputFormat }" ;
794+ $ sourceImage = $ function ($ source );
795+ if ($ sourceImage === false ) {
796+ throw new RuntimeException (sprintf ("Function %s('%s') has failed. " , $ function , $ source ));
797+ }
798+ } else {
799+ $ sourceImage = null ;
800+ foreach (self ::$ imageFunctionSuffixes as $ suffix ) {
801+ $ function = "imagecreatefrom {$ suffix }" ;
802+ $ sourceImage = $ function ($ source );
803+ if ($ sourceImage !== false ) {
804+ break ;
805+ }
806+ }
807+ if ($ sourceImage === false ) {
808+ throw new RuntimeException (sprintf ("Could not autodetect function which could open %s. " , $ source ));
809+ }
810+ }
811+ if ($ outputFormat === null ) {
812+ $ outputFormat = function_exists ("imagewebp " ) ? "webp " : "jpeg " ;
813+ }
814+ $ outputFunction = "image {$ outputFormat }" ;
815+ $ cleanup = function ($ cleanDestination = false ) use (&$ sourceImage , $ destination ) {
816+ if (function_exists ("imagedestroy " )) {
817+ imagedestroy ($ sourceImage );
818+ }
819+ unset($ sourceImage );
820+ if ($ cleanDestination && self ::exists ($ destination )) {
821+ self ::delete ($ destination );
822+ }
823+ };
824+ if ($ outputFunction ($ sourceImage , $ destination , $ quality ) === false ) {
825+ $ cleanup (true );
826+ throw new RuntimeException (sprintf ("Function %s('%s', '%s', %s) has failed. " , $ outputFunction , $ source , $ destination , $ quality ));
827+ }
828+ $ cleanup (false );
829+ return $ destination ;
830+ }
671831}
0 commit comments