6
6
// Copyright (c) 2015 Twitter. All rights reserved.
7
7
//
8
8
9
+ #import < Accelerate/Accelerate.h>
9
10
#import < TwitterImagePipeline/TwitterImagePipeline.h>
10
11
11
12
#import " TIPTestImageFetchDownloadInternalWithStubbing.h"
27
28
{ @" public.png" , " PNG" , " twitterfied.png" , NO , NO },
28
29
{ @" public.tiff" , " TIFF" , " twitterfied.tiff" , NO , NO },
29
30
{ @" com.compuserve.gif" , " GIF" , " fireworks_original.gif" , NO , YES },
31
+ { @" com.google.webp" , " WEBP" , " twitterfied.webp" , NO , NO },
32
+ { @" public.jpeg" , " Small-PJPEG" , " twitterfied.small.pjpg" , YES , NO },
30
33
};
31
34
35
+ static const NSUInteger kBitrateDribble = 4 * 1000 ;
36
+ static const NSUInteger kBitrate80sModem = 16 * 1000 ;
37
+ static const NSUInteger kBitrateBad2G = 56 * 1000 ;
32
38
static const NSUInteger kBitrate2G = 128 * 1000 ; // 2G
33
39
static const NSUInteger kBitrate2GPlus = kBitrate2G * 2 ; // 2.5G
34
40
static const NSUInteger kBitrate3G = kBitrate2GPlus * 2 ; // 3G
37
43
static const NSUInteger kBitrate4GPlus = kBitrate4G * 2 ; // ~LTE
38
44
39
45
static const NSUInteger sBitrates [] = {
46
+ kBitrateDribble , kBitrate80sModem , kBitrateBad2G ,
40
47
kBitrate2G , kBitrate2GPlus ,
41
48
kBitrate3G , kBitrate3GPlus ,
42
49
kBitrate4G , kBitrate4GPlus
43
50
};
44
51
45
- static const NSUInteger kDefaultBitrateIndex = 2 ;
52
+ static const NSUInteger kDefaultBitrateIndex = 5 ;
46
53
47
- @interface ViewController () <UIPickerViewDataSource, UIPickerViewDelegate, TIPImageFetchRequest, TIPImageFetchDelegate>
54
+ @interface ViewController () <UIPickerViewDataSource, UIPickerViewDelegate, TIPImageFetchRequest, TIPImageFetchDelegate, TIPImageFetchTransformer >
48
55
@end
49
56
50
57
@implementation ViewController
@@ -56,6 +63,7 @@ @implementation ViewController
56
63
IBOutlet UIButton *_startButton;
57
64
IBOutlet UIPickerView *_pickerView;
58
65
IBOutlet UILabel *_resultsLabel;
66
+ IBOutlet UISwitch *_blurSwitch;
59
67
60
68
BOOL _selectingSpeed;
61
69
UITapGestureRecognizer *_tapper;
@@ -314,6 +322,148 @@ - (UIViewContentMode)targetContentMode
314
322
return _imageView.contentMode ;
315
323
}
316
324
325
+ - (id <TIPImageFetchTransformer>)transformer
326
+ {
327
+ return _blurSwitch.on ? self : nil ;
328
+ }
329
+
330
+ - (UIImage *)tip_transformImage : (UIImage *)image withProgress : (float )progress hintTargetDimensions : (CGSize )targetDimensions hintTargetContentMode : (UIViewContentMode)targetContentMode forImageFetchOperation : (TIPImageFetchOperation *)op
331
+ {
332
+ if (!image.CGImage ) {
333
+ return nil ;
334
+ }
335
+
336
+ BOOL shouldScaleFirst = NO ;
337
+ const CGSize imageDimension = [image tip_dimensions ];
338
+ CGFloat blurRadius = 0 ;
339
+ if (progress < 0 || progress >= 1 .f ) {
340
+ // placeholder?
341
+ id <TIPImageFetchRequest> request = op.request ;
342
+ if (![request respondsToSelector: @selector (options )]) {
343
+ return nil ;
344
+ }
345
+ if ((request.options & TIPImageFetchTreatAsPlaceholder) == 0 ) {
346
+ return nil ;
347
+ }
348
+ if (targetDimensions.width <= imageDimension.width && targetDimensions.height <= imageDimension.height ) {
349
+ return nil ;
350
+ }
351
+ blurRadius = log2 (MAX (targetDimensions.height / imageDimension.height , targetDimensions.width / targetDimensions.width ));
352
+ shouldScaleFirst = YES ;
353
+ } else {
354
+ // progressive
355
+ if (progress > .65f ) {
356
+ return nil ;
357
+ }
358
+ const CGFloat divisor = (1 .f + progress) * 2 .f ;
359
+ blurRadius = log2 (MAX (imageDimension.width , imageDimension.height )) / divisor;
360
+ blurRadius *= 1 .f - progress;
361
+ }
362
+
363
+ if (blurRadius < 0.5 ) {
364
+ return nil ;
365
+ }
366
+
367
+ // TRANSFORM!
368
+ if (shouldScaleFirst) {
369
+ image = [image tip_scaledImageWithTargetDimensions: targetDimensions contentMode: targetContentMode];
370
+ }
371
+ UIImage *transformed = [self applyBlurToImage: image withRadius: blurRadius];
372
+ NSAssert (CGSizeEqualToSize([image tip_dimensions ], [transformed tip_dimensions ]), @"sizing missmatch!");
373
+ return transformed;
374
+ }
375
+
376
+ // This is a modified version of Apple's 2013 WWDC sample code for UIImage(ImageEffects)
377
+ // https://developer.apple.com/library/content/samplecode/UIImageEffects/Listings/UIImageEffects_UIImageEffects_m.html
378
+ - (UIImage *)applyBlurToImage : (UIImage *)image withRadius : (CGFloat )blurRadius
379
+ {
380
+ // Check pre-conditions
381
+ if (image.size .width < 1 || image.size .height < 1 ) {
382
+ return nil ;
383
+ }
384
+ if (!image.CGImage ) {
385
+ return nil ;
386
+ }
387
+
388
+ CGRect imageRect = { CGPointZero , image.size };
389
+ UIImage *effectImage = image;
390
+
391
+ const BOOL hasBlur = blurRadius > __FLT_EPSILON__;
392
+ if (hasBlur) {
393
+ UIGraphicsBeginImageContextWithOptions (image.size , NO , [[UIScreen mainScreen ] scale ]);
394
+ CGContextRef effectInContext = UIGraphicsGetCurrentContext ();
395
+ CGContextScaleCTM (effectInContext, 1.0 , -1.0 );
396
+ CGContextTranslateCTM (effectInContext, 0 , -image.size .height );
397
+ CGContextDrawImage (effectInContext, imageRect, image.CGImage );
398
+
399
+ vImage_Buffer effectInBuffer;
400
+ effectInBuffer.data = CGBitmapContextGetData (effectInContext);
401
+ effectInBuffer.width = CGBitmapContextGetWidth (effectInContext);
402
+ effectInBuffer.height = CGBitmapContextGetHeight (effectInContext);
403
+ effectInBuffer.rowBytes = CGBitmapContextGetBytesPerRow (effectInContext);
404
+
405
+ UIGraphicsBeginImageContextWithOptions (image.size , NO , [[UIScreen mainScreen ] scale ]);
406
+ CGContextRef effectOutContext = UIGraphicsGetCurrentContext ();
407
+ vImage_Buffer effectOutBuffer;
408
+ effectOutBuffer.data = CGBitmapContextGetData (effectOutContext);
409
+ effectOutBuffer.width = CGBitmapContextGetWidth (effectOutContext);
410
+ effectOutBuffer.height = CGBitmapContextGetHeight (effectOutContext);
411
+ effectOutBuffer.rowBytes = CGBitmapContextGetBytesPerRow (effectOutContext);
412
+
413
+ // A description of how to compute the box kernel width from the Gaussian
414
+ // radius (aka standard deviation) appears in the SVG spec:
415
+ // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
416
+ //
417
+ // For larger values of 's' (s >= 2.0), an approximation can be used: Three
418
+ // successive box-blurs build a piece-wise quadratic convolution kernel, which
419
+ // approximates the Gaussian kernel to within roughly 3%.
420
+ //
421
+ // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
422
+ //
423
+ // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
424
+ //
425
+ const CGFloat inputRadius = blurRadius * [[UIScreen mainScreen ] scale ];
426
+ uint32_t radius = (uint32_t )floor (inputRadius * 3 . * sqrt (2 * M_PI) / 4 + 0.5 );
427
+ if (radius % 2 != 1 ) {
428
+ radius += 1 ; // force radius to be odd so that the three box-blur methodology works.
429
+ }
430
+ vImageBoxConvolve_ARGB8888 (&effectInBuffer, &effectOutBuffer, NULL , 0 , 0 , radius, radius, 0 , kvImageEdgeExtend);
431
+ vImageBoxConvolve_ARGB8888 (&effectOutBuffer, &effectInBuffer, NULL , 0 , 0 , radius, radius, 0 , kvImageEdgeExtend);
432
+ vImageBoxConvolve_ARGB8888 (&effectInBuffer, &effectOutBuffer, NULL , 0 , 0 , radius, radius, 0 , kvImageEdgeExtend);
433
+
434
+ effectImage = UIGraphicsGetImageFromCurrentImageContext ();
435
+ UIGraphicsEndImageContext ();
436
+ UIGraphicsEndImageContext ();
437
+ }
438
+
439
+ // Set up output context.
440
+ UIGraphicsBeginImageContextWithOptions (image.size , NO , [[UIScreen mainScreen ] scale ]);
441
+ CGContextRef outputContext = UIGraphicsGetCurrentContext ();
442
+ CGContextScaleCTM (outputContext, 1.0 , -1.0 );
443
+ CGContextTranslateCTM (outputContext, 0 , -image.size .height );
444
+
445
+ // Draw base image.
446
+ CGContextDrawImage (outputContext, imageRect, image.CGImage );
447
+
448
+ // Draw effect image.
449
+ if (hasBlur) {
450
+ CGContextSaveGState (outputContext);
451
+ CGContextDrawImage (outputContext, imageRect, effectImage.CGImage );
452
+ CGContextRestoreGState (outputContext);
453
+ }
454
+
455
+ // Output image is ready.
456
+ UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext ();
457
+ UIGraphicsEndImageContext ();
458
+
459
+ return outputImage;
460
+ }
461
+
462
+ // - (NSDictionary *)progressiveLoadingPolicies
463
+ // {
464
+ // return @{ TIPImageTypeJPEG : [[TIPGreedyProgressiveLoadingPolicy alloc] init] };
465
+ // }
466
+
317
467
// - (NSDictionary *)progressiveLoadingPolicies
318
468
// {
319
469
// if ([sImageTypes[_imageTypeIndex].type isEqualToString:TIPImageTypeJPEG2000] || [sImageTypes[_imageTypeIndex].type isEqualToString:TIPImageTypePNG]) {
0 commit comments