react-native/React/Base/RCTUtils.m
Nick Lockwood fe4b4c2d83 Optimised blending for translucent images with opaque background color + fixed cropping for images in cover/contain mode.
Summary:
@public

Our background color propagation mechanism is designed to make rendering of translucent content more efficient by pre-blending against an opaque background. Currently this only works for text however, because images are not composited into their background even if the background color is opaque.

This diff precomposites network images with their background color when the background is opaque, allowing them to take advantage of this performance optimization.

I've also added some logic to correctly crop the downloaded image when the resizeMode is "cover" or "contain" - previously it was only correct for "stretch".

Before:{F22437859}
After:{F22437862}

Test Plan: Run the UIExplorer "<ListView> - Paging" example with "color blended layers" enabled and observe that the images appear in green now, instead of red as they did before.
2015-05-22 07:19:06 -08:00

276 lines
7.9 KiB
Objective-C

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTUtils.h"
#import <mach/mach_time.h>
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
#import <CommonCrypto/CommonCrypto.h>
#import "RCTLog.h"
NSString *RCTJSONStringify(id jsonObject, NSError **error)
{
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonObject options:(NSJSONWritingOptions)NSJSONReadingAllowFragments error:error];
return jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : nil;
}
id RCTJSONParse(NSString *jsonString, NSError **error)
{
if (!jsonString) {
return nil;
}
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];
if (!jsonData) {
jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
if (jsonData) {
RCTLogWarn(@"RCTJSONParse received the following string, which could not be losslessly converted to UTF8 data: '%@'", jsonString);
} else {
RCTLogError(@"RCTJSONParse received invalid UTF8 data");
return nil;
}
}
return [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:error];
}
id RCTJSONClean(id object)
{
static dispatch_once_t onceToken;
static NSSet *validLeafTypes;
dispatch_once(&onceToken, ^{
validLeafTypes = [[NSSet alloc] initWithArray:@[
[NSString class],
[NSMutableString class],
[NSNumber class],
[NSNull class],
]];
});
if ([validLeafTypes containsObject:[object classForCoder]]) {
return object;
}
if ([object isKindOfClass:[NSDictionary class]]) {
__block BOOL copy = NO;
NSMutableDictionary *values = [[NSMutableDictionary alloc] initWithCapacity:[object count]];
[object enumerateKeysAndObjectsUsingBlock:^(NSString *key, id item, BOOL *stop) {
id value = RCTJSONClean(item);
values[key] = value;
copy |= value != item;
}];
return copy ? values : object;
}
if ([object isKindOfClass:[NSArray class]]) {
__block BOOL copy = NO;
__block NSArray *values = object;
[object enumerateObjectsUsingBlock:^(id item, NSUInteger idx, BOOL *stop) {
id value = RCTJSONClean(item);
if (copy) {
[(NSMutableArray *)values addObject:value];
} else if (value != item) {
// Converted value is different, so we'll need to copy the array
values = [[NSMutableArray alloc] initWithCapacity:values.count];
for (NSInteger i = 0; i < idx; i++) {
[(NSMutableArray *)values addObject:object[i]];
}
[(NSMutableArray *)values addObject:value];
copy = YES;
}
}];
return values;
}
return (id)kCFNull;
}
NSString *RCTMD5Hash(NSString *string)
{
const char *str = [string UTF8String];
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), result);
return [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}
CGFloat RCTScreenScale()
{
static CGFloat scale;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (![NSThread isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{
scale = [UIScreen mainScreen].scale;
});
} else {
scale = [UIScreen mainScreen].scale;
}
});
return scale;
}
CGSize RCTScreenSize()
{
static CGSize size;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (![NSThread isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{
size = [UIScreen mainScreen].bounds.size;
});
} else {
size = [UIScreen mainScreen].bounds.size;
}
});
return size;
}
CGFloat RCTRoundPixelValue(CGFloat value)
{
CGFloat scale = RCTScreenScale();
return round(value * scale) / scale;
}
CGFloat RCTCeilPixelValue(CGFloat value)
{
CGFloat scale = RCTScreenScale();
return ceil(value * scale) / scale;
}
CGFloat RCTFloorPixelValue(CGFloat value)
{
CGFloat scale = RCTScreenScale();
return floor(value * scale) / scale;
}
NSTimeInterval RCTTGetAbsoluteTime(void)
{
static struct mach_timebase_info tb_info = {0};
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
int ret = mach_timebase_info(&tb_info);
assert(0 == ret);
});
uint64_t timeInNanoseconds = (mach_absolute_time() * tb_info.numer) / tb_info.denom;
return ((NSTimeInterval)timeInNanoseconds) / 1000000;
}
void RCTSwapClassMethods(Class cls, SEL original, SEL replacement)
{
Method originalMethod = class_getClassMethod(cls, original);
IMP originalImplementation = method_getImplementation(originalMethod);
const char *originalArgTypes = method_getTypeEncoding(originalMethod);
Method replacementMethod = class_getClassMethod(cls, replacement);
IMP replacementImplementation = method_getImplementation(replacementMethod);
const char *replacementArgTypes = method_getTypeEncoding(replacementMethod);
if (class_addMethod(cls, original, replacementImplementation, replacementArgTypes))
{
class_replaceMethod(cls, replacement, originalImplementation, originalArgTypes);
}
else
{
method_exchangeImplementations(originalMethod, replacementMethod);
}
}
void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement)
{
Method originalMethod = class_getInstanceMethod(cls, original);
IMP originalImplementation = method_getImplementation(originalMethod);
const char *originalArgTypes = method_getTypeEncoding(originalMethod);
Method replacementMethod = class_getInstanceMethod(cls, replacement);
IMP replacementImplementation = method_getImplementation(replacementMethod);
const char *replacementArgTypes = method_getTypeEncoding(replacementMethod);
if (class_addMethod(cls, original, replacementImplementation, replacementArgTypes))
{
class_replaceMethod(cls, replacement, originalImplementation, originalArgTypes);
}
else
{
method_exchangeImplementations(originalMethod, replacementMethod);
}
}
BOOL RCTClassOverridesClassMethod(Class cls, SEL selector)
{
return RCTClassOverridesInstanceMethod(object_getClass(cls), selector);
}
BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector)
{
unsigned int numberOfMethods;
Method *methods = class_copyMethodList(cls, &numberOfMethods);
for (unsigned int i = 0; i < numberOfMethods; i++)
{
if (method_getName(methods[i]) == selector)
{
free(methods);
return YES;
}
}
free(methods);
return NO;
}
NSDictionary *RCTMakeError(NSString *message, id toStringify, NSDictionary *extraData)
{
if (toStringify) {
message = [NSString stringWithFormat:@"%@%@", message, toStringify];
}
NSMutableDictionary *error = [@{@"message": message} mutableCopy];
if (extraData) {
[error addEntriesFromDictionary:extraData];
}
return error;
}
NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary *extraData)
{
id error = RCTMakeError(message, toStringify, extraData);
RCTLogError(@"\nError: %@", error);
return error;
}
BOOL RCTRunningInTestEnvironment(void)
{
static BOOL _isTestEnvironment = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_isTestEnvironment = (NSClassFromString(@"SenTestCase") != nil || NSClassFromString(@"XCTest") != nil);
});
return _isTestEnvironment;
}
BOOL RCTImageHasAlpha(CGImageRef image)
{
switch (CGImageGetAlphaInfo(image)) {
case kCGImageAlphaNone:
case kCGImageAlphaNoneSkipLast:
case kCGImageAlphaNoneSkipFirst:
return NO;
default:
return YES;
}
}