Skip to content

Commit

Permalink
Correct photo orientation
Browse files Browse the repository at this point in the history
  • Loading branch information
xissburg committed Apr 17, 2012
1 parent f259d92 commit 0b9c0a0
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 99 deletions.
2 changes: 1 addition & 1 deletion XBImageFilters/Classes/Sample/CameraViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ - (void)viewDidLoad
NSLog(@"Error setting shader: %@", [error localizedDescription]);
}

self.cameraView.flashMode = XBFlashModeOn;
//self.cameraView.flashMode = XBFlashModeOn;
[self.cameraView startCapturing];
}

Expand Down
7 changes: 7 additions & 0 deletions XBImageFilters/Classes/XBImageFilters/XBFilteredCameraView.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ typedef enum {
XBTorchModeAuto = 2
} XBTorchMode;

typedef enum {
XBPhotoOrientationAuto = 0, // Determines photo orientation from [UIDevice currentDevice]'s orientation
XBPhotoOrientationPortrait = 1,
XBPhotoOrientationLandscape = 2
} XBPhotoOrientation;

extern NSString *const XBCaptureQualityPhoto;
extern NSString *const XBCaptureQualityHigh;
extern NSString *const XBCaptureQualityMedium;
Expand Down Expand Up @@ -61,6 +67,7 @@ extern NSString *const XBCaptureQuality352x288;
@property (copy, nonatomic) NSString *imageCaptureQuality;
@property (assign, nonatomic) XBFlashMode flashMode;
@property (assign, nonatomic) XBTorchMode torchMode;
@property (assign, nonatomic) XBPhotoOrientation photoOrientation;

/*
* Starts/stops capturing and rendering the camera image with filters applied in realtime.
Expand Down
36 changes: 35 additions & 1 deletion XBImageFilters/Classes/XBImageFilters/XBFilteredCameraView.m
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ @implementation XBFilteredCameraView
@synthesize imageCaptureQuality = _imageCaptureQuality;
@synthesize flashMode = _flashMode;
@synthesize torchMode = _torchMode;
@synthesize photoOrientation = _photoOrientation;

- (void)_XBFilteredCameraViewInit
{
Expand All @@ -55,6 +56,7 @@ - (void)_XBFilteredCameraViewInit
self.captureSession = [[AVCaptureSession alloc] init];
self.videoCaptureQuality = XBCaptureQualityPhoto;
self.imageCaptureQuality = XBCaptureQualityPhoto;
self.photoOrientation = XBPhotoOrientationAuto;

// Use the rear camera by default
self.cameraPosition = XBCameraPositionBack;
Expand Down Expand Up @@ -274,6 +276,9 @@ - (void)takeAPhotoWithCompletion:(void (^)(UIImage *))completion
}
}

imageConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
imageConnection.videoMirrored = YES;

[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:imageConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
self.captureSession.sessionPreset = [self captureSessionPresetFromCaptureQuality:self.videoCaptureQuality];

Expand All @@ -284,7 +289,36 @@ - (void)takeAPhotoWithCompletion:(void (^)(UIImage *))completion
size_t width = CVPixelBufferGetBytesPerRow(imageBuffer)/4;
size_t height = CVPixelBufferGetHeight(imageBuffer);

UIImage *filteredImage = [self filteredImageWithData:baseAddress width:width height:height];
BOOL portrait;

if (self.photoOrientation == XBPhotoOrientationPortrait) {
portrait = YES;
}
else if (self.photoOrientation == XBPhotoOrientationLandscape) {
portrait = NO;
}
else if (self.photoOrientation == XBPhotoOrientationAuto) {
switch ([[UIDevice currentDevice] orientation]) {
case UIDeviceOrientationLandscapeLeft:
case UIDeviceOrientationLandscapeRight:
case UIDeviceOrientationFaceUp:
portrait = NO;
break;

case UIDeviceOrientationPortrait:
case UIDeviceOrientationPortraitUpsideDown:
case UIDeviceOrientationFaceDown:
case UIDeviceOrientationUnknown:
portrait = YES;
break;
}
}

GLint targetWidth = portrait? height: width;
GLint targetHeight = portrait? width: height;
GLKMatrix4 contentTransform = portrait? GLKMatrix4MakeRotation(M_PI_2, 0, 0, 1): GLKMatrix4Identity;

UIImage *filteredImage = [self _filteredImageWithData:baseAddress textureWidth:width textureHeight:height targetWidth:targetWidth targetHeight:targetHeight contentTransform:contentTransform];

completion(filteredImage);
}];
Expand Down
3 changes: 1 addition & 2 deletions XBImageFilters/Classes/XBImageFilters/XBFilteredView.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,11 @@
*/
- (void)display;

