iOS开发者iOS初中级开发iOS开发实用技术

iOS 加载高清大图片

2017-10-18  本文已影响497人  飘金

苹果官方加载大图片例子

首先得知道图片加载到内存中的大小怎么计算

图片width x heigth x 4(ARGB)

比如一张1000 x 1000的png图片,则其解压出来的位图的占用大小为1000 x 1000 x 4(即3.81MB左右),也就是说这张图片会占用3.81MB左右的内存。

加载大图片到内存在低设备上动不动就内存挂掉,很是麻烦。显示时会挂,压缩时也会挂,决绝方案可以参考iOS压缩高清大图片

下载大图片

这时候大图片的下载不能够使用SDWebImage,内存肯定吃不消,可以通过AFNetWorking把大图片下载写入沙盒中而不是内存中,大图片的下载就可以搞定了,可以参考iOS大文件下载、断点下载

上传大图片

有时候需要上传大图片到服务器,这时候如果把大图片压缩(且不说压缩过程就会内存暴增,并且容易挂掉)成NSData形式,会占用很多的内存,然后在上传,很容易就会挂掉。故,上传大图片显然不能够一下子全部压缩上传,不过可以大文件分片上传和断点续传。把大图片保存到沙盒里面(不用把图片一下子全部压缩),分片去上传,这样大图片上传问题就搞定了。

加载显示大图片

如果大图片直接全部加载显示,低设备基本是会挂。不过找到了苹果官方加载大图片的例子,苹果官方例子改变了图片的渲染方式,利用GPU 进行渲染,有效降低内存,不改变图片的质量,亲测加载47M图片都没问题,最好不要用于加载太小的图片,因为没意义,比较适合用于加载大图(大概1M以上)。

苹果官方加载大图片的原理

附上代码:

LargeImageDownsizingViewController.h

/*
     File: LargeImageDownsizingViewController.h 
 Abstract: The primary view controller for this project. 
  Version: 1.1
 Copyright (C) 2014 Apple Inc. All Rights Reserved. 
  
*/

#import <UIKit/UIKit.h>

@class ImageScrollView;

@interface LargeImageDownsizingViewController : UIViewController {
    // The input image file
    UIImage* sourceImage;
    // output image file
    UIImage* destImage;
    // sub rect of the input image bounds that represents the 
    // maximum amount of pixel data to load into mem at one time.
    CGRect sourceTile;
    // sub rect of the output image that is proportionate to the
    // size of the sourceTile. 
    CGRect destTile;
    // the ratio of the size of the input image to the output image.
    float imageScale;
    // source image width and height
    CGSize sourceResolution;
    // total number of pixels in the source image
    float sourceTotalPixels;
    // total number of megabytes of uncompressed pixel data in the source image.
    float sourceTotalMB;
    // output image width and height
    CGSize destResolution;
    // the temporary container used to hold the resulting output image pixel 
    // data, as it is being assembled.
    CGContextRef destContext;
    // the number of pixels to overlap tiles as they are assembled.
    float sourceSeemOverlap;
    // an image view to visualize the image as it is being pieced together
    UIImageView* progressView;
    // a scroll view to display the resulting downsized image
    ImageScrollView* scrollView;
}

@property (strong) UIImage* destImage;

-(void)downsize:(id)arg;
-(void)updateScrollView:(id)arg;
-(void)initializeScrollView:(id)arg;
-(void)createImageFromContext;

@end

LargeImageDownsizingViewController.m

/*
     File: LargeImageDownsizingViewController.m 
 Abstract: The primary view controller for this project. 
  Version: 1.1
 Copyright (C) 2014 Apple Inc. All Rights Reserved. 
  
*/

#import "LargeImageDownsizingViewController.h"
#import <QuartzCore/QuartzCore.h>
#import "ImageScrollView.h"
#define kImageFilename @"large_leaves_70mp.jpg" // 7033x10110 image, 271 MB uncompressed

