Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion OGImage/OGImageCache.m
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ + (OGImageCache *)shared {
+ (NSString *)MD5:(NSString *)string {
const char *d = [string UTF8String];
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(d, strlen(d), r);
CC_MD5(d, (CC_LONG)strlen(d), r);
NSMutableString *hexString = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH];
for (int ii = 0; ii < CC_MD5_DIGEST_LENGTH; ++ii) {
[hexString appendFormat:@"%02x", r[ii]];
Expand Down Expand Up @@ -104,8 +104,13 @@ - (void)imageForKey:(NSString *)key block:(OGImageCacheCompletionBlock)block {
}

- (void)setImage:(__OGImage *)image forKey:(NSString *)key {
// assert for developers, and guard against production crashes
NSParameterAssert(nil != image);
NSParameterAssert(nil != key);
if (nil == image || nil == key) {
return;
}

[_memoryCache setObject:image forKey:key];
dispatch_async(_cacheFileTasksQueue, ^{
NSURL *fileURL = [OGImageCache fileURLForKey:key];
Expand Down
25 changes: 17 additions & 8 deletions OGImage/OGImageProcessing.m
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,21 @@ OSStatus UIImageToVImageBuffer(UIImage *image, vImage_Buffer *buffer, CGImageAlp
buffer->width,
buffer->height, 8,
buffer->rowBytes, colorSpace, alphaInfo);
if (UIImageOrientationRight == image.imageOrientation) {
CGContextRotateCTM(ctx, -M_PI/2.f);
CGContextTranslateCTM(ctx, -(CGFloat)height, 0.f);
} else if (UIImageOrientationLeft == image.imageOrientation) {
CGContextRotateCTM(ctx, M_PI/2.f);
CGContextTranslateCTM(ctx, 0.f, -(CGFloat)width);
if (NULL == ctx) {
free(buffer->data);
buffer->data = NULL;
err = OGImageProcessingError;
} else {
if (UIImageOrientationRight == image.imageOrientation) {
CGContextRotateCTM(ctx, -M_PI/2.f);
CGContextTranslateCTM(ctx, -(CGFloat)height, 0.f);
} else if (UIImageOrientationLeft == image.imageOrientation) {
CGContextRotateCTM(ctx, M_PI/2.f);
CGContextTranslateCTM(ctx, 0.f, -(CGFloat)width);
}
CGContextDrawImage(ctx, CGRectMake(0.f, 0.f, CGImageGetWidth(cgImage), CGImageGetHeight(cgImage)), cgImage);
CGContextRelease(ctx);
}
CGContextDrawImage(ctx, CGRectMake(0.f, 0.f, CGImageGetWidth(cgImage), CGImageGetHeight(cgImage)), cgImage);
CGContextRelease(ctx);
CGColorSpaceRelease(colorSpace);
return err;
}
Expand Down Expand Up @@ -184,6 +190,9 @@ - (void)scaleImage:(__OGImage *)image toSize:(CGSize)size cornerRadius:(CGFloat)
if (kCGImageAlphaNone == alphaInfo) {
// kCGImageAlphaNone w/8-bit channels not supported
alphaInfo = kCGImageAlphaNoneSkipLast;
} else if (kCGImageAlphaFirst == alphaInfo || kCGImageAlphaLast == alphaInfo) {
// non-premultiplied contexts are not supported
alphaInfo = kCGImageAlphaPremultipliedFirst;
}
if (0.f < cornerRadius) {
alphaInfo = kCGImageAlphaPremultipliedFirst;
Expand Down
2 changes: 1 addition & 1 deletion OGImage/OGImageRequest.m
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ - (void)prepareImageAndNotify {
}
} else {
// if we get here, we have an http status code other than 200
tmpError = [NSError errorWithDomain:NSCocoaErrorDomain code:OGImageLoadingError userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"OGImage: Received http status code: %d", _httpResponse.statusCode]}];
tmpError = [NSError errorWithDomain:NSCocoaErrorDomain code:OGImageLoadingError userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"OGImage: Received http status code: %ld", (long)_httpResponse.statusCode]}];
}
NSAssert((nil == tmpImage && nil != tmpError) || (nil != tmpImage && nil == tmpError), @"One of tmpImage or tmpError should be non-nil");
dispatch_async(dispatch_get_main_queue(), ^{
Expand Down
32 changes: 32 additions & 0 deletions OGImage/OGImageView.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// OGImageView.h
// OGImageDemo
//
// Created by Art Gillespie on 8/23/13.
// Copyright (c) 2013 Origami Labs. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface OGImageView : UIImageView

/**
* Set image view's image with the image at `url`. `OGImageView` supports the following
* protocols:
*
* * `http`
* * `file`
* * `assets-library`
*
* The image will be scaled and fit according to the view's `bounds` and `contentMode`,
* respectively.
*/
- (void)setImageURL:(NSURL *)url placeholder:(UIImage *)image;

/**
* If there's a problem loading the image, this property will be set. KVO-observable.
* @see `OGImage.error`
*/
@property (nonatomic, strong, readonly) NSError *imageError;

@end
52 changes: 52 additions & 0 deletions OGImage/OGImageView.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// OGImageView.m
// OGImageDemo
//
// Created by Art Gillespie on 8/23/13.
// Copyright (c) 2013 Origami Labs. All rights reserved.
//

#import "OGImageView.h"
#import "OGScaledImage.h"

static NSString *KVOContext = @"OGImageView observation";

@implementation OGImageView {
OGScaledImage *_scaledImage;
}

- (void)setImageURL:(NSURL *)url placeholder:(UIImage *)placeholder {
self.image = placeholder;
[_scaledImage removeObserver:self context:&KVOContext];
OGImageProcessingScaleMethod scaleMethod = OGImageProcessingScale_AspectFill;
if (UIViewContentModeScaleAspectFit == self.contentMode) {
scaleMethod = OGImageProcessingScale_AspectFit;
}
_scaledImage = [[OGScaledImage alloc] initWithURL:url size:self.bounds.size cornerRadius:0.f method:scaleMethod key:nil placeholderImage:nil];
[_scaledImage addObserver:self context:&KVOContext];
if (nil != _scaledImage.scaledImage) {
self.image = _scaledImage.scaledImage;
}
}

#pragma mark KVO

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (((void *)&KVOContext) == context) {
if ([@"scaledImage" isEqualToString:keyPath]) {
self.image = _scaledImage.scaledImage;
} else if ([@"error" isEqualToString:keyPath]) {
[self willChangeValueForKey:@"imageError"];
_imageError = _scaledImage.error;
[self didChangeValueForKey:@"imageError"];
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

- (void)dealloc {
[_scaledImage removeObserver:self context:&KVOContext];
}

@end
6 changes: 3 additions & 3 deletions OGImage/OGScaledImage.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
#import "OGScaledImage.h"
#import "OGImageCache.h"

NSString *OGKeyWithSize(NSString *origKey, CGSize size, CGFloat cornerRadius) {
return [NSString stringWithFormat:@"%@-%f-%f-%f", origKey, size.width, size.height, cornerRadius];
NSString *OGKeyWithSize(NSString *origKey, CGSize size, CGFloat cornerRadius, OGImageProcessingScaleMethod method) {
return [NSString stringWithFormat:@"%@-%f-%f-%f-%ld", origKey, size.width, size.height, cornerRadius, (long)method];
}

@implementation OGScaledImage {
Expand Down Expand Up @@ -45,7 +45,7 @@ - (id)initWithURL:(NSURL *)url size:(CGSize)size cornerRadius:(CGFloat)cornerRad
self.scaledImage = placeholderImage;
_scaledSize = size;
_cornerRadius = cornerRadius;
_scaledKey = OGKeyWithSize(self.key, _scaledSize, _cornerRadius);
_scaledKey = OGKeyWithSize(self.key, _scaledSize, _cornerRadius, method);
[self loadImageFromURL];
}
return self;
Expand Down
15 changes: 8 additions & 7 deletions OGImage/__OGImage.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@
*
* When you create an __OGImage with a fileURL or NSData instance, it uses the
* Image I/O framework under the hood to find out about the file's format, metadata
* and alpha. Additionally, if the file ends with the extension `.@2x`, __OGImage
* will set UIImage's `scale` property correctly.
* and alpha. Additionally, if the file ends with an extension like `.@2x`,
* __OGImage will set UIImage's `scale` property correctly.
*
* When you save an __OGImage using `writeToURL`, it will automatically choose
* the best format based on the current alpha properties and original file format.
* Additionally, if the superclass' property `scale` is `2.f`, `writeToURL` will
* automatically append the `.@2x` suffix. (Ultimately, this `.@2x` stuff is
* an implementation detail: If you're not worried about cache internals, you
* don't need to worry about this. Just use keys normally and `__OGImage` and
* friends will figure everything out for you.
* Additionally, if the superclass' property `scale` is > 1, `writeToURL` will
* automatically append a resolution suffix like `.@2x`.
*
* (Ultimately, this `.@2x` stuff is an implementation detail: If you're not
* worried about cache internals, you don't need to worry about this. Just use
* keys normally and `__OGImage` and friends will figure everything out for you.
*/

@interface __OGImage : UIImage
Expand Down
37 changes: 29 additions & 8 deletions OGImage/__OGImage.m
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,39 @@ UIImageOrientation OGEXIFOrientationToUIImageOrientation(NSInteger exif) {
}
}

NSString *OGResolutionSuffixForScale(CGFloat scale) {
return [NSString stringWithFormat:@"@%.0fx", scale];
}

@implementation __OGImage

- (id)initWithDataAtURL:(NSURL *)url {
CGFloat scale = 1.f;
if (NO == [[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
url = [url URLByAppendingPathExtension:@"@2x"];
if (NO == [[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
// no such file
// handling of scaled images is still limited, but at a practical level, the odds of seeing scale factors > 10 seem pretty long
// try to optimize by starting with screen rez of device
// possible that directory enumeration might be faster? but generally, hard to prove the negative proposition that there's no file
scale = [[UIScreen mainScreen] scale];
NSURL *scaledURL = [url URLByAppendingPathExtension:OGResolutionSuffixForScale(scale)];
if (YES == [[NSFileManager defaultManager] fileExistsAtPath:[scaledURL path]]) {
url = scaledURL;
}
else {
// no file at the device resolution; try others
// ??? or should we refuse to load images at mis-matched rez?
for (scale = 2.f; scale < 11.f; ++scale) {
if( scale != [[UIScreen mainScreen] scale] ) {
scaledURL = [url URLByAppendingPathExtension:OGResolutionSuffixForScale(scale)];
if (YES == [[NSFileManager defaultManager] fileExistsAtPath:[scaledURL path]]) {
url = scaledURL;
break;
}
}
}
}
if( url != scaledURL ) {
return nil;
}
scale = 2.f;
}
NSData *data = [NSData dataWithContentsOfURL:url];
return [self initWithData:data scale:scale];
Expand Down Expand Up @@ -89,8 +111,7 @@ - (id)initWithData:(NSData *)data scale:(CGFloat)scale {
// do we have an OGImageDictionary?
_originalFileType = (__bridge NSString *)CGImageSourceGetType(imageSource);
_originalFileAlphaInfo = CGImageGetAlphaInfo(cgImage);
NSDictionary *propDict = CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL));
_originalFileOrientation = [propDict[(__bridge NSString *)kCGImagePropertyOrientation] integerValue];
_originalFileOrientation = [_originalFileProperties[(__bridge NSString *)kCGImagePropertyOrientation] integerValue];
self = [super initWithCGImage:cgImage scale:scale orientation:OGEXIFOrientationToUIImageOrientation(_originalFileOrientation)];
CGImageRelease(cgImage);
}
Expand All @@ -115,8 +136,8 @@ - (BOOL)writeToURL:(NSURL *)fileURL error:(NSError **)error {
imgType = @"public.jpeg";
}
}
if (2.f == self.scale) {
fileURL = [fileURL URLByAppendingPathExtension:@"@2x"];
if (1.f < self.scale) {
fileURL = [fileURL URLByAppendingPathExtension:OGResolutionSuffixForScale(self.scale)];
}
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithURL((__bridge CFURLRef)fileURL, (__bridge CFStringRef)imgType, 1, NULL);
if (NULL == imageDestination) {
Expand Down
Loading