- (UIImage *)filteredImageWithData:(GLvoid *)data width:(GLint)width height:(GLint)height;

/* These methods are conceptually protected and should not be called directly. They are intended to be called by subclasses. */
- (void)_setTextureData:(GLvoid *)textureData width:(GLint)width height:(GLint)height;
- (void)_updateTextureWithData:(GLvoid *)textureData;
- (void)_deleteMainTexture;
- (UIImage *)_filteredImageWithData:(GLvoid *)data textureWidth:(GLint)textureWidth textureHeight:(GLint)textureHeight targetWidth:(GLint)targetWidth targetHeight:(GLint)targetHeight contentTransform:(GLKMatrix4)contentTransform;

@end

Expand Down
198 changes: 103 additions & 95 deletions XBImageFilters/Classes/XBImageFilters/XBFilteredView.m
Original file line number Diff line number Diff line change
Expand Up @@ -259,114 +259,29 @@ - (void)_deleteMainTexture
_mainTexture = 0;
}

#pragma mark - Public Methods

- (BOOL)setFilterFragmentShaderFromFile:(NSString *)path error:(NSError *__autoreleasing *)error
{
NSArray *paths = [[NSArray alloc] initWithObjects:path, nil];
return [self setFilterFragmentShadersFromFiles:paths error:error];
}

- (BOOL)setFilterFragmentShadersFromFiles:(NSArray *)paths error:(NSError *__autoreleasing *)error
- (UIImage *)_filteredImageWithData:(GLvoid *)data textureWidth:(GLint)textureWidth textureHeight:(GLint)textureHeight targetWidth:(GLint)targetWidth targetHeight:(GLint)targetHeight contentTransform:(GLKMatrix4)contentTransform
{
[EAGLContext setCurrentContext:self.context];

[self destroyEvenPass];
[self destroyOddPass];

/* Create frame buffers for render to texture in multi-pass filters if necessary. If we have a single pass/fragment shader, we'll render
* directly to the framebuffer. If we have two passes, we'll render to the evenPassFramebuffer using the original image as the filter source
* texture and then render directly to the framebuffer using the evenPassTexture as the filter source. If we have three passes, the second
* filter will instead render to the oddPassFramebuffer and the third/last pass will render to the framebuffer using the oddPassTexture.
* And so on... */

if (paths.count >= 2) {
// Two or more passes, create evenPass*
[self setupEvenPass];
}

if (paths.count > 2) {
// More than two passes, create oddPass*
[self setupOddPass];
}

NSMutableArray *programs = [[NSMutableArray alloc] initWithCapacity:paths.count];
NSString *vertexShaderPath = [[NSBundle mainBundle] pathForResource:@"DefaultVertexShader" ofType:@"glsl"];

for (int i = 0; i < paths.count; ++i) {
NSString *fragmentShaderPath = [paths objectAtIndex:i];
GLKProgram *program = [[GLKProgram alloc] initWithVertexShaderFromFile:vertexShaderPath fragmentShaderFromFile:fragmentShaderPath error:error];

if (program == nil) {
return NO;
}

[program setValue:i == paths.count - 1?&_contentTransform: (void *)&GLKMatrix4Identity forUniformNamed:@"u_contentTransform"];
[program setValue:i == 0?&_texCoordTransform: (void *)&GLKMatrix2Identity forUniformNamed:@"u_texCoordTransform"];

GLuint sourceTexture = 0;

if (i == 0) { // First pass always uses the original image
sourceTexture = self.mainTexture;
}
else if (i%2 == 1) { // Second pass uses the result of the first, and the first is 0, hence even
sourceTexture = self.evenPassTexture;
}
else { // Third pass uses the result of the second, which is number 1, then it's odd
sourceTexture = self.oddPassTexture;
}

[program bindSamplerNamed:@"s_texture" toTexture:sourceTexture unit:0];

// Enable vertex position and texCoord attributes
GLKAttribute *positionAttribute = [program.attributes objectForKey:@"a_position"];
glVertexAttribPointer(positionAttribute.location, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid *)offsetof(Vertex, position));
glEnableVertexAttribArray(positionAttribute.location);

GLKAttribute *texCoordAttribute = [program.attributes objectForKey:@"a_texCoord"];
glVertexAttribPointer(texCoordAttribute.location, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid *)offsetof(Vertex, texCoord));
glEnableVertexAttribArray(texCoordAttribute.location);