#define IPAD1_IPHONE3GS
#ifdef IPAD1_IPHONE3GS
#   define kDestImageSizeMB 60.0f // The resulting image will be (x)MB of uncompressed image data. 
#   define kSourceImageTileSizeMB 20.0f // The tile size will be (x)MB of uncompressed image data. 
#endif

/* 这些常量为iPad2和iphone4提供了初始值 */
//#define IPAD2_IPHONE4
#ifdef IPAD2_IPHONE4
#   define kDestImageSizeMB 120.0f // The resulting image will be (x)MB of uncompressed image data. 
#   define kSourceImageTileSizeMB 40.0f // The tile size will be (x)MB of uncompressed image data. 
#endif

/* 这些常量为iPhone3G、iPod2和早期设备提供了初始值 */
//#define IPHONE3G_IPOD2_AND_EARLIER
#ifdef IPHONE3G_IPOD2_AND_EARLIER
#   define kDestImageSizeMB 30.0f // The resulting image will be (x)MB of uncompressed image data. 
#   define kSourceImageTileSizeMB 10.0f // The tile size will be (x)MB of uncompressed image data. 
#endif

#define bytesPerMB 1048576.0f  //1024 * 1024
//每个像素占用4字节
#define bytesPerPixel 4.0f
//每个像素占用1/MB数
#define pixelsPerMB ( bytesPerMB / bytesPerPixel ) // 262144 pixels, for 4 bytes per pixel.
#define destTotalPixels kDestImageSizeMB * pixelsPerMB
#define tileTotalPixels kSourceImageTileSizeMB * pixelsPerMB
#define destSeemOverlap 2.0f // the numbers of pixels to overlap the seems where tiles meet.

@implementation LargeImageDownsizingViewController

@synthesize destImage;

- (void)viewDidLoad {
    [super viewDidLoad];
    progressView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:progressView];
    [NSThread detachNewThreadSelector:@selector(downsize:) toTarget:self withObject:nil];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

