Apple official loading large image examples
So the first thing you have to know is how big is the image going to load into memory
-
In the case of bitmaps, the size of the bitmap is how much memory it takes to load into memory.
-
If a non-bitmap image, such as JPG/PNG, needs to be decoded into a bitmap for referenceIOS image loading speed limit optimization, the size of the decoded bitmap means how much memory the JPG/PNG occupies. Bitmap size calculation formula:
Width x heigth x 4 (ARGB)Copy the code
For example, if a 1000 x 1000 PNG image is extracted, the bitmap will consume 1000 x 1000 x 4(about 3.81MB), which means that the image will consume about 3.81MB of memory.
-
Since the memory occupied by a picture is mainly the size of the bitmap, the memory occupied by a picture is related to the resolution of the picture, and has nothing to do with the disk size occupied by the picture (after multiple picture tests, all verified correct, limited number of pictures may be wrong).
Loading large images onto a low memory device can be very troublesome and often memory hangs. Display will be hung, compression will be hung, the resolution scheme can refer toIOS compression of large hd images.
Download big pictures
In this case, SDWebImage can not be used to download large images, so the memory will not be enough. You can use AFNetWorking to download large images into the sandbox instead of the memoryIOS large file download, breakpoint download.
Uploading big pictures
Sometimes you need to upload a large image to the server. At this time, if you compress the large image (not to mention the compression process will increase the memory, and easy to hang) into the form of NSData, it will occupy a lot of memory, and then upload, it is easy to hang. Therefore, uploading large images obviously does not compress all of them at once, but it doesLarge file fragment upload and resumable breakpoint upload. Save the large image in a sandbox (don’t compress it all at once), fragment it and upload it so that the large image uploading problem is solved.
Load to show large images
If the large picture is directly loaded all display, the low device is basically hanging. But found the apple official load large picture example, apple example, change the way the image rendering using the GPU to render, effectively reduce the memory, do not change the quality of the picture, close test load 47 m pictures are no problem, had better not be used to load too little pictures, because no significance, is suitable for loading a larger (about 1 m or more).
Apple’s official principle of loading large images
-
Load large images in fragments. The size is different for different devices, depending on the device. First, calculate each fragment CGRect of the original image, and then obtain the corresponding fragment CGRect after scaling according to the device. The scaling ratio is also set according to the device.
-
Obtain a small fragment P1 of the original image according to the fragment CGRect of the original image, generate the scaled fragments according to the fragment CGRect and P1, and draw to the screen. In this way, all the small fragments of the original image are generated to the corresponding scaled fragments, and then draw.
-
Draw all the small fragments of the original picture to the screen after scaling, and the quality of the small picture does not change a lot, but it takes up a lot of memory. The general idea is that you can read the official Apple sample source code for yourself.
-
However, apple’s official method is to scale the original image and fragment the image to control memory, which means that the final image quality does not change much, but the image resolution (i.e. the size of width x heigth) is reduced. So if the display of large images can be handled in this way, upload the original image to goLarge file fragment upload and resumable breakpoint uploadWay.
Attached code:
LargeImageDownsizingViewController.h
/*
File: LargeImageDownsizingViewController.h
Abstract: The primary view controller forVersion: 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
Copy the code
LargeImageDownsizingViewController.m
/*
File: LargeImageDownsizingViewController.m
Abstract: The primary view controller forVersion: 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/* These constants provide initial values for iPad2 and 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/* these constants provide initial values for iPhone3G, iPod2, and earlier devices */ //#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// Each pixel takes up 4 bytes# define bytesPerPixel 4.0 f// Each pixel takes up 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!"); // Original image sizesourceResolution.width = CGImageGetWidth(sourceImage.CGImage);
sourceResolution.height = CGImageGetHeight(sourceImage.CGImage); // Original image resolutionsourceTotalPixels = sourceResolution.width * sourceResolution.height; // The bitmap size of the original imagesourceTotalMB = sourceTotalPixels / pixelsPerMB; DestTotalPixels/imageScale = destTotalPixels/imageScale = destTotalPixels /sourceTotalPixels;
destResolution.width = (int)( sourceResolution.width * imageScale );
destResolution.height = (int)( sourceResolution.height * imageScale ); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); Int bytesPerRow = bytesPerPixel * destresolution. width; // Allocate enough pixel data to hold the output preview image, the total number of bytes occupied by the bitmap. 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 doneCGColorSpaceRelease( colorSpace ); CGContextTranslateCTM(destContext, 0.0f, destresolution.height); CGContextScaleCTM(destContext, 1.0f, -1.0f); // Load the original image one piece at a time so that the memory of large images does not surge too much all at oncesourceTile.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.0 f; destTile.size.width = destResolution.width; destTile.size.height =sourceTile.size.height * imageScale; DestTile. Origin. X = 0.0 f; 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."); / / around CGImage encapsulate a UIImage self. DestImage = [UIImage imageWithCGImage: destImageRef scale: 1.0 f 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
Copy the code
ImageScrollView.h
/*
File: ImageScrollView.h
Abstract: This scroll view allows the user to inspect the resulting image's levels of detail. Version: Copyright (C) 2014 Apple Inc. All Rights Reserved. */ #import
@class TiledImageView; @interface ImageScrollView : UIScrollView
{ // 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
Copy the code
ImageScrollView.m
/*
File: ImageScrollView.m
Abstract: This scroll view allows the user to inspect the resulting image's levels of detail. Version: Copyright (C) 2014 Apple Inc. All Rights Reserved. */ #import "imagesCrollView.h" #import "tiledimageView.h" #import
@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.0 f; Self. MinimumZoomScale = 0.25 f; 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.0 f, 0.0 f, 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(); // Blur background image, used for display before loading clear image, after loading clear image will overwrite blur image, It is better visual effect backgroundImageView = [[UIImageView alloc] initWithImage: backgroundImage]; backgroundImageView.frame = imageRect; backgroundImageView.contentMode = UIViewContentModeScaleAspectFit; [self addSubview:backgroundImageView]; / / in the parent view behind the display [self sendSubviewToBack: backgroundImageView]; // Create the TiledImageView based on the size of the image and scale it to fit the view [[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 forTiles 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.0 f, 0.0 f, 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 andset 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
Copy the code
TiledImageView.h
/*
File: TiledImageView.h
Abstract: View backed by CATiledLayer which is good forCopyright (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
Copy the code
TiledImageView.m
/*
File: TiledImageView.m
Abstract: View backed by CATiledLayer which is good forCopyright (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); } @endCopy the code
Finally, two large pictures are attached
-
Very high resolution
-
Takes up a lot of disk space