[programs addObject:program];
}

self.programs = [programs copy];

[self setNeedsLayout];

return YES;
}

- (UIImage *)takeScreenshot
{
return [self takeScreenshotWithImageOrientation:UIImageOrientationDownMirrored];
}

- (UIImage *)takeScreenshotWithImageOrientation:(UIImageOrientation)orientation
{
int width = (int)(self.bounds.size.width * self.contentScaleFactor);
int height = (int)(self.bounds.size.height * self.contentScaleFactor);
return [self imageFromFramebuffer:self.framebuffer width:width height:height orientation:orientation];
}

- (UIImage *)filteredImageWithData:(GLvoid *)data width:(GLint)width height:(GLint)height
{
[EAGLContext setCurrentContext:self.context];
GLKMatrix4 oldContentTransform = self.contentTransform;
self.contentTransform = contentTransform;
UIViewContentMode oldContentMode = self.contentMode;
self.contentMode = UIViewContentModeScaleToFill;

GLuint mainTexture = [self generateDefaultTextureWithWidth:width height:height data:data];
GLuint evenPassTexture = [self generateDefaultTextureWithWidth:width height:height data:NULL];
GLuint mainTexture = [self generateDefaultTextureWithWidth:textureWidth height:textureHeight data:data];
GLuint evenPassTexture = [self generateDefaultTextureWithWidth:targetWidth height:targetHeight data:NULL];
GLuint evenPassFrambuffer = [self generateDefaultFramebufferWithTargetTexture:evenPassTexture];
GLuint oddPassTexture = 0;
GLuint oddPassFramebuffer = 0;

if (self.programs.count > 1) {
oddPassTexture = [self generateDefaultTextureWithWidth:width height:height data:NULL];
oddPassTexture = [self generateDefaultTextureWithWidth:targetWidth height:targetHeight data:NULL];
oddPassFramebuffer = [self generateDefaultFramebufferWithTargetTexture:oddPassTexture];
}

GLuint lastFramebuffer = 0;

glViewport(0, 0, width, height);
glViewport(0, 0, targetWidth, targetHeight);

for (int pass = 0; pass < self.programs.count; ++pass) {
GLKProgram *program = [self.programs objectAtIndex:pass];
Expand Down Expand Up @@ -408,7 +323,7 @@ - (UIImage *)filteredImageWithData:(GLvoid *)data width:(GLint)width height:(GLi
}
}

UIImage *image = [self imageFromFramebuffer:lastFramebuffer width:width height:height orientation:UIImageOrientationDownMirrored];
UIImage *image = [self imageFromFramebuffer:lastFramebuffer width:targetWidth height:targetHeight orientation:UIImageOrientationUp];

// Now discard the lastFramebuffer
const GLenum discards[] = {GL_COLOR_ATTACHMENT0};
Expand Down Expand Up @@ -438,6 +353,9 @@ - (UIImage *)filteredImageWithData:(GLvoid *)data width:(GLint)width height:(GLi
glDeleteTextures(1, &oddPassTexture);
glDeleteFramebuffers(1, &oddPassFramebuffer);

self.contentTransform = oldContentTransform;
self.contentMode = oldContentMode;

#ifdef DEBUG
GLenum error = glGetError();
if (error != GL_NO_ERROR) {
Expand All @@ -448,6 +366,96 @@ - (UIImage *)filteredImageWithData:(GLvoid *)data width:(GLint)width height:(GLi
return image;
}

#pragma mark - Public Methods

- (BOOL)setFilterFragmentShaderFromFile:(NSString *)path error:(NSError *__autoreleasing *)error
{
NSArray *paths = [[NSArray alloc] initWithObjects:path, nil];
return [self setFilterFragmentShadersFromFiles:paths error:error];
}

- (BOOL)setFilterFragmentShadersFromFiles:(NSArray *)paths error:(NSError *__autoreleasing *)error
{
[EAGLContext setCurrentContext:self.context];

[self destroyEvenPass];
[self destroyOddPass];

/* Create frame buffers for render to texture in multi-pass filters if necessary. If we have a single pass/fragment shader, we'll render
* directly to the framebuffer. If we have two passes, we'll render to the evenPassFramebuffer using the original image as the filter source
* texture and then render directly to the framebuffer using the evenPassTexture as the filter source. If we have three passes, the second
* filter will instead render to the oddPassFramebuffer and the third/last pass will render to the framebuffer using the oddPassTexture.
* And so on... */

if (paths.count >= 2) {
// Two or more passes, create evenPass*
[self setupEvenPass];
}

if (paths.count > 2) {
// More than two passes, create oddPass*
[self setupOddPass];
}

NSMutableArray *programs = [[NSMutableArray alloc] initWithCapacity:paths.count];
NSString *vertexShaderPath = [[NSBundle mainBundle] pathForResource:@"DefaultVertexShader" ofType:@"glsl"];

for (int i = 0; i < paths.count; ++i) {
NSString *fragmentShaderPath = [paths objectAtIndex:i];
GLKProgram *program = [[GLKProgram alloc] initWithVertexShaderFromFile:vertexShaderPath fragmentShaderFromFile:fragmentShaderPath error:error];

if (program == nil) {
return NO;
}

[program setValue:i == paths.count - 1?&_contentTransform: (void *)&GLKMatrix4Identity forUniformNamed:@"u_contentTransform"];
[program setValue:i == 0?&_texCoordTransform: (void *)&GLKMatrix2Identity forUniformNamed:@"u_texCoordTransform"];

GLuint sourceTexture = 0;

if (i == 0) { // First pass always uses the original image
sourceTexture = self.mainTexture;
}
else if (i%2 == 1) { // Second pass uses the result of the first, and the first is 0, hence even
sourceTexture = self.evenPassTexture;
}
else { // Third pass uses the result of the second, which is number 1, then it's odd
sourceTexture = self.oddPassTexture;
}

[program bindSamplerNamed:@"s_texture" toTexture:sourceTexture unit:0];

// Enable vertex position and texCoord attributes
GLKAttribute *positionAttribute = [program.attributes objectForKey:@"a_position"];
glVertexAttribPointer(positionAttribute.location, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid *)offsetof(Vertex, position));
glEnableVertexAttribArray(positionAttribute.location);

GLKAttribute *texCoordAttribute = [program.attributes objectForKey:@"a_texCoord"];
glVertexAttribPointer(texCoordAttribute.location, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid *)offsetof(Vertex, texCoord));
glEnableVertexAttribArray(texCoordAttribute.location);

[programs addObject:program];
}

self.programs = [programs copy];

[self setNeedsLayout];

return YES;
}

- (UIImage *)takeScreenshot
{
return [self takeScreenshotWithImageOrientation:UIImageOrientationDownMirrored];
}

- (UIImage *)takeScreenshotWithImageOrientation:(UIImageOrientation)orientation
{
int width = (int)(self.bounds.size.width * self.contentScaleFactor);
int height = (int)(self.bounds.size.height * self.contentScaleFactor);
return [self imageFromFramebuffer:self.framebuffer width:width height:height orientation:orientation];
}

- (void)display
{
[EAGLContext setCurrentContext:self.context];
Expand Down

0 comments on commit 0b9c0a0

Please sign in to comment.