-(void)downsize:(id)arg {
    @autoreleasepool {
    NSString *path_document = NSHomeDirectory();
    NSString *imagePath = [path_document stringByAppendingString:@"/Documents/bigImage1.png"];
    sourceImage = [[UIImage alloc] initWithContentsOfFile:imagePath];
    if( sourceImage == nil ) NSLog(@"input image not found!");
        //原图片size
    sourceResolution.width = CGImageGetWidth(sourceImage.CGImage);
    sourceResolution.height = CGImageGetHeight(sourceImage.CGImage);
        //原图片分辨率
    sourceTotalPixels = sourceResolution.width * sourceResolution.height;
        //原图片位图大小
    sourceTotalMB = sourceTotalPixels / pixelsPerMB;
        //各种设备上对应的缩放比例,各种设备上的destTotalPixels(预览图分辨率)值也不一样
    imageScale = destTotalPixels / sourceTotalPixels;
    destResolution.width = (int)( sourceResolution.width * imageScale );
    destResolution.height = (int)( sourceResolution.height * imageScale );
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        //位图每行占用的字节数
    int bytesPerRow = bytesPerPixel * destResolution.width;
    // 分配足够的像素数据来保存输出预览图图像,位图占用的全部字节数。
    void* destBitmapData = malloc( bytesPerRow * destResolution.height );
    if( destBitmapData == NULL ) NSLog(@"failed to allocate space for the output image!");
    // create the output bitmap context
    destContext = CGBitmapContextCreate( destBitmapData, destResolution.width, destResolution.height, 8, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast );
    // remember CFTypes assign/check for NULL. NSObjects assign/check for nil.
    if( destContext == NULL ) {
        free( destBitmapData ); 
        NSLog(@"failed to create the output bitmap context!");
    }        
    // release the color space object as its job is done
    CGColorSpaceRelease( colorSpace );
    CGContextTranslateCTM( destContext, 0.0f, destResolution.height );
    CGContextScaleCTM( destContext, 1.0f, -1.0f );
        //加载原图时每次加载一小块,这样大图片内存不会一下子飙升很大
    sourceTile.size.width = sourceResolution.width;
    sourceTile.size.height = (int)( tileTotalPixels / sourceTile.size.width );     
    NSLog(@"source tile size: %f x %f",sourceTile.size.width, sourceTile.size.height);
    sourceTile.origin.x = 0.0f;
    destTile.size.width = destResolution.width;
    destTile.size.height = sourceTile.size.height * imageScale;        
    destTile.origin.x = 0.0f;
    NSLog(@"dest tile size: %f x %f",destTile.size.width, destTile.size.height);
    sourceSeemOverlap = (int)( ( destSeemOverlap / destResolution.height ) * sourceResolution.height );
    NSLog(@"dest seem overlap: %f, source seem overlap: %f",destSeemOverlap, sourceSeemOverlap);    
    CGImageRef sourceTileImageRef;
    int iterations = (int)( sourceResolution.height / sourceTile.size.height );
    int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
    if( remainder ) iterations++;
    float sourceTileHeightMinusOverlap = sourceTile.size.height;
    sourceTile.size.height += sourceSeemOverlap;
    destTile.size.height += destSeemOverlap;    
    NSLog(@"beginning downsize. iterations: %d, tile height: %f, remainder height: %d", iterations, sourceTile.size.height,remainder );
    for( int y = 0; y < iterations; ++y ) {
        // create an autorelease pool to catch calls to -autorelease made within the downsize loop.
        @autoreleasepool {
        NSLog(@"iteration %d of %d",y+1,iterations);
        sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap; 
        destTile.origin.y = ( destResolution.height ) - ( ( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + destSeemOverlap );
        sourceTileImageRef = CGImageCreateWithImageInRect( sourceImage.CGImage, sourceTile );
        if( y == iterations - 1 && remainder ) {
            float dify = destTile.size.height;
            destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
            dify -= destTile.size.height;
            destTile.origin.y += dify;
        }
         
        CGContextDrawImage( destContext, destTile, sourceTileImageRef );
        CGImageRelease( sourceTileImageRef );
        }
        if( y < iterations - 1 ) {            
            sourceImage = [[UIImage alloc] initWithContentsOfFile:imagePath];
            [self performSelectorOnMainThread:@selector(updateScrollView:) withObject:nil waitUntilDone:YES];
        }
    }
    NSLog(@"downsize complete.");
    [self performSelectorOnMainThread:@selector(initializeScrollView:) withObject:nil waitUntilDone:YES];
    CGContextRelease( destContext );
    }
}

-(void)createImageFromContext {
    // create a CGImage from the offscreen image context
    CGImageRef destImageRef = CGBitmapContextCreateImage( destContext );
    if( destImageRef == NULL ) NSLog(@"destImageRef is null.");
    // 在CGImage周围封装一个UIImage
    self.destImage = [UIImage imageWithCGImage:destImageRef scale:1.0f orientation:UIImageOrientationDownMirrored];
    // release ownership of the CGImage, since destImage retains ownership of the object now.
    CGImageRelease( destImageRef );
    if( destImage == nil ) NSLog(@"destImage is nil.");
}

-(void)updateScrollView:(id)arg {
    [self createImageFromContext];
    // display the output image on the screen.
    progressView.image = destImage;
}

-(void)initializeScrollView:(id)arg {
    [progressView removeFromSuperview];
    [self createImageFromContext];
    // create a scroll view to display the resulting image.
    scrollView = [[ImageScrollView alloc] initWithFrame:self.view.bounds image:self.destImage];
    [self.view addSubview:scrollView];
}

@end

ImageScrollView.h

/*
     File: ImageScrollView.h 
 Abstract: This scroll view allows the user to inspect the resulting image's levels of detail. 
  Version: 1.1
 Copyright (C) 2014 Apple Inc. All Rights Reserved. 
  
*/

#import <UIKit/UIKit.h>

@class TiledImageView;

@interface ImageScrollView : UIScrollView <UIScrollViewDelegate> {
    // The TiledImageView that is currently front most
    TiledImageView* frontTiledView;
    // The old TiledImageView that we draw on top of when the zooming stops
    TiledImageView* backTiledView;  
    // A low res version of the image that is displayed until the TiledImageView
    // renders its content.
    UIImageView *backgroundImageView;
    float minimumScale;
    // current image zoom scale
    CGFloat imageScale;
    UIImage* image;
}
@property (strong) UIImage* image;
@property (strong) TiledImageView* backTiledView;   

-(id)initWithFrame:(CGRect)frame image:(UIImage*)image;

@end

ImageScrollView.m

/*
     File: ImageScrollView.m 
 Abstract: This scroll view allows the user to inspect the resulting image's levels of detail. 
  Version: 1.1
 Copyright (C) 2014 Apple Inc. All Rights Reserved. 
  
*/

#import "ImageScrollView.h"
#import "TiledImageView.h"
#import <QuartzCore/QuartzCore.h>

@implementation ImageScrollView;

@synthesize image, backTiledView;

- (void)dealloc {
    self.backTiledView = nil;
}
-(id)initWithFrame:(CGRect)frame image:(UIImage*)img {
    if((self = [super initWithFrame:frame])) {      
        // Set up the UIScrollView
        self.showsVerticalScrollIndicator = NO;
        self.showsHorizontalScrollIndicator = NO;
        self.bouncesZoom = YES;
        self.decelerationRate = UIScrollViewDecelerationRateFast;
        self.delegate = self;
        self.maximumZoomScale = 5.0f;
        self.minimumZoomScale = 0.25f;
        self.backgroundColor = [UIColor colorWithRed:0.4f green:0.2f blue:0.2f alpha:1.0f];
        // determine the size of the image
        self.image = img;
        CGRect imageRect = CGRectMake(0.0f,0.0f,CGImageGetWidth(image.CGImage),CGImageGetHeight(image.CGImage));
        imageScale = self.frame.size.width/imageRect.size.width;
        minimumScale = imageScale * 0.75f;
        NSLog(@"imageScale: %f",imageScale);
        imageRect.size = CGSizeMake(imageRect.size.width*imageScale, imageRect.size.height*imageScale);
        // Create a low res image representation of the image to display before the TiledImageView
        // renders its content.
        UIGraphicsBeginImageContext(imageRect.size);        
        CGContextRef context = UIGraphicsGetCurrentContext();       
        CGContextSaveGState(context);
        CGContextDrawImage(context, imageRect, image.CGImage);
        CGContextRestoreGState(context);        
        UIImage *backgroundImage = UIGraphicsGetImageFromCurrentImageContext();     
        UIGraphicsEndImageContext();
        //模糊的背景图片,用于在加载好清晰图前显示,清晰图加载好后会覆盖掉模糊图,这样视觉效果比较好
        backgroundImageView = [[UIImageView alloc] initWithImage:backgroundImage];
        backgroundImageView.frame = imageRect;
        backgroundImageView.contentMode = UIViewContentModeScaleAspectFit;
        [self addSubview:backgroundImageView];
        //放到父视图的最后面显示
        [self sendSubviewToBack:backgroundImageView];
        // Create the TiledImageView based on the size of the image and scale it to fit the view.
        //显示的清晰图片
        frontTiledView = [[TiledImageView alloc] initWithFrame:imageRect image:image scale:imageScale];
        [self addSubview:frontTiledView];
    }
    return self;
}

#pragma mark -
#pragma mark Override layoutSubviews to center content

// We use layoutSubviews to center the image in the view
- (void)layoutSubviews {
    [super layoutSubviews];
    // center the image as it becomes smaller than the size of the screen
    CGSize boundsSize = self.bounds.size;
    CGRect frameToCenter = frontTiledView.frame;
    // center horizontally
    if (frameToCenter.size.width < boundsSize.width)
        frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
    else
        frameToCenter.origin.x = 0;
    // center vertically
    if (frameToCenter.size.height < boundsSize.height)
        frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
    else
        frameToCenter.origin.y = 0;
    frontTiledView.frame = frameToCenter;
    backgroundImageView.frame = frameToCenter;
    // to handle the interaction between CATiledLayer and high resolution screens, we need to manually set the
    // tiling view's contentScaleFactor to 1.0. (If we omitted this, it would be 2.0 on high resolution screens,
    // which would cause the CATiledLayer to ask us for tiles of the wrong scales.)
    frontTiledView.contentScaleFactor = 1.0;
}
#pragma mark -
#pragma mark UIScrollView delegate methods
// A UIScrollView delegate callback, called when the user starts zooming. 
// We return our current TiledImageView.
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return frontTiledView;
}
// A UIScrollView delegate callback, called when the user stops zooming.  When the user stops zooming
// we create a new TiledImageView based on the new zoom level and draw it on top of the old TiledImageView.
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
    // set the new scale factor for the TiledImageView
    imageScale *=scale;
    if( imageScale < minimumScale ) imageScale = minimumScale;
    CGRect imageRect = CGRectMake(0.0f,0.0f,CGImageGetWidth(image.CGImage) * imageScale,CGImageGetHeight(image.CGImage) * imageScale);
    // Create a new TiledImageView based on new frame and scaling.
    frontTiledView = [[TiledImageView alloc] initWithFrame:imageRect image:image scale:imageScale]; 
    [self addSubview:frontTiledView];
}

