@@ -50,6 +50,30 @@ static void ConvertAvifImagePlanarToRGB(avifImage * avif, uint8_t * outPixels) {
5050 }
5151}
5252
53+ static void FillRGBABufferWithAVIFImage (vImage_Buffer *red, vImage_Buffer *green, vImage_Buffer *blue, vImage_Buffer *alpha, avifImage *img) {
54+ red->width = img->width ;
55+ red->height = img->height ;
56+ red->data = img->rgbPlanes [AVIF_CHAN_R];
57+ red->rowBytes = img->rgbRowBytes [AVIF_CHAN_R];
58+
59+ green->width = img->width ;
60+ green->height = img->height ;
61+ green->data = img->rgbPlanes [AVIF_CHAN_G];
62+ green->rowBytes = img->rgbRowBytes [AVIF_CHAN_G];
63+
64+ blue->width = img->width ;
65+ blue->height = img->height ;
66+ blue->data = img->rgbPlanes [AVIF_CHAN_B];
67+ blue->rowBytes = img->rgbRowBytes [AVIF_CHAN_B];
68+
69+ if (img->alphaPlane != NULL ) {
70+ alpha->width = img->width ;
71+ alpha->height = img->height ;
72+ alpha->data = img->alphaPlane ;
73+ alpha->rowBytes = img->alphaRowBytes ;
74+ }
75+ }
76+
5377static void FreeImageData (void *info, const void *data, size_t size) {
5478 free ((void *)data);
5579}
@@ -142,11 +166,124 @@ - (nullable CGImageRef)sd_createAVIFImageWithData:(nonnull NSData *)data CF_RETU
142166
143167// The AVIF encoding seems too slow at the current time
144168- (BOOL )canEncodeToFormat : (SDImageFormat)format {
145- return NO ;
169+ return format == SDImageFormatAVIF ;
146170}
147171
148- - (nullable NSData *)encodedDataWithImage : (nullable UIImage *)image format : (SDImageFormat)format options : (nullable SDImageCoderOptions *)options {
149- return nil ;
172+ - (nullable NSData *)encodedDataWithImage : (nullable UIImage *)image format : (SDImageFormat)format options : (nullable SDImageCoderOptions *)options {
173+ CGImageRef imageRef = image.CGImage ;
174+ if (!imageRef) {
175+ return nil ;
176+ }
177+
178+ size_t width = CGImageGetWidth (imageRef);
179+ size_t height = CGImageGetHeight (imageRef);
180+ size_t bitsPerPixel = CGImageGetBitsPerPixel (imageRef);
181+ size_t bitsPerComponent = CGImageGetBitsPerComponent (imageRef);
182+ CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo (imageRef);
183+ CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask ;
184+ CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask ;
185+ BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
186+ alphaInfo == kCGImageAlphaNoneSkipFirst ||
187+ alphaInfo == kCGImageAlphaNoneSkipLast );
188+ BOOL byteOrderNormal = NO ;
189+ switch (byteOrderInfo) {
190+ case kCGBitmapByteOrderDefault : {
191+ byteOrderNormal = YES ;
192+ } break ;
193+ case kCGBitmapByteOrder32Little : {
194+ } break ;
195+ case kCGBitmapByteOrder32Big : {
196+ byteOrderNormal = YES ;
197+ } break ;
198+ default : break ;
199+ }
200+
201+ vImageConverterRef convertor = NULL ;
202+ vImage_Error v_error = kvImageNoError;
203+
204+ vImage_CGImageFormat srcFormat = {
205+ .bitsPerComponent = (uint32_t )bitsPerComponent,
206+ .bitsPerPixel = (uint32_t )bitsPerPixel,
207+ .colorSpace = CGImageGetColorSpace (imageRef),
208+ .bitmapInfo = bitmapInfo
209+ };
210+ vImage_CGImageFormat destFormat = {
211+ .bitsPerComponent = 8 ,
212+ .bitsPerPixel = hasAlpha ? 32 : 24 ,
213+ .colorSpace = [SDImageCoderHelper colorSpaceGetDeviceRGB ],
214+ .bitmapInfo = hasAlpha ? kCGImageAlphaFirst | kCGBitmapByteOrderDefault : kCGImageAlphaNone | kCGBitmapByteOrderDefault // RGB888/ARGB8888 (Non-premultiplied to works for libbpg)
215+ };
216+
217+ convertor = vImageConverter_CreateWithCGImageFormat (&srcFormat, &destFormat, NULL , kvImageNoFlags, &v_error);
218+ if (v_error != kvImageNoError) {
219+ return nil ;
220+ }
221+
222+ vImage_Buffer src;
223+ v_error = vImageBuffer_InitWithCGImage (&src, &srcFormat, NULL , imageRef, kvImageNoFlags);
224+ if (v_error != kvImageNoError) {
225+ return nil ;
226+ }
227+ vImage_Buffer dest;
228+ vImageBuffer_Init (&dest, height, width, hasAlpha ? 32 : 24 , kvImageNoFlags);
229+ if (!dest.data ) {
230+ free (src.data );
231+ return nil ;
232+ }
233+
234+ // Convert input color mode to RGB888/ARGB8888
235+ v_error = vImageConvert_AnyToAny (convertor, &src, &dest, NULL , kvImageNoFlags);
236+ free (src.data );
237+ vImageConverter_Release (convertor);
238+ if (v_error != kvImageNoError) {
239+ free (dest.data );
240+ return nil ;
241+ }
242+
243+ avifPixelFormat avifFormat = AVIF_PIXEL_FORMAT_YUV444;
244+ enum avifPlanesFlags planesFlags = hasAlpha ? AVIF_PLANES_RGB | AVIF_PLANES_A : AVIF_PLANES_RGB;
245+
246+ avifImage *avif = avifImageCreate ((int )width, (int )height, 8 , avifFormat);
247+ if (!avif) {
248+ free (dest.data );
249+ return nil ;
250+ }
251+ avifImageAllocatePlanes (avif, planesFlags);
252+
253+ NSData *iccProfile = (__bridge_transfer NSData *)CGColorSpaceCopyICCProfile ([SDImageCoderHelper colorSpaceGetDeviceRGB ]);
254+
255+ avifImageSetProfileICC (avif, (uint8_t *)iccProfile.bytes , iccProfile.length );
256+
257+ vImage_Buffer red, green, blue, alpha;
258+ FillRGBABufferWithAVIFImage (&red, &green, &blue, &alpha, avif);
259+
260+ if (hasAlpha) {
261+ v_error = vImageConvert_ARGB8888toPlanar8 (&dest, &alpha, &red, &green, &blue, kvImageNoFlags);
262+ } else {
263+ v_error = vImageConvert_RGB888toPlanar8 (&dest, &red, &green, &blue, kvImageNoFlags);
264+ }
265+ free (dest.data );
266+ if (v_error != kvImageNoError) {
267+ return nil ;
268+ }
269+
270+ double compressionQuality = 1 ;
271+ if (options[SDImageCoderEncodeCompressionQuality]) {
272+ compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue ];
273+ }
274+ int rescaledQuality = 63 - (int )((compressionQuality) * 63 .0f );
275+
276+ avifRawData raw = AVIF_RAW_DATA_EMPTY;
277+ avifResult result = avifImageWrite (avif, &raw, 2 , rescaledQuality);
278+
279+ if (result != AVIF_RESULT_OK) {
280+ return nil ;
281+ }
282+
283+ NSData *imageData = [NSData dataWithBytes: raw.data length: raw.size];
284+ free (raw.data );
285+
286+ return imageData;
150287}
151288
152289
0 commit comments