Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,121 @@
#import <LegacyComponents/ASActor.h>
#import <LegacyComponents/ActionStage.h>
#import "LegacyComponentsInternal.h"
static NSMutableDictionary *registeredRequestBuilders()
{
static NSMutableDictionary *dict = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
dict = [[NSMutableDictionary alloc] init];
});
return dict;
}
@implementation ASActor
+ (void)registerActorClass:(Class)requestBuilderClass
{
NSString *genericPath = [requestBuilderClass genericPath];
if (genericPath == nil || genericPath.length == 0)
{
TGLegacyLog(@"Error: ASActor::registerActorClass: genericPath is nil");
return;
}
[registeredRequestBuilders() setObject:requestBuilderClass forKey:genericPath];
}
+ (ASActor *)requestBuilderForGenericPath:(NSString *)genericPath path:(NSString *)path
{
Class builderClass = [registeredRequestBuilders() objectForKey:genericPath];
if (builderClass != nil)
{
ASActor *builderInstance = [[builderClass alloc] initWithPath:path];
return builderInstance;
}
return nil;
}
+ (NSString *)genericPath
{
TGLegacyLog(@"Error: ASActor::genericPath: no default implementation provided");
return nil;
}
@synthesize path = _path;
@synthesize requestQueueName = _requestQueueName;
@synthesize storedOptions = _storedOptions;
@synthesize requiresAuthorization = _requiresAuthorization;
@synthesize cancelTimeout = _cancelTimeout;
@synthesize cancelToken = _cancelToken;
@synthesize cancelled = _cancelled;
/*#if TARGET_IPHONE_SIMULATOR
static int instanceCount = 0;
#endif*/
- (id)initWithPath:(NSString *)path
{
self = [super init];
if (self != nil)
{
_cancelTimeout = 0;
_path = path;
/*#if TARGET_IPHONE_SIMULATOR
instanceCount++;
TGLegacyLog(@"%d actors (++)", instanceCount);
#endif*/
}
return self;
}
- (void)dealloc
{
/*#if TARGET_IPHONE_SIMULATOR
instanceCount--;
TGLegacyLog(@"%d actors (--)", instanceCount);
#endif*/
}
- (void)prepare:(NSDictionary *)__unused options
{
}
- (void)execute:(NSDictionary *)__unused options
{
TGLegacyLog(@"Error: ASActor::execute: no default implementation provided");
}
- (void)cancel
{
self.cancelled = true;
}
- (void)addCancelToken:(id)token
{
if (_multipleCancelTokens == nil)
_multipleCancelTokens = [[NSMutableArray alloc] init];
[_multipleCancelTokens addObject:token];
}
- (void)handleRequestProblem
{
}
- (void)watcherJoined:(ASHandle *)__unused watcherHandle options:(NSDictionary *)__unused options waitingInActorQueue:(bool)__unused waitingInActorQueue
{
}
@end
@@ -0,0 +1,130 @@
#import <LegacyComponents/ASHandle.h>
#import <LegacyComponents/LegacyComponents.h>
#import <LegacyComponents/ASWatcher.h>
@interface ASHandle ()
{
TG_SYNCHRONIZED_DEFINE(_delegate);
}
@end
@implementation ASHandle
@synthesize delegate = _delegate;
@synthesize releaseOnMainThread = _releaseOnMainThread;
- (id)initWithDelegate:(id<ASWatcher>)delegate
{
self = [super init];
if (self != nil)
{
TG_SYNCHRONIZED_INIT(_delegate);
_delegate = delegate;
}
return self;
}
- (id)initWithDelegate:(id<ASWatcher>)delegate releaseOnMainThread:(bool)releaseOnMainThread
{
self = [super init];
if (self != nil)
{
TG_SYNCHRONIZED_INIT(_delegate);
_delegate = delegate;
_releaseOnMainThread = releaseOnMainThread;
}
return self;
}
- (void)reset
{
TG_SYNCHRONIZED_BEGIN(_delegate);
_delegate = nil;
TG_SYNCHRONIZED_END(_delegate);
}
- (bool)hasDelegate
{
bool result = false;
TG_SYNCHRONIZED_BEGIN(_delegate);
result = _delegate != nil;
TG_SYNCHRONIZED_END(_delegate);
return result;
}
- (id<ASWatcher>)delegate
{
id<ASWatcher> result = nil;
TG_SYNCHRONIZED_BEGIN(_delegate);
result = _delegate;
TG_SYNCHRONIZED_END(_delegate);
return result;
}
- (void)setDelegate:(id<ASWatcher>)delegate
{
TG_SYNCHRONIZED_BEGIN(_delegate);
_delegate = delegate;
TG_SYNCHRONIZED_END(_delegate);
}
- (void)requestAction:(NSString *)action options:(id)options
{
__strong id<ASWatcher> delegate = self.delegate;
if (delegate != nil && [delegate respondsToSelector:@selector(actionStageActionRequested:options:)])
[delegate actionStageActionRequested:action options:options];
if (_releaseOnMainThread && ![NSThread isMainThread])
{
dispatch_async(dispatch_get_main_queue(), ^
{
[delegate class];
});
}
}
- (void)receiveActorMessage:(NSString *)path messageType:(NSString *)messageType message:(id)message
{
__strong id<ASWatcher> delegate = self.delegate;
if (delegate != nil && [delegate respondsToSelector:@selector(actorMessageReceived:messageType:message:)])
[delegate actorMessageReceived:path messageType:messageType message:message];
if (_releaseOnMainThread && ![NSThread isMainThread])
{
dispatch_async(dispatch_get_main_queue(), ^
{
[delegate class];
});
}
}
- (void)notifyResourceDispatched:(NSString *)path resource:(id)resource
{
[self notifyResourceDispatched:path resource:resource arguments:nil];
}
- (void)notifyResourceDispatched:(NSString *)path resource:(id)resource arguments:(id)arguments
{
__strong id<ASWatcher> delegate = self.delegate;
if (delegate != nil && [delegate respondsToSelector:@selector(actionStageResourceDispatched:resource:arguments:)])
[delegate actionStageResourceDispatched:path resource:resource arguments:arguments];
if (_releaseOnMainThread && ![NSThread isMainThread])
{
dispatch_async(dispatch_get_main_queue(), ^
{
[delegate class];
});
}
}
@end
@@ -0,0 +1,100 @@
#import <LegacyComponents/ASQueue.h>
@interface ASQueue ()
{
bool _isMainQueue;
dispatch_queue_t _queue;
const char *_name;
}
@end
@implementation ASQueue
- (instancetype)initWithName:(const char *)name
{
self = [super init];
if (self != nil)
{
_name = name;
_queue = dispatch_queue_create(_name, 0);
dispatch_queue_set_specific(_queue, _name, (void *)_name, NULL);
}
return self;
}
- (void)dealloc
{
#if !OS_OBJECT_HAVE_OBJC_SUPPORT
dispatch_release(_queue);
#endif
_queue = nil;
}
+ (ASQueue *)mainQueue
{
static ASQueue *queue = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
queue = [[ASQueue alloc] init];
queue->_queue = dispatch_get_main_queue();
queue->_isMainQueue = true;
});
return queue;
}
- (dispatch_queue_t)nativeQueue
{
return _queue;
}
- (bool)isCurrentQueue
{
if (_queue == nil)
return false;
if (_isMainQueue)
return [NSThread isMainThread];
else
return dispatch_get_specific(_name) == _name;
}
- (void)dispatchOnQueue:(dispatch_block_t)block
{
[self dispatchOnQueue:block synchronous:false];
}
- (void)dispatchOnQueue:(dispatch_block_t)block synchronous:(bool)synchronous
{
if (block == nil)
return;
if (_queue != nil)
{
if (_isMainQueue)
{
if ([NSThread isMainThread])
block();
else if (synchronous)
dispatch_sync(_queue, block);
else
dispatch_async(_queue, block);
}
else
{
if (dispatch_get_specific(_name) == _name)
block();
else if (synchronous)
dispatch_sync(_queue, block);
else
dispatch_async(_queue, block);
}
}
}
@end
@@ -0,0 +1,42 @@
#import <LegacyComponents/AVURLAsset+TGMediaItem.h>
#import <LegacyComponents/TGMediaAssetImageSignals.h>
#import <LegacyComponents/TGPhotoEditorUtils.h>
@implementation AVURLAsset (TGMediaItem)
- (bool)isVideo
{
return true;
}
- (NSString *)uniqueIdentifier
{
return self.URL.absoluteString;
}
- (CGSize)originalSize
{
AVAssetTrack *track = [self tracksWithMediaType:AVMediaTypeVideo].firstObject;
return CGRectApplyAffineTransform((CGRect){ CGPointZero, track.naturalSize }, track.preferredTransform).size;
}
- (SSignal *)thumbnailImageSignal
{
CGFloat thumbnailImageSide = TGPhotoThumbnailSizeForCurrentScreen().width;
CGSize size = TGScaleToSize(self.originalSize, CGSizeMake(thumbnailImageSide, thumbnailImageSide));
return [TGMediaAssetImageSignals videoThumbnailForAVAsset:self size:size timestamp:kCMTimeZero];
}
- (SSignal *)screenImageSignal:(NSTimeInterval)__unused position
{
return nil;
}
- (SSignal *)originalImageSignal:(NSTimeInterval)__unused position
{
return nil;
}
@end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,76 @@
//
// FLAnimatedImage.h
// Flipboard
//
// Created by Raphael Schaad on 7/8/13.
// Copyright (c) 2013-2014 Flipboard. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@protocol FLAnimatedImageDebugDelegate;
//
// An `FLAnimatedImage`'s job is to deliver frames in a highly performant way and works in conjunction with `FLAnimatedImageView`.
// It subclasses `NSObject` and not `UIImage` because it's only an "image" in the sense that a sea lion is a lion.
// It tries to intelligently choose the frame cache size depending on the image and memory situation with the goal to lower CPU usage for smaller ones, lower memory usage for larger ones and always deliver frames for high performant play-back.
// Note: `posterImage`, `size`, `loopCount`, `delayTimes` and `frameCount` don't change after successful initialization.
//
@interface FLAnimatedImage : NSObject
@property (nonatomic, strong) UIImage *(^imageDrawingBlock)(UIImage *);
@property (nonatomic, strong, readonly) UIImage *posterImage; // Guaranteed to be loaded; usually equivalent to `-imageLazilyCachedAtIndex:0`
@property (nonatomic, assign, readonly) CGSize size; // The `.posterImage`'s `.size`
@property (nonatomic, assign, readonly) NSUInteger loopCount; // 0 means repeating the animation indefinitely
@property (nonatomic, strong, readonly) NSArray *delayTimes; // Of type `NSTimeInterval` boxed in `NSNumber`s
@property (nonatomic, assign, readonly) NSUInteger frameCount; // Number of valid frames; equal to `[.delayTimes count]`
@property (nonatomic, assign, readonly) NSUInteger frameCacheSizeCurrent; // Current size of intelligently chosen buffer window; can range in the interval [1..frameCount]
@property (nonatomic, assign) NSUInteger frameCacheSizeMax; // Allow to cap the cache size; 0 means no specific limit (default)
// Intended to be called from main thread synchronously; will return immediately.
// If the result isn't cached, will return `nil`; the caller should then pause playback, not increment frame counter and keep polling.
// After an initial loading time, depending on `frameCacheSize`, frames should be available immediately from the cache.
- (UIImage *)imageLazilyCachedAtIndex:(NSUInteger)index;
// Pass either a `UIImage` or an `FLAnimatedImage` and get back its size
+ (CGSize)sizeForImage:(id)image;
// Designated initializer
// On success, returns a new `FLAnimatedImage` with all fields populated, on failure returns `nil` and an error will be logged.
- (instancetype)initWithAnimatedGIFData:(NSData *)data imageDrawingBlock:(UIImage *(^)(UIImage *))imageDrawingBlock;
@property (nonatomic, strong, readonly) NSData *data; // The data the receiver was initialized with; read-only
#if DEBUG
// Only intended to report internal state for debugging
@property (nonatomic, weak) id<FLAnimatedImageDebugDelegate> debug_delegate;
@property (nonatomic, strong) NSMutableDictionary *debug_info; // To track arbitrary data (e.g. original URL, loading durations, cache hits, etc.)
#endif
@end
@interface FLWeakProxy : NSProxy
+ (instancetype)weakProxyForObject:(id)targetObject;
@end
#if DEBUG
@protocol FLAnimatedImageDebugDelegate <NSObject>
@optional
- (void)debug_animatedImage:(FLAnimatedImage *)animatedImage didUpdateCachedFrames:(NSIndexSet *)indexesOfFramesInCache;
- (void)debug_animatedImage:(FLAnimatedImage *)animatedImage didRequestCachedFrame:(NSUInteger)index;
- (CGFloat)debug_animatedImagePredrawingSlowdownFactor:(FLAnimatedImage *)animatedImage;
@end
#endif
@@ -0,0 +1,740 @@
//
// FLAnimatedImage.m
// Flipboard
//
// Created by Raphael Schaad on 7/8/13.
// Copyright (c) 2013-2014 Flipboard. All rights reserved.
//
#import "FLAnimatedImage.h"
#import <ImageIO/ImageIO.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <CoreGraphics/CoreGraphics.h>
// From vm_param.h, define for iOS 8.0 or higher to build on device.
#ifndef BYTE_SIZE
#define BYTE_SIZE 8 // byte size in bits
#endif
#define MEGABYTE (1024 * 1024)
// An animated image's data size (dimensions * frameCount) category; its value is the max allowed memory (in MB).
// E.g.: A 100x200px GIF with 30 frames is ~2.3MB in our pixel format and would fall into the `FLAnimatedImageDataSizeCategoryAll` category.
typedef NS_ENUM(NSUInteger, FLAnimatedImageDataSizeCategory) {
FLAnimatedImageDataSizeCategoryAll = 10, // All frames permanently in memory (be nice to the CPU)
FLAnimatedImageDataSizeCategoryDefault = 75, // A frame cache of default size in memory (usually real-time performance and keeping low memory profile)
FLAnimatedImageDataSizeCategoryOnDemand = 250, // Only keep one frame at the time in memory (easier on memory, slowest performance)
FLAnimatedImageDataSizeCategoryUnsupported // Even for one frame too large, computer says no.
};
typedef NS_ENUM(NSUInteger, FLAnimatedImageFrameCacheSize) {
FLAnimatedImageFrameCacheSizeNoLimit = 0, // 0 means no specific limit
FLAnimatedImageFrameCacheSizeLowMemory = 1, // The minimum frame cache size; this will produce frames on-demand.
FLAnimatedImageFrameCacheSizeGrowAfterMemoryWarning = 2, // If we can produce the frames faster than we consume, one frame ahead will already result in a stutter-free playback.
FLAnimatedImageFrameCacheSizeDefault = 5 // Build up a comfy buffer window to cope with CPU hiccups etc.
};
@interface FLAnimatedImage ()
{
// Use old school ivar instead of property for retained non-object types (CF type, dispatch "object") to avoid ARC confusion: http://stackoverflow.com/questions/9684972/strong-property-with-attribute-nsobject-for-a-cf-type-doesnt-retain/9690656#9690656
CGImageSourceRef _imageSource;
// Note: Only if the deployment target is iOS 6.0 or higher, dispatch objects are declared as "true objects" and participate in ARC, etc.; See <os/object.h> or https://github.com/AFNetworking/AFNetworking/pull/517 for details.
dispatch_queue_t _serialQueue;
}
@property (nonatomic, assign, readonly) NSUInteger frameCacheSizeOptimal; // The optimal number of frames to cache based on image size & number of frames; never changes
@property (nonatomic, assign) NSUInteger frameCacheSizeMaxInternal; // Allow to cap the cache size e.g. when memory warnings occur; 0 means no specific limit (default)
@property (nonatomic, assign) NSUInteger requestedFrameIndex; // Most recently requested frame index
@property (nonatomic, assign, readonly) NSUInteger posterImageFrameIndex; // Index of non-purgable poster image; never changes
@property (nonatomic, strong, readonly) NSMutableArray *cachedFrames; // Uncached frame indexes hold `NSNull`
@property (nonatomic, strong, readonly) NSMutableIndexSet *cachedFrameIndexes; // Indexes of cached frames
@property (nonatomic, strong, readonly) NSMutableIndexSet *requestedFrameIndexes; // Indexes of frames that are currently produced in the background
@property (nonatomic, strong, readonly) NSIndexSet *allFramesIndexSet; // Default index set with the full range of indexes; never changes
@property (nonatomic, assign) NSUInteger memoryWarningCount;
// The weak proxy is used to break retain cycles with delayed actions from memory warnings.
// We are lying about the actual type here to gain static type checking and eliminate casts.
// The actual type of the object is `FLWeakProxy`. Lazily instantiated since it is not typically needed.
@property (nonatomic, strong, readonly) FLAnimatedImage *weakProxy;
@end
@implementation FLAnimatedImage
#pragma mark - Accessors
#pragma mark Public
// This is the definite value the frame cache needs to size itself to.
- (NSUInteger)frameCacheSizeCurrent
{
NSUInteger frameCacheSizeCurrent = self.frameCacheSizeOptimal;
// If set, respect the caps.
if (self.frameCacheSizeMax > FLAnimatedImageFrameCacheSizeNoLimit) {
frameCacheSizeCurrent = MIN(frameCacheSizeCurrent, self.frameCacheSizeMax);
}
if (self.frameCacheSizeMaxInternal > FLAnimatedImageFrameCacheSizeNoLimit) {
frameCacheSizeCurrent = MIN(frameCacheSizeCurrent, self.frameCacheSizeMaxInternal);
}
return frameCacheSizeCurrent;
}
- (void)setFrameCacheSizeMax:(NSUInteger)frameCacheSizeMax
{
if (_frameCacheSizeMax != frameCacheSizeMax) {
// Remember whether the new cap will cause the current cache size to shrink; then we'll make sure to purge from the cache if needed.
BOOL willFrameCacheSizeShrink = (frameCacheSizeMax < self.frameCacheSizeCurrent);
// Update the value
_frameCacheSizeMax = frameCacheSizeMax;
if (willFrameCacheSizeShrink) {
[self purgeFrameCacheIfNeeded];
}
}
}
#pragma mark Private
- (void)setFrameCacheSizeMaxInternal:(NSUInteger)frameCacheSizeMaxInternal
{
if (_frameCacheSizeMaxInternal != frameCacheSizeMaxInternal) {
// Remember whether the new cap will cause the current cache size to shrink; then we'll make sure to purge from the cache if needed.
BOOL willFrameCacheSizeShrink = (frameCacheSizeMaxInternal < self.frameCacheSizeCurrent);
// Update the value
_frameCacheSizeMaxInternal = frameCacheSizeMaxInternal;
if (willFrameCacheSizeShrink) {
[self purgeFrameCacheIfNeeded];
}
}
}
// Explicit synthesizing for `readonly` property with overridden getter.
@synthesize weakProxy = _weakProxy;
- (FLAnimatedImage *)weakProxy
{
if (!_weakProxy) {
_weakProxy = (id)[FLWeakProxy weakProxyForObject:self];
}
return _weakProxy;
}
#pragma mark - Life Cycle
- (id)init
{
NSLog(@"Error: Use `-initWithAnimatedGIFData:` and supply the animated GIF data as an argument to initialize an object of type `FLAnimatedImage`.");
return nil;
}
- (instancetype)initWithAnimatedGIFData:(NSData *)data imageDrawingBlock:(UIImage *(^)(UIImage *))imageDrawingBlock
{
// Early return if no data supplied!
BOOL hasData = ([data length] > 0);
if (!hasData) {
NSLog(@"Error: No animated GIF data supplied.");
return nil;
}
self = [super init];
if (self) {
self.imageDrawingBlock = imageDrawingBlock;
// Do one-time initializations of `readonly` properties directly to ivar to prevent implicit actions and avoid need for private `readwrite` property overrides.
// Keep a strong reference to `data` and expose it read-only publicly.
// However, we will use the `_imageSource` as handler to the image data throughout our life cycle.
_data = data;
// Initialize internal data structures
// We'll fill in the initial `NSNull` values below, when we loop through all frames.
_cachedFrames = [[NSMutableArray alloc] init];
_cachedFrameIndexes = [[NSMutableIndexSet alloc] init];
_requestedFrameIndexes = [[NSMutableIndexSet alloc] init];
// Note: We could leverage `CGImageSourceCreateWithURL` too to add a second initializer `-initWithAnimatedGIFContentsOfURL:`.
_imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
// Early return on failure!
if (!_imageSource) {
NSLog(@"Error: Failed to `CGImageSourceCreateWithData` for animated GIF data %@", data);
return nil;
}
// Early return if not GIF!
CFStringRef imageSourceContainerType = CGImageSourceGetType(_imageSource);
BOOL isGIFData = UTTypeConformsTo(imageSourceContainerType, kUTTypeGIF);
if (!isGIFData) {
NSLog(@"Error: Supplied data is of type %@ and doesn't seem to be GIF data %@", imageSourceContainerType, data);
return nil;
}
// Get `LoopCount`
// Note: 0 means repeating the animation indefinitely.
// Image properties example:
// {
// FileSize = 314446;
// "{GIF}" = {
// HasGlobalColorMap = 1;
// LoopCount = 0;
// };
// }
NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(_imageSource, NULL);
_loopCount = [[[imageProperties objectForKey:(id)kCGImagePropertyGIFDictionary] objectForKey:(id)kCGImagePropertyGIFLoopCount] unsignedIntegerValue];
// Iterate through frame images
size_t imageCount = CGImageSourceGetCount(_imageSource);
NSMutableArray *delayTimesMutable = [NSMutableArray arrayWithCapacity:imageCount];
for (size_t i = 0; i < imageCount; i++) {
CGImageRef frameImageRef = CGImageSourceCreateImageAtIndex(_imageSource, i, NULL);
if (frameImageRef) {
UIImage *frameImage = [UIImage imageWithCGImage:frameImageRef];
// Check for valid `frameImage` before parsing its properties as frames can be corrupted (and `frameImage` even `nil` when `frameImageRef` was valid).
if (frameImage) {
// Set poster image
if (!self.posterImage) {
if (_imageDrawingBlock)
_posterImage = _imageDrawingBlock(frameImage);
else
_posterImage = frameImage;
// Set its size to proxy our size.
_size = _posterImage.size;
// Remember index of poster image so we never purge it; also add it to the cache.
_posterImageFrameIndex = i;
self.cachedFrames[self.posterImageFrameIndex] = self.posterImage;
[self.cachedFrameIndexes addIndex:self.posterImageFrameIndex];
} else {
// Placeholder indicates that we don't have a cached frame.
// We use an array instead of a dictionary for slightly faster access.
self.cachedFrames[i] = [NSNull null];
}
// Get `DelayTime`
// Note: It's not in (1/100) of a second like still falsly described in the documentation as per iOS 7 but in seconds stored as `kCFNumberFloat32Type`.
// Frame properties example:
// {
// ColorModel = RGB;
// Depth = 8;
// PixelHeight = 960;
// PixelWidth = 640;
// "{GIF}" = {
// DelayTime = "0.4";
// UnclampedDelayTime = "0.4";
// };
// }
NSDictionary *frameProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(_imageSource, i, NULL);
NSDictionary *framePropertiesGIF = [frameProperties objectForKey:(id)kCGImagePropertyGIFDictionary];
// Try to use the unclamped delay time; fall back to the normal delay time.
NSNumber *delayTime = [framePropertiesGIF objectForKey:(id)kCGImagePropertyGIFUnclampedDelayTime];
if (!delayTime) {
delayTime = [framePropertiesGIF objectForKey:(id)kCGImagePropertyGIFDelayTime];
}
// If we don't get a delay time from the properties, fall back to `kDelayTimeIntervalDefault` or carry over the preceding frame's value.
const NSTimeInterval kDelayTimeIntervalDefault = 0.1;
if (!delayTime) {
if (i == 0) {
NSLog(@"Verbose: Falling back to default delay time for first frame %@ because none found in GIF properties %@", frameImage, frameProperties);
delayTime = @(kDelayTimeIntervalDefault);
} else {
NSLog(@"Verbose: Falling back to preceding delay time for frame %zu %@ because none found in GIF properties %@", i, frameImage, frameProperties);
delayTime = delayTimesMutable[i - 1];
}
}
// Support frame delays as low as `kDelayTimeIntervalMinimum`, with anything below being rounded up to `kDelayTimeIntervalDefault` for legacy compatibility.
// This is how the fastest browsers do it as per 2012: http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser-compatibility
const NSTimeInterval kDelayTimeIntervalMinimum = 0.02;
// To support the minimum even when rounding errors occur, use an epsilon when comparing. We downcast to float because that's what we get for delayTime from ImageIO.
if ([delayTime floatValue] < ((float)kDelayTimeIntervalMinimum - FLT_EPSILON)) {
NSLog(@"Verbose: Rounding frame %zu's `delayTime` from %f up to default %f (minimum supported: %f).", i, [delayTime floatValue], kDelayTimeIntervalDefault, kDelayTimeIntervalMinimum);
delayTime = @(kDelayTimeIntervalDefault);
}
delayTimesMutable[i] = delayTime;
} else {
NSLog(@"Verbose: Dropping frame %zu because valid `CGImageRef` %@ did result in `nil`-`UIImage`.", i, frameImageRef);
}
CFRelease(frameImageRef);
} else {
NSLog(@"Verbose: Dropping frame %zu because failed to `CGImageSourceCreateImageAtIndex` with image source %@", i, _imageSource);
}
}
_delayTimes = [delayTimesMutable copy];
_frameCount = [_delayTimes count];
if (self.frameCount == 0) {
NSLog(@"Error: Failed to create any valid frames for GIF with properties %@", imageProperties);
return nil;
} else if (self.frameCount == 1) {
// Warn when we only have a single frame but return a valid GIF.
NSLog(@"Verbose: Created valid GIF but with only a single frame. Image properties: %@", imageProperties);
} else {
// We have multiple frames, rock on!
}
// Calculate the optimal frame cache size: try choosing a larger buffer window depending on the predicted image size.
// It's only dependent on the image size & number of frames and never changes.
CGFloat animatedImageDataSize = CGImageGetBytesPerRow(self.posterImage.CGImage) * self.size.height * self.frameCount / MEGABYTE;
if (animatedImageDataSize <= FLAnimatedImageDataSizeCategoryAll) {
_frameCacheSizeOptimal = self.frameCount;
} else if (animatedImageDataSize <= FLAnimatedImageDataSizeCategoryDefault) {
// This value doesn't depend on device memory much because if we're not keeping all frames in memory we will always be decoding 1 frame up ahead per 1 frame that gets played and at this point we might as well just keep a small buffer just large enough to keep from running out of frames.
_frameCacheSizeOptimal = FLAnimatedImageFrameCacheSizeDefault;
} else {
// The predicted size exceeds the limits to build up a cache and we go into low memory mode from the beginning.
_frameCacheSizeOptimal = FLAnimatedImageFrameCacheSizeLowMemory;
}
// In any case, cap the optimal cache size at the frame count.
_frameCacheSizeOptimal = MIN(_frameCacheSizeOptimal, self.frameCount);
// Convenience/minor performance optimization; keep an index set handy with the full range to return in `-frameIndexesToCache`.
_allFramesIndexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, self.frameCount)];
// System Memory Warnings Notification Handler
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (_weakProxy) {
[NSObject cancelPreviousPerformRequestsWithTarget:_weakProxy];
}
if (_imageSource) {
CFRelease(_imageSource);
}
// Needed for deployment target iOS 5.0
#if ((__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0) || (!defined(__IPHONE_6_0)))
if (_serialQueue) {
dispatch_release(_serialQueue);
}
#endif
}
#pragma mark - Public Methods
// See header for more details.
// Note: both consumer and producer are throttled: consumer by frame timings and producer by the available memory (max buffer window size).
- (UIImage *)imageLazilyCachedAtIndex:(NSUInteger)index
{
// Early return if the requested index is beyond bounds.
// Note: We're comparing an index with a count and need to bail on greater than or equal to.
if (index >= self.frameCount) {
NSLog(@"Error: Skipping requested frame %lu beyond bounds (total frame count: %lu) for animated image: %@", (unsigned long)index, (unsigned long)self.frameCount, self);
return nil;
}
// Remember requested frame index, this influences what we should cache next.
self.requestedFrameIndex = index;
// Quick check to avoid doing any work if we already have all possible frames cached, a common case.
if ([self.cachedFrameIndexes count] < self.frameCount) {
// If we have frames that should be cached but aren't and aren't requested yet, request them.
// Exclude existing cached frames, frames already requested, and specially cached poster image.
NSMutableIndexSet *frameIndexesToAddToCacheMutable = [[self frameIndexesToCache] mutableCopy];
[frameIndexesToAddToCacheMutable removeIndexes:self.cachedFrameIndexes];
[frameIndexesToAddToCacheMutable removeIndexes:self.requestedFrameIndexes];
[frameIndexesToAddToCacheMutable removeIndex:self.posterImageFrameIndex];
NSIndexSet *frameIndexesToAddToCache = [frameIndexesToAddToCacheMutable copy];
// Asynchronously add frames to our cache.
if ([frameIndexesToAddToCache count] > 0) {
[self addFrameIndexesToCache:frameIndexesToAddToCache];
}
}
// Get the specified image. Watch out for `NSNull` placeholders.
UIImage *image = nil;
id tryImage = self.cachedFrames[index];
if ([tryImage isKindOfClass:[UIImage class]]) {
image = tryImage;
}
// Purge if needed based on the current playhead position.
[self purgeFrameCacheIfNeeded];
return image;
}
// Only called once from `-imageLazilyCachedAtIndex` but factored into its own method for logical grouping.
- (void)addFrameIndexesToCache:(NSIndexSet *)frameIndexesToAddToCache
{
// Order matters. First, iterate over the indexes starting from the requested frame index.
// Then, if there are any indexes before the requested frame index, do those.
NSRange firstRange = NSMakeRange(self.requestedFrameIndex, self.frameCount - self.requestedFrameIndex);
NSRange secondRange = NSMakeRange(0, self.requestedFrameIndex);
if (firstRange.length + secondRange.length != self.frameCount) {
NSLog(@"Error: Two-part frame cache range doesn't equal full range.");
}
// Add to the requested list before we actually kick them off, so they don't get into the queue twice.
[self.requestedFrameIndexes addIndexes:frameIndexesToAddToCache];
// Lazily create dedicated isolation queue.
if (!_serialQueue) {
_serialQueue = dispatch_queue_create("com.flipboard.framecachingqueue", DISPATCH_QUEUE_SERIAL);
}
// Start streaming requested frames in the background into the cache.
// Avoid capturing self in the block as there's no reason to keep doing work if the animated image went away.
FLAnimatedImage * __weak weakSelf = self;
dispatch_async(_serialQueue, ^{
// Produce and cache next needed frame.
void (^frameRangeBlock)(NSRange, BOOL *) = ^(NSRange range, __unused BOOL *stop) {
// Iterate through contiguous indexes; can be faster than `enumerateIndexesInRange:options:usingBlock:`.
__strong FLAnimatedImage *strongSelf = weakSelf;
for (NSUInteger i = range.location; i < NSMaxRange(range); i++) {
UIImage *image = [strongSelf predrawnImageAtIndex:i];
// The results get returned one by one as soon as they're ready (and not in batch).
// The benefits of having the first frames as quick as possible outweigh building up a buffer to cope with potential hiccups when the CPU suddenly gets busy.
if (image && strongSelf) {
dispatch_async(dispatch_get_main_queue(), ^{
strongSelf.cachedFrames[i] = image;
[strongSelf.cachedFrameIndexes addIndex:i];
[strongSelf.requestedFrameIndexes removeIndex:i];
});
}
}
};
// Guard against crashing on 0-length ranges with an 'NSRangeException' "last range index (-1) beyond bounds" (Apple's error message is bogus here).
// This is only needed on iOS 5, iPad only, running on device and only for the range {0,0} but regardless of whether the index set is mutable or immutable or what the indexes in the set are (can even be empty).
if (firstRange.length > 0) {
[frameIndexesToAddToCache enumerateRangesInRange:firstRange options:0 usingBlock:frameRangeBlock];
}
if (secondRange.length > 0) {
[frameIndexesToAddToCache enumerateRangesInRange:secondRange options:0 usingBlock:frameRangeBlock];
}
});
}
+ (CGSize)sizeForImage:(id)image
{
CGSize imageSize = CGSizeZero;
// Early return for nil
if (!image) {
return imageSize;
}
if ([image isKindOfClass:[UIImage class]]) {
UIImage *uiImage = (UIImage *)image;
imageSize = uiImage.size;
} else if ([image isKindOfClass:[FLAnimatedImage class]]) {
FLAnimatedImage *animatedImage = (FLAnimatedImage *)image;
imageSize = animatedImage.size;
} else {
// Bear trap to capture bad images; we have seen crashers cropping up on iOS 7.
NSLog(@"Error: `image` isn't of expected types `UIImage` or `FLAnimatedImage`: %@", image);
}
return imageSize;
}
#pragma mark - Private Methods
#pragma mark Frame Loading
- (UIImage *)predrawnImageAtIndex:(NSUInteger)index
{
// It's very important to use the cached `_imageSource` since the random access to a frame with `CGImageSourceCreateImageAtIndex` turns from an O(1) into an O(n) operation when re-initializing the image source every time.
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_imageSource, index, NULL);
UIImage *image = [UIImage imageWithCGImage:imageRef];
CFRelease(imageRef);
if (_imageDrawingBlock)
image = _imageDrawingBlock(image);
else
{
// Loading in the image object is only half the work, the displaying image view would still have to synchronosly wait and decode the image, so we go ahead and do that here on the background thread.
image = [[self class] predrawnImageFromImage:image];
}
return image;
}
#pragma mark Frame Caching
- (NSIndexSet *)frameIndexesToCache
{
NSIndexSet *indexesToCache = nil;
// Quick check to avoid building the index set if the number of frames to cache equals the total frame count.
if (self.frameCacheSizeCurrent == self.frameCount) {
indexesToCache = self.allFramesIndexSet;
} else {
NSMutableIndexSet *indexesToCacheMutable = [[NSMutableIndexSet alloc] init];
// Add indexes to the set in two separate blocks- the first starting from the requested frame index, up to the limit or the end.
// The second, if needed, the remaining number of frames beginning at index zero.
NSUInteger firstLength = MIN(self.frameCacheSizeCurrent, self.frameCount - self.requestedFrameIndex);
NSRange firstRange = NSMakeRange(self.requestedFrameIndex, firstLength);
[indexesToCacheMutable addIndexesInRange:firstRange];
NSUInteger secondLength = self.frameCacheSizeCurrent - firstLength;
if (secondLength > 0) {
NSRange secondRange = NSMakeRange(0, secondLength);
[indexesToCacheMutable addIndexesInRange:secondRange];
}
// Double check our math, before we add the poster image index which may increase it by one.
if ([indexesToCacheMutable count] != self.frameCacheSizeCurrent) {
NSLog(@"Error: Number of frames to cache doesn't equal expected cache size.");
}
[indexesToCacheMutable addIndex:self.posterImageFrameIndex];
indexesToCache = [indexesToCacheMutable copy];
}
return indexesToCache;
}
- (void)purgeFrameCacheIfNeeded
{
// Purge frames that are currently cached but don't need to be.
// But not if we're still under the number of frames to cache.
// This way, if all frames are allowed to be cached (the common case), we can skip all the `NSIndexSet` math below.
if ([self.cachedFrameIndexes count] > self.frameCacheSizeCurrent) {
NSMutableIndexSet *indexesToPurge = [self.cachedFrameIndexes mutableCopy];
[indexesToPurge removeIndexes:[self frameIndexesToCache]];
[indexesToPurge enumerateRangesUsingBlock:^(NSRange range, __unused BOOL *stop) {
// Iterate through contiguous indexes; can be faster than `enumerateIndexesInRange:options:usingBlock:`.
for (NSUInteger i = range.location; i < NSMaxRange(range); i++) {
[self.cachedFrameIndexes removeIndex:i];
self.cachedFrames[i] = [NSNull null];
// Note: Don't `CGImageSourceRemoveCacheAtIndex` on the image source for frames that we don't want cached any longer to maintain O(1) time access.
}
}];
}
}
- (void)growFrameCacheSizeAfterMemoryWarning:(NSNumber *)frameCacheSize
{
self.frameCacheSizeMaxInternal = [frameCacheSize unsignedIntegerValue];
NSLog(@"Verbose: Grew frame cache size max to %lu after memory warning for animated image: %@", (unsigned long)self.frameCacheSizeMaxInternal, self);
// Schedule resetting the frame cache size max completely after a while.
const NSTimeInterval kResetDelay = 3.0;
[self.weakProxy performSelector:@selector(resetFrameCacheSizeMaxInternal) withObject:nil afterDelay:kResetDelay];
}
- (void)resetFrameCacheSizeMaxInternal
{
self.frameCacheSizeMaxInternal = FLAnimatedImageFrameCacheSizeNoLimit;
NSLog(@"Verbose: Reset frame cache size max (current frame cache size: %lu) for animated image: %@", (unsigned long)self.frameCacheSizeCurrent, self);
}
#pragma mark System Memory Warnings Notification Handler
- (void)didReceiveMemoryWarning:(NSNotification *)__unused notification
{
// Hold (and use!) a strong reference to self, since `NSNotificationCenter` no longer strongly references the observer.
// This is another example of the lame fallout from the LLVM change in Xcode 5.1.
FLAnimatedImage *strongSelf = self;
strongSelf.memoryWarningCount++;
// If we were about to grow larger, but got rapped on our knuckles by the system again, cancel.
[NSObject cancelPreviousPerformRequestsWithTarget:strongSelf.weakProxy selector:@selector(growFrameCacheSizeAfterMemoryWarning:) object:@(FLAnimatedImageFrameCacheSizeGrowAfterMemoryWarning)];
[NSObject cancelPreviousPerformRequestsWithTarget:strongSelf.weakProxy selector:@selector(resetFrameCacheSizeMaxInternal) object:nil];
// Go down to the minimum and by that implicitly immediately purge from the cache if needed to not get jettisoned by the system and start producing frames on-demand.
NSLog(@"Verbose: Attempt setting frame cache size max to %lu (previous was %lu) after memory warning #%lu for animated image: %@", (unsigned long)FLAnimatedImageFrameCacheSizeLowMemory, (unsigned long)strongSelf.frameCacheSizeMaxInternal, (unsigned long)strongSelf.memoryWarningCount, strongSelf);
strongSelf.frameCacheSizeMaxInternal = FLAnimatedImageFrameCacheSizeLowMemory;
// Schedule growing larger again after a while, but cap our attempts to prevent a periodic sawtooth wave (ramps upward and then sharply drops) of memory usage.
//
// [mem]^ (2) (5) (6) 1) Loading frames for the first time
// (*)| , , , 2) Mem warning #1; purge cache
// | /| (4)/| /| 3) Grow cache size a bit after a while, if no mem warning occurs
// | / | _/ | _/ | 4) Try to grow cache size back to optimum after a while, if no mem warning occurs
// |(1)/ |_/ |/ |__(7) 5) Mem warning #2; purge cache
// |__/ (3) 6) After repetition of (3) and (4), mem warning #3; purge cache
// +----------------------> 7) After 3 mem warnings, stay at minimum cache size
// [t]
// *) The mem high water mark before we get warned might change for every cycle.
//
const NSUInteger kGrowAttemptsMax = 2;
const NSTimeInterval kGrowDelay = 2.0;
if ((strongSelf.memoryWarningCount - 1) <= kGrowAttemptsMax) {
[strongSelf.weakProxy performSelector:@selector(growFrameCacheSizeAfterMemoryWarning:) withObject:@(FLAnimatedImageFrameCacheSizeGrowAfterMemoryWarning) afterDelay:kGrowDelay];
}
// Note: It's not possible to get the level of a memory warning with a public API: http://stackoverflow.com/questions/2915247/iphone-os-memory-warnings-what-do-the-different-levels-mean/2915477#2915477
}
#pragma mark Image Decoding
// Decodes the image's data and draws it off-screen fully in memory; it's thread-safe and hence can be called on a background thread.
// On success, the returned object is a new `UIImage` instance with the same content as the one passed in.
// On failure, the returned object is the unchanged passed in one; the data will not be predrawn in memory though and an error will be logged.
// First inspired by & good Karma to: https://gist.github.com/steipete/1144242
+ (UIImage *)predrawnImageFromImage:(UIImage *)imageToPredraw
{
// Always use a device RGB color space for simplicity and predictability what will be going on.
CGColorSpaceRef colorSpaceDeviceRGBRef = CGColorSpaceCreateDeviceRGB();
// Early return on failure!
if (!colorSpaceDeviceRGBRef) {
NSLog(@"Error: Failed to `CGColorSpaceCreateDeviceRGB` for image %@", imageToPredraw);
return imageToPredraw;
}
// Even when the image doesn't have transparency, we have to add the extra channel because Quartz doesn't support other pixel formats than 32 bpp/8 bpc for RGB:
// kCGImageAlphaNoneSkipFirst, kCGImageAlphaNoneSkipLast, kCGImageAlphaPremultipliedFirst, kCGImageAlphaPremultipliedLast
// (source: docs "Quartz 2D Programming Guide > Graphics Contexts > Table 2-1 Pixel formats supported for bitmap graphics contexts")
size_t numberOfComponents = CGColorSpaceGetNumberOfComponents(colorSpaceDeviceRGBRef) + 1; // 4: RGB + A
// "In iOS 4.0 and later, and OS X v10.6 and later, you can pass NULL if you want Quartz to allocate memory for the bitmap." (source: docs)
void *data = NULL;
size_t width = (size_t)imageToPredraw.size.width;
size_t height = (size_t)imageToPredraw.size.height;
size_t bitsPerComponent = CHAR_BIT;
size_t bitsPerPixel = (bitsPerComponent * numberOfComponents);
size_t bytesPerPixel = (bitsPerPixel / BYTE_SIZE);
size_t bytesPerRow = (bytesPerPixel * width);
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageToPredraw.CGImage);
// If the alpha info doesn't match to one of the supported formats (see above), pick a reasonable supported one.
// "For bitmaps created in iOS 3.2 and later, the drawing environment uses the premultiplied ARGB format to store the bitmap data." (source: docs)
if (alphaInfo == kCGImageAlphaNone || alphaInfo == kCGImageAlphaOnly) {
alphaInfo = kCGImageAlphaNoneSkipFirst;
} else if (alphaInfo == kCGImageAlphaFirst) {
alphaInfo = kCGImageAlphaPremultipliedFirst;
} else if (alphaInfo == kCGImageAlphaLast) {
alphaInfo = kCGImageAlphaPremultipliedLast;
}
// "The constants for specifying the alpha channel information are declared with the `CGImageAlphaInfo` type but can be passed to this parameter safely." (source: docs)
bitmapInfo |= alphaInfo;
// Create our own graphics context to draw to; `UIGraphicsGetCurrentContext`/`UIGraphicsBeginImageContextWithOptions` doesn't create a new context but returns the current one which isn't thread-safe (e.g. main thread could use it at the same time).
// Note: It's not worth caching the bitmap context for multiple frames ("unique key" would be `width`, `height` and `hasAlpha`), it's ~50% slower. Time spent in libRIP's `CGSBlendBGRA8888toARGB8888` suddenly shoots up -- not sure why.
CGContextRef bitmapContextRef = CGBitmapContextCreate(data, width, height, bitsPerComponent, bytesPerRow, colorSpaceDeviceRGBRef, bitmapInfo);
CGColorSpaceRelease(colorSpaceDeviceRGBRef);
// Early return on failure!
if (!bitmapContextRef) {
NSLog(@"Error: Failed to `CGBitmapContextCreate` with color space %@ and parameters (width: %zu height: %zu bitsPerComponent: %zu bytesPerRow: %zu) for image %@", colorSpaceDeviceRGBRef, width, height, bitsPerComponent, bytesPerRow, imageToPredraw);
return imageToPredraw;
}
// Draw image in bitmap context and create image by preserving receiver's properties.
CGContextDrawImage(bitmapContextRef, CGRectMake(0.0, 0.0, imageToPredraw.size.width, imageToPredraw.size.height), imageToPredraw.CGImage);
CGImageRef predrawnImageRef = CGBitmapContextCreateImage(bitmapContextRef);
UIImage *predrawnImage = [UIImage imageWithCGImage:predrawnImageRef scale:imageToPredraw.scale orientation:imageToPredraw.imageOrientation];
CGImageRelease(predrawnImageRef);
CGContextRelease(bitmapContextRef);
// Early return on failure!
if (!predrawnImage) {
NSLog(@"Error: Failed to `imageWithCGImage:scale:orientation:` with image ref %@ created with color space %@ and bitmap context %@ and properties and properties (scale: %f orientation: %ld) for image %@", predrawnImageRef, colorSpaceDeviceRGBRef, bitmapContextRef, imageToPredraw.scale, (long)imageToPredraw.imageOrientation, imageToPredraw);
return imageToPredraw;
}
return predrawnImage;
}
#pragma mark - Description
- (NSString *)description
{
NSString *description = [super description];
description = [description stringByAppendingFormat:@" size=%@", NSStringFromCGSize(self.size)];
description = [description stringByAppendingFormat:@" frameCount=%lu", (unsigned long)self.frameCount];
return description;
}
@end
#pragma mark - FLWeakProxy
@interface FLWeakProxy ()
@property (nonatomic, weak) id target;
@end
@implementation FLWeakProxy
#pragma mark Life Cycle
// This is the designated creation method of an `FLWeakProxy` and
// as a subclass of `NSProxy` it doesn't respond to or need `-init`.
+ (instancetype)weakProxyForObject:(id)targetObject
{
FLWeakProxy *weakProxy = [FLWeakProxy alloc];
weakProxy.target = targetObject;
return weakProxy;
}
#pragma mark Forwarding Messages
- (id)forwardingTargetForSelector:(SEL)__unused selector
{
// Keep it lightweight: access the ivar directly
return _target;
}
#pragma mark - NSWeakProxy Method Overrides
#pragma mark Handling Unimplemented Methods
- (void)forwardInvocation:(NSInvocation *)invocation
{
// Fallback for when target is nil. Don't do anything, just return 0/NULL/nil.
// The method signature we've received to get here is just a dummy to keep `doesNotRecognizeSelector:` from firing.
// We can't really handle struct return types here because we don't know the length.
void *nullPointer = NULL;
[invocation setReturnValue:&nullPointer];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)__unused selector
{
// We only get here if `forwardingTargetForSelector:` returns nil.
// In that case, our weak target has been reclaimed. Return a dummy method signature to keep `doesNotRecognizeSelector:` from firing.
// We'll emulate the Obj-c messaging nil behavior by setting the return value to nil in `forwardInvocation:`, but we'll assume that the return value is `sizeof(void *)`.
// Other libraries handle this situation by making use of a global method signature cache, but that seems heavier than necessary and has issues as well.
// See https://www.mikeash.com/pyblog/friday-qa-2010-02-26-futures.html and https://github.com/steipete/PSTDelegateProxy/issues/1 for examples of using a method signature cache.
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
@end
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2007 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef FloatConversion_h
#define FloatConversion_h
#include <CoreGraphics/CGBase.h>
namespace WebCore {
template<typename T>
float narrowPrecisionToFloat(T);
template<>
inline float narrowPrecisionToFloat(double number)
{
return static_cast<float>(number);
}
template<typename T>
CGFloat narrowPrecisionToCGFloat(T);
template<>
inline CGFloat narrowPrecisionToCGFloat(double number)
{
return static_cast<CGFloat>(number);
}
} // namespace WebCore
#endif // FloatConversion_h
@@ -0,0 +1,680 @@
#import <LegacyComponents/Freedom.h>
#import <objc/runtime.h>
#import <objc/message.h>
#import <map>
#include <inttypes.h>
#define FORCE_INLINE __attribute__((always_inline))
static inline uint32_t rotl32 ( uint32_t x, int8_t r )
{
return (x << r) | (x >> (32 - r));
}
#define ROTL32(x,y) rotl32(x,y)
static FORCE_INLINE uint32_t getblock ( const uint32_t * p, int i )
{
return p[i];
}
static FORCE_INLINE uint32_t fmix ( uint32_t h )
{
h ^= h >> 16;
h *= 0x85ebca6b;
h ^= h >> 13;
h *= 0xc2b2ae35;
h ^= h >> 16;
return h;
}
static void MurmurHash3_x86_32 ( const void * key, int len,
uint32_t seed, void * out )
{
const uint8_t * data = (const uint8_t*)key;
const int nblocks = len / 4;
uint32_t h1 = seed;
const uint32_t c1 = 0xcc9e2d51;
const uint32_t c2 = 0x1b873593;
//----------
// body
const uint32_t * blocks = (const uint32_t *)(data + nblocks*4);
for(int i = -nblocks; i; i++)
{
uint32_t k1 = getblock(blocks,i);
k1 *= c1;
k1 = ROTL32(k1,15);
k1 *= c2;
h1 ^= k1;
h1 = ROTL32(h1,13);
h1 = h1*5+0xe6546b64;
}
//----------
// tail
const uint8_t * tail = (const uint8_t*)(data + nblocks*4);
uint32_t k1 = 0;
switch(len & 3)
{
case 3: k1 ^= tail[2] << 16;
case 2: k1 ^= tail[1] << 8;
case 1: k1 ^= tail[0];
k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
};
//----------
// finalization
h1 ^= len;
h1 = fmix(h1);
*(uint32_t*)out = h1;
}
static int32_t murMurHashBytes32(void *bytes, int length)
{
int32_t result = 0;
MurmurHash3_x86_32(bytes, length, -137723950, &result);
return result;
}
static const char *freedomDecoratedClass = "freedomDecoratedClass";
static int (*freedom_getClassList)(Class *, int) = NULL;
static const char * (*freedom_class_getName)(Class cls) = NULL;
static bool freedom_initialized = false;
FreedomIdentifier FreedomIdentifierEmpty = (FreedomIdentifier){ .string = NULL, .key = 0 };
char *copyFreedomIdentifierValue(FreedomIdentifier identifier)
{
if (identifier.string == NULL)
return NULL;
int length = (int)strlen(identifier.string) / 2;
char *buf = (char *)malloc(length + 1);
buf[length] = 0;
for (int i = 0; i < length; i++)
{
int b = 0;
sscanf(identifier.string + i * 2, "%02x", &b);
buf[i] = ((char)b) ^ (((uint8_t *)&identifier.key)[i % 4]);
}
return buf;
}
Class freedomClass(uint32_t name)
{
static std::map<uint32_t, __unsafe_unretained Class> classMap;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
int classCount = freedom_getClassList(NULL, 0);
if (classCount > 0)
{
__unsafe_unretained Class *classList = (Class *)malloc(classCount * sizeof(Class));
objc_getClassList(classList, classCount);
for (int i = 0; i < classCount; i++)
{
const char *className = freedom_class_getName(classList[i]);
uint32_t hashName = (uint32_t)murMurHashBytes32((void *)className, (int)strlen(className));
//NSLog(@"0x%" PRIx32 " -> %s", hashName, className);
classMap[hashName] = classList[i];
}
free(classList);
}
freedom_initialized = true;
});
auto it = classMap.find(name);
if (it != classMap.end())
return it->second;
return nil;
}
Class freedomMakeClass(Class superclass, Class subclass)
{
if (superclass == Nil || subclass == Nil)
return nil;
int32_t randomId = 0;
arc4random_buf(&randomId, 4);
Class decoratedClass = objc_allocateClassPair(superclass, [[NSString alloc] initWithFormat:@"Decorated%" PRIx32 "", randomId].UTF8String, 0);
unsigned int count = 0;
Method *methodList = class_copyMethodList(subclass, &count);
if (methodList != NULL)
{
for (unsigned int i = 0; i < count; i++)
{
class_addMethod(decoratedClass, method_getName(methodList[i]), method_getImplementation(methodList[i]), method_getTypeEncoding(methodList[i]));
}
free(methodList);
}
objc_registerClassPair(decoratedClass);
return decoratedClass;
}
ptrdiff_t freedomIvarOffset(Class targetClass, uint32_t name)
{
ptrdiff_t result = -1;
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(targetClass, &count);
if (ivarList != NULL)
{
for (unsigned int i = 0; i < count; i++)
{
const char *ivarName = ivar_getName(ivarList[i]);
uint32_t hashName = (uint32_t)murMurHashBytes32((void *)ivarName, (int)strlen(ivarName));
//NSLog(@"%s -> 0x%x", ivarName, hashName);
if (hashName == name)
{
result = ivar_getOffset(ivarList[i]);
}
}
free(ivarList);
}
return result;
}
static bool consumeToken(const char *desc, int length, int &offset, const char *token)
{
size_t tokenLength = strlen(token);
if (offset + (int)tokenLength > length)
return false;
if (strncmp(desc + offset, token, tokenLength))
return false;
offset += tokenLength;
return true;
}
static bool consumeNumber(const char *desc, int length, int &offset, int &number)
{
if (offset + 1 > length)
return false;
int result = 0;
for (int i = offset; i < length; i++)
{
if (desc[i] < '0' || desc[i] > '9')
{
if (i != offset)
{
number = result;
offset = i;
return true;
}
return false;
}
else
result = result * 10 + (desc[i] - '0');
}
return false;
}
static bool consumeField(const char *desc, int length, int &offset, uint32_t &outName, int &outBitSize)
{
if (!consumeToken(desc, length, offset, "\""))
return false;
for (int i = offset; i < length; i++)
{
if (desc[i] == '"')
{
outName = (uint32_t)murMurHashBytes32((void *)(desc + offset), i - offset);
#pragma clang diagnostic push
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 180500
#pragma clang diagnostic ignored "-Wvla-cxx-extension"
#endif
char buf[i - offset + 1];
#pragma clang diagnostic pop
memcpy(buf, desc + offset, i - offset);
buf[i - offset] = 0;
int newOffset = i + 1;
if (consumeToken(desc, length, newOffset, "b"))
{
int fieldLength = 0;
if (consumeNumber(desc, length, newOffset, fieldLength))
{
offset = newOffset;
outBitSize = fieldLength;
//NSLog(@"%s", buf);
return true;
}
else
return false;
}
else if (consumeToken(desc, length, newOffset, "I"))
{
offset = newOffset;
outBitSize = sizeof(unsigned int) * 8;
//NSLog(@"%s", buf);
return true;
}
else
return false;
break;
}
}
return false;
}
static int freedomBitfieldOffsetInBits(const char *desc, uint32_t name)
{
size_t length = strlen(desc);
int offset = 0;
if (!consumeToken(desc, (int)length, offset, "{?="))
return -1;
int currentBitOffset = 0;
while (true)
{
uint32_t fieldName = 0;
int bitLength = 0;
if (consumeField(desc, (int)length, offset, fieldName, bitLength))
{
if (fieldName == name)
return currentBitOffset;
currentBitOffset += bitLength;
}
else
break;
}
return -1;
}
static void freedomDumpBitfieldsByDescription(void *address, const char *desc)
{
size_t length = strlen(desc);
int offset = 0;
if (!consumeToken(desc, (int)length, offset, "{?="))
return;
int currentBitOffset = 0;
while (true)
{
uint32_t fieldName = 0;
int bitLength = 0;
if (consumeField(desc, (int)length, offset, fieldName, bitLength))
{
if (bitLength == 1)
{
uint8_t *value = ((uint8_t *)address) + currentBitOffset / 8 + (currentBitOffset % 8 == 0 ? 0 : 1);
NSLog(@" : %d", ((*value) & ((uint8_t)(1 << (8 - currentBitOffset % 8)))) != 0 ? 1 : 0);
}
currentBitOffset += bitLength;
}
else
break;
}
}
FreedomBitfield freedomIvarBitOffset(Class targetClass, uint32_t fieldName, uint32_t bitfieldName)
{
FreedomBitfield result = {.offset = -1, .bit = -1 };
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(targetClass, &count);
if (ivarList != NULL)
{
for (unsigned int i = 0; i < count; i++)
{
const char *ivarName = ivar_getName(ivarList[i]);
uint32_t hashName = (uint32_t)murMurHashBytes32((void *)ivarName, (int)strlen(ivarName));
//NSLog(@"%s -> 0x%x", ivarName, hashName);
if (hashName == fieldName)
{
ptrdiff_t ivarOffset = ivar_getOffset(ivarList[i]);
int bitOffset = freedomBitfieldOffsetInBits(ivar_getTypeEncoding(ivarList[i]), bitfieldName);
if (bitOffset >= 0)
{
result.offset = ivarOffset + bitOffset / 8 + (bitOffset % 8 == 0 ? 0 : 1);
result.bit = bitOffset % 8;
}
}
}
free(ivarList);
}
return result;
}
FreedomBitfield freedomIvarBitOffset2(Class targetClass, uint32_t fieldName, uint32_t bitfieldName)
{
FreedomBitfield result = {.offset = -1, .bit = -1 };
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(targetClass, &count);
if (ivarList != NULL)
{
for (unsigned int i = 0; i < count; i++)
{
const char *ivarName = ivar_getName(ivarList[i]);
uint32_t hashName = (uint32_t)murMurHashBytes32((void *)ivarName, (int)strlen(ivarName));
//NSLog(@"%s -> 0x%x", ivarName, hashName);
if (hashName == fieldName)
{
ptrdiff_t ivarOffset = ivar_getOffset(ivarList[i]);
int bitOffset = freedomBitfieldOffsetInBits(ivar_getTypeEncoding(ivarList[i]), bitfieldName);
if (bitOffset >= 0)
{
result.offset = ivarOffset + bitOffset / 8;
result.bit = bitOffset % 8;
}
}
}
free(ivarList);
}
return result;
}
void freedomSetBitfield(void *object, FreedomBitfield bitfield, int value)
{
if (object == nil || bitfield.offset < 0 || bitfield.bit < 0)
return;
uint8_t *bytePtr = (((uint8_t *)object) + bitfield.offset);
if (value != 0)
*bytePtr = (*bytePtr) | ((uint8_t)(1 << (bitfield.bit)));
else
*bytePtr = (*bytePtr) & ((uint8_t)~(1 << (bitfield.bit)));
}
int freedomGetBitfield(void *object, FreedomBitfield bitfield)
{
if (object == nil || bitfield.offset < 0 || bitfield.bit < 0)
return 0;
uint8_t *bytePtr = (((uint8_t *)object) + bitfield.offset);
return ((*bytePtr) & ((uint8_t)(1 << (bitfield.bit)))) != 0;
}
void freedomDumpBitfields(Class targetClass, void *object, uint32_t fieldName)
{
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(targetClass, &count);
if (ivarList != NULL)
{
for (unsigned int i = 0; i < count; i++)
{
const char *ivarName = ivar_getName(ivarList[i]);
uint32_t hashName = (uint32_t)murMurHashBytes32((void *)ivarName, (int)strlen(ivarName));
if (hashName == fieldName)
{
ptrdiff_t ivarOffset = ivar_getOffset(ivarList[i]);
freedomDumpBitfieldsByDescription(((uint8_t *)object) + ivarOffset, ivar_getTypeEncoding(ivarList[i]));
}
}
free(ivarList);
}
}
IMP freedomNativeImpl(Class targetClass, SEL selector)
{
return class_getMethodImplementation(class_getSuperclass(targetClass), selector);
}
Class adjustDecoratedClass(Class targetClass)
{
__unsafe_unretained Class decoratedClass = Nil;
__unsafe_unretained Class currentClass = targetClass;
while (currentClass != Nil)
{
__unsafe_unretained Class currentDecoratedClass = objc_getAssociatedObject(currentClass, freedomDecoratedClass);
if (currentDecoratedClass != Nil)
{
decoratedClass = currentDecoratedClass;
if (currentClass != targetClass)
{
const char *currentClassName = class_getName(targetClass);
Class adjustedDecoratedClass = objc_allocateClassPair(targetClass, [[NSString alloc] initWithFormat:@"%s_Super%" PRIx32 "", class_getName(decoratedClass), murMurHashBytes32((void *)currentClassName, (int)strlen(currentClassName))].UTF8String, 0);
unsigned int methodCount = 0;
Method *methodList = class_copyMethodList(decoratedClass, &methodCount);
for (unsigned int i = 0; i < methodCount; i++)
{
class_addMethod(adjustedDecoratedClass, method_getName(methodList[i]), method_getImplementation(methodList[i]), method_getTypeEncoding(methodList[i]));
}
free(methodList);
objc_registerClassPair(adjustedDecoratedClass);
decoratedClass = adjustedDecoratedClass;
objc_setAssociatedObject(targetClass, freedomDecoratedClass, adjustedDecoratedClass, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
break;
}
currentClass = class_getSuperclass(currentClass);
}
return decoratedClass;
}
id freedomAllocImpl(id self, SEL _cmd)
{
static id (*nativeImp)(id, SEL) = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
nativeImp = (id (*)(id, SEL))class_getMethodImplementation(object_getClass([NSObject class]), @selector(alloc));
});
id result = nativeImp(self, _cmd);
Class decoratedClass = adjustDecoratedClass(self);
if (decoratedClass != Nil)
object_setClass(result, decoratedClass);
return result;
}
static SEL freedomFindMethodName(uint32_t name, Class className, Method *methodList, unsigned int methodCount, const char **methodTypeEncoding)
{
for (unsigned int j = 0; j < methodCount; j++)
{
SEL methodName = method_getName(methodList[j]);
const char *selectorName = sel_getName(methodName);
uint32_t methodHashName = (uint32_t)murMurHashBytes32((void *)selectorName, (int)strlen(selectorName));
//NSLog(@"0x%" PRIx32 " -> %s", methodHashName, selectorName);
if (methodHashName == name)
{
if (methodTypeEncoding != NULL)
*methodTypeEncoding = method_getTypeEncoding(methodList[j]);
return methodName;
}
}
Class superClass = class_getSuperclass(className);
if (superClass != NULL)
{
unsigned int superClassMethodCount = 0;
Method *superClassMethodList = class_copyMethodList(superClass, &superClassMethodCount);
SEL result = freedomFindMethodName(name, superClass, superClassMethodList, superClassMethodCount, methodTypeEncoding);
free(superClassMethodList);
return result;
}
return NULL;
}
void freedomClassAutoDecorate(uint32_t name, __unused FreedomDecoration *classDecorations, __unused int numClassDecorations, FreedomDecoration *instanceDecorations, int numInstanceDecorations)
{
__unsafe_unretained Class className = freedomClass(name);
freedomClassAutoDecorateExplicit(className, name, classDecorations, numClassDecorations, instanceDecorations, numInstanceDecorations);
}
void freedomClassAutoDecorateExplicit(Class className, uint32_t name, __unused FreedomDecoration *classDecorations, __unused int numClassDecorations, FreedomDecoration *instanceDecorations, int numInstanceDecorations)
{
if (className != nil)
{
if (name == 0)
{
const char *string = [NSStringFromClass(className) UTF8String];
name = (uint32_t)murMurHashBytes32((void *)string, (int)strlen(string));
}
Class decoratedClass = objc_allocateClassPair(className, [[NSString alloc] initWithFormat:@"Decorated%" PRIx32 "", name].UTF8String, 0);
unsigned int methodCount = 0;
Method *methodList = class_copyMethodList(className, &methodCount);
for (int i = 0; i < numInstanceDecorations; i++)
{
if (instanceDecorations[i].name != 0)
{
const char *methodTypeEncoding = NULL;
SEL methodName = freedomFindMethodName(instanceDecorations[i].name, className, methodList, methodCount, &methodTypeEncoding);
if (methodName != NULL)
class_addMethod(decoratedClass, methodName, instanceDecorations[i].imp, methodTypeEncoding);
else
{
#ifdef DEBUG
NSLog(@"[Freedom coulnd'n find method named 0x%" PRIx32 "]", instanceDecorations[i].name);
#endif
}
}
else if (instanceDecorations[i].newIdentifier.string != NULL && instanceDecorations[i].newEncoding.string != NULL)
{
char *identifier = copyFreedomIdentifierValue(instanceDecorations[i].newIdentifier);
char *encoding = copyFreedomIdentifierValue(instanceDecorations[i].newEncoding);
SEL selector = sel_getUid(identifier);
if (!sel_isMapped(selector))
selector = sel_registerName(identifier);
class_addMethod(decoratedClass, selector, instanceDecorations[i].imp, encoding);
free(identifier);
free(encoding);
}
else
{
#ifdef DEBUG
NSLog(@"[Freedom invalid decoration description]");
#endif
}
}
free(methodList);
objc_registerClassPair(decoratedClass);
objc_setAssociatedObject(className, freedomDecoratedClass, decoratedClass, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
Class metaClass = object_getClass(className);
class_addMethod(metaClass, @selector(alloc), (IMP)&freedomAllocImpl, "@:@");
}
else
{
#ifdef DEBUG
NSLog(@"[Freedom coulnd'n find class named 0x%" PRIx32 "]", name);
assert(false);
#endif
}
}
IMP freedomImpl(id target, uint32_t name, SEL *selector)
{
if (target == nil)
return NULL;
Class targetClass = object_getClass(target);
return freedomImplInstancesOfClass(targetClass, name, selector);
}
IMP freedomImplInstancesOfClass(Class targetClass, uint32_t name, SEL *selector) {
if (targetClass == NULL)
return NULL;
unsigned int methodCount = 0;
Method *methodList = class_copyMethodList(targetClass, &methodCount);
SEL methodName = freedomFindMethodName(name, targetClass, methodList, methodCount, NULL);
free(methodList);
if (methodName != NULL)
{
if (selector != NULL)
*selector = methodName;
IMP result = class_getMethodImplementation(targetClass, methodName);
if (result == NULL)
{
#ifdef DEBUG
NSLog(@"[Freedom coulnd'n find method named 0x%" PRIx32 "]", name);
#endif
}
return result;
}
return NULL;
}
void freedomInit()
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
freedom_getClassList = &objc_getClassList;
freedom_class_getName = &class_getName;
});
}
bool freedomInitialized()
{
return freedom_initialized;
}
@@ -0,0 +1,288 @@
#import <LegacyComponents/FreedomUIKit.h>
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/Freedom.h>
#import <objc/runtime.h>
#import <objc/message.h>
#import <LegacyComponents/TGHacks.h>
#import <LegacyComponents/TGNavigationController.h>
#define DEBUG_KEYBOARD_QUEUE false
void freedomUIKitInit2(); // iOS 6
void freedomUIKitInit3(); // Notification Center Hook
#if defined(DEBUG) && DEBUG_KEYBOARD_QUEUE
void freedomUIKitInit4(); // Keyboard Queue Debug
#endif
void freedomUIKitInit()
{
freedomUIKitInit2();
freedomUIKitInit3();
#if defined(DEBUG) && DEBUG_KEYBOARD_QUEUE
freedomUIKitInit4();
#endif
}
#pragma mark -
static int freedomUIKit_decorated2(__unused id self, __unused SEL _cmd)
{
return 0;
}
void freedomUIKitInit2()
{
if (iosMajorVersion() == 6)
{
FreedomDecoration instanceDecorations[] = {
{ .name = 0,
.imp = (IMP)&freedomUIKit_decorated2,
.newIdentifier = (FreedomIdentifier){ .string = "3066a13b3e6b", .key = 0x52d50551U },
.newEncoding = (FreedomIdentifier){ .string = "1a7291", .key = 0x3ab3273U }
}
};
freedomClassAutoDecorate(0x1468e61aU, NULL, 0, instanceDecorations, sizeof(instanceDecorations) / sizeof(instanceDecorations[0]));
}
}
#pragma mark -
static bool test3 = false;
static bool test3_1 = false;
@implementation FFNotificationCenter
static bool (^shouldRotateBlock)() = nil;
+ (void)setShouldRotateBlock:(bool (^)())block
{
shouldRotateBlock = [block copy];
}
- (void)postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo
{
bool clearFlag = false;
if ([aName isEqualToString:UIDeviceOrientationDidChangeNotification])
{
clearFlag = true;
test3 = true;
test3_1 = true;
}
if ([aName isEqualToString:UIDeviceOrientationDidChangeNotification])
{
if (shouldRotateBlock == nil || shouldRotateBlock())
[super postNotificationName:aName object:anObject userInfo:aUserInfo];
}
else
[super postNotificationName:aName object:anObject userInfo:aUserInfo];
if (clearFlag)
test3 = false;
}
@end
bool freedomUIKitTest3()
{
return test3;
}
bool freedomUIKitTest3_1()
{
bool value = test3_1;
test3_1 = false;
return value;
}
void freedomUIKitInit3()
{
object_setClass([NSNotificationCenter defaultCenter], [FFNotificationCenter class]);
}
#pragma mark -
static bool test4 = false;
static bool test4_1 = false;
@interface TGHelperQueue : NSProxy
{
id _target;
}
@end
@implementation TGHelperQueue
- (instancetype)initWithTargetQueue:(id)target
{
if (self != nil)
{
_target = target;
}
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
return [_target methodSignatureForSelector:selector];
}
- (void)forwardInvocation:(NSInvocation *)__unused invocation
{
}
- (id)forwardingTargetForSelector:(SEL)selector
{
static char *name = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
name = copyFreedomIdentifierValue((FreedomIdentifier){ .string = "536772a971686fb4484777b1706768b6574769b8626f75b4576e7eb9", .key = 0xdd1b0624U });
});
if (!strcmp(sel_getName(selector), name))
{
if (test4 || test4_1)
{
return nil;
}
}
return _target;
}
- (void)doWork
{
}
@end
static UIView *freedomUIKitFindView(UIView *view)
{
if (view == nil)
return nil;
if (object_getClass(view) == freedomClass(0x9cb128e7U))
return view;
for (UIView *subview in view.subviews)
{
UIView *result = freedomUIKitFindView(subview);
if (result != nil)
return result;
}
return nil;
}
void freedomUIKitTest4(dispatch_block_t block)
{
if (iosMajorVersion() < 7 || iosMajorVersion() > 7 || (iosMajorVersion() == 7 && iosMinorVersion() >= 1))
{
if (block != nil)
block();
}
else
{
UIView *view = freedomUIKitFindView([TGHacks applicationKeyboardWindow]);
if (view != nil)
{
static ptrdiff_t queueOffset = -1;
static bool queueInitialized = false;
if (!queueInitialized)
{
queueInitialized = true;
queueOffset = freedomIvarOffset(object_getClass(view), 0xba913cbU);
}
if (queueOffset >= 0)
{
__strong NSObject **queue = ((__strong NSObject **)(void *)(((uint8_t *)(__bridge void *)view) + queueOffset));
if (*queue != nil)
{
if (object_getClass(*queue) != [TGHelperQueue class])
{
TGHelperQueue *helper = [[TGHelperQueue alloc] initWithTargetQueue:*queue];
*queue = (NSObject *)helper;
}
}
}
}
bool previousTest4 = test4;
test4 = true;
if (block != nil)
block();
test4 = previousTest4;
}
}
void freedomUIKitTest4_1()
{
test4_1 = true;
dispatch_async(dispatch_get_main_queue(), ^
{
test4_1 = false;
});
}
#if defined(DEBUG) && DEBUG_KEYBOARD_QUEUE
void freedomUIKit_decorated4_1(id self, SEL _cmd, id arg1)
{
static void (*nativeImpl)(id, SEL, id) = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
nativeImpl = (void *)freedomNativeImpl([self class], _cmd);
});
if (nativeImpl != NULL)
nativeImpl(self, _cmd, arg1);
TGLegacyLog(@"invoke %@", NSStringFromSelector(_cmd));
}
void freedomUIKit_decorated4_2(id self, SEL _cmd)
{
static void (*nativeImpl)(id, SEL) = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
nativeImpl = (void *)freedomNativeImpl([self class], _cmd);
});
if (nativeImpl != NULL)
nativeImpl(self, _cmd);
TGLegacyLog(@"invoke %@", NSStringFromSelector(_cmd));
}
void freedomUIKitInit4()
{
FreedomDecoration instanceDecorations[] = {
{ .name = 0xbb6dbb9eU, //addTask:
.imp = (IMP)&freedomUIKit_decorated4_1,
.newIdentifier = FreedomIdentifierEmpty,
.newEncoding = FreedomIdentifierEmpty
},
{ .name = 0x757d9b1cU, //waitUntilAllTasksAreFinished
.imp = (IMP)&freedomUIKit_decorated4_2,
.newIdentifier = FreedomIdentifierEmpty,
.newEncoding = FreedomIdentifierEmpty
}
};
freedomClassAutoDecorate(0xfed2643dU, NULL, 0, instanceDecorations, sizeof(instanceDecorations) / sizeof(instanceDecorations[0]));
}
#endif
+42
View File
@@ -0,0 +1,42 @@
// This is Jeff LaMarche's GLProgram OpenGL shader wrapper class from his OpenGL ES 2.0 book.
// A description of this can be found at his page on the topic:
// http://iphonedevelopment.blogspot.com/2010/11/opengl-es-20-for-ios-chapter-4.html
// I've extended this to be able to take programs as NSStrings in addition to files, for baked-in shaders
#import <Foundation/Foundation.h>
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
#else
#import <OpenGL/OpenGL.h>
#import <OpenGL/gl.h>
#endif
@interface GLProgram : NSObject
{
NSMutableArray *attributes;
NSMutableArray *uniforms;
GLuint program,
vertShader,
fragShader;
}
@property(readwrite, nonatomic) BOOL initialized;
- (id)initWithVertexShaderString:(NSString *)vShaderString
fragmentShaderString:(NSString *)fShaderString;
- (id)initWithVertexShaderString:(NSString *)vShaderString
fragmentShaderFilename:(NSString *)fShaderFilename;
- (id)initWithVertexShaderFilename:(NSString *)vShaderFilename
fragmentShaderFilename:(NSString *)fShaderFilename;
- (void)addAttribute:(NSString *)attributeName;
- (GLuint)attributeIndex:(NSString *)attributeName;
- (GLuint)uniformIndex:(NSString *)uniformName;
- (BOOL)link;
- (void)use;
- (NSString *)vertexShaderLog;
- (NSString *)fragmentShaderLog;
- (NSString *)programLog;
- (void)validate;
@end
+280
View File
@@ -0,0 +1,280 @@
// This is Jeff LaMarche's GLProgram OpenGL shader wrapper class from his OpenGL ES 2.0 book.
// A description of this can be found at his page on the topic:
// http://iphonedevelopment.blogspot.com/2010/11/opengl-es-20-for-ios-chapter-4.html
#import "LegacyComponentsInternal.h"
#import "GLProgram.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// START:typedefs
#pragma mark Function Pointer Definitions
typedef void (*GLInfoFunction)(GLuint program,
GLenum pname,
GLint* params);
typedef void (*GLLogFunction) (GLuint program,
GLsizei bufsize,
GLsizei* length,
GLchar* infolog);
// END:typedefs
#pragma mark -
#pragma mark Private Extension Method Declaration
// START:extension
@interface GLProgram()
- (BOOL)compileShader:(GLuint *)shader
type:(GLenum)type
string:(NSString *)shaderString;
- (NSString *)logForOpenGLObject:(GLuint)object
infoCallback:(GLInfoFunction)infoFunc
logFunc:(GLLogFunction)logFunc;
@end
// END:extension
#pragma mark -
@implementation GLProgram
// START:init
@synthesize initialized = _initialized;
- (id)initWithVertexShaderString:(NSString *)vShaderString
fragmentShaderString:(NSString *)fShaderString
{
if ((self = [super init]))
{
_initialized = NO;
attributes = [[NSMutableArray alloc] init];
uniforms = [[NSMutableArray alloc] init];
program = glCreateProgram();
if (![self compileShader:&vertShader
type:GL_VERTEX_SHADER
string:vShaderString])
NSLog(@"Failed to compile vertex shader");
// Create and compile fragment shader
if (![self compileShader:&fragShader
type:GL_FRAGMENT_SHADER
string:fShaderString])
NSLog(@"Failed to compile fragment shader");
glAttachShader(program, vertShader);
glAttachShader(program, fragShader);
}
return self;
}
- (id)initWithVertexShaderString:(NSString *)vShaderString
fragmentShaderFilename:(NSString *)fShaderFilename
{
NSString *fragShaderPathname = TGComponentsPathForResource(fShaderFilename, @"fsh");
NSString *fragmentShaderString = [NSString stringWithContentsOfFile:fragShaderPathname encoding:NSUTF8StringEncoding error:nil];
if ((self = [self initWithVertexShaderString:vShaderString fragmentShaderString:fragmentShaderString]))
{
}
return self;
}
- (id)initWithVertexShaderFilename:(NSString *)vShaderFilename
fragmentShaderFilename:(NSString *)fShaderFilename
{
NSString *vertShaderPathname = TGComponentsPathForResource(vShaderFilename, @"vsh");
NSString *vertexShaderString = [NSString stringWithContentsOfFile:vertShaderPathname encoding:NSUTF8StringEncoding error:nil];
NSString *fragShaderPathname = TGComponentsPathForResource(fShaderFilename, @"fsh");
NSString *fragmentShaderString = [NSString stringWithContentsOfFile:fragShaderPathname encoding:NSUTF8StringEncoding error:nil];
if ((self = [self initWithVertexShaderString:vertexShaderString fragmentShaderString:fragmentShaderString]))
{
}
return self;
}
// END:init
// START:compile
- (BOOL)compileShader:(GLuint *)shader
type:(GLenum)type
string:(NSString *)shaderString
{
// CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
GLint status;
const GLchar *source;
source =
(GLchar *)[shaderString UTF8String];
if (!source)
{
NSLog(@"Failed to load vertex shader");
return NO;
}
*shader = glCreateShader(type);
glShaderSource(*shader, 1, &source, NULL);
glCompileShader(*shader);
glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
if (status != GL_TRUE)
{
GLint logLength;
glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0)
{
GLchar *log = (GLchar *)malloc(logLength);
glGetShaderInfoLog(*shader, logLength, &logLength, log);
NSLog(@"Shader compile log:\n%s", log);
free(log);
}
}
// CFAbsoluteTime linkTime = (CFAbsoluteTimeGetCurrent() - startTime);
// NSLog(@"Compiled in %f ms", linkTime * 1000.0);
return status == GL_TRUE;
}
// END:compile
#pragma mark -
// START:addattribute
- (void)addAttribute:(NSString *)attributeName
{
if (![attributes containsObject:attributeName])
{
[attributes addObject:attributeName];
glBindAttribLocation(program,
(GLuint)[attributes indexOfObject:attributeName],
[attributeName UTF8String]);
}
}
// END:addattribute
// START:indexmethods
- (GLuint)attributeIndex:(NSString *)attributeName
{
return (GLuint)[attributes indexOfObject:attributeName];
}
- (GLuint)uniformIndex:(NSString *)uniformName
{
return glGetUniformLocation(program, [uniformName UTF8String]);
}
// END:indexmethods
#pragma mark -
// START:link
- (BOOL)link
{
// CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
GLint status;
glLinkProgram(program);
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE)
return NO;
if (vertShader)
{
glDeleteShader(vertShader);
vertShader = 0;
}
if (fragShader)
{
glDeleteShader(fragShader);
fragShader = 0;
}
self.initialized = YES;
// CFAbsoluteTime linkTime = (CFAbsoluteTimeGetCurrent() - startTime);
// NSLog(@"Linked in %f ms", linkTime * 1000.0);
return YES;
}
// END:link
// START:use
- (void)use
{
glUseProgram(program);
}
// END:use
#pragma mark -
// START:privatelog
- (NSString *)logForOpenGLObject:(GLuint)object
infoCallback:(GLInfoFunction)infoFunc
logFunc:(GLLogFunction)logFunc
{
GLint logLength = 0, charsWritten = 0;
infoFunc(object, GL_INFO_LOG_LENGTH, &logLength);
if (logLength < 1)
return nil;
char *logBytes = malloc(logLength);
logFunc(object, logLength, &charsWritten, logBytes);
NSString *log = [[NSString alloc] initWithBytes:logBytes
length:logLength
encoding:NSUTF8StringEncoding];
free(logBytes);
return log;
}
// END:privatelog
// START:log
- (NSString *)vertexShaderLog
{
return [self logForOpenGLObject:vertShader
infoCallback:(GLInfoFunction)&glGetProgramiv
logFunc:(GLLogFunction)&glGetProgramInfoLog];
}
- (NSString *)fragmentShaderLog
{
return [self logForOpenGLObject:fragShader
infoCallback:(GLInfoFunction)&glGetProgramiv
logFunc:(GLLogFunction)&glGetProgramInfoLog];
}
- (NSString *)programLog
{
return [self logForOpenGLObject:program
infoCallback:(GLInfoFunction)&glGetProgramiv
logFunc:(GLLogFunction)&glGetProgramInfoLog];
}
// END:log
- (void)validate
{
GLint logLength;
glValidateProgram(program);
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0)
{
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(program, logLength, &logLength, log);
NSLog(@"Program validate log:\n%s", log);
free(log);
}
}
#pragma mark -
// START:dealloc
- (void)dealloc
{
if (vertShader)
glDeleteShader(vertShader);
if (fragShader)
glDeleteShader(fragShader);
if (program)
glDeleteProgram(program);
}
// END:dealloc
@end
#pragma clang diagnostic pop
+14
View File
@@ -0,0 +1,14 @@
#import "GLProgram.h"
// Base classes
#import "GPUImageContext.h"
#import "GPUImageOutput.h"
#import "GPUImageFilterGroup.h"
#import "GPUImageFramebuffer.h"
#import "GPUImageFramebufferCache.h"
// Filters
#import "GPUImageFilter.h"
#import "GPUImageTwoInputFilter.h"
#import "GPUImageThreeInputFilter.h"
#import "GPUImageGaussianBlurFilter.h"
+63
View File
@@ -0,0 +1,63 @@
#import "GLProgram.h"
#import "GPUImageFramebuffer.h"
#import "GPUImageFramebufferCache.h"
#define GPUImageRotationSwapsWidthAndHeight(rotation) ((rotation) == kGPUImageRotateLeft || (rotation) == kGPUImageRotateRight || (rotation) == kGPUImageRotateRightFlipVertical || (rotation) == kGPUImageRotateRightFlipHorizontal)
typedef enum { kGPUImageNoRotation, kGPUImageRotateLeft, kGPUImageRotateRight, kGPUImageFlipVertical, kGPUImageFlipHorizonal, kGPUImageRotateRightFlipVertical, kGPUImageRotateRightFlipHorizontal, kGPUImageRotate180, kGPUImageRotate180FlipHorizontal } GPUImageRotationMode;
@interface GPUImageContext : NSObject
@property(readonly, nonatomic) dispatch_queue_t contextQueue;
@property(readwrite, retain, nonatomic) GLProgram *currentShaderProgram;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@property(readonly, retain, nonatomic) EAGLContext *context;
#pragma clang diagnostic pop
@property(readonly, nonatomic) CVOpenGLESTextureCacheRef coreVideoTextureCache;
@property(readonly, nonatomic) GPUImageFramebufferCache *framebufferCache;
+ (void *)contextKey;
+ (GPUImageContext *)sharedImageProcessingContext;
+ (dispatch_queue_t)sharedContextQueue;
+ (GPUImageFramebufferCache *)sharedFramebufferCache;
+ (void)useImageProcessingContext;
- (void)useAsCurrentContext;
+ (void)setActiveShaderProgram:(GLProgram *)shaderProgram;
- (void)setContextShaderProgram:(GLProgram *)shaderProgram;
+ (GLint)maximumTextureSizeForThisDevice;
+ (GLint)maximumTextureUnitsForThisDevice;
+ (GLint)maximumVaryingVectorsForThisDevice;
+ (BOOL)deviceSupportsOpenGLESExtension:(NSString *)extension;
+ (BOOL)deviceSupportsRedTextures;
+ (BOOL)deviceSupportsFramebufferReads;
+ (CGSize)sizeThatFitsWithinATextureForSize:(CGSize)inputSize;
- (void)presentBufferForDisplay;
- (GLProgram *)programForVertexShaderString:(NSString *)vertexShaderString fragmentShaderString:(NSString *)fragmentShaderString;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (void)useSharegroup:(EAGLSharegroup *)sharegroup;
#pragma clang diagnostic pop
// Manage fast texture upload
+ (BOOL)supportsFastTextureUpload;
@end
@protocol GPUImageInput <NSObject>
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
- (NSInteger)nextAvailableTextureIndex;
- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex;
- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex;
- (CGSize)maximumOutputSize;
- (void)endProcessing;
- (BOOL)shouldIgnoreUpdatesToThisTarget;
- (BOOL)enabled;
- (BOOL)wantsMonochromeInput;
- (void)setCurrentlyReceivingMonochromeInput:(BOOL)newValue;
@end
+318
View File
@@ -0,0 +1,318 @@
#import "GPUImageContext.h"
#import <OpenGLES/EAGLDrawable.h>
#import <AVFoundation/AVFoundation.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#define MAXSHADERPROGRAMSALLOWEDINCACHE 40
@interface GPUImageContext()
{
NSMutableDictionary *shaderProgramCache;
NSMutableArray *shaderProgramUsageHistory;
EAGLSharegroup *_sharegroup;
}
@end
@implementation GPUImageContext
@synthesize context = _context;
@synthesize currentShaderProgram = _currentShaderProgram;
@synthesize contextQueue = _contextQueue;
@synthesize coreVideoTextureCache = _coreVideoTextureCache;
@synthesize framebufferCache = _framebufferCache;
static void *openGLESContextQueueKey;
- (id)init
{
if (!(self = [super init]))
{
return nil;
}
openGLESContextQueueKey = &openGLESContextQueueKey;
_contextQueue = dispatch_queue_create("com.sunsetlakesoftware.GPUImage.openGLESContextQueue", NULL);
#if OS_OBJECT_USE_OBJC
dispatch_queue_set_specific(_contextQueue, openGLESContextQueueKey, (__bridge void *)self, NULL);
#endif
shaderProgramCache = [[NSMutableDictionary alloc] init];
shaderProgramUsageHistory = [[NSMutableArray alloc] init];
return self;
}
+ (void *)contextKey {
return openGLESContextQueueKey;
}
// Based on Colin Wheeler's example here: http://cocoasamurai.blogspot.com/2011/04/singletons-your-doing-them-wrong.html
+ (GPUImageContext *)sharedImageProcessingContext
{
static dispatch_once_t pred;
static GPUImageContext *sharedImageProcessingContext = nil;
dispatch_once(&pred, ^{
sharedImageProcessingContext = [[[self class] alloc] init];
});
return sharedImageProcessingContext;
}
+ (dispatch_queue_t)sharedContextQueue
{
return [[self sharedImageProcessingContext] contextQueue];
}
+ (GPUImageFramebufferCache *)sharedFramebufferCache
{
return [[self sharedImageProcessingContext] framebufferCache];
}
+ (void)useImageProcessingContext
{
[[GPUImageContext sharedImageProcessingContext] useAsCurrentContext];
}
- (void)useAsCurrentContext
{
EAGLContext *imageProcessingContext = [self context];
if ([EAGLContext currentContext] != imageProcessingContext)
{
[EAGLContext setCurrentContext:imageProcessingContext];
}
}
+ (void)setActiveShaderProgram:(GLProgram *)shaderProgram
{
GPUImageContext *sharedContext = [GPUImageContext sharedImageProcessingContext];
[sharedContext setContextShaderProgram:shaderProgram];
}
- (void)setContextShaderProgram:(GLProgram *)shaderProgram
{
EAGLContext *imageProcessingContext = [self context];
if ([EAGLContext currentContext] != imageProcessingContext)
{
[EAGLContext setCurrentContext:imageProcessingContext];
}
if (self.currentShaderProgram != shaderProgram)
{
self.currentShaderProgram = shaderProgram;
[shaderProgram use];
}
}
+ (GLint)maximumTextureSizeForThisDevice
{
static dispatch_once_t pred;
static GLint maxTextureSize = 0;
dispatch_once(&pred, ^{
[self useImageProcessingContext];
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
});
return maxTextureSize;
}
+ (GLint)maximumTextureUnitsForThisDevice
{
static dispatch_once_t pred;
static GLint maxTextureUnits = 0;
dispatch_once(&pred, ^{
[self useImageProcessingContext];
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
});
return maxTextureUnits;
}
+ (GLint)maximumVaryingVectorsForThisDevice
{
static dispatch_once_t pred;
static GLint maxVaryingVectors = 0;
dispatch_once(&pred, ^{
[self useImageProcessingContext];
glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryingVectors);
});
return maxVaryingVectors;
}
+ (BOOL)deviceSupportsOpenGLESExtension:(NSString *)extension
{
static dispatch_once_t pred;
static NSArray *extensionNames = nil;
// Cache extensions for later quick reference, since this won't change for a given device
dispatch_once(&pred, ^{
[GPUImageContext useImageProcessingContext];
NSString *extensionsString = [NSString stringWithCString:(const char *)glGetString(GL_EXTENSIONS) encoding:NSASCIIStringEncoding];
extensionNames = [extensionsString componentsSeparatedByString:@" "];
});
return [extensionNames containsObject:extension];
}
// http://www.khronos.org/registry/gles/extensions/EXT/EXT_texture_rg.txt
+ (BOOL)deviceSupportsRedTextures
{
static dispatch_once_t pred;
static BOOL supportsRedTextures = NO;
dispatch_once(&pred, ^{
supportsRedTextures = [GPUImageContext deviceSupportsOpenGLESExtension:@"GL_EXT_texture_rg"];
});
return supportsRedTextures;
}
+ (BOOL)deviceSupportsFramebufferReads
{
static dispatch_once_t pred;
static BOOL supportsFramebufferReads = NO;
dispatch_once(&pred, ^{
supportsFramebufferReads = [GPUImageContext deviceSupportsOpenGLESExtension:@"GL_EXT_shader_framebuffer_fetch"];
});
return supportsFramebufferReads;
}
+ (CGSize)sizeThatFitsWithinATextureForSize:(CGSize)inputSize
{
GLint maxTextureSize = [self maximumTextureSizeForThisDevice];
if ( (inputSize.width < maxTextureSize) && (inputSize.height < maxTextureSize) )
{
return inputSize;
}
CGSize adjustedSize;
if (inputSize.width > inputSize.height)
{
adjustedSize.width = (CGFloat)maxTextureSize;
adjustedSize.height = ((CGFloat)maxTextureSize / inputSize.width) * inputSize.height;
}
else
{
adjustedSize.height = (CGFloat)maxTextureSize;
adjustedSize.width = ((CGFloat)maxTextureSize / inputSize.height) * inputSize.width;
}
return CGSizeMake(floor(adjustedSize.width), floor(adjustedSize.height));
}
- (void)presentBufferForDisplay
{
[self.context presentRenderbuffer:GL_RENDERBUFFER];
}
- (GLProgram *)programForVertexShaderString:(NSString *)vertexShaderString fragmentShaderString:(NSString *)fragmentShaderString
{
NSString *lookupKeyForShaderProgram = [NSString stringWithFormat:@"V: %@ - F: %@", vertexShaderString, fragmentShaderString];
GLProgram *programFromCache = [shaderProgramCache objectForKey:lookupKeyForShaderProgram];
if (programFromCache == nil)
{
programFromCache = [[GLProgram alloc] initWithVertexShaderString:vertexShaderString fragmentShaderString:fragmentShaderString];
[shaderProgramCache setObject:programFromCache forKey:lookupKeyForShaderProgram];
// [shaderProgramUsageHistory addObject:lookupKeyForShaderProgram];
// if ([shaderProgramUsageHistory count] >= MAXSHADERPROGRAMSALLOWEDINCACHE)
// {
// for (NSUInteger currentShaderProgramRemovedFromCache = 0; currentShaderProgramRemovedFromCache < 10; currentShaderProgramRemovedFromCache++)
// {
// NSString *shaderProgramToRemoveFromCache = [shaderProgramUsageHistory objectAtIndex:0];
// [shaderProgramUsageHistory removeObjectAtIndex:0];
// [shaderProgramCache removeObjectForKey:shaderProgramToRemoveFromCache];
// }
// }
}
return programFromCache;
}
- (void)useSharegroup:(EAGLSharegroup *)sharegroup
{
NSAssert(_context == nil, @"Unable to use a share group when the context has already been created. Call this method before you use the context for the first time.");
_sharegroup = sharegroup;
}
- (EAGLContext *)createContext
{
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup:_sharegroup];
NSAssert(context != nil, @"Unable to create an OpenGL ES 2.0 context. The GPUImage framework requires OpenGL ES 2.0 support to work.");
return context;
}
#pragma mark -
#pragma mark Manage fast texture upload
+ (BOOL)supportsFastTextureUpload
{
#if TARGET_IPHONE_SIMULATOR
return NO;
#else
return true;
#endif
}
#pragma mark -
#pragma mark Accessors
- (EAGLContext *)context
{
if (_context == nil)
{
_context = [self createContext];
[EAGLContext setCurrentContext:_context];
// Set up a few global settings for the image processing pipeline
glDisable(GL_DEPTH_TEST);
}
return _context;
}
- (CVOpenGLESTextureCacheRef)coreVideoTextureCache
{
if (_coreVideoTextureCache == NULL)
{
#if defined(__IPHONE_6_0)
CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, [self context], NULL, &_coreVideoTextureCache);
#else
CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, (__bridge void *)[self context], NULL, &_coreVideoTextureCache);
#endif
if (err)
{
NSAssert(NO, @"Error at CVOpenGLESTextureCacheCreate %d", err);
}
}
return _coreVideoTextureCache;
}
- (GPUImageFramebufferCache *)framebufferCache
{
if (_framebufferCache == nil)
{
_framebufferCache = [[GPUImageFramebufferCache alloc] init];
}
return _framebufferCache;
}
@end
#pragma clang diagnostic pop
+15
View File
@@ -0,0 +1,15 @@
#import "GPUImageFilter.h"
@interface GPUImageCropFilter : GPUImageFilter
{
GLfloat cropTextureCoordinates[8];
}
// The crop region is the rectangle within the image to crop. It is normalized to a coordinate space from 0.0 to 1.0, with 0.0, 0.0 being the upper left corner of the image
@property(readwrite, nonatomic) CGRect cropRegion;
@property(readwrite, nonatomic) GPUImageRotationMode rotationMode;
// Initialization and teardown
- (id)initWithCropRegion:(CGRect)newCropRegion;
@end
+249
View File
@@ -0,0 +1,249 @@
#import "GPUImageCropFilter.h"
NSString *const kGPUImageCropFragmentShaderString = SHADER_STRING
(
varying highp vec2 texCoord;
uniform sampler2D sourceImage;
void main()
{
gl_FragColor = texture2D(sourceImage, texCoord);
}
);
@interface GPUImageCropFilter ()
- (void)calculateCropTextureCoordinates;
@end
@interface GPUImageCropFilter()
{
CGSize originallySuppliedInputSize;
}
@end
@implementation GPUImageCropFilter
@synthesize cropRegion = _cropRegion;
#pragma mark -
#pragma mark Initialization and teardown
- (id)initWithCropRegion:(CGRect)newCropRegion;
{
if (!(self = [super initWithFragmentShaderFromString:kGPUImageCropFragmentShaderString]))
{
return nil;
}
self.cropRegion = newCropRegion;
return self;
}
- (id)init;
{
if (!(self = [self initWithCropRegion:CGRectMake(0.0, 0.0, 1.0, 1.0)]))
{
return nil;
}
return self;
}
#pragma mark -
#pragma mark Rendering
- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex;
{
if (self.preventRendering)
{
return;
}
CGSize rotatedSize = [self rotatedSize:newSize forIndex:textureIndex];
originallySuppliedInputSize = rotatedSize;
CGSize scaledSize;
scaledSize.width = rotatedSize.width * _cropRegion.size.width;
scaledSize.height = rotatedSize.height * _cropRegion.size.height;
if (CGSizeEqualToSize(scaledSize, CGSizeZero))
{
inputTextureSize = scaledSize;
}
else if (!CGSizeEqualToSize(inputTextureSize, scaledSize))
{
inputTextureSize = scaledSize;
}
}
#pragma mark -
#pragma mark GPUImageInput
- (void)calculateCropTextureCoordinates;
{
CGFloat minX = _cropRegion.origin.x;
CGFloat minY = _cropRegion.origin.y;
CGFloat maxX = CGRectGetMaxX(_cropRegion);
CGFloat maxY = CGRectGetMaxY(_cropRegion);
switch(inputRotation)
{
case kGPUImageNoRotation: // Works
{
cropTextureCoordinates[0] = minX; // 0,0
cropTextureCoordinates[1] = minY;
cropTextureCoordinates[2] = maxX; // 1,0
cropTextureCoordinates[3] = minY;
cropTextureCoordinates[4] = minX; // 0,1
cropTextureCoordinates[5] = maxY;
cropTextureCoordinates[6] = maxX; // 1,1
cropTextureCoordinates[7] = maxY;
}; break;
case kGPUImageRotateLeft: // Fixed
{
cropTextureCoordinates[0] = maxY; // 1,0
cropTextureCoordinates[1] = 1.0 - maxX;
cropTextureCoordinates[2] = maxY; // 1,1
cropTextureCoordinates[3] = 1.0 - minX;
cropTextureCoordinates[4] = minY; // 0,0
cropTextureCoordinates[5] = 1.0 - maxX;
cropTextureCoordinates[6] = minY; // 0,1
cropTextureCoordinates[7] = 1.0 - minX;
}; break;
case kGPUImageRotateRight: // Fixed
{
cropTextureCoordinates[0] = minY; // 0,1
cropTextureCoordinates[1] = 1.0 - minX;
cropTextureCoordinates[2] = minY; // 0,0
cropTextureCoordinates[3] = 1.0 - maxX;
cropTextureCoordinates[4] = maxY; // 1,1
cropTextureCoordinates[5] = 1.0 - minX;
cropTextureCoordinates[6] = maxY; // 1,0
cropTextureCoordinates[7] = 1.0 - maxX;
}; break;
case kGPUImageFlipVertical: // Works for me
{
cropTextureCoordinates[0] = minX; // 0,1
cropTextureCoordinates[1] = maxY;
cropTextureCoordinates[2] = maxX; // 1,1
cropTextureCoordinates[3] = maxY;
cropTextureCoordinates[4] = minX; // 0,0
cropTextureCoordinates[5] = minY;
cropTextureCoordinates[6] = maxX; // 1,0
cropTextureCoordinates[7] = minY;
}; break;
case kGPUImageFlipHorizonal: // Works for me
case kGPUImageRotate180FlipHorizontal:
{
cropTextureCoordinates[0] = maxX; // 1,0
cropTextureCoordinates[1] = minY;
cropTextureCoordinates[2] = minX; // 0,0
cropTextureCoordinates[3] = minY;
cropTextureCoordinates[4] = maxX; // 1,1
cropTextureCoordinates[5] = maxY;
cropTextureCoordinates[6] = minX; // 0,1
cropTextureCoordinates[7] = maxY;
}; break;
case kGPUImageRotate180: // Fixed
{
cropTextureCoordinates[0] = maxX; // 1,1
cropTextureCoordinates[1] = maxY;
cropTextureCoordinates[2] = minX; // 0,1
cropTextureCoordinates[3] = maxY;
cropTextureCoordinates[4] = maxX; // 1,0
cropTextureCoordinates[5] = minY;
cropTextureCoordinates[6] = minX; // 0,0
cropTextureCoordinates[7] = minY;
}; break;
case kGPUImageRotateRightFlipVertical: // Fixed
{
cropTextureCoordinates[0] = minY; // 0,0
cropTextureCoordinates[1] = 1.0 - maxX;
cropTextureCoordinates[2] = minY; // 0,1
cropTextureCoordinates[3] = 1.0 - minX;
cropTextureCoordinates[4] = maxY; // 1,0
cropTextureCoordinates[5] = 1.0 - maxX;
cropTextureCoordinates[6] = maxY; // 1,1
cropTextureCoordinates[7] = 1.0 - minX;
}; break;
case kGPUImageRotateRightFlipHorizontal: // Fixed
{
cropTextureCoordinates[0] = maxY; // 1,1
cropTextureCoordinates[1] = 1.0 - minX;
cropTextureCoordinates[2] = maxY; // 1,0
cropTextureCoordinates[3] = 1.0 - maxX;
cropTextureCoordinates[4] = minY; // 0,1
cropTextureCoordinates[5] = 1.0 - minX;
cropTextureCoordinates[6] = minY; // 0,0
cropTextureCoordinates[7] = 1.0 - maxX;
}; break;
}
}
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
{
static const GLfloat cropSquareVertices[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
};
[self renderToTextureWithVertices:cropSquareVertices textureCoordinates:cropTextureCoordinates];
[self informTargetsAboutNewFrameAtTime:frameTime];
}
#pragma mark -
#pragma mark Accessors
- (void)setCropRegion:(CGRect)newValue;
{
NSParameterAssert(newValue.origin.x >= 0 && newValue.origin.x <= 1 &&
newValue.origin.y >= 0 && newValue.origin.y <= 1 &&
newValue.size.width >= 0 && newValue.size.width <= 1 &&
newValue.size.height >= 0 && newValue.size.height <= 1);
_cropRegion = newValue;
[self calculateCropTextureCoordinates];
}
- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex;
{
[super setInputRotation:newInputRotation atIndex:textureIndex];
[self calculateCropTextureCoordinates];
}
@end
@@ -0,0 +1,11 @@
#import "GPUImageTwoInputFilter.h"
@interface GPUImageDissolveBlendFilter : GPUImageTwoInputFilter
{
GLint mixUniform;
}
// Mix ranges from 0.0 (only image 1) to 1.0 (only image 2), with 0.5 (half of either) as the normal level
@property(readwrite, nonatomic) CGFloat mix;
@end
@@ -0,0 +1,52 @@
#import "GPUImageDissolveBlendFilter.h"
NSString *const kGPUImageDissolveBlendFragmentShaderString = SHADER_STRING
(
varying highp vec2 texCoord;
varying highp vec2 texCoord2;
uniform sampler2D sourceImage;
uniform sampler2D inputImageTexture2;
uniform lowp float mixturePercent;
void main()
{
lowp vec4 textureColor = texture2D(sourceImage, texCoord);
lowp vec4 textureColor2 = texture2D(inputImageTexture2, texCoord2);
gl_FragColor = mix(textureColor, textureColor2, mixturePercent);
}
);
@implementation GPUImageDissolveBlendFilter
@synthesize mix = _mix;
#pragma mark -
#pragma mark Initialization and teardown
- (id)init;
{
if (!(self = [super initWithFragmentShaderFromString:kGPUImageDissolveBlendFragmentShaderString]))
{
return nil;
}
mixUniform = [filterProgram uniformIndex:@"mixturePercent"];
self.mix = 0.5;
return self;
}
#pragma mark -
#pragma mark Accessors
- (void)setMix:(CGFloat)newValue;
{
_mix = newValue;
[self setFloat:_mix forUniform:mixUniform program:filterProgram];
}
@end
@@ -0,0 +1,11 @@
#import "GPUImageFilter.h"
@interface GPUImageExposureFilter : GPUImageFilter
{
GLint exposureUniform;
}
// Exposure ranges from -10.0 to 10.0, with 0.0 as the normal level
@property(readwrite, nonatomic) CGFloat exposure;
@end
@@ -0,0 +1,49 @@
#import "GPUImageExposureFilter.h"
NSString *const kGPUImageExposureFragmentShaderString = SHADER_STRING
(
varying highp vec2 texCoord;
uniform sampler2D sourceImage;
uniform highp float exposure;
void main()
{
highp vec4 textureColor = texture2D(sourceImage, texCoord);
gl_FragColor = vec4(textureColor.rgb * pow(2.0, exposure), textureColor.w);
}
);
@implementation GPUImageExposureFilter
@synthesize exposure = _exposure;
#pragma mark -
#pragma mark Initialization and teardown
- (id)init;
{
if (!(self = [super initWithFragmentShaderFromString:kGPUImageExposureFragmentShaderString]))
{
return nil;
}
exposureUniform = [filterProgram uniformIndex:@"exposure"];
self.exposure = 0.0;
return self;
}
#pragma mark -
#pragma mark Accessors
- (void)setExposure:(CGFloat)newValue;
{
_exposure = newValue;
[self setFloat:_exposure forUniform:exposureUniform program:filterProgram];
}
@end
+139
View File
@@ -0,0 +1,139 @@
#import "GPUImageOutput.h"
#define STRINGIZE(x) #x
#define STRINGIZE2(x) STRINGIZE(x)
#define SHADER_STRING(text) @ STRINGIZE2(text)
#define GPUImageHashIdentifier #
#define GPUImageWrappedLabel(x) x
#define GPUImageEscapedHashIdentifier(a) GPUImageWrappedLabel(GPUImageHashIdentifier)a
extern NSString *const kGPUImageVertexShaderString;
extern NSString *const kGPUImagePassthroughFragmentShaderString;
struct GPUVector4 {
GLfloat one;
GLfloat two;
GLfloat three;
GLfloat four;
};
typedef struct GPUVector4 GPUVector4;
struct GPUVector3 {
GLfloat one;
GLfloat two;
GLfloat three;
};
typedef struct GPUVector3 GPUVector3;
struct GPUMatrix4x4 {
GPUVector4 one;
GPUVector4 two;
GPUVector4 three;
GPUVector4 four;
};
typedef struct GPUMatrix4x4 GPUMatrix4x4;
struct GPUMatrix3x3 {
GPUVector3 one;
GPUVector3 two;
GPUVector3 three;
};
typedef struct GPUMatrix3x3 GPUMatrix3x3;
/** GPUImage's base filter class
Filters and other subsequent elements in the chain conform to the GPUImageInput protocol, which lets them take in the supplied or processed texture from the previous link in the chain and do something with it. Objects one step further down the chain are considered targets, and processing can be branched by adding multiple targets to a single output or filter.
*/
@interface GPUImageFilter : GPUImageOutput <GPUImageInput>
{
GPUImageFramebuffer *firstInputFramebuffer;
@public
GLProgram *filterProgram;
@protected
GLint filterPositionAttribute, filterTextureCoordinateAttribute;
GLint filterInputTextureUniform;
GLfloat backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha;
BOOL isEndProcessing;
CGSize currentFilterSize;
GPUImageRotationMode inputRotation;
BOOL currentlyReceivingMonochromeInput;
NSMutableDictionary *uniformStateRestorationBlocks;
dispatch_semaphore_t imageCaptureSemaphore;
}
@property (readonly, nonatomic) GLProgram *program;
@property (nonatomic, readonly) CVPixelBufferRef renderTarget;
@property (readwrite, nonatomic) BOOL preventRendering;
@property (readwrite, nonatomic) BOOL currentlyReceivingMonochromeInput;
/// @name Initialization and teardown
/**
Initialize with vertex and fragment shaders
You make take advantage of the SHADER_STRING macro to write your shaders in-line.
@param vertexShaderString Source code of the vertex shader to use
@param fragmentShaderString Source code of the fragment shader to use
*/
- (id)initWithVertexShaderFromString:(NSString *)vertexShaderString fragmentShaderFromString:(NSString *)fragmentShaderString;
/**
Initialize with a fragment shader
You may take advantage of the SHADER_STRING macro to write your shader in-line.
@param fragmentShaderString Source code of fragment shader to use
*/
- (id)initWithFragmentShaderFromString:(NSString *)fragmentShaderString;
/**
Initialize with a fragment shader
@param fragmentShaderFilename Filename of fragment shader to load
*/
- (id)initWithFragmentShaderFromFile:(NSString *)fragmentShaderFilename;
- (void)initializeAttributes;
- (void)setupFilterForSize:(CGSize)filterFrameSize;
- (CGSize)rotatedSize:(CGSize)sizeToRotate forIndex:(NSInteger)textureIndex;
- (CGPoint)rotatedPoint:(CGPoint)pointToRotate forRotation:(GPUImageRotationMode)rotation;
/// @name Managing the display FBOs
/** Size of the frame buffer object
*/
- (CGSize)sizeOfFBO;
/// @name Rendering
+ (const GLfloat *)textureCoordinatesForRotation:(GPUImageRotationMode)rotationMode;
- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;
- (void)informTargetsAboutNewFrameAtTime:(CMTime)frameTime;
- (CGSize)outputFrameSize;
/// @name Input parameters
- (void)setBackgroundColorRed:(GLfloat)redComponent green:(GLfloat)greenComponent blue:(GLfloat)blueComponent alpha:(GLfloat)alphaComponent;
- (void)setInteger:(GLint)newInteger forUniformName:(NSString *)uniformName;
- (void)setFloat:(GLfloat)newFloat forUniformName:(NSString *)uniformName;
- (void)setSize:(CGSize)newSize forUniformName:(NSString *)uniformName;
- (void)setPoint:(CGPoint)newPoint forUniformName:(NSString *)uniformName;
- (void)setFloatVec3:(GPUVector3)newVec3 forUniformName:(NSString *)uniformName;
- (void)setFloatVec4:(GPUVector4)newVec4 forUniform:(NSString *)uniformName;
- (void)setFloatArray:(GLfloat *)array length:(GLsizei)count forUniform:(NSString*)uniformName;
- (void)setMatrix3f:(GPUMatrix3x3)matrix forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
- (void)setMatrix4f:(GPUMatrix4x4)matrix forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
- (void)setFloat:(GLfloat)floatValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
- (void)setPoint:(CGPoint)pointValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
- (void)setSize:(CGSize)sizeValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
- (void)setVec3:(GPUVector3)vectorValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
- (void)setVec4:(GPUVector4)vectorValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
- (void)setFloatArray:(GLfloat *)arrayValue length:(GLsizei)arrayLength forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
- (void)setInteger:(GLint)intValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
- (void)setAndExecuteUniformStateCallbackAtIndex:(GLint)uniform forProgram:(GLProgram *)shaderProgram toBlock:(dispatch_block_t)uniformStateBlock;
- (void)setUniformsForProgramAtIndex:(NSUInteger)programIndex;
- (GLint)uniformIndexForName:(NSString *)name;
@end
+789
View File
@@ -0,0 +1,789 @@
#import "GPUImageFilter.h"
#import <AVFoundation/AVFoundation.h>
#import "LegacyComponentsInternal.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// Hardcode the vertex shader for standard filters, but this can be overridden
NSString *const kGPUImageVertexShaderString = SHADER_STRING
(
attribute vec4 position;
attribute vec4 inputTexCoord;
varying vec2 texCoord;
void main()
{
gl_Position = position;
texCoord = inputTexCoord.xy;
}
);
NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING
(
varying highp vec2 texCoord;
uniform sampler2D sourceImage;
void main()
{
gl_FragColor = texture2D(sourceImage, texCoord);
}
);
@implementation GPUImageFilter
@synthesize preventRendering = _preventRendering;
@synthesize currentlyReceivingMonochromeInput;
#pragma mark -
#pragma mark Initialization and teardown
- (id)initWithVertexShaderFromString:(NSString *)vertexShaderString fragmentShaderFromString:(NSString *)fragmentShaderString
{
if (!(self = [super init]))
{
return nil;
}
uniformStateRestorationBlocks = [NSMutableDictionary dictionaryWithCapacity:10];
_preventRendering = NO;
currentlyReceivingMonochromeInput = NO;
inputRotation = kGPUImageNoRotation;
backgroundColorRed = 0.0;
backgroundColorGreen = 0.0;
backgroundColorBlue = 0.0;
backgroundColorAlpha = 0.0;
imageCaptureSemaphore = dispatch_semaphore_create(0);
dispatch_semaphore_signal(imageCaptureSemaphore);
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext useImageProcessingContext];
filterProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:vertexShaderString fragmentShaderString:fragmentShaderString];
if (!filterProgram.initialized)
{
[self initializeAttributes];
if (![filterProgram link])
{
NSString *progLog = [filterProgram programLog];
NSLog(@"Program link log: %@", progLog);
NSString *fragLog = [filterProgram fragmentShaderLog];
NSLog(@"Fragment shader compile log: %@", fragLog);
NSString *vertLog = [filterProgram vertexShaderLog];
NSLog(@"Vertex shader compile log: %@", vertLog);
filterProgram = nil;
NSAssert(NO, @"Filter shader link failed");
}
}
filterPositionAttribute = [filterProgram attributeIndex:@"position"];
filterTextureCoordinateAttribute = [filterProgram attributeIndex:@"inputTexCoord"];
filterInputTextureUniform = [filterProgram uniformIndex:@"sourceImage"];
[GPUImageContext setActiveShaderProgram:filterProgram];
glEnableVertexAttribArray(filterPositionAttribute);
glEnableVertexAttribArray(filterTextureCoordinateAttribute);
});
return self;
}
- (id)initWithFragmentShaderFromString:(NSString *)fragmentShaderString
{
if (!(self = [self initWithVertexShaderFromString:kGPUImageVertexShaderString fragmentShaderFromString:fragmentShaderString]))
{
return nil;
}
return self;
}
- (id)initWithFragmentShaderFromFile:(NSString *)fragmentShaderFilename
{
NSString *fragmentShaderPathname = TGComponentsPathForResource(fragmentShaderFilename, @"fsh");
NSString *fragmentShaderString = [NSString stringWithContentsOfFile:fragmentShaderPathname encoding:NSUTF8StringEncoding error:nil];
if (!(self = [self initWithFragmentShaderFromString:fragmentShaderString]))
{
return nil;
}
return self;
}
- (id)init
{
if (!(self = [self initWithFragmentShaderFromString:kGPUImagePassthroughFragmentShaderString]))
{
return nil;
}
return self;
}
- (void)initializeAttributes
{
[filterProgram addAttribute:@"position"];
[filterProgram addAttribute:@"inputTexCoord"];
// Override this, calling back to this super method, in order to add new attributes to your vertex shader
}
- (void)setupFilterForSize:(CGSize)__unused filterFrameSize
{
// This is where you can override to provide some custom setup, if your filter has a size-dependent element
}
- (void)dealloc
{
#if !OS_OBJECT_USE_OBJC
if (imageCaptureSemaphore != NULL)
{
dispatch_release(imageCaptureSemaphore);
}
#endif
}
#pragma mark -
#pragma mark Still image processing
- (void)useNextFrameForImageCapture
{
usingNextFrameForImageCapture = YES;
// Set the semaphore high, if it isn't already
if (dispatch_semaphore_wait(imageCaptureSemaphore, DISPATCH_TIME_NOW) != 0)
{
return;
}
}
- (CGImageRef)newCGImageFromCurrentlyProcessedOutput
{
// Give it three seconds to process, then abort if they forgot to set up the image capture properly
double timeoutForImageCapture = 3.0;
dispatch_time_t convertedTimeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutForImageCapture * NSEC_PER_SEC));
if (dispatch_semaphore_wait(imageCaptureSemaphore, convertedTimeout) != 0)
{
return NULL;
}
GPUImageFramebuffer* framebuffer = [self framebufferForOutput];
usingNextFrameForImageCapture = NO;
dispatch_semaphore_signal(imageCaptureSemaphore);
CGImageRef image = [framebuffer newCGImageFromFramebufferContents];
return image;
}
- (void)newCIImageFromCurrentlyProcessedOutput:(void (^)(CIImage *image, void(^unlock)(void)))completion
{
// Give it three seconds to process, then abort if they forgot to set up the image capture properly
double timeoutForImageCapture = 3.0;
dispatch_time_t convertedTimeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutForImageCapture * NSEC_PER_SEC));
if (dispatch_semaphore_wait(imageCaptureSemaphore, convertedTimeout) != 0)
{
completion(nil, ^{});
return;
}
GPUImageFramebuffer *framebuffer = [self framebufferForOutput];
usingNextFrameForImageCapture = NO;
dispatch_semaphore_signal(imageCaptureSemaphore);
[framebuffer newCIImageFromFramebufferContents:completion];
}
- (void)commitImageCapture {
dispatch_semaphore_signal(imageCaptureSemaphore);
}
#pragma mark -
#pragma mark Managing the display FBOs
- (CGSize)sizeOfFBO
{
CGSize outputSize = [self maximumOutputSize];
if ( (CGSizeEqualToSize(outputSize, CGSizeZero)) || (inputTextureSize.width < outputSize.width) )
{
return inputTextureSize;
}
else
{
return outputSize;
}
}
#pragma mark -
#pragma mark Rendering
+ (const GLfloat *)textureCoordinatesForRotation:(GPUImageRotationMode)rotationMode
{
static const GLfloat noRotationTextureCoordinates[] = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
};
static const GLfloat rotateLeftTextureCoordinates[] = {
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 0.0f,
0.0f, 1.0f,
};
static const GLfloat rotateRightTextureCoordinates[] = {
0.0f, 1.0f,
0.0f, 0.0f,
1.0f, 1.0f,
1.0f, 0.0f,
};
static const GLfloat verticalFlipTextureCoordinates[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
static const GLfloat horizontalFlipTextureCoordinates[] = {
1.0f, 0.0f,
0.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f,
};
static const GLfloat rotateRightVerticalFlipTextureCoordinates[] = {
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f,
};
static const GLfloat rotateRightHorizontalFlipTextureCoordinates[] = {
1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 1.0f,
0.0f, 0.0f,
};
static const GLfloat rotate180TextureCoordinates[] = {
1.0f, 1.0f,
0.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,
};
static const GLfloat rotate180HorizontalFlipTextureCoordinates[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
switch(rotationMode)
{
case kGPUImageNoRotation: return noRotationTextureCoordinates;
case kGPUImageRotateLeft: return rotateLeftTextureCoordinates;
case kGPUImageRotateRight: return rotateRightTextureCoordinates;
case kGPUImageFlipVertical: return verticalFlipTextureCoordinates;
case kGPUImageFlipHorizonal: return horizontalFlipTextureCoordinates;
case kGPUImageRotateRightFlipVertical: return rotateRightVerticalFlipTextureCoordinates;
case kGPUImageRotateRightFlipHorizontal: return rotateRightHorizontalFlipTextureCoordinates;
case kGPUImageRotate180: return rotate180TextureCoordinates;
case kGPUImageRotate180FlipHorizontal: return rotate180HorizontalFlipTextureCoordinates;
}
}
- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates
{
if (self.preventRendering)
{
[firstInputFramebuffer unlock];
return;
}
[GPUImageContext setActiveShaderProgram:filterProgram];
outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:[self sizeOfFBO] textureOptions:self.outputTextureOptions onlyTexture:NO mark: true];
[outputFramebuffer activateFramebuffer];
if (usingNextFrameForImageCapture)
{
[outputFramebuffer lock];
}
[self setUniformsForProgramAtIndex:0];
glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, [firstInputFramebuffer texture]);
glUniform1i(filterInputTextureUniform, 2);
glVertexAttribPointer(filterPositionAttribute, 2, GL_FLOAT, 0, 0, vertices);
glVertexAttribPointer(filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[firstInputFramebuffer unlock];
if (usingNextFrameForImageCapture)
{
dispatch_semaphore_signal(imageCaptureSemaphore);
}
}
- (void)informTargetsAboutNewFrameAtTime:(CMTime)frameTime
{
if (self.frameProcessingCompletionBlock != NULL)
{
self.frameProcessingCompletionBlock(self, frameTime);
}
// Get all targets the framebuffer so they can grab a lock on it
for (id<GPUImageInput> currentTarget in targets)
{
if (currentTarget != self.targetToIgnoreForUpdates)
{
NSInteger indexOfObject = [targets indexOfObject:currentTarget];
NSInteger textureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
[self setInputFramebufferForTarget:currentTarget atIndex:textureIndex];
[currentTarget setInputSize:[self outputFrameSize] atIndex:textureIndex];
}
}
// Release our hold so it can return to the cache immediately upon processing
[[self framebufferForOutput] unlock];
if (usingNextFrameForImageCapture)
{
// usingNextFrameForImageCapture = NO;
}
else
{
[self removeOutputFramebuffer];
}
// Trigger processing last, so that our unlock comes first in serial execution, avoiding the need for a callback
for (id<GPUImageInput> currentTarget in targets)
{
if (currentTarget != self.targetToIgnoreForUpdates)
{
NSInteger indexOfObject = [targets indexOfObject:currentTarget];
NSInteger textureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
[currentTarget newFrameReadyAtTime:frameTime atIndex:textureIndex];
}
}
}
- (CGSize)outputFrameSize
{
return inputTextureSize;
}
#pragma mark -
#pragma mark Input parameters
- (void)setBackgroundColorRed:(GLfloat)redComponent green:(GLfloat)greenComponent blue:(GLfloat)blueComponent alpha:(GLfloat)alphaComponent
{
backgroundColorRed = redComponent;
backgroundColorGreen = greenComponent;
backgroundColorBlue = blueComponent;
backgroundColorAlpha = alphaComponent;
}
- (void)setInteger:(GLint)newInteger forUniformName:(NSString *)uniformName
{
GLint uniformIndex = [filterProgram uniformIndex:uniformName];
[self setInteger:newInteger forUniform:uniformIndex program:filterProgram];
}
- (void)setFloat:(GLfloat)newFloat forUniformName:(NSString *)uniformName
{
GLint uniformIndex = [filterProgram uniformIndex:uniformName];
[self setFloat:newFloat forUniform:uniformIndex program:filterProgram];
}
- (void)setSize:(CGSize)newSize forUniformName:(NSString *)uniformName
{
GLint uniformIndex = [filterProgram uniformIndex:uniformName];
[self setSize:newSize forUniform:uniformIndex program:filterProgram];
}
- (void)setPoint:(CGPoint)newPoint forUniformName:(NSString *)uniformName
{
GLint uniformIndex = [filterProgram uniformIndex:uniformName];
[self setPoint:newPoint forUniform:uniformIndex program:filterProgram];
}
- (void)setFloatVec3:(GPUVector3)newVec3 forUniformName:(NSString *)uniformName
{
GLint uniformIndex = [filterProgram uniformIndex:uniformName];
[self setVec3:newVec3 forUniform:uniformIndex program:filterProgram];
}
- (void)setFloatVec4:(GPUVector4)newVec4 forUniform:(NSString *)uniformName
{
GLint uniformIndex = [filterProgram uniformIndex:uniformName];
[self setVec4:newVec4 forUniform:uniformIndex program:filterProgram];
}
- (void)setFloatArray:(GLfloat *)array length:(GLsizei)count forUniform:(NSString*)uniformName
{
GLint uniformIndex = [filterProgram uniformIndex:uniformName];
[self setFloatArray:array length:count forUniform:uniformIndex program:filterProgram];
}
- (void)setMatrix3f:(GPUMatrix3x3)matrix forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
{
runAsynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext setActiveShaderProgram:shaderProgram];
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
glUniformMatrix3fv(uniform, 1, GL_FALSE, (GLfloat *)&matrix);
}];
});
}
- (void)setMatrix4f:(GPUMatrix4x4)matrix forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
{
runAsynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext setActiveShaderProgram:shaderProgram];
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
glUniformMatrix4fv(uniform, 1, GL_FALSE, (GLfloat *)&matrix);
}];
});
}
- (void)setFloat:(GLfloat)floatValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
{
runAsynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext setActiveShaderProgram:shaderProgram];
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
glUniform1f(uniform, floatValue);
}];
});
}
- (void)setPoint:(CGPoint)pointValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
{
runAsynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext setActiveShaderProgram:shaderProgram];
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
GLfloat positionArray[2];
positionArray[0] = (GLfloat)pointValue.x;
positionArray[1] = (GLfloat)pointValue.y;
glUniform2fv(uniform, 1, positionArray);
}];
});
}
- (void)setSize:(CGSize)sizeValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
{
runAsynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext setActiveShaderProgram:shaderProgram];
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
GLfloat sizeArray[2];
sizeArray[0] = (GLfloat)sizeValue.width;
sizeArray[1] = (GLfloat)sizeValue.height;
glUniform2fv(uniform, 1, sizeArray);
}];
});
}
- (void)setVec3:(GPUVector3)vectorValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
{
runAsynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext setActiveShaderProgram:shaderProgram];
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
glUniform3fv(uniform, 1, (GLfloat *)&vectorValue);
}];
});
}
- (void)setVec4:(GPUVector4)vectorValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
{
runAsynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext setActiveShaderProgram:shaderProgram];
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
glUniform4fv(uniform, 1, (GLfloat *)&vectorValue);
}];
});
}
- (void)setFloatArray:(GLfloat *)arrayValue length:(GLsizei)arrayLength forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
{
// Make a copy of the data, so it doesn't get overwritten before async call executes
NSData* arrayData = [NSData dataWithBytes:arrayValue length:arrayLength * sizeof(arrayValue[0])];
runAsynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext setActiveShaderProgram:shaderProgram];
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
glUniform1fv(uniform, arrayLength, [arrayData bytes]);
}];
});
}
- (void)setInteger:(GLint)intValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
{
runAsynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext setActiveShaderProgram:shaderProgram];
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
glUniform1i(uniform, intValue);
}];
});
}
- (void)setAndExecuteUniformStateCallbackAtIndex:(GLint)uniform forProgram:(GLProgram *)__unused shaderProgram toBlock:(dispatch_block_t)uniformStateBlock
{
[uniformStateRestorationBlocks setObject:[uniformStateBlock copy] forKey:[NSNumber numberWithInt:uniform]];
uniformStateBlock();
}
- (void)setUniformsForProgramAtIndex:(NSUInteger)__unused programIndex
{
[uniformStateRestorationBlocks enumerateKeysAndObjectsUsingBlock:^(__unused id key, id obj, __unused BOOL *stop){
dispatch_block_t currentBlock = obj;
currentBlock();
}];
}
#pragma mark -
#pragma mark GPUImageInput
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)__unused textureIndex
{
static const GLfloat imageVertices[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
};
[self renderToTextureWithVertices:imageVertices textureCoordinates:[[self class] textureCoordinatesForRotation:inputRotation]];
[self informTargetsAboutNewFrameAtTime:frameTime];
}
- (NSInteger)nextAvailableTextureIndex
{
return 0;
}
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)__unused textureIndex
{
firstInputFramebuffer = newInputFramebuffer;
[firstInputFramebuffer lock];
}
- (CGSize)rotatedSize:(CGSize)sizeToRotate forIndex:(NSInteger)__unused textureIndex
{
CGSize rotatedSize = sizeToRotate;
if (GPUImageRotationSwapsWidthAndHeight(inputRotation))
{
rotatedSize.width = sizeToRotate.height;
rotatedSize.height = sizeToRotate.width;
}
return rotatedSize;
}
- (CGPoint)rotatedPoint:(CGPoint)pointToRotate forRotation:(GPUImageRotationMode)rotation
{
CGPoint rotatedPoint;
switch(rotation)
{
case kGPUImageNoRotation: return pointToRotate; break;
case kGPUImageFlipHorizonal:
{
rotatedPoint.x = 1.0f - (GLfloat)pointToRotate.x;
rotatedPoint.y = (GLfloat)pointToRotate.y;
}; break;
case kGPUImageFlipVertical:
{
rotatedPoint.x = pointToRotate.x;
rotatedPoint.y = 1.0f - pointToRotate.y;
}; break;
case kGPUImageRotateLeft:
{
rotatedPoint.x = 1.0f - pointToRotate.y;
rotatedPoint.y = pointToRotate.x;
}; break;
case kGPUImageRotateRight:
{
rotatedPoint.x = pointToRotate.y;
rotatedPoint.y = 1.0f - pointToRotate.x;
}; break;
case kGPUImageRotateRightFlipVertical:
{
rotatedPoint.x = pointToRotate.y;
rotatedPoint.y = pointToRotate.x;
}; break;
case kGPUImageRotateRightFlipHorizontal:
{
rotatedPoint.x = 1.0f - pointToRotate.y;
rotatedPoint.y = 1.0f - pointToRotate.x;
}; break;
case kGPUImageRotate180:
{
rotatedPoint.x = 1.0f - pointToRotate.x;
rotatedPoint.y = 1.0f - pointToRotate.y;
}; break;
case kGPUImageRotate180FlipHorizontal:
{
rotatedPoint.x = pointToRotate.x;
rotatedPoint.y = 1.0f - pointToRotate.y;
}; break;
}
return rotatedPoint;
}
- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex
{
if (self.preventRendering)
{
return;
}
if (overrideInputSize)
{
if (CGSizeEqualToSize(forcedMaximumSize, CGSizeZero))
{
}
else
{
CGRect insetRect = AVMakeRectWithAspectRatioInsideRect(newSize, CGRectMake(0.0, 0.0, forcedMaximumSize.width, forcedMaximumSize.height));
inputTextureSize = insetRect.size;
}
}
else
{
CGSize rotatedSize = [self rotatedSize:newSize forIndex:textureIndex];
if (CGSizeEqualToSize(rotatedSize, CGSizeZero))
{
inputTextureSize = rotatedSize;
}
else if (!CGSizeEqualToSize(inputTextureSize, rotatedSize))
{
inputTextureSize = rotatedSize;
}
}
[self setupFilterForSize:[self sizeOfFBO]];
}
- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)__unused textureIndex
{
inputRotation = newInputRotation;
}
- (void)forceProcessingAtSize:(CGSize)frameSize
{
if (CGSizeEqualToSize(frameSize, CGSizeZero))
{
overrideInputSize = NO;
}
else
{
overrideInputSize = YES;
inputTextureSize = frameSize;
forcedMaximumSize = CGSizeZero;
}
}
- (void)forceProcessingAtSizeRespectingAspectRatio:(CGSize)frameSize
{
if (CGSizeEqualToSize(frameSize, CGSizeZero))
{
overrideInputSize = NO;
inputTextureSize = CGSizeZero;
forcedMaximumSize = CGSizeZero;
}
else
{
overrideInputSize = YES;
forcedMaximumSize = frameSize;
}
}
- (CGSize)maximumOutputSize
{
// I'm temporarily disabling adjustments for smaller output sizes until I figure out how to make this work better
return CGSizeZero;
/*
if (CGSizeEqualToSize(cachedMaximumOutputSize, CGSizeZero))
{
for (id<GPUImageInput> currentTarget in targets)
{
if ([currentTarget maximumOutputSize].width > cachedMaximumOutputSize.width)
{
cachedMaximumOutputSize = [currentTarget maximumOutputSize];
}
}
}
return cachedMaximumOutputSize;
*/
}
- (void)endProcessing
{
if (!isEndProcessing)
{
isEndProcessing = YES;
for (id<GPUImageInput> currentTarget in targets)
{
[currentTarget endProcessing];
}
}
}
- (BOOL)wantsMonochromeInput
{
return NO;
}
- (GLint)uniformIndexForName:(NSString *)name
{
return [filterProgram uniformIndex:name];
}
#pragma mark -
#pragma mark Accessors
- (GLProgram *)program
{
return filterProgram;
}
@end
#pragma clang diagnostic pop
+19
View File
@@ -0,0 +1,19 @@
#import "GPUImageOutput.h"
#import "GPUImageFilter.h"
@interface GPUImageFilterGroup : GPUImageOutput <GPUImageInput>
{
NSMutableArray *filters;
BOOL isEndProcessing;
}
@property(readwrite, nonatomic, strong) GPUImageOutput<GPUImageInput> *terminalFilter;
@property(readwrite, nonatomic, strong) NSArray *initialFilters;
@property(readwrite, nonatomic, strong) GPUImageOutput<GPUImageInput> *inputFilterToIgnoreForUpdates;
// Filter management
- (void)addFilter:(GPUImageOutput<GPUImageInput> *)newFilter;
- (GPUImageOutput<GPUImageInput> *)filterAtIndex:(NSUInteger)filterIndex;
- (NSUInteger)filterCount;
@end
+207
View File
@@ -0,0 +1,207 @@
#import "GPUImageFilterGroup.h"
@implementation GPUImageFilterGroup
@synthesize terminalFilter = _terminalFilter;
@synthesize initialFilters = _initialFilters;
@synthesize inputFilterToIgnoreForUpdates = _inputFilterToIgnoreForUpdates;
- (id)init;
{
if (!(self = [super init]))
{
return nil;
}
filters = [[NSMutableArray alloc] init];
return self;
}
#pragma mark -
#pragma mark Filter management
- (void)addFilter:(GPUImageOutput<GPUImageInput> *)newFilter;
{
[filters addObject:newFilter];
}
- (GPUImageOutput<GPUImageInput> *)filterAtIndex:(NSUInteger)filterIndex;
{
return [filters objectAtIndex:filterIndex];
}
- (NSUInteger)filterCount;
{
return [filters count];
}
#pragma mark -
#pragma mark Still image processing
- (void)useNextFrameForImageCapture;
{
[self.terminalFilter useNextFrameForImageCapture];
}
- (CGImageRef)newCGImageFromCurrentlyProcessedOutput;
{
return [self.terminalFilter newCGImageFromCurrentlyProcessedOutput];
}
#pragma mark -
#pragma mark GPUImageOutput overrides
- (void)setTargetToIgnoreForUpdates:(id<GPUImageInput>)targetToIgnoreForUpdates;
{
[_terminalFilter setTargetToIgnoreForUpdates:targetToIgnoreForUpdates];
}
- (void)addTarget:(id<GPUImageInput>)newTarget atTextureLocation:(NSInteger)textureLocation;
{
[_terminalFilter addTarget:newTarget atTextureLocation:textureLocation];
}
- (void)removeTarget:(id<GPUImageInput>)targetToRemove;
{
[_terminalFilter removeTarget:targetToRemove];
}
- (void)removeAllTargets;
{
[_terminalFilter removeAllTargets];
}
- (NSArray *)targets;
{
return [_terminalFilter targets];
}
- (void)setFrameProcessingCompletionBlock:(void (^)(GPUImageOutput *, CMTime))frameProcessingCompletionBlock;
{
[_terminalFilter setFrameProcessingCompletionBlock:frameProcessingCompletionBlock];
}
- (void (^)(GPUImageOutput *, CMTime))frameProcessingCompletionBlock;
{
return [_terminalFilter frameProcessingCompletionBlock];
}
#pragma mark -
#pragma mark GPUImageInput protocol
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
{
for (GPUImageOutput<GPUImageInput> *currentFilter in _initialFilters)
{
if (currentFilter != self.inputFilterToIgnoreForUpdates)
{
[currentFilter newFrameReadyAtTime:frameTime atIndex:textureIndex];
}
}
}
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
{
for (GPUImageOutput<GPUImageInput> *currentFilter in _initialFilters)
{
[currentFilter setInputFramebuffer:newInputFramebuffer atIndex:textureIndex];
}
}
- (NSInteger)nextAvailableTextureIndex;
{
// if ([_initialFilters count] > 0)
// {
// return [[_initialFilters objectAtIndex:0] nextAvailableTextureIndex];
// }
return 0;
}
- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex;
{
for (GPUImageOutput<GPUImageInput> *currentFilter in _initialFilters)
{
[currentFilter setInputSize:newSize atIndex:textureIndex];
}
}
- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex;
{
for (GPUImageOutput<GPUImageInput> *currentFilter in _initialFilters)
{
[currentFilter setInputRotation:newInputRotation atIndex:(NSInteger)textureIndex];
}
}
- (void)forceProcessingAtSize:(CGSize)frameSize;
{
for (GPUImageOutput<GPUImageInput> *currentFilter in filters)
{
[currentFilter forceProcessingAtSize:frameSize];
}
}
- (void)forceProcessingAtSizeRespectingAspectRatio:(CGSize)frameSize;
{
for (GPUImageOutput<GPUImageInput> *currentFilter in filters)
{
[currentFilter forceProcessingAtSizeRespectingAspectRatio:frameSize];
}
}
- (CGSize)maximumOutputSize;
{
// I'm temporarily disabling adjustments for smaller output sizes until I figure out how to make this work better
return CGSizeZero;
/*
if (CGSizeEqualToSize(cachedMaximumOutputSize, CGSizeZero))
{
for (id<GPUImageInput> currentTarget in _initialFilters)
{
if ([currentTarget maximumOutputSize].width > cachedMaximumOutputSize.width)
{
cachedMaximumOutputSize = [currentTarget maximumOutputSize];
}
}
}
return cachedMaximumOutputSize;
*/
}
- (void)endProcessing;
{
if (!isEndProcessing)
{
isEndProcessing = YES;
for (id<GPUImageInput> currentTarget in _initialFilters)
{
[currentTarget endProcessing];
}
}
}
- (BOOL)wantsMonochromeInput;
{
BOOL allInputsWantMonochromeInput = YES;
for (GPUImageOutput<GPUImageInput> *currentFilter in _initialFilters)
{
allInputsWantMonochromeInput = allInputsWantMonochromeInput && [currentFilter wantsMonochromeInput];
}
return allInputsWantMonochromeInput;
}
- (void)setCurrentlyReceivingMonochromeInput:(BOOL)newValue;
{
for (GPUImageOutput<GPUImageInput> *currentFilter in _initialFilters)
{
[currentFilter setCurrentlyReceivingMonochromeInput:newValue];
}
}
@end
+60
View File
@@ -0,0 +1,60 @@
#import <Foundation/Foundation.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
#import <QuartzCore/QuartzCore.h>
#import <CoreMedia/CoreMedia.h>
#import <CoreImage/CoreImage.h>
typedef struct GPUTextureOptions {
GLenum minFilter;
GLenum magFilter;
GLenum wrapS;
GLenum wrapT;
GLenum internalFormat;
GLenum format;
GLenum type;
} GPUTextureOptions;
@interface GPUImageFramebuffer : NSObject
@property (nonatomic, readonly) CGSize size;
@property (nonatomic, readonly) GPUTextureOptions textureOptions;
@property (nonatomic, readonly) GLuint texture;
@property (nonatomic, readonly) BOOL missingFramebuffer;
@property (nonatomic, assign) BOOL mark;
// Initialization and teardown
- (id)initWithSize:(CGSize)framebufferSize;
- (id)initWithSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)fboTextureOptions onlyTexture:(BOOL)onlyGenerateTexture;
- (id)initWithSize:(CGSize)framebufferSize overriddenTexture:(GLuint)inputTexture;
- (id)initWithSize:(CGSize)framebufferSize overridenFramebuffer:(GLuint)overridenFramebuffer overriddenTexture:(GLuint)inputTexture;
// Usage
- (void)useFramebuffer;
- (void)activateFramebuffer;
// Reference counting
- (void)lock;
- (void)unlock;
- (void)clearAllLocks;
- (void)disableReferenceCounting;
- (void)enableReferenceCounting;
// Image capture
- (CGImageRef)newCGImageFromFramebufferContents;
- (void)newCIImageFromFramebufferContents:(void (^)(CIImage *image, void(^unlock)(void)))completion;
- (void)restoreRenderTarget;
// Raw data bytes
- (void)lockForReading;
- (void)unlockAfterReading;
- (NSUInteger)bytesPerRow;
- (GLubyte *)byteBuffer;
+ (void)setMark:(BOOL)mark;
@end
+500
View File
@@ -0,0 +1,500 @@
#import "GPUImageFramebuffer.h"
#import "GPUImageOutput.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@interface GPUImageFramebuffer()
{
GLuint framebuffer;
CVPixelBufferRef renderTarget;
CVOpenGLESTextureRef renderTexture;
NSUInteger readLockCount;
NSUInteger framebufferReferenceCount;
BOOL referenceCountingDisabled;
}
- (void)generateFramebuffer;
- (void)generateTexture;
- (void)destroyFramebuffer;
@end
void dataProviderReleaseCallback (void *info, const void *data, size_t size);
void dataProviderUnlockCallback (void *info, const void *data, size_t size);
@implementation GPUImageFramebuffer
#pragma mark -
#pragma mark Initialization and teardown
static BOOL mark = false;
+ (void)setMark:(BOOL)mark_ {
mark = mark_;
}
- (id)initWithSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)fboTextureOptions onlyTexture:(BOOL)onlyGenerateTexture
{
if (!(self = [super init]))
{
return nil;
}
_mark = mark;
_textureOptions = fboTextureOptions;
_size = framebufferSize;
framebufferReferenceCount = 0;
referenceCountingDisabled = NO;
_missingFramebuffer = onlyGenerateTexture;
if (_missingFramebuffer)
{
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext useImageProcessingContext];
[self generateTexture];
framebuffer = 0;
});
}
else
{
[self generateFramebuffer];
}
return self;
}
- (id)initWithSize:(CGSize)framebufferSize overriddenTexture:(GLuint)inputTexture
{
if (!(self = [super init]))
{
return nil;
}
_mark = mark;
GPUTextureOptions defaultTextureOptions;
defaultTextureOptions.minFilter = GL_LINEAR;
defaultTextureOptions.magFilter = GL_LINEAR;
defaultTextureOptions.wrapS = GL_CLAMP_TO_EDGE;
defaultTextureOptions.wrapT = GL_CLAMP_TO_EDGE;
defaultTextureOptions.internalFormat = GL_RGBA;
defaultTextureOptions.format = GL_BGRA;
defaultTextureOptions.type = GL_UNSIGNED_BYTE;
_textureOptions = defaultTextureOptions;
_size = framebufferSize;
framebufferReferenceCount = 0;
referenceCountingDisabled = YES;
_texture = inputTexture;
return self;
}
- (id)initWithSize:(CGSize)framebufferSize overridenFramebuffer:(GLuint)overridenFramebuffer overriddenTexture:(GLuint)inputTexture
{
if (!(self = [super init]))
{
return nil;
}
_mark = mark;
GPUTextureOptions defaultTextureOptions;
defaultTextureOptions.minFilter = GL_LINEAR;
defaultTextureOptions.magFilter = GL_LINEAR;
defaultTextureOptions.wrapS = GL_CLAMP_TO_EDGE;
defaultTextureOptions.wrapT = GL_CLAMP_TO_EDGE;
defaultTextureOptions.internalFormat = GL_RGBA;
defaultTextureOptions.format = GL_BGRA;
defaultTextureOptions.type = GL_UNSIGNED_BYTE;
_textureOptions = defaultTextureOptions;
_size = framebufferSize;
framebufferReferenceCount = 0;
referenceCountingDisabled = YES;
framebuffer = overridenFramebuffer;
_texture = inputTexture;
return self;
}
- (id)initWithSize:(CGSize)framebufferSize
{
_mark = mark;
GPUTextureOptions defaultTextureOptions;
defaultTextureOptions.minFilter = GL_LINEAR;
defaultTextureOptions.magFilter = GL_LINEAR;
defaultTextureOptions.wrapS = GL_CLAMP_TO_EDGE;
defaultTextureOptions.wrapT = GL_CLAMP_TO_EDGE;
defaultTextureOptions.internalFormat = GL_RGBA;
defaultTextureOptions.format = GL_BGRA;
defaultTextureOptions.type = GL_UNSIGNED_BYTE;
if (!(self = [self initWithSize:framebufferSize textureOptions:defaultTextureOptions onlyTexture:NO]))
{
return nil;
}
return self;
}
- (void)dealloc
{
[self destroyFramebuffer];
}
#pragma mark -
#pragma mark Internal
- (void)generateTexture
{
glActiveTexture(GL_TEXTURE1);
glGenTextures(1, &_texture);
glBindTexture(GL_TEXTURE_2D, _texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _textureOptions.minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _textureOptions.magFilter);
// This is necessary for non-power-of-two textures
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, _textureOptions.wrapS);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _textureOptions.wrapT);
// TODO: Handle mipmaps
}
- (void)generateFramebuffer
{
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext useImageProcessingContext];
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
if ([GPUImageContext supportsFastTextureUpload])
{
CVOpenGLESTextureCacheRef coreVideoTextureCache = [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache];
// Code originally sourced from http://allmybrain.com/2011/12/08/rendering-to-a-texture-with-ios-5-texture-cache-api/
CFDictionaryRef empty; // empty value for attr value.
CFMutableDictionaryRef attrs;
empty = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // our empty IOSurface properties dictionary
attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(attrs, kCVPixelBufferIOSurfacePropertiesKey, empty);
CVReturn err = CVPixelBufferCreate(kCFAllocatorDefault, (int)_size.width, (int)_size.height, kCVPixelFormatType_32BGRA, attrs, &renderTarget);
if (err)
{
NSLog(@"FBO size: %f, %f", _size.width, _size.height);
NSAssert(NO, @"Error at CVPixelBufferCreate %d", err);
}
err = CVOpenGLESTextureCacheCreateTextureFromImage (kCFAllocatorDefault, coreVideoTextureCache, renderTarget,
NULL, // texture attributes
GL_TEXTURE_2D,
_textureOptions.internalFormat, // opengl format
(int)_size.width,
(int)_size.height,
_textureOptions.format, // native iOS format
_textureOptions.type,
0,
&renderTexture);
if (err)
{
NSAssert(NO, @"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
}
CFRelease(attrs);
CFRelease(empty);
glBindTexture(CVOpenGLESTextureGetTarget(renderTexture), CVOpenGLESTextureGetName(renderTexture));
_texture = CVOpenGLESTextureGetName(renderTexture);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, _textureOptions.wrapS);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _textureOptions.wrapT);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, CVOpenGLESTextureGetName(renderTexture), 0);
}
else
{
[self generateTexture];
glBindTexture(GL_TEXTURE_2D, _texture);
glTexImage2D(GL_TEXTURE_2D, 0, _textureOptions.internalFormat, (int)_size.width, (int)_size.height, 0, _textureOptions.format, _textureOptions.type, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
}
#ifndef NS_BLOCK_ASSERTIONS
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
NSAssert(status == GL_FRAMEBUFFER_COMPLETE, @"Incomplete filter FBO: %d", status);
#endif
glBindTexture(GL_TEXTURE_2D, 0);
});
}
- (void)destroyFramebuffer
{
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext useImageProcessingContext];
if (framebuffer)
{
glDeleteFramebuffers(1, &framebuffer);
framebuffer = 0;
}
if ([GPUImageContext supportsFastTextureUpload] && (!_missingFramebuffer))
{
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
if (renderTarget)
{
CFRelease(renderTarget);
renderTarget = NULL;
}
if (renderTexture)
{
CFRelease(renderTexture);
renderTexture = NULL;
}
#endif
}
else
{
glDeleteTextures(1, &_texture);
}
});
}
#pragma mark -
#pragma mark Usage
- (void)useFramebuffer
{
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
}
- (void)activateFramebuffer
{
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glViewport(0, 0, (int)_size.width, (int)_size.height);
}
#pragma mark -
#pragma mark Reference counting
- (void)lock
{
if (referenceCountingDisabled)
{
return;
}
framebufferReferenceCount++;
}
- (void)unlock
{
if (referenceCountingDisabled)
{
return;
}
NSAssert(framebufferReferenceCount > 0, @"Tried to overrelease a framebuffer, did you forget to call -useNextFrameForImageCapture before using -imageFromCurrentFramebuffer?");
framebufferReferenceCount--;
if (framebufferReferenceCount < 1)
{
[[GPUImageContext sharedFramebufferCache] returnFramebufferToCache:self];
}
}
- (void)clearAllLocks
{
framebufferReferenceCount = 0;
}
- (void)disableReferenceCounting
{
referenceCountingDisabled = YES;
}
- (void)enableReferenceCounting
{
referenceCountingDisabled = NO;
}
#pragma mark -
#pragma mark Image capture
void dataProviderReleaseCallback (__unused void *info, const void *data, __unused size_t size)
{
free((void *)data);
}
void dataProviderUnlockCallback (void *info, __unused const void *data, __unused size_t size)
{
GPUImageFramebuffer *framebuffer = (__bridge_transfer GPUImageFramebuffer*)info;
[framebuffer restoreRenderTarget];
[framebuffer unlock];
[[GPUImageContext sharedFramebufferCache] removeFramebufferFromActiveImageCaptureList:framebuffer];
}
- (CGImageRef)newCGImageFromFramebufferContents
{
// a CGImage can only be created from a 'normal' color texture
NSAssert(self.textureOptions.internalFormat == GL_RGBA, @"For conversion to a CGImage the output texture format for this filter must be GL_RGBA.");
NSAssert(self.textureOptions.type == GL_UNSIGNED_BYTE, @"For conversion to a CGImage the type of the output texture of this filter must be GL_UNSIGNED_BYTE.");
__block CGImageRef cgImageFromBytes;
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext useImageProcessingContext];
NSUInteger totalBytesForImage = (int)_size.width * (int)_size.height * 4;
// It appears that the width of a texture must be padded out to be a multiple of 8 (32 bytes) if reading from it using a texture cache
GLubyte *rawImagePixels;
CGDataProviderRef dataProvider = NULL;
if ([GPUImageContext supportsFastTextureUpload])
{
NSUInteger paddedWidthOfImage = (NSUInteger)(CVPixelBufferGetBytesPerRow(renderTarget) / 4.0);
NSUInteger paddedBytesForImage = paddedWidthOfImage * (int)_size.height * 4;
glFinish();
CFRetain(renderTarget); // I need to retain the pixel buffer here and release in the data source callback to prevent its bytes from being prematurely deallocated during a photo write operation
[self lockForReading];
rawImagePixels = (GLubyte *)CVPixelBufferGetBaseAddress(renderTarget);
dataProvider = CGDataProviderCreateWithData((__bridge_retained void*)self, rawImagePixels, paddedBytesForImage, dataProviderUnlockCallback);
[[GPUImageContext sharedFramebufferCache] addFramebufferToActiveImageCaptureList:self]; // In case the framebuffer is swapped out on the filter, need to have a strong reference to it somewhere for it to hang on while the image is in existence
}
else
{
[self activateFramebuffer];
rawImagePixels = (GLubyte *)malloc(totalBytesForImage);
glReadPixels(0, 0, (int)_size.width, (int)_size.height, GL_RGBA, GL_UNSIGNED_BYTE, rawImagePixels);
dataProvider = CGDataProviderCreateWithData(NULL, rawImagePixels, totalBytesForImage, dataProviderReleaseCallback);
[self unlock]; // Don't need to keep this around anymore
}
CGColorSpaceRef defaultRGBColorSpace = CGColorSpaceCreateDeviceRGB();
if ([GPUImageContext supportsFastTextureUpload])
{
cgImageFromBytes = CGImageCreate((int)_size.width, (int)_size.height, 8, 32, CVPixelBufferGetBytesPerRow(renderTarget), defaultRGBColorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst, dataProvider, NULL, NO, kCGRenderingIntentDefault);
}
else
{
cgImageFromBytes = CGImageCreate((int)_size.width, (int)_size.height, 8, 32, 4 * (int)_size.width, defaultRGBColorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast, dataProvider, NULL, NO, kCGRenderingIntentDefault);
}
// Capture image with current device orientation
CGDataProviderRelease(dataProvider);
CGColorSpaceRelease(defaultRGBColorSpace);
});
return cgImageFromBytes;
}
- (void)newCIImageFromFramebufferContents:(void (^)(CIImage *image, void(^unlock)(void)))completion
{
// a CGImage can only be created from a 'normal' color texture
NSAssert(self.textureOptions.internalFormat == GL_RGBA, @"For conversion to a CGImage the output texture format for this filter must be GL_RGBA.");
NSAssert(self.textureOptions.type == GL_UNSIGNED_BYTE, @"For conversion to a CGImage the type of the output texture of this filter must be GL_UNSIGNED_BYTE.");
__block CIImage *ciImage;
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext useImageProcessingContext];
if ([GPUImageContext supportsFastTextureUpload])
{
glFinish();
CFRetain(renderTarget);
[self lockForReading];
[[GPUImageContext sharedFramebufferCache] addFramebufferToActiveImageCaptureList:self];
ciImage = [[CIImage alloc] initWithCVPixelBuffer:renderTarget options:nil];
}
});
completion(ciImage, ^{
runSynchronouslyOnVideoProcessingQueue(^{
[self restoreRenderTarget];
[self unlock];
[[GPUImageContext sharedFramebufferCache] removeFramebufferFromActiveImageCaptureList:self];
});
});
}
- (void)restoreRenderTarget
{
[self unlockAfterReading];
CFRelease(renderTarget);
}
#pragma mark -
#pragma mark Raw data bytes
- (void)lockForReading
{
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
if ([GPUImageContext supportsFastTextureUpload])
{
if (readLockCount == 0)
{
CVPixelBufferLockBaseAddress(renderTarget, 0);
}
readLockCount++;
}
#endif
}
- (void)unlockAfterReading
{
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
if ([GPUImageContext supportsFastTextureUpload])
{
NSAssert(readLockCount > 0, @"Unbalanced call to -[GPUImageFramebuffer unlockAfterReading]");
readLockCount--;
if (readLockCount == 0)
{
CVPixelBufferUnlockBaseAddress(renderTarget, 0);
}
}
#endif
}
- (NSUInteger)bytesPerRow
{
if ([GPUImageContext supportsFastTextureUpload])
{
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
return CVPixelBufferGetBytesPerRow(renderTarget);
#else
return _size.width * 4; // TODO: do more with this on the non-texture-cache side
#endif
}
else
{
return (NSUInteger)_size.width * 4;
}
}
- (GLubyte *)byteBuffer
{
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
[self lockForReading];
GLubyte * bufferBytes = CVPixelBufferGetBaseAddress(renderTarget);
[self unlockAfterReading];
return bufferBytes;
#else
return NULL; // TODO: do more with this on the non-texture-cache side
#endif
}
@end
#pragma clang diagnostic pop
@@ -0,0 +1,16 @@
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#import "GPUImageFramebuffer.h"
@interface GPUImageFramebufferCache : NSObject
// Framebuffer management
- (GPUImageFramebuffer *)fetchFramebufferForSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)textureOptions onlyTexture:(BOOL)onlyTexture;
- (GPUImageFramebuffer *)fetchFramebufferForSize:(CGSize)framebufferSize onlyTexture:(BOOL)onlyTexture;
- (GPUImageFramebuffer *)fetchFramebufferForSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)textureOptions onlyTexture:(BOOL)onlyTexture mark:(BOOL)mark;
- (void)returnFramebufferToCache:(GPUImageFramebuffer *)framebuffer;
- (void)purgeAllUnassignedFramebuffers;
- (void)addFramebufferToActiveImageCaptureList:(GPUImageFramebuffer *)framebuffer;
- (void)removeFramebufferFromActiveImageCaptureList:(GPUImageFramebuffer *)framebuffer;
@end
@@ -0,0 +1,196 @@
#import "GPUImageFramebufferCache.h"
#import "GPUImageContext.h"
#import "GPUImageOutput.h"
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#endif
@interface GPUImageFramebufferCache()
{
// NSCache *framebufferCache;
NSMutableDictionary *framebufferCache;
NSMutableDictionary *framebufferTypeCounts;
NSMutableArray *activeImageCaptureList; // Where framebuffers that may be lost by a filter, but which are still needed for a UIImage, etc., are stored
id memoryWarningObserver;
dispatch_queue_t framebufferCacheQueue;
}
- (NSString *)hashForSize:(CGSize)size textureOptions:(GPUTextureOptions)textureOptions onlyTexture:(BOOL)onlyTexture;
@end
@implementation GPUImageFramebufferCache
#pragma mark -
#pragma mark Initialization and teardown
- (id)init
{
if (!(self = [super init]))
{
return nil;
}
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
__weak GPUImageFramebufferCache *weakSelf = self;
memoryWarningObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:nil usingBlock:^(__unused NSNotification *note)
{
__strong GPUImageFramebufferCache *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf purgeAllUnassignedFramebuffers];
}];
#else
#endif
// framebufferCache = [[NSCache alloc] init];
framebufferCache = [[NSMutableDictionary alloc] init];
framebufferTypeCounts = [[NSMutableDictionary alloc] init];
activeImageCaptureList = [[NSMutableArray alloc] init];
framebufferCacheQueue = dispatch_queue_create("com.sunsetlakesoftware.GPUImage.framebufferCacheQueue", NULL);
return self;
}
- (void)dealloc
{
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] removeObserver:self];
#endif
}
#pragma mark -
#pragma mark Framebuffer management
- (NSString *)hashForSize:(CGSize)size textureOptions:(GPUTextureOptions)textureOptions onlyTexture:(BOOL)onlyTexture
{
if (onlyTexture)
{
return [NSString stringWithFormat:@"%.1fx%.1f-%d:%d:%d:%d:%d:%d:%d-NOFB", size.width, size.height, textureOptions.minFilter, textureOptions.magFilter, textureOptions.wrapS, textureOptions.wrapT, textureOptions.internalFormat, textureOptions.format, textureOptions.type];
}
else
{
return [NSString stringWithFormat:@"%.1fx%.1f-%d:%d:%d:%d:%d:%d:%d", size.width, size.height, textureOptions.minFilter, textureOptions.magFilter, textureOptions.wrapS, textureOptions.wrapT, textureOptions.internalFormat, textureOptions.format, textureOptions.type];
}
}
- (GPUImageFramebuffer *)fetchFramebufferForSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)textureOptions onlyTexture:(BOOL)onlyTexture {
return [self fetchFramebufferForSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture mark:false];
}
- (GPUImageFramebuffer *)fetchFramebufferForSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)textureOptions onlyTexture:(BOOL)onlyTexture mark:(BOOL)mark
{
__block GPUImageFramebuffer *framebufferFromCache = nil;
// dispatch_sync(framebufferCacheQueue, ^{
runSynchronouslyOnVideoProcessingQueue(^{
NSString *lookupHash = [self hashForSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture];
NSNumber *numberOfMatchingTexturesInCache = [framebufferTypeCounts objectForKey:lookupHash];
NSInteger numberOfMatchingTextures = [numberOfMatchingTexturesInCache integerValue];
if ([numberOfMatchingTexturesInCache integerValue] < 1)
{
// Nothing in the cache, create a new framebuffer to use
framebufferFromCache = [[GPUImageFramebuffer alloc] initWithSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture];
}
else
{
// Something found, pull the old framebuffer and decrement the count
NSInteger currentTextureID = (numberOfMatchingTextures - 1);
while ((framebufferFromCache == nil) && (currentTextureID >= 0))
{
NSString *textureHash = [NSString stringWithFormat:@"%@-%ld", lookupHash, (long)currentTextureID];
framebufferFromCache = [framebufferCache objectForKey:textureHash];
// Test the values in the cache first, to see if they got invalidated behind our back
if (framebufferFromCache != nil)
{
// Withdraw this from the cache while it's in use
[framebufferCache removeObjectForKey:textureHash];
}
currentTextureID--;
}
currentTextureID++;
[framebufferTypeCounts setObject:[NSNumber numberWithInteger:currentTextureID] forKey:lookupHash];
if (framebufferFromCache == nil)
{
framebufferFromCache = [[GPUImageFramebuffer alloc] initWithSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture];
}
}
});
[framebufferFromCache lock];
return framebufferFromCache;
}
- (GPUImageFramebuffer *)fetchFramebufferForSize:(CGSize)framebufferSize onlyTexture:(BOOL)onlyTexture
{
GPUTextureOptions defaultTextureOptions;
defaultTextureOptions.minFilter = GL_LINEAR;
defaultTextureOptions.magFilter = GL_LINEAR;
defaultTextureOptions.wrapS = GL_CLAMP_TO_EDGE;
defaultTextureOptions.wrapT = GL_CLAMP_TO_EDGE;
defaultTextureOptions.internalFormat = GL_RGBA;
defaultTextureOptions.format = GL_BGRA;
defaultTextureOptions.type = GL_UNSIGNED_BYTE;
return [self fetchFramebufferForSize:framebufferSize textureOptions:defaultTextureOptions onlyTexture:onlyTexture];
}
- (void)returnFramebufferToCache:(GPUImageFramebuffer *)framebuffer
{
[framebuffer clearAllLocks];
// dispatch_async(framebufferCacheQueue, ^{
runAsynchronouslyOnVideoProcessingQueue(^{
CGSize framebufferSize = framebuffer.size;
GPUTextureOptions framebufferTextureOptions = framebuffer.textureOptions;
NSString *lookupHash = [self hashForSize:framebufferSize textureOptions:framebufferTextureOptions onlyTexture:framebuffer.missingFramebuffer];
NSNumber *numberOfMatchingTexturesInCache = [framebufferTypeCounts objectForKey:lookupHash];
NSInteger numberOfMatchingTextures = [numberOfMatchingTexturesInCache integerValue];
NSString *textureHash = [NSString stringWithFormat:@"%@-%ld", lookupHash, (long)numberOfMatchingTextures];
// [framebufferCache setObject:framebuffer forKey:textureHash cost:round(framebufferSize.width * framebufferSize.height * 4.0)];
[framebufferCache setObject:framebuffer forKey:textureHash];
[framebufferTypeCounts setObject:[NSNumber numberWithInteger:(numberOfMatchingTextures + 1)] forKey:lookupHash];
});
}
- (void)purgeAllUnassignedFramebuffers
{
runAsynchronouslyOnVideoProcessingQueue(^{
// dispatch_async(framebufferCacheQueue, ^{
[framebufferCache removeAllObjects];
[framebufferTypeCounts removeAllObjects];
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CVOpenGLESTextureCacheFlush([[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache], 0);
#pragma clang diagnostic pop
#else
#endif
});
}
- (void)addFramebufferToActiveImageCaptureList:(GPUImageFramebuffer *)framebuffer
{
runAsynchronouslyOnVideoProcessingQueue(^{
// dispatch_async(framebufferCacheQueue, ^{
[activeImageCaptureList addObject:framebuffer];
});
}
- (void)removeFramebufferFromActiveImageCaptureList:(GPUImageFramebuffer *)framebuffer
{
runAsynchronouslyOnVideoProcessingQueue(^{
// dispatch_async(framebufferCacheQueue, ^{
[activeImageCaptureList removeObject:framebuffer];
});
}
@end
@@ -0,0 +1,36 @@
#import "GPUImageTwoPassTextureSamplingFilter.h"
/** A Gaussian blur filter
Interpolated optimization based on Daniel Rákos' work at http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/
*/
@interface GPUImageGaussianBlurFilter : GPUImageTwoPassTextureSamplingFilter
{
BOOL shouldResizeBlurRadiusWithImageSize;
CGFloat _blurRadiusInPixels;
}
/** A multiplier for the spacing between texels, ranging from 0.0 on up, with a default of 1.0. Adjusting this may slightly increase the blur strength, but will introduce artifacts in the result.
*/
@property (readwrite, nonatomic) CGFloat texelSpacingMultiplier;
/** A radius in pixels to use for the blur, with a default of 2.0. This adjusts the sigma variable in the Gaussian distribution function.
*/
@property (readwrite, nonatomic) CGFloat blurRadiusInPixels;
/** Setting these properties will allow the blur radius to scale with the size of the image. These properties are mutually exclusive; setting either will set the other to 0.
*/
@property (readwrite, nonatomic) CGFloat blurRadiusAsFractionOfImageWidth;
@property (readwrite, nonatomic) CGFloat blurRadiusAsFractionOfImageHeight;
/// The number of times to sequentially blur the incoming image. The more passes, the slower the filter.
@property(readwrite, nonatomic) NSUInteger blurPasses;
+ (NSString *)vertexShaderForStandardBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
+ (NSString *)fragmentShaderForStandardBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
+ (NSString *)vertexShaderForOptimizedBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
+ (NSString *)fragmentShaderForOptimizedBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
- (void)switchToVertexShader:(NSString *)newVertexShader fragmentShader:(NSString *)newFragmentShader;
@end
@@ -0,0 +1,490 @@
#import "GPUImageGaussianBlurFilter.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@implementation GPUImageGaussianBlurFilter
@synthesize texelSpacingMultiplier = _texelSpacingMultiplier;
@synthesize blurRadiusInPixels = _blurRadiusInPixels;
@synthesize blurRadiusAsFractionOfImageWidth = _blurRadiusAsFractionOfImageWidth;
@synthesize blurRadiusAsFractionOfImageHeight = _blurRadiusAsFractionOfImageHeight;
@synthesize blurPasses = _blurPasses;
#pragma mark -
#pragma mark Initialization and teardown
- (id)initWithFirstStageVertexShaderFromString:(NSString *)firstStageVertexShaderString firstStageFragmentShaderFromString:(NSString *)firstStageFragmentShaderString secondStageVertexShaderFromString:(NSString *)secondStageVertexShaderString secondStageFragmentShaderFromString:(NSString *)secondStageFragmentShaderString
{
if (!(self = [super initWithFirstStageVertexShaderFromString:firstStageVertexShaderString firstStageFragmentShaderFromString:firstStageFragmentShaderString secondStageVertexShaderFromString:secondStageVertexShaderString secondStageFragmentShaderFromString:secondStageFragmentShaderString]))
{
return nil;
}
self.texelSpacingMultiplier = 1.0;
_blurRadiusInPixels = 2.0;
shouldResizeBlurRadiusWithImageSize = NO;
return self;
}
- (id)init;
{
NSString *currentGaussianBlurVertexShader = [[self class] vertexShaderForOptimizedBlurOfRadius:4 sigma:2.0];
NSString *currentGaussianBlurFragmentShader = [[self class] fragmentShaderForOptimizedBlurOfRadius:4 sigma:2.0];
return [self initWithFirstStageVertexShaderFromString:currentGaussianBlurVertexShader firstStageFragmentShaderFromString:currentGaussianBlurFragmentShader secondStageVertexShaderFromString:currentGaussianBlurVertexShader secondStageFragmentShaderFromString:currentGaussianBlurFragmentShader];
}
#pragma mark -
#pragma mark Auto-generation of optimized Gaussian shaders
// "Implementation limit of 32 varying components exceeded" - Max number of varyings for these GPUs
+ (NSString *)vertexShaderForStandardBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
{
if (blurRadius < 1)
{
return kGPUImageVertexShaderString;
}
// NSLog(@"Max varyings: %d", [GPUImageContext maximumVaryingVectorsForThisDevice]);
NSMutableString *shaderString = [[NSMutableString alloc] init];
// Header
[shaderString appendFormat:@"\
attribute vec4 position;\n\
attribute vec4 inputTexCoord;\n\
\n\
uniform float texelWidthOffset;\n\
uniform float texelHeightOffset;\n\
\n\
varying vec2 blurCoordinates[%lu];\n\
\n\
void main()\n\
{\n\
gl_Position = position;\n\
\n\
vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n", (unsigned long)(blurRadius * 2 + 1) ];
// Inner offset loop
for (NSUInteger currentBlurCoordinateIndex = 0; currentBlurCoordinateIndex < (blurRadius * 2 + 1); currentBlurCoordinateIndex++)
{
NSInteger offsetFromCenter = currentBlurCoordinateIndex - blurRadius;
if (offsetFromCenter < 0)
{
[shaderString appendFormat:@"blurCoordinates[%ld] = inputTexCoord.xy - singleStepOffset * %f;\n", (unsigned long)currentBlurCoordinateIndex, (GLfloat)(-offsetFromCenter)];
}
else if (offsetFromCenter > 0)
{
[shaderString appendFormat:@"blurCoordinates[%ld] = inputTexCoord.xy + singleStepOffset * %f;\n", (unsigned long)currentBlurCoordinateIndex, (GLfloat)(offsetFromCenter)];
}
else
{
[shaderString appendFormat:@"blurCoordinates[%ld] = inputTexCoord.xy;\n", (unsigned long)currentBlurCoordinateIndex];
}
}
// Footer
[shaderString appendString:@"}\n"];
return shaderString;
}
+ (NSString *)fragmentShaderForStandardBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
{
if (blurRadius < 1)
{
return kGPUImagePassthroughFragmentShaderString;
}
// First, generate the normal Gaussian weights for a given sigma
GLfloat *standardGaussianWeights = calloc(blurRadius + 1, sizeof(GLfloat));
GLfloat sumOfWeights = 0.0;
for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
{
standardGaussianWeights[currentGaussianWeightIndex] = (1.0 / sqrt(2.0 * M_PI * pow(sigma, 2.0))) * exp(-pow(currentGaussianWeightIndex, 2.0) / (2.0 * pow(sigma, 2.0)));
if (currentGaussianWeightIndex == 0)
{
sumOfWeights += standardGaussianWeights[currentGaussianWeightIndex];
}
else
{
sumOfWeights += 2.0 * standardGaussianWeights[currentGaussianWeightIndex];
}
}
// Next, normalize these weights to prevent the clipping of the Gaussian curve at the end of the discrete samples from reducing luminance
for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
{
standardGaussianWeights[currentGaussianWeightIndex] = standardGaussianWeights[currentGaussianWeightIndex] / sumOfWeights;
}
// Finally, generate the shader from these weights
NSMutableString *shaderString = [[NSMutableString alloc] init];
// Header
[shaderString appendFormat:@"\
uniform sampler2D sourceImage;\n\
\n\
varying highp vec2 blurCoordinates[%lu];\n\
\n\
void main()\n\
{\n\
lowp vec4 sum = vec4(0.0);\n", (unsigned long)(blurRadius * 2 + 1) ];
// Inner texture loop
for (NSUInteger currentBlurCoordinateIndex = 0; currentBlurCoordinateIndex < (blurRadius * 2 + 1); currentBlurCoordinateIndex++)
{
NSInteger offsetFromCenter = currentBlurCoordinateIndex - blurRadius;
if (offsetFromCenter < 0)
{
[shaderString appendFormat:@"sum += texture2D(sourceImage, blurCoordinates[%lu]) * %f;\n", (unsigned long)currentBlurCoordinateIndex, standardGaussianWeights[-offsetFromCenter]];
}
else
{
[shaderString appendFormat:@"sum += texture2D(sourceImage, blurCoordinates[%lu]) * %f;\n", (unsigned long)currentBlurCoordinateIndex, standardGaussianWeights[offsetFromCenter]];
}
}
// Footer
[shaderString appendString:@"\
gl_FragColor = sum;\n\
}\n"];
free(standardGaussianWeights);
return shaderString;
}
+ (NSString *)vertexShaderForOptimizedBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
{
if (blurRadius < 1)
{
return kGPUImageVertexShaderString;
}
// First, generate the normal Gaussian weights for a given sigma
GLfloat *standardGaussianWeights = calloc(blurRadius + 1, sizeof(GLfloat));
GLfloat sumOfWeights = 0.0;
for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
{
standardGaussianWeights[currentGaussianWeightIndex] = (1.0 / sqrt(2.0 * M_PI * pow(sigma, 2.0))) * exp(-pow(currentGaussianWeightIndex, 2.0) / (2.0 * pow(sigma, 2.0)));
if (currentGaussianWeightIndex == 0)
{
sumOfWeights += standardGaussianWeights[currentGaussianWeightIndex];
}
else
{
sumOfWeights += 2.0 * standardGaussianWeights[currentGaussianWeightIndex];
}
}
// Next, normalize these weights to prevent the clipping of the Gaussian curve at the end of the discrete samples from reducing luminance
for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
{
standardGaussianWeights[currentGaussianWeightIndex] = standardGaussianWeights[currentGaussianWeightIndex] / sumOfWeights;
}
// From these weights we calculate the offsets to read interpolated values from
NSUInteger numberOfOptimizedOffsets = MIN(blurRadius / 2 + (blurRadius % 2), 7);
GLfloat *optimizedGaussianOffsets = calloc(numberOfOptimizedOffsets, sizeof(GLfloat));
for (NSUInteger currentOptimizedOffset = 0; currentOptimizedOffset < numberOfOptimizedOffsets; currentOptimizedOffset++)
{
GLfloat firstWeight = standardGaussianWeights[currentOptimizedOffset*2 + 1];
GLfloat secondWeight = standardGaussianWeights[currentOptimizedOffset*2 + 2];
GLfloat optimizedWeight = firstWeight + secondWeight;
optimizedGaussianOffsets[currentOptimizedOffset] = (firstWeight * (currentOptimizedOffset*2 + 1) + secondWeight * (currentOptimizedOffset*2 + 2)) / optimizedWeight;
}
NSMutableString *shaderString = [[NSMutableString alloc] init];
// Header
[shaderString appendFormat:@"\
attribute vec4 position;\n\
attribute vec4 inputTexCoord;\n\
\n\
uniform float texelWidthOffset;\n\
uniform float texelHeightOffset;\n\
\n\
varying vec2 blurCoordinates[%lu];\n\
\n\
void main()\n\
{\n\
gl_Position = position;\n\
\n\
vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n", (unsigned long)(1 + (numberOfOptimizedOffsets * 2))];
// Inner offset loop
[shaderString appendString:@"blurCoordinates[0] = inputTexCoord.xy;\n"];
for (NSUInteger currentOptimizedOffset = 0; currentOptimizedOffset < numberOfOptimizedOffsets; currentOptimizedOffset++)
{
[shaderString appendFormat:@"\
blurCoordinates[%lu] = inputTexCoord.xy + singleStepOffset * %f;\n\
blurCoordinates[%lu] = inputTexCoord.xy - singleStepOffset * %f;\n", (unsigned long)((currentOptimizedOffset * 2) + 1), optimizedGaussianOffsets[currentOptimizedOffset], (unsigned long)((currentOptimizedOffset * 2) + 2), optimizedGaussianOffsets[currentOptimizedOffset]];
}
// Footer
[shaderString appendString:@"}\n"];
free(optimizedGaussianOffsets);
free(standardGaussianWeights);
return shaderString;
}
+ (NSString *)fragmentShaderForOptimizedBlurOfRadius:(NSUInteger)blurRadius sigma:(CGFloat)sigma;
{
if (blurRadius < 1)
{
return kGPUImagePassthroughFragmentShaderString;
}
// First, generate the normal Gaussian weights for a given sigma
GLfloat *standardGaussianWeights = calloc(blurRadius + 1, sizeof(GLfloat));
GLfloat sumOfWeights = 0.0;
for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
{
standardGaussianWeights[currentGaussianWeightIndex] = (1.0 / sqrt(2.0 * M_PI * pow(sigma, 2.0))) * exp(-pow(currentGaussianWeightIndex, 2.0) / (2.0 * pow(sigma, 2.0)));
if (currentGaussianWeightIndex == 0)
{
sumOfWeights += standardGaussianWeights[currentGaussianWeightIndex];
}
else
{
sumOfWeights += 2.0 * standardGaussianWeights[currentGaussianWeightIndex];
}
}
// Next, normalize these weights to prevent the clipping of the Gaussian curve at the end of the discrete samples from reducing luminance
for (NSUInteger currentGaussianWeightIndex = 0; currentGaussianWeightIndex < blurRadius + 1; currentGaussianWeightIndex++)
{
standardGaussianWeights[currentGaussianWeightIndex] = standardGaussianWeights[currentGaussianWeightIndex] / sumOfWeights;
}
// From these weights we calculate the offsets to read interpolated values from
NSUInteger numberOfOptimizedOffsets = MIN(blurRadius / 2 + (blurRadius % 2), 7);
NSUInteger trueNumberOfOptimizedOffsets = blurRadius / 2 + (blurRadius % 2);
NSMutableString *shaderString = [[NSMutableString alloc] init];
// Header
[shaderString appendFormat:@"\
uniform sampler2D sourceImage;\n\
uniform highp float texelWidthOffset;\n\
uniform highp float texelHeightOffset;\n\
\n\
varying highp vec2 blurCoordinates[%lu];\n\
\n\
void main()\n\
{\n\
lowp vec4 sum = vec4(0.0);\n", (unsigned long)(1 + (numberOfOptimizedOffsets * 2)) ];
// Inner texture loop
[shaderString appendFormat:@"sum += texture2D(sourceImage, blurCoordinates[0]) * %f;\n", standardGaussianWeights[0]];
for (NSUInteger currentBlurCoordinateIndex = 0; currentBlurCoordinateIndex < numberOfOptimizedOffsets; currentBlurCoordinateIndex++)
{
GLfloat firstWeight = standardGaussianWeights[currentBlurCoordinateIndex * 2 + 1];
GLfloat secondWeight = standardGaussianWeights[currentBlurCoordinateIndex * 2 + 2];
GLfloat optimizedWeight = firstWeight + secondWeight;
[shaderString appendFormat:@"sum += texture2D(sourceImage, blurCoordinates[%lu]) * %f;\n", (unsigned long)((currentBlurCoordinateIndex * 2) + 1), optimizedWeight];
[shaderString appendFormat:@"sum += texture2D(sourceImage, blurCoordinates[%lu]) * %f;\n", (unsigned long)((currentBlurCoordinateIndex * 2) + 2), optimizedWeight];
}
// If the number of required samples exceeds the amount we can pass in via varyings, we have to do dependent texture reads in the fragment shader
if (trueNumberOfOptimizedOffsets > numberOfOptimizedOffsets)
{
[shaderString appendString:@"highp vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);\n"];
for (NSUInteger currentOverlowTextureRead = numberOfOptimizedOffsets; currentOverlowTextureRead < trueNumberOfOptimizedOffsets; currentOverlowTextureRead++)
{
GLfloat firstWeight = standardGaussianWeights[currentOverlowTextureRead * 2 + 1];
GLfloat secondWeight = standardGaussianWeights[currentOverlowTextureRead * 2 + 2];
GLfloat optimizedWeight = firstWeight + secondWeight;
GLfloat optimizedOffset = (firstWeight * (currentOverlowTextureRead * 2 + 1) + secondWeight * (currentOverlowTextureRead * 2 + 2)) / optimizedWeight;
[shaderString appendFormat:@"sum += texture2D(sourceImage, blurCoordinates[0] + singleStepOffset * %f) * %f;\n", optimizedOffset, optimizedWeight];
[shaderString appendFormat:@"sum += texture2D(sourceImage, blurCoordinates[0] - singleStepOffset * %f) * %f;\n", optimizedOffset, optimizedWeight];
}
}
// Footer
[shaderString appendString:@"\
gl_FragColor = sum;\n\
}\n"];
free(standardGaussianWeights);
return shaderString;
}
- (void)setupFilterForSize:(CGSize)filterFrameSize;
{
[super setupFilterForSize:filterFrameSize];
if (shouldResizeBlurRadiusWithImageSize)
{
if (self.blurRadiusAsFractionOfImageWidth > 0)
{
self.blurRadiusInPixels = filterFrameSize.width * self.blurRadiusAsFractionOfImageWidth;
}
else
{
self.blurRadiusInPixels = filterFrameSize.height * self.blurRadiusAsFractionOfImageHeight;
}
}
}
#pragma mark -
#pragma mark Rendering
- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;
{
[super renderToTextureWithVertices:vertices textureCoordinates:textureCoordinates];
for (NSUInteger currentAdditionalBlurPass = 1; currentAdditionalBlurPass < _blurPasses; currentAdditionalBlurPass++)
{
[super renderToTextureWithVertices:vertices textureCoordinates:[[self class] textureCoordinatesForRotation:kGPUImageNoRotation]];
}
}
- (void)switchToVertexShader:(NSString *)newVertexShader fragmentShader:(NSString *)newFragmentShader;
{
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext useImageProcessingContext];
filterProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:newVertexShader fragmentShaderString:newFragmentShader];
if (!filterProgram.initialized)
{
[self initializeAttributes];
if (![filterProgram link])
{
NSString *progLog = [filterProgram programLog];
NSLog(@"Program link log: %@", progLog);
NSString *fragLog = [filterProgram fragmentShaderLog];
NSLog(@"Fragment shader compile log: %@", fragLog);
NSString *vertLog = [filterProgram vertexShaderLog];
NSLog(@"Vertex shader compile log: %@", vertLog);
filterProgram = nil;
NSAssert(NO, @"Filter shader link failed");
}
}
filterPositionAttribute = [filterProgram attributeIndex:@"position"];
filterTextureCoordinateAttribute = [filterProgram attributeIndex:@"inputTexCoord"];
filterInputTextureUniform = [filterProgram uniformIndex:@"sourceImage"]; // This does assume a name of "inputImageTexture" for the fragment shader
verticalPassTexelWidthOffsetUniform = [filterProgram uniformIndex:@"texelWidthOffset"];
verticalPassTexelHeightOffsetUniform = [filterProgram uniformIndex:@"texelHeightOffset"];
[GPUImageContext setActiveShaderProgram:filterProgram];
glEnableVertexAttribArray(filterPositionAttribute);
glEnableVertexAttribArray(filterTextureCoordinateAttribute);
secondFilterProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:newVertexShader fragmentShaderString:newFragmentShader];
if (!secondFilterProgram.initialized)
{
[self initializeSecondaryAttributes];
if (![secondFilterProgram link])
{
NSString *progLog = [secondFilterProgram programLog];
NSLog(@"Program link log: %@", progLog);
NSString *fragLog = [secondFilterProgram fragmentShaderLog];
NSLog(@"Fragment shader compile log: %@", fragLog);
NSString *vertLog = [secondFilterProgram vertexShaderLog];
NSLog(@"Vertex shader compile log: %@", vertLog);
secondFilterProgram = nil;
NSAssert(NO, @"Filter shader link failed");
}
}
secondFilterPositionAttribute = [secondFilterProgram attributeIndex:@"position"];
secondFilterTextureCoordinateAttribute = [secondFilterProgram attributeIndex:@"inputTexCoord"];
secondFilterInputTextureUniform = [secondFilterProgram uniformIndex:@"sourceImage"]; // This does assume a name of "inputImageTexture" for the fragment shader
secondFilterInputTextureUniform2 = [secondFilterProgram uniformIndex:@"inputImageTexture2"]; // This does assume a name of "inputImageTexture2" for second input texture in the fragment shader
horizontalPassTexelWidthOffsetUniform = [secondFilterProgram uniformIndex:@"texelWidthOffset"];
horizontalPassTexelHeightOffsetUniform = [secondFilterProgram uniformIndex:@"texelHeightOffset"];
[GPUImageContext setActiveShaderProgram:secondFilterProgram];
glEnableVertexAttribArray(secondFilterPositionAttribute);
glEnableVertexAttribArray(secondFilterTextureCoordinateAttribute);
[self setupFilterForSize:[self sizeOfFBO]];
glFinish();
});
}
#pragma mark -
#pragma mark Accessors
- (void)setTexelSpacingMultiplier:(CGFloat)newValue;
{
_texelSpacingMultiplier = newValue;
_verticalTexelSpacing = _texelSpacingMultiplier;
_horizontalTexelSpacing = _texelSpacingMultiplier;
[self setupFilterForSize:[self sizeOfFBO]];
}
// inputRadius for Core Image's CIGaussianBlur is really sigma in the Gaussian equation, so I'm using that for my blur radius, to be consistent
- (void)setBlurRadiusInPixels:(CGFloat)newValue;
{
// 7.0 is the limit for blur size for hardcoded varying offsets
if (round(newValue) != _blurRadiusInPixels)
{
_blurRadiusInPixels = round(newValue); // For now, only do integral sigmas
NSUInteger calculatedSampleRadius = 0;
if (_blurRadiusInPixels >= 1) // Avoid a divide-by-zero error here
{
// Calculate the number of pixels to sample from by setting a bottom limit for the contribution of the outermost pixel
CGFloat minimumWeightToFindEdgeOfSamplingArea = 1.0/256.0;
calculatedSampleRadius = floor(sqrt(-2.0 * pow(_blurRadiusInPixels, 2.0) * log(minimumWeightToFindEdgeOfSamplingArea * sqrt(2.0 * M_PI * pow(_blurRadiusInPixels, 2.0))) ));
calculatedSampleRadius += calculatedSampleRadius % 2; // There's nothing to gain from handling odd radius sizes, due to the optimizations I use
}
// NSLog(@"Blur radius: %f, calculated sample radius: %d", _blurRadiusInPixels, calculatedSampleRadius);
//
NSString *newGaussianBlurVertexShader = [[self class] vertexShaderForOptimizedBlurOfRadius:calculatedSampleRadius sigma:_blurRadiusInPixels];
NSString *newGaussianBlurFragmentShader = [[self class] fragmentShaderForOptimizedBlurOfRadius:calculatedSampleRadius sigma:_blurRadiusInPixels];
// NSLog(@"Optimized vertex shader: \n%@", newGaussianBlurVertexShader);
// NSLog(@"Optimized fragment shader: \n%@", newGaussianBlurFragmentShader);
//
[self switchToVertexShader:newGaussianBlurVertexShader fragmentShader:newGaussianBlurFragmentShader];
}
shouldResizeBlurRadiusWithImageSize = NO;
}
- (void)setBlurRadiusAsFractionOfImageWidth:(CGFloat)blurRadiusAsFractionOfImageWidth
{
if (blurRadiusAsFractionOfImageWidth < 0) return;
shouldResizeBlurRadiusWithImageSize = _blurRadiusAsFractionOfImageWidth != blurRadiusAsFractionOfImageWidth && blurRadiusAsFractionOfImageWidth > 0;
_blurRadiusAsFractionOfImageWidth = blurRadiusAsFractionOfImageWidth;
_blurRadiusAsFractionOfImageHeight = 0;
}
- (void)setBlurRadiusAsFractionOfImageHeight:(CGFloat)blurRadiusAsFractionOfImageHeight
{
if (blurRadiusAsFractionOfImageHeight < 0) return;
shouldResizeBlurRadiusWithImageSize = _blurRadiusAsFractionOfImageHeight != blurRadiusAsFractionOfImageHeight && blurRadiusAsFractionOfImageHeight > 0;
_blurRadiusAsFractionOfImageHeight = blurRadiusAsFractionOfImageHeight;
_blurRadiusAsFractionOfImageWidth = 0;
}
@end
#pragma clang diagnostic pop
+91
View File
@@ -0,0 +1,91 @@
#import "GPUImageContext.h"
#import "GPUImageFramebuffer.h"
#import <UIKit/UIKit.h>
#import <CoreImage/CoreImage.h>
void runOnMainQueueWithoutDeadlocking(void (^block)(void));
void runSynchronouslyOnVideoProcessingQueue(void (^block)(void));
void runAsynchronouslyOnVideoProcessingQueue(void (^block)(void));
void runSynchronouslyOnContextQueue(GPUImageContext *context, void (^block)(void));
void runAsynchronouslyOnContextQueue(GPUImageContext *context, void (^block)(void));
void reportAvailableMemoryForGPUImage(NSString *tag);
@interface GPUImageOutput : NSObject
{
GPUImageFramebuffer *outputFramebuffer;
NSMutableArray *targets, *targetTextureIndices;
CGSize inputTextureSize, cachedMaximumOutputSize, forcedMaximumSize;
BOOL overrideInputSize;
BOOL allTargetsWantMonochromeData;
BOOL usingNextFrameForImageCapture;
}
@property(readwrite, nonatomic) BOOL shouldSmoothlyScaleOutput;
@property(readwrite, nonatomic) BOOL shouldIgnoreUpdatesToThisTarget;
@property(readwrite, nonatomic, unsafe_unretained) id<GPUImageInput> targetToIgnoreForUpdates;
@property(nonatomic, copy) void(^frameProcessingCompletionBlock)(GPUImageOutput*, CMTime);
@property(nonatomic) BOOL enabled;
@property(readwrite, nonatomic) GPUTextureOptions outputTextureOptions;
- (void)setInputFramebufferForTarget:(id<GPUImageInput>)target atIndex:(NSInteger)inputTextureIndex;
- (GPUImageFramebuffer *)framebufferForOutput;
- (void)removeOutputFramebuffer;
- (void)notifyTargetsAboutNewOutputTexture;
- (CGSize)inputTextureSize;
/** Returns an array of the current targets.
*/
- (NSArray*)targets;
/** Adds a target to receive notifications when new frames are available.
The target will be asked for its next available texture.
See [GPUImageInput newFrameReadyAtTime:]
@param newTarget Target to be added
*/
- (void)addTarget:(id<GPUImageInput>)newTarget;
/** Adds a target to receive notifications when new frames are available.
See [GPUImageInput newFrameReadyAtTime:]
@param newTarget Target to be added
*/
- (void)addTarget:(id<GPUImageInput>)newTarget atTextureLocation:(NSInteger)textureLocation;
/** Removes a target. The target will no longer receive notifications when new frames are available.
@param targetToRemove Target to be removed
*/
- (void)removeTarget:(id<GPUImageInput>)targetToRemove;
/** Removes all targets.
*/
- (void)removeAllTargets;
/// @name Manage the output texture
- (void)forceProcessingAtSize:(CGSize)frameSize;
- (void)forceProcessingAtSizeRespectingAspectRatio:(CGSize)frameSize;
/// @name Still image processing
- (void)useNextFrameForImageCapture;
- (CGImageRef)newCGImageFromCurrentlyProcessedOutput;
- (void)newCIImageFromCurrentlyProcessedOutput:(void (^)(CIImage *image, void(^unlock)(void)))completion;
- (void)commitImageCapture;
- (UIImage *)imageFromCurrentFramebuffer;
- (UIImage *)imageFromCurrentFramebufferWithOrientation:(UIImageOrientation)imageOrientation;
- (BOOL)providesMonochromeOutput;
@end
+372
View File
@@ -0,0 +1,372 @@
#import "GPUImageOutput.h"
//#import "GPUImagePicture.h"
#import <mach/mach.h>
void runOnMainQueueWithoutDeadlocking(void (^block)(void))
{
if ([NSThread isMainThread])
{
block();
}
else
{
dispatch_sync(dispatch_get_main_queue(), block);
}
}
void runSynchronouslyOnVideoProcessingQueue(void (^block)(void))
{
dispatch_queue_t videoProcessingQueue = [GPUImageContext sharedContextQueue];
#if !OS_OBJECT_USE_OBJC
if (dispatch_get_current_queue() == videoProcessingQueue)
#else
if (dispatch_get_specific([GPUImageContext contextKey]))
#endif
{
block();
}else
{
dispatch_sync(videoProcessingQueue, block);
}
}
void runAsynchronouslyOnVideoProcessingQueue(void (^block)(void))
{
dispatch_queue_t videoProcessingQueue = [GPUImageContext sharedContextQueue];
#if !OS_OBJECT_USE_OBJC
if (dispatch_get_current_queue() == videoProcessingQueue)
#else
if (dispatch_get_specific([GPUImageContext contextKey]))
#endif
{
block();
}else
{
dispatch_async(videoProcessingQueue, block);
}
}
void runSynchronouslyOnContextQueue(GPUImageContext *context, void (^block)(void))
{
dispatch_queue_t videoProcessingQueue = [context contextQueue];
#if !OS_OBJECT_USE_OBJC
if (dispatch_get_current_queue() == videoProcessingQueue)
#else
if (dispatch_get_specific([GPUImageContext contextKey]))
#endif
{
block();
}else
{
dispatch_sync(videoProcessingQueue, block);
}
}
void runAsynchronouslyOnContextQueue(GPUImageContext *context, void (^block)(void))
{
dispatch_queue_t videoProcessingQueue = [context contextQueue];
#if !OS_OBJECT_USE_OBJC
if (dispatch_get_current_queue() == videoProcessingQueue)
#else
if (dispatch_get_specific([GPUImageContext contextKey]))
#endif
{
block();
}else
{
dispatch_async(videoProcessingQueue, block);
}
}
void reportAvailableMemoryForGPUImage(NSString *tag)
{
if (!tag)
tag = @"Default";
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kerr = task_info(mach_task_self(),
TASK_BASIC_INFO,
(task_info_t)&info,
&size);
if( kerr == KERN_SUCCESS ) {
NSLog(@"%@ - Memory used: %u", tag, (unsigned int)info.resident_size); //in bytes
} else {
NSLog(@"%@ - Error: %s", tag, mach_error_string(kerr));
}
}
@implementation GPUImageOutput
@synthesize shouldSmoothlyScaleOutput = _shouldSmoothlyScaleOutput;
@synthesize shouldIgnoreUpdatesToThisTarget = _shouldIgnoreUpdatesToThisTarget;
@synthesize targetToIgnoreForUpdates = _targetToIgnoreForUpdates;
@synthesize frameProcessingCompletionBlock = _frameProcessingCompletionBlock;
@synthesize enabled = _enabled;
@synthesize outputTextureOptions = _outputTextureOptions;
#pragma mark -
#pragma mark Initialization and teardown
- (id)init
{
if (!(self = [super init]))
{
return nil;
}
targets = [[NSMutableArray alloc] init];
targetTextureIndices = [[NSMutableArray alloc] init];
_enabled = YES;
allTargetsWantMonochromeData = YES;
usingNextFrameForImageCapture = NO;
// set default texture options
_outputTextureOptions.minFilter = GL_LINEAR;
_outputTextureOptions.magFilter = GL_LINEAR;
_outputTextureOptions.wrapS = GL_CLAMP_TO_EDGE;
_outputTextureOptions.wrapT = GL_CLAMP_TO_EDGE;
_outputTextureOptions.internalFormat = GL_RGBA;
_outputTextureOptions.format = GL_BGRA;
_outputTextureOptions.type = GL_UNSIGNED_BYTE;
return self;
}
- (void)dealloc
{
[self removeAllTargets];
}
#pragma mark -
#pragma mark Managing targets
- (void)setInputFramebufferForTarget:(id<GPUImageInput>)target atIndex:(NSInteger)inputTextureIndex
{
[target setInputFramebuffer:[self framebufferForOutput] atIndex:inputTextureIndex];
}
- (GPUImageFramebuffer *)framebufferForOutput
{
return outputFramebuffer;
}
- (void)removeOutputFramebuffer
{
outputFramebuffer = nil;
}
- (void)notifyTargetsAboutNewOutputTexture
{
for (id<GPUImageInput> currentTarget in targets)
{
NSInteger indexOfObject = [targets indexOfObject:currentTarget];
NSInteger textureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
[self setInputFramebufferForTarget:currentTarget atIndex:textureIndex];
}
}
- (NSArray*)targets
{
return [NSArray arrayWithArray:targets];
}
- (void)addTarget:(id<GPUImageInput>)newTarget
{
NSInteger nextAvailableTextureIndex = [newTarget nextAvailableTextureIndex];
[self addTarget:newTarget atTextureLocation:nextAvailableTextureIndex];
if ([newTarget shouldIgnoreUpdatesToThisTarget])
{
_targetToIgnoreForUpdates = newTarget;
}
}
- (void)addTarget:(id<GPUImageInput>)newTarget atTextureLocation:(NSInteger)textureLocation
{
if (newTarget == nil || [targets containsObject:newTarget])
{
return;
}
cachedMaximumOutputSize = CGSizeZero;
runSynchronouslyOnVideoProcessingQueue(^{
[self setInputFramebufferForTarget:newTarget atIndex:textureLocation];
[targets addObject:newTarget];
[targetTextureIndices addObject:[NSNumber numberWithInteger:textureLocation]];
allTargetsWantMonochromeData = allTargetsWantMonochromeData && [newTarget wantsMonochromeInput];
});
}
- (void)removeTarget:(id<GPUImageInput>)targetToRemove
{
if(![targets containsObject:targetToRemove])
{
return;
}
if (_targetToIgnoreForUpdates == targetToRemove)
{
_targetToIgnoreForUpdates = nil;
}
cachedMaximumOutputSize = CGSizeZero;
NSInteger indexOfObject = [targets indexOfObject:targetToRemove];
NSInteger textureIndexOfTarget = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
runSynchronouslyOnVideoProcessingQueue(^{
[targetToRemove setInputSize:CGSizeZero atIndex:textureIndexOfTarget];
[targetToRemove setInputRotation:kGPUImageNoRotation atIndex:textureIndexOfTarget];
[targetTextureIndices removeObjectAtIndex:indexOfObject];
[targets removeObject:targetToRemove];
[targetToRemove endProcessing];
});
}
- (void)removeAllTargets
{
cachedMaximumOutputSize = CGSizeZero;
runSynchronouslyOnVideoProcessingQueue(^{
for (id<GPUImageInput> targetToRemove in targets)
{
NSInteger indexOfObject = [targets indexOfObject:targetToRemove];
NSInteger textureIndexOfTarget = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
[targetToRemove setInputSize:CGSizeZero atIndex:textureIndexOfTarget];
[targetToRemove setInputRotation:kGPUImageNoRotation atIndex:textureIndexOfTarget];
}
[targets removeAllObjects];
[targetTextureIndices removeAllObjects];
allTargetsWantMonochromeData = YES;
});
}
#pragma mark -
#pragma mark Manage the output texture
- (void)forceProcessingAtSize:(CGSize)__unused frameSize
{
}
- (void)forceProcessingAtSizeRespectingAspectRatio:(CGSize)__unused frameSize
{
}
#pragma mark -
#pragma mark Still image processing
- (void)useNextFrameForImageCapture
{
}
- (CGImageRef)newCGImageFromCurrentlyProcessedOutput
{
return nil;
}
- (void)newCIImageFromCurrentlyProcessedOutput:(void (^)(CIImage *image, void(^unlock)(void)))completion
{
}
- (void)commitImageCapture
{
}
- (BOOL)providesMonochromeOutput
{
return NO;
}
#pragma mark -
#pragma mark Platform-specific image output methods
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
- (UIImage *)imageFromCurrentFramebuffer
{
UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation];
UIImageOrientation imageOrientation = UIImageOrientationLeft;
switch (deviceOrientation)
{
case UIDeviceOrientationPortrait:
imageOrientation = UIImageOrientationUp;
break;
case UIDeviceOrientationPortraitUpsideDown:
imageOrientation = UIImageOrientationDown;
break;
case UIDeviceOrientationLandscapeLeft:
imageOrientation = UIImageOrientationLeft;
break;
case UIDeviceOrientationLandscapeRight:
imageOrientation = UIImageOrientationRight;
break;
default:
imageOrientation = UIImageOrientationUp;
break;
}
return [self imageFromCurrentFramebufferWithOrientation:imageOrientation];
}
- (UIImage *)imageFromCurrentFramebufferWithOrientation:(UIImageOrientation)imageOrientation
{
CGImageRef cgImageFromBytes = [self newCGImageFromCurrentlyProcessedOutput];
UIImage *finalImage = [UIImage imageWithCGImage:cgImageFromBytes scale:1.0 orientation:imageOrientation];
CGImageRelease(cgImageFromBytes);
return finalImage;
}
- (CGSize)inputTextureSize
{
return inputTextureSize;
}
#else
- (NSImage *)imageFromCurrentFramebuffer;
{
return [self imageFromCurrentFramebufferWithOrientation:UIImageOrientationLeft];
}
- (NSImage *)imageFromCurrentFramebufferWithOrientation:(UIImageOrientation)imageOrientation;
{
CGImageRef cgImageFromBytes = [self newCGImageFromCurrentlyProcessedOutput];
NSImage *finalImage = [[NSImage alloc] initWithCGImage:cgImageFromBytes size:NSZeroSize];
CGImageRelease(cgImageFromBytes);
return finalImage;
}
- (NSImage *)imageByFilteringImage:(NSImage *)imageToFilter;
{
CGImageRef image = [self newCGImageByFilteringCGImage:[imageToFilter CGImageForProposedRect:NULL context:[NSGraphicsContext currentContext] hints:nil]];
NSImage *processedImage = [[NSImage alloc] initWithCGImage:image size:NSZeroSize];
CGImageRelease(image);
return processedImage;
}
- (CGImageRef)newCGImageByFilteringImage:(NSImage *)imageToFilter
{
return [self newCGImageByFilteringCGImage:[imageToFilter CGImageForProposedRect:NULL context:[NSGraphicsContext currentContext] hints:nil]];
}
#endif
@end
@@ -0,0 +1,12 @@
#import "GPUImageFilter.h"
@interface GPUImageSharpenFilter : GPUImageFilter
{
GLint sharpnessUniform;
GLint imageWidthFactorUniform, imageHeightFactorUniform;
}
// Sharpness ranges from -4.0 to 4.0, with 0.0 as the normal level
@property(readwrite, nonatomic) CGFloat sharpness;
@end
+124
View File
@@ -0,0 +1,124 @@
#import "GPUImageSharpenFilter.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSString *const kGPUImageSharpenVertexShaderString = SHADER_STRING
(
attribute vec4 position;
attribute vec4 inputTexCoord;
uniform float imageWidthFactor;
uniform float imageHeightFactor;
uniform float sharpness;
varying vec2 texCoord;
varying vec2 leftTextureCoordinate;
varying vec2 rightTextureCoordinate;
varying vec2 topTextureCoordinate;
varying vec2 bottomTextureCoordinate;
varying float centerMultiplier;
varying float edgeMultiplier;
void main()
{
gl_Position = position;
vec2 widthStep = vec2(imageWidthFactor, 0.0);
vec2 heightStep = vec2(0.0, imageHeightFactor);
texCoord = inputTexCoord.xy;
leftTextureCoordinate = inputTexCoord.xy - widthStep;
rightTextureCoordinate = inputTexCoord.xy + widthStep;
topTextureCoordinate = inputTexCoord.xy + heightStep;
bottomTextureCoordinate = inputTexCoord.xy - heightStep;
centerMultiplier = 1.0 + 4.0 * sharpness;
edgeMultiplier = sharpness;
}
);
NSString *const kGPUImageSharpenFragmentShaderString = SHADER_STRING
(
precision highp float;
varying highp vec2 texCoord;
varying highp vec2 leftTextureCoordinate;
varying highp vec2 rightTextureCoordinate;
varying highp vec2 topTextureCoordinate;
varying highp vec2 bottomTextureCoordinate;
varying highp float centerMultiplier;
varying highp float edgeMultiplier;
uniform sampler2D sourceImage;
void main()
{
mediump vec3 textureColor = texture2D(sourceImage, texCoord).rgb;
mediump vec3 leftTextureColor = texture2D(sourceImage, leftTextureCoordinate).rgb;
mediump vec3 rightTextureColor = texture2D(sourceImage, rightTextureCoordinate).rgb;
mediump vec3 topTextureColor = texture2D(sourceImage, topTextureCoordinate).rgb;
mediump vec3 bottomTextureColor = texture2D(sourceImage, bottomTextureCoordinate).rgb;
gl_FragColor = vec4((textureColor * centerMultiplier - (leftTextureColor * edgeMultiplier + rightTextureColor * edgeMultiplier + topTextureColor * edgeMultiplier + bottomTextureColor * edgeMultiplier)), texture2D(sourceImage, bottomTextureCoordinate).w);
}
);
@implementation GPUImageSharpenFilter
@synthesize sharpness = _sharpness;
#pragma mark -
#pragma mark Initialization and teardown
- (id)init;
{
if (!(self = [super initWithVertexShaderFromString:kGPUImageSharpenVertexShaderString fragmentShaderFromString:kGPUImageSharpenFragmentShaderString]))
{
return nil;
}
sharpnessUniform = [filterProgram uniformIndex:@"sharpness"];
self.sharpness = 0.0;
imageWidthFactorUniform = [filterProgram uniformIndex:@"imageWidthFactor"];
imageHeightFactorUniform = [filterProgram uniformIndex:@"imageHeightFactor"];
return self;
}
- (void)setupFilterForSize:(CGSize)filterFrameSize;
{
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext setActiveShaderProgram:filterProgram];
if (GPUImageRotationSwapsWidthAndHeight(inputRotation))
{
glUniform1f(imageWidthFactorUniform, 1.0 / filterFrameSize.height);
glUniform1f(imageHeightFactorUniform, 1.0 / filterFrameSize.width);
}
else
{
glUniform1f(imageWidthFactorUniform, 1.0 / filterFrameSize.width);
glUniform1f(imageHeightFactorUniform, 1.0 / filterFrameSize.height);
}
});
}
#pragma mark -
#pragma mark Accessors
- (void)setSharpness:(CGFloat)newValue;
{
_sharpness = newValue;
[self setFloat:_sharpness forUniform:sharpnessUniform program:filterProgram];
}
@end
#pragma clang diagnostic pop
+18
View File
@@ -0,0 +1,18 @@
#import "GPUImageOutput.h"
#import <CoreImage/CoreImage.h>
@interface GPUImageTextureInput : GPUImageOutput
{
CGSize textureSize;
}
- (instancetype)initWithTexture:(GLuint)newInputTexture size:(CGSize)newTextureSize;
- (instancetype)initWithCIImage:(CIImage *)ciImage;
- (void)setCIImage:(CIImage *)ciImage;
- (void)processTextureWithFrameTime:(CMTime)frameTime synchronous:(bool)synchronous completion:(void (^)(void))completion;
- (CGSize)textureSize;
@end
+148
View File
@@ -0,0 +1,148 @@
#import "GPUImageTextureInput.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@implementation GPUImageTextureInput
{
CIContext *ciContext;
}
#pragma mark -
#pragma mark Initialization and teardown
- (instancetype)initWithTexture:(GLuint)newInputTexture size:(CGSize)newTextureSize
{
if (!(self = [super init]))
{
return nil;
}
textureSize = newTextureSize;
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext useImageProcessingContext];
outputFramebuffer = [[GPUImageFramebuffer alloc] initWithSize:newTextureSize overriddenTexture:newInputTexture];
});
return self;
}
- (instancetype)initWithCIImage:(CIImage *)ciImage
{
if (!(self = [super init]))
{
return nil;
}
textureSize = ciImage.extent.size;
runSynchronouslyOnVideoProcessingQueue(^{
EAGLContext *context = [[GPUImageContext sharedImageProcessingContext] context];
[EAGLContext setCurrentContext:context];
GLsizei backingWidth = ciImage.extent.size.width;
GLsizei backingHeight = ciImage.extent.size.height;
GLuint outputTexture, defaultFramebuffer;
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &outputTexture);
glBindTexture(GL_TEXTURE_2D, outputTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE1);
glGenFramebuffers(1, &defaultFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);
glBindTexture(GL_TEXTURE_2D, outputTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, backingWidth, backingHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, outputTexture, 0);
NSAssert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, @"Incomplete filter FBO: %d", glCheckFramebufferStatus(GL_FRAMEBUFFER));
glBindTexture(GL_TEXTURE_2D, 0);
CIImage *updatedCIImage = [ciImage imageByApplyingTransform:CGAffineTransformConcat(CGAffineTransformMakeScale(1.0f, -1.0f), CGAffineTransformMakeTranslation(0.0f, ciImage.extent.size.height))];
ciContext = [CIContext contextWithEAGLContext:context options:@{kCIContextWorkingColorSpace: [NSNull null]}];
[ciContext drawImage:updatedCIImage inRect:updatedCIImage.extent fromRect:updatedCIImage.extent];
[GPUImageContext useImageProcessingContext];
outputFramebuffer = [[GPUImageFramebuffer alloc] initWithSize:ciImage.extent.size overridenFramebuffer:defaultFramebuffer overriddenTexture:outputTexture];
});
return self;
}
- (void)dealloc {
NSLog(@"deall texinp");
}
- (void)setCIImage:(CIImage *)ciImage {
runSynchronouslyOnVideoProcessingQueue(^{
EAGLContext *context = [[GPUImageContext sharedImageProcessingContext] context];
[EAGLContext setCurrentContext:context];
GLsizei backingWidth = ciImage.extent.size.width;
GLsizei backingHeight = ciImage.extent.size.height;
GLint outputTexture = outputFramebuffer.texture;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, outputTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE1);
[outputFramebuffer useFramebuffer];
glBindTexture(GL_TEXTURE_2D, outputTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, backingWidth, backingHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, outputTexture, 0);
NSAssert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, @"Incomplete filter FBO: %d", glCheckFramebufferStatus(GL_FRAMEBUFFER));
glBindTexture(GL_TEXTURE_2D, 0);
CIImage *updatedCIImage = [ciImage imageByApplyingTransform:CGAffineTransformConcat(CGAffineTransformMakeScale(1.0f, -1.0f), CGAffineTransformMakeTranslation(0.0f, ciImage.extent.size.height))];
[ciContext drawImage:updatedCIImage inRect:updatedCIImage.extent fromRect:updatedCIImage.extent];
});
}
- (void)processTextureWithFrameTime:(CMTime)frameTime synchronous:(bool)synchronous completion:(void (^)(void))completion
{
void (^block)(void) = ^
{
for (id<GPUImageInput> currentTarget in targets)
{
NSInteger indexOfObject = [targets indexOfObject:currentTarget];
NSInteger targetTextureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
[currentTarget setInputSize:textureSize atIndex:targetTextureIndex];
[currentTarget setInputFramebuffer:outputFramebuffer atIndex:targetTextureIndex];
[currentTarget newFrameReadyAtTime:frameTime atIndex:targetTextureIndex];
}
if (completion != nil)
completion();
};
if (synchronous)
runSynchronouslyOnVideoProcessingQueue(block);
else
runAsynchronouslyOnVideoProcessingQueue(block);
}
- (CGSize)textureSize {
return textureSize;
}
@end
#pragma clang diagnostic pop
@@ -0,0 +1,21 @@
#import "GPUImageTwoInputFilter.h"
extern NSString *const kGPUImageThreeInputTextureVertexShaderString;
@interface GPUImageThreeInputFilter : GPUImageTwoInputFilter
{
GPUImageFramebuffer *thirdInputFramebuffer;
GLint filterThirdTextureCoordinateAttribute;
GLint filterInputTextureUniform3;
GPUImageRotationMode inputRotation3;
GLuint filterSourceTexture3;
CMTime thirdFrameTime;
BOOL hasSetSecondTexture, hasReceivedThirdFrame, thirdFrameWasVideo;
BOOL thirdFrameCheckDisabled;
}
- (void)disableThirdFrameCheck;
@end
@@ -0,0 +1,332 @@
#import "GPUImageThreeInputFilter.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSString *const kGPUImageThreeInputTextureVertexShaderString = SHADER_STRING
(
attribute vec4 position;
attribute vec4 inputTexCoord;
attribute vec4 inputTexCoord2;
attribute vec4 inputTexCoord3;
varying vec2 texCoord;
varying vec2 texCoord2;
varying vec2 texCoord3;
void main()
{
gl_Position = position;
texCoord = inputTexCoord.xy;
texCoord2 = inputTexCoord2.xy;
texCoord3 = inputTexCoord3.xy;
}
);
@implementation GPUImageThreeInputFilter
#pragma mark -
#pragma mark Initialization and teardown
- (id)initWithFragmentShaderFromString:(NSString *)fragmentShaderString;
{
if (!(self = [self initWithVertexShaderFromString:kGPUImageThreeInputTextureVertexShaderString fragmentShaderFromString:fragmentShaderString]))
{
return nil;
}
return self;
}
- (id)initWithVertexShaderFromString:(NSString *)vertexShaderString fragmentShaderFromString:(NSString *)fragmentShaderString;
{
if (!(self = [super initWithVertexShaderFromString:vertexShaderString fragmentShaderFromString:fragmentShaderString]))
{
return nil;
}
inputRotation3 = kGPUImageNoRotation;
hasSetSecondTexture = NO;
hasReceivedThirdFrame = NO;
thirdFrameWasVideo = NO;
thirdFrameCheckDisabled = NO;
thirdFrameTime = kCMTimeInvalid;
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext useImageProcessingContext];
filterThirdTextureCoordinateAttribute = [filterProgram attributeIndex:@"inputTexCoord3"];
filterInputTextureUniform3 = [filterProgram uniformIndex:@"inputImageTexture3"]; // This does assume a name of "inputImageTexture3" for the third input texture in the fragment shader
glEnableVertexAttribArray(filterThirdTextureCoordinateAttribute);
});
return self;
}
- (void)initializeAttributes;
{
[super initializeAttributes];
[filterProgram addAttribute:@"inputTexCoord3"];
}
- (void)disableThirdFrameCheck;
{
thirdFrameCheckDisabled = YES;
}
#pragma mark -
#pragma mark Rendering
- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;
{
if (self.preventRendering)
{
[firstInputFramebuffer unlock];
[secondInputFramebuffer unlock];
[thirdInputFramebuffer unlock];
return;
}
[GPUImageContext setActiveShaderProgram:filterProgram];
outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:[self sizeOfFBO] textureOptions:self.outputTextureOptions onlyTexture:NO];
[outputFramebuffer activateFramebuffer];
if (usingNextFrameForImageCapture)
{
[outputFramebuffer lock];
}
[self setUniformsForProgramAtIndex:0];
glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, [firstInputFramebuffer texture]);
glUniform1i(filterInputTextureUniform, 2);
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, [secondInputFramebuffer texture]);
glUniform1i(filterInputTextureUniform2, 3);
glActiveTexture(GL_TEXTURE4);
glBindTexture(GL_TEXTURE_2D, [thirdInputFramebuffer texture]);
glUniform1i(filterInputTextureUniform3, 4);
glVertexAttribPointer(filterPositionAttribute, 2, GL_FLOAT, 0, 0, vertices);
glVertexAttribPointer(filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);
glVertexAttribPointer(filterSecondTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, [[self class] textureCoordinatesForRotation:inputRotation2]);
glVertexAttribPointer(filterThirdTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, [[self class] textureCoordinatesForRotation:inputRotation3]);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[firstInputFramebuffer unlock];
[secondInputFramebuffer unlock];
[thirdInputFramebuffer unlock];
if (usingNextFrameForImageCapture)
{
dispatch_semaphore_signal(imageCaptureSemaphore);
}
}
#pragma mark -
#pragma mark GPUImageInput
- (NSInteger)nextAvailableTextureIndex;
{
if (hasSetSecondTexture)
{
return 2;
}
else if (hasSetFirstTexture)
{
return 1;
}
else
{
return 0;
}
}
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
{
if (textureIndex == 0)
{
firstInputFramebuffer = newInputFramebuffer;
hasSetFirstTexture = YES;
[firstInputFramebuffer lock];
}
else if (textureIndex == 1)
{
secondInputFramebuffer = newInputFramebuffer;
hasSetSecondTexture = YES;
[secondInputFramebuffer lock];
}
else
{
thirdInputFramebuffer = newInputFramebuffer;
[thirdInputFramebuffer lock];
}
}
- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex;
{
if (textureIndex == 0)
{
[super setInputSize:newSize atIndex:textureIndex];
if (CGSizeEqualToSize(newSize, CGSizeZero))
{
hasSetFirstTexture = NO;
}
}
else if (textureIndex == 1)
{
if (CGSizeEqualToSize(newSize, CGSizeZero))
{
hasSetSecondTexture = NO;
}
}
}
- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex;
{
if (textureIndex == 0)
{
inputRotation = newInputRotation;
}
else if (textureIndex == 1 && !self.rotateOnlyFirstTexture)
{
inputRotation2 = newInputRotation;
}
else if (textureIndex == 2 && !self.rotateOnlyFirstTexture)
{
inputRotation3 = newInputRotation;
}
}
- (CGSize)rotatedSize:(CGSize)sizeToRotate forIndex:(NSInteger)textureIndex;
{
CGSize rotatedSize = sizeToRotate;
GPUImageRotationMode rotationToCheck;
if (textureIndex == 0)
{
rotationToCheck = inputRotation;
}
else if (textureIndex == 1)
{
rotationToCheck = inputRotation2;
}
else
{
rotationToCheck = inputRotation3;
}
if (GPUImageRotationSwapsWidthAndHeight(rotationToCheck))
{
rotatedSize.width = sizeToRotate.height;
rotatedSize.height = sizeToRotate.width;
}
return rotatedSize;
}
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
{
// You can set up infinite update loops, so this helps to short circuit them
if (hasReceivedFirstFrame && hasReceivedSecondFrame && hasReceivedThirdFrame)
{
return;
}
BOOL updatedMovieFrameOppositeStillImage = NO;
if (textureIndex == 0)
{
hasReceivedFirstFrame = YES;
firstFrameTime = frameTime;
if (secondFrameCheckDisabled)
{
hasReceivedSecondFrame = YES;
}
if (thirdFrameCheckDisabled)
{
hasReceivedThirdFrame = YES;
}
if (!CMTIME_IS_INDEFINITE(frameTime))
{
if CMTIME_IS_INDEFINITE(secondFrameTime)
{
updatedMovieFrameOppositeStillImage = YES;
}
}
}
else if (textureIndex == 1)
{
hasReceivedSecondFrame = YES;
secondFrameTime = frameTime;
if (firstFrameCheckDisabled)
{
hasReceivedFirstFrame = YES;
}
if (thirdFrameCheckDisabled)
{
hasReceivedThirdFrame = YES;
}
if (!CMTIME_IS_INDEFINITE(frameTime))
{
if CMTIME_IS_INDEFINITE(firstFrameTime)
{
updatedMovieFrameOppositeStillImage = YES;
}
}
}
else
{
hasReceivedThirdFrame = YES;
thirdFrameTime = frameTime;
if (firstFrameCheckDisabled)
{
hasReceivedFirstFrame = YES;
}
if (secondFrameCheckDisabled)
{
hasReceivedSecondFrame = YES;
}
if (!CMTIME_IS_INDEFINITE(frameTime))
{
if CMTIME_IS_INDEFINITE(firstFrameTime)
{
updatedMovieFrameOppositeStillImage = YES;
}
}
}
// || (hasReceivedFirstFrame && secondFrameCheckDisabled) || (hasReceivedSecondFrame && firstFrameCheckDisabled)
if ((hasReceivedFirstFrame && hasReceivedSecondFrame && hasReceivedThirdFrame) || updatedMovieFrameOppositeStillImage)
{
static const GLfloat imageVertices[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
};
[self renderToTextureWithVertices:imageVertices textureCoordinates:[[self class] textureCoordinatesForRotation:inputRotation]];
[self informTargetsAboutNewFrameAtTime:frameTime];
hasReceivedFirstFrame = NO;
hasReceivedSecondFrame = NO;
hasReceivedThirdFrame = NO;
}
}
@end
#pragma clang diagnostic pop
@@ -0,0 +1,21 @@
#import "GPUImageFilter.h"
@interface GPUImageToneCurveFilter : GPUImageFilter
@property(readwrite, nonatomic, copy) NSArray *redControlPoints;
@property(readwrite, nonatomic, copy) NSArray *greenControlPoints;
@property(readwrite, nonatomic, copy) NSArray *blueControlPoints;
@property(readwrite, nonatomic, copy) NSArray *rgbCompositeControlPoints;
// This lets you set all three red, green, and blue tone curves at once.
// NOTE: Deprecated this function because this effect can be accomplished
// using the rgbComposite channel rather then setting all 3 R, G, and B channels.
- (void)setRGBControlPoints:(NSArray *)points DEPRECATED_ATTRIBUTE;
// Curve calculation
- (NSMutableArray *)getPreparedSplineCurve:(NSArray *)points;
- (NSMutableArray *)splineCurve:(NSArray *)points;
- (NSMutableArray *)secondDerivative:(NSArray *)cgPoints;
- (void)updateToneCurveTexture;
@end
@@ -0,0 +1,461 @@
#import "GPUImageToneCurveFilter.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#pragma mark -
#pragma mark GPUImageToneCurveFilter Implementation
NSString *const kGPUImageToneCurveFragmentShaderString = SHADER_STRING
(
varying highp vec2 texCoord;
uniform sampler2D sourceImage;
uniform sampler2D toneCurveTexture;
void main()
{
lowp vec4 textureColor = texture2D(sourceImage, texCoord);
lowp float redCurveValue = texture2D(toneCurveTexture, vec2(textureColor.r, 0.0)).r;
lowp float greenCurveValue = texture2D(toneCurveTexture, vec2(textureColor.g, 0.0)).g;
lowp float blueCurveValue = texture2D(toneCurveTexture, vec2(textureColor.b, 0.0)).b;
gl_FragColor = vec4(redCurveValue, greenCurveValue, blueCurveValue, textureColor.a);
}
);
@interface GPUImageToneCurveFilter()
{
GLint toneCurveTextureUniform;
GLuint toneCurveTexture;
GLubyte *toneCurveByteArray;
NSArray *_redCurve, *_greenCurve, *_blueCurve, *_rgbCompositeCurve;
}
@end
@implementation GPUImageToneCurveFilter
@synthesize rgbCompositeControlPoints = _rgbCompositeControlPoints;
@synthesize redControlPoints = _redControlPoints;
@synthesize greenControlPoints = _greenControlPoints;
@synthesize blueControlPoints = _blueControlPoints;
#pragma mark -
#pragma mark Initialization and teardown
- (id)init;
{
if (!(self = [super initWithFragmentShaderFromString:kGPUImageToneCurveFragmentShaderString]))
{
return nil;
}
toneCurveTextureUniform = [filterProgram uniformIndex:@"toneCurveTexture"];
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
NSArray *defaultCurve = [NSArray arrayWithObjects:[NSValue valueWithCGPoint:CGPointMake(0.0, 0.0)], [NSValue valueWithCGPoint:CGPointMake(0.5, 0.5)], [NSValue valueWithCGPoint:CGPointMake(1.0, 1.0)], nil];
#else
NSArray *defaultCurve = [NSArray arrayWithObjects:[NSValue valueWithPoint:NSMakePoint(0.0, 0.0)], [NSValue valueWithPoint:NSMakePoint(0.5, 0.5)], [NSValue valueWithPoint:NSMakePoint(1.0, 1.0)], nil];
#endif
[self setRgbCompositeControlPoints:defaultCurve];
[self setRedControlPoints:defaultCurve];
[self setGreenControlPoints:defaultCurve];
[self setBlueControlPoints:defaultCurve];
return self;
}
- (void)dealloc
{
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext useImageProcessingContext];
if (toneCurveTexture)
{
glDeleteTextures(1, &toneCurveTexture);
toneCurveTexture = 0;
free(toneCurveByteArray);
}
});
}
#pragma mark -
#pragma mark Curve calculation
- (NSArray *)getPreparedSplineCurve:(NSArray *)points
{
if (points && [points count] > 0)
{
// Sort the array.
NSArray *sortedPoints = [points sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
float x1 = [(NSValue *)a CGPointValue].x;
float x2 = [(NSValue *)b CGPointValue].x;
#else
float x1 = [(NSValue *)a pointValue].x;
float x2 = [(NSValue *)b pointValue].x;
#endif
return x1 > x2;
}];
// Convert from (0, 1) to (0, 255).
NSMutableArray *convertedPoints = [NSMutableArray arrayWithCapacity:[sortedPoints count]];
for (int i=0; i<[points count]; i++){
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
CGPoint point = [[sortedPoints objectAtIndex:i] CGPointValue];
#else
NSPoint point = [[sortedPoints objectAtIndex:i] pointValue];
#endif
point.x = point.x * 255;
point.y = point.y * 255;
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
[convertedPoints addObject:[NSValue valueWithCGPoint:point]];
#else
[convertedPoints addObject:[NSValue valueWithPoint:point]];
#endif
}
NSMutableArray *splinePoints = [self splineCurve:convertedPoints];
// If we have a first point like (0.3, 0) we'll be missing some points at the beginning
// that should be 0.
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
CGPoint firstSplinePoint = [[splinePoints objectAtIndex:0] CGPointValue];
#else
NSPoint firstSplinePoint = [[splinePoints objectAtIndex:0] pointValue];
#endif
if (firstSplinePoint.x > 0) {
for (int i=firstSplinePoint.x; i >= 0; i--) {
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
CGPoint newCGPoint = CGPointMake(i, 0);
[splinePoints insertObject:[NSValue valueWithCGPoint:newCGPoint] atIndex:0];
#else
NSPoint newNSPoint = NSMakePoint(i, 0);
[splinePoints insertObject:[NSValue valueWithPoint:newNSPoint] atIndex:0];
#endif
}
}
// Insert points similarly at the end, if necessary.
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
CGPoint lastSplinePoint = [[splinePoints lastObject] CGPointValue];
if (lastSplinePoint.x < 255) {
for (int i = lastSplinePoint.x + 1; i <= 255; i++) {
CGPoint newCGPoint = CGPointMake(i, 255);
[splinePoints addObject:[NSValue valueWithCGPoint:newCGPoint]];
}
}
#else
NSPoint lastSplinePoint = [[splinePoints lastObject] pointValue];
if (lastSplinePoint.x < 255) {
for (int i = lastSplinePoint.x + 1; i <= 255; i++) {
NSPoint newNSPoint = NSMakePoint(i, 255);
[splinePoints addObject:[NSValue valueWithPoint:newNSPoint]];
}
}
#endif
// Prepare the spline points.
NSMutableArray *preparedSplinePoints = [NSMutableArray arrayWithCapacity:[splinePoints count]];
for (int i=0; i<[splinePoints count]; i++)
{
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
CGPoint newPoint = [[splinePoints objectAtIndex:i] CGPointValue];
#else
NSPoint newPoint = [[splinePoints objectAtIndex:i] pointValue];
#endif
CGPoint origPoint = CGPointMake(newPoint.x, newPoint.x);
float distance = sqrt(pow((origPoint.x - newPoint.x), 2.0) + pow((origPoint.y - newPoint.y), 2.0));
if (origPoint.y > newPoint.y)
{
distance = -distance;
}
[preparedSplinePoints addObject:[NSNumber numberWithFloat:distance]];
}
return preparedSplinePoints;
}
return nil;
}
- (NSMutableArray *)splineCurve:(NSArray *)points
{
NSMutableArray *sdA = [self secondDerivative:points];
// [points count] is equal to [sdA count]
NSInteger n = [sdA count];
if (n < 1)
{
return nil;
}
double sd[n];
// From NSMutableArray to sd[n];
for (int i=0; i<n; i++)
{
sd[i] = [[sdA objectAtIndex:i] doubleValue];
}
NSMutableArray *output = [NSMutableArray arrayWithCapacity:(n+1)];
for(int i=0; i<n-1 ; i++)
{
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
CGPoint cur = [[points objectAtIndex:i] CGPointValue];
CGPoint next = [[points objectAtIndex:(i+1)] CGPointValue];
#else
NSPoint cur = [[points objectAtIndex:i] pointValue];
NSPoint next = [[points objectAtIndex:(i+1)] pointValue];
#endif
for(int x=cur.x;x<(int)next.x;x++)
{
double t = (double)(x-cur.x)/(next.x-cur.x);
double a = 1-t;
double b = t;
double h = next.x-cur.x;
double y= a*cur.y + b*next.y + (h*h/6)*( (a*a*a-a)*sd[i]+ (b*b*b-b)*sd[i+1] );
if (y > 255.0)
{
y = 255.0;
}
else if (y < 0.0)
{
y = 0.0;
}
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
[output addObject:[NSValue valueWithCGPoint:CGPointMake(x, y)]];
#else
[output addObject:[NSValue valueWithPoint:NSMakePoint(x, y)]];
#endif
}
}
// The above always misses the last point because the last point is the last next, so we approach but don't equal it.
[output addObject:[points lastObject]];
return output;
}
- (NSMutableArray *)secondDerivative:(NSArray *)points
{
const NSInteger n = [points count];
if ((n <= 0) || (n == 1))
{
return nil;
}
double matrix[n][3];
double result[n];
matrix[0][1]=1;
// What about matrix[0][1] and matrix[0][0]? Assuming 0 for now (Brad L.)
matrix[0][0]=0;
matrix[0][2]=0;
for(int i=1;i<n-1;i++)
{
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
CGPoint P1 = [[points objectAtIndex:(i-1)] CGPointValue];
CGPoint P2 = [[points objectAtIndex:i] CGPointValue];
CGPoint P3 = [[points objectAtIndex:(i+1)] CGPointValue];
#else
NSPoint P1 = [[points objectAtIndex:(i-1)] pointValue];
NSPoint P2 = [[points objectAtIndex:i] pointValue];
NSPoint P3 = [[points objectAtIndex:(i+1)] pointValue];
#endif
matrix[i][0]=(double)(P2.x-P1.x)/6;
matrix[i][1]=(double)(P3.x-P1.x)/3;
matrix[i][2]=(double)(P3.x-P2.x)/6;
result[i]=(double)(P3.y-P2.y)/(P3.x-P2.x) - (double)(P2.y-P1.y)/(P2.x-P1.x);
}
// What about result[0] and result[n-1]? Assuming 0 for now (Brad L.)
result[0] = 0;
result[n-1] = 0;
matrix[n-1][1]=1;
// What about matrix[n-1][0] and matrix[n-1][2]? For now, assuming they are 0 (Brad L.)
matrix[n-1][0]=0;
matrix[n-1][2]=0;
// solving pass1 (up->down)
for(int i=1;i<n;i++)
{
double k = matrix[i][0]/matrix[i-1][1];
matrix[i][1] -= k*matrix[i-1][2];
matrix[i][0] = 0;
result[i] -= k*result[i-1];
}
// solving pass2 (down->up)
for(NSInteger i=n-2;i>=0;i--)
{
double k = matrix[i][2]/matrix[i+1][1];
matrix[i][1] -= k*matrix[i+1][0];
matrix[i][2] = 0;
result[i] -= k*result[i+1];
}
double y2[n];
for(int i=0;i<n;i++) y2[i]=result[i]/matrix[i][1];
NSMutableArray *output = [NSMutableArray arrayWithCapacity:n];
for (int i=0;i<n;i++)
{
[output addObject:[NSNumber numberWithDouble:y2[i]]];
}
return output;
}
- (void)updateToneCurveTexture;
{
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext useImageProcessingContext];
if (!toneCurveTexture)
{
glActiveTexture(GL_TEXTURE3);
glGenTextures(1, &toneCurveTexture);
glBindTexture(GL_TEXTURE_2D, toneCurveTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
toneCurveByteArray = calloc(256 * 4, sizeof(GLubyte));
}
else
{
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, toneCurveTexture);
}
if ( ([_redCurve count] >= 256) && ([_greenCurve count] >= 256) && ([_blueCurve count] >= 256) && ([_rgbCompositeCurve count] >= 256))
{
for (unsigned int currentCurveIndex = 0; currentCurveIndex < 256; currentCurveIndex++)
{
// BGRA for upload to texture
GLubyte b = fmin(fmax(currentCurveIndex + [[_blueCurve objectAtIndex:currentCurveIndex] floatValue], 0), 255);
toneCurveByteArray[currentCurveIndex * 4] = fmin(fmax(b + [[_rgbCompositeCurve objectAtIndex:b] floatValue], 0), 255);
GLubyte g = fmin(fmax(currentCurveIndex + [[_greenCurve objectAtIndex:currentCurveIndex] floatValue], 0), 255);
toneCurveByteArray[currentCurveIndex * 4 + 1] = fmin(fmax(g + [[_rgbCompositeCurve objectAtIndex:g] floatValue], 0), 255);
GLubyte r = fmin(fmax(currentCurveIndex + [[_redCurve objectAtIndex:currentCurveIndex] floatValue], 0), 255);
toneCurveByteArray[currentCurveIndex * 4 + 2] = fmin(fmax(r + [[_rgbCompositeCurve objectAtIndex:r] floatValue], 0), 255);
toneCurveByteArray[currentCurveIndex * 4 + 3] = 255;
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256 /*width*/, 1 /*height*/, 0, GL_BGRA, GL_UNSIGNED_BYTE, toneCurveByteArray);
}
});
}
#pragma mark -
#pragma mark Rendering
- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;
{
if (self.preventRendering)
{
[firstInputFramebuffer unlock];
return;
}
[GPUImageContext setActiveShaderProgram:filterProgram];
outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:[self sizeOfFBO] textureOptions:self.outputTextureOptions onlyTexture:NO];
[outputFramebuffer activateFramebuffer];
if (usingNextFrameForImageCapture)
{
[outputFramebuffer lock];
}
glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, [firstInputFramebuffer texture]);
glUniform1i(filterInputTextureUniform, 2);
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, toneCurveTexture);
glUniform1i(toneCurveTextureUniform, 3);
glVertexAttribPointer(filterPositionAttribute, 2, GL_FLOAT, 0, 0, vertices);
glVertexAttribPointer(filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[firstInputFramebuffer unlock];
if (usingNextFrameForImageCapture)
{
dispatch_semaphore_signal(imageCaptureSemaphore);
}
}
#pragma mark -
#pragma mark Accessors
- (void)setRGBControlPoints:(NSArray *)points
{
_redControlPoints = [points copy];
_redCurve = [self getPreparedSplineCurve:_redControlPoints];
_greenControlPoints = [points copy];
_greenCurve = [self getPreparedSplineCurve:_greenControlPoints];
_blueControlPoints = [points copy];
_blueCurve = [self getPreparedSplineCurve:_blueControlPoints];
[self updateToneCurveTexture];
}
- (void)setRgbCompositeControlPoints:(NSArray *)newValue
{
_rgbCompositeControlPoints = [newValue copy];
_rgbCompositeCurve = [self getPreparedSplineCurve:_rgbCompositeControlPoints];
[self updateToneCurveTexture];
}
- (void)setRedControlPoints:(NSArray *)newValue;
{
_redControlPoints = [newValue copy];
_redCurve = [self getPreparedSplineCurve:_redControlPoints];
[self updateToneCurveTexture];
}
- (void)setGreenControlPoints:(NSArray *)newValue
{
_greenControlPoints = [newValue copy];
_greenCurve = [self getPreparedSplineCurve:_greenControlPoints];
[self updateToneCurveTexture];
}
- (void)setBlueControlPoints:(NSArray *)newValue
{
_blueControlPoints = [newValue copy];
_blueCurve = [self getPreparedSplineCurve:_blueControlPoints];
[self updateToneCurveTexture];
}
@end
#pragma clang diagnostic pop
@@ -0,0 +1,23 @@
#import "GPUImageFilter.h"
extern NSString *const kGPUImageTwoInputTextureVertexShaderString;
@interface GPUImageTwoInputFilter : GPUImageFilter
{
GPUImageFramebuffer *secondInputFramebuffer;
GLint filterSecondTextureCoordinateAttribute;
GLint filterInputTextureUniform2;
GPUImageRotationMode inputRotation2;
CMTime firstFrameTime, secondFrameTime;
BOOL hasSetFirstTexture, hasReceivedFirstFrame, hasReceivedSecondFrame, firstFrameWasVideo, secondFrameWasVideo;
BOOL firstFrameCheckDisabled, secondFrameCheckDisabled;
}
@property (nonatomic, assign) bool rotateOnlyFirstTexture;
- (void)disableFirstFrameCheck;
- (void)disableSecondFrameCheck;
@end
@@ -0,0 +1,264 @@
#import "GPUImageTwoInputFilter.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSString *const kGPUImageTwoInputTextureVertexShaderString = SHADER_STRING
(
attribute vec4 position;
attribute vec4 inputTexCoord;
attribute vec4 inputTexCoord2;
varying vec2 texCoord;
varying vec2 texCoord2;
void main()
{
gl_Position = position;
texCoord = inputTexCoord.xy;
texCoord2 = inputTexCoord2.xy;
}
);
@implementation GPUImageTwoInputFilter
#pragma mark -
#pragma mark Initialization and teardown
- (id)initWithFragmentShaderFromString:(NSString *)fragmentShaderString
{
if (!(self = [self initWithVertexShaderFromString:kGPUImageTwoInputTextureVertexShaderString fragmentShaderFromString:fragmentShaderString]))
{
return nil;
}
return self;
}
- (id)initWithVertexShaderFromString:(NSString *)vertexShaderString fragmentShaderFromString:(NSString *)fragmentShaderString
{
if (!(self = [super initWithVertexShaderFromString:vertexShaderString fragmentShaderFromString:fragmentShaderString]))
{
return nil;
}
inputRotation2 = kGPUImageNoRotation;
hasSetFirstTexture = NO;
hasReceivedFirstFrame = NO;
hasReceivedSecondFrame = NO;
firstFrameWasVideo = NO;
secondFrameWasVideo = NO;
firstFrameCheckDisabled = NO;
secondFrameCheckDisabled = NO;
firstFrameTime = kCMTimeInvalid;
secondFrameTime = kCMTimeInvalid;
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext useImageProcessingContext];
filterSecondTextureCoordinateAttribute = [filterProgram attributeIndex:@"inputTexCoord2"];
filterInputTextureUniform2 = [filterProgram uniformIndex:@"inputImageTexture2"];
glEnableVertexAttribArray(filterSecondTextureCoordinateAttribute);
});
return self;
}
- (void)initializeAttributes
{
[super initializeAttributes];
[filterProgram addAttribute:@"inputTexCoord2"];
}
- (void)disableFirstFrameCheck
{
firstFrameCheckDisabled = YES;
}
- (void)disableSecondFrameCheck
{
secondFrameCheckDisabled = YES;
}
#pragma mark -
#pragma mark Rendering
- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates
{
if (self.preventRendering)
{
[firstInputFramebuffer unlock];
[secondInputFramebuffer unlock];
return;
}
[GPUImageContext setActiveShaderProgram:filterProgram];
outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:[self sizeOfFBO] textureOptions:self.outputTextureOptions onlyTexture:NO];
[outputFramebuffer activateFramebuffer];
if (usingNextFrameForImageCapture)
{
[outputFramebuffer lock];
}
[self setUniformsForProgramAtIndex:0];
glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, [firstInputFramebuffer texture]);
glUniform1i(filterInputTextureUniform, 2);
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, [secondInputFramebuffer texture]);
glUniform1i(filterInputTextureUniform2, 3);
glVertexAttribPointer(filterPositionAttribute, 2, GL_FLOAT, 0, 0, vertices);
glVertexAttribPointer(filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);
glVertexAttribPointer(filterSecondTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, [[self class] textureCoordinatesForRotation:inputRotation2]);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[firstInputFramebuffer unlock];
[secondInputFramebuffer unlock];
if (usingNextFrameForImageCapture)
dispatch_semaphore_signal(imageCaptureSemaphore);
}
#pragma mark -
#pragma mark GPUImageInput
- (NSInteger)nextAvailableTextureIndex
{
if (hasSetFirstTexture)
{
return 1;
}
else
{
return 0;
}
}
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex
{
if (textureIndex == 0)
{
firstInputFramebuffer = newInputFramebuffer;
hasSetFirstTexture = YES;
[firstInputFramebuffer lock];
}
else
{
secondInputFramebuffer = newInputFramebuffer;
[secondInputFramebuffer lock];
}
}
- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex
{
if (textureIndex == 0)
{
[super setInputSize:newSize atIndex:textureIndex];
if (CGSizeEqualToSize(newSize, CGSizeZero))
{
hasSetFirstTexture = NO;
}
}
}
- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex
{
if (textureIndex == 0)
{
inputRotation = newInputRotation;
}
else if (textureIndex == 1 && !_rotateOnlyFirstTexture)
{
inputRotation2 = newInputRotation;
}
}
- (CGSize)rotatedSize:(CGSize)sizeToRotate forIndex:(NSInteger)textureIndex
{
CGSize rotatedSize = sizeToRotate;
GPUImageRotationMode rotationToCheck;
if (textureIndex == 0)
{
rotationToCheck = inputRotation;
}
else
{
rotationToCheck = inputRotation2;
}
if (GPUImageRotationSwapsWidthAndHeight(rotationToCheck))
{
rotatedSize.width = sizeToRotate.height;
rotatedSize.height = sizeToRotate.width;
}
return rotatedSize;
}
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex
{
// You can set up infinite update loops, so this helps to short circuit them
if (hasReceivedFirstFrame && hasReceivedSecondFrame)
{
return;
}
if (textureIndex == 0)
{
hasReceivedFirstFrame = YES;
firstFrameTime = frameTime;
if (secondFrameCheckDisabled)
{
hasReceivedSecondFrame = YES;
}
if (!CMTIME_IS_INDEFINITE(frameTime))
{
if CMTIME_IS_INDEFINITE(secondFrameTime)
{
}
}
}
else
{
hasReceivedSecondFrame = YES;
secondFrameTime = frameTime;
if (firstFrameCheckDisabled)
{
hasReceivedFirstFrame = YES;
}
if (!CMTIME_IS_INDEFINITE(frameTime))
{
if CMTIME_IS_INDEFINITE(firstFrameTime)
{
}
}
}
// || (hasReceivedFirstFrame && secondFrameCheckDisabled) || (hasReceivedSecondFrame && firstFrameCheckDisabled)
if ((hasReceivedFirstFrame && hasReceivedSecondFrame))
{
CMTime passOnFrameTime = (!CMTIME_IS_INDEFINITE(firstFrameTime)) ? firstFrameTime : secondFrameTime;
[super newFrameReadyAtTime:passOnFrameTime atIndex:0]; // Bugfix when trying to record: always use time from first input (unless indefinite, in which case use the second input)
hasReceivedFirstFrame = NO;
hasReceivedSecondFrame = NO;
}
}
@end
#pragma clang diagnostic pop
@@ -0,0 +1,19 @@
#import "GPUImageFilter.h"
@interface GPUImageTwoPassFilter : GPUImageFilter
{
GPUImageFramebuffer *secondOutputFramebuffer;
GLProgram *secondFilterProgram;
GLint secondFilterPositionAttribute, secondFilterTextureCoordinateAttribute;
GLint secondFilterInputTextureUniform, secondFilterInputTextureUniform2;
NSMutableDictionary *secondProgramUniformStateRestorationBlocks;
}
// Initialization and teardown
- (id)initWithFirstStageVertexShaderFromString:(NSString *)firstStageVertexShaderString firstStageFragmentShaderFromString:(NSString *)firstStageFragmentShaderString secondStageVertexShaderFromString:(NSString *)secondStageVertexShaderString secondStageFragmentShaderFromString:(NSString *)secondStageFragmentShaderString;
- (id)initWithFirstStageFragmentShaderFromString:(NSString *)firstStageFragmentShaderString secondStageFragmentShaderFromString:(NSString *)secondStageFragmentShaderString;
- (void)initializeSecondaryAttributes;
@end
+206
View File
@@ -0,0 +1,206 @@
#import "GPUImageTwoPassFilter.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@implementation GPUImageTwoPassFilter
#pragma mark -
#pragma mark Initialization and teardown
- (id)initWithFirstStageVertexShaderFromString:(NSString *)firstStageVertexShaderString firstStageFragmentShaderFromString:(NSString *)firstStageFragmentShaderString secondStageVertexShaderFromString:(NSString *)secondStageVertexShaderString secondStageFragmentShaderFromString:(NSString *)secondStageFragmentShaderString;
{
if (!(self = [super initWithVertexShaderFromString:firstStageVertexShaderString fragmentShaderFromString:firstStageFragmentShaderString]))
{
return nil;
}
secondProgramUniformStateRestorationBlocks = [NSMutableDictionary dictionaryWithCapacity:10];
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext useImageProcessingContext];
secondFilterProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:secondStageVertexShaderString fragmentShaderString:secondStageFragmentShaderString];
if (!secondFilterProgram.initialized)
{
[self initializeSecondaryAttributes];
if (![secondFilterProgram link])
{
NSString *progLog = [secondFilterProgram programLog];
NSLog(@"Program link log: %@", progLog);
NSString *fragLog = [secondFilterProgram fragmentShaderLog];
NSLog(@"Fragment shader compile log: %@", fragLog);
NSString *vertLog = [secondFilterProgram vertexShaderLog];
NSLog(@"Vertex shader compile log: %@", vertLog);
secondFilterProgram = nil;
NSAssert(NO, @"Filter shader link failed");
}
}
secondFilterPositionAttribute = [secondFilterProgram attributeIndex:@"position"];
secondFilterTextureCoordinateAttribute = [secondFilterProgram attributeIndex:@"inputTexCoord"];
secondFilterInputTextureUniform = [secondFilterProgram uniformIndex:@"sourceImage"]; // This does assume a name of "inputImageTexture" for the fragment shader
secondFilterInputTextureUniform2 = [secondFilterProgram uniformIndex:@"inputImageTexture2"]; // This does assume a name of "inputImageTexture2" for second input texture in the fragment shader
[GPUImageContext setActiveShaderProgram:secondFilterProgram];
glEnableVertexAttribArray(secondFilterPositionAttribute);
glEnableVertexAttribArray(secondFilterTextureCoordinateAttribute);
});
return self;
}
- (id)initWithFirstStageFragmentShaderFromString:(NSString *)firstStageFragmentShaderString secondStageFragmentShaderFromString:(NSString *)secondStageFragmentShaderString;
{
if (!(self = [self initWithFirstStageVertexShaderFromString:kGPUImageVertexShaderString firstStageFragmentShaderFromString:firstStageFragmentShaderString secondStageVertexShaderFromString:kGPUImageVertexShaderString secondStageFragmentShaderFromString:secondStageFragmentShaderString]))
{
return nil;
}
return self;
}
- (void)initializeSecondaryAttributes;
{
[secondFilterProgram addAttribute:@"position"];
[secondFilterProgram addAttribute:@"inputTexCoord"];
}
#pragma mark -
#pragma mark Managing targets
- (GPUImageFramebuffer *)framebufferForOutput;
{
return secondOutputFramebuffer;
}
- (void)removeOutputFramebuffer;
{
secondOutputFramebuffer = nil;
}
#pragma mark -
#pragma mark Rendering
- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;
{
if (self.preventRendering)
{
[firstInputFramebuffer unlock];
return;
}
[GPUImageContext setActiveShaderProgram:filterProgram];
outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:[self sizeOfFBO] textureOptions:self.outputTextureOptions onlyTexture:NO];
[outputFramebuffer activateFramebuffer];
[self setUniformsForProgramAtIndex:0];
glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, [firstInputFramebuffer texture]);
glUniform1i(filterInputTextureUniform, 2);
glVertexAttribPointer(filterPositionAttribute, 2, GL_FLOAT, 0, 0, vertices);
glVertexAttribPointer(filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[firstInputFramebuffer unlock];
firstInputFramebuffer = nil;
// This assumes that any two-pass filter that says it desires monochrome input is using the first pass for a luminance conversion, which can be dropped
// if (!currentlyReceivingMonochromeInput)
// {
// Run the first stage of the two-pass filter
// [super renderToTextureWithVertices:vertices textureCoordinates:textureCoordinates];
// }
// Run the second stage of the two-pass filter
secondOutputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:[self sizeOfFBO] textureOptions:self.outputTextureOptions onlyTexture:NO];
[secondOutputFramebuffer activateFramebuffer];
[GPUImageContext setActiveShaderProgram:secondFilterProgram];
if (usingNextFrameForImageCapture)
{
[secondOutputFramebuffer lock];
}
[self setUniformsForProgramAtIndex:1];
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, [outputFramebuffer texture]);
glVertexAttribPointer(secondFilterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, [[self class] textureCoordinatesForRotation:kGPUImageNoRotation]);
// TODO: Re-enable this monochrome optimization
// if (!currentlyReceivingMonochromeInput)
// {
// glActiveTexture(GL_TEXTURE3);
// glBindTexture(GL_TEXTURE_2D, outputTexture);
// glVertexAttribPointer(secondFilterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, [[self class] textureCoordinatesForRotation:kGPUImageNoRotation]);
// }
// else
// {
// glActiveTexture(GL_TEXTURE3);
// glBindTexture(GL_TEXTURE_2D, sourceTexture);
// glVertexAttribPointer(secondFilterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);
// }
glUniform1i(secondFilterInputTextureUniform, 3);
glVertexAttribPointer(secondFilterPositionAttribute, 2, GL_FLOAT, 0, 0, vertices);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[outputFramebuffer unlock];
outputFramebuffer = nil;
if (usingNextFrameForImageCapture)
{
dispatch_semaphore_signal(imageCaptureSemaphore);
}
}
- (void)setAndExecuteUniformStateCallbackAtIndex:(GLint)uniform forProgram:(GLProgram *)shaderProgram toBlock:(dispatch_block_t)uniformStateBlock;
{
// TODO: Deal with the fact that two-pass filters may have the same shader program identifier
if (shaderProgram == filterProgram)
{
[uniformStateRestorationBlocks setObject:[uniformStateBlock copy] forKey:[NSNumber numberWithInt:uniform]];
}
else
{
[secondProgramUniformStateRestorationBlocks setObject:[uniformStateBlock copy] forKey:[NSNumber numberWithInt:uniform]];
}
uniformStateBlock();
}
- (void)setUniformsForProgramAtIndex:(NSUInteger)programIndex;
{
if (programIndex == 0)
{
[uniformStateRestorationBlocks enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
dispatch_block_t currentBlock = obj;
currentBlock();
}];
}
else
{
[secondProgramUniformStateRestorationBlocks enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
dispatch_block_t currentBlock = obj;
currentBlock();
}];
}
}
@end
#pragma clang diagnostic pop
@@ -0,0 +1,13 @@
#import "GPUImageTwoPassFilter.h"
@interface GPUImageTwoPassTextureSamplingFilter : GPUImageTwoPassFilter
{
GLint verticalPassTexelWidthOffsetUniform, verticalPassTexelHeightOffsetUniform, horizontalPassTexelWidthOffsetUniform, horizontalPassTexelHeightOffsetUniform;
GLfloat verticalPassTexelWidthOffset, verticalPassTexelHeightOffset, horizontalPassTexelWidthOffset, horizontalPassTexelHeightOffset;
CGFloat _verticalTexelSpacing, _horizontalTexelSpacing;
}
// This sets the spacing between texels (in pixels) when sampling for the first. By default, this is 1.0
@property(readwrite, nonatomic) CGFloat verticalTexelSpacing, horizontalTexelSpacing;
@end
@@ -0,0 +1,90 @@
#import "GPUImageTwoPassTextureSamplingFilter.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@implementation GPUImageTwoPassTextureSamplingFilter
@synthesize verticalTexelSpacing = _verticalTexelSpacing;
@synthesize horizontalTexelSpacing = _horizontalTexelSpacing;
#pragma mark -
#pragma mark Initialization and teardown
- (id)initWithFirstStageVertexShaderFromString:(NSString *)firstStageVertexShaderString firstStageFragmentShaderFromString:(NSString *)firstStageFragmentShaderString secondStageVertexShaderFromString:(NSString *)secondStageVertexShaderString secondStageFragmentShaderFromString:(NSString *)secondStageFragmentShaderString
{
if (!(self = [super initWithFirstStageVertexShaderFromString:firstStageVertexShaderString firstStageFragmentShaderFromString:firstStageFragmentShaderString secondStageVertexShaderFromString:secondStageVertexShaderString secondStageFragmentShaderFromString:secondStageFragmentShaderString]))
{
return nil;
}
runSynchronouslyOnVideoProcessingQueue(^{
[GPUImageContext useImageProcessingContext];
verticalPassTexelWidthOffsetUniform = [filterProgram uniformIndex:@"texelWidthOffset"];
verticalPassTexelHeightOffsetUniform = [filterProgram uniformIndex:@"texelHeightOffset"];
horizontalPassTexelWidthOffsetUniform = [secondFilterProgram uniformIndex:@"texelWidthOffset"];
horizontalPassTexelHeightOffsetUniform = [secondFilterProgram uniformIndex:@"texelHeightOffset"];
});
self.verticalTexelSpacing = 1.0;
self.horizontalTexelSpacing = 1.0;
return self;
}
- (void)setUniformsForProgramAtIndex:(NSUInteger)programIndex;
{
[super setUniformsForProgramAtIndex:programIndex];
if (programIndex == 0)
{
glUniform1f(verticalPassTexelWidthOffsetUniform, verticalPassTexelWidthOffset);
glUniform1f(verticalPassTexelHeightOffsetUniform, verticalPassTexelHeightOffset);
}
else
{
glUniform1f(horizontalPassTexelWidthOffsetUniform, horizontalPassTexelWidthOffset);
glUniform1f(horizontalPassTexelHeightOffsetUniform, horizontalPassTexelHeightOffset);
}
}
- (void)setupFilterForSize:(CGSize)filterFrameSize;
{
runSynchronouslyOnVideoProcessingQueue(^{
// The first pass through the framebuffer may rotate the inbound image, so need to account for that by changing up the kernel ordering for that pass
if (GPUImageRotationSwapsWidthAndHeight(inputRotation))
{
verticalPassTexelWidthOffset = _verticalTexelSpacing / filterFrameSize.height;
verticalPassTexelHeightOffset = 0.0;
}
else
{
verticalPassTexelWidthOffset = 0.0;
verticalPassTexelHeightOffset = _verticalTexelSpacing / filterFrameSize.height;
}
horizontalPassTexelWidthOffset = _horizontalTexelSpacing / filterFrameSize.width;
horizontalPassTexelHeightOffset = 0.0;
});
}
#pragma mark -
#pragma mark Accessors
- (void)setVerticalTexelSpacing:(CGFloat)newValue;
{
_verticalTexelSpacing = newValue;
[self setupFilterForSize:[self sizeOfFBO]];
}
- (void)setHorizontalTexelSpacing:(CGFloat)newValue;
{
_horizontalTexelSpacing = newValue;
[self setupFilterForSize:[self sizeOfFBO]];
}
@end
#pragma clang diagnostic pop
@@ -0,0 +1,295 @@
#import <LegacyComponents/JNWSpringAnimation.h>
#import "NSValue+JNWAdditions.h"
#import <LegacyComponents/TGHacks.h>
static const CGFloat JNWSpringAnimationDefaultMass = 5.f;
static const CGFloat JNWSpringAnimationDefaultDamping = 30.f;
static const CGFloat JNWSpringAnimationDefaultStiffness = 300.f;
static const CGFloat JNWSpringAnimationKeyframeStep = 0.001f;
static const CGFloat JNWSpringAnimationMinimumThreshold = 0.0001f;
@interface JNWSpringAnimation()
@property (nonatomic, copy) NSArray *interpolatedValues;
@property (nonatomic, assign) BOOL needsRecalculation;
@end
@implementation JNWSpringAnimation
#pragma mark Initialization
+ (instancetype)animationWithKeyPath:(NSString *)path {
return [super animationWithKeyPath:path];
}
- (id)init {
self = [super init];
_mass = JNWSpringAnimationDefaultMass;
_damping = JNWSpringAnimationDefaultDamping;
_stiffness = JNWSpringAnimationDefaultStiffness;
_durationFactor = 1.0f;
_needsRecalculation = NO;
return self;
}
// Since animations are copied before they are added onto the layer, we
// take this opportunity to override the copy method and actually
// calculate the key frames, and move those over to the new animation.
- (id)copyWithZone:(NSZone *)zone {
JNWSpringAnimation *copy = [super copyWithZone:zone];
copy.interpolatedValues = self.interpolatedValues;
copy.duration = self.interpolatedValues.count * JNWSpringAnimationKeyframeStep * TGAnimationSpeedFactor() * _durationFactor;
copy.fromValue = self.fromValue;
copy.stiffness = self.stiffness;
copy.toValue = self.toValue;
copy.damping = self.damping;
copy.mass = self.mass;
copy.needsRecalculation = NO;
return copy;
}
#pragma mark API
- (void)setToValue:(id)toValue {
_toValue = toValue;
self.needsRecalculation = YES;
}
- (void)setFromValue:(id)fromValue {
_fromValue = fromValue;
self.needsRecalculation = YES;
}
- (void)setStiffness:(CGFloat)stiffness {
_stiffness = stiffness;
self.needsRecalculation = YES;
}
- (void)setMass:(CGFloat)mass {
_mass = mass;
self.needsRecalculation = YES;
}
- (NSArray *)values {
return self.interpolatedValues;
}
- (void)setDamping:(CGFloat)damping {
if (damping <= 0) {
NSLog(@"[%@] LOGIC ERROR. `damping` should be > 0.0f to avoid an infinite spring calculation", NSStringFromClass([self class]));
damping = 1.0f;
}
_damping = damping;
self.needsRecalculation = YES;
}
- (CFTimeInterval)duration {
if (self.fromValue != nil && self.toValue != nil) {
return self.interpolatedValues.count * JNWSpringAnimationKeyframeStep * TGAnimationSpeedFactor() * _durationFactor;
}
return 0.f;
}
- (NSArray *)interpolatedValues {
if (self.needsRecalculation || _interpolatedValues == nil) {
[self calculateInterpolatedValues];
}
return _interpolatedValues;
}
#pragma mark Interpolation
- (void)calculateInterpolatedValues {
NSAssert(self.fromValue != nil && self.toValue != nil, @"fromValue and or toValue must not be nil.");
JNWValueType fromType = [self.fromValue jnw_type];
JNWValueType toType = [self.toValue jnw_type];
NSAssert(fromType == toType, @"fromValue and toValue must be of the same type.");
NSAssert(fromType != JNWValueTypeUnknown, @"Type of value could not be determined. Please ensure the value types are supported.");
NSArray *values = nil;
if (fromType == JNWValueTypeNumber) {
values = [self valuesFromNumbers:@[self.fromValue] toNumbers:@[self.toValue] map:^id(CGFloat *values, NSUInteger __unused count) {
return @(values[0]);
}];
} else if (fromType == JNWValueTypePoint) {
CGPoint f = [self.fromValue jnw_pointValue];
CGPoint t = [self.toValue jnw_pointValue];
values = [self valuesFromNumbers:@[@(f.x), @(f.y)]
toNumbers:@[@(t.x), @(t.y)]
map:^id(CGFloat *values, __unused NSUInteger count) {
return [NSValue jnw_valueWithPoint:CGPointMake(values[0], values[1])];
}];
} else if (fromType == JNWValueTypeSize) {
CGSize f = [self.fromValue jnw_sizeValue];
CGSize t = [self.toValue jnw_sizeValue];
values = [self valuesFromNumbers:@[@(f.width), @(f.height)]
toNumbers:@[@(t.width), @(t.height)]
map:^id(CGFloat *values, __unused NSUInteger count) {
return [NSValue jnw_valueWithSize:CGSizeMake(values[0], values[1])];
}];
} else if (fromType == JNWValueTypeRect) { // note that CA will not animate the `frame` property
CGRect f = [self.fromValue jnw_rectValue];
CGRect t = [self.toValue jnw_rectValue];
values = [self valuesFromNumbers:@[@(f.origin.x), @(f.origin.y), @(f.size.width), @(f.size.height)]
toNumbers:@[@(t.origin.x), @(t.origin.y), @(t.size.width), @(t.size.height)]
map:^id(CGFloat *values, __unused NSUInteger count) {
return [NSValue jnw_valueWithRect:CGRectMake(values[0], values[1], values[2], values[3])];
}];
} else if (fromType == JNWValueTypeAffineTransform) {
CGAffineTransform f = [self.fromValue jnw_affineTransformValue];
CGAffineTransform t = [self.toValue jnw_affineTransformValue];
values = [self valuesFromNumbers:@[@(f.a), @(f.b), @(f.c), @(f.d), @(f.tx), @(f.ty)]
toNumbers:@[@(t.a), @(t.b), @(t.c), @(t.d), @(t.tx), @(t.ty)]
map:^id(CGFloat *values, __unused NSUInteger count) {
CGAffineTransform transform;
transform.a = values[0];
transform.b = values[1];
transform.c = values[2];
transform.d = values[3];
transform.tx = values[4];
transform.ty = values[5];
return [NSValue jnw_valueWithAffineTransform:transform];
}];
} else if (fromType == JNWValueTypeTransform3D) {
CATransform3D f = [self.fromValue CATransform3DValue];
CATransform3D t = [self.toValue CATransform3DValue];
values = [self valuesFromNumbers:@[@(f.m11), @(f.m12), @(f.m13), @(f.m14), @(f.m21), @(f.m22), @(f.m23), @(f.m24), @(f.m31), @(f.m32), @(f.m33), @(f.m34), @(f.m41), @(f.m42), @(f.m43), @(f.m44) ]
toNumbers:@[@(t.m11), @(t.m12), @(t.m13), @(t.m14), @(t.m21), @(t.m22), @(t.m23), @(t.m24), @(t.m31), @(t.m32), @(t.m33), @(t.m34), @(t.m41), @(t.m42), @(t.m43), @(t.m44) ]
map:^id(CGFloat *values, __unused NSUInteger count) {
CATransform3D transform = CATransform3DIdentity;
transform.m11 = values[0];
transform.m12 = values[1];
transform.m13 = values[2];
transform.m14 = values[3];
transform.m21 = values[4];
transform.m22 = values[5];
transform.m23 = values[6];
transform.m24 = values[7];
transform.m31 = values[8];
transform.m32 = values[9];
transform.m33 = values[10];
transform.m34 = values[11];
transform.m41 = values[12];
transform.m42 = values[13];
transform.m43 = values[14];
transform.m44 = values[15];
return [NSValue valueWithCATransform3D:transform];
}];
}
self.interpolatedValues = values;
self.needsRecalculation = NO;
}
- (NSArray *)valuesFromNumbers:(NSArray *)fromNumbers toNumbers:(NSArray *)toNumbers map:(id (^)(CGFloat *values, NSUInteger count))map {
NSAssert(fromNumbers.count == toNumbers.count, @"count of from and to numbers must be equal");
NSUInteger count = fromNumbers.count;
// This will never happen, but this is peformed in order to shush the analyzer.
if (count < 1) {
return [NSArray array];
}
CGFloat *distances = calloc(count, sizeof(CGFloat));
CGFloat *thresholds = calloc(count, sizeof(CGFloat));
for (NSUInteger i = 0; i < count; i++) {
distances[i] = [toNumbers[i] floatValue] - [fromNumbers[i] floatValue];
thresholds[i] = JNWSpringAnimationThreshold(ABS(distances[i]));
}
CFTimeInterval step = JNWSpringAnimationKeyframeStep;
CFTimeInterval elapsed = 0;
CGFloat *stepValues = calloc(count, sizeof(CGFloat));
CGFloat *stepProposedValues = calloc(count, sizeof(CGFloat));
NSMutableArray *valuesMapped = [NSMutableArray array];
while (YES) {
BOOL thresholdReached = YES;
for (NSUInteger i = 0; i < count; i++) {
stepProposedValues[i] = JNWAbsolutePosition(distances[i], (CGFloat)elapsed, 0, self.damping, self.mass, self.stiffness, [fromNumbers[i] floatValue]);
if (thresholdReached)
thresholdReached = JNWThresholdReached(stepValues[i], stepProposedValues[i], [toNumbers[i] floatValue], thresholds[i]);
}
if (thresholdReached)
break;
for (NSUInteger i = 0; i < count; i++) {
stepValues[i] = stepProposedValues[i];
}
[valuesMapped addObject:map(stepValues, count)];
elapsed += step;
}
free(distances);
free(thresholds);
free(stepValues);
free(stepProposedValues);
return valuesMapped;
}
BOOL JNWThresholdReached(CGFloat previousValue, CGFloat proposedValue, CGFloat finalValue, CGFloat threshold) {
CGFloat previousDifference = ABS(proposedValue - previousValue);
CGFloat finalDifference = ABS(previousValue - finalValue);
if (previousDifference <= threshold && finalDifference <= threshold) {
return YES;
}
return NO;
}
BOOL JNWCalculationsAreComplete(CGFloat value1, CGFloat proposedValue1, CGFloat to1, CGFloat value2, CGFloat proposedValue2, CGFloat to2, CGFloat value3, CGFloat proposedValue3, CGFloat to3) {
return ((fabs(proposedValue1 - value1) < JNWSpringAnimationKeyframeStep) && (fabs(value1 - to1) < JNWSpringAnimationKeyframeStep)
&& (fabs(proposedValue2 - value2) < JNWSpringAnimationKeyframeStep) && (fabs(value2 - to2) < JNWSpringAnimationKeyframeStep)
&& (fabs(proposedValue3 - value3) < JNWSpringAnimationKeyframeStep) && (fabs(value3 - to3) < JNWSpringAnimationKeyframeStep));
}
#pragma mark Damped Harmonic Oscillation
CGFloat JNWAngularFrequency(CGFloat k, CGFloat m, CGFloat b) {
CGFloat w0 = (CGFloat)sqrt(k / m);
CGFloat frequency = (CGFloat)sqrt(pow(w0, 2) - (pow(b, 2) / (4*pow(m, 2))));
if (isnan(frequency)) frequency = 0;
return frequency;
}
CGFloat JNWRelativePosition(CGFloat A, CGFloat t, CGFloat phi, CGFloat b, CGFloat m, CGFloat k) {
if (A == 0) return A;
CGFloat ex = (-b / (2 * m) * t);
CGFloat freq = JNWAngularFrequency(k, m, b);
return (CGFloat)(A * exp(ex) * cos(freq * t + phi));
}
CGFloat JNWAbsolutePosition(CGFloat A, CGFloat t, CGFloat phi, CGFloat b, CGFloat m, CGFloat k, CGFloat from) {
return from + A - JNWRelativePosition(A, t, phi, b, m, k);
}
// This feels a bit hacky. I'm sure there's a better way to accomplish this.
CGFloat JNWSpringAnimationThreshold(CGFloat magnitude) {
return JNWSpringAnimationMinimumThreshold * magnitude;
}
#pragma mark Description
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p> mass: %f, damping: %f, stiffness: %f, keyPath: %@, toValue: %@, fromValue %@", self.class, self, self.mass, self.damping, self.stiffness, self.keyPath, self.toValue, self.fromValue];
}
@end
@@ -0,0 +1,19 @@
#import <LegacyComponents/LegacyComponentsContext.h>
@implementation LegacyComponentsActionSheetAction
- (instancetype)initWithTitle:(NSString *)title action:(NSString *)action {
return [self initWithTitle:title action:action type:LegacyComponentsActionSheetActionTypeGeneric];
}
- (instancetype)initWithTitle:(NSString *)title action:(NSString *)action type:(LegacyComponentsActionSheetActionType)type {
self = [super init];
if (self != nil) {
_title = title;
_action = action;
_type = type;
}
return self;
}
@end
@@ -0,0 +1,19 @@
#import <Foundation/Foundation.h>
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/TGLocalization.h>
static id<LegacyComponentsGlobalsProvider> _provider;
@implementation LegacyComponentsGlobals
+ (void)setProvider:(id<LegacyComponentsGlobalsProvider>)provider {
_provider = provider;
}
+ (id<LegacyComponentsGlobalsProvider>)provider {
return _provider;
}
@end
@@ -0,0 +1,65 @@
#import <LegacyComponents/LegacyComponentsGlobals.h>
@class TGLocalization;
#ifdef __cplusplus
extern "C" {
#endif
TGLocalization *legacyEffectiveLocalization();
NSString *TGLocalized(NSString *s);
bool TGObjectCompare(id obj1, id obj2);
bool TGStringCompare(NSString *s1, NSString *s2);
void TGLegacyLog(NSString *format, ...);
int iosMajorVersion();
int iosMinorVersion();
void TGDispatchOnMainThread(dispatch_block_t block);
void TGDispatchAfter(double delay, dispatch_queue_t queue, dispatch_block_t block);
int deviceMemorySize();
int cpuCoreCount();
@interface UIColor (Int32)
- (int32_t)int32Value;
@end
#define UIColorRGB(rgb) ([[UIColor alloc] initWithRed:(((rgb >> 16) & 0xff) / 255.0f) green:(((rgb >> 8) & 0xff) / 255.0f) blue:(((rgb) & 0xff) / 255.0f) alpha:1.0f])
#define UIColorARGB(rgb) ([[UIColor alloc] initWithRed:(((rgb >> 16) & 0xff) / 255.0f) green:(((rgb >> 8) & 0xff) / 255.0f) blue:(((rgb) & 0xff) / 255.0f) alpha:(((rgb >> 24) & 0xff) / 255.0f)])
#define UIColorRGBA(rgb,a) ([[UIColor alloc] initWithRed:(((rgb >> 16) & 0xff) / 255.0f) green:(((rgb >> 8) & 0xff) / 255.0f) blue:(((rgb) & 0xff) / 255.0f) alpha:a])
#define TGRestrictedToMainThread {if(![[NSThread currentThread] isMainThread]) TGLegacyLog(@"***** Warning: main thread-bound operation is running in background! *****");}
#define TG_TIMESTAMP_DEFINE(s) CFAbsoluteTime tg_timestamp_##s = CFAbsoluteTimeGetCurrent(); int tg_timestamp_line_##s = __LINE__;
#define TG_TIMESTAMP_MEASURE(s) { CFAbsoluteTime tg_timestamp_current_time = CFAbsoluteTimeGetCurrent(); TGLegacyLog(@"%s %d-%d: %f ms", #s, tg_timestamp_line_##s, __LINE__, (tg_timestamp_current_time - tg_timestamp_##s) * 1000.0); tg_timestamp_##s = tg_timestamp_current_time; tg_timestamp_line_##s = __LINE__; }
#ifdef __LP64__
# define CGFloor floor
#else
# define CGFloor floorf
#endif
#ifdef __LP64__
# define CGRound round
# define CGCeil ceil
# define CGPow pow
# define CGSin sin
# define CGCos cos
# define CGSqrt sqrt
#else
# define CGRound roundf
# define CGCeil ceilf
# define CGPow powf
# define CGSin sinf
# define CGCos cosf
# define CGSqrt sqrtf
#endif
#define CGEven(x) ((((int)x) & 1) ? (x + 1) : x)
#define CGOdd(x) ((((int)x) & 1) ? x : (x + 1))
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,170 @@
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/TGLocalization.h>
#import <UIKit/UIKit.h>
#import <sys/sysctl.h>
#import <AppBundle/AppBundle.h>
TGLocalization *legacyEffectiveLocalization() {
return [[LegacyComponentsGlobals provider] effectiveLocalization];
}
NSString *TGLocalized(NSString *s) {
return [legacyEffectiveLocalization() get:s];
}
bool TGObjectCompare(id obj1, id obj2) {
if (obj1 == nil && obj2 == nil)
return true;
return [obj1 isEqual:obj2];
}
bool TGStringCompare(NSString *s1, NSString *s2) {
if (s1.length == 0 && s2.length == 0)
return true;
if ((s1 == nil) != (s2 == nil))
return false;
return s1 == nil || [s1 isEqualToString:s2];
}
void TGLegacyLog(NSString *format, ...)
{
va_list L;
va_start(L, format);
NSString *string = [[NSString alloc] initWithFormat:format arguments:L];
va_end(L);
[[LegacyComponentsGlobals provider] log:string];
}
int iosMajorVersion()
{
static bool initialized = false;
static int version = 7;
if (!initialized) {
version = [[[UIDevice currentDevice] systemVersion] intValue];
initialized = true;
}
return version;
}
int iosMinorVersion()
{
static bool initialized = false;
static int version = 0;
if (!initialized)
{
NSString *versionString = [[UIDevice currentDevice] systemVersion];
NSRange range = [versionString rangeOfString:@"."];
if (range.location != NSNotFound) {
version = [[versionString substringFromIndex:range.location + 1] intValue];
}
initialized = true;
}
return version;
}
int deviceMemorySize()
{
static int memorySize = 0;
if (memorySize == 0)
{
size_t len;
__int64_t nmem;
len = sizeof(nmem);
sysctlbyname("hw.memsize", &nmem, &len, NULL, 0);
memorySize = (int)(nmem / (1024 * 1024));
}
return memorySize;
}
int cpuCoreCount()
{
static int count = 0;
if (count == 0)
{
size_t len;
unsigned int ncpu;
len = sizeof(ncpu);
sysctlbyname("hw.ncpu", &ncpu, &len, NULL, 0);
count = ncpu;
}
return count;
}
void TGDispatchOnMainThread(dispatch_block_t block)
{
if ([NSThread isMainThread])
block();
else
dispatch_async(dispatch_get_main_queue(), block);
}
void TGDispatchAfter(double delay, dispatch_queue_t queue, dispatch_block_t block)
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((delay) * NSEC_PER_SEC)), queue, block);
}
static NSBundle *resourcesBundle() {
static NSBundle *currentBundle = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
currentBundle = getAppBundle();
NSString *updatedPath = [[currentBundle bundlePath] stringByAppendingPathComponent:@"LegacyComponentsResources.bundle"];
currentBundle = [NSBundle bundleWithPath:updatedPath];
});
return currentBundle;
}
UIImage *TGComponentsImageNamed(NSString *name) {
if (iosMajorVersion() < 8)
return [UIImage imageNamed:[NSString stringWithFormat:@"LegacyComponentsResources.bundle/%@", name]];
UIImage *image = [UIImage imageNamed:name inBundle:resourcesBundle() compatibleWithTraitCollection:nil];
if (image == nil) {
assert(true);
}
return image;
}
NSString *TGComponentsPathForResource(NSString *name, NSString *type) {
NSBundle *bundle = resourcesBundle();
if (bundle == nil) {
bundle = getAppBundle();
}
return [bundle pathForResource:name ofType:type];
}
@implementation UIColor (Int32)
- (int32_t)int32Value {
CGFloat red, green, blue, alpha;
if (![self getRed:&red green:&green blue:&blue alpha:&alpha]) {
if ([self getWhite:&red alpha:&alpha]) {
green = red;
blue = red;
} else {
red = green = blue = alpha = 1.0;
}
}
int8_t r = (int8_t)(red * 255);
int8_t g = (int8_t)(green * 255);
int8_t b = (int8_t)(blue * 255);
int8_t a = (int8_t)(alpha * 255);
int32_t intValue = (a << 24) | (r << 16) | (g << 8) | b;
return intValue;
}
@end
@@ -0,0 +1,390 @@
#import <LegacyComponents/NSInputStream+TL.h>
#import "LegacyComponentsInternal.h"
#import <endian.h>
static inline int roundUpInput(int numToRound, int multiple)
{
if (multiple == 0)
{
return numToRound;
}
int remainder = numToRound % multiple;
if (remainder == 0)
{
return numToRound;
}
return numToRound + multiple - remainder;
}
@implementation NSInputStream (TL)
- (int32_t)readInt32
{
int32_t value = 0;
if ([self read:(uint8_t *)&value maxLength:4] != 4)
{
TGLegacyLog(@"***** Couldn't read int32");
@throw [[NSException alloc] initWithName:@"NSInputStream+TLException" reason:@"readInt32 end of stream" userInfo:@{}];
}
#if __BYTE_ORDER == __LITTLE_ENDIAN
#elif __BYTE_ORDER == __BIG_ENDIAN
# error "Big endian is not implemented"
#else
# error "Unknown byte order"
#endif
return value;
}
- (int32_t)readInt32:(bool *)failed
{
int32_t value = 0;
if ([self read:(uint8_t *)&value maxLength:4] != 4)
{
*failed = true;
return 0;
}
#if __BYTE_ORDER == __LITTLE_ENDIAN
#elif __BYTE_ORDER == __BIG_ENDIAN
# error "Big endian is not implemented"
#else
# error "Unknown byte order"
#endif
return value;
}
- (int64_t)readInt64
{
int64_t value = 0;
if ([self read:(uint8_t *)&value maxLength:8] != 8)
{
TGLegacyLog(@"***** Couldn't read int64");
}
#if __BYTE_ORDER == __LITTLE_ENDIAN
#elif __BYTE_ORDER == __BIG_ENDIAN
# error "Big endian is not implemented"
#else
# error "Unknown byte order"
#endif
return value;
}
- (int64_t)readInt64:(bool *)failed
{
int64_t value = 0;
if ([self read:(uint8_t *)&value maxLength:8] != 8)
{
*failed = true;
return 0;
}
#if __BYTE_ORDER == __LITTLE_ENDIAN
#elif __BYTE_ORDER == __BIG_ENDIAN
# error "Big endian is not implemented"
#else
# error "Unknown byte order"
#endif
return value;
}
- (double)readDouble
{
double value = 0.0;
if ([self read:(uint8_t *)&value maxLength:8] != 8)
{
TGLegacyLog(@"***** Couldn't read double");
}
#if __BYTE_ORDER == __LITTLE_ENDIAN
#elif __BYTE_ORDER == __BIG_ENDIAN
# error "Big endian is not implemented"
#else
# error "Unknown byte order"
#endif
return value;
}
- (double)readDouble:(bool *)failed
{
double value = 0.0;
if ([self read:(uint8_t *)&value maxLength:8] != 8)
{
*failed = true;
return 0.0;
}
#if __BYTE_ORDER == __LITTLE_ENDIAN
#elif __BYTE_ORDER == __BIG_ENDIAN
# error "Big endian is not implemented"
#else
# error "Unknown byte order"
#endif
return value;
}
- (NSData *)readData:(int)length
{
uint8_t *bytes = (uint8_t *)malloc(length);
int readLen = (int)[self read:bytes maxLength:length];
if (readLen != length)
{
TGLegacyLog(@"***** Couldn't read %d bytes", length);
}
NSData *data = [[NSData alloc] initWithBytesNoCopy:bytes length:length freeWhenDone:true];
return data;
}
- (NSData *)readData:(int)length failed:(bool *)failed
{
uint8_t *bytes = (uint8_t *)malloc(length);
int readLen = (int)[self read:bytes maxLength:length];
if (readLen != length)
{
free(bytes);
*failed = true;
return nil;
}
NSData *data = [[NSData alloc] initWithBytesNoCopy:bytes length:length freeWhenDone:true];
return data;
}
- (NSMutableData *)readMutableData:(int)length
{
uint8_t *bytes = (uint8_t *)malloc(length);
int readLen = (int)[self read:bytes maxLength:length];
if (readLen != length)
{
TGLegacyLog(@"***** Couldn't read %d bytes", length);
}
NSMutableData *data = [[NSMutableData alloc] initWithBytesNoCopy:bytes length:length freeWhenDone:true];
return data;
}
- (NSMutableData *)readMutableData:(int)length failed:(bool *)failed
{
uint8_t *bytes = (uint8_t *)malloc(length);
int readLen = (int)[self read:bytes maxLength:length];
if (readLen != length)
{
free(bytes);
*failed = true;
return nil;
}
NSMutableData *data = [[NSMutableData alloc] initWithBytesNoCopy:bytes length:length freeWhenDone:true];
return data;
}
- (NSString *)readString
{
uint8_t tmp = 0;
[self read:&tmp maxLength:1];
int paddingBytes = 0;
int32_t length = tmp;
if (length == 254)
{
length = 0;
[self read:((uint8_t *)&length) + 1 maxLength:3];
length >>= 8;
#if __BYTE_ORDER == __LITTLE_ENDIAN
#elif __BYTE_ORDER == __BIG_ENDIAN
# error "Big endian is not implemented"
#else
# error "Unknown byte order"
#endif
paddingBytes = roundUpInput(length, 4) - length;
}
else
{
paddingBytes = roundUpInput(length + 1, 4) - (length + 1);
}
NSString *string = nil;
if (length > 0)
{
uint8_t *bytes = (uint8_t *)malloc(length);
int readLen = (int)[self read:bytes maxLength:length];
if (readLen != length)
{
TGLegacyLog(@"***** Couldn't read %d bytes", length);
}
string = [[NSString alloc] initWithBytesNoCopy:bytes length:length encoding:NSUTF8StringEncoding freeWhenDone:true];
}
else
{
string = @"";
}
for (int i = 0; i < paddingBytes; i++)
[self read:&tmp maxLength:1];
return string;
}
- (NSString *)readString:(bool *)failed
{
uint8_t tmp = 0;
[self read:&tmp maxLength:1];
int paddingBytes = 0;
int32_t length = tmp;
if (length == 254)
{
length = 0;
[self read:((uint8_t *)&length) + 1 maxLength:3];
length >>= 8;
#if __BYTE_ORDER == __LITTLE_ENDIAN
#elif __BYTE_ORDER == __BIG_ENDIAN
# error "Big endian is not implemented"
#else
# error "Unknown byte order"
#endif
paddingBytes = roundUpInput(length, 4) - length;
}
else
{
paddingBytes = roundUpInput(length + 1, 4) - (length + 1);
}
NSString *string = nil;
if (length > 0)
{
uint8_t *bytes = (uint8_t *)malloc(length);
int readLen = (int)[self read:bytes maxLength:length];
if (readLen != length)
{
free(bytes);
*failed = true;
return nil;
}
string = [[NSString alloc] initWithBytesNoCopy:bytes length:length encoding:NSUTF8StringEncoding freeWhenDone:true];
}
else
{
string = @"";
}
for (int i = 0; i < paddingBytes; i++)
[self read:&tmp maxLength:1];
return string;
}
- (NSData *)readBytes
{
uint8_t tmp = 0;
[self read:&tmp maxLength:1];
int paddingBytes = 0;
int32_t length = tmp;
if (length == 254)
{
length = 0;
[self read:((uint8_t *)&length) + 1 maxLength:3];
length >>= 8;
#if __BYTE_ORDER == __LITTLE_ENDIAN
#elif __BYTE_ORDER == __BIG_ENDIAN
# error "Big endian is not implemented"
#else
# error "Unknown byte order"
#endif
paddingBytes = roundUpInput(length, 4) - length;
}
else
{
paddingBytes = roundUpInput(length + 1, 4) - (length + 1);
}
uint8_t *bytes = (uint8_t *)malloc(length);
int readLen = (int)[self read:bytes maxLength:length];
if (readLen != length)
{
TGLegacyLog(@"***** Couldn't read %d bytes", length);
}
NSData *result = [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:true];
for (int i = 0; i < paddingBytes; i++)
[self read:&tmp maxLength:1];
return result;
}
- (NSData *)readBytes:(bool *)failed
{
uint8_t tmp = 0;
[self read:&tmp maxLength:1];
int paddingBytes = 0;
int32_t length = tmp;
if (length == 254)
{
length = 0;
[self read:((uint8_t *)&length) + 1 maxLength:3];
length >>= 8;
#if __BYTE_ORDER == __LITTLE_ENDIAN
#elif __BYTE_ORDER == __BIG_ENDIAN
# error "Big endian is not implemented"
#else
# error "Unknown byte order"
#endif
paddingBytes = roundUpInput(length, 4) - length;
}
else
{
paddingBytes = roundUpInput(length + 1, 4) - (length + 1);
}
uint8_t *bytes = (uint8_t *)malloc(length);
int readLen = (int)[self read:bytes maxLength:length];
if (readLen != length)
{
free(bytes);
*failed = true;
return nil;
}
NSData *result = [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:true];
for (int i = 0; i < paddingBytes; i++)
[self read:&tmp maxLength:1];
return result;
}
@end
@@ -0,0 +1,73 @@
#import <LegacyComponents/NSObject+TGLock.h>
#import <objc/runtime.h>
static const char *lockPropertyKey = "TGObjectLock::lock";
@interface TGObjectLockImpl : NSObject
{
TG_SYNCHRONIZED_DEFINE(objectLock);
}
- (void)tgTakeLock;
- (void)tgFreeLock;
@end
@implementation TGObjectLockImpl
- (id)init
{
self = [super init];
if (self != nil)
{
TG_SYNCHRONIZED_INIT(objectLock);
}
return self;
}
- (void)tgTakeLock
{
TG_SYNCHRONIZED_BEGIN(objectLock);
}
- (void)tgFreeLock
{
TG_SYNCHRONIZED_END(objectLock);
}
@end
@implementation NSObject (TGLock)
- (void)tgLockObject
{
TGObjectLockImpl *lock = (TGObjectLockImpl *)objc_getAssociatedObject(self, lockPropertyKey);
if (lock == nil)
{
@synchronized(self)
{
lock = [[TGObjectLockImpl alloc] init];
objc_setAssociatedObject(self, lockPropertyKey, lock, OBJC_ASSOCIATION_RETAIN);
}
}
[lock tgTakeLock];
}
- (void)tgUnlockObject
{
TGObjectLockImpl *lock = (TGObjectLockImpl *)objc_getAssociatedObject(self, lockPropertyKey);
if (lock == nil)
{
@synchronized(self)
{
lock = [[TGObjectLockImpl alloc] init];
objc_setAssociatedObject(self, lockPropertyKey, lock, OBJC_ASSOCIATION_RETAIN);
}
}
[lock tgFreeLock];
}
@end
@@ -0,0 +1,28 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, JNWValueType) {
JNWValueTypeNumber,
JNWValueTypePoint,
JNWValueTypeSize,
JNWValueTypeRect,
JNWValueTypeAffineTransform,
JNWValueTypeTransform3D,
JNWValueTypeUnknown
};
@interface NSValue (JNWAdditions)
- (CGRect)jnw_rectValue;
- (CGSize)jnw_sizeValue;
- (CGPoint)jnw_pointValue;
- (CGAffineTransform)jnw_affineTransformValue;
+ (NSValue *)jnw_valueWithRect:(CGRect)rect;
+ (NSValue *)jnw_valueWithSize:(CGSize)size;
+ (NSValue *)jnw_valueWithPoint:(CGPoint)point;
+ (NSValue *)jnw_valueWithAffineTransform:(CGAffineTransform)transform;
- (JNWValueType)jnw_type;
@end
@@ -0,0 +1,124 @@
/*
Copyright (c) 2013, Jonathan Willing. All rights reserved.
Licensed under the MIT license <http://opensource.org/licenses/MIT>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
*/
#import "NSValue+JNWAdditions.h"
@implementation NSValue (JNWAdditions)
#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED
- (CGRect)jnw_rectValue {
return [self CGRectValue];
}
- (CGSize)jnw_sizeValue {
return [self CGSizeValue];
}
- (CGPoint)jnw_pointValue {
return [self CGPointValue];
}
- (CGAffineTransform)jnw_affineTransformValue {
return [self CGAffineTransformValue];
}
+ (NSValue *)jnw_valueWithRect:(CGRect)rect {
return [self valueWithCGRect:rect];
}
+ (NSValue *)jnw_valueWithPoint:(CGPoint)point {
return [self valueWithCGPoint:point];
}
+ (NSValue *)jnw_valueWithSize:(CGSize)size {
return [self valueWithCGSize:size];
}
+ (NSValue *)jnw_valueWithAffineTransform:(CGAffineTransform)transform {
return [self valueWithCGAffineTransform:transform];
}
#elif TARGET_OS_MAC
- (CGRect)jnw_rectValue {
return [self rectValue];
}
- (CGSize)jnw_sizeValue {
return [self sizeValue];
}
- (CGPoint)jnw_pointValue {
return [self pointValue];
}
- (CGAffineTransform)jnw_affineTransformValue {
CGAffineTransform transform;
[self getValue:&transform];
return transform;
}
+ (NSValue *)jnw_valueWithRect:(CGRect)rect {
return [self valueWithRect:rect];
}
+ (NSValue *)jnw_valueWithPoint:(CGPoint)point {
return [self valueWithPoint:point];
}
+ (NSValue *)jnw_valueWithSize:(CGSize)size {
return [self valueWithSize:size];
}
+ (NSValue *)jnw_valueWithAffineTransform:(CGAffineTransform)transform {
return [NSValue valueWithBytes:&transform objCType:@encode(CGAffineTransform)];
}
#endif
- (JNWValueType)jnw_type {
const char *type = self.objCType;
static const NSInteger numberofNumberTypes = 10;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-folding-constant"
static const char *numberTypes[numberofNumberTypes] = { "i", "s", "l", "q", "I", "S", "L", "Q", "f", "d" };
#pragma clang diagnostic pop
for (NSInteger i = 0; i < numberofNumberTypes; i++) {
if (strcmp(type, numberTypes[i]) == 0) {
return JNWValueTypeNumber;
}
}
if (strcmp(type, @encode(CGPoint)) == 0) {
return JNWValueTypePoint;
} else if (strcmp(type, @encode(CGSize)) == 0) {
return JNWValueTypeSize;
} else if (strcmp(type, @encode(CGRect)) == 0) {
return JNWValueTypeRect;
} else if (strcmp(type, @encode(CGAffineTransform)) == 0) {
return JNWValueTypeAffineTransform;
} else if (strcmp(type, @encode(CATransform3D)) == 0) {
return JNWValueTypeTransform3D;
} else {
return JNWValueTypeUnknown;
}
}
@end
@@ -0,0 +1,21 @@
#import "PGPhotoTool.h"
#import "PGPhotoBlurPass.h"
@interface PGBlurToolValue : NSObject <NSCopying>
@property (nonatomic, assign) PGBlurToolType type;
@property (nonatomic, assign) CGPoint point;
@property (nonatomic, assign) CGFloat size;
@property (nonatomic, assign) CGFloat falloff;
@property (nonatomic, assign) CGFloat angle;
@property (nonatomic, assign) CGFloat intensity;
@property (nonatomic, assign) bool editingIntensity;
@end
@interface PGBlurTool : PGPhotoTool
@property (nonatomic, readonly) NSString *intensityEditingTitle;
@end
@@ -0,0 +1,231 @@
#import "PGBlurTool.h"
#import "LegacyComponentsInternal.h"
#import "PGPhotoBlurPass.h"
#import "TGPhotoEditorBlurToolView.h"
#import "TGPhotoEditorBlurAreaView.h"
@implementation PGBlurToolValue
- (instancetype)copyWithZone:(NSZone *)__unused zone
{
PGBlurToolValue *value = [[PGBlurToolValue alloc] init];
value.type = self.type;
value.point = self.point;
value.size = self.size;
value.falloff = self.falloff;
value.angle = self.angle;
value.intensity = self.intensity;
value.editingIntensity = self.editingIntensity;
return value;
}
- (BOOL)isEqual:(id)object
{
if (object == self)
return true;
if (!object || ![object isKindOfClass:[self class]])
return false;
PGBlurToolValue *value = (PGBlurToolValue *)object;
if (value.type != self.type)
return false;
if (!CGPointEqualToPoint(value.point, self.point))
return false;
if (value.size != self.size)
return false;
if (value.falloff != self.falloff)
return false;
if (value.angle != self.angle)
return false;
if (value.intensity != self.intensity)
return false;
return true;
}
@end
@implementation PGBlurTool
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_identifier = @"blur";
_type = PGPhotoToolTypePass;
_order = 3;
_pass = [[PGPhotoBlurPass alloc] init];
_minimumValue = 0;
_maximumValue = 100;
_defaultValue = 50;
PGBlurToolValue *value = [[PGBlurToolValue alloc] init];
value.type = PGBlurToolTypeNone;
value.point = CGPointMake(0.5f, 0.5f);
value.falloff = 0.12f;
value.size = 0.24f;
value.angle = 0;
value.intensity = _defaultValue;
self.value = value;
}
return self;
}
- (NSString *)title
{
return TGLocalized(@"PhotoEditor.BlurTool");
}
- (NSString *)intensityEditingTitle
{
return TGLocalized(@"PhotoEditor.BlurToolRadius");
}
- (UIView <TGPhotoEditorToolView> *)itemControlViewWithChangeBlock:(void (^)(id newValue, bool animated))changeBlock
{
return [self itemControlViewWithChangeBlock:changeBlock explicit:false nameWidth:0.0f];
}
- (UIView <TGPhotoEditorToolView> *)itemControlViewWithChangeBlock:(void (^)(id, bool))changeBlock explicit:(bool)explicit nameWidth:(CGFloat)__unused nameWidth
{
__weak PGBlurTool *weakSelf = self;
UIView <TGPhotoEditorToolView> *view = [[TGPhotoEditorBlurToolView alloc] initWithEditorItem:self];
view.valueChanged = ^(id newValue, bool animated)
{
__strong PGBlurTool *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (!explicit && [strongSelf.tempValue isEqual:newValue])
return;
if (explicit && [strongSelf.value isEqual:newValue])
return;
if (!explicit)
strongSelf.tempValue = newValue;
else
strongSelf.value = newValue;
if (changeBlock != nil)
changeBlock(newValue, animated);
};
return view;
}
- (UIView <TGPhotoEditorToolView> *)itemAreaViewWithChangeBlock:(void (^)(id))changeBlock explicit:(bool)explicit
{
__weak PGBlurTool *weakSelf = self;
UIView <TGPhotoEditorToolView> *view = [[TGPhotoEditorBlurAreaView alloc] initWithEditorItem:self];
view.valueChanged = ^(id newValue, __unused bool animated)
{
__strong PGPhotoTool *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (newValue != nil)
{
if (!explicit && [strongSelf.tempValue isEqual:newValue])
return;
if (explicit && [strongSelf.value isEqual:newValue])
return;
if (!explicit)
strongSelf.tempValue = newValue;
else
strongSelf.value = newValue;
}
if (changeBlock != nil)
changeBlock(newValue);
};
return view;
}
- (Class)valueClass
{
return [PGBlurToolValue class];
}
- (PGPhotoProcessPass *)pass
{
PGBlurToolValue *value = (PGBlurToolValue *)self.displayValue;
if (value.type == PGBlurToolTypeNone)
return nil;
[self updatePassParameters];
return _pass;
}
- (void)updatePassParameters
{
PGBlurToolValue *value = (PGBlurToolValue *)self.displayValue;
PGPhotoBlurPass *blurPass = (PGPhotoBlurPass *)_pass;
if ([value isKindOfClass:[PGBlurToolValue class]]) {
blurPass.type = value.type;
blurPass.size = value.size;
blurPass.point = value.point;
blurPass.angle = value.angle;
blurPass.falloff = value.falloff;
} else {
blurPass.type = PGBlurToolTypeNone;
}
}
- (bool)shouldBeSkipped
{
if (self.disabled)
return true;
return (((PGBlurToolValue *)self.displayValue).type == PGBlurToolTypeNone);
}
- (NSString *)stringValue
{
if ([self.value isKindOfClass:[PGBlurToolValue class]])
{
PGBlurToolValue *value = (PGBlurToolValue *)self.value;
if (value.type == PGBlurToolTypeRadial)
return TGLocalized(@"PhotoEditor.BlurToolRadial");
else if (value.type == PGBlurToolTypeLinear)
return TGLocalized(@"PhotoEditor.BlurToolLinear");
}
return nil;
}
- (bool)isSimple
{
return false;
}
- (bool)isRegional
{
return true;
}
- (bool)isAvialableForVideo
{
return false;
}
@end
@@ -0,0 +1,885 @@
#import <LegacyComponents/PGCamera.h>
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/PGCameraCaptureSession.h>
#import <LegacyComponents/PGCameraMovieWriter.h>
#import <LegacyComponents/PGCameraDeviceAngleSampler.h>
#import <SSignalKit/SSignalKit.h>
#import <LegacyComponents/TGCameraPreviewView.h>
NSString *const PGCameraFlashActiveKey = @"flashActive";
NSString *const PGCameraFlashAvailableKey = @"flashAvailable";
NSString *const PGCameraTorchActiveKey = @"torchActive";
NSString *const PGCameraTorchAvailableKey = @"torchAvailable";
NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
@interface PGCamera ()
{
dispatch_queue_t cameraProcessingQueue;
dispatch_queue_t audioProcessingQueue;
AVCaptureDevice *_microphone;
AVCaptureVideoDataOutput *videoOutput;
AVCaptureAudioDataOutput *audioOutput;
PGCameraDeviceAngleSampler *_deviceAngleSampler;
bool _subscribedForCameraChanges;
bool _invalidated;
bool _wasCapturingOnEnterBackground;
bool _capturing;
bool _moment;
TGCameraPreviewView *_previewView;
UIInterfaceOrientation _currentPhotoOrientation;
NSTimeInterval _captureStartTime;
}
@property (nonatomic, copy) void(^photoCaptureCompletionBlock)(UIImage *image, PGCameraShotMetadata *metadata);
@end
@implementation PGCamera
- (instancetype)init
{
return [self initWithMode:PGCameraModePhoto position:PGCameraPositionUndefined];
}
- (instancetype)initWithMode:(PGCameraMode)mode position:(PGCameraPosition)position
{
self = [super init];
if (self != nil)
{
_captureSession = [[PGCameraCaptureSession alloc] initWithMode:mode position:position];
_deviceAngleSampler = [[PGCameraDeviceAngleSampler alloc] init];
[_deviceAngleSampler startMeasuring];
__weak PGCamera *weakSelf = self;
self.captureSession.requestPreviewIsMirrored = ^bool
{
__strong PGCamera *strongSelf = weakSelf;
if (strongSelf == nil || strongSelf->_previewView == nil)
return false;
TGCameraPreviewView *previewView = strongSelf->_previewView;
return previewView.captureConnection.videoMirrored;
};
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleEnteredBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleEnteredForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterrupted:) name:AVCaptureSessionWasInterruptedNotification object:nil];
}
return self;
}
- (void)dealloc
{
TGLegacyLog(@"Camera: dealloc");
[_deviceAngleSampler stopMeasuring];
[self _unsubscribeFromCameraChanges];
self.captureSession.requestPreviewIsMirrored = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureSessionRuntimeErrorNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureSessionWasInterruptedNotification object:nil];
}
- (void)handleEnteredBackground:(NSNotification *)__unused notification
{
if (self.isCapturing) {
_wasCapturingOnEnterBackground = true;
[_previewView fadeOutAnimated:false];
}
[self stopCaptureForPause:true completion:nil];
}
- (void)handleEnteredForeground:(NSNotification *)__unused notification
{
if (_wasCapturingOnEnterBackground)
{
_wasCapturingOnEnterBackground = false;
__weak PGCamera *weakSelf = self;
[self startCaptureForResume:true completion:^{
__strong PGCamera *strongSelf = weakSelf;
if (strongSelf != nil) {
[strongSelf->_previewView fadeInAnimated:true];
}
}];
}
}
- (void)handleRuntimeError:(NSNotification *)notification
{
TGLegacyLog(@"ERROR: Camera runtime error: %@", notification.userInfo[AVCaptureSessionErrorKey]);
__weak PGCamera *weakSelf = self;
TGDispatchAfter(1.5f, [PGCameraCaptureSession cameraQueue]._dispatch_queue, ^
{
__strong PGCamera *strongSelf = weakSelf;
if (strongSelf == nil || strongSelf->_invalidated)
return;
[strongSelf _unsubscribeFromCameraChanges];
for (AVCaptureInput *input in strongSelf.captureSession.inputs)
[strongSelf.captureSession removeInput:input];
for (AVCaptureOutput *output in strongSelf.captureSession.outputs)
[strongSelf.captureSession removeOutput:output];
[strongSelf.captureSession performInitialConfigurationWithCompletion:^
{
__strong PGCamera *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf _subscribeForCameraChanges];
}];
});
}
- (void)handleInterrupted:(NSNotification *)notification
{
if (iosMajorVersion() < 9)
return;
AVCaptureSessionInterruptionReason reason = [notification.userInfo[AVCaptureSessionInterruptionReasonKey] integerValue];
TGLegacyLog(@"WARNING: Camera was interrupted with reason %d", reason);
if (self.captureInterrupted != nil)
self.captureInterrupted(reason);
if (self.isRecordingVideo)
[self stopVideoRecording];
}
- (void)_subscribeForCameraChanges
{
if (_subscribedForCameraChanges)
return;
_subscribedForCameraChanges = true;
[self.captureSession.videoDevice addObserver:self forKeyPath:PGCameraFlashActiveKey options:NSKeyValueObservingOptionNew context:NULL];
[self.captureSession.videoDevice addObserver:self forKeyPath:PGCameraFlashAvailableKey options:NSKeyValueObservingOptionNew context:NULL];
[self.captureSession.videoDevice addObserver:self forKeyPath:PGCameraTorchActiveKey options:NSKeyValueObservingOptionNew context:NULL];
[self.captureSession.videoDevice addObserver:self forKeyPath:PGCameraTorchAvailableKey options:NSKeyValueObservingOptionNew context:NULL];
[self.captureSession.videoDevice addObserver:self forKeyPath:PGCameraAdjustingFocusKey options:NSKeyValueObservingOptionNew context:NULL];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(subjectAreaChanged:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:self.captureSession.videoDevice];
}
- (void)_unsubscribeFromCameraChanges
{
if (!_subscribedForCameraChanges)
return;
_subscribedForCameraChanges = false;
@try {
[self.captureSession.videoDevice removeObserver:self forKeyPath:PGCameraFlashActiveKey];
[self.captureSession.videoDevice removeObserver:self forKeyPath:PGCameraFlashAvailableKey];
[self.captureSession.videoDevice removeObserver:self forKeyPath:PGCameraTorchActiveKey];
[self.captureSession.videoDevice removeObserver:self forKeyPath:PGCameraTorchAvailableKey];
[self.captureSession.videoDevice removeObserver:self forKeyPath:PGCameraAdjustingFocusKey];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:self.captureSession.videoDevice];
} @catch(NSException *e) { }
}
- (void)attachPreviewView:(TGCameraPreviewView *)previewView
{
TGCameraPreviewView *currentPreviewView = _previewView;
if (currentPreviewView != nil)
[currentPreviewView invalidate];
_previewView = previewView;
[previewView setupWithCamera:self];
__weak PGCamera *weakSelf = self;
[[PGCameraCaptureSession cameraQueue] dispatch:^
{
__strong PGCamera *strongSelf = weakSelf;
if (strongSelf == nil || strongSelf->_invalidated)
return;
[strongSelf.captureSession performInitialConfigurationWithCompletion:^
{
__strong PGCamera *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf _subscribeForCameraChanges];
}];
}];
}
#pragma mark -
- (bool)isCapturing
{
return _capturing;
}
- (void)startCaptureForResume:(bool)resume completion:(void (^)(void))completion
{
if (_invalidated)
return;
[[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (self.captureSession.isRunning)
return;
_capturing = true;
TGLegacyLog(@"Camera: start capture");
#if !TARGET_IPHONE_SIMULATOR
[self.captureSession startRunning];
#endif
if (_captureStartTime < FLT_EPSILON)
_captureStartTime = CFAbsoluteTimeGetCurrent();
TGDispatchOnMainThread(^
{
if (self.captureStarted != nil)
self.captureStarted(resume);
if (completion != nil)
completion();
});
}];
}
- (void)stopCaptureForPause:(bool)pause completion:(void (^)(void))completion
{
if (_invalidated)
return;
if (!pause)
_invalidated = true;
TGLegacyLog(@"Camera: stop capture");
[[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (_invalidated)
{
#if !TARGET_IPHONE_SIMULATOR
[self.captureSession beginConfiguration];
[self.captureSession resetFlashMode];
TGLegacyLog(@"Camera: stop capture invalidated");
TGCameraPreviewView *previewView = _previewView;
if (previewView != nil)
[previewView invalidate];
for (AVCaptureInput *input in self.captureSession.inputs)
[self.captureSession removeInput:input];
for (AVCaptureOutput *output in self.captureSession.outputs)
[self.captureSession removeOutput:output];
[self.captureSession commitConfiguration];
#endif
}
TGLegacyLog(@"Camera: stop running");
#if !TARGET_IPHONE_SIMULATOR
@try {
[self.captureSession stopRunning];
} @catch (NSException *exception) {
TGLegacyLog(@"Camera: caught exception %@", exception.description);
[self.captureSession commitConfiguration];
[self.captureSession stopRunning];
TGLegacyLog(@"Camera: seems to be successfully resolved");
}
#endif
_capturing = false;
TGDispatchOnMainThread(^
{
if (_invalidated)
_previewView = nil;
if (self.captureStopped != nil)
self.captureStopped(pause);
});
if (completion != nil)
completion();
}];
}
- (bool)isResetNeeded
{
return self.captureSession.isResetNeeded;
}
- (void)resetSynchronous:(bool)synchronous completion:(void (^)(void))completion
{
[self resetTerminal:false synchronous:synchronous completion:completion];
}
- (void)resetTerminal:(bool)__unused terminal synchronous:(bool)synchronous completion:(void (^)(void))completion
{
void (^block)(void) = ^
{
[self _unsubscribeFromCameraChanges];
[self.captureSession reset];
[self _subscribeForCameraChanges];
if (completion != nil)
completion();
};
if (synchronous)
[[PGCameraCaptureSession cameraQueue] dispatchSync:block];
else
[[PGCameraCaptureSession cameraQueue] dispatch:block];
}
#pragma mark -
- (void)captureNextFrameCompletion:(void (^)(UIImage * image))completion
{
[self.captureSession captureNextFrameCompletion:completion];
}
- (UIImage *)normalizeImageOrientation:(UIImage *)image
{
if (image.imageOrientation == UIImageOrientationUp) {
return image;
}
CGRect newRect = CGRectMake(0.0f, 0.0f, image.size.width, image.size.height);
UIGraphicsBeginImageContextWithOptions(newRect.size, true, image.scale);
[image drawInRect:newRect];
UIImage *normalized = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return normalized;
}
- (void)takePhotoWithCompletion:(void (^)(UIImage *result, PGCameraShotMetadata *metadata))completion
{
bool videoMirrored = !self.disableResultMirroring ? _previewView.captureConnection.videoMirrored : false;
void (^takePhoto)(void) = ^
{
self.photoCaptureCompletionBlock = completion;
[[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (!self.captureSession.isRunning || _invalidated)
return;
AVCaptureConnection *imageConnection = [self.captureSession.imageOutput connectionWithMediaType:AVMediaTypeVideo];
[imageConnection setVideoMirrored:videoMirrored];
UIInterfaceOrientation orientation = UIInterfaceOrientationPortrait;
if (self.requestedCurrentInterfaceOrientation != nil)
orientation = self.requestedCurrentInterfaceOrientation(NULL);
[imageConnection setVideoOrientation:[PGCamera _videoOrientationForInterfaceOrientation:orientation mirrored:false]];
_currentPhotoOrientation = orientation;
AVCapturePhotoSettings *photoSettings = [AVCapturePhotoSettings photoSettings];
photoSettings.flashMode = self.captureSession.currentDeviceFlashMode;
[self.captureSession.imageOutput capturePhotoWithSettings:photoSettings delegate:self];
}];
};
NSTimeInterval delta = CFAbsoluteTimeGetCurrent() - _captureStartTime;
if (CFAbsoluteTimeGetCurrent() - _captureStartTime > 0.4)
takePhoto();
else
TGDispatchAfter(0.4 - delta, [[PGCameraCaptureSession cameraQueue] _dispatch_queue], takePhoto);
}
- (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhoto:(AVCapturePhoto *)photo error:(NSError *)error {
if (error) {
NSLog(@"Error capturing photo: %@", error);
return;
}
NSData *photoData = [photo fileDataRepresentation];
UIImage *capturedImage = [UIImage imageWithData:photoData];
PGCameraShotMetadata *metadata = [[PGCameraShotMetadata alloc] init];
metadata.deviceAngle = [PGCameraShotMetadata relativeDeviceAngleFromAngle:_deviceAngleSampler.currentDeviceAngle orientation:_currentPhotoOrientation];
UIImage *image = [self normalizeImageOrientation:capturedImage];
TGDispatchOnMainThread(^
{
if (self.photoCaptureCompletionBlock != nil) {
self.photoCaptureCompletionBlock(image, metadata);
self.photoCaptureCompletionBlock = nil;
}
});
}
- (void)startVideoRecordingForMoment:(bool)moment completion:(void (^)(NSURL *, CGAffineTransform transform, CGSize dimensions, NSTimeInterval duration, bool success))completion
{
[[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (!self.captureSession.isRunning || _invalidated)
return;
void (^startRecording)(void) = ^
{
UIInterfaceOrientation orientation = UIInterfaceOrientationPortrait;
bool mirrored = false;
if (self.requestedCurrentInterfaceOrientation != nil)
orientation = self.requestedCurrentInterfaceOrientation(&mirrored);
_moment = moment;
[self.captureSession startVideoRecordingWithOrientation:[PGCamera _videoOrientationForInterfaceOrientation:orientation mirrored:mirrored] mirrored:mirrored completion:completion];
TGDispatchOnMainThread(^
{
if (self.reallyBeganVideoRecording != nil)
self.reallyBeganVideoRecording(moment);
});
};
if (CFAbsoluteTimeGetCurrent() - _captureStartTime > 1.5)
startRecording();
else
TGDispatchAfter(1.5, [[PGCameraCaptureSession cameraQueue] _dispatch_queue], startRecording);
TGDispatchOnMainThread(^
{
if (self.beganVideoRecording != nil)
self.beganVideoRecording(moment);
});
}];
}
- (void)stopVideoRecording
{
[[PGCameraCaptureSession cameraQueue] dispatch:^
{
[self.captureSession stopVideoRecording];
TGDispatchOnMainThread(^
{
if (self.finishedVideoRecording != nil)
self.finishedVideoRecording(_moment);
});
}];
}
- (bool)isRecordingVideo
{
return self.captureSession.movieWriter.isRecording;
}
- (NSTimeInterval)videoRecordingDuration
{
return self.captureSession.movieWriter.currentDuration;
}
#pragma mark - Mode
- (PGCameraMode)cameraMode
{
return self.captureSession.currentMode;
}
- (void)setCameraMode:(PGCameraMode)cameraMode
{
if (self.disabled || self.captureSession.currentMode == cameraMode)
return;
__weak PGCamera *weakSelf = self;
void(^commitBlock)(void) = ^
{
__strong PGCamera *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[[PGCameraCaptureSession cameraQueue] dispatch:^
{
strongSelf.captureSession.currentMode = cameraMode;
_captureStartTime = CFAbsoluteTimeGetCurrent();
if (strongSelf.finishedModeChange != nil)
strongSelf.finishedModeChange();
if (strongSelf.autoStartVideoRecording && strongSelf.onAutoStartVideoRecording != nil)
{
TGDispatchAfter(0.5, dispatch_get_main_queue(), ^
{
strongSelf.onAutoStartVideoRecording();
});
}
strongSelf.autoStartVideoRecording = false;
}];
};
if (self.beganModeChange != nil)
self.beganModeChange(cameraMode, commitBlock);
}
#pragma mark - Focus and Exposure
- (void)subjectAreaChanged:(NSNotification *)__unused notification
{
[self resetFocusPoint];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)__unused object change:(NSDictionary *)__unused change context:(void *)__unused context
{
TGDispatchOnMainThread(^
{
if (!_subscribedForCameraChanges) {
return;
}
if ([keyPath isEqualToString:PGCameraAdjustingFocusKey])
{
bool adjustingFocus = [[change objectForKey:NSKeyValueChangeNewKey] isEqualToNumber:@YES];
if (adjustingFocus && self.beganAdjustingFocus != nil)
self.beganAdjustingFocus();
else if (!adjustingFocus && self.finishedAdjustingFocus != nil)
self.finishedAdjustingFocus();
}
else if ([keyPath isEqualToString:PGCameraFlashActiveKey] || [keyPath isEqualToString:PGCameraTorchActiveKey])
{
bool active = [[change objectForKey:NSKeyValueChangeNewKey] isEqualToNumber:@YES];
if (self.flashActivityChanged != nil)
self.flashActivityChanged(active);
}
else if ([keyPath isEqualToString:PGCameraFlashAvailableKey] || [keyPath isEqualToString:PGCameraTorchAvailableKey])
{
bool available = [[change objectForKey:NSKeyValueChangeNewKey] isEqualToNumber:@YES];
if (self.flashAvailabilityChanged != nil)
self.flashAvailabilityChanged(available);
}
});
}
- (bool)supportsExposurePOI
{
return [self.captureSession.videoDevice isExposurePointOfInterestSupported];
}
- (bool)supportsFocusPOI
{
return [self.captureSession.videoDevice isFocusPointOfInterestSupported];
}
- (void)resetFocusPoint
{
const CGPoint centerPoint = CGPointMake(0.5f, 0.5f);
[self _setFocusPoint:centerPoint focusMode:AVCaptureFocusModeContinuousAutoFocus exposureMode:AVCaptureExposureModeContinuousAutoExposure monitorSubjectAreaChange:false];
}
- (void)setFocusPoint:(CGPoint)point
{
[self _setFocusPoint:point focusMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose monitorSubjectAreaChange:true];
}
- (void)_setFocusPoint:(CGPoint)point focusMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode monitorSubjectAreaChange:(bool)monitorSubjectAreaChange
{
[[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (self.disabled)
return;
[self.captureSession setFocusPoint:point focusMode:focusMode exposureMode:exposureMode monitorSubjectAreaChange:monitorSubjectAreaChange];
}];
}
- (bool)supportsExposureTargetBias
{
return [self.captureSession.videoDevice respondsToSelector:@selector(setExposureTargetBias:completionHandler:)];
}
- (void)beginExposureTargetBiasChange
{
[[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (self.disabled)
return;
[self.captureSession setFocusPoint:self.captureSession.focusPoint focusMode:AVCaptureFocusModeLocked exposureMode:AVCaptureExposureModeLocked monitorSubjectAreaChange:false];
}];
}
- (void)setExposureTargetBias:(CGFloat)bias
{
[[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (self.disabled)
return;
[self.captureSession setExposureTargetBias:bias];
}];
}
- (void)endExposureTargetBiasChange
{
[[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (self.disabled)
return;
[self.captureSession setFocusPoint:self.captureSession.focusPoint focusMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose monitorSubjectAreaChange:true];
}];
}
#pragma mark - Flash
- (bool)hasFlash
{
return self.captureSession.videoDevice.hasFlash;
}
- (bool)flashActive
{
if (self.cameraMode == PGCameraModeVideo || self.cameraMode == PGCameraModeSquareVideo || self.cameraMode == PGCameraModeSquareSwing)
return self.captureSession.videoDevice.torchActive;
return self.captureSession.imageOutput.isFlashScene;
}
- (bool)flashAvailable
{
if (self.cameraMode == PGCameraModeVideo || self.cameraMode == PGCameraModeSquareVideo || self.cameraMode == PGCameraModeSquareSwing)
return self.captureSession.videoDevice.torchAvailable;
return self.captureSession.videoDevice.flashAvailable;
}
- (PGCameraFlashMode)flashMode
{
return self.captureSession.currentFlashMode;
}
- (void)setFlashMode:(PGCameraFlashMode)flashMode
{
[[PGCameraCaptureSession cameraQueue] dispatch:^
{
self.captureSession.currentFlashMode = flashMode;
}];
}
#pragma mark - Position
- (PGCameraPosition)togglePosition
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if ([AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo].count < 2 || self.disabled)
return self.captureSession.currentCameraPosition;
#pragma clang diagnostic pop
[self _unsubscribeFromCameraChanges];
PGCameraPosition targetCameraPosition = PGCameraPositionFront;
if (self.captureSession.currentCameraPosition == PGCameraPositionFront)
targetCameraPosition = PGCameraPositionRear;
AVCaptureDevice *targetDevice = [PGCameraCaptureSession _deviceWithCameraPosition:targetCameraPosition];
__weak PGCamera *weakSelf = self;
void(^commitBlock)(void) = ^
{
__strong PGCamera *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[[PGCameraCaptureSession cameraQueue] dispatch:^
{
[strongSelf.captureSession setCurrentCameraPosition:targetCameraPosition];
if (strongSelf.finishedPositionChange != nil)
strongSelf.finishedPositionChange([PGCameraCaptureSession _isZoomAvailableForDevice:targetDevice]);
[strongSelf _subscribeForCameraChanges];
}];
};
if (self.beganPositionChange != nil)
self.beganPositionChange(targetDevice.hasFlash, [PGCameraCaptureSession _isZoomAvailableForDevice:targetDevice], commitBlock);
return targetCameraPosition;
}
#pragma mark - Zoom
- (bool)hasUltrawideCamera {
return self.captureSession.hasUltrawideCamera;
}
- (bool)hasTelephotoCamera {
return self.captureSession.hasTelephotoCamera;
}
- (bool)isZoomAvailable
{
return self.captureSession.isZoomAvailable;
}
- (CGFloat)minZoomLevel {
return self.captureSession.minZoomLevel;
}
- (CGFloat)maxZoomLevel {
return self.captureSession.maxZoomLevel;
}
- (CGFloat)zoomLevel
{
return self.captureSession.zoomLevel;
}
- (void)setZoomLevel:(CGFloat)zoomLevel
{
[self setZoomLevel:zoomLevel animated:false];
}
- (void)setZoomLevel:(CGFloat)zoomLevel animated:(bool)animated
{
if (self.cameraMode == PGCameraModeVideo) {
animated = false;
}
[[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (self.disabled)
return;
self.captureSession.zoomLevels = self.zoomLevels;
[self.captureSession setZoomLevel:zoomLevel animated:animated];
}];
}
#pragma mark - Device Angle
- (void)startDeviceAngleMeasuring
{
[_deviceAngleSampler startMeasuring];
}
- (void)stopDeviceAngleMeasuring
{
[_deviceAngleSampler stopMeasuring];
}
#pragma mark - Availability
+ (bool)cameraAvailable
{
#if TARGET_IPHONE_SIMULATOR
return false;
#endif
return [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
}
+ (bool)hasRearCamera
{
return ([PGCameraCaptureSession _deviceWithCameraPosition:PGCameraPositionRear] != nil);
}
+ (bool)hasFrontCamera
{
return ([PGCameraCaptureSession _deviceWithCameraPosition:PGCameraPositionFront] != nil);
}
+ (AVCaptureVideoOrientation)_videoOrientationForInterfaceOrientation:(UIInterfaceOrientation)deviceOrientation mirrored:(bool)mirrored
{
switch (deviceOrientation)
{
case UIInterfaceOrientationPortraitUpsideDown:
return AVCaptureVideoOrientationPortraitUpsideDown;
case UIInterfaceOrientationLandscapeLeft:
return mirrored ? AVCaptureVideoOrientationLandscapeRight : AVCaptureVideoOrientationLandscapeLeft;
case UIInterfaceOrientationLandscapeRight:
return mirrored ? AVCaptureVideoOrientationLandscapeLeft : AVCaptureVideoOrientationLandscapeRight;
default:
return AVCaptureVideoOrientationPortrait;
}
}
+ (PGCameraAuthorizationStatus)cameraAuthorizationStatus
{
if ([AVCaptureDevice respondsToSelector:@selector(authorizationStatusForMediaType:)])
return [PGCamera _cameraAuthorizationStatusForAuthorizationStatus:[AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]];
return PGCameraAuthorizationStatusAuthorized;
}
+ (PGMicrophoneAuthorizationStatus)microphoneAuthorizationStatus
{
if ([AVCaptureDevice respondsToSelector:@selector(authorizationStatusForMediaType:)])
return [PGCamera _microphoneAuthorizationStatusForAuthorizationStatus:[AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]];
return PGMicrophoneAuthorizationStatusAuthorized;
}
+ (PGCameraAuthorizationStatus)_cameraAuthorizationStatusForAuthorizationStatus:(AVAuthorizationStatus)authorizationStatus
{
switch (authorizationStatus)
{
case AVAuthorizationStatusRestricted:
return PGCameraAuthorizationStatusRestricted;
case AVAuthorizationStatusDenied:
return PGCameraAuthorizationStatusDenied;
case AVAuthorizationStatusAuthorized:
return PGCameraAuthorizationStatusAuthorized;
default:
return PGCameraAuthorizationStatusNotDetermined;
}
}
+ (PGMicrophoneAuthorizationStatus)_microphoneAuthorizationStatusForAuthorizationStatus:(AVAuthorizationStatus)authorizationStatus
{
switch (authorizationStatus)
{
case AVAuthorizationStatusRestricted:
return PGMicrophoneAuthorizationStatusRestricted;
case AVAuthorizationStatusDenied:
return PGMicrophoneAuthorizationStatusDenied;
case AVAuthorizationStatusAuthorized:
return PGMicrophoneAuthorizationStatusAuthorized;
default:
return PGMicrophoneAuthorizationStatusNotDetermined;
}
}
+ (bool)isPhotoCameraMode:(PGCameraMode)mode
{
return mode == PGCameraModePhoto || mode == PGCameraModeSquarePhoto || mode == PGCameraModePhotoScan;
}
+ (bool)isVideoCameraMode:(PGCameraMode)mode
{
return mode == PGCameraModeVideo || mode == PGCameraModeSquareVideo;
}
@end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,154 @@
#import <LegacyComponents/PGCameraDeviceAngleSampler.h>
#import <CoreMotion/CoreMotion.h>
#import <LegacyComponents/TGPhotoEditorUtils.h>
#import "LegacyComponentsInternal.h"
@interface PGCameraDeviceAngleSampler ()
{
CMMotionManager *_motionManager;
NSOperationQueue *_motionQueue;
}
@end
@implementation PGCameraDeviceAngleSampler
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_deviceOrientation = UIDeviceOrientationUnknown;
_motionManager = [[CMMotionManager alloc] init];
_motionManager.accelerometerUpdateInterval = 1.0f;
_motionManager.deviceMotionUpdateInterval = 1.0f;
_motionManager.gyroUpdateInterval = 1.0f;
_motionManager.magnetometerUpdateInterval = 1.0f;
_motionQueue = [[NSOperationQueue alloc] init];
}
return self;
}
- (void)dealloc
{
[self stopMeasuring];
}
- (bool)isMeasuring
{
return [_motionManager isDeviceMotionActive];
}
- (void)stopMeasuring
{
[_motionManager stopDeviceMotionUpdates];
}
- (void)startMeasuring
{
if (![_motionManager isDeviceMotionAvailable])
return;
__weak PGCameraDeviceAngleSampler *weakSelf = self;
[_motionManager startAccelerometerUpdatesToQueue:_motionQueue withHandler:^(CMAccelerometerData *accelerometerData, __unused NSError *error)
{
__strong PGCameraDeviceAngleSampler *strongSelf = weakSelf;
if (strongSelf == nil || accelerometerData == nil || error != nil)
return;
CMAcceleration acceleration = accelerometerData.acceleration;
CGFloat xx = -acceleration.x;
CGFloat yy = acceleration.y;
CGFloat z = acceleration.z;
CGFloat angle = atan2(yy, xx);
UIDeviceOrientation deviceOrientation = strongSelf.deviceOrientation;
CGFloat absoluteZ = fabs(z);
if (deviceOrientation == UIDeviceOrientationFaceUp || deviceOrientation == UIDeviceOrientationFaceDown)
{
if (absoluteZ < 0.845f)
{
if (angle < -2.6f)
deviceOrientation = UIDeviceOrientationLandscapeRight;
else if (angle > -2.05f && angle < -1.1f)
deviceOrientation = UIDeviceOrientationPortrait;
else if (angle > -0.48f && angle < 0.48f)
deviceOrientation = UIDeviceOrientationLandscapeLeft;
else if (angle > 1.08f && angle < 2.08f)
deviceOrientation = UIDeviceOrientationPortraitUpsideDown;
}
else if (z < 0.f)
{
deviceOrientation = UIDeviceOrientationFaceUp;
}
else if (z > 0.f)
{
deviceOrientation = UIDeviceOrientationFaceDown;
}
}
else
{
if (z > 0.875f)
{
deviceOrientation = UIDeviceOrientationFaceDown;
}
else if (z < -0.875f)
{
deviceOrientation = UIDeviceOrientationFaceUp;
}
else
{
switch (deviceOrientation)
{
case UIDeviceOrientationLandscapeLeft:
if (angle < -1.07f) deviceOrientation = UIDeviceOrientationPortrait;
if (angle > 1.08f) deviceOrientation = UIDeviceOrientationPortraitUpsideDown;
break;
case UIDeviceOrientationLandscapeRight:
if (angle < 0.f && angle > -2.05f) deviceOrientation = UIDeviceOrientationPortrait;
if (angle > 0.f && angle < 2.05f) deviceOrientation = UIDeviceOrientationPortraitUpsideDown;
break;
case UIDeviceOrientationPortraitUpsideDown:
if (angle > 2.66f) deviceOrientation = UIDeviceOrientationLandscapeRight;
if (angle < 0.48f) deviceOrientation = UIDeviceOrientationLandscapeLeft;
break;
case UIDeviceOrientationPortrait:
default:
if (angle > -0.47f) deviceOrientation = UIDeviceOrientationLandscapeLeft;
if (angle < -2.64f) deviceOrientation = UIDeviceOrientationLandscapeRight;
break;
}
}
}
if (deviceOrientation != strongSelf.deviceOrientation)
{
strongSelf->_deviceOrientation = deviceOrientation;
TGDispatchOnMainThread(^
{
if (strongSelf.deviceOrientationChanged != nil)
strongSelf.deviceOrientationChanged(deviceOrientation);
});
}
}];
[_motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryZVertical toQueue:_motionQueue withHandler:^(CMDeviceMotion *motion, __unused NSError *error)
{
__strong PGCameraDeviceAngleSampler *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->_currentDeviceAngle = TGRadiansToDegrees((CGFloat)(atan2(motion.gravity.x, motion.gravity.y) - M_PI)) * -1;
}];
}
@end
@@ -0,0 +1,288 @@
#import <LegacyComponents/PGCameraMovieWriter.h>
#import "LegacyComponentsInternal.h"
#import <SSignalKit/SSignalKit.h>
#import <LegacyComponents/TGPhotoEditorUtils.h>
#import <AVFoundation/AVFoundation.h>
@interface PGCameraMovieWriter ()
{
AVAssetWriter *_assetWriter;
AVAssetWriterInput *_videoInput;
AVAssetWriterInput *_audioInput;
bool _startedWriting;
bool _finishedWriting;
CGAffineTransform _videoTransform;
NSDictionary *_videoOutputSettings;
NSDictionary *_audioOutputSettings;
CMTime _startTimeStamp;
CMTime _lastVideoTimeStamp;
CMTime _lastAudioTimeStamp;
NSMutableArray *_delayedAudioSamples;
NSTimeInterval _captureStartTime;
SQueue *_queue;
bool _stopIminent;
void (^_finishCompletion)(void);
}
@end
@implementation PGCameraMovieWriter
- (instancetype)initWithVideoTransform:(CGAffineTransform)videoTransform videoOutputSettings:(NSDictionary *)videoSettings audioOutputSettings:(NSDictionary *)audioSettings
{
self = [super init];
if (self != nil)
{
_videoTransform = videoTransform;
_videoOutputSettings = videoSettings;
_audioOutputSettings = audioSettings;
_queue = [[SQueue alloc] init];
_delayedAudioSamples = [[NSMutableArray alloc] init];
}
return self;
}
- (void)startRecording
{
[_queue dispatch:^
{
if (_isRecording || _finishedWriting)
return;
_captureStartTime = CFAbsoluteTimeGetCurrent();
NSError *error = nil;
NSString *path = [PGCameraMovieWriter tempOutputPath];
if ([[NSFileManager defaultManager] fileExistsAtPath:path])
[[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
_assetWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:path] fileType:[PGCameraMovieWriter outputFileType] error:&error];
if (_assetWriter == nil && error != nil)
{
TGLegacyLog(@"ERROR: camera movie writer failed to initialize: %@", error);
return;
}
_videoInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:_videoOutputSettings];
_videoInput.expectsMediaDataInRealTime = true;
_videoInput.transform = _videoTransform;
if ([_assetWriter canAddInput:_videoInput])
{
[_assetWriter addInput:_videoInput];
}
else
{
TGLegacyLog(@"ERROR: camera movie writer failed to add video input");
return;
}
if (_audioOutputSettings != nil)
{
_audioInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:_audioOutputSettings];
_audioInput.expectsMediaDataInRealTime = true;
if ([_assetWriter canAddInput:_audioInput])
{
[_assetWriter addInput:_audioInput];
}
else
{
TGLegacyLog(@"ERROR: camera movie writer failed to add audio input");
return;
}
}
[_assetWriter startWriting];
_isRecording = true;
}];
}
- (void)stopRecordingWithCompletion:(void (^)(void))completion
{
[_queue dispatch:^
{
if (fabs(CFAbsoluteTimeGetCurrent() - _captureStartTime) < 0.5)
return;
_stopIminent = true;
_finishCompletion = completion;
if (_assetWriter.status == AVAssetWriterStatusUnknown || _assetWriter.status > AVAssetWriterStatusCompleted)
{
TGDispatchOnMainThread(^
{
if (self.finishedWithMovieAtURL != nil)
self.finishedWithMovieAtURL(nil, CGAffineTransformIdentity, CGSizeZero, 0.0, false);
TGLegacyLog(@"ERROR: camera movie writer failed to write movie: %@", _assetWriter.error);
_assetWriter = nil;
});
return;
}
if (_audioOutputSettings == nil)
[self _finishWithCompletion];
}];
}
- (void)_finishWithCompletion
{
_isRecording = false;
__weak PGCameraMovieWriter *weakSelf = self;
[_assetWriter finishWritingWithCompletionHandler:^
{
__strong PGCameraMovieWriter *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->_finishedWriting = true;
TGDispatchOnMainThread(^
{
if (strongSelf->_assetWriter.status == AVAssetWriterStatusCompleted)
{
if (strongSelf.finishedWithMovieAtURL != nil)
{
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:strongSelf->_assetWriter.outputURL options:nil];
AVAssetTrack *track = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
CGSize dimensions = TGTransformDimensionsWithTransform(track.naturalSize, strongSelf->_videoTransform);
strongSelf.finishedWithMovieAtURL(strongSelf->_assetWriter.outputURL, strongSelf->_videoTransform, dimensions, strongSelf.currentDuration, true);
}
}
else
{
if (strongSelf.finishedWithMovieAtURL != nil)
strongSelf.finishedWithMovieAtURL(strongSelf->_assetWriter.outputURL, CGAffineTransformIdentity, CGSizeZero, 0.0, false);
TGLegacyLog(@"ERROR: camera movie writer failed to write movie: %@", strongSelf->_assetWriter.error);
}
strongSelf->_assetWriter = nil;
if (_finishCompletion != nil)
_finishCompletion();
});
}];
}
- (void)_processSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
CFRetain(sampleBuffer);
[_queue dispatch:^
{
CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
CMMediaType mediaType = CMFormatDescriptionGetMediaType(formatDescription);
CMTime timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
if (_assetWriter.status > AVAssetWriterStatusCompleted)
{
TGLegacyLog(@"WARNING: camera movie writer status is %d", _assetWriter.status);
if (_assetWriter.status == AVAssetWriterStatusFailed)
{
TGLegacyLog(@"ERROR: camera movie writer error: %@", _assetWriter.error);
_isRecording = false;
if (self.finishedWithMovieAtURL != nil)
self.finishedWithMovieAtURL(_assetWriter.outputURL, CGAffineTransformIdentity, CGSizeZero, 0.0, false);
}
return;
}
bool keepSample = false;
if (mediaType == kCMMediaType_Video)
{
if (!_startedWriting)
{
[_assetWriter startSessionAtSourceTime:timestamp];
_startTimeStamp = timestamp;
_startedWriting = true;
}
while (!_videoInput.readyForMoreMediaData)
{
NSDate *maxDate = [NSDate dateWithTimeIntervalSinceNow:0.1];
[[NSRunLoop currentRunLoop] runUntilDate:maxDate];
}
bool success = [_videoInput appendSampleBuffer:sampleBuffer];
if (success)
_lastVideoTimeStamp = timestamp;
else
TGLegacyLog(@"ERROR: camera movie writer failed to append pixel buffer");
if (_audioOutputSettings != nil && _stopIminent && CMTimeCompare(_lastVideoTimeStamp, _lastAudioTimeStamp) != -1) {
[self _finishWithCompletion];
}
}
else if (mediaType == kCMMediaType_Audio && !_stopIminent)
{
if (!_startedWriting)
{
[_delayedAudioSamples addObject:(__bridge id _Nonnull)(sampleBuffer)];
keepSample = true;
}
else
{
if (_delayedAudioSamples.count > 0)
{
for (id sample in _delayedAudioSamples)
{
CMSampleBufferRef buffer = (__bridge CMSampleBufferRef)(sample);
[_audioInput appendSampleBuffer:buffer];
CFRelease(buffer);
}
_delayedAudioSamples = nil;
}
while (!_audioInput.isReadyForMoreMediaData)
{
NSDate *maxDate = [NSDate dateWithTimeIntervalSinceNow:0.1];
[[NSRunLoop currentRunLoop] runUntilDate:maxDate];
}
bool success = [_audioInput appendSampleBuffer:sampleBuffer];
if (success)
_lastAudioTimeStamp = timestamp;
else
TGLegacyLog(@"ERROR: camera movie writer failed to append audio buffer");
}
}
if (!keepSample)
CFRelease(sampleBuffer);
}];
}
- (NSTimeInterval)currentDuration
{
return CMTimeGetSeconds(CMTimeSubtract(_lastVideoTimeStamp, _startTimeStamp));
}
+ (NSString *)outputFileType
{
return AVFileTypeMPEG4;
}
+ (NSString *)tempOutputPath
{
return [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"camvideo_%x.mp4", (int)arc4random()]];
}
@end
@@ -0,0 +1,33 @@
#import <LegacyComponents/PGCameraShotMetadata.h>
@implementation PGCameraShotMetadata
+ (CGFloat)relativeDeviceAngleFromAngle:(CGFloat)angle orientation:(UIInterfaceOrientation)orientation
{
switch (orientation)
{
case UIInterfaceOrientationPortraitUpsideDown:
angle -= 180.0f;
break;
case UIInterfaceOrientationLandscapeLeft:
angle -= 90.0f;
break;
case UIInterfaceOrientationLandscapeRight:
angle -= 270.0f;
break;
default:
if (angle > 180.0f)
angle = angle - 360.0f;
break;
}
if (ABS(angle) < 45.0f)
return angle;
return 0.0f;
}
@end
@@ -0,0 +1,234 @@
#import <LegacyComponents/PGCameraVolumeButtonHandler.h>
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/TGStringUtils.h>
#import <LegacyComponents/Freedom.h>
#import <AVKit/AVKit.h>
static NSString *encodeText(NSString *string, int key) {
NSMutableString *result = [[NSMutableString alloc] init];
for (int i = 0; i < (int)[string length]; i++) {
unichar c = [string characterAtIndex:i];
c += key;
[result appendString:[NSString stringWithCharacters:&c length:1]];
}
return result;
}
@interface PGCameraVolumeButtonHandler () {
id _dataSource;
id<UIInteraction> _eventInteraction;
}
@property (nonatomic, weak) UIView *eventView;
@property (nonatomic, copy) void(^upButtonPressedBlock)(void);
@property (nonatomic, copy) void(^upButtonReleasedBlock)(void);
@property (nonatomic, copy) void(^downButtonPressedBlock)(void);
@property (nonatomic, copy) void(^downButtonReleasedBlock)(void);
@end
@implementation PGCameraVolumeButtonHandler
- (instancetype)initWithIsCameraSpecific:(bool)isCameraSpecific eventView:(UIView *)eventView upButtonPressedBlock:(void (^)(void))upButtonPressedBlock upButtonReleasedBlock:(void (^)(void))upButtonReleasedBlock downButtonPressedBlock:(void (^)(void))downButtonPressedBlock downButtonReleasedBlock:(void (^)(void))downButtonReleasedBlock
{
self = [super init];
if (self != nil)
{
self.eventView = eventView;
self.upButtonPressedBlock = upButtonPressedBlock;
self.upButtonReleasedBlock = upButtonReleasedBlock;
self.downButtonPressedBlock = downButtonPressedBlock;
self.downButtonReleasedBlock = downButtonReleasedBlock;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:nil object:nil];
self.enabled = true;
if (@available(iOS 17.2, *)) {
if (isCameraSpecific) {
__weak PGCameraVolumeButtonHandler *weakSelf = self;
AVCaptureEventInteraction *interaction = [[AVCaptureEventInteraction alloc] initWithPrimaryEventHandler:^(AVCaptureEvent * _Nonnull event) {
__strong PGCameraVolumeButtonHandler *strongSelf = weakSelf;
switch (event.phase) {
case AVCaptureEventPhaseBegan:
strongSelf.downButtonPressedBlock();
break;
case AVCaptureEventPhaseEnded:
strongSelf.downButtonReleasedBlock();
break;
case AVCaptureEventPhaseCancelled:
strongSelf.downButtonReleasedBlock();
break;
default:
break;
}
} secondaryEventHandler:^(AVCaptureEvent * _Nonnull event) {
__strong PGCameraVolumeButtonHandler *strongSelf = weakSelf;
switch (event.phase) {
case AVCaptureEventPhaseBegan:
strongSelf.upButtonPressedBlock();
break;
case AVCaptureEventPhaseEnded:
strongSelf.upButtonReleasedBlock();
break;
case AVCaptureEventPhaseCancelled:
strongSelf.upButtonReleasedBlock();
break;
default:
break;
}
}];
interaction.enabled = true;
[eventView addInteraction:interaction];
_eventInteraction = interaction;
} else {
NSString *className = encodeText(@"NQWpmvnfDpouspmmfsTztufnEbubTpvsdf", -1);
Class c = NSClassFromString(className);
_dataSource = [[c alloc] init];
}
}
}
return self;
}
- (void)dealloc
{
if (_eventInteraction != nil) {
[self.eventView removeInteraction:_eventInteraction];
}
self.enabled = false;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark -
static void PGButtonHandlerEnableMonitoring(bool enable)
{
static void (*methodImpl)(id, SEL, BOOL) = NULL;
static dispatch_once_t onceToken;
static SEL methodSelector = NULL;
dispatch_once(&onceToken, ^
{
methodImpl = (void (*)(id, SEL, BOOL))freedomImplInstancesOfClass([UIApplication class], 0xf8de0049, NULL);
});
if (methodImpl != NULL) {
methodImpl([[LegacyComponentsGlobals provider] applicationInstance], methodSelector, enable);
}
}
#pragma mark -
- (void)handleNotification:(NSNotification *)notification
{
NSUInteger nameLength = notification.name.length;
if (nameLength == 46 || nameLength == 44 || nameLength == 42 || nameLength == 21)
{
uint32_t hash = legacy_murMurHash32(notification.name);
switch (hash)
{
case 0xaeae3258: //_UIApplicationVolumeDownButtonDownNotification
{
if (self.downButtonPressedBlock != nil)
self.downButtonPressedBlock();
}
break;
case 0x784c165e: //_UIApplicationVolumeDownButtonUpNotification
{
if (self.downButtonReleasedBlock != nil)
self.downButtonReleasedBlock();
}
break;
case 0xba416d8e: //_UIApplicationVolumeUpButtonDownNotification
{
if (self.upButtonPressedBlock != nil)
self.upButtonPressedBlock();
}
break;
case 0x4074ecfb: //_UIApplicationVolumeUpButtonUpNotification
{
if (self.upButtonReleasedBlock != nil)
self.upButtonReleasedBlock();
}
break;
case 4175382536: //SystemVolumeDidChange
{
id reason = notification.userInfo[@"Reason"];
if (reason && [@"ExplicitVolumeChange" isEqual:reason]) {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.upButtonPressedBlock != nil) {
self.upButtonPressedBlock();
}
});
}
break;
}
default:
break;
}
}
}
#pragma mark -
- (void)setEnabled:(bool)enabled
{
_enabled = enabled;
TGDispatchOnMainThread(^{
PGButtonHandlerEnableMonitoring(enabled);
});
}
- (void)enableIn:(NSTimeInterval)timeInterval
{
if (_enabled)
return;
TGDispatchAfter(timeInterval, dispatch_get_main_queue(), ^
{
[self setEnabled:true];
});
}
- (void)disableFor:(NSTimeInterval)timeInterval
{
if (!_enabled)
return;
TGDispatchAfter(timeInterval, dispatch_get_main_queue(), ^
{
_enabled = true;
});
}
- (void)ignoreEventsFor:(NSTimeInterval)timeInterval andDisable:(bool)disable
{
if (!self.enabled)
return;
_ignoring = true;
[self performSelector:@selector(_ignoreFinished:) withObject:@(disable) afterDelay:timeInterval];
}
- (void)_ignoreFinished:(NSNumber *)disable
{
_ignoring = false;
if (disable.boolValue)
self.enabled = false;
}
@end
@@ -0,0 +1,5 @@
#import "PGPhotoTool.h"
@interface PGContrastTool : PGPhotoTool
@end
@@ -0,0 +1,68 @@
#import "PGContrastTool.h"
#import "LegacyComponentsInternal.h"
@interface PGContrastTool ()
{
PGPhotoProcessPassParameter *_parameter;
}
@end
@implementation PGContrastTool
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_identifier = @"contrast";
_type = PGPhotoToolTypeShader;
_order = 6;
_minimumValue = -100;
_maximumValue = 100;
_defaultValue = 0;
self.value = @(_defaultValue);
}
return self;
}
- (NSString *)title
{
return TGLocalized(@"PhotoEditor.ContrastTool");
}
- (bool)shouldBeSkipped
{
return (ABS(((NSNumber *)self.displayValue).floatValue - (float)self.defaultValue) < FLT_EPSILON);
}
- (NSArray *)parameters
{
if (!_parameters)
{
_parameter = [PGPhotoProcessPassParameter parameterWithName:@"contrast" type:@"lowp float"];
_parameters = @[ _parameter ];
}
return _parameters;
}
- (void)updateParameters
{
NSNumber *value = (NSNumber *)self.displayValue;
CGFloat parameterValue = (value.floatValue / 100.0f) * 0.3f + 1;
[_parameter setFloatValue:parameterValue];
}
- (NSString *)shaderString
{
return PGShaderString
(
result = vec4(clamp(((result.rgb - vec3(0.5)) * contrast + vec3(0.5)), 0.0, 1.0), result.a);
);
}
@end
@@ -0,0 +1,41 @@
#import "PGPhotoTool.h"
typedef enum
{
PGCurvesTypeLuminance,
PGCurvesTypeRed,
PGCurvesTypeGreen,
PGCurvesTypeBlue
} PGCurvesType;
@interface PGCurvesValue : NSObject <NSCopying>
@property (nonatomic, assign) CGFloat blacksLevel;
@property (nonatomic, assign) CGFloat shadowsLevel;
@property (nonatomic, assign) CGFloat midtonesLevel;
@property (nonatomic, assign) CGFloat highlightsLevel;
@property (nonatomic, assign) CGFloat whitesLevel;
- (NSArray *)interpolateCurve;
+ (instancetype)defaultValue;
@end
@interface PGCurvesToolValue : NSObject <NSCopying, PGCustomToolValue>
@property (nonatomic, strong) PGCurvesValue *luminanceCurve;
@property (nonatomic, strong) PGCurvesValue *redCurve;
@property (nonatomic, strong) PGCurvesValue *greenCurve;
@property (nonatomic, strong) PGCurvesValue *blueCurve;
@property (nonatomic, assign) PGCurvesType activeType;
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
- (NSDictionary *)dictionary;
@end
@interface PGCurvesTool : PGPhotoTool
@end
@@ -0,0 +1,432 @@
#import "PGCurvesTool.h"
#import "LegacyComponentsInternal.h"
#import "TGPhotoEditorCurvesToolView.h"
#import "TGPhotoEditorCurvesHistogramView.h"
const NSUInteger PGCurveGranularity = 100;
const NSUInteger PGCurveDataStep = 2;
@interface PGCurvesValue ()
{
NSArray *_cachedDataPoints;
}
@end
@implementation PGCurvesValue
- (instancetype)copyWithZone:(NSZone *)__unused zone
{
PGCurvesValue *value = [[PGCurvesValue alloc] init];
value.blacksLevel = self.blacksLevel;
value.shadowsLevel = self.shadowsLevel;
value.midtonesLevel = self.midtonesLevel;
value.highlightsLevel = self.highlightsLevel;
value.whitesLevel = self.whitesLevel;
return value;
}
+ (instancetype)defaultValue
{
PGCurvesValue *value = [[PGCurvesValue alloc] init];
value.blacksLevel = 0;
value.shadowsLevel = 25;
value.midtonesLevel = 50;
value.highlightsLevel = 75;
value.whitesLevel = 100;
return value;
}
- (NSArray *)dataPoints
{
if (_cachedDataPoints == nil)
[self interpolateCurve];
return _cachedDataPoints;
}
- (bool)isDefault
{
if (fabs(self.blacksLevel - 0) < FLT_EPSILON
&& fabs(self.shadowsLevel - 25) < FLT_EPSILON
&& fabs(self.midtonesLevel - 50) < FLT_EPSILON
&& fabs(self.highlightsLevel - 75) < FLT_EPSILON
&& fabs(self.whitesLevel - 100) < FLT_EPSILON)
{
return true;
}
return false;
}
- (NSArray *)interpolateCurve
{
NSMutableArray *points = [[NSMutableArray alloc] init];
[points addObject:[NSValue valueWithCGPoint:CGPointMake(-0.001, self.blacksLevel / 100.0)]];
[points addObject:[NSValue valueWithCGPoint:CGPointMake(0.0, self.blacksLevel / 100.0)]];
[points addObject:[NSValue valueWithCGPoint:CGPointMake(0.25, self.shadowsLevel / 100.0)]];
[points addObject:[NSValue valueWithCGPoint:CGPointMake(0.5, self.midtonesLevel / 100.0)]];
[points addObject:[NSValue valueWithCGPoint:CGPointMake(0.75, self.highlightsLevel / 100.0)]];
[points addObject:[NSValue valueWithCGPoint:CGPointMake(1, self.whitesLevel / 100.0)]];
[points addObject:[NSValue valueWithCGPoint:CGPointMake(1.001, self.whitesLevel / 100.0)]];
NSMutableArray *dataPoints = [[NSMutableArray alloc] init];
NSMutableArray *interpolatedPoints = [[NSMutableArray alloc] init];
[interpolatedPoints addObject:points.firstObject];
for (NSUInteger index = 1; index < points.count - 2; index++)
{
CGPoint point0 = [points[index - 1] CGPointValue];
CGPoint point1 = [points[index] CGPointValue];
CGPoint point2 = [points[index + 1] CGPointValue];
CGPoint point3 = [points[index + 2] CGPointValue];
for (NSUInteger i = 1; i < PGCurveGranularity; i++)
{
CGFloat t = (CGFloat)i * (1.0f / (CGFloat)PGCurveGranularity);
CGFloat tt = t * t;
CGFloat ttt = tt * t;
CGPoint pi =
{
0.5 * (2 * point1.x + (point2.x - point0.x) * t + (2 * point0.x - 5 * point1.x + 4 * point2.x - point3.x) * tt + (3 * point1.x - point0.x - 3 * point2.x + point3.x) * ttt),
0.5 * (2 * point1.y + (point2.y - point0.y) * t + (2 * point0.y - 5 * point1.y + 4 * point2.y - point3.y) * tt + (3 * point1.y - point0.y - 3 * point2.y + point3.y) * ttt)
};
pi.y = MAX(0, MIN(1, pi.y));
if (pi.x > point0.x)
[interpolatedPoints addObject:[NSValue valueWithCGPoint:pi]];
if ((i - 1) % PGCurveDataStep == 0)
[dataPoints addObject:@(pi.y)];
}
[interpolatedPoints addObject:[NSValue valueWithCGPoint:point2]];
}
[interpolatedPoints addObject:points.lastObject];
_cachedDataPoints = dataPoints;
return interpolatedPoints;
}
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
if (dictionary.count == 0) {
return nil;
}
PGCurvesValue *value = [[PGCurvesValue alloc] init];
if (dictionary[@"blacks"]) {
value.blacksLevel = [dictionary[@"blacks"] floatValue];
}
if (dictionary[@"shadows"]) {
value.shadowsLevel = [dictionary[@"shadows"] floatValue];
}
if (dictionary[@"midtones"]) {
value.midtonesLevel = [dictionary[@"midtones"] floatValue];
}
if (dictionary[@"highlights"]) {
value.highlightsLevel = [dictionary[@"highlights"] floatValue];
}
if (dictionary[@"whites"]) {
value.whitesLevel = [dictionary[@"whites"] floatValue];
}
return value;
}
- (NSDictionary *)dictionary {
return @{
@"blacks": @(self.blacksLevel),
@"shadows": @(self.shadowsLevel),
@"midtones": @(self.midtonesLevel),
@"highlights": @(self.highlightsLevel),
@"whites": @(self.whitesLevel)
};
}
@end
@implementation PGCurvesToolValue
- (instancetype)copyWithZone:(NSZone *)__unused zone
{
PGCurvesToolValue *value = [[PGCurvesToolValue alloc] init];
value.luminanceCurve = [self.luminanceCurve copy];
value.redCurve = [self.redCurve copy];
value.greenCurve = [self.greenCurve copy];
value.blueCurve = [self.blueCurve copy];
value.activeType = self.activeType;
return value;
}
+ (instancetype)defaultValue
{
PGCurvesToolValue *value = [[PGCurvesToolValue alloc] init];
value.luminanceCurve = [PGCurvesValue defaultValue];
value.redCurve = [PGCurvesValue defaultValue];
value.greenCurve = [PGCurvesValue defaultValue];
value.blueCurve = [PGCurvesValue defaultValue];
value.activeType = PGCurvesTypeLuminance;
return value;
}
- (id<PGCustomToolValue>)cleanValue
{
PGCurvesToolValue *value = [[PGCurvesToolValue alloc] init];
value.luminanceCurve = [self.luminanceCurve copy];
value.redCurve = [self.redCurve copy];
value.greenCurve = [self.greenCurve copy];
value.blueCurve = [self.blueCurve copy];
value.activeType = PGCurvesTypeLuminance;
return value;
}
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
if (dictionary.count == 0) {
return nil;
}
PGCurvesToolValue *value = [[PGCurvesToolValue alloc] init];
if (dictionary[@"luminance"]) {
value.luminanceCurve = [[PGCurvesValue alloc] initWithDictionary:dictionary[@"luminance"]];
}
if (dictionary[@"red"]) {
value.redCurve = [[PGCurvesValue alloc] initWithDictionary:dictionary[@"red"]];
}
if (dictionary[@"green"]) {
value.greenCurve = [[PGCurvesValue alloc] initWithDictionary:dictionary[@"green"]];
}
if (dictionary[@"blue"]) {
value.blueCurve = [[PGCurvesValue alloc] initWithDictionary:dictionary[@"blue"]];
}
return value;
}
- (NSDictionary *)dictionary {
return @{
@"luminance": self.luminanceCurve.dictionary,
@"red": self.redCurve.dictionary,
@"green": self.greenCurve.dictionary,
@"blue": self.blueCurve.dictionary
};
}
@end
@interface PGCurvesTool ()
{
PGPhotoProcessPassParameter *_rgbCurveParameter;
PGPhotoProcessPassParameter *_redCurveParameter;
PGPhotoProcessPassParameter *_greenCurveParameter;
PGPhotoProcessPassParameter *_blueCurveParameter;
PGPhotoProcessPassParameter *_skipToneParameter;
}
@end
@implementation PGCurvesTool
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_identifier = @"curves";
_type = PGPhotoToolTypeShader;
_order = 1;
_minimumValue = 0;
_maximumValue = 100;
_defaultValue = 0;
self.value = [PGCurvesToolValue defaultValue];
}
return self;
}
- (NSString *)title
{
return TGLocalized(@"PhotoEditor.CurvesTool");
}
- (UIView <TGPhotoEditorToolView> *)itemAreaViewWithChangeBlock:(void (^)(id))changeBlock explicit:(bool)explicit
{
__weak PGCurvesTool *weakSelf = self;
UIView <TGPhotoEditorToolView> *view = [[TGPhotoEditorCurvesToolView alloc] initWithEditorItem:self];
view.valueChanged = ^(id newValue, __unused bool animated)
{
__strong PGPhotoTool *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (newValue != nil)
{
if (!explicit && [strongSelf.tempValue isEqual:newValue])
return;
if (explicit && [strongSelf.value isEqual:newValue])
return;
if (!explicit)
strongSelf.tempValue = newValue;
else
strongSelf.value = newValue;
}
if (changeBlock != nil)
changeBlock(newValue);
};
return view;
}
- (UIView <TGPhotoEditorToolView> *)itemControlViewWithChangeBlock:(void (^)(id, bool))__unused changeBlock explicit:(bool)explicit nameWidth:(CGFloat)__unused nameWidth
{
__weak PGCurvesTool *weakSelf = self;
UIView <TGPhotoEditorToolView> *view = [[TGPhotoEditorCurvesHistogramView alloc] initWithEditorItem:self];
view.valueChanged = ^(id newValue, __unused bool animated)
{
__strong PGPhotoTool *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (newValue != nil)
{
if (!explicit && [strongSelf.tempValue isEqual:newValue])
return;
if (explicit && [strongSelf.value isEqual:newValue])
return;
if (!explicit)
strongSelf.tempValue = newValue;
else
strongSelf.value = newValue;
}
if (changeBlock != nil)
changeBlock(newValue, false);
};
return view;
}
- (Class)valueClass
{
return [PGCurvesToolValue class];
}
- (bool)shouldBeSkipped
{
PGCurvesToolValue *value = (PGCurvesToolValue *)self.displayValue;
return [value.luminanceCurve isDefault] && [value.redCurve isDefault] && [value.greenCurve isDefault] && [value.blueCurve isDefault];
}
- (NSArray *)parameters
{
if (!_parameters)
{
NSInteger count = PGCurveGranularity * PGCurveDataStep;
_rgbCurveParameter = [PGPhotoProcessPassParameter parameterWithName:@"rgbCurveValues" type:@"lowp float" count:count];
_redCurveParameter = [PGPhotoProcessPassParameter parameterWithName:@"redCurveValues" type:@"lowp float" count:count];
_greenCurveParameter = [PGPhotoProcessPassParameter parameterWithName:@"greenCurveValues" type:@"lowp float" count:count];
_blueCurveParameter = [PGPhotoProcessPassParameter parameterWithName:@"blueCurveValues" type:@"lowp float" count:count];
_skipToneParameter = [PGPhotoProcessPassParameter parameterWithName:@"skipTone" type:@"lowp float"];
_parameters = @[ _rgbCurveParameter, _redCurveParameter, _greenCurveParameter, _blueCurveParameter, _skipToneParameter ];
}
return _parameters;
}
- (id)displayValue {
if (self.disabled) {
return [PGCurvesToolValue defaultValue];
} else {
return [super displayValue];
}
}
- (void)updateParameters
{
PGCurvesToolValue *value = (PGCurvesToolValue *)self.displayValue;
[_rgbCurveParameter setFloatArray:[value.luminanceCurve dataPoints]];
[_redCurveParameter setFloatArray:[value.redCurve dataPoints]];
[_greenCurveParameter setFloatArray:[value.greenCurve dataPoints]];
[_blueCurveParameter setFloatArray:[value.blueCurve dataPoints]];
[_skipToneParameter setFloatValue:self.shouldBeSkipped ? 1.0 : 0.0];
}
- (NSString *)ancillaryShaderString
{
return PGShaderString
(
lowp vec3 applyLuminanceCurve(lowp vec3 pixel) {
int index = int(clamp(pixel.z / (1.0 / 200.0), 0.0, 199.0));
highp float value = rgbCurveValues[index];
highp float grayscale = (smoothstep(0.0, 0.1, pixel.z) * (1.0 - smoothstep(0.8, 1.0, pixel.z)));
highp float saturation = mix(0.0, pixel.y, grayscale);
pixel.y = saturation;
pixel.z = value;
return pixel;
}
lowp vec3 applyRGBCurve(lowp vec3 pixel) {
int index = int(clamp(pixel.r / (1.0 / 200.0), 0.0, 199.0));
highp float value = redCurveValues[index];
pixel.r = value;
index = int(clamp(pixel.g / (1.0 / 200.0), 0.0, 199.0));
value = greenCurveValues[index];
pixel.g = clamp(value, 0.0, 1.0);
index = int(clamp(pixel.b / (1.0 / 200.0), 0.0, 199.0));
value = blueCurveValues[index];
pixel.b = clamp(value, 0.0, 1.0);
return pixel;
}
);
}
- (NSString *)stringValue
{
if (![self shouldBeSkipped])
{
return @"";
}
return nil;
}
- (NSString *)shaderString
{
return PGShaderString
(
if (skipTone < toolEpsilon) {
result = vec4(applyRGBCurve(hslToRgb(applyLuminanceCurve(rgbToHsl(result.rgb)))), result.a);
}
);
}
- (bool)isSimple
{
return false;
}
@end
@@ -0,0 +1,5 @@
#import "PGPhotoTool.h"
@interface PGEnhanceTool : PGPhotoTool
@end
@@ -0,0 +1,52 @@
#import "PGEnhanceTool.h"
#import "LegacyComponentsInternal.h"
#import "PGPhotoEnhancePass.h"
@implementation PGEnhanceTool
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_identifier = @"enhance";
_type = PGPhotoToolTypePass;
_order = 0;
_pass = [[PGPhotoEnhancePass alloc] init];
_minimumValue = 0;
_maximumValue = 100;
_defaultValue = 0;
self.value = @(_defaultValue);
}
return self;
}
- (NSString *)title
{
return TGLocalized(@"PhotoEditor.EnhanceTool");
}
- (PGPhotoProcessPass *)pass
{
[self updatePassParameters];
return _pass;
}
- (bool)shouldBeSkipped
{
return (ABS(((NSNumber *)self.displayValue).floatValue - self.defaultValue) < FLT_EPSILON);
}
- (void)updatePassParameters
{
NSNumber *value = (NSNumber *)self.displayValue;
[(PGPhotoEnhancePass *)_pass setIntensity:value.floatValue / 100];
}
@end
@@ -0,0 +1,5 @@
#import "PGPhotoTool.h"
@interface PGExposureTool : PGPhotoTool
@end
@@ -0,0 +1,79 @@
#import "PGExposureTool.h"
#import "LegacyComponentsInternal.h"
@interface PGExposureTool ()
{
PGPhotoProcessPassParameter *_parameter;
}
@end
@implementation PGExposureTool
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_identifier = @"exposure";
_type = PGPhotoToolTypeShader;
_order = 10;
_minimumValue = -100;
_maximumValue = 100;
_defaultValue = 0;
self.value = @(_defaultValue);
}
return self;
}
- (NSString *)title
{
return TGLocalized(@"PhotoEditor.ExposureTool");
}
- (bool)shouldBeSkipped
{
return (ABS(((NSNumber *)self.displayValue).floatValue - (float)self.defaultValue) < FLT_EPSILON);
}
- (NSArray *)parameters
{
if (!_parameters)
{
_parameter = [PGPhotoProcessPassParameter parameterWithName:@"exposure" type:@"lowp float"];
_parameters = @[ _parameter ];
}
return _parameters;
}
- (void)updateParameters
{
NSNumber *value = (NSNumber *)self.displayValue;
CGFloat parameterValue = (value.floatValue / 100.0f);
[_parameter setFloatValue:parameterValue];
}
- (NSString *)shaderString
{
return PGShaderString
(
if (abs(exposure) > toolEpsilon) {
mediump float mag = exposure * 1.045;
mediump float exppower = 1.0 + abs(mag);
if (mag < 0.0) {
exppower = 1.0 / exppower;
}
result.r = 1.0 - pow((1.0 - result.r), exppower);
result.g = 1.0 - pow((1.0 - result.g), exppower);
result.b = 1.0 - pow((1.0 - result.b), exppower);
}
);
}
@end
@@ -0,0 +1,5 @@
#import "PGPhotoTool.h"
@interface PGFadeTool : PGPhotoTool
@end
@@ -0,0 +1,96 @@
#import "PGFadeTool.h"
#import "LegacyComponentsInternal.h"
@interface PGFadeTool ()
{
PGPhotoProcessPassParameter *_parameter;
}
@end
@implementation PGFadeTool
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_identifier = @"fade";
_type = PGPhotoToolTypeShader;
_order = 7;
_minimumValue = 0;
_maximumValue = 100;
_defaultValue = 0;
self.value = @(_defaultValue);
}
return self;
}
- (NSString *)title
{
return TGLocalized(@"PhotoEditor.FadeTool");
}
- (bool)shouldBeSkipped
{
return (ABS(((NSNumber *)self.displayValue).floatValue - self.defaultValue) < FLT_EPSILON);
}
- (NSArray *)parameters
{
if (!_parameters)
{
_parameter = [PGPhotoProcessPassParameter parameterWithName:@"fadeAmount" type:@"lowp float"];
_parameters = @[ _parameter ];
}
return _parameters;
}
- (void)updateParameters
{
NSNumber *value = (NSNumber *)self.displayValue;
CGFloat parameterValue = value.floatValue / 100.0f;
[_parameter setFloatValue:parameterValue];
}
- (NSString *)ancillaryShaderString
{
return PGShaderString
(
highp vec3 fadeAdjust(highp vec3 color, highp float fadeVal) {
highp vec3 co1 = vec3(-0.9772);
highp vec3 co2 = vec3(1.708);
highp vec3 co3 = vec3(-0.1603);
highp vec3 co4 = vec3(0.2878);
highp vec3 comp1 = co1 * pow(vec3(color), vec3(3.0));
highp vec3 comp2 = co2 * pow(vec3(color), vec3(2.0));
highp vec3 comp3 = co3 * vec3(color);
highp vec3 comp4 = co4;
highp vec3 finalComponent = comp1 + comp2 + comp3 + comp4;
highp vec3 difference = finalComponent - color;
highp vec3 scalingValue = vec3(0.9);
highp vec3 faded = color + (difference * scalingValue);
return (color * (1.0 - fadeVal)) + (faded * fadeVal);
}
);
}
- (NSString *)shaderString
{
return PGShaderString
(
if (abs(fadeAmount) > toolEpsilon) {
result.rgb = fadeAdjust(result.rgb, fadeAmount);
}
);
}
@end
@@ -0,0 +1,5 @@
#import "PGPhotoTool.h"
@interface PGGrainTool : PGPhotoTool
@end
@@ -0,0 +1,163 @@
#import "PGGrainTool.h"
#import "LegacyComponentsInternal.h"
@interface PGGrainTool ()
{
PGPhotoProcessPassParameter *_parameter;
}
@end
@implementation PGGrainTool
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_identifier = @"grain";
_type = PGPhotoToolTypeShader;
_order = 12;
_minimumValue = 0;
_maximumValue = 100;
_defaultValue = 0;
self.value = @(_defaultValue);
}
return self;
}
- (NSString *)title
{
return TGLocalized(@"PhotoEditor.GrainTool");
}
- (bool)shouldBeSkipped
{
return (ABS(((NSNumber *)self.displayValue).floatValue - (float)self.defaultValue) < FLT_EPSILON);
}
- (NSArray *)parameters
{
if (!_parameters)
{
_parameter = [PGPhotoProcessPassParameter parameterWithName:@"grain" type:@"lowp float"];
_parameters = @[ _parameter,
[PGPhotoProcessPassParameter constWithName:@"permTexUnit" type:@"lowp float" value:@"1.0 / 256.0"],
[PGPhotoProcessPassParameter constWithName:@"permTexUnitHalf" type:@"lowp float" value:@"0.5 / 256.0"],
[PGPhotoProcessPassParameter constWithName:@"grainsize" type:@"lowp float" value:@"2.3"] ];
}
return _parameters;
}
- (void)updateParameters
{
NSNumber *value = (NSNumber *)self.displayValue;
CGFloat parameterValue = value.floatValue / 100.0f * 0.04f;
[_parameter setFloatValue:parameterValue];
}
- (NSString *)ancillaryShaderString
{
return PGShaderString
(
highp vec4 rnm(in highp vec2 tc) {
highp float noise = sin(dot(tc,vec2(12.9898,78.233))) * 43758.5453;
highp float noiseR = fract(noise)*2.0-1.0;
highp float noiseG = fract(noise*1.2154)*2.0-1.0;
highp float noiseB = fract(noise*1.3453)*2.0-1.0;
highp float noiseA = fract(noise*1.3647)*2.0-1.0;
return vec4(noiseR,noiseG,noiseB,noiseA);
}
highp float fade(in highp float t) {
return t*t*t*(t*(t*6.0-15.0)+10.0);
}
highp float pnoise3D(in highp vec3 p)
{
highp vec3 pi = permTexUnit*floor(p)+permTexUnitHalf;
highp vec3 pf = fract(p);
// Noise contributions from (x=0, y=0), z=0 and z=1
highp float perm00 = rnm(pi.xy).a ;
highp vec3 grad000 = rnm(vec2(perm00, pi.z)).rgb * 4.0 - 1.0;
highp float n000 = dot(grad000, pf);
highp vec3 grad001 = rnm(vec2(perm00, pi.z + permTexUnit)).rgb * 4.0 - 1.0;
highp float n001 = dot(grad001, pf - vec3(0.0, 0.0, 1.0));
// Noise contributions from (x=0, y=1), z=0 and z=1
highp float perm01 = rnm(pi.xy + vec2(0.0, permTexUnit)).a ;
highp vec3 grad010 = rnm(vec2(perm01, pi.z)).rgb * 4.0 - 1.0;
highp float n010 = dot(grad010, pf - vec3(0.0, 1.0, 0.0));
highp vec3 grad011 = rnm(vec2(perm01, pi.z + permTexUnit)).rgb * 4.0 - 1.0;
highp float n011 = dot(grad011, pf - vec3(0.0, 1.0, 1.0));
// Noise contributions from (x=1, y=0), z=0 and z=1
highp float perm10 = rnm(pi.xy + vec2(permTexUnit, 0.0)).a ;
highp vec3 grad100 = rnm(vec2(perm10, pi.z)).rgb * 4.0 - 1.0;
highp float n100 = dot(grad100, pf - vec3(1.0, 0.0, 0.0));
highp vec3 grad101 = rnm(vec2(perm10, pi.z + permTexUnit)).rgb * 4.0 - 1.0;
highp float n101 = dot(grad101, pf - vec3(1.0, 0.0, 1.0));
// Noise contributions from (x=1, y=1), z=0 and z=1
highp float perm11 = rnm(pi.xy + vec2(permTexUnit, permTexUnit)).a ;
highp vec3 grad110 = rnm(vec2(perm11, pi.z)).rgb * 4.0 - 1.0;
highp float n110 = dot(grad110, pf - vec3(1.0, 1.0, 0.0));
highp vec3 grad111 = rnm(vec2(perm11, pi.z + permTexUnit)).rgb * 4.0 - 1.0;
highp float n111 = dot(grad111, pf - vec3(1.0, 1.0, 1.0));
// Blend contributions along x
highp vec4 n_x = mix(vec4(n000, n001, n010, n011), vec4(n100, n101, n110, n111), fade(pf.x));
// Blend contributions along y
highp vec2 n_xy = mix(n_x.xy, n_x.zw, fade(pf.y));
// Blend contributions along z
highp float n_xyz = mix(n_xy.x, n_xy.y, fade(pf.z));
return n_xyz;
}
lowp vec2 coordRot(in lowp vec2 tc, in lowp float angle)
{
lowp float rotX = ((tc.x * 2.0 - 1.0) * cos(angle)) - ((tc.y * 2.0 - 1.0) * sin(angle));
lowp float rotY = ((tc.y * 2.0 - 1.0) * cos(angle)) + ((tc.x * 2.0 - 1.0) * sin(angle));
rotX = rotX * 0.5 + 0.5;
rotY = rotY * 0.5 + 0.5;
return vec2(rotX,rotY);
}
);
}
- (NSString *)shaderString
{
return PGShaderString
(
if (abs(grain) > toolEpsilon) {
highp vec3 rotOffset = vec3(1.425, 3.892, 5.835);
highp vec2 rotCoordsR = coordRot(texCoord, rotOffset.x);
highp vec3 noise = vec3(pnoise3D(vec3(rotCoordsR * vec2(width / grainsize, height / grainsize),0.0)));
lowp vec3 lumcoeff = vec3(0.299,0.587,0.114);
lowp float luminance = dot(result.rgb, lumcoeff);
lowp float lum = smoothstep(0.2, 0.0, luminance);
lum += luminance;
noise = mix(noise,vec3(0.0),pow(lum,4.0));
result.rgb = result.rgb + noise * grain;
}
);
}
- (bool)isAvialableForVideo
{
return false;
}
@end
@@ -0,0 +1,5 @@
#import "PGPhotoTool.h"
@interface PGHighlightsTool : PGPhotoTool
@end
@@ -0,0 +1,85 @@
#import "PGHighlightsTool.h"
#import "LegacyComponentsInternal.h"
@interface PGHighlightsTool ()
{
PGPhotoProcessPassParameter *_parameter;
}
@end
@implementation PGHighlightsTool
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_identifier = @"highlights";
_type = PGPhotoToolTypeShader;
_order = 5;
_minimumValue = -100;
_maximumValue = 100;
_defaultValue = 0;
self.value = @(_defaultValue);
}
return self;
}
- (NSString *)title
{
return TGLocalized(@"PhotoEditor.HighlightsTool");
}
- (bool)shouldBeSkipped
{
return (ABS(((NSNumber *)self.displayValue).floatValue - self.defaultValue) < FLT_EPSILON);
}
- (NSArray *)parameters
{
if (!_parameters)
{
_parameter = [PGPhotoProcessPassParameter parameterWithName:@"highlights" type:@"lowp float"];
_parameters = @[ [PGPhotoProcessPassParameter constWithName:@"hsLuminanceWeighting" type:@"mediump vec3" value:@"vec3(0.3, 0.3, 0.3)"],
_parameter ];
}
return _parameters;
}
- (void)updateParameters
{
NSNumber *value = (NSNumber *)self.displayValue;
CGFloat parameterValue = (value.floatValue * 0.75f + 100.0f) / 100.0f;
[_parameter setFloatValue:parameterValue];
}
- (NSString *)shaderString
{
return PGShaderString
(
mediump float hsLuminance = dot(result.rgb, hsLuminanceWeighting);
mediump float shadow = clamp((pow(hsLuminance, 1.0 / shadows) + (-0.76) * pow(hsLuminance, 2.0 / shadows)) - hsLuminance, 0.0, 1.0);
mediump float highlight = clamp((1.0 - (pow(1.0 - hsLuminance, 1.0 / (2.0 - highlights)) + (-0.8) * pow(1.0 - hsLuminance, 2.0 / (2.0 - highlights)))) - hsLuminance, -1.0, 0.0);
lowp vec3 hsresult = vec3(0.0, 0.0, 0.0) + ((hsLuminance + shadow + highlight) - 0.0) * ((result.rgb - vec3(0.0, 0.0, 0.0)) / (hsLuminance - 0.0));
mediump float contrastedLuminance = ((hsLuminance - 0.5) * 1.5) + 0.5;
mediump float whiteInterp = contrastedLuminance * contrastedLuminance * contrastedLuminance;
mediump float whiteTarget = clamp(highlights, 1.0, 2.0) - 1.0;
hsresult = mix(hsresult, vec3(1.0), whiteInterp * whiteTarget);
mediump float invContrastedLuminance = 1.0 - contrastedLuminance;
mediump float blackInterp = invContrastedLuminance * invContrastedLuminance * invContrastedLuminance;
mediump float blackTarget = 1.0 - clamp(shadows, 0.0, 1.0);
hsresult = mix(hsresult, vec3(0.0), blackInterp * blackTarget);
result = vec4(hsresult.rgb, result.a);
);
}
@end
@@ -0,0 +1,19 @@
#import "PGPhotoProcessPass.h"
typedef enum
{
PGBlurToolTypeNone,
PGBlurToolTypeRadial,
PGBlurToolTypeLinear,
PGBlurToolTypePortrait
} PGBlurToolType;
@interface PGPhotoBlurPass : PGPhotoProcessPass
@property (nonatomic, assign) PGBlurToolType type;
@property (nonatomic, assign) CGFloat size;
@property (nonatomic, assign) CGFloat falloff;
@property (nonatomic, assign) CGPoint point;
@property (nonatomic, assign) CGFloat angle;
@end
@@ -0,0 +1,305 @@
#import "PGPhotoBlurPass.h"
#import "GPUImageTwoInputFilter.h"
#import "GPUImageThreeInputFilter.h"
#import "PGPhotoGaussianBlurFilter.h"
NSString *const PGPhotoRadialBlurShaderString = PGShaderString
(
varying highp vec2 texCoord;
varying highp vec2 texCoord2;
uniform sampler2D sourceImage;
uniform sampler2D inputImageTexture2;
uniform lowp float excludeSize;
uniform lowp vec2 excludePoint;
uniform lowp float excludeFalloff;
uniform highp float aspectRatio;
void main()
{
lowp vec4 sharpImageColor = texture2D(sourceImage, texCoord);
lowp vec4 blurredImageColor = texture2D(inputImageTexture2, texCoord2);
highp vec2 texCoordToUse = vec2(texCoord2.x, (texCoord2.y * aspectRatio + 0.5 - 0.5 * aspectRatio));
highp float distanceFromCenter = distance(excludePoint, texCoordToUse);
gl_FragColor = mix(blurredImageColor, sharpImageColor, smoothstep(1.0, excludeFalloff, clamp(distanceFromCenter / excludeSize, 0.0, 1.0)));
}
);
NSString *const PGPhotoLinearBlurShaderString = PGShaderString
(
varying highp vec2 texCoord;
varying highp vec2 texCoord2;
uniform sampler2D sourceImage;
uniform sampler2D inputImageTexture2;
uniform lowp float excludeSize;
uniform lowp vec2 excludePoint;
uniform lowp float excludeFalloff;
uniform highp float angle;
uniform highp float aspectRatio;
void main()
{
lowp vec4 sharpImageColor = texture2D(sourceImage, texCoord);
lowp vec4 blurredImageColor = texture2D(inputImageTexture2, texCoord2);
highp vec2 texCoordToUse = vec2(texCoord2.x, (texCoord2.y * aspectRatio + 0.5 - 0.5 * aspectRatio));
highp float distanceFromCenter = abs((texCoordToUse.x - excludePoint.x) * cos(angle) + (texCoordToUse.y - excludePoint.y) * sin(angle));
gl_FragColor = mix(blurredImageColor, sharpImageColor, smoothstep(1.0, excludeFalloff, clamp(distanceFromCenter / excludeSize, 0.0, 1.0)));
}
);
NSString *const PGPhotoMaskedBlurShaderString = PGShaderString
(
varying highp vec2 texCoord;
varying highp vec2 texCoord2;
varying highp vec2 texCoord3;
uniform sampler2D sourceImage;
uniform sampler2D inputImageTexture2;
uniform sampler2D inputImageTexture3;
void main()
{
lowp vec4 sharpImageColor = texture2D(sourceImage, texCoord);
lowp vec4 blurredImageColor = texture2D(inputImageTexture2, texCoord2);
lowp vec4 maskImageColor = texture2D(inputImageTexture3, texCoord3);
gl_FragColor = mix(blurredImageColor, sharpImageColor, maskImageColor.r);
}
);
@interface PGPhotoBlurFilter : GPUImageOutput <GPUImageInput>
{
PGPhotoGaussianBlurFilter *_blurFilter;
GPUImageTwoInputFilter *_radialFocusFilter;
GPUImageTwoInputFilter *_linearFocusFilter;
GPUImageThreeInputFilter *_maskedFilter;
GPUImageOutput <GPUImageInput> *_currentFocusFilter;
bool _endProcessing;
}
@end
@implementation PGPhotoBlurFilter
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_blurFilter = [[PGPhotoGaussianBlurFilter alloc] init];
_radialFocusFilter = [[GPUImageTwoInputFilter alloc] initWithFragmentShaderFromString:PGPhotoRadialBlurShaderString];
_linearFocusFilter = [[GPUImageTwoInputFilter alloc] initWithFragmentShaderFromString:PGPhotoLinearBlurShaderString];
}
return self;
}
- (void)setType:(PGBlurToolType)type
{
id<GPUImageInput> target = nil;
if (_currentFocusFilter.targets.count > 0)
target = _currentFocusFilter.targets[0];
[_currentFocusFilter removeAllTargets];
switch (type)
{
case PGBlurToolTypeRadial:
{
_currentFocusFilter = _radialFocusFilter;
}
break;
case PGBlurToolTypeLinear:
{
_currentFocusFilter = _linearFocusFilter;
}
break;
case PGBlurToolTypePortrait:
{
_currentFocusFilter = _maskedFilter;
}
default:
break;
}
if (target != nil)
[_currentFocusFilter addTarget:target atTextureLocation:0];
[_blurFilter removeAllTargets];
[_blurFilter addTarget:_currentFocusFilter atTextureLocation:1];
}
- (void)setExcludeSize:(CGFloat)excludeSize
{
[_radialFocusFilter setFloat:(float)excludeSize forUniformName:@"excludeSize"];
[_linearFocusFilter setFloat:(float)excludeSize forUniformName:@"excludeSize"];
}
- (void)setExcludeFalloff:(CGFloat)excludeFalloff
{
[_radialFocusFilter setFloat:(float)excludeFalloff forUniformName:@"excludeFalloff"];
[_linearFocusFilter setFloat:(float)excludeFalloff forUniformName:@"excludeFalloff"];
}
- (void)setExcludePoint:(CGPoint)excludePoint
{
[_radialFocusFilter setPoint:excludePoint forUniformName:@"excludePoint"];
[_linearFocusFilter setPoint:excludePoint forUniformName:@"excludePoint"];
}
- (void)setAngle:(CGFloat)angle
{
[_linearFocusFilter setFloat:(float)angle forUniformName:@"angle"];
}
#pragma mark GPUImageOutput
- (void)setTargetToIgnoreForUpdates:(id<GPUImageInput>)targetToIgnoreForUpdates
{
[_currentFocusFilter setTargetToIgnoreForUpdates:targetToIgnoreForUpdates];
}
- (void)addTarget:(id<GPUImageInput>)newTarget atTextureLocation:(NSInteger)textureLocation
{
[_currentFocusFilter addTarget:newTarget atTextureLocation:textureLocation];
}
- (void)removeTarget:(id<GPUImageInput>)targetToRemove
{
[_currentFocusFilter removeTarget:targetToRemove];
}
- (void)removeAllTargets
{
[_currentFocusFilter removeAllTargets];
}
- (void)setFrameProcessingCompletionBlock:(void (^)(GPUImageOutput *, CMTime))frameProcessingCompletionBlock
{
[_currentFocusFilter setFrameProcessingCompletionBlock:frameProcessingCompletionBlock];
}
- (void (^)(GPUImageOutput *, CMTime))frameProcessingCompletionBlock
{
return [_currentFocusFilter frameProcessingCompletionBlock];
}
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)__unused textureIndex
{
[_blurFilter newFrameReadyAtTime:frameTime atIndex:0];
[_currentFocusFilter newFrameReadyAtTime:frameTime atIndex:0];
}
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)__unused textureIndex
{
[_blurFilter setInputFramebuffer:newInputFramebuffer atIndex:0];
[_currentFocusFilter setInputFramebuffer:newInputFramebuffer atIndex:0];
}
- (NSInteger)nextAvailableTextureIndex
{
return 0;
}
- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex
{
[_blurFilter setInputSize:newSize atIndex:textureIndex];
[_radialFocusFilter setInputSize:newSize atIndex:textureIndex];
[_linearFocusFilter setInputSize:newSize atIndex:textureIndex];
[_maskedFilter setInputSize:newSize atIndex:textureIndex];
CGFloat aspectRatio = newSize.height / newSize.width;
[_radialFocusFilter setFloat:(float)aspectRatio forUniformName:@"aspectRatio"];
[_linearFocusFilter setFloat:(float)aspectRatio forUniformName:@"aspectRatio"];
}
- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex
{
[_blurFilter setInputRotation:newInputRotation atIndex:textureIndex];
[_radialFocusFilter setInputRotation:newInputRotation atIndex:textureIndex];
[_linearFocusFilter setInputRotation:newInputRotation atIndex:textureIndex];
[_maskedFilter setInputRotation:newInputRotation atIndex:textureIndex];
}
- (CGSize)maximumOutputSize
{
return CGSizeZero;
}
- (void)endProcessing
{
if (!_endProcessing)
{
_endProcessing = true;
[_currentFocusFilter endProcessing];
}
}
- (BOOL)wantsMonochromeInput
{
return false;
}
- (void)setCurrentlyReceivingMonochromeInput:(BOOL)__unused newValue
{
}
@end
@implementation PGPhotoBlurPass
- (instancetype)init
{
self = [super init];
if (self != nil)
{
PGPhotoBlurFilter *filter = [[PGPhotoBlurFilter alloc] init];
_filter = filter;
}
return self;
}
- (void)setType:(PGBlurToolType)type
{
_type = type;
[(PGPhotoBlurFilter *)_filter setType:type];
}
- (void)setSize:(CGFloat)size
{
_size = size;
[(PGPhotoBlurFilter *)_filter setExcludeSize:size];
}
- (void)setFalloff:(CGFloat)falloff
{
_falloff = falloff;
[(PGPhotoBlurFilter *)_filter setExcludeFalloff:falloff];
}
- (void)setPoint:(CGPoint)point
{
_point = point;
[(PGPhotoBlurFilter *)_filter setExcludePoint:point];
}
- (void)setAngle:(CGFloat)angle
{
_angle = angle;
[(PGPhotoBlurFilter *)_filter setAngle:angle + (CGFloat)M_PI_2];
}
@end
@@ -0,0 +1,13 @@
#import "PGPhotoProcessPass.h"
@class TGPhotoEditingContext;
@interface PGPhotoCustomFilterPass : PGPhotoProcessPass
@property (nonatomic, assign) CGFloat intensity;
- (instancetype)initWithShaderFile:(NSString *)shaderFile textureFiles:(NSArray *)textureFiles;
- (instancetype)initWithShaderFile:(NSString *)shaderFile textureFiles:(NSArray *)textureFiles optimized:(bool)optimized;
- (instancetype)initWithShaderString:(NSString *)shaderString textureImages:(NSArray *)textureImages;
@end
@@ -0,0 +1,429 @@
#import "PGPhotoCustomFilterPass.h"
#import "PGPhotoEditorPicture.h"
#import "LegacyComponentsInternal.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSString *const PGPhotoFilterDefinitionsShaderString = PGShaderString
(
precision highp float;
varying vec2 texCoord;
uniform sampler2D sourceImage;
uniform float intensity;
);
NSString *const PGPhotoFilterMainShaderString = PGShaderString
(
void main() {
vec4 texel = texture2D(sourceImage, texCoord);
vec4 result = filter(texel);
gl_FragColor = vec4(mix(texel.rgb, result.rgb, intensity), texel.a);
}
);
@interface PGPhotoCustomFilter : GPUImageFilter
{
GLint _intensityUniform;
GLuint _filterSourceTexture2;
GLuint _filterSourceTexture3;
GLuint _filterSourceTexture4;
GLuint _filterSourceTexture5;
GLuint _filterSourceTexture6;
GLint _filterInputTextureUniform2;
GLint _filterInputTextureUniform3;
GLint _filterInputTextureUniform4;
GLint _filterInputTextureUniform5;
GLint _filterInputTextureUniform6;
}
@property (nonatomic, assign) CGFloat intensity;
@end
@implementation PGPhotoCustomFilter
- (instancetype)initWithFragmentShaderFromString:(NSString *)fragmentShaderString
{
self = [super initWithFragmentShaderFromString:fragmentShaderString];
if (self != nil)
{
_intensityUniform = [filterProgram uniformIndex:@"intensity"];
self.intensity = 1.0f;
_filterInputTextureUniform2 = [filterProgram uniformIndex:@"inputImageTexture2"];
_filterInputTextureUniform3 = [filterProgram uniformIndex:@"inputImageTexture3"];
_filterInputTextureUniform4 = [filterProgram uniformIndex:@"inputImageTexture4"];
_filterInputTextureUniform5 = [filterProgram uniformIndex:@"inputImageTexture5"];
_filterInputTextureUniform6 = [filterProgram uniformIndex:@"inputImageTexture6"];
}
return self;
}
- (void)dealloc
{
runAsynchronouslyOnVideoProcessingQueue(^
{
if (_filterSourceTexture2)
glDeleteTextures(1, &_filterSourceTexture2);
if (_filterSourceTexture3)
glDeleteTextures(1, &_filterSourceTexture3);
if (_filterSourceTexture4)
glDeleteTextures(1, &_filterSourceTexture4);
if (_filterSourceTexture5)
glDeleteTextures(1, &_filterSourceTexture5);
if (_filterSourceTexture6)
glDeleteTextures(1, &_filterSourceTexture6);
});
}
- (void)addTextureWithImage:(UIImage *)image textureIndex:(NSInteger)textureIndex
{
bool redrawNeeded = false;
CGImageRef cgImage = image.CGImage;
if (image.imageOrientation != UIImageOrientationUp)
redrawNeeded = true;
CGSize imageSize = CGSizeMake(CGImageGetWidth(cgImage), CGImageGetHeight(cgImage));
if (image.imageOrientation == UIImageOrientationLeft || image.imageOrientation == UIImageOrientationRight)
imageSize = CGSizeMake(imageSize.height, imageSize.width);
GLubyte *imageData = NULL;
CFDataRef dataFromImageDataProvider = NULL;
if (!redrawNeeded)
{
if (CGImageGetBytesPerRow(cgImage) != CGImageGetWidth(cgImage) * 4 ||
CGImageGetBitsPerPixel(cgImage) != 32 ||
CGImageGetBitsPerComponent(cgImage) != 8)
{
redrawNeeded = true;
}
else
{
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage);
if ((bitmapInfo & kCGBitmapFloatComponents) != 0)
{
redrawNeeded = true;
}
else
{
CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
if (byteOrderInfo == kCGBitmapByteOrder32Little)
{
/* Little endian, for alpha-first we can use this bitmap directly in GL */
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
if (alphaInfo != kCGImageAlphaPremultipliedFirst && alphaInfo != kCGImageAlphaFirst &&
alphaInfo != kCGImageAlphaNoneSkipFirst)
{
redrawNeeded = true;
}
}
else if (byteOrderInfo == kCGBitmapByteOrderDefault || byteOrderInfo == kCGBitmapByteOrder32Big)
{
/* Big endian, for alpha-last we can use this bitmap directly in GL */
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
if (alphaInfo != kCGImageAlphaPremultipliedLast && alphaInfo != kCGImageAlphaLast &&
alphaInfo != kCGImageAlphaNoneSkipLast)
{
redrawNeeded = true;
}
}
}
}
}
if (redrawNeeded)
{
imageData = (GLubyte *) calloc(1, (int)imageSize.width * (int)imageSize.height * 4);
CGColorSpaceRef genericRGBColorspace = CGColorSpaceCreateDeviceRGB();
CGContextRef imageContext = CGBitmapContextCreate(imageData, (size_t)imageSize.width, (size_t)imageSize.height, 8, (size_t)imageSize.width * 4, genericRGBColorspace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
CGSize imageDrawSize = CGSizeMake(imageSize.width, imageSize.height);
if (image.imageOrientation == UIImageOrientationLeft || image.imageOrientation == UIImageOrientationRight)
imageDrawSize = CGSizeMake(imageDrawSize.height, imageDrawSize.width);
CGAffineTransform transform = CGAffineTransformIdentity;
switch (image.imageOrientation)
{
case UIImageOrientationDown:
case UIImageOrientationDownMirrored:
transform = CGAffineTransformTranslate(transform, imageSize.width, imageSize.height);
transform = CGAffineTransformRotate(transform, (CGFloat)M_PI);
break;
case UIImageOrientationLeft:
case UIImageOrientationLeftMirrored:
transform = CGAffineTransformTranslate(transform, imageSize.width, 0);
transform = CGAffineTransformRotate(transform, (CGFloat)M_PI_2);
break;
case UIImageOrientationRight:
case UIImageOrientationRightMirrored:
transform = CGAffineTransformTranslate(transform, 0, imageSize.height);
transform = CGAffineTransformRotate(transform, (CGFloat)-M_PI_2);
break;
default:
break;
}
switch (image.imageOrientation)
{
case UIImageOrientationUpMirrored:
case UIImageOrientationDownMirrored:
transform = CGAffineTransformTranslate(transform, imageSize.width,0);
transform = CGAffineTransformScale(transform, -1, 1);
break;
case UIImageOrientationLeftMirrored:
case UIImageOrientationRightMirrored:
transform = CGAffineTransformTranslate(transform, imageSize.height, 0);
transform = CGAffineTransformScale(transform, -1, 1);
break;
default:
break;
}
CGContextConcatCTM(imageContext, transform);
CGContextDrawImage(imageContext, CGRectMake(0.0f, 0.0f, imageDrawSize.width, imageDrawSize.height), cgImage);
CGContextRelease(imageContext);
CGColorSpaceRelease(genericRGBColorspace);
}
else
{
dataFromImageDataProvider = CGDataProviderCopyData(CGImageGetDataProvider(cgImage));
imageData = (GLubyte *)CFDataGetBytePtr(dataFromImageDataProvider);
}
runSynchronouslyOnVideoProcessingQueue(^
{
[GPUImageContext useImageProcessingContext];
switch (textureIndex)
{
case 0:
{
glActiveTexture(GL_TEXTURE3);
glGenTextures(1, &_filterSourceTexture2);
glBindTexture(GL_TEXTURE_2D, _filterSourceTexture2);
}
break;
case 1:
{
glActiveTexture(GL_TEXTURE4);
glGenTextures(1, &_filterSourceTexture3);
glBindTexture(GL_TEXTURE_2D, _filterSourceTexture3);
}
break;
case 2:
{
glActiveTexture(GL_TEXTURE5);
glGenTextures(1, &_filterSourceTexture4);
glBindTexture(GL_TEXTURE_2D, _filterSourceTexture4);
}
break;
case 3:
{
glActiveTexture(GL_TEXTURE6);
glGenTextures(1, &_filterSourceTexture5);
glBindTexture(GL_TEXTURE_2D, _filterSourceTexture5);
break;
}
case 4:
{
glActiveTexture(GL_TEXTURE7);
glGenTextures(1, &_filterSourceTexture6);
glBindTexture(GL_TEXTURE_2D, _filterSourceTexture6);
break;
}
default:
break;
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)imageSize.width, (GLsizei)imageSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
if (redrawNeeded)
free(imageData);
else if (dataFromImageDataProvider)
CFRelease(dataFromImageDataProvider);
});
}
- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates
{
if (self.preventRendering)
{
[firstInputFramebuffer unlock];
return;
}
[GPUImageContext setActiveShaderProgram:filterProgram];
outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:[self sizeOfFBO] textureOptions:self.outputTextureOptions onlyTexture:false];
[outputFramebuffer activateFramebuffer];
if (usingNextFrameForImageCapture)
{
[outputFramebuffer lock];
}
[self setUniformsForProgramAtIndex:0];
glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, [firstInputFramebuffer texture]);
glUniform1i(filterInputTextureUniform, 2);
if (_filterSourceTexture2)
{
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, _filterSourceTexture2);
glUniform1i(_filterInputTextureUniform2, 3);
}
if (_filterSourceTexture3)
{
glActiveTexture(GL_TEXTURE4);
glBindTexture(GL_TEXTURE_2D, _filterSourceTexture3);
glUniform1i(_filterInputTextureUniform3, 4);
}
if (_filterSourceTexture4)
{
glActiveTexture(GL_TEXTURE5);
glBindTexture(GL_TEXTURE_2D, _filterSourceTexture4);
glUniform1i(_filterInputTextureUniform4, 5);
}
if (_filterSourceTexture5)
{
glActiveTexture(GL_TEXTURE6);
glBindTexture(GL_TEXTURE_2D, _filterSourceTexture5);
glUniform1i(_filterInputTextureUniform5, 6);
}
if (_filterSourceTexture6)
{
glActiveTexture(GL_TEXTURE7);
glBindTexture(GL_TEXTURE_2D, _filterSourceTexture6);
glUniform1i(_filterInputTextureUniform6, 7);
}
glVertexAttribPointer(filterPositionAttribute, 2, GL_FLOAT, 0, 0, vertices);
glVertexAttribPointer(filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[firstInputFramebuffer unlock];
if (usingNextFrameForImageCapture)
dispatch_semaphore_signal(imageCaptureSemaphore);
}
- (void)setIntensity:(CGFloat)intensity
{
_intensity = intensity;
[self setFloat:(float)_intensity forUniform:_intensityUniform program:filterProgram];
}
@end
@implementation PGPhotoCustomFilterPass
@dynamic intensity;
- (instancetype)initWithShaderFile:(NSString *)shaderFile textureFiles:(NSArray *)textureFiles
{
return [self initWithShaderFile:shaderFile textureFiles:textureFiles optimized:false];
}
- (instancetype)initWithShaderFile:(NSString *)shaderFile textureFiles:(NSArray *)textureFiles optimized:(bool)optimized
{
NSString *fragmentShaderPathname = TGComponentsPathForResource(shaderFile, @"fsh");
NSString *fragmentShaderString = [NSString stringWithContentsOfFile:fragmentShaderPathname encoding:NSUTF8StringEncoding error:nil];
NSMutableArray *textureImages = [[NSMutableArray alloc] init];
for (id textureDefinition in textureFiles)
{
NSString *textureFile = nil;
if ([textureDefinition isKindOfClass:[NSString class]])
{
textureFile = (NSString *)textureDefinition;
}
else if ([textureDefinition isKindOfClass:[NSArray class]])
{
NSArray *textureDefinitions = (NSArray *)textureDefinition;
textureFile = optimized ? textureDefinitions.lastObject : textureDefinitions.firstObject;
}
NSString *name = [[textureFile lastPathComponent] stringByDeletingPathExtension];
NSString *extension = [textureFile pathExtension];
NSString *texturePathname = TGComponentsPathForResource(name, extension);
UIImage *textureImage = [UIImage imageWithContentsOfFile:texturePathname];
[textureImages addObject:textureImage];
}
return [self initWithShaderString:fragmentShaderString textureImages:textureImages];
}
- (instancetype)initWithShaderString:(NSString *)shaderString textureImages:(NSArray *)textureImages
{
self = [super init];
if (self != nil)
{
NSMutableString *fullShaderString = [[NSMutableString alloc] initWithString:PGPhotoFilterDefinitionsShaderString];
[fullShaderString appendString:shaderString];
[fullShaderString appendString:PGPhotoFilterMainShaderString];
PGPhotoCustomFilter *filter = [[PGPhotoCustomFilter alloc] initWithFragmentShaderFromString:fullShaderString];
NSInteger index = 0;
for (UIImage *image in textureImages)
{
[filter addTextureWithImage:image textureIndex:index];
index++;
}
_filter = filter;
}
return self;
}
- (void)setIntensity:(CGFloat)intensity
{
[(PGPhotoCustomFilter *)_filter setIntensity:intensity];
}
@end
#pragma clang diagnostic pop
@@ -0,0 +1,68 @@
#import <Foundation/Foundation.h>
#import <SSignalKit/SSignalKit.h>
#import <LegacyComponents/TGVideoEditAdjustments.h>
@class TGPhotoEditorPreviewView;
@class TGPaintingData;
@interface PGPhotoEditor : NSObject
@property (nonatomic, assign) CGSize originalSize;
@property (nonatomic, assign) CGRect cropRect;
@property (nonatomic, readonly) CGSize rotatedCropSize;
@property (nonatomic, assign) CGFloat cropRotation;
@property (nonatomic, assign) UIImageOrientation cropOrientation;
@property (nonatomic, assign) CGFloat cropLockedAspectRatio;
@property (nonatomic, assign) bool cropMirrored;
@property (nonatomic, strong) TGPaintingData *paintingData;
@property (nonatomic, assign) NSTimeInterval trimStartValue;
@property (nonatomic, assign) NSTimeInterval trimEndValue;
@property (nonatomic, assign) bool sendAsGif;
@property (nonatomic, assign) TGMediaVideoConversionPreset preset;
@property (nonatomic, weak) TGPhotoEditorPreviewView *previewOutput;
@property (nonatomic, strong) NSArray *additionalOutputs;
@property (nonatomic, readonly) NSArray *tools;
@property (nonatomic, readonly) bool processing;
@property (nonatomic, readonly) bool readyForProcessing;
@property (nonatomic, readonly) bool enableStickers;
@property (nonatomic, assign) bool cropOnLast;
@property (nonatomic, readonly) bool forVideo;
@property (nonatomic, assign) bool standalone;
@property (nonatomic, assign) bool disableAll;
- (instancetype)initWithOriginalSize:(CGSize)originalSize adjustments:(id<TGMediaEditAdjustments>)adjustments forVideo:(bool)forVideo enableStickers:(bool)enableStickers;
- (void)cleanup;
- (void)setImage:(UIImage *)image forCropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropMirrored:(bool)cropMirrored fullSize:(bool)fullSize;
- (void)setPlayerItem:(AVPlayerItem *)playerItem forCropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropMirrored:(bool)cropMirrored;
- (void)setCIImage:(CIImage *)ciImage;
- (void)updateProcessChain:(bool)force;
- (void)processAnimated:(bool)animated completion:(void (^)(void))completion;
- (void)reprocess;
- (void)createResultImageWithCompletion:(void (^)(UIImage *image))completion;
- (UIImage *)currentResultImage;
- (void)currentResultCIImage:(void (^)(CIImage *image, void(^unlock)(void)))completion;
- (bool)hasDefaultCropping;
- (SSignal *)histogramSignal;
- (void)importAdjustments:(id<TGMediaEditAdjustments>)adjustments;
- (id<TGMediaEditAdjustments>)exportAdjustments;
- (id<TGMediaEditAdjustments>)exportAdjustmentsWithPaintingData:(TGPaintingData *)paintingData;
+ (UIImage *)resultImageForImage:(UIImage *)image adjustments:(id<TGMediaEditAdjustments>)adjustments;
@end
@@ -0,0 +1,641 @@
#import "PGPhotoEditor.h"
#import <SSignalKit/SSignalKit.h>
#import <LegacyComponents/TGMemoryImageCache.h>
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/TGPhotoEditorUtils.h>
#import "TGPhotoEditorPreviewView.h"
#import "PGPhotoEditorView.h"
#import "PGPhotoEditorPicture.h"
#import "GPUImageTextureInput.h"
#import "GPUImageCropFilter.h"
#import <LegacyComponents/PGPhotoEditorValues.h>
#import <LegacyComponents/TGVideoEditAdjustments.h>
#import <LegacyComponents/TGPaintingData.h>
#import "PGVideoMovie.h"
#import "PGPhotoToolComposer.h"
#import "PGEnhanceTool.h"
#import "PGSkinTool.h"
#import "PGExposureTool.h"
#import "PGContrastTool.h"
#import "PGWarmthTool.h"
#import "PGSaturationTool.h"
#import "PGHighlightsTool.h"
#import "PGShadowsTool.h"
#import "PGVignetteTool.h"
#import "PGGrainTool.h"
#import "PGBlurTool.h"
#import "PGSharpenTool.h"
#import "PGFadeTool.h"
#import "PGTintTool.h"
#import "PGCurvesTool.h"
#import "PGPhotoHistogramGenerator.h"
@interface PGPhotoEditor ()
{
PGPhotoToolComposer *_toolComposer;
id<TGMediaEditAdjustments> _initialAdjustments;
GPUImageOutput *_currentInput;
GPUImageCropFilter *_cropFilter;
GPUImageRotationMode _rotationMode;
NSArray *_currentProcessChain;
GPUImageOutput <GPUImageInput> *_finalFilter;
PGPhotoHistogram *_currentHistogram;
PGPhotoHistogramGenerator *_histogramGenerator;
UIImageOrientation _imageCropOrientation;
CGRect _imageCropRect;
CGFloat _imageCropRotation;
bool _imageCropMirrored;
SPipe *_histogramPipe;
SQueue *_queue;
SQueue *_videoQueue;
bool _playing;
bool _processing;
bool _needsReprocessing;
bool _fullSize;
}
@end
@implementation PGPhotoEditor
- (instancetype)initWithOriginalSize:(CGSize)originalSize adjustments:(id<TGMediaEditAdjustments>)adjustments forVideo:(bool)forVideo enableStickers:(bool)enableStickers
{
self = [super init];
if (self != nil)
{
_queue = [[SQueue alloc] init];
_videoQueue = [[SQueue alloc] init];
_forVideo = forVideo;
_enableStickers = enableStickers;
_originalSize = originalSize;
_cropRect = CGRectMake(0.0f, 0.0f, _originalSize.width, _originalSize.height);
_paintingData = adjustments.paintingData;
_tools = [self toolsInit];
_toolComposer = [[PGPhotoToolComposer alloc] init];
[_toolComposer addPhotoTools:_tools];
[_toolComposer compose];
_histogramPipe = [[SPipe alloc] init];
if (!forVideo) {
__weak PGPhotoEditor *weakSelf = self;
_histogramGenerator = [[PGPhotoHistogramGenerator alloc] init];
_histogramGenerator.histogramReady = ^(PGPhotoHistogram *histogram)
{
__strong PGPhotoEditor *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->_currentHistogram = histogram;
strongSelf->_histogramPipe.sink(histogram);
};
}
[self importAdjustments:adjustments];
}
return self;
}
- (void)dealloc
{
if ([_currentInput isKindOfClass:[PGVideoMovie class]]) {
[(PGVideoMovie *)_currentInput cancelProcessing];
}
TGDispatchAfter(1.5f, dispatch_get_main_queue(), ^
{
[[GPUImageContext sharedFramebufferCache] purgeAllUnassignedFramebuffers];
});
}
- (void)cleanup
{
[[GPUImageContext sharedFramebufferCache] purgeAllUnassignedFramebuffers];
}
- (NSArray *)toolsInit
{
NSMutableArray *tools = [NSMutableArray array];
for (Class toolClass in [PGPhotoEditor availableTools])
{
PGPhotoTool *toolInstance = [[toolClass alloc] init];
if (!_forVideo || toolInstance.isAvialableForVideo) {
[tools addObject:toolInstance];
}
}
return tools;
}
- (void)setImage:(UIImage *)image forCropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropMirrored:(bool)cropMirrored fullSize:(bool)fullSize
{
[_toolComposer invalidate];
_currentProcessChain = nil;
_imageCropRect = cropRect;
_imageCropRotation = cropRotation;
_imageCropOrientation = cropOrientation;
_imageCropMirrored = cropMirrored;
[_currentInput removeAllTargets];
_currentInput = [[PGPhotoEditorPicture alloc] initWithImage:image];
_histogramGenerator.imageSize = image.size;
_fullSize = fullSize;
}
- (CGFloat)_cropRectEpsilon
{
return MAX(_originalSize.width, _originalSize.height) * 0.005f;
}
- (CGRect)normalizedCropRect:(CGRect)cropRect
{
CGRect normalizedCropRect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
normalizedCropRect = CGRectMake(MAX(0.0, MIN(1.0, cropRect.origin.x / _originalSize.width)), MAX(0.0, MIN(1.0, cropRect.origin.y / _originalSize.height)), MAX(0.0, MIN(1.0, cropRect.size.width / _originalSize.width)), MAX(0.0, MIN(1.0, cropRect.size.height / _originalSize.height)));
return normalizedCropRect;
}
- (void)setCropRect:(CGRect)cropRect
{
_cropRect = cropRect;
_cropFilter.cropRegion = [self normalizedCropRect:cropRect];
}
- (void)setCropOrientation:(UIImageOrientation)cropOrientation
{
_cropOrientation = cropOrientation;
}
- (void)setPlayerItem:(AVPlayerItem *)playerItem forCropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropMirrored:(bool)cropMirrored {
[_toolComposer invalidate];
_currentProcessChain = nil;
[_currentInput removeAllTargets];
PGVideoMovie *movie = [[PGVideoMovie alloc] initWithPlayerItem:playerItem];
_currentInput = movie;
CGRect defaultCropRect = CGRectMake(0, 0, _originalSize.width, _originalSize.height);
bool hasCropping = !_CGRectEqualToRectWithEpsilon(cropRect, CGRectZero, [self _cropRectEpsilon]) && !_CGRectEqualToRectWithEpsilon(cropRect, defaultCropRect, [self _cropRectEpsilon]);
_rotationMode = kGPUImageNoRotation;
if (cropOrientation != UIImageOrientationUp || cropMirrored || hasCropping) {
if (_cropFilter == nil)
_cropFilter = [[GPUImageCropFilter alloc] initWithCropRegion:[self normalizedCropRect:cropRect]];
else
_cropFilter.cropRegion = [self normalizedCropRect:cropRect];
if (cropOrientation != UIImageOrientationUp || cropMirrored) {
switch (cropOrientation) {
case UIImageOrientationLeft:
_rotationMode = kGPUImageRotateLeft;
break;
case UIImageOrientationRight:
_rotationMode = cropMirrored ? kGPUImageRotateRightFlipHorizontal : kGPUImageRotateRight;
break;
case UIImageOrientationDown:
_rotationMode = cropMirrored ? kGPUImageRotate180FlipHorizontal : kGPUImageRotate180;
break;
case UIImageOrientationUp:
if (cropMirrored)
_rotationMode = kGPUImageFlipHorizonal;
break;
default:
break;
}
}
}
_fullSize = true;
}
- (void)setCIImage:(CIImage *)ciImage {
[_toolComposer invalidate];
_currentProcessChain = nil;
[_currentInput removeAllTargets];
if ([_currentInput isKindOfClass:[GPUImageTextureInput class]]) {
[(GPUImageTextureInput *)_currentInput setCIImage:ciImage];
} else {
GPUImageTextureInput *input = [[GPUImageTextureInput alloc] initWithCIImage:ciImage];
_currentInput = input;
}
_fullSize = true;
}
#pragma mark - Properties
- (CGSize)rotatedCropSize
{
if (_cropOrientation == UIImageOrientationLeft || _cropOrientation == UIImageOrientationRight)
return CGSizeMake(_cropRect.size.height, _cropRect.size.width);
return _cropRect.size;
}
- (bool)hasDefaultCropping
{
if (!_CGRectEqualToRectWithEpsilon(self.cropRect, CGRectMake(0, 0, _originalSize.width, _originalSize.height), 1.0f) || self.cropOrientation != UIImageOrientationUp || ABS(self.cropRotation) > FLT_EPSILON || self.cropMirrored)
{
return false;
}
return true;
}
#pragma mark - Processing
- (bool)readyForProcessing
{
return (_currentInput != nil);
}
- (void)processAnimated:(bool)animated completion:(void (^)(void))completion
{
[self processAnimated:animated capture:false synchronous:false completion:completion];
}
- (void)processAnimated:(bool)animated capture:(bool)capture synchronous:(bool)synchronous completion:(void (^)(void))completion
{
if (self.previewOutput == nil && !self.standalone)
return;
if (![_currentInput isKindOfClass:[PGPhotoEditorPicture class]]) {
[_queue dispatch:^
{
[self updateProcessChain];
GPUImageOutput *currentInput = _currentInput;
if ([currentInput isKindOfClass:[PGVideoMovie class]]) {
if (capture) {
if ([currentInput isKindOfClass:[PGVideoMovie class]])
[(PGVideoMovie *)currentInput process];
[_finalFilter useNextFrameForImageCapture];
if (completion != nil)
completion();
} else {
if (!_playing) {
_playing = true;
[_videoQueue dispatch:^{
if ([currentInput isKindOfClass:[PGVideoMovie class]]) {
[(PGVideoMovie *)currentInput startProcessing];
}
}];
}
}
} else if ([currentInput isKindOfClass:[GPUImageTextureInput class]]) {
if (capture)
[_finalFilter useNextFrameForImageCapture];
[(GPUImageTextureInput *)currentInput processTextureWithFrameTime:kCMTimeZero synchronous:synchronous completion:^{
if (completion != nil)
completion();
}];
[_finalFilter commitImageCapture];
}
} synchronous:synchronous];
return;
}
if (iosMajorVersion() < 7)
animated = false;
if (_processing && completion == nil)
{
_needsReprocessing = true;
return;
}
_processing = true;
[_queue dispatch:^
{
[self updateProcessChain];
if (!self.forVideo && capture)
[_finalFilter useNextFrameForImageCapture];
TGPhotoEditorPreviewView *previewOutput = self.previewOutput;
if ([_currentInput isKindOfClass:[PGPhotoEditorPicture class]]) {
PGPhotoEditorPicture *picture = (PGPhotoEditorPicture *)_currentInput;
if (animated)
{
TGDispatchOnMainThread(^
{
[previewOutput prepareTransitionFadeView];
});
}
[picture processSynchronous:true completion:^
{
if (completion != nil)
completion();
_processing = false;
if (animated)
{
TGDispatchOnMainThread(^
{
[previewOutput performTransitionFade];
});
}
if (_needsReprocessing && !synchronous)
{
_needsReprocessing = false;
[self processAnimated:false completion:nil];
}
}];
} else {
}
} synchronous:synchronous];
}
- (void)reprocess {
if ([_currentInput isKindOfClass:[PGVideoMovie class]]) {
[(PGVideoMovie *)_currentInput reprocessCurrent];
}
}
- (void)updateProcessChain {
[self updateProcessChain:false];
}
- (void)updateProcessChain:(bool)force {
[GPUImageFramebuffer setMark:self.forVideo];
NSMutableArray *processChain = [NSMutableArray array];
for (PGPhotoTool *tool in _toolComposer.advancedTools)
{
if (!tool.shouldBeSkipped && tool.pass != nil)
[processChain addObject:tool.pass];
}
_toolComposer.imageSize = _cropRect.size;
[processChain addObject:_toolComposer];
TGPhotoEditorPreviewView *previewOutput = self.previewOutput;
if (![_currentProcessChain isEqualToArray:processChain] || force)
{
[_currentInput removeAllTargets];
[_cropFilter removeAllTargets];
for (PGPhotoProcessPass *pass in _currentProcessChain)
[pass.filter removeAllTargets];
_currentProcessChain = processChain;
GPUImageOutput <GPUImageInput> *lastFilter = ((PGPhotoProcessPass *)_currentProcessChain.firstObject).filter;
if (_cropFilter != nil && !self.cropOnLast) {
[_currentInput addTarget:_cropFilter];
[_cropFilter addTarget:lastFilter];
} else {
[_currentInput addTarget:lastFilter];
}
NSInteger chainLength = _currentProcessChain.count;
if (chainLength > 1)
{
for (NSInteger i = 1; i < chainLength; i++)
{
PGPhotoProcessPass *pass = ((PGPhotoProcessPass *)_currentProcessChain[i]);
GPUImageOutput <GPUImageInput> *filter = pass.filter;
[lastFilter addTarget:filter];
lastFilter = filter;
}
}
_finalFilter = lastFilter;
if (self.cropOnLast) {
if (_cropFilter == nil)
_cropFilter = [[GPUImageCropFilter alloc] initWithCropRegion:[self normalizedCropRect:_cropRect]];
for (PGPhotoEditorView *view in _additionalOutputs) {
[_finalFilter addTarget:view];
}
[_finalFilter addTarget:_cropFilter];
if (previewOutput != nil) {
[_cropFilter addTarget:previewOutput.imageView];
}
} else {
if (previewOutput != nil) {
[_finalFilter addTarget:previewOutput.imageView];
}
for (PGPhotoEditorView *view in _additionalOutputs) {
[_finalFilter addTarget:view];
}
}
if (_histogramGenerator != nil && !self.standalone) {
[_finalFilter addTarget:_histogramGenerator];
}
}
}
- (void)setAdditionalOutputs:(NSArray *)additionalOutputs {
_additionalOutputs = additionalOutputs;
if (_finalFilter == nil)
return;
[_cropFilter removeAllTargets];
[_finalFilter removeAllTargets];
if (self.cropOnLast) {
for (PGPhotoEditorView *view in _additionalOutputs) {
[_finalFilter addTarget:view];
}
[_finalFilter addTarget:_cropFilter];
if (self.previewOutput != nil) {
[_cropFilter addTarget:self.previewOutput.imageView];
}
} else {
for (PGPhotoEditorView *view in _additionalOutputs) {
[_finalFilter addTarget:view];
}
if (self.previewOutput != nil) {
[_finalFilter addTarget:self.previewOutput.imageView];
}
}
if (_histogramGenerator != nil && !self.standalone) {
[_finalFilter addTarget:_histogramGenerator];
}
}
#pragma mark - Result
- (void)createResultImageWithCompletion:(void (^)(UIImage *image))completion
{
[self processAnimated:false capture:true synchronous:false completion:^
{
UIImage *image = [_finalFilter imageFromCurrentFramebufferWithOrientation:UIImageOrientationUp];
if (completion != nil)
completion(image);
}];
}
- (UIImage *)currentResultImage
{
__block UIImage *image = nil;
[self processAnimated:false capture:true synchronous:true completion:^
{
image = [_finalFilter imageFromCurrentFramebufferWithOrientation:UIImageOrientationUp];
}];
return image;
}
- (void)currentResultCIImage:(void (^)(CIImage *image, void(^unlock)(void)))completion
{
[self processAnimated:false capture:true synchronous:true completion:^
{
[_finalFilter newCIImageFromCurrentlyProcessedOutput:completion];
}];
}
#pragma mark - Editor Values
- (void)importAdjustments:(id<TGMediaEditAdjustments>)adjustments
{
_initialAdjustments = adjustments;
if (adjustments != nil)
self.cropRect = adjustments.cropRect;
self.cropOrientation = adjustments.cropOrientation;
self.cropLockedAspectRatio = adjustments.cropLockedAspectRatio;
self.cropMirrored = adjustments.cropMirrored;
self.paintingData = adjustments.paintingData;
if ([adjustments isKindOfClass:[PGPhotoEditorValues class]])
{
PGPhotoEditorValues *editorValues = (PGPhotoEditorValues *)adjustments;
self.cropRotation = editorValues.cropRotation;
}
else if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]])
{
TGVideoEditAdjustments *videoAdjustments = (TGVideoEditAdjustments *)adjustments;
self.trimStartValue = videoAdjustments.trimStartValue;
self.trimEndValue = videoAdjustments.trimEndValue;
self.sendAsGif = videoAdjustments.sendAsGif;
self.preset = videoAdjustments.preset;
}
for (PGPhotoTool *tool in self.tools)
{
id value = adjustments.toolValues[tool.identifier];
if (value != nil && [value isKindOfClass:[tool valueClass]])
tool.value = [value copy];
}
}
- (id<TGMediaEditAdjustments>)exportAdjustments
{
return [self exportAdjustmentsWithPaintingData:_paintingData];
}
- (id<TGMediaEditAdjustments>)exportAdjustmentsWithPaintingData:(TGPaintingData *)paintingData
{
NSMutableDictionary *toolValues = [[NSMutableDictionary alloc] init];
for (PGPhotoTool *tool in self.tools)
{
if (!tool.shouldBeSkipped && (!_forVideo || tool.isAvialableForVideo))
{
if (!([tool.value isKindOfClass:[NSNumber class]] && ABS([tool.value floatValue] - (float)tool.defaultValue) < FLT_EPSILON))
toolValues[tool.identifier] = [tool.value copy];
}
}
if (!_forVideo)
{
return [PGPhotoEditorValues editorValuesWithOriginalSize:self.originalSize cropRect:self.cropRect cropRotation:self.cropRotation cropOrientation:self.cropOrientation cropLockedAspectRatio:self.cropLockedAspectRatio cropMirrored:self.cropMirrored toolValues:toolValues paintingData:paintingData sendAsGif:self.sendAsGif];
}
else
{
TGVideoEditAdjustments *initialAdjustments = (TGVideoEditAdjustments *)_initialAdjustments;
return [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:self.originalSize cropRect:self.cropRect cropOrientation:self.cropOrientation cropRotation:self.cropRotation cropLockedAspectRatio:self.cropLockedAspectRatio cropMirrored:self.cropMirrored trimStartValue:initialAdjustments.trimStartValue trimEndValue:initialAdjustments.trimEndValue toolValues:toolValues paintingData:paintingData sendAsGif:self.sendAsGif preset:self.preset];
}
}
- (void)setDisableAll:(bool)disableAll {
_disableAll = disableAll;
for (PGPhotoTool *tool in self.tools)
{
tool.disabled = disableAll;
}
}
- (SSignal *)histogramSignal
{
return [[SSignal single:_currentHistogram] then:_histogramPipe.signalProducer()];
}
+ (NSArray *)availableTools
{
static NSArray *tools;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
tools = @[ [PGSkinTool class],
[PGEnhanceTool class],
[PGExposureTool class],
[PGContrastTool class],
[PGSaturationTool class],
[PGWarmthTool class],
[PGFadeTool class],
[PGTintTool class],
[PGHighlightsTool class],
[PGShadowsTool class],
[PGVignetteTool class],
[PGGrainTool class],
[PGBlurTool class],
[PGSharpenTool class],
[PGCurvesTool class] ];
});
return tools;
}
+ (UIImage *)resultImageForImage:(UIImage *)image adjustments:(id<TGMediaEditAdjustments>)adjustments {
PGPhotoEditor *editor = [[PGPhotoEditor alloc] initWithOriginalSize:adjustments.originalSize adjustments:adjustments forVideo:false enableStickers:true];
editor.standalone = true;
[editor setImage:image forCropRect:adjustments.cropRect cropRotation:0.0 cropOrientation:adjustments.cropOrientation cropMirrored:adjustments.cropMirrored fullSize:false];
return [editor currentResultImage];
}
@end
@@ -0,0 +1,33 @@
#import <LegacyComponents/TGPhotoEditorToolView.h>
@protocol PGPhotoEditorItem <NSObject>
@property (nonatomic, readonly) NSString *identifier;
@property (nonatomic, readonly) NSString *title;
@property (nonatomic, readonly) NSArray *parameters;
@property (nonatomic, readonly) CGFloat defaultValue;
@property (nonatomic, readonly) CGFloat minimumValue;
@property (nonatomic, readonly) CGFloat maximumValue;
@property (nonatomic, readonly) bool segmented;
@property (nonatomic, strong) id value;
@property (nonatomic, strong) id tempValue;
@property (nonatomic, readonly) id displayValue;
@property (nonatomic, readonly) NSString *stringValue;
@property (nonatomic, readonly) bool shouldBeSkipped;
@property (nonatomic, assign) bool beingEdited;
@property (nonatomic, assign) bool disabled;
@property (copy, nonatomic) void(^parametersChanged)(void);
- (UIView <TGPhotoEditorToolView> *)itemControlViewWithChangeBlock:(void (^)(id newValue, bool animated))changeBlock;
- (UIView <TGPhotoEditorToolView> *)itemAreaViewWithChangeBlock:(void (^)(id newValue))changeBlock;
- (Class)valueClass;
- (void)updateParameters;
@end
@@ -0,0 +1,10 @@
#import "GPUImageOutput.h"
@interface PGPhotoEditorPicture : GPUImageOutput
- (instancetype)initWithImage:(UIImage *)image;
- (instancetype)initWithImage:(UIImage *)image textureOptions:(GPUTextureOptions)textureOptions;
- (bool)processSynchronous:(bool)synchronous completion:(void (^)(void))completion;
@end
@@ -0,0 +1,254 @@
#import "PGPhotoEditorPicture.h"
@interface PGPhotoEditorPicture ()
{
CGSize _imageSize;
bool _processed;
dispatch_semaphore_t _updateSemaphore;
}
@end
@implementation PGPhotoEditorPicture
- (instancetype)initWithImage:(UIImage *)image
{
GPUTextureOptions defaultOptions;
defaultOptions.minFilter = GL_LINEAR;
defaultOptions.magFilter = GL_LINEAR;
defaultOptions.wrapS = GL_CLAMP_TO_EDGE;
defaultOptions.wrapT = GL_CLAMP_TO_EDGE;
defaultOptions.internalFormat = GL_RGBA;
defaultOptions.format = GL_BGRA;
defaultOptions.type = GL_UNSIGNED_BYTE;
return [self initWithImage:image textureOptions:defaultOptions];
}
- (instancetype)initWithImage:(UIImage *)image textureOptions:(GPUTextureOptions)textureOptions
{
self = [super init];
if (self != nil)
{
_updateSemaphore = dispatch_semaphore_create(0);
dispatch_semaphore_signal(_updateSemaphore);
[self setupWithCGImage:image.CGImage orientation:image.imageOrientation textureOptions:textureOptions];
}
return self;
}
- (void)setupWithCGImage:(CGImageRef)image orientation:(UIImageOrientation)orientation textureOptions:(GPUTextureOptions)textureOptions
{
bool redrawNeeded = false;
if (orientation != UIImageOrientationUp)
redrawNeeded = true;
CGSize imageSize = CGSizeMake(CGImageGetWidth(image), CGImageGetHeight(image));
if (orientation == UIImageOrientationLeft || orientation == UIImageOrientationRight)
imageSize = CGSizeMake(imageSize.height, imageSize.width);
CGSize fittedImageSize = [GPUImageContext sizeThatFitsWithinATextureForSize:imageSize];
if (!CGSizeEqualToSize(fittedImageSize, imageSize))
{
imageSize = fittedImageSize;
redrawNeeded = true;
}
GLubyte *imageData = NULL;
CFDataRef dataFromImageDataProvider = NULL;
GLenum format = GL_BGRA;
if (!redrawNeeded)
{
if (CGImageGetBytesPerRow(image) != CGImageGetWidth(image) * 4 ||
CGImageGetBitsPerPixel(image) != 32 ||
CGImageGetBitsPerComponent(image) != 8)
{
redrawNeeded = true;
}
else
{
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image);
if ((bitmapInfo & kCGBitmapFloatComponents) != 0)
{
redrawNeeded = true;
}
else
{
CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
if (byteOrderInfo == kCGBitmapByteOrder32Little)
{
/* Little endian, for alpha-first we can use this bitmap directly in GL */
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
if (alphaInfo != kCGImageAlphaPremultipliedFirst && alphaInfo != kCGImageAlphaFirst &&
alphaInfo != kCGImageAlphaNoneSkipFirst)
{
redrawNeeded = true;
}
}
else if (byteOrderInfo == kCGBitmapByteOrderDefault || byteOrderInfo == kCGBitmapByteOrder32Big)
{
/* Big endian, for alpha-last we can use this bitmap directly in GL */
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
if (alphaInfo != kCGImageAlphaPremultipliedLast && alphaInfo != kCGImageAlphaLast &&
alphaInfo != kCGImageAlphaNoneSkipLast)
{
redrawNeeded = true;
} else
{
/* Can access directly using GL_RGBA pixel format */
format = GL_RGBA;
}
}
}
}
}
_imageSize = imageSize;
if (redrawNeeded)
{
imageData = (GLubyte *) calloc(1, (int)imageSize.width * (int)imageSize.height * 4);
CGColorSpaceRef genericRGBColorspace = CGColorSpaceCreateDeviceRGB();
CGContextRef imageContext = CGBitmapContextCreate(imageData, (size_t)imageSize.width, (size_t)imageSize.height, 8, (size_t)imageSize.width * 4, genericRGBColorspace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
CGSize imageDrawSize = CGSizeMake(imageSize.width, imageSize.height);
if (orientation == UIImageOrientationLeft || orientation == UIImageOrientationRight)
imageDrawSize = CGSizeMake(imageDrawSize.height, imageDrawSize.width);
CGAffineTransform transform = CGAffineTransformIdentity;
switch (orientation)
{
case UIImageOrientationDown:
case UIImageOrientationDownMirrored:
transform = CGAffineTransformTranslate(transform, imageSize.width, imageSize.height);
transform = CGAffineTransformRotate(transform, (CGFloat)M_PI);
break;
case UIImageOrientationLeft:
case UIImageOrientationLeftMirrored:
transform = CGAffineTransformTranslate(transform, imageSize.width, 0);
transform = CGAffineTransformRotate(transform, (CGFloat)M_PI_2);
break;
case UIImageOrientationRight:
case UIImageOrientationRightMirrored:
transform = CGAffineTransformTranslate(transform, 0, imageSize.height);
transform = CGAffineTransformRotate(transform, (CGFloat)-M_PI_2);
break;
default:
break;
}
switch (orientation)
{
case UIImageOrientationUpMirrored:
case UIImageOrientationDownMirrored:
transform = CGAffineTransformTranslate(transform, imageSize.width,0);
transform = CGAffineTransformScale(transform, -1, 1);
break;
case UIImageOrientationLeftMirrored:
case UIImageOrientationRightMirrored:
transform = CGAffineTransformTranslate(transform, imageSize.height, 0);
transform = CGAffineTransformScale(transform, -1, 1);
break;
default:
break;
}
CGContextConcatCTM(imageContext, transform);
CGContextSetInterpolationQuality(imageContext, kCGInterpolationHigh);
CGContextDrawImage(imageContext, CGRectMake(0.0f, 0.0f, imageDrawSize.width, imageDrawSize.height), image);
CGContextRelease(imageContext);
CGColorSpaceRelease(genericRGBColorspace);
}
else
{
dataFromImageDataProvider = CGDataProviderCopyData(CGImageGetDataProvider(image));
imageData = (GLubyte *)CFDataGetBytePtr(dataFromImageDataProvider);
}
runSynchronouslyOnVideoProcessingQueue(^
{
[GPUImageContext useImageProcessingContext];
outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:imageSize textureOptions:textureOptions onlyTexture:true];
[outputFramebuffer disableReferenceCounting];
glBindTexture(GL_TEXTURE_2D, [outputFramebuffer texture]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)imageSize.width, (int)imageSize.height, 0, format, GL_UNSIGNED_BYTE, imageData);
glBindTexture(GL_TEXTURE_2D, 0);
});
if (redrawNeeded)
free(imageData);
else if (dataFromImageDataProvider)
CFRelease(dataFromImageDataProvider);
}
- (void)dealloc
{
[outputFramebuffer enableReferenceCounting];
[outputFramebuffer unlock];
}
- (void)addTarget:(id<GPUImageInput>)newTarget atTextureLocation:(NSInteger)textureLocation
{
[super addTarget:newTarget atTextureLocation:textureLocation];
if (_processed)
{
[newTarget setInputSize:_imageSize atIndex:textureLocation];
[newTarget newFrameReadyAtTime:kCMTimeIndefinite atIndex:textureLocation];
}
}
- (void)removeAllTargets
{
[super removeAllTargets];
_processed = false;
}
- (bool)processSynchronous:(bool)synchronous completion:(void (^)(void))completion
{
_processed = true;
if (dispatch_semaphore_wait(_updateSemaphore, DISPATCH_TIME_NOW) != 0)
return false;
void (^block)(void) = ^
{
for (id<GPUImageInput> currentTarget in targets)
{
NSInteger indexOfObject = [targets indexOfObject:currentTarget];
NSInteger textureIndexOfTarget = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
[currentTarget setCurrentlyReceivingMonochromeInput:false];
[currentTarget setInputSize:_imageSize atIndex:textureIndexOfTarget];
[currentTarget setInputFramebuffer:outputFramebuffer atIndex:textureIndexOfTarget];
[currentTarget newFrameReadyAtTime:kCMTimeIndefinite atIndex:textureIndexOfTarget];
}
dispatch_semaphore_signal(_updateSemaphore);
if (completion != nil)
completion();
};
if (synchronous)
runSynchronouslyOnVideoProcessingQueue(block);
else
runAsynchronouslyOnVideoProcessingQueue(block);
return true;
}
@end
@@ -0,0 +1,29 @@
#import "GPUImageOutput.h"
typedef enum {
GPUPixelFormatBGRA = GL_BGRA,
GPUPixelFormatRGBA = GL_RGBA,
GPUPixelFormatRGB = GL_RGB
} GPUPixelFormat;
typedef enum {
GPUPixelTypeUByte = GL_UNSIGNED_BYTE,
GPUPixelTypeFloat = GL_FLOAT
} GPUPixelType;
@interface PGPhotoEditorRawDataInput : GPUImageOutput
- (instancetype)initWithBytes:(GLubyte *)bytesToUpload size:(CGSize)imageSize;
- (instancetype)initWithBytes:(GLubyte *)bytesToUpload size:(CGSize)imageSize pixelFormat:(GPUPixelFormat)pixelFormat;
- (instancetype)initWithBytes:(GLubyte *)bytesToUpload size:(CGSize)imageSize pixelFormat:(GPUPixelFormat)pixelFormat type:(GPUPixelType)pixelType;
@property (nonatomic, assign) GPUPixelFormat pixelFormat;
@property (nonatomic, assign) GPUPixelType pixelType;
- (void)updateDataWithBytes:(GLubyte *)bytesToUpload size:(CGSize)imageSize;
- (void)processData;
- (void)processDataForTimestamp:(CMTime)frameTime;
- (CGSize)outputImageSize;
- (void)invalidate;
@end
@@ -0,0 +1,155 @@
#import "PGPhotoEditorRawDataInput.h"
@interface PGPhotoEditorRawDataInput ()
{
CGSize _imageSize;
bool _processed;
dispatch_semaphore_t _updateSemaphore;
}
@end
@implementation PGPhotoEditorRawDataInput
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_updateSemaphore = dispatch_semaphore_create(1);
_processed = false;
}
return self;
}
- (void)invalidate
{
}
- (instancetype)initWithBytes:(GLubyte *)bytes size:(CGSize)size
{
return [self initWithBytes:bytes size:size pixelFormat:GPUPixelFormatBGRA type:GPUPixelTypeUByte];
}
- (instancetype)initWithBytes:(GLubyte *)bytes size:(CGSize)size pixelFormat:(GPUPixelFormat)pixelFormat
{
return [self initWithBytes:bytes size:size pixelFormat:pixelFormat type:GPUPixelTypeUByte];
}
- (instancetype)initWithBytes:(GLubyte *)bytes size:(CGSize)size pixelFormat:(GPUPixelFormat)pixelFormat type:(GPUPixelType)pixelType
{
self = [self init];
if (self != nil)
{
_imageSize = size;
self.pixelFormat = pixelFormat;
self.pixelType = pixelType;
if (bytes != NULL)
[self uploadBytes:bytes];
}
return self;
}
- (void)dealloc
{
[outputFramebuffer enableReferenceCounting];
[outputFramebuffer unlock];
}
- (void)addTarget:(id<GPUImageInput>)newTarget atTextureLocation:(NSInteger)textureLocation
{
[super addTarget:newTarget atTextureLocation:textureLocation];
if (_processed)
{
[newTarget setInputSize:_imageSize atIndex:textureLocation];
[newTarget setInputFramebuffer:outputFramebuffer atIndex:textureLocation];
[newTarget newFrameReadyAtTime:kCMTimeIndefinite atIndex:textureLocation];
}
}
- (void)removeAllTargets
{
[super removeAllTargets];
_processed = false;
}
- (void)uploadBytes:(GLubyte *)bytes
{
[GPUImageContext useImageProcessingContext];
if (outputFramebuffer != nil)
{
[outputFramebuffer enableReferenceCounting];
[outputFramebuffer unlock];
}
outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:_imageSize textureOptions:self.outputTextureOptions onlyTexture:true];
[outputFramebuffer disableReferenceCounting];
glBindTexture(GL_TEXTURE_2D, [outputFramebuffer texture]);
glTexImage2D(GL_TEXTURE_2D, 0, _pixelFormat==GPUPixelFormatRGB ? GL_RGB : GL_RGBA, (int)_imageSize.width, (int)_imageSize.height, 0, (GLint)_pixelFormat, (GLenum)_pixelType, bytes);
}
- (void)updateDataWithBytes:(GLubyte *)bytes size:(CGSize)size
{
_imageSize = size;
[self uploadBytes:bytes];
}
- (void)processData
{
_processed = true;
if (dispatch_semaphore_wait(_updateSemaphore, DISPATCH_TIME_NOW) != 0)
return;
runAsynchronouslyOnVideoProcessingQueue(^
{
CGSize pixelSizeOfImage = [self outputImageSize];
for (id<GPUImageInput> currentTarget in targets)
{
NSInteger indexOfObject = [targets indexOfObject:currentTarget];
NSInteger textureIndexOfTarget = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
[currentTarget setInputSize:pixelSizeOfImage atIndex:textureIndexOfTarget];
[currentTarget setInputFramebuffer:outputFramebuffer atIndex:textureIndexOfTarget];
[currentTarget newFrameReadyAtTime:kCMTimeInvalid atIndex:textureIndexOfTarget];
}
dispatch_semaphore_signal(_updateSemaphore);
});
}
- (void)processDataForTimestamp:(CMTime)frameTime
{
_processed = true;
if (dispatch_semaphore_wait(_updateSemaphore, DISPATCH_TIME_NOW) != 0)
return;
runAsynchronouslyOnVideoProcessingQueue(^
{
CGSize pixelSizeOfImage = [self outputImageSize];
for (id<GPUImageInput> currentTarget in targets)
{
NSInteger indexOfObject = [targets indexOfObject:currentTarget];
NSInteger textureIndexOfTarget = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
[currentTarget setInputSize:pixelSizeOfImage atIndex:textureIndexOfTarget];
[currentTarget newFrameReadyAtTime:frameTime atIndex:textureIndexOfTarget];
}
dispatch_semaphore_signal(_updateSemaphore);
});
}
- (CGSize)outputImageSize
{
return _imageSize;
}
@end
@@ -0,0 +1,33 @@
#import <Foundation/Foundation.h>
#import "GPUImageContext.h"
typedef struct
{
GLubyte red;
GLubyte green;
GLubyte blue;
GLubyte alpha;
} PGByteColorVector;
@protocol GPURawDataProcessor;
@interface PGPhotoEditorRawDataOutput : NSObject <GPUImageInput>
{
GPUImageRotationMode inputRotation;
bool outputBGRA;
}
@property (nonatomic, readonly) GLubyte *rawBytesForImage;
@property (nonatomic, copy) void(^newFrameAvailableBlock)(void);
@property (nonatomic, assign) bool enabled;
@property (nonatomic, assign) CGSize imageSize;
- (instancetype)initWithImageSize:(CGSize)newImageSize resultsInBGRAFormat:(bool)resultsInBGRAFormat;
- (PGByteColorVector)colorAtLocation:(CGPoint)locationInImage;
- (NSUInteger)bytesPerRowInOutput;
- (void)lockFramebufferForReading;
- (void)unlockFramebufferAfterReading;
@end
@@ -0,0 +1,293 @@
#import "PGPhotoEditorRawDataOutput.h"
#import "PGPhotoProcessPass.h"
#import "GPUImageContext.h"
#import "GLProgram.h"
#import "GPUImageFilter.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@interface PGPhotoEditorRawDataOutput ()
{
GPUImageFramebuffer *firstInputFramebuffer, *outputFramebuffer, *retainedFramebuffer;
bool hasReadFromTheCurrentFrame;
GLProgram *dataProgram;
GLint dataPositionAttribute, dataTextureCoordinateAttribute;
GLint dataInputTextureUniform;
GLubyte *_rawBytesForImage;
bool lockNextFramebuffer;
}
@end
@implementation PGPhotoEditorRawDataOutput
@synthesize rawBytesForImage = _rawBytesForImage;
@synthesize newFrameAvailableBlock = _newFrameAvailableBlock;
@synthesize enabled;
#pragma mark -
#pragma mark Initialization and teardown
- (instancetype)initWithImageSize:(CGSize)newImageSize resultsInBGRAFormat:(bool)resultsInBGRAFormat
{
if (!(self = [super init]))
{
return nil;
}
self.enabled = true;
lockNextFramebuffer = false;
outputBGRA = resultsInBGRAFormat;
_imageSize = newImageSize;
hasReadFromTheCurrentFrame = false;
_rawBytesForImage = NULL;
inputRotation = kGPUImageNoRotation;
[GPUImageContext useImageProcessingContext];
if ( (outputBGRA && ![GPUImageContext supportsFastTextureUpload]) || (!outputBGRA && [GPUImageContext supportsFastTextureUpload]) )
{
dataProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:kGPUImageVertexShaderString fragmentShaderString:PGPhotoEnhanceColorSwapShaderString];
}
else
{
dataProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:kGPUImageVertexShaderString fragmentShaderString:kGPUImagePassthroughFragmentShaderString];
}
if (!dataProgram.initialized)
{
[dataProgram addAttribute:@"position"];
[dataProgram addAttribute:@"inputTextureCoordinate"];
if (![dataProgram link])
{
NSString *progLog = [dataProgram programLog];
NSLog(@"Program link log: %@", progLog);
NSString *fragLog = [dataProgram fragmentShaderLog];
NSLog(@"Fragment shader compile log: %@", fragLog);
NSString *vertLog = [dataProgram vertexShaderLog];
NSLog(@"Vertex shader compile log: %@", vertLog);
dataProgram = nil;
NSAssert(NO, @"Filter shader link failed");
}
}
dataPositionAttribute = [dataProgram attributeIndex:@"position"];
dataTextureCoordinateAttribute = [dataProgram attributeIndex:@"inputTextureCoordinate"];
dataInputTextureUniform = [dataProgram uniformIndex:@"inputImageTexture"];
return self;
}
- (void)dealloc
{
if (_rawBytesForImage != NULL && (![GPUImageContext supportsFastTextureUpload]))
{
free(_rawBytesForImage);
_rawBytesForImage = NULL;
}
}
#pragma mark -
#pragma mark Data access
- (void)renderAtInternalSize
{
[GPUImageContext setActiveShaderProgram:dataProgram];
outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:_imageSize onlyTexture:false];
[outputFramebuffer activateFramebuffer];
if(lockNextFramebuffer)
{
retainedFramebuffer = outputFramebuffer;
[retainedFramebuffer lock];
[retainedFramebuffer lockForReading];
lockNextFramebuffer = NO;
}
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
static const GLfloat squareVertices[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
};
static const GLfloat textureCoordinates[] = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
};
glActiveTexture(GL_TEXTURE4);
glBindTexture(GL_TEXTURE_2D, [firstInputFramebuffer texture]);
glUniform1i(dataInputTextureUniform, 4);
glVertexAttribPointer(dataPositionAttribute, 2, GL_FLOAT, 0, 0, squareVertices);
glVertexAttribPointer(dataTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);
glEnableVertexAttribArray(dataPositionAttribute);
glEnableVertexAttribArray(dataTextureCoordinateAttribute);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[firstInputFramebuffer unlock];
}
- (PGByteColorVector)colorAtLocation:(CGPoint)locationInImage
{
PGByteColorVector *imageColorBytes = (PGByteColorVector *)self.rawBytesForImage;
CGPoint locationToPickFrom = CGPointZero;
locationToPickFrom.x = MIN(MAX(locationInImage.x, 0.0), (_imageSize.width - 1.0));
locationToPickFrom.y = MIN(MAX((_imageSize.height - locationInImage.y), 0.0), (_imageSize.height - 1.0));
if (outputBGRA)
{
PGByteColorVector flippedColor = imageColorBytes[(int)(round((locationToPickFrom.y * _imageSize.width) + locationToPickFrom.x))];
GLubyte temporaryRed = flippedColor.red;
flippedColor.red = flippedColor.blue;
flippedColor.blue = temporaryRed;
return flippedColor;
}
else
{
return imageColorBytes[(int)(round((locationToPickFrom.y * _imageSize.width) + locationToPickFrom.x))];
}
}
#pragma mark -
#pragma mark GPUImageInput protocol
- (void)newFrameReadyAtTime:(CMTime)__unused frameTime atIndex:(NSInteger)__unused textureIndex
{
hasReadFromTheCurrentFrame = NO;
if (_newFrameAvailableBlock != nil)
_newFrameAvailableBlock();
}
- (NSInteger)nextAvailableTextureIndex
{
return 0;
}
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)__unused textureIndex
{
firstInputFramebuffer = newInputFramebuffer;
[firstInputFramebuffer lock];
}
- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)__unused textureIndex
{
inputRotation = newInputRotation;
}
- (void)setInputSize:(CGSize)__unused newSize atIndex:(NSInteger)__unused textureIndex
{
}
- (CGSize)maximumOutputSize
{
return _imageSize;
}
- (void)endProcessing
{
}
- (BOOL)shouldIgnoreUpdatesToThisTarget
{
return NO;
}
- (BOOL)wantsMonochromeInput
{
return NO;
}
- (void)setCurrentlyReceivingMonochromeInput:(BOOL)__unused newValue
{
}
#pragma mark -
#pragma mark Accessors
- (GLubyte *)rawBytesForImage
{
if ((_rawBytesForImage == NULL) && (![GPUImageContext supportsFastTextureUpload]))
{
_rawBytesForImage = (GLubyte *) calloc((unsigned long)(_imageSize.width * _imageSize.height * 4), sizeof(GLubyte));
hasReadFromTheCurrentFrame = NO;
}
if (hasReadFromTheCurrentFrame)
{
return _rawBytesForImage;
}
else
{
runSynchronouslyOnVideoProcessingQueue(^
{
[GPUImageContext useImageProcessingContext];
[self renderAtInternalSize];
if ([GPUImageContext supportsFastTextureUpload])
{
glFinish();
_rawBytesForImage = [outputFramebuffer byteBuffer];
}
else
{
glReadPixels(0, 0, (GLsizei)(_imageSize.width), (GLsizei)_imageSize.height, GL_RGBA, GL_UNSIGNED_BYTE, _rawBytesForImage);
}
hasReadFromTheCurrentFrame = YES;
});
return _rawBytesForImage;
}
}
- (NSUInteger)bytesPerRowInOutput
{
return [retainedFramebuffer bytesPerRow];
}
- (void)setImageSize:(CGSize)newImageSize
{
_imageSize = newImageSize;
if (_rawBytesForImage != NULL && (![GPUImageContext supportsFastTextureUpload]))
{
free(_rawBytesForImage);
_rawBytesForImage = NULL;
}
}
- (void)lockFramebufferForReading
{
lockNextFramebuffer = YES;
}
- (void)unlockFramebufferAfterReading
{
[retainedFramebuffer unlockAfterReading];
[retainedFramebuffer unlock];
retainedFramebuffer = nil;
}
@end
#pragma clang diagnostic pop
@@ -0,0 +1,145 @@
#import <LegacyComponents/PGPhotoEditorValues.h>
#import <LegacyComponents/TGPaintingData.h>
#import <LegacyComponents/TGPhotoEditorUtils.h>
@implementation PGPhotoEditorValues
@synthesize originalSize = _originalSize;
@synthesize cropRect = _cropRect;
@synthesize cropOrientation = _cropOrientation;
@synthesize cropRotation = _cropRotation;
@synthesize cropLockedAspectRatio = _cropLockedAspectRatio;
@synthesize cropMirrored = _cropMirrored;
@synthesize paintingData = _paintingData;
@synthesize sendAsGif = _sendAsGif;
@synthesize toolValues = _toolValues;
+ (instancetype)editorValuesWithOriginalSize:(CGSize)originalSize cropRectangle:(PGRectangle *)cropRectangle cropOrientation:(UIImageOrientation)cropOrientation cropSize:(CGSize)cropSize enhanceDocument:(bool)enhanceDocument paintingData:(TGPaintingData *)paintingData
{
PGPhotoEditorValues *values = [[PGPhotoEditorValues alloc] init];
values->_originalSize = originalSize;
values->_cropRect = CGRectMake(0.0, 0.0, cropSize.width, cropSize.height);
values->_cropSize = cropSize;
values->_cropRectangle = cropRectangle;
values->_cropOrientation = cropOrientation;
values->_enhanceDocument = enhanceDocument;
values->_paintingData = paintingData;
return values;
}
+ (instancetype)editorValuesWithOriginalSize:(CGSize)originalSize cropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropLockedAspectRatio:(CGFloat)cropLockedAspectRatio cropMirrored:(bool)cropMirrored toolValues:(NSDictionary *)toolValues paintingData:(TGPaintingData *)paintingData sendAsGif:(bool)sendAsGif
{
PGPhotoEditorValues *values = [[PGPhotoEditorValues alloc] init];
values->_originalSize = originalSize;
values->_cropRect = cropRect;
values->_cropRotation = cropRotation;
values->_cropOrientation = cropOrientation;
values->_cropLockedAspectRatio = cropLockedAspectRatio;
values->_cropMirrored = cropMirrored;
values->_toolValues = toolValues;
values->_paintingData = paintingData;
values->_sendAsGif = sendAsGif;
return values;
}
- (bool)hasPainting
{
return (_paintingData != nil);
}
- (bool)cropAppliedForAvatar:(bool)forAvatar
{
if (_cropRectangle != nil)
return true;
CGRect defaultCropRect = CGRectMake(0, 0, _originalSize.width, _originalSize.height);
if (forAvatar)
{
CGFloat shortSide = MIN(_originalSize.width, _originalSize.height);
defaultCropRect = CGRectMake((_originalSize.width - shortSide) / 2, (_originalSize.height - shortSide) / 2, shortSide, shortSide);
}
if (!_CGRectEqualToRectWithEpsilon(self.cropRect, defaultCropRect, [self _cropRectEpsilon]))
return true;
if (ABS(self.cropRotation) > FLT_EPSILON)
return true;
if (self.cropOrientation != UIImageOrientationUp)
return true;
if (self.cropLockedAspectRatio > FLT_EPSILON)
return true;
if (self.cropMirrored)
return true;
return false;
}
- (bool)toolsApplied
{
if (_enhanceDocument)
return true;
if (self.toolValues.count > 0)
return true;
return false;
}
- (bool)isDefaultValuesForAvatar:(bool)forAvatar
{
return ![self cropAppliedForAvatar:forAvatar] && ![self toolsApplied] && ![self hasPainting];
}
- (bool)isCropEqualWith:(id<TGMediaEditAdjustments>)adjusments
{
return (_CGRectEqualToRectWithEpsilon(self.cropRect, adjusments.cropRect, [self _cropRectEpsilon]));
}
- (BOOL)isEqual:(id)object
{
if (object == self)
return true;
if (!object || ![object isKindOfClass:[self class]])
return false;
PGPhotoEditorValues *values = (PGPhotoEditorValues *)object;
if (!_CGRectEqualToRectWithEpsilon(self.cropRect, values.cropRect, [self _cropRectEpsilon]))
return false;
if (ABS(self.cropRotation - values.cropRotation) > FLT_EPSILON)
return false;
if (self.cropOrientation != values.cropOrientation)
return false;
if (ABS(self.cropLockedAspectRatio - values.cropLockedAspectRatio) > FLT_EPSILON)
return false;
if (self.cropMirrored != values.cropMirrored)
return false;
if (![self.toolValues isEqual:values.toolValues])
return false;
if (self.paintingData != values.paintingData && ![self.paintingData isEqual:values.paintingData])
return false;
if (self.enhanceDocument != values.enhanceDocument)
return false;
return true;
}
- (CGFloat)_cropRectEpsilon
{
return MAX(_originalSize.width, _originalSize.height) * 0.005f;
}
@end
@@ -0,0 +1,9 @@
#import <UIKit/UIKit.h>
#import "GPUImageContext.h"
@interface PGPhotoEditorView : UIView <GPUImageInput>
@property (nonatomic, assign) bool enabled;
@end
@@ -0,0 +1,423 @@
#import "PGPhotoEditorView.h"
#import "GPUImageContext.h"
#import "GPUImageFilter.h"
#import <OpenGLES/EAGLDrawable.h>
#import <QuartzCore/QuartzCore.h>
#import <AVFoundation/AVFoundation.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@interface PGPhotoEditorView ()
{
GPUImageRotationMode inputRotation;
CGSize _sizeInPixels;
GPUImageFramebuffer *inputFramebufferForDisplay;
GLuint displayRenderbuffer;
GLuint displayFramebuffer;
GLProgram *displayProgram;
GLint displayPositionAttribute;
GLint displayTextureCoordinateAttribute;
GLint displayInputTextureUniform;
CGSize inputImageSize;
GLfloat imageVertices[8];
GLfloat backgroundColorRed;
GLfloat backgroundColorGreen;
GLfloat backgroundColorBlue;
GLfloat backgroundColorAlpha;
CGSize boundsSizeAtFrameBufferEpoch;
}
@property (assign, nonatomic) NSUInteger aspectRatio;
@end
@implementation PGPhotoEditorView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
[self commonInit];
}
return self;
}
+ (Class)layerClass
{
return [CAEAGLLayer class];
}
- (void)commonInit
{
if ([self respondsToSelector:@selector(setContentScaleFactor:)])
self.contentScaleFactor = [[UIScreen mainScreen] scale];
self.enabled = true;
inputRotation = kGPUImageNoRotation;
self.opaque = true;
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
eaglLayer.opaque = true;
eaglLayer.drawableProperties = @{ kEAGLDrawablePropertyRetainedBacking: @NO,
kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8 };
runSynchronouslyOnVideoProcessingQueue(^
{
[GPUImageContext useImageProcessingContext];
displayProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:kGPUImageVertexShaderString fragmentShaderString:kGPUImagePassthroughFragmentShaderString];
if (!displayProgram.initialized)
{
[displayProgram addAttribute:@"position"];
[displayProgram addAttribute:@"inputTexCoord"];
if (![displayProgram link])
{
NSString *progLog = [displayProgram programLog];
NSLog(@"Program link log: %@", progLog);
NSString *fragLog = [displayProgram fragmentShaderLog];
NSLog(@"Fragment shader compile log: %@", fragLog);
NSString *vertLog = [displayProgram vertexShaderLog];
NSLog(@"Vertex shader compile log: %@", vertLog);
displayProgram = nil;
NSAssert(NO, @"Filter shader link failed");
}
}
displayPositionAttribute = [displayProgram attributeIndex:@"position"];
displayTextureCoordinateAttribute = [displayProgram attributeIndex:@"inputTexCoord"];
displayInputTextureUniform = [displayProgram uniformIndex:@"sourceImage"];
[GPUImageContext setActiveShaderProgram:displayProgram];
glEnableVertexAttribArray(displayPositionAttribute);
glEnableVertexAttribArray(displayTextureCoordinateAttribute);
[self setBackgroundColorRed:0.0 green:0.0 blue:0.0 alpha:1.0];
[self createDisplayFramebuffer];
});
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (!CGSizeEqualToSize(self.bounds.size, boundsSizeAtFrameBufferEpoch) &&
!CGSizeEqualToSize(self.bounds.size, CGSizeZero))
{
runSynchronouslyOnVideoProcessingQueue(^
{
[self destroyDisplayFramebuffer];
[self createDisplayFramebuffer];
[self recalculateViewGeometry];
});
}
}
- (void)dealloc
{
runSynchronouslyOnVideoProcessingQueue(^
{
[self destroyDisplayFramebuffer];
});
}
- (void)createDisplayFramebuffer
{
[GPUImageContext useImageProcessingContext];
glGenFramebuffers(1, &displayFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, displayFramebuffer);
glGenRenderbuffers(1, &displayRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, displayRenderbuffer);
[[[GPUImageContext sharedImageProcessingContext] context] renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];
GLint backingWidth, backingHeight;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);
if ( (backingWidth == 0) || (backingHeight == 0) )
{
[self destroyDisplayFramebuffer];
return;
}
_sizeInPixels.width = (CGFloat)backingWidth;
_sizeInPixels.height = (CGFloat)backingHeight;
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, displayRenderbuffer);
GLuint framebufferCreationStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
NSAssert(framebufferCreationStatus == GL_FRAMEBUFFER_COMPLETE, @"Failure with display framebuffer generation for display of size: %f, %f", self.bounds.size.width, self.bounds.size.height);
boundsSizeAtFrameBufferEpoch = self.bounds.size;
}
- (void)destroyDisplayFramebuffer
{
[GPUImageContext useImageProcessingContext];
if (displayFramebuffer)
{
glDeleteFramebuffers(1, &displayFramebuffer);
displayFramebuffer = 0;
}
if (displayRenderbuffer)
{
glDeleteRenderbuffers(1, &displayRenderbuffer);
displayRenderbuffer = 0;
}
}
- (void)setDisplayFramebuffer
{
if (!displayFramebuffer)
{
[self createDisplayFramebuffer];
}
glBindFramebuffer(GL_FRAMEBUFFER, displayFramebuffer);
glViewport(0, 0, (GLint)_sizeInPixels.width, (GLint)_sizeInPixels.height);
}
- (void)presentFramebuffer
{
glBindRenderbuffer(GL_RENDERBUFFER, displayRenderbuffer);
[[GPUImageContext sharedImageProcessingContext] presentBufferForDisplay];
}
#pragma mark -
#pragma mark Handling fill mode
- (void)recalculateViewGeometry
{
runSynchronouslyOnVideoProcessingQueue(^
{
CGFloat heightScaling, widthScaling;
widthScaling = 1.0;
heightScaling = 1.0;
imageVertices[0] = (GLfloat)-widthScaling;
imageVertices[1] = (GLfloat)-heightScaling;
imageVertices[2] = (GLfloat)widthScaling;
imageVertices[3] = (GLfloat)-heightScaling;
imageVertices[4] = (GLfloat)-widthScaling;
imageVertices[5] = (GLfloat)heightScaling;
imageVertices[6] = (GLfloat)widthScaling;
imageVertices[7] = (GLfloat)heightScaling;
});
}
- (void)setBackgroundColorRed:(GLfloat)redComponent green:(GLfloat)greenComponent blue:(GLfloat)blueComponent alpha:(GLfloat)alphaComponent
{
backgroundColorRed = redComponent;
backgroundColorGreen = greenComponent;
backgroundColorBlue = blueComponent;
backgroundColorAlpha = alphaComponent;
}
+ (const GLfloat *)textureCoordinatesForRotation:(GPUImageRotationMode)rotationMode
{
static const GLfloat noRotationTextureCoordinates[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
static const GLfloat rotateRightTextureCoordinates[] = {
1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 1.0f,
0.0f, 0.0f,
};
static const GLfloat rotateLeftTextureCoordinates[] = {
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f,
};
static const GLfloat verticalFlipTextureCoordinates[] = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
};
static const GLfloat horizontalFlipTextureCoordinates[] = {
1.0f, 1.0f,
0.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,
};
static const GLfloat rotateRightVerticalFlipTextureCoordinates[] = {
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 0.0f,
0.0f, 1.0f,
};
static const GLfloat rotateRightHorizontalFlipTextureCoordinates[] = {
1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 1.0f,
0.0f, 0.0f,
};
static const GLfloat rotate180TextureCoordinates[] = {
1.0f, 0.0f,
0.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f,
};
static const GLfloat rotate180HorizontalFlipTextureCoordinates[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
switch(rotationMode)
{
case kGPUImageNoRotation: return noRotationTextureCoordinates;
case kGPUImageRotateLeft: return rotateLeftTextureCoordinates;
case kGPUImageRotateRight: return rotateRightTextureCoordinates;
case kGPUImageFlipVertical: return verticalFlipTextureCoordinates;
case kGPUImageFlipHorizonal: return horizontalFlipTextureCoordinates;
case kGPUImageRotateRightFlipVertical: return rotateRightVerticalFlipTextureCoordinates;
case kGPUImageRotateRightFlipHorizontal: return rotateRightHorizontalFlipTextureCoordinates;
case kGPUImageRotate180: return rotate180TextureCoordinates;
case kGPUImageRotate180FlipHorizontal: return rotate180HorizontalFlipTextureCoordinates;
}
}
#pragma mark - Input
- (void)newFrameReadyAtTime:(CMTime)__unused frameTime atIndex:(NSInteger)__unused textureIndex
{
runSynchronouslyOnVideoProcessingQueue(^
{
[GPUImageContext setActiveShaderProgram:displayProgram];
[self setDisplayFramebuffer];
glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE4);
glBindTexture(GL_TEXTURE_2D, [inputFramebufferForDisplay texture]);
glUniform1i(displayInputTextureUniform, 4);
glVertexAttribPointer(displayPositionAttribute, 2, GL_FLOAT, 0, 0, imageVertices);
glVertexAttribPointer(displayTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, [PGPhotoEditorView textureCoordinatesForRotation:inputRotation]);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[self presentFramebuffer];
[inputFramebufferForDisplay unlock];
inputFramebufferForDisplay = nil;
});
}
- (NSInteger)nextAvailableTextureIndex
{
return 0;
}
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)__unused textureIndex
{
inputFramebufferForDisplay = newInputFramebuffer;
[inputFramebufferForDisplay lock];
}
- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)__unused textureIndex
{
inputRotation = newInputRotation;
}
- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)__unused textureIndex
{
runSynchronouslyOnVideoProcessingQueue(^{
CGSize rotatedSize = newSize;
if (GPUImageRotationSwapsWidthAndHeight(inputRotation))
{
rotatedSize.width = newSize.height;
rotatedSize.height = newSize.width;
}
if (!CGSizeEqualToSize(inputImageSize, rotatedSize))
{
inputImageSize = rotatedSize;
[self recalculateViewGeometry];
}
});
}
- (CGSize)maximumOutputSize
{
if ([self respondsToSelector:@selector(setContentScaleFactor:)])
{
CGSize pointSize = self.bounds.size;
return CGSizeMake(self.contentScaleFactor * pointSize.width, self.contentScaleFactor * pointSize.height);
}
else
{
return self.bounds.size;
}
}
- (void)endProcessing
{
}
- (BOOL)shouldIgnoreUpdatesToThisTarget
{
return NO;
}
- (BOOL)wantsMonochromeInput
{
return NO;
}
- (void)setCurrentlyReceivingMonochromeInput:(BOOL)__unused newValue
{
}
#pragma mark -
- (CGSize)sizeInPixels
{
if (CGSizeEqualToSize(_sizeInPixels, CGSizeZero))
{
return [self maximumOutputSize];
}
else
{
return _sizeInPixels;
}
}
@end
#pragma clang diagnostic pop
@@ -0,0 +1,13 @@
#import "GPUImageFilter.h"
typedef enum
{
PGPhotoEnhanceColorConversionRGBToHSVMode,
PGPhotoEnhanceColorConversionHSVToRGBMode
} PGPhotoEnhanceColorConversionMode;
@interface PGPhotoEnhanceColorConversionFilter : GPUImageFilter
- (instancetype)initWithMode:(PGPhotoEnhanceColorConversionMode)mode;
@end
@@ -0,0 +1,56 @@
#import "PGPhotoEnhanceColorConversionFilter.h"
#import "PGPhotoProcessPass.h"
NSString *const PGPhotoEnhanceRGBToHSVShaderString = PGShaderString
(
precision highp float;
varying vec2 texCoord;
uniform sampler2D sourceImage;
vec3 rgb_to_hsv(vec3 c) {
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy);
vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx);
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
void main() {
vec4 texel = texture2D(sourceImage, texCoord);
gl_FragColor = vec4(rgb_to_hsv(texel.rgb), texel.a);
}
);
NSString *const PGPhotoEnhanceHSVToRGBShaderString = PGShaderString
(
precision highp float;
varying vec2 texCoord;
uniform sampler2D sourceImage;
vec3 hsv_to_rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
vec4 texel = texture2D(sourceImage, texCoord);
gl_FragColor = vec4(hsv_to_rgb(texel.rgb), texel.a);
}
);
@implementation PGPhotoEnhanceColorConversionFilter
- (instancetype)initWithMode:(PGPhotoEnhanceColorConversionMode)mode
{
return [super initWithFragmentShaderFromString:(mode == PGPhotoEnhanceColorConversionRGBToHSVMode) ? PGPhotoEnhanceRGBToHSVShaderString : PGPhotoEnhanceHSVToRGBShaderString];
}
@end
@@ -0,0 +1,7 @@
#import "GPUImageTwoInputFilter.h"
@interface PGPhotoEnhanceInterpolationFilter : GPUImageTwoInputFilter
@property (nonatomic, assign) CGFloat intensity;
@end
@@ -0,0 +1,87 @@
#import "PGPhotoEnhanceInterpolationFilter.h"
#import "PGPhotoProcessPass.h"
NSString *const PGPhotoEnhanceToolInterpolationShaderString = PGShaderString
(
precision highp float;
varying vec2 texCoord;
uniform sampler2D sourceImage;
uniform sampler2D inputImageTexture2;
uniform float intensity;
vec3 hsv_to_rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
float enhance(float value) {
const vec2 offset = vec2(0.001953125, 0.03125); // vec2(0.5 / 256.0, 0.5 / 16.0)
value = value + offset.x;
vec2 coord = (clamp(texCoord, 0.125, 1.0 - 0.125001) - 0.125) * 4.0;
vec2 frac = fract(coord);
coord = floor(coord); // vec2(0..3, 0..3)
// 1.0 / 16.0 = 0.0625
float p00 = float(coord.y * 4.0 + coord.x) * 0.0625 + offset.y;
float p01 = float(coord.y * 4.0 + coord.x + 1.0) * 0.0625 + offset.y;
float p10 = float((coord.y + 1.0) * 4.0 + coord.x) * 0.0625 + offset.y;
float p11 = float((coord.y + 1.0) * 4.0 + coord.x + 1.0) * 0.0625 + offset.y;
vec3 c00 = texture2D(inputImageTexture2, vec2(value, p00)).rgb;
vec3 c01 = texture2D(inputImageTexture2, vec2(value, p01)).rgb;
vec3 c10 = texture2D(inputImageTexture2, vec2(value, p10)).rgb;
vec3 c11 = texture2D(inputImageTexture2, vec2(value, p11)).rgb;
// r - cdf, g - cdfMin, b - cdfMax
float c1 = ((c00.r - c00.g) / (c00.b - c00.g));
float c2 = ((c01.r - c01.g) / (c01.b - c01.g));
float c3 = ((c10.r - c10.g) / (c10.b - c10.g));
float c4 = ((c11.r - c11.g) / (c11.b - c11.g));
float c1_2 = mix(c1, c2, frac.x);
float c3_4 = mix(c3, c4, frac.x);
return mix(c1_2, c3_4, frac.y);
}
void main() {
vec4 texel = texture2D(sourceImage, texCoord);
vec4 hsv = texel;
hsv.y = min(1.0, hsv.y * 1.2);
hsv.z = min(1.0, enhance(hsv.z) * 1.1);
gl_FragColor = vec4(hsv_to_rgb(mix(texel.xyz, hsv.xyz, intensity)), texel.w);
}
);
@interface PGPhotoEnhanceInterpolationFilter ()
{
GLint _intensityUniform;
}
@end
@implementation PGPhotoEnhanceInterpolationFilter
@dynamic intensity;
- (instancetype)init
{
self = [self initWithFragmentShaderFromString:PGPhotoEnhanceToolInterpolationShaderString];
if (self != nil)
{
_intensityUniform = [filterProgram uniformIndex:@"intensity"];
}
return self;
}
- (void)setIntensity:(CGFloat)intensity
{
[self setFloat:(float)intensity forUniformName:@"intensity"];
}
@end
@@ -0,0 +1,11 @@
#import "GPUImageContext.h"
@interface PGPhotoEnhanceLUTGenerator : NSObject <GPUImageInput>
@property (nonatomic, copy) void(^lutDataReady)(GLubyte *data);
@property (nonatomic, assign) bool skip;
@end
extern const NSUInteger PGPhotoEnhanceHistogramBins;
extern const NSUInteger PGPhotoEnhanceSegments;
@@ -0,0 +1,342 @@
#import "PGPhotoEnhanceLUTGenerator.h"
#import "LegacyComponentsInternal.h"
#import "PGPhotoProcessPass.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
const NSUInteger PGPhotoEnhanceHistogramBins = 256;
const NSUInteger PGPhotoEnhanceSegments = 4;
@interface PGPhotoEnhanceLUTGenerator ()
{
CGFloat _clipLimit;
CGSize _imageSize;
GPUImageRotationMode _inputRotation;
GPUImageFramebuffer *_firstInputFramebuffer;
GPUImageFramebuffer *_outputFramebuffer;
GPUImageFramebuffer *_retainedFramebuffer;
GLProgram *_dataProgram;
GLint _dataPositionAttribute;
GLint _dataTextureCoordinateAttribute;
GLint _dataInputTextureUniform;
bool _hasReadCurrentFrame;
GLubyte *_rawBytesForImage;
bool _lockNextFramebuffer;
}
@property (nonatomic, assign) BOOL enabled;
@end
@implementation PGPhotoEnhanceLUTGenerator
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_clipLimit = 1.25f;
self.enabled = true;
_lockNextFramebuffer = false;
_inputRotation = kGPUImageNoRotation;
[GPUImageContext useImageProcessingContext];
if (([GPUImageContext supportsFastTextureUpload]))
{
_dataProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:kGPUImageVertexShaderString fragmentShaderString:PGPhotoEnhanceColorSwapShaderString];
}
else
{
_dataProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:kGPUImageVertexShaderString fragmentShaderString:kGPUImagePassthroughFragmentShaderString];
}
if (!_dataProgram.initialized)
{
[_dataProgram addAttribute:@"position"];
[_dataProgram addAttribute:@"inputTexCoord"];
if (![_dataProgram link])
{
NSString *progLog = [_dataProgram programLog];
NSLog(@"Program link log: %@", progLog);
NSString *fragLog = [_dataProgram fragmentShaderLog];
NSLog(@"Fragment shader compile log: %@", fragLog);
NSString *vertLog = [_dataProgram vertexShaderLog];
NSLog(@"Vertex shader compile log: %@", vertLog);
_dataProgram = nil;
NSAssert(NO, @"Filter shader link failed");
}
}
_dataPositionAttribute = [_dataProgram attributeIndex:@"position"];
_dataTextureCoordinateAttribute = [_dataProgram attributeIndex:@"inputTexCoord"];
_dataInputTextureUniform = [_dataProgram uniformIndex:@"sourceImage"];
}
return self;
}
- (void)dealloc
{
if (_rawBytesForImage != NULL && (![GPUImageContext supportsFastTextureUpload]))
{
free(_rawBytesForImage);
_rawBytesForImage = NULL;
}
}
#pragma mark - GPUImageInput
- (void)newFrameReadyAtTime:(CMTime)__unused frameTime atIndex:(NSInteger)__unused textureIndex
{
if (self.skip)
return;
_hasReadCurrentFrame = false;
_lockNextFramebuffer = true;
NSUInteger totalSegments = PGPhotoEnhanceSegments * PGPhotoEnhanceSegments;
CGSize tileSize = CGSizeMake(CGFloor(_imageSize.width / PGPhotoEnhanceSegments), CGFloor(_imageSize.height / PGPhotoEnhanceSegments));
NSUInteger tileArea = (NSUInteger)(tileSize.width * tileSize.height);
NSUInteger clipLimit = (NSUInteger)MAX(1, _clipLimit * tileArea / (CGFloat)PGPhotoEnhanceHistogramBins);
CGFloat scale = 255.0f / (CGFloat)tileArea;
GLubyte *bytes = [self _rawBytes];
NSUInteger bytesPerRow = [_retainedFramebuffer bytesPerRow];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-folding-constant"
NSUInteger hist[totalSegments][PGPhotoEnhanceHistogramBins];
NSUInteger cdfs[totalSegments][PGPhotoEnhanceHistogramBins];
NSUInteger cdfsMin[totalSegments];
NSUInteger cdfsMax[totalSegments];
#pragma clang diagnostic pop
memset(hist, 0, totalSegments * PGPhotoEnhanceHistogramBins * sizeof(NSUInteger));
memset(cdfs, 0, totalSegments * PGPhotoEnhanceHistogramBins * sizeof(NSUInteger));
memset(cdfsMin, 0, totalSegments * sizeof(NSUInteger));
memset(cdfsMax, 0, totalSegments * sizeof(NSUInteger));
CGFloat xMul = PGPhotoEnhanceSegments / _imageSize.width;
CGFloat yMul = PGPhotoEnhanceSegments / _imageSize.height;
for (NSUInteger y = 0; y < _imageSize.height; y++)
{
NSUInteger yOffset = y * bytesPerRow;
for (NSUInteger x = 0; x < _imageSize.width; x++)
{
NSUInteger index = x * 4 + yOffset;
NSUInteger tx = (NSUInteger)(x * xMul);
NSUInteger ty = (NSUInteger)(y * yMul);
NSUInteger t = ty * PGPhotoEnhanceSegments + tx;
GLubyte value = bytes[index + 2];
hist[t][value]++;
}
}
[_retainedFramebuffer unlockAfterReading];
[_retainedFramebuffer unlock];
_retainedFramebuffer = nil;
for (NSUInteger i = 0; i < totalSegments; i++)
{
if (clipLimit > 0)
{
NSUInteger clipped = 0;
for (NSUInteger j = 0; j < PGPhotoEnhanceHistogramBins; ++j)
{
if (hist[i][j] > clipLimit)
{
clipped += hist[i][j] - clipLimit;
hist[i][j] = clipLimit;
}
}
NSUInteger redistBatch = clipped / PGPhotoEnhanceHistogramBins;
NSUInteger residual = clipped - redistBatch * PGPhotoEnhanceHistogramBins;
for (NSUInteger j = 0; j < PGPhotoEnhanceHistogramBins; ++j)
hist[i][j] += redistBatch;
for (NSUInteger j = 0; j < residual; ++j)
hist[i][j]++;
}
memcpy(&cdfs[i], &hist[i], PGPhotoEnhanceHistogramBins * sizeof(NSUInteger));
NSUInteger hMin = PGPhotoEnhanceHistogramBins - 1;
for (NSUInteger j = 0; j < hMin; ++j)
{
if (cdfs[i][j] != 0)
hMin = j;
}
NSUInteger cdf = 0;
for (NSUInteger j = hMin; j < PGPhotoEnhanceHistogramBins; ++j)
{
cdf += cdfs[i][j];
cdfs[i][j] = (uint8_t)MIN(255, cdf * scale);
}
cdfsMin[i] = cdfs[i][hMin];
cdfsMax[i] = cdfs[i][PGPhotoEnhanceHistogramBins - 1];
}
NSUInteger resultSize = 4 * PGPhotoEnhanceHistogramBins * totalSegments;
NSUInteger resultBytesPerRow = 4 * PGPhotoEnhanceHistogramBins;
GLubyte *result = calloc(resultSize, sizeof(GLubyte));
for (NSUInteger tile = 0; tile < totalSegments; tile++)
{
NSUInteger yOffset = tile * resultBytesPerRow;
for (NSUInteger i = 0; i < PGPhotoEnhanceHistogramBins; i++)
{
NSUInteger index = i * 4 + yOffset;
result[index] = (uint8_t)cdfs[tile][i];
result[index + 1] = (uint8_t)cdfsMin[tile];
result[index + 2] = (uint8_t)cdfsMax[tile];
}
}
if (self.lutDataReady != nil)
self.lutDataReady(result);
}
- (NSInteger)nextAvailableTextureIndex
{
return 0;
}
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)__unused textureIndex
{
_firstInputFramebuffer = newInputFramebuffer;
[_firstInputFramebuffer lock];
}
- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)__unused textureIndex
{
_inputRotation = newInputRotation;
}
- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)__unused textureIndex
{
_imageSize = newSize;
}
- (CGSize)maximumOutputSize
{
return CGSizeMake(PGPhotoEnhanceHistogramBins, PGPhotoEnhanceSegments * PGPhotoEnhanceSegments);
}
- (void)endProcessing
{
}
- (BOOL)shouldIgnoreUpdatesToThisTarget
{
return false;
}
- (BOOL)wantsMonochromeInput
{
return false;
}
- (void)setCurrentlyReceivingMonochromeInput:(BOOL)__unused newValue
{
}
- (void)_render
{
[GPUImageContext setActiveShaderProgram:_dataProgram];
_outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:_imageSize onlyTexture:false];
[_outputFramebuffer activateFramebuffer];
if(_lockNextFramebuffer)
{
_retainedFramebuffer = _outputFramebuffer;
[_retainedFramebuffer lock];
[_retainedFramebuffer lockForReading];
_lockNextFramebuffer = false;
}
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
static const GLfloat squareVertices[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
};
static const GLfloat textureCoordinates[] = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
};
glActiveTexture(GL_TEXTURE4);
glBindTexture(GL_TEXTURE_2D, [_firstInputFramebuffer texture]);
glUniform1i(_dataInputTextureUniform, 4);
glVertexAttribPointer(_dataPositionAttribute, 2, GL_FLOAT, 0, 0, squareVertices);
glVertexAttribPointer(_dataTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);
glEnableVertexAttribArray(_dataPositionAttribute);
glEnableVertexAttribArray(_dataTextureCoordinateAttribute);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[_firstInputFramebuffer unlock];
}
- (GLubyte *)_rawBytes
{
if ((_rawBytesForImage == NULL) && (![GPUImageContext supportsFastTextureUpload]))
{
_rawBytesForImage = (GLubyte *)calloc((NSInteger)(_imageSize.width * _imageSize.height) * 4, sizeof(GLubyte));
_hasReadCurrentFrame = false;
}
if (!_hasReadCurrentFrame)
{
runSynchronouslyOnVideoProcessingQueue(^
{
[GPUImageContext useImageProcessingContext];
[self _render];
if ([GPUImageContext supportsFastTextureUpload])
{
glFinish();
_rawBytesForImage = [_outputFramebuffer byteBuffer];
}
else
{
glReadPixels(0, 0, (GLsizei)_imageSize.width, (GLsizei)_imageSize.height, GL_RGBA, GL_UNSIGNED_BYTE, _rawBytesForImage);
}
_hasReadCurrentFrame = true;
});
}
return _rawBytesForImage;
}
@end
#pragma clang diagnostic pop

Some files were not shown because too many files have changed in this diff Show More