@@ -609,12 +609,16 @@ - (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format o
609609 if (options[SDImageCoderEncodeCompressionQuality]) {
610610 compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue ];
611611 }
612+ NSUInteger maxFileSize = 0 ;
613+ if (options[SDImageCoderEncodeMaxFileSize]) {
614+ maxFileSize = [options[SDImageCoderEncodeMaxFileSize] unsignedIntegerValue ];
615+ }
612616 NSArray <SDImageFrame *> *frames = [SDImageCoderHelper framesFromAnimatedImage: image];
613617
614618 BOOL encodeFirstFrame = [options[SDImageCoderEncodeFirstFrameOnly] boolValue ];
615619 if (encodeFirstFrame || frames.count == 0 ) {
616620 // for static single webp image
617- data = [self sd_encodedWebpDataWithImage: image.CGImage quality: compressionQuality];
621+ data = [self sd_encodedWebpDataWithImage: image.CGImage quality: compressionQuality fileSize: maxFileSize ];
618622 } else {
619623 // for animated webp image
620624 WebPMux *mux = WebPMuxNew ();
@@ -623,7 +627,7 @@ - (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format o
623627 }
624628 for (size_t i = 0 ; i < frames.count ; i++) {
625629 SDImageFrame *currentFrame = frames[i];
626- NSData *webpData = [self sd_encodedWebpDataWithImage: currentFrame.image.CGImage quality: compressionQuality];
630+ NSData *webpData = [self sd_encodedWebpDataWithImage: currentFrame.image.CGImage quality: compressionQuality fileSize: maxFileSize ];
627631 int duration = currentFrame.duration * 1000 ;
628632 WebPMuxFrameInfo frame = { .bitstream .bytes = webpData.bytes ,
629633 .bitstream .size = webpData.length ,
@@ -660,7 +664,7 @@ - (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format o
660664 return data;
661665}
662666
663- - (nullable NSData *)sd_encodedWebpDataWithImage : (nullable CGImageRef)imageRef quality : (double )quality {
667+ - (nullable NSData *)sd_encodedWebpDataWithImage : (nullable CGImageRef)imageRef quality : (double )quality fileSize : ( NSUInteger ) fileSize {
664668 NSData *webpData;
665669 if (!imageRef) {
666670 return nil ;
@@ -704,7 +708,7 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef q
704708 return nil ;
705709 }
706710
707- uint8_t *rgba = NULL ;
711+ uint8_t *rgba = NULL ; // RGBA Buffer managed by CFData, don't call `free` on it, instead call `CFRelease` on `dataRef`
708712 // We could not assume that input CGImage's color mode is always RGB888/RGBA8888. Convert all other cases to target color mode using vImage
709713 if (byteOrderNormal && ((alphaInfo == kCGImageAlphaNone ) || (alphaInfo == kCGImageAlphaLast ))) {
710714 // If the input CGImage is already RGB888/RGBA8888
@@ -758,34 +762,58 @@ - (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef q
758762
759763 rgba = dest.data ; // Converted buffer
760764 bytesPerRow = dest.rowBytes ; // Converted bytePerRow
761- CFRelease (dataRef);
762- dataRef = NULL ;
765+ CFRelease (dataRef); // Use CFData to manage bytes for free, the same code path for error handling
766+ dataRef = CFDataCreateWithBytesNoCopy ( kCFAllocatorDefault , rgba, bytesPerRow * height, kCFAllocatorDefault ) ;
763767 }
764768
765- uint8_t *data = NULL ; // Output WebP data
766769 float qualityFactor = quality * 100 ; // WebP quality is 0-100
767770 // Encode RGB888/RGBA8888 buffer to WebP data
768- size_t size;
771+ // Using the libwebp advanced API: https://developers.google.com/speed/webp/docs/api#advanced_encoding_api
772+ WebPConfig config;
773+ WebPPicture picture;
774+ WebPMemoryWriter writer;
775+
776+ if (!WebPConfigPreset (&config, WEBP_PRESET_DEFAULT, qualityFactor) ||
777+ !WebPPictureInit (&picture)) {
778+ // shouldn't happen, except if system installation is broken
779+ CFRelease (dataRef);
780+ return nil ;
781+ }
782+
783+ config.target_size = (int )fileSize; // Max filesize for output, 0 means use quality instead
784+ config.thread_level = 1 ; // Thread encoding for fast
785+ config.lossless = 0 ; // Disable lossless encoding (If we need, can add new Encoding Options in future version)
786+ picture.use_argb = config.lossless ; // Lossy encoding use YUV for internel bitstream
787+ picture.width = (int )width;
788+ picture.height = (int )height;
789+ picture.writer = WebPMemoryWrite; // Output in memory data buffer
790+ picture.custom_ptr = &writer;
791+ WebPMemoryWriterInit (&writer);
792+
793+ int result;
769794 if (hasAlpha) {
770- size = WebPEncodeRGBA (rgba, ( int )width , (int )height, ( int ) bytesPerRow, qualityFactor, &data );
795+ result = WebPPictureImportRGBA (&picture, rgba , (int )bytesPerRow);
771796 } else {
772- size = WebPEncodeRGB (rgba, ( int )width , (int )height, ( int ) bytesPerRow, qualityFactor, &data );
797+ result = WebPPictureImportRGB (&picture, rgba , (int )bytesPerRow);
773798 }
774- if (dataRef) {
775- CFRelease (dataRef); // free non-converted rgba buffer
776- dataRef = NULL ;
777- } else {
778- free (rgba); // free converted rgba buffer
779- rgba = NULL ;
799+ if (!result) {
800+ WebPMemoryWriterClear (&writer);
801+ CFRelease (dataRef);
802+ return nil ;
780803 }
781804
782- if (size) {
805+ result = WebPEncode (&config, &picture);
806+ CFRelease (dataRef); // Free bitmap buffer
807+ WebPPictureFree (&picture);
808+
809+ if (result) {
783810 // success
784- webpData = [NSData dataWithBytes: data length: size];
785- }
786- if (data) {
787- WebPFree (data) ;
811+ webpData = [NSData dataWithBytes: writer.mem length: writer. size];
812+ } else {
813+ // failed
814+ webpData = nil ;
788815 }
816+ WebPMemoryWriterClear (&writer);
789817
790818 return webpData;
791819}
0 commit comments