-
Notifications
You must be signed in to change notification settings - Fork 401
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow image scaling on the mjepg stream #138
Changes from 21 commits
b7e7f69
1680edb
037cf0a
7d2c654
460b398
dc4e8fe
05b6fb0
77dca7b
8bfc49a
47b2884
fdae9b4
22bac0b
78a4299
eec117c
c4b9a8d
6b65907
2c36d35
c2dcdf4
d9043ed
97ab4d2
e30ff85
6308603
e94b474
cb47679
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/** | ||
* Copyright (c) 2015-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
*/ | ||
|
||
#import <Foundation/Foundation.h> | ||
#import <CoreGraphics/CoreGraphics.h> | ||
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
/** | ||
Scales images and compresses it to JPEG using Image I/O | ||
It allows to enqueue only a single screenshot. If a new one arrives before the currently queued gets discared | ||
*/ | ||
@interface FBImageIOScaler : NSObject | ||
|
||
/** | ||
Puts the passed image on the queue and dispatches a scaling operation. If there is already a image on the | ||
queue it will be replaced with the new one | ||
@param image The image to scale down | ||
@param completionHandler called after successfully scaling down an image | ||
@param scalingFactor the scaling factor in range 0.01..1.0. A value of 1.0 won't perform scaling at all | ||
@param compressionQuality the compression quality in range 0.0..1.0 (0.0 for max. compression and 1.0 for lossless compression) | ||
*/ | ||
- (void)submitImage:(NSData *)image scalingFactor:(CGFloat)scalingFactor compressionQuality:(CGFloat)compressionQuality completionHandler:(void (^)(NSData *))completionHandler; | ||
|
||
@end | ||
|
||
NS_ASSUME_NONNULL_END |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
/** | ||
* Copyright (c) 2015-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
*/ | ||
|
||
#import "FBImageIOScaler.h" | ||
#import <ImageIO/ImageIO.h> | ||
#import <MobileCoreServices/MobileCoreServices.h> | ||
#import "FBLogger.h" | ||
|
||
static const CGFloat FBMinScalingFactor = 0.01f; | ||
static const CGFloat FBMaxScalingFactor = 1.0f; | ||
static const CGFloat FBMinCompressionQuality = 0.0f; | ||
static const CGFloat FBMaxCompressionQuality = 1.0f; | ||
|
||
@interface FBImageIOScaler () | ||
|
||
@property (nonatomic) NSData *nextImage; | ||
@property (nonatomic, readonly) NSLock *nextImageLock; | ||
@property (nonatomic, readonly) dispatch_queue_t scalingQueue; | ||
|
||
@end | ||
|
||
@implementation FBImageIOScaler | ||
|
||
- (id)init | ||
{ | ||
self = [super init]; | ||
if (self) { | ||
_nextImageLock = [[NSLock alloc] init]; | ||
_scalingQueue = dispatch_queue_create("image.scaling.queue", NULL); | ||
} | ||
return self; | ||
} | ||
|
||
- (void)submitImage:(NSData *)image scalingFactor:(CGFloat)scalingFactor compressionQuality:(CGFloat)compressionQuality completionHandler:(void (^)(NSData *))completionHandler { | ||
[self.nextImageLock lock]; | ||
if (self.nextImage != nil) { | ||
[FBLogger verboseLog:@"Discarding screenshot"]; | ||
} | ||
scalingFactor = MAX(FBMinScalingFactor, MIN(FBMaxScalingFactor, scalingFactor)); | ||
compressionQuality = MAX(FBMinCompressionQuality, MIN(FBMaxCompressionQuality, compressionQuality)); | ||
self.nextImage = image; | ||
[self.nextImageLock unlock]; | ||
|
||
dispatch_async(self.scalingQueue, ^{ | ||
[self.nextImageLock lock]; | ||
NSData *next = self.nextImage; | ||
self.nextImage = nil; | ||
[self.nextImageLock unlock]; | ||
if (next == nil) { | ||
return; | ||
} | ||
NSData *scaled = [self scaledImageWithImage:next | ||
scalingFactor:scalingFactor | ||
compressionQuality:compressionQuality]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need to recompress the original image after it has been already compressed by XCTest? Wouldn't this be a waste of resources? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, otherwise the size keeps roughly the same. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the additional setting for compression quality and now we use the same value for screenshot taking and when we create the jpeg after scaling |
||
if (scaled == nil) { | ||
[FBLogger log:@"Could not scale down the image"]; | ||
return; | ||
} | ||
completionHandler(scaled); | ||
}); | ||
} | ||
|
||
- (nullable NSData *)scaledImageWithImage:(NSData *)image scalingFactor:(CGFloat)scalingFactor compressionQuality:(CGFloat)compressionQuality { | ||
CGImageSourceRef imageData = CGImageSourceCreateWithData((CFDataRef)image, nil); | ||
|
||
CGSize size = [FBImageIOScaler imageSizeWithImage:imageData]; | ||
CGFloat scaledMaxPixelSize = MAX(size.width, size.height) * scalingFactor; | ||
|
||
CFDictionaryRef params = (__bridge CFDictionaryRef)@{ | ||
(const NSString *)kCGImageSourceCreateThumbnailWithTransform: @(YES), | ||
(const NSString *)kCGImageSourceCreateThumbnailFromImageIfAbsent: @(YES), | ||
(const NSString *)kCGImageSourceThumbnailMaxPixelSize: @(scaledMaxPixelSize) | ||
}; | ||
|
||
CGImageRef scaled = CGImageSourceCreateThumbnailAtIndex(imageData, 0, params); | ||
if (scaled == nil) { | ||
[FBLogger log:@"Failed to scale the image"]; | ||
CFRelease(imageData); | ||
return nil; | ||
} | ||
NSData *jpegData = [self jpegDataWithImage:scaled | ||
compressionQuality:compressionQuality]; | ||
CFRelease(scaled); | ||
mykola-mokhnach marked this conversation as resolved.
Show resolved
Hide resolved
|
||
CFRelease(imageData); | ||
return jpegData; | ||
} | ||
|
||
- (nullable NSData *)jpegDataWithImage:(CGImageRef)imageRef compressionQuality:(CGFloat)compressionQuality | ||
{ | ||
NSMutableData *newImageData = [NSMutableData data]; | ||
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((CFMutableDataRef)newImageData, kUTTypeJPEG, 1, NULL); | ||
|
||
CFDictionaryRef compressionOptions = (__bridge CFDictionaryRef)@{ | ||
(const NSString *)kCGImageDestinationLossyCompressionQuality: @(compressionQuality) | ||
}; | ||
|
||
CGImageDestinationAddImage(imageDestination, imageRef, compressionOptions); | ||
if(!CGImageDestinationFinalize(imageDestination)) { | ||
[FBLogger log:@"Failed to write the image"]; | ||
newImageData = nil; | ||
} | ||
CFRelease(imageDestination); | ||
return newImageData; | ||
} | ||
|
||
+ (CGSize)imageSizeWithImage:(CGImageSourceRef)imageSource | ||
{ | ||
NSDictionary *options = @{ | ||
(const NSString *)kCGImageSourceShouldCache: @(NO) | ||
}; | ||
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (CFDictionaryRef)options); | ||
|
||
NSNumber *width = [(__bridge NSDictionary *)properties objectForKey:(const NSString *)kCGImagePropertyPixelWidth]; | ||
NSNumber *height = [(__bridge NSDictionary *)properties objectForKey:(const NSString *)kCGImagePropertyPixelHeight]; | ||
|
||
CGSize size = CGSizeMake([width floatValue], [height floatValue]); | ||
CFRelease(properties); | ||
return size; | ||
} | ||
|
||
@end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add this setting value in https://github.com/appium/appium/blob/f60b12cae069de3a306b26da622cee0dbbee7d7e/docs/en/advanced-concepts/settings.md#xcuitest after merging this?