BracketStripes/StripedImage.m
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Implements a composite image constructed of CMSampleBuffer stripes. |
*/ |
@import ImageIO; |
#import "StripedImage.h" |
@implementation StripedImage { |
// Size of the rendered striped image |
CGSize _imageSize; |
// Size of a stripe |
CGSize _stripeSize; |
// Number of stripes before they repeat in the rendered image |
NSUInteger _stride; |
// Current stripe index |
int _stripeIndex; |
// Bitmap context we render into |
CGContextRef _renderContext; |
} |
- (void)_prepareImageOfSize:(CGSize)size |
{ |
const size_t bitsPerComponent = 8; |
const size_t width = (size_t)size.width; |
const size_t paddedWidth = (width + 15) & ~15; |
const size_t bytesPerPixel = 4; |
const size_t bytesPerRow = paddedWidth * bytesPerPixel; |
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); |
_renderContext = CGBitmapContextCreate(NULL, size.width, size.height, bitsPerComponent, bytesPerRow, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedFirst); |
CGColorSpaceRelease(colorSpace); |
} |
- (CGImageRef)_createImageFromSampleBuffer:(CMSampleBufferRef)sampleBuffer |
{ |
CGImageRef image = NULL; |
CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer); |
const FourCharCode subType = CMFormatDescriptionGetMediaSubType(formatDescription); |
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer); |
if (blockBuffer) { |
NSAssert(subType == kCMVideoCodecType_JPEG, @"Block buffer must be JPEG encoded."); |
// Sample buffer is a JPEG compressed image |
size_t lengthAtOffset; |
size_t length; |
char *jpegBytes; |
if ( (CMBlockBufferGetDataPointer(blockBuffer, 0, &lengthAtOffset, &length, &jpegBytes) == kCMBlockBufferNoErr) && |
(lengthAtOffset == length) ) { |
NSData *jpegData = [NSData dataWithBytes:jpegBytes length:length]; |
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)jpegData, NULL); |
NSDictionary *decodeOptions = @{ |
(id)kCGImageSourceShouldAllowFloat: @NO, |
(id)kCGImageSourceShouldCache: @NO, |
}; |
image = CGImageSourceCreateImageAtIndex(imageSource, 0, (__bridge CFDictionaryRef)decodeOptions); |
CFRelease(imageSource); |
} |
} |
else { |
NSAssert(subType == kCVPixelFormatType_32BGRA, @"Image buffer must be BGRA encoded."); |
// Sample buffer is a BGRA uncompressed image |
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); |
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); |
CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly); |
void *baseAddress = (void *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0); |
const size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); |
const size_t bitsPerComponent = 8; |
const size_t width = CVPixelBufferGetWidth(imageBuffer); |
const size_t height = CVPixelBufferGetHeight(imageBuffer); |
CGContextRef bitmapContext = CGBitmapContextCreate(baseAddress, width, height, bitsPerComponent, bytesPerRow, colorSpace, (CGBitmapInfo)(kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst)); |
image = CGBitmapContextCreateImage(bitmapContext); |
CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly); |
CGContextRelease(bitmapContext); |
CGColorSpaceRelease(colorSpace); |
} |
return image; |
} |
- (instancetype)initForSize:(CGSize)size stripWidth:(CGFloat)stripWidth stride:(NSUInteger)stride |
{ |
self = [super init]; |
if (self) { |
_imageSize = size; |
_stride = stride; |
_stripeSize = CGSizeMake( |
stripWidth, |
size.height |
); |
[self _prepareImageOfSize:size]; |
} |
return self; |
} |
- (void)dealloc |
{ |
if (_renderContext) { |
CGContextRelease(_renderContext); |
} |
} |
- (UIImage *)imageWithOrientation:(UIImageOrientation)orientation |
{ |
const CGFloat scale = [[UIScreen mainScreen] scale]; |
CGImageRef cgImage = CGBitmapContextCreateImage(_renderContext); |
UIImage *image = [UIImage imageWithCGImage:cgImage scale:scale orientation:orientation]; |
CGImageRelease(cgImage); |
return image; |
} |
- (void)addSampleBuffer:(CMSampleBufferRef)sampleBuffer |
{ |
NSDate *renderStartTime = [NSDate date]; |
CGImageRef image = [self _createImageFromSampleBuffer:sampleBuffer]; |
const CGRect imageRect = CGRectMake( |
0, 0, |
CGImageGetWidth(image), CGImageGetHeight(image) |
); |
NSMutableArray *maskRects = [[NSMutableArray alloc] init]; |
CGRect maskRect = CGRectMake( |
_stripeSize.width * _stripeIndex, 0, |
_stripeSize.width, _stripeSize.height |
); |
// Scan the input sample buffer across the rendered image until we can't squeeze in any more... |
while (maskRect.origin.x < _imageSize.width) { |
[maskRects addObject:[NSValue valueWithCGRect:maskRect]]; |
// Move the mask to the right |
maskRect.origin.x += _stripeSize.width * _stride; |
} |
// Convert maskRects NSMutableArray to something Core Graphics can use |
const int maskCount = (int)[maskRects count]; |
CGRect *masks = malloc(sizeof(CGRect)*maskCount); |
for (int index = 0; index < maskCount; ++index) { |
masks[index] = [maskRects[index] CGRectValue]; |
} |
// Perform the render |
CGContextSaveGState(_renderContext); |
CGContextClipToRects(_renderContext, masks, maskCount); |
CGContextDrawImage(_renderContext, imageRect, image); |
CGContextRestoreGState(_renderContext); |
free(masks); |
CGImageRelease(image); |
const NSTimeInterval renderDuration = [[NSDate date] timeIntervalSinceDate:renderStartTime]; |
NSLog(@"Render time for contributor %d: %.3f msec", _stripeIndex, renderDuration * 1e3); |
// Move to the next stripe, allowing wrapping |
_stripeIndex = (_stripeIndex + 1) % _stride; |
} |
@end |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-28