16
16
17
17
#pragma mark WebP includes
18
18
19
+ // TODO: remove this pushed diagnostic "ignored" when the modular closure of libwebp framework headers is completely fixed
20
+ #pragma clang diagnostic push
21
+ #pragma clang diagnostic ignored "-Wquoted-include-in-framework-header"
22
+
19
23
#if TARGET_OS_MACCATALYST
20
24
#import < webp/webp.h>
21
25
#if TIPX_WEBP_ANIMATION_DECODING_ENABLED
30
34
#endif
31
35
#endif
32
36
37
+ #pragma clang diagnostic pop
38
+
39
+
33
40
NS_ASSUME_NONNULL_BEGIN
34
41
35
42
#pragma mark - Declarations
36
43
37
44
static UIImage * __nullable TIPXWebPRenderImage (NSData *dataBuffer,
38
45
CGSize sourceDimensions,
39
46
CGSize targetDimensions,
40
- UIViewContentMode targetContentMode);
47
+ UIViewContentMode targetContentMode,
48
+ CGRect framingRect,
49
+ CGContextRef __nullable canvas);
41
50
static UIImage * __nullable TIPXWebPConstructImage (CGDataProviderRef dataProvider,
42
51
const size_t width,
43
52
const size_t height,
@@ -84,20 +93,14 @@ @implementation TIPXWebPCodec
84
93
- (instancetype )init
85
94
{
86
95
// Shouldn't be called, but will permit in case of type erasure
87
- return [self initPreservingDefaultCodecsIfPresent: NO ];
96
+ return [self initWithPreferredCodec: nil ];
88
97
}
89
98
90
- - (instancetype )initPreservingDefaultCodecsIfPresent : ( BOOL ) preserve
99
+ - (instancetype )initWithPreferredCodec : (nullable id <TIPImageCodec>) preferredCodec
91
100
{
92
101
if (self = [super init ]) {
93
- if (preserve) {
94
- id <TIPImageCodec> webpCodec = [TIPImageCodecCatalogue defaultCodecs ][TIPImageTypeWEBP];
95
- _tip_decoder = webpCodec.tip_decoder ?: [[TIPXWebPDecoder alloc ] init ];
96
- _tip_encoder = webpCodec.tip_encoder ?: [[TIPXWebPEncoder alloc ] init ];
97
- } else {
98
- _tip_decoder = [[TIPXWebPDecoder alloc ] init ];
99
- _tip_encoder = [[TIPXWebPEncoder alloc ] init ];
100
- }
102
+ _tip_decoder = preferredCodec.tip_decoder ?: [[TIPXWebPDecoder alloc ] init ];
103
+ _tip_encoder = preferredCodec.tip_encoder ?: [[TIPXWebPEncoder alloc ] init ];
101
104
}
102
105
return self;
103
106
}
@@ -392,7 +395,9 @@ - (nullable TIPImageContainer *)_renderStaticImage:(TIPImageDecoderRenderMode)re
392
395
UIImage *image = TIPXWebPRenderImage (_dataBuffer,
393
396
_tip_dimensions,
394
397
targetDimensions,
395
- targetContentMode);
398
+ targetContentMode,
399
+ CGRectZero,
400
+ NULL );
396
401
if (image) {
397
402
_cachedImageContainer = [[TIPImageContainer alloc ] initWithImage: image];
398
403
[self _cleanup ];
@@ -463,17 +468,57 @@ - (nullable TIPImageContainer *)_renderAnimatedImage:(TIPImageDecoderRenderMode)
463
468
// Go through the animation and pull out the frames (stopping after the first frame if `justFirstFrame` is `YES`)
464
469
NSTimeInterval totalDuration = 0 ;
465
470
const NSUInteger loopCount = (NSUInteger )WebPDemuxGetI (demuxer, WEBP_FF_LOOP_COUNT);
471
+ #if DEBUG
472
+ const CGSize canvasDimensions = CGSizeMake (WebPDemuxGetI (demuxer, WEBP_FF_CANVAS_WIDTH), WebPDemuxGetI (demuxer, WEBP_FF_CANVAS_HEIGHT));
473
+ NSCParameterAssert (canvasDimensions.width == _tip_dimensions.width);
474
+ NSCParameterAssert (canvasDimensions.height == _tip_dimensions.height);
475
+ #endif
476
+ const CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast ;
477
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB ();
478
+ TIPXDeferRelease (colorSpace);
479
+ CGContextRef canvas = CGBitmapContextCreate (NULL /* data*/ ,
480
+ (size_t )_tip_dimensions.width ,
481
+ (size_t )_tip_dimensions.height ,
482
+ 8 ,
483
+ 4 * (size_t )_tip_dimensions.width ,
484
+ colorSpace,
485
+ bitmapInfo);
486
+ TIPXDeferRelease (canvas);
487
+ CGContextClearRect (canvas, (CGRect){ .origin = CGPointZero, .size = _tip_dimensions });
466
488
NSMutableArray <UIImage *> *frames = [[NSMutableArray alloc ] initWithCapacity: (NSUInteger )iter->num_frames];
467
489
NSMutableArray <NSNumber *> *frameDurations = [[NSMutableArray alloc ] initWithCapacity: (NSUInteger )iter->num_frames];
468
490
do {
469
491
470
492
NSData *fragment = [[NSData alloc ] initWithBytesNoCopy: (void *)iter->fragment.bytes
471
493
length: iter->fragment.size
472
494
freeWhenDone: NO ];
495
+ const CGRect framingRect = CGRectMake (iter->x_offset ,
496
+ iter->y_offset ,
497
+ iter->width ,
498
+ iter->height );
499
+ // CGBitmapContext is bottem-left aligned instead of top-left aligned, so adjust the origin for that use case
500
+ const CGRect canvasFramingRect = CGRectMake (framingRect.origin .x ,
501
+ _tip_dimensions.height - framingRect.size .height - framingRect.origin .y ,
502
+ framingRect.size .width ,
503
+ framingRect.size .height );
504
+
505
+ const BOOL isSizedToCanvas = CGSizeEqualToSize (framingRect.size , _tip_dimensions) && CGPointEqualToPoint (framingRect.origin , CGPointZero);
506
+
507
+ if (iter->blend_method == WEBP_MUX_NO_BLEND) {
508
+ // clear the area we are about to draw to
509
+ CGContextClearRect (canvas, canvasFramingRect);
510
+ }
473
511
UIImage *frame = TIPXWebPRenderImage (fragment,
474
512
_tip_dimensions,
475
- justFirstFrame ? targetDimensions : CGSizeZero,
476
- targetContentMode);
513
+ (justFirstFrame && isSizedToCanvas) ? targetDimensions : CGSizeZero,
514
+ targetContentMode,
515
+ framingRect,
516
+ canvas);
517
+ if (iter->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
518
+ // clear the area we just finished drawing to
519
+ CGContextClearRect (canvas, canvasFramingRect);
520
+ }
521
+
477
522
if (!frame) {
478
523
return nil ;
479
524
}
@@ -546,7 +591,9 @@ - (void)_cleanup TIPX_OBJC_DIRECT
546
591
static UIImage *TIPXWebPRenderImage (NSData *dataBuffer,
547
592
CGSize sourceDimensions,
548
593
CGSize targetDimensions,
549
- UIViewContentMode targetContentMode)
594
+ UIViewContentMode targetContentMode,
595
+ CGRect framingRect,
596
+ CGContextRef __nullable canvas)
550
597
{
551
598
__block WebPDecoderConfig* config = (WebPDecoderConfig*)WebPMalloc (sizeof (WebPDecoderConfig));
552
599
tipx_defer (^{
@@ -558,15 +605,32 @@ - (void)_cleanup TIPX_OBJC_DIRECT
558
605
return nil ;
559
606
}
560
607
561
- const CGSize scaledDimensions = TIPDimensionsScaledToTargetSizing (sourceDimensions,
562
- targetDimensions,
563
- targetContentMode);
564
- if (!CGSizeEqualToSize (scaledDimensions, sourceDimensions)) {
565
- config->options .scaled_width = (int )scaledDimensions.width ;
566
- config->options .scaled_height = (int )scaledDimensions.height ;
567
- config->options .use_scaling = 1 ;
568
- // should we stop fancy upscaling? config.options.no_fancy_upsampling = 1;
608
+ BOOL isAligned = canvas != NULL ; // having a canvas indicates an animation, always treat fragment as being "aligned"
609
+ if (!isAligned) {
610
+ // is aligned if the origin is offset
611
+ isAligned = !CGPointEqualToPoint (framingRect.origin , CGPointZero);
612
+ if (!isAligned) {
613
+ // is aligned if the size is not the canvas size (nor zero size)
614
+ isAligned = !CGSizeEqualToSize (framingRect.size , sourceDimensions)
615
+ && !CGSizeEqualToSize (framingRect.size , CGSizeZero);
616
+ }
569
617
}
618
+
619
+ if (!isAligned) {
620
+ const CGSize scaledDimensions = TIPDimensionsScaledToTargetSizing (sourceDimensions,
621
+ targetDimensions,
622
+ targetContentMode);
623
+ if (!CGSizeEqualToSize (scaledDimensions, sourceDimensions)) {
624
+ config->options .scaled_width = (int )scaledDimensions.width ;
625
+ config->options .scaled_height = (int )scaledDimensions.height ;
626
+ config->options .use_scaling = 1 ;
627
+ // should we stop fancy upscaling? config.options.no_fancy_upsampling = 1;
628
+ }
629
+ framingRect.origin = CGPointZero;
630
+ framingRect.size = scaledDimensions;
631
+ }
632
+
633
+ // Set the output colorspace as RGB (TODO: there might be a device optimization using BGRA or ABGR...)
570
634
config->output .colorspace = MODE_RGBA;
571
635
572
636
if (VP8_STATUS_OK != WebPDecode (dataBuffer.bytes , dataBuffer.length , config)) {
@@ -593,11 +657,31 @@ - (void)_cleanup TIPX_OBJC_DIRECT
593
657
return nil ;
594
658
}
595
659
596
- return TIPXWebPConstructImage (provider,
597
- (size_t )configLongLived->output .width ,
598
- (size_t )configLongLived->output .height ,
599
- componentsPerPixel,
600
- bytesPerPixel);
660
+ UIImage *image = TIPXWebPConstructImage (provider,
661
+ (size_t )configLongLived->output .width ,
662
+ (size_t )configLongLived->output .height ,
663
+ componentsPerPixel,
664
+ bytesPerPixel);
665
+ if (image && isAligned) {
666
+
667
+ if (canvas) {
668
+ framingRect.origin .y = sourceDimensions.height - framingRect.size .height - framingRect.origin .y ;
669
+ CGContextDrawImage (canvas, framingRect, image.CGImage );
670
+ CGImageRef imageRef = CGBitmapContextCreateImage (canvas);
671
+ TIPXDeferRelease (imageRef);
672
+ image = [UIImage imageWithCGImage: imageRef];
673
+ } else {
674
+ UIGraphicsBeginImageContextWithOptions (sourceDimensions, !configLongLived->input .has_alpha , 1.0 );
675
+ tipx_defer (^{
676
+ UIGraphicsEndImageContext ();
677
+ });
678
+ CGContextClearRect (UIGraphicsGetCurrentContext (), (CGRect){ .origin = CGPointZero, .size = sourceDimensions });
679
+ [image drawInRect: framingRect];
680
+ image = UIGraphicsGetImageFromCurrentImageContext ();
681
+ }
682
+
683
+ }
684
+ return image;
601
685
}
602
686
603
687
@end
0 commit comments