Sources/IOSurface2D.mm
/* |
Copyright (C) 2015 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Utility class for creating a 2D I/O surface with immutable properties. |
*/ |
#import <CoreVideo/CVPixelBuffer.h> |
#import "CGImageCopy.h" |
#import "NSBitmap.h" |
#import "IOSurface2D.h" |
enum : long |
{ |
kCVPixelFormatType_24RGB_BPP = 3, |
kCVPixelFormatType_32RGBA_BPP = 4, |
kCVPixelFormatType_48RGB_BPP = 6, |
kCVPixelFormatType_64RGBAHalf_BPP = 8 |
}; |
static const size_t kIOSurface2DCount = 6; |
static const void* kIOSurface2DKeys[kIOSurface2DCount] = |
{ |
kIOSurfaceWidth, |
kIOSurfaceHeight, |
kIOSurfacePixelFormat, |
kIOSurfaceBytesPerElement, |
kIOSurfaceBytesPerRow, |
kIOSurfaceAllocSize |
}; |
#define CGBitmapIsRGBA(bpp,bpc) (((bpp / bpc) % 2) == 0) |
#define CGBitmapCheckBPP(bpp) ((bpp == 24) || (bpp == 32) || (bpp == 48) || (bpp == 64)) |
#define CGBitmapHasAlpha(bpp) ((bpp == 32) || (bpp == 64)) |
#define CGBitmapIs8BPC(bpp) ((bpp == 24) || (bpp == 32)) |
#define CGBitmapGetChannels(bpp) (((bpp == 24) || (bpp == 48)) ? 3 : 4) |
#define CGBitmapGetByteOrder(bpp) (CGBitmapIs8BPC(bpp) ? kCGBitmapByteOrder32Host : kCGBitmapByteOrder16Host) |
@implementation IOSurface2D |
{ |
@private |
BOOL _isRGBA; |
BOOL _avoidSync; |
size_t _bytesPerPixel; |
size_t _width; |
size_t _height; |
size_t _samplesPerPixel; |
size_t _bitsPerComponent; |
size_t _bitsPerPixel; |
size_t _bytesPerRow; |
size_t _size; |
uint32_t _byteOrder; |
uint32_t _bitmapInfo; |
OSType _format; |
IOSurfaceRef _surface; |
CGImageAlphaInfo _alphaInfo; |
BOOL isFinalized; |
uint32_t seed; |
IOSurfaceLockOptions options; |
} |
//----------------------------------------------------------------------------- |
// |
// FIXME: Replace with the correct values once deep images are fully supported! |
// |
//----------------------------------------------------------------------------- |
- (void) _initSurfaceRGBA |
{ |
_bitsPerComponent = _bitsPerPixel / CGBitmapGetChannels(_bitsPerPixel); |
_isRGBA = CGBitmapIsRGBA(_bitsPerPixel, _bitsPerComponent); |
if(!_isRGBA) |
{ |
_bitsPerPixel = 4 * _bitsPerComponent; |
} // if |
} // _initSurfaceRGBA |
- (void) _initSurfaceInfo |
{ |
if(!_isRGBA) |
{ |
_bitsPerPixel = 4 * _bitsPerComponent; |
_alphaInfo = kCGImageAlphaNoneSkipLast; |
} // if |
else |
{ |
_alphaInfo = CGBitmapHasAlpha(_bitsPerPixel) |
? kCGImageAlphaPremultipliedLast |
: kCGImageAlphaNone; |
} // else |
_byteOrder = CGBitmapGetByteOrder(_bitsPerPixel); |
_bitmapInfo = _alphaInfo | _byteOrder; |
} // _initSurfaceInfo |
- (void) _initSurfaceSize |
{ |
_samplesPerPixel = _bitsPerPixel / _bitsPerComponent; |
_bytesPerPixel = _bitsPerPixel / 8; |
_bytesPerRow = _width * _bytesPerPixel; |
_size = _height * _bytesPerRow; |
} // _initSurfaceSize |
- (void) _initSurfaceType |
{ |
switch(_bytesPerPixel) |
{ |
case kCVPixelFormatType_24RGB_BPP: |
_format = kCVPixelFormatType_24RGB; |
break; |
case kCVPixelFormatType_48RGB_BPP: |
_format = kCVPixelFormatType_48RGB; |
break; |
case kCVPixelFormatType_64RGBAHalf_BPP: |
_format = kCVPixelFormatType_64RGBAHalf; |
break; |
default: |
case kCVPixelFormatType_32RGBA_BPP: |
_format = kCVPixelFormatType_32RGBA; |
break; |
} // switch |
} // _initSurfaceType |
- (BOOL) _initSurfaceProperties |
{ |
BOOL success = CGBitmapCheckBPP(_bitsPerPixel); |
if(success) |
{ |
[self _initSurfaceRGBA]; |
[self _initSurfaceInfo]; |
[self _initSurfaceSize]; |
[self _initSurfaceType]; |
} // if |
return success; |
} // _initSurfaceProperties |
- (BOOL) _initSurfaceProperties:(nullable CGImageRef)pImage |
{ |
size_t nBPP = CGImageGetBitsPerPixel(pImage); |
BOOL success = CGBitmapCheckBPP(nBPP); |
if(success) |
{ |
_bitsPerPixel = nBPP; |
_bitsPerComponent = CGImageGetBitsPerComponent(pImage); |
_isRGBA = CGBitmapIsRGBA(_bitsPerPixel, _bitsPerComponent); |
if(!_isRGBA) |
{ |
_bitsPerPixel = 4 * _bitsPerComponent; |
_alphaInfo = kCGImageAlphaNoneSkipLast; |
_byteOrder = CGBitmapGetByteOrder(_bitsPerPixel); |
_bitmapInfo = _alphaInfo | _byteOrder; |
} // if |
else |
{ |
_alphaInfo = CGImageGetAlphaInfo(pImage); |
_bitmapInfo = CGImageGetBitmapInfo(pImage); |
_byteOrder = _bitmapInfo & kCGBitmapByteOrderMask; |
} // else |
_width = CGImageGetWidth(pImage); |
_height = CGImageGetHeight(pImage); |
[self _initSurfaceSize]; |
[self _initSurfaceType]; |
} // if |
return success; |
} // _initSurfaceProperties |
- (nullable IOSurfaceRef) _newSurface |
{ |
IOSurfaceRef surface = nullptr; |
const void* values[kIOSurface2DCount]; |
values[0] = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &_width); |
values[1] = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &_height); |
values[2] = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &_format); |
values[3] = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &_bytesPerPixel); |
values[4] = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &_bytesPerRow); |
values[5] = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &_size); |
CFDictionaryRef properties = CFDictionaryCreate(kCFAllocatorDefault, |
kIOSurface2DKeys, |
values, |
kIOSurface2DCount, |
&kCFTypeDictionaryKeyCallBacks, |
&kCFTypeDictionaryValueCallBacks); |
if(properties != nullptr) |
{ |
surface = IOSurfaceCreate(properties); |
CFRelease(properties); |
} // if |
size_t i; |
for(i = 0; i < kIOSurface2DCount; ++i) |
{ |
if(values[i] != nullptr) |
{ |
CFRelease(values[i]); |
} // if |
} // for |
return surface; |
} // _newSurface |
- (BOOL) _newSurface:(nullable CGImageRef)pImage |
{ |
isFinalized = pImage != nullptr; |
if(isFinalized) |
{ |
isFinalized = [self _initSurfaceProperties:pImage]; |
if(isFinalized) |
{ |
_surface = [self _newSurface]; |
isFinalized = _surface != nullptr; |
} // if |
} // if |
return isFinalized; |
} // _newSurface |
- (BOOL) _drawImage:(nullable CGImageRef)pImage |
{ |
BOOL success = [self _newSurface:pImage]; |
if(success) |
{ |
CGColorSpaceRef pColorSpace = CGColorSpaceCreateDeviceRGB(); |
if(pColorSpace != nullptr) |
{ |
IOReturn result = IOSurfaceLock(_surface, 0, &seed); |
success = result == kIOReturnSuccess; |
if(success) |
{ |
void* pBaseAddr = IOSurfaceGetBaseAddressOfPlane(_surface, 0); |
if(pBaseAddr != nullptr) |
{ |
CGContextRef pContext = CGBitmapContextCreate(pBaseAddr, |
_width, |
_height, |
_bitsPerComponent, |
_bytesPerRow, |
pColorSpace, |
_bitmapInfo); |
if(pContext != nullptr) |
{ |
CGRect bounds = CGRectMake(0, 0, _width, _height); |
CGContextDrawImage(pContext, bounds, pImage); |
CFRelease(pContext); |
} // if |
} // if |
IOSurfaceUnlock(_surface, 0, &seed); |
} // if |
CFRelease(pColorSpace); |
} // if |
} // if |
return success; |
} // _drawImage |
- (BOOL) _initSurfaceWithImageSource:(nullable CGImageSourceRef)pImageSource |
{ |
BOOL success = pImageSource != nullptr; |
if(success) |
{ |
CGImageRef pImage = CGImageSourceCreateImageAtIndex(pImageSource, |
0, |
NULL); |
success = pImage != nullptr; |
if(success) |
{ |
success = [self _drawImage:pImage]; |
CFRelease(pImage); |
} // if |
} // if |
return success; |
} // _initSurfaceWithImageSource |
- (BOOL) _initSurfaceWithImage:(nullable NSImage *)image |
{ |
BOOL success = NO; |
if(image) |
{ |
NSBitmapImageRep* bitmap = [NSBitmap bitmapWithImage:image].bitmap; |
if(bitmap) |
{ |
success = !bitmap.isPlanar; |
if(success) |
{ |
success = [self _drawImage:bitmap.CGImage]; |
} // if |
} // if |
} // if |
return success; |
} // _initSurfaceWithImage |
- (void) _initSurfaceWithURL:(nullable NSURL *)url |
{ |
if(url) |
{ |
// Create an image source from the URL. |
CGImageSourceRef pImageSource = CGImageSourceCreateWithURL(CFURLRef(url), nullptr); |
if(pImageSource != nullptr) |
{ |
[self _initSurfaceWithImageSource:pImageSource]; |
CFRelease(pImageSource); |
} // if |
} // if |
} // _initSurfaceWithFile |
- (void) _initSurfaceWithFile:(nullable NSString *)path |
{ |
if(path) |
{ |
// Load the source image |
NSURL* url = [NSURL fileURLWithPath:path]; |
if(url) |
{ |
[self _initSurfaceWithURL:url]; |
} // if |
} // if |
} // _initSurfaceWithFile |
- (void) _initIOSurface2DWithResource:(nullable NSString *)name |
{ |
if(name) |
{ |
// Acquire the absolute pathname to the image resource in application's bundle |
NSBundle* bundle = [NSBundle mainBundle]; |
NSString* resource = [bundle resourcePath]; |
NSString* path = [NSString stringWithFormat:@"%@/%@", resource, name]; |
[self _initSurfaceWithFile:path]; |
} // if |
} // initTextureWithResource |
// Initialize instance variables |
- (void) _initialize |
{ |
_bytesPerPixel = 0; |
_samplesPerPixel = 0; |
_bytesPerRow = 0; |
_size = 0; |
_bitmapInfo = 0; |
_byteOrder = 0; |
_format = 0; |
_width = 1920; |
_height = 1080; |
_bitsPerPixel = 32; |
_bitsPerComponent = 8; |
_alphaInfo = kCGImageAlphaPremultipliedLast; |
_surface = nullptr; |
_avoidSync = YES; |
_isRGBA = YES; |
isFinalized = NO; |
seed = 0; |
options = 0; |
} // _initialize |
// Default initializer to create a 2D I/O surface |
- (nullable instancetype) init |
{ |
self = [super init]; |
if(self) |
{ |
[self _initialize]; |
} // if |
return self; |
} // init |
+ (nullable instancetype) surface |
{ |
return [[[IOSurface2D allocWithZone:[self zone]] init] autorelease]; |
} // surfaceWithImage |
// Designated initializer to create a 2D I/O surface from an image. |
- (nullable instancetype) initWithImage:(nullable NSImage *)image |
{ |
self = [super init]; |
if(self) |
{ |
[self _initialize]; |
[self _initSurfaceWithImage:image]; |
} // if |
return self; |
} // initWithImage |
+ (nullable instancetype) surfaceWithImage:(nullable NSImage *)image |
{ |
return [[[IOSurface2D allocWithZone:[self zone]] initWithImage:image] autorelease]; |
} // surfaceWithImage |
// Designated initializer to create a 2D I/O surface from an image |
- (nullable instancetype) initWithImageRef:(nullable CGImageRef)image |
{ |
self = [super init]; |
if(self) |
{ |
[self _initialize]; |
[self _drawImage:image]; |
} // if |
return self; |
} // initWithImageRef |
+ (nullable instancetype) surfaceWithImageRef:(nullable CGImageRef)image |
{ |
return [[[IOSurface2D allocWithZone:[self zone]] initWithImageRef:image] autorelease]; |
} // surfaceWithImageRef |
// Designated initializer to create a 2D I/O surface from an image file located at a URL |
- (nullable instancetype) initWithURL:(nullable NSURL *)url |
{ |
self = [super init]; |
if(self) |
{ |
[self _initialize]; |
[self _initSurfaceWithURL:url]; |
} // if |
return self; |
} // initWithURL |
+ (nullable instancetype) surfaceWithURL:(nullable NSURL *)url |
{ |
return [[[IOSurface2D allocWithZone:[self zone]] initWithURL:url] autorelease]; |
} // surfaceWithURL |
// Designated initializer to create a 2D I/O surface from an image file located at an absolute path |
- (nullable instancetype) initWithFile:(nullable NSString *)path |
{ |
self = [super init]; |
if(self) |
{ |
[self _initialize]; |
[self _initSurfaceWithFile:path]; |
} // if |
return self; |
} // initWithFile |
+ (nullable instancetype) surfaceWithFile:(nullable NSString *)path |
{ |
return [[[IOSurface2D allocWithZone:[self zone]] initWithFile:path] autorelease]; |
} // surfaceWithFile |
// Designated initializer to create a 2D I/O surface from an image file in application's bundle |
- (nullable instancetype) initWithResource:(nullable NSString *)name |
{ |
self = [super init]; |
if(self) |
{ |
[self _initialize]; |
[self _initIOSurface2DWithResource:name]; |
} // if |
return self; |
} // initWithResource |
+ (nullable instancetype) surfaceWithResource:(nullable NSString *)name |
{ |
return [[[IOSurface2D allocWithZone:[self zone]] initWithResource:name] autorelease]; |
} // surfaceWithResource |
// Destructor |
- (void) dealloc |
{ |
if(_surface != nullptr) |
{ |
CFRelease(_surface); |
} // if |
[super dealloc]; |
} // dealloc |
- (nonnull instancetype) copyWithZone:(NSZone *)zone |
{ |
IOSurface2D* pSurface = [IOSurface2D new]; |
if(pSurface) |
{ |
pSurface.avoidSync = _avoidSync; |
pSurface.bitsPerComponent = _bitsPerComponent; |
pSurface.bitsPerPixel = _bitsPerPixel; |
pSurface.alphaInfo = _alphaInfo; |
pSurface.width = _width; |
pSurface.height = _height; |
if([pSurface acquire]) |
{ |
[pSurface copy:_surface]; |
} // if |
} // if |
return pSurface; |
} // copyWithZone |
// I/O surface bits-per-component |
- (void) setBitsPerComponent:(size_t)bitsPerComponent |
{ |
if(!isFinalized) |
{ |
_bitsPerComponent = bitsPerComponent; |
} // if |
} // setBitsPerSample |
// I/O surface bits-per-pixel |
- (void) setBitsPerPixel:(size_t)bitsPerPixel |
{ |
if(!isFinalized) |
{ |
_bitsPerPixel = bitsPerPixel; |
} // if |
} // setSamplesPerPixel |
// I/O surface alpha info |
- (void) setAlphaInfo:(CGImageAlphaInfo)alphaInfo |
{ |
if(!isFinalized) |
{ |
_alphaInfo = alphaInfo; |
} // if |
} // setAlphaInfo |
// I/O surface width |
- (void) setWidth:(size_t)width |
{ |
if(!isFinalized) |
{ |
_width = width; |
} // if |
} // setWidth |
// I/O surface height |
- (void) setHeight:(size_t)height |
{ |
if(!isFinalized) |
{ |
_height = height; |
} // if |
} // setHeight |
// Create a new I/O surface if the properties were set and default |
// initializer was used to instantiate the object |
- (BOOL) acquire |
{ |
BOOL success = _surface == nullptr; |
if(success) |
{ |
success = [self _initSurfaceProperties]; |
if(success) |
{ |
_surface = [self _newSurface]; |
isFinalized = _surface != nullptr; |
} // if |
} // if |
return success; |
} // acquire |
// Update the I/O surface data. Does not require locking or unlocking. |
// Also, note that the source data properties used here for write must |
// match the created i/o surface. You can not use this method to copy |
// RGB to RGBA image. |
- (BOOL) update:(nullable const uint8_t *)pBaseAddrSrc |
{ |
BOOL success = (pBaseAddrSrc != nullptr) && isFinalized; |
if(success) |
{ |
IOReturn result = IOSurfaceLock(_surface, 0, &seed); |
success = result == kIOReturnSuccess; |
if(success) |
{ |
uint8_t* pBaseAddrDst = static_cast<uint8_t *>(IOSurfaceGetBaseAddressOfPlane(_surface, 0)); |
success = pBaseAddrDst != nullptr; |
if(success) |
{ |
CG::memcpy(_size, pBaseAddrSrc, pBaseAddrDst); |
} // if |
IOSurfaceUnlock(_surface, 0, &seed); |
} // if |
} // if |
return success; |
} // update |
// The copy methods below do not require locking or unlocking. |
// Also, note that the source i/o surface properties used here |
// for copy must match the created i/o surface. Furthermore, |
// you can not use this method to copy RGB to RGBA i/o surface. |
// Make a copy of an I/O surface image in a plane with index 0. |
- (BOOL) copy:(nullable const IOSurfaceRef)surface |
{ |
IOReturn nResult = kIOReturnError; |
CG::s2dcpy(0, surface, 0, _surface, nResult); |
return nResult == kIOReturnSuccess; |
} // copy |
// Make a copy of an I/O surface image in a plane at an index. |
- (BOOL) copy:(nullable const IOSurfaceRef)surface |
index:(const size_t)index |
{ |
IOReturn nResult = kIOReturnError; |
CG::s2dcpy(index, surface, 0, _surface, nResult); |
return nResult == kIOReturnSuccess; |
} // copy |
// Map the base address of the I/O surface for reading data |
- (nullable uint8_t *) map |
{ |
uint8_t* pBaseAddr = nullptr; |
if(isFinalized) |
{ |
pBaseAddr = static_cast<uint8_t *>(IOSurfaceGetBaseAddress(_surface)); |
} // if |
return pBaseAddr; |
} // map |
// Map the base address of the I/O surface for reading/writing data |
- (nullable uint8_t *) map:(const NSPoint)point |
{ |
uint8_t* ptr = nullptr; |
if(isFinalized) |
{ |
uint8_t* base = static_cast<uint8_t *>(IOSurfaceGetBaseAddressOfPlane(_surface, 0)); |
const size_t width = size_t(point.x) * _bytesPerPixel; |
const size_t height = size_t(point.y) * _bytesPerRow; |
const size_t offset = width + height; |
ptr = base + offset; |
} // if |
return ptr; |
} // map |
// Lock the I/O surface before mapping to read or write |
- (BOOL) lock:(const BOOL)isReadOnly |
{ |
IOReturn result = kIOReturnError; |
if(isFinalized) |
{ |
options = (isReadOnly) ? (kIOSurfaceLockReadOnly | ((_avoidSync) ? kIOSurfaceLockAvoidSync : 0)) : 0; |
result = IOSurfaceLock(_surface, options, &seed); |
} // if |
return result == kIOReturnSuccess; |
} // lock |
// Unlock the I/O surface after a mapped read or write |
- (BOOL) unlock |
{ |
IOReturn result = kIOReturnError; |
if(isFinalized) |
{ |
result = IOSurfaceUnlock(_surface, options, &seed); |
} // if |
return result == kIOReturnSuccess; |
} // unlock |
@end |
Copyright © 2015 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2015-12-10