// A UIScrollView delegate callback, called when the user begins zooming.  When the user begins zooming
// we remove the old TiledImageView and set the current TiledImageView to be the old view so we can create a
// a new TiledImageView when the zooming ends.
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
    // Remove back tiled view.
    [backTiledView removeFromSuperview];
    // Set the current TiledImageView to be the old view.
    self.backTiledView = frontTiledView;
}

@end

TiledImageView.h

/*
     File: TiledImageView.h 
 Abstract: View backed by CATiledLayer which is good for particularly large views. 
  Version: 1.1
 Copyright (C) 2014 Apple Inc. All Rights Reserved. 
  
*/

#import <UIKit/UIKit.h>

@interface TiledImageView : UIView {
    CGFloat imageScale;
    UIImage* image;
    CGRect imageRect;
}
@property (strong) UIImage* image;

-(id)initWithFrame:(CGRect)_frame image:(UIImage*)image scale:(CGFloat)scale;

@end

TiledImageView.m

/*
     File: TiledImageView.m 
 Abstract: View backed by CATiledLayer which is good for particularly large views. 
  Version: 1.1
 Copyright (C) 2014 Apple Inc. All Rights Reserved. 
  
*/

#import "TiledImageView.h"
#import <QuartzCore/QuartzCore.h>

@implementation TiledImageView

@synthesize image;

// Set the layer's class to be CATiledLayer.
+ (Class)layerClass {
    return [CATiledLayer class];
}

// Create a new TiledImageView with the desired frame and scale.
-(id)initWithFrame:(CGRect)_frame image:(UIImage*)img scale:(CGFloat)scale {
    if ((self = [super initWithFrame:_frame])) {
        self.image = img;
        imageRect = CGRectMake(0.0f, 0.0f, CGImageGetWidth(image.CGImage), CGImageGetHeight(image.CGImage));
        imageScale = scale;
        CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
        // levelsOfDetail and levelsOfDetailBias determine how
        // the layer is rendered at different zoom levels.  This
        // only matters while the view is zooming, since once the 
        // the view is done zooming a new TiledImageView is created
        // at the correct size and scale.
        tiledLayer.levelsOfDetail = 4;
        tiledLayer.levelsOfDetailBias = 4;
        tiledLayer.tileSize = CGSizeMake(512.0, 512.0); 
    }
    return self;
}

-(void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();    
    CGContextSaveGState(context);
    // Scale the context so that the image is rendered 
    // at the correct size for the zoom level.
    CGContextScaleCTM(context, imageScale,imageScale);  
    CGContextDrawImage(context, imageRect, image.CGImage);
    CGContextRestoreGState(context);    
}

@end

最后附上两张大图片

上一篇下一篇

猜你喜欢

热点阅读