Skip to content
Browse files

Initial import

git-svn-id: http://simple-iphone-image-processing.googlecode.com/svn/trunk@2 f0a95276-1093-11de-b0d4-7fec019aa467
  • Loading branch information...
1 parent ff578c8 commit a1825c78404ffd2dc76af61df9ac3b8fe04d51b6 cmgreening committed Mar 14, 2009
View
122 Classes/Image.h
@@ -0,0 +1,122 @@
+/*
+ * Image.h
+ * ImageProcessing
+ *
+ * Created by Chris Greening on 02/01/2009.
+ * Copyright 2009 __MyCompanyName__. All rights reserved.
+ *
+ */
+
+#import <UIKit/UIImage.h>
+
+#include <vector>
+
+class Image;
+// objective C wrapper for our C++ image class - makes memory management with autorelease pools a lot easier
+@interface ImageWrapper : NSObject {
+ // the C++ image
+ Image *image;
+ // do we own the image - ie should we delete it when we dealloc
+ bool ownsImage;
+}
+
+@property(assign, nonatomic) Image *image;
+@property(assign, nonatomic) bool ownsImage;
++ (ImageWrapper *) imageWithCPPImage:(Image *) theImage;
+
+@end
+
+// simple class for holding an image point
+class ImagePoint {
+public:
+ short x,y;
+ inline ImagePoint(short xpos, short ypos) {
+ x=xpos;
+ y=ypos;
+ }
+ inline ImagePoint(int xpos, int ypos) {
+ x=xpos;
+ y=ypos;
+ }
+ inline ImagePoint(const ImagePoint &other) {
+ x=other.x;
+ y=other.y;
+ }
+ inline ImagePoint() {
+ x=0; y=0;
+ }
+};
+
+
+// image class - handles grey scale images
+class Image {
+private:
+ // pointer to the image data
+ uint8_t *m_imageData;
+ // do we actually own the image
+ bool m_ownsData;
+ // width and height
+ int m_width;
+ int m_height;
+ // constructors used internally by the static helper
+ Image(ImageWrapper *other, int x1, int y1, int x2, int y2);
+ Image(int width, int height);
+ Image(uint8_t *imageData, int width, int height, bool ownsData=false);
+ Image(UIImage *srcImage, int width, int height, bool imageIsRotatedBy90degrees=false);
+public:
+ // destructor
+ ~Image() {
+ if(m_ownsData)
+ free(m_imageData);
+ }
+ // static helpers for creating images - these all return an Objective-C wrapper to the resulting image
+
+ // copy a section of another image
+ static ImageWrapper *createImage(ImageWrapper *other, int x1, int y1, int x2, int y2);
+ // create an empty image of the required width and height
+ static ImageWrapper *createImage(int width, int height);
+ // create an image from data
+ static ImageWrapper *createImage(uint8_t *imageData, int width, int height, bool ownsData=false);
+ // take a source UIImage and convert it to greyscale
+ static ImageWrapper *createImage(UIImage *srcImage, int width, int height, bool imageIsRotatedBy90degrees=false);
+
+ // edge detection
+ ImageWrapper *cannyEdgeExtract(float tlow, float thigh);
+ // local thresholding
+ ImageWrapper* autoLocalThreshold();
+ // threshold using integral
+ ImageWrapper *autoIntegratingThreshold();
+ // threshold an image automatically
+ ImageWrapper *autoThreshold();
+ // gaussian smooth the image
+ ImageWrapper *gaussianBlur();
+ // exrtact a connected area from the image
+ void extractConnectedRegion(int x, int y, std::vector<ImagePoint> *points);
+ // find the largest connected region in the image
+ void findLargestStructure(std::vector<ImagePoint> *maxPoints);
+ // normalise an image
+ void normalise();
+ // shrink to a new size
+ ImageWrapper *resize(int newX, int newY);
+ // histogram equalisation
+ void HistogramEqualisation();
+ // skeltonize
+ void skeletonise();
+ // convert back to a UIImage for display
+ UIImage *toUIImage();
+ // access the image data
+ inline uint8_t* operator[](const int rowIndex) {
+ return (m_imageData+rowIndex*m_width);
+ }
+ inline int getWidth() {
+ return m_width;
+ }
+ inline int getHeight() {
+ return m_height;
+ }
+ // helper functions for resizing
+ static float Interpolate1(float a, float b, float c);
+ static float Interpolate2(float a, float b, float c, float d, float x, float y);
+};
+
+
View
825 Classes/Image.mm
@@ -0,0 +1,825 @@
+/*
+ * Image.cpp
+ * ImageProcessing
+ *
+ * Created by Chris Greening on 04/01/2009.
+ * Copyright 2009 __MyCompanyName__. All rights reserved.
+ *
+ */
+#include "Image.h"
+#include <stack>
+
+@implementation ImageWrapper
+
+@synthesize image;
+@synthesize ownsImage;
+
+// creates an Objective-C wrapper around a C++ image
++ (ImageWrapper *) imageWithCPPImage:(Image *) theImage;
+{
+ ImageWrapper *wrapper = [[ImageWrapper alloc] init];
+ wrapper.image=theImage;
+ wrapper.ownsImage=true;
+ return [wrapper autorelease];
+}
+
+// override to specify if the wrapper should take ownership of the C++ image
++ (ImageWrapper *) imageWithCPPImage:(Image *) theImage ownsImage:(bool) ownsTheImage;
+{
+ ImageWrapper *wrapper = [[ImageWrapper alloc] init];
+ wrapper.image=theImage;
+ wrapper.ownsImage=ownsTheImage;
+ return [wrapper autorelease];
+}
+
+// cleanup
+- (void) dealloc
+{
+ // delete the image that we have been holding onto
+ if(ownsImage) delete image;
+ [super dealloc];
+}
+
+
+@end
+
+// these constructors are all private. Use the helper function defined below to create images
+
+// extract a region of an image to a new image
+Image::Image(ImageWrapper *other, int x1, int y1, int x2, int y2) {
+ m_width=x2-x1;
+ m_height=y2-y1;
+ m_imageData=(uint8_t *) malloc(m_width*m_height);
+ Image *otherImage=other.image;
+ for(int y=y1; y<y2; y++) {
+ for(int x=x1; x<x2; x++) {
+ (*this)[y-y1][x-x1]=(*otherImage)[y][x];
+ }
+ }
+ m_ownsData=true;
+}
+
+// create an empty image - note, memory is not intialised to zero
+Image::Image(int width, int height) {
+ m_imageData=(uint8_t *) malloc(width*height);
+ m_width=width;
+ m_height=height;
+ m_ownsData=true;
+}
+
+// create an image from data
+Image::Image(uint8_t *imageData, int width, int height, bool ownsData) {
+ m_imageData=imageData;
+ m_width=width;
+ m_height=height;
+ m_ownsData=ownsData;
+}
+
+// convert from a UIImage to a grey scale image
+/* We do a very quick and dirty conversion to grey scale by only taking the green channel from the image.
+ This tends to be the channel that has the least noise in a jpeg compressed image */
+Image::Image(UIImage *srcImage, int width, int height, bool imageIsRotatedBy90degrees) {
+ NSDate *start=[NSDate date];
+ if(imageIsRotatedBy90degrees) {
+ int tmp=width;
+ width=height;
+ height=tmp;
+ }
+ m_width=width;
+ m_height=height;
+ // get hold of the image bytes
+ uint32_t *rgbImage=(uint32_t *) malloc(m_width*m_height*sizeof(uint32_t));
+ CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();
+ CGContextRef context=CGBitmapContextCreate(rgbImage, m_width, m_height, 8, m_width*4, colorSpace, kCGBitmapByteOrder32Little|kCGImageAlphaNoneSkipLast);
+ CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
+ CGContextSetShouldAntialias(context, NO);
+ CGContextDrawImage(context, CGRectMake(0,0, m_width, m_height), [srcImage CGImage]);
+ CGContextRelease(context);
+ CGColorSpaceRelease(colorSpace);
+
+ start=[NSDate date];
+ // now convert to grayscale
+ m_imageData=(uint8_t *) malloc(m_width*m_height);
+ m_ownsData=true;
+ for(int y=0; y<m_height; y++) {
+ for(int x=0; x<m_width; x++) {
+ uint32_t rgbPixel=rgbImage[y*m_width+x];
+ // uint32_t r=(rgbPixel>>8)&255;
+ uint32_t g=(rgbPixel>>16)&255;
+ // uint32_t b=(rgbPixel>>24)&255;
+ // m_imageData[y*m_width+x]=(r+g+b)/3;
+ m_imageData[y*m_width+x]=g;
+ }
+ }
+ free(rgbImage);
+ if(imageIsRotatedBy90degrees) {
+ uint8_t *tmpImage=(uint8_t *) malloc(m_width*m_height);
+ for(int y=0; y<m_height; y++) {
+ for(int x=0; x<m_width; x++) {
+ tmpImage[x*m_height+y]=m_imageData[(m_height-y-1)*m_width+x];
+ }
+ }
+ int tmp=m_width;
+ m_width=m_height;
+ m_height=tmp;
+ free(m_imageData);
+ m_imageData=tmpImage;
+ }
+}
+
+// helper functions - use these to create images. They will give you an autoreleased Objective-C wrapper for easier memory handling
+
+// copy a section of another image
+ImageWrapper *Image::createImage(ImageWrapper *other, int x1, int y1, int x2, int y2)
+{
+ return [ImageWrapper imageWithCPPImage:new Image(other, x1, y1, x2, y2)];
+}
+// create an empty image of the required width and height
+ImageWrapper *Image::createImage(int width, int height) {
+ return [ImageWrapper imageWithCPPImage:new Image(width, height)];
+}
+// create an image from data
+ImageWrapper *Image::createImage(uint8_t *imageData, int width, int height, bool ownsData) {
+ return [ImageWrapper imageWithCPPImage:new Image(imageData, width, height, ownsData)];
+}
+// take a source UIImage and convert it to greyscale
+ImageWrapper *Image::createImage(UIImage *srcImage, int width, int height, bool imageIsRotatedBy90degrees) {
+ return [ImageWrapper imageWithCPPImage:new Image(srcImage, width, height, imageIsRotatedBy90degrees)];
+}
+
+// stretch the image brightness so that it takes the range 0-255
+// http://en.wikipedia.org/wiki/Normalization_(image_processing)
+void Image::normalise() {
+ int min=INT_MAX;
+ int max=0;
+
+ for(int i=0; i<m_width*m_height; i++) {
+ if(m_imageData[i]>max) max=m_imageData[i];
+ if(m_imageData[i]<min) min=m_imageData[i];
+ }
+ for(int i=0; i<m_width*m_height; i++) {
+ m_imageData[i]=255*(m_imageData[i]-min)/(max-min);
+ }
+}
+
+
+/*
+// recursively extract connected region - this has been replace by the non recursive version
+void Image::extractConnectedRegion(int x, int y, std::vector<short> *xpoints, std::vector<short> *ypoints) {
+ // remove the current point from the image
+ (*this)[y][x]=0;
+ (*xpoints).push_back(x);
+ (*ypoints).push_back(y);
+ for(int ypos=y-1; ypos<=y+1; ypos++) {
+ for(int xpos=x-1; xpos<=x+1; xpos++) {
+ if(xpos>0 && ypos>0 && xpos<getWidth() && ypos<getHeight() && (*this)[ypos][xpos]!=0) {
+ extractConnectedRegion(xpos, ypos, xpoints, ypoints);
+ }
+ }
+ }
+}
+*/
+
+// extract a connected region from an image - this uses a non-recursive algorithm to prevent us running out of stack
+// space when extracting very large regions
+void Image::extractConnectedRegion(int x, int y, std::vector<ImagePoint> *points) {
+
+ // remove the current point from the image
+ (*this)[y][x]=0;
+ (*points).push_back(ImagePoint(x,y));
+
+ std::stack<ImagePoint> myStack;
+ std::stack<short> stackXpos;
+ std::stack<short> stackYpos;
+ myStack.push(ImagePoint(x,y));
+ while(myStack.size()>0) {
+ // get the entry at the top of the stack
+ x=myStack.top().x;
+ y=myStack.top().y;
+ myStack.pop();
+ // check the surrounding region for other points
+ for(int ypos=y-1; ypos<=y+1; ypos++) {
+ for(int xpos=x-1; xpos<=x+1; xpos++) {
+ if(xpos>=0 && ypos>=0 && xpos<getWidth() && ypos<getHeight() && (*this)[ypos][xpos]!=0) {
+ // found a point - add it to the list of points and change the x and y to reflect this new point
+ (*points).push_back(ImagePoint(xpos,ypos));
+ (*this)[ypos][xpos]=0;
+ // push the current x and y onto the stack
+ myStack.push(ImagePoint(x,y));
+ // x and y are the new x and y
+ x=xpos;
+ y=ypos;
+ // reset the loop counter
+ ypos=y-1;
+ xpos=x-1;
+ }
+ }
+ }
+ }
+}
+
+// find the largest structure in a thresholded image
+void Image::findLargestStructure(std::vector<ImagePoint> *maxPoints) {
+ // process the image
+ std::vector<ImagePoint> points;
+ for(int y=0; y<m_height; y++) {
+ for(int x=0; x<m_width; x++) {
+ // if we've found a point in the image then extract everything connected to it
+ if((*this)[y][x]!=0) {
+ extractConnectedRegion(x, y, &points);
+ if(points.size()>maxPoints->size()) {
+ maxPoints->clear();
+ maxPoints->resize(points.size());
+ std::copy(points.begin(), points.end(), maxPoints->begin());
+ }
+ points.clear();
+ }
+ }
+ }
+}
+
+
+// help function for autoLocalThreshold
+int findThresholdAtPosition(int startx, int starty, int size, Image* src) {
+ int total=0;
+ for(int y=starty; y<starty+size; y++) {
+ for(int x=startx; x<startx+size; x++) {
+ total+=(*src)[y][x];
+ }
+ }
+ int threshold=total/(size*size);
+ return threshold;
+};
+
+// threshold an image using a threshold that is computed at every pixel point
+// This is designed for text segmentation. Dark pixels are set to 255, light pixels to 0
+ImageWrapper* Image::autoLocalThreshold() {
+ const int local_size=10;
+ // now produce the thresholded image
+ Image *result=new Image(m_width, m_height);
+ // process the image
+ int threshold=0;
+ for(int y=local_size/2; y<m_height-local_size/2; y++) {
+ for(int x=local_size/2; x<m_width-local_size/2; x++) {
+ threshold=findThresholdAtPosition(x-local_size/2, y-local_size/2, local_size, this);
+ int val=(*this)[y][x];
+ // to remove noise we only accept pixels that are less than 90% of the threshold
+ if(val>threshold*0.9)
+ (*result)[y][x]=0;
+ else
+ (*result)[y][x]=255;
+ }
+ }
+ return [ImageWrapper imageWithCPPImage:result];
+}
+
+// Threshold using the average value of the image brightness
+ImageWrapper *Image::autoThreshold() {
+ int total=0;
+ int count=0;
+ for(int y=0; y<m_height; y++) {
+ for(int x=0; x<m_width; x++) {
+ total+=(*this)[y][x];
+ count++;
+ }
+ }
+ int threshold=total/count;
+ Image *result=new Image(m_width, m_height);
+ for(int y=0; y<m_height; y++) {
+ for(int x=0; x<m_width; x++) {
+ if((*this)[y][x]>threshold*0.9) {
+ (*result)[y][x]=0;
+ } else {
+ (*result)[y][x]=255;
+ }
+ }
+ }
+ return [ImageWrapper imageWithCPPImage:result];
+}
+
+
+// skeletonise an image
+void Image::skeletonise() {
+ bool changes=true;
+ while(changes) {
+ changes=false;
+ for(int y=1; y<m_height-1; y++) {
+ for(int x=1; x<m_width-1; x++) {
+ if((*this)[y][x]!=0) {
+ bool val[8];
+ val[0]=(*this)[y-1][x-1]!=0;
+ val[1]=(*this)[y-1][x]!=0;
+ val[2]=(*this)[y-1][x+1]!=0;
+ val[3]=(*this)[y][x+1]!=0;
+ val[4]=(*this)[y+1][x+1]!=0;
+ val[5]=(*this)[y+1][x]!=0;
+ val[6]=(*this)[y+1][x-1]!=0;
+ val[7]=(*this)[y][x-1]!=0;
+
+ bool remove=false;
+ for(int i=0; i<7 && !remove;i++) {
+ remove=(val[(0+i)%8] && val[(1+i)%8] && val[(7+i)%8] && val[(6+i)%8] && val[(5+i)%8] && !(val[(2+i)%8] || val[(3+i)%8] || val[(4+i)%8]))
+ || (val[(0+i)%8] && val[(1+i)%8] && val[(7+i)%8] && !(val[(3+i)%8] || val[(6+i)%8] || val[(5+i)%8] || val[(4+i)%8])) ||
+ !(val[(0+i)%8] || val[(1+i)%8] || val[(2+i)%8] || val[(3+i)%8] || val[(4+i)%8] || val[(5+i)%8] || val[(6+i)%8] || val[(7+i)%8]);
+ }
+ if(remove) {
+ (*this)[y][x]=0;
+ changes=true;
+ }
+ }
+ }
+ }
+ }
+}
+
+// Canny edge detection - this code is a bit of a mess
+
+#define NOEDGE 255
+#define POSSIBLE_EDGE 128
+#define EDGE 0
+
+void non_max_supp(int *mag, int *gradx, int *grady, int nrows, int ncols,
+ uint8_t *result)
+{
+ int rowcount, colcount,count;
+ int *magrowptr,*magptr;
+ int *gxrowptr,*gxptr;
+ int *gyrowptr,*gyptr,z1,z2;
+ int m00,gx,gy;
+ float mag1,mag2,xperp,yperp;
+ uint8_t *resultrowptr, *resultptr;
+
+
+ /****************************************************************************
+ * Zero the edges of the result image.
+ ****************************************************************************/
+ for(count=0,resultrowptr=result,resultptr=result+ncols*(nrows-1);
+ count<ncols; resultptr++,resultrowptr++,count++){
+ *resultrowptr = *resultptr = (unsigned char) 0;
+ }
+
+ for(count=0,resultptr=result,resultrowptr=result+ncols-1;
+ count<nrows; count++,resultptr+=ncols,resultrowptr+=ncols){
+ *resultptr = *resultrowptr = (unsigned char) 0;
+ }
+
+ /****************************************************************************
+ * Suppress non-maximum points.
+ ****************************************************************************/
+ for(rowcount=1,magrowptr=mag+ncols+1,gxrowptr=gradx+ncols+1,
+ gyrowptr=grady+ncols+1,resultrowptr=result+ncols+1;
+ rowcount<nrows-2;
+ rowcount++,magrowptr+=ncols,gyrowptr+=ncols,gxrowptr+=ncols,
+ resultrowptr+=ncols){
+ for(colcount=1,magptr=magrowptr,gxptr=gxrowptr,gyptr=gyrowptr,
+ resultptr=resultrowptr;colcount<ncols-2;
+ colcount++,magptr++,gxptr++,gyptr++,resultptr++){
+ m00 = *magptr;
+ if(m00 == 0){
+ *resultptr = (unsigned char) NOEDGE;
+ }
+ else{
+ xperp = -(gx = *gxptr)/((float)m00);
+ yperp = (gy = *gyptr)/((float)m00);
+ }
+
+ if(gx >= 0){
+ if(gy >= 0){
+ if (gx >= gy)
+ {
+ /* 111 */
+ /* Left point */
+ z1 = *(magptr - 1);
+ z2 = *(magptr - ncols - 1);
+
+ mag1 = (m00 - z1)*xperp + (z2 - z1)*yperp;
+
+ /* Right point */
+ z1 = *(magptr + 1);
+ z2 = *(magptr + ncols + 1);
+
+ mag2 = (m00 - z1)*xperp + (z2 - z1)*yperp;
+ }
+ else
+ {
+ /* 110 */
+ /* Left point */
+ z1 = *(magptr - ncols);
+ z2 = *(magptr - ncols - 1);
+
+ mag1 = (z1 - z2)*xperp + (z1 - m00)*yperp;
+
+ /* Right point */
+ z1 = *(magptr + ncols);
+ z2 = *(magptr + ncols + 1);
+
+ mag2 = (z1 - z2)*xperp + (z1 - m00)*yperp;
+ }
+ }
+ else
+ {
+ if (gx >= -gy)
+ {
+ /* 101 */
+ /* Left point */
+ z1 = *(magptr - 1);
+ z2 = *(magptr + ncols - 1);
+
+ mag1 = (m00 - z1)*xperp + (z1 - z2)*yperp;
+
+ /* Right point */
+ z1 = *(magptr + 1);
+ z2 = *(magptr - ncols + 1);
+
+ mag2 = (m00 - z1)*xperp + (z1 - z2)*yperp;
+ }
+ else
+ {
+ /* 100 */
+ /* Left point */
+ z1 = *(magptr + ncols);
+ z2 = *(magptr + ncols - 1);
+
+ mag1 = (z1 - z2)*xperp + (m00 - z1)*yperp;
+
+ /* Right point */
+ z1 = *(magptr - ncols);
+ z2 = *(magptr - ncols + 1);
+
+ mag2 = (z1 - z2)*xperp + (m00 - z1)*yperp;
+ }
+ }
+ }
+ else
+ {
+ if ((gy = *gyptr) >= 0)
+ {
+ if (-gx >= gy)
+ {
+ /* 011 */
+ /* Left point */
+ z1 = *(magptr + 1);
+ z2 = *(magptr - ncols + 1);
+
+ mag1 = (z1 - m00)*xperp + (z2 - z1)*yperp;
+
+ /* Right point */
+ z1 = *(magptr - 1);
+ z2 = *(magptr + ncols - 1);
+
+ mag2 = (z1 - m00)*xperp + (z2 - z1)*yperp;
+ }
+ else
+ {
+ /* 010 */
+ /* Left point */
+ z1 = *(magptr - ncols);
+ z2 = *(magptr - ncols + 1);
+
+ mag1 = (z2 - z1)*xperp + (z1 - m00)*yperp;
+
+ /* Right point */
+ z1 = *(magptr + ncols);
+ z2 = *(magptr + ncols - 1);
+
+ mag2 = (z2 - z1)*xperp + (z1 - m00)*yperp;
+ }
+ }
+ else
+ {
+ if (-gx > -gy)
+ {
+ /* 001 */
+ /* Left point */
+ z1 = *(magptr + 1);
+ z2 = *(magptr + ncols + 1);
+
+ mag1 = (z1 - m00)*xperp + (z1 - z2)*yperp;
+
+ /* Right point */
+ z1 = *(magptr - 1);
+ z2 = *(magptr - ncols - 1);
+
+ mag2 = (z1 - m00)*xperp + (z1 - z2)*yperp;
+ }
+ else
+ {
+ /* 000 */
+ /* Left point */
+ z1 = *(magptr + ncols);
+ z2 = *(magptr + ncols + 1);
+
+ mag1 = (z2 - z1)*xperp + (m00 - z1)*yperp;
+
+ /* Right point */
+ z1 = *(magptr - ncols);
+ z2 = *(magptr - ncols - 1);
+
+ mag2 = (z2 - z1)*xperp + (m00 - z1)*yperp;
+ }
+ }
+ }
+
+ /* Now determine if the current point is a maximum point */
+
+ if ((mag1 > 0.0) || (mag2 > 0.0))
+ {
+ *resultptr = (unsigned char) NOEDGE;
+ }
+ else
+ {
+ if (mag2 == 0.0)
+ *resultptr = (unsigned char) NOEDGE;
+ else
+ *resultptr = (unsigned char) POSSIBLE_EDGE;
+ }
+ }
+ }
+}
+
+void follow_edges(uint8_t *edgemapptr, int *edgemagptr, short lowval,
+ int cols)
+{
+ int *tempmagptr;
+ uint8_t *tempmapptr;
+ int i;
+ int x[8] = {1,1,0,-1,-1,-1,0,1},
+ y[8] = {0,1,1,1,0,-1,-1,-1};
+
+ for(i=0;i<8;i++){
+ tempmapptr = edgemapptr - y[i]*cols + x[i];
+ tempmagptr = edgemagptr - y[i]*cols + x[i];
+
+ if((*tempmapptr == POSSIBLE_EDGE) && (*tempmagptr > lowval)){
+ *tempmapptr = (unsigned char) EDGE;
+ follow_edges(tempmapptr,tempmagptr, lowval, cols);
+ }
+ }
+}
+
+void apply_hysteresis(int *mag, uint8_t *nms, int rows, int cols,
+ float tlow, float thigh, uint8_t *edge)
+{
+ int r, c, pos, numedges, highcount, lowthreshold, highthreshold,hist[32768];
+ int maximum_mag;
+
+ /****************************************************************************
+ * Initialize the edge map to possible edges everywhere the non-maximal
+ * suppression suggested there could be an edge except for the border. At
+ * the border we say there can not be an edge because it makes the
+ * follow_edges algorithm more efficient to not worry about tracking an
+ * edge off the side of the image.
+ ****************************************************************************/
+ for(r=0,pos=0;r<rows;r++){
+ for(c=0;c<cols;c++,pos++){
+ if(nms[pos] == POSSIBLE_EDGE) edge[pos] = POSSIBLE_EDGE;
+ else edge[pos] = NOEDGE;
+ }
+ }
+
+ for(r=0,pos=0;r<rows;r++,pos+=cols){
+ edge[pos] = NOEDGE;
+ edge[pos+cols-1] = NOEDGE;
+ }
+ pos = (rows-1) * cols;
+ for(c=0;c<cols;c++,pos++){
+ edge[c] = NOEDGE;
+ edge[pos] = NOEDGE;
+ }
+
+ /****************************************************************************
+ * Compute the histogram of the magnitude image. Then use the histogram to
+ * compute hysteresis thresholds.
+ ****************************************************************************/
+ for(r=0;r<32768;r++) hist[r] = 0;
+ for(r=0,pos=0;r<rows;r++){
+ for(c=0;c<cols;c++,pos++){
+ if(edge[pos] == POSSIBLE_EDGE) hist[mag[pos]]++;
+ }
+ }
+
+ /****************************************************************************
+ * Compute the number of pixels that passed the nonmaximal suppression.
+ ****************************************************************************/
+ for(r=1,numedges=0;r<32768;r++){
+ if(hist[r] != 0) maximum_mag = r;
+ numedges += hist[r];
+ }
+
+ highcount = (int)(numedges * thigh + 0.5);
+
+ /****************************************************************************
+ * Compute the high threshold value as the (100 * thigh) percentage point
+ * in the magnitude of the gradient histogram of all the pixels that passes
+ * non-maximal suppression. Then calculate the low threshold as a fraction
+ * of the computed high threshold value. John Canny said in his paper
+ * "A Computational Approach to Edge Detection" that "The ratio of the
+ * high to low threshold in the implementation is in the range two or three
+ * to one." That means that in terms of this implementation, we should
+ * choose tlow ~= 0.5 or 0.33333.
+ ****************************************************************************/
+ r = 1;
+ numedges = hist[1];
+ while((r<(maximum_mag-1)) && (numedges < highcount)){
+ r++;
+ numedges += hist[r];
+ }
+ highthreshold = r;
+ lowthreshold = (int)(highthreshold * tlow + 0.5);
+/*
+ if(VERBOSE){
+ printf("The input low and high fractions of %f and %f computed to\n",
+ tlow, thigh);
+ printf("magnitude of the gradient threshold values of: %d %d\n",
+ lowthreshold, highthreshold);
+ }
+*/
+ /****************************************************************************
+ * This loop looks for pixels above the highthreshold to locate edges and
+ * then calls follow_edges to continue the edge.
+ ****************************************************************************/
+ for(r=0,pos=0;r<rows;r++){
+ for(c=0;c<cols;c++,pos++){
+ if((edge[pos] == POSSIBLE_EDGE) && (mag[pos] >= highthreshold)){
+ edge[pos] = EDGE;
+ follow_edges((edge+pos), (mag+pos), lowthreshold, cols);
+ }
+ }
+ }
+
+ /****************************************************************************
+ * Set all the remaining possible edges to non-edges.
+ ****************************************************************************/
+ for(r=0,pos=0;r<rows;r++){
+ for(c=0;c<cols;c++,pos++) if(edge[pos] != EDGE) edge[pos] = NOEDGE;
+ }
+}
+
+/*
+ Canny edge detection - http://en.wikipedia.org/wiki/Canny_edge_detector
+
+ These are suitable values for tlow and thigh:
+ tlow 0.20-0.50
+ thigh 0.60-0.90
+*/
+ImageWrapper *Image::cannyEdgeExtract(float tlow, float thigh) {
+ // masks for sobel edge detection
+ int gx[3][3]={
+ { -1, 0, 1 },
+ { -2, 0, 2 },
+ { -1, 0, 1 }};
+ int gy[3][3]={
+ { 1, 2, 1 },
+ { 0, 0, 0 },
+ { -1, -2, -1 }};
+ int resultWidth=m_width-3;
+ int resultHeight=m_height-3;
+ int *diffx=(int *) malloc(sizeof(int)*resultHeight*resultWidth);
+ int *diffy=(int *) malloc(sizeof(int)*resultHeight*resultWidth);
+ int *mag=(int *) malloc(sizeof(int)*resultHeight*resultWidth);
+ memset(diffx, 0, sizeof(int)*resultHeight*resultWidth);
+ memset(diffy, 0, sizeof(int)*resultHeight*resultWidth);
+ memset(mag, 0, sizeof(int)*resultHeight*resultWidth);
+
+ // compute the magnitute and the x and y differences in the image
+ for(int y=0; y<m_height-3; y++) {
+ for(int x=0; x<m_width-3; x++) {
+ int resultX=0;
+ int resultY=0;
+ for(int dy=0; dy<3; dy++) {
+ for(int dx=0; dx<3; dx++) {
+ int pixel=(*this)[y+dy][x+dx];
+ resultX+=pixel*gx[dy][dx];
+ resultY+=pixel*gy[dy][dx];
+ }
+ }
+ mag[y*resultWidth+x]=abs(resultX)+abs(resultY);
+ diffx[y*resultWidth+x]=resultX;
+ diffy[y*resultWidth+x]=resultY;
+ }
+ }
+ uint8_t*nms=(uint8_t *) malloc(sizeof(uint8_t)*resultHeight*resultWidth);
+ memset(nms, 0, sizeof(uint8_t)*resultHeight*resultWidth);
+ non_max_supp(mag, diffx, diffy, resultHeight, resultWidth, nms);
+
+ free(diffx);
+ free(diffy);
+
+ uint8_t *edge=(uint8_t *) malloc(sizeof(uint8_t)*resultHeight*resultWidth);
+ memset(edge, 0, sizeof(uint8_t)*resultHeight*resultWidth);
+ apply_hysteresis(mag, nms, resultHeight, resultWidth, tlow, thigh, edge);
+
+ free(nms);
+ free(mag);
+
+ Image *result=new Image(edge, resultWidth, resultHeight, true);
+ return [ImageWrapper imageWithCPPImage:result];
+}
+
+// smooth an image using gaussian blur - required before performing canny edge detection
+ImageWrapper *Image::gaussianBlur() {
+ int blur[5][5]={
+ { 1, 4, 7, 4, 1 },
+ { 4,16,26,16, 4 },
+ { 7,26,41,26, 7 },
+ { 4,16,26,16, 4 },
+ { 1, 4, 7, 4, 1 }};
+
+ Image *result=new Image(m_width-5, m_height-5);
+ for(int y=0; y<m_height-5; y++) {
+ for(int x=0; x<m_width-5; x++) {
+ int val=0;
+ for(int dy=0; dy<5; dy++) {
+ for(int dx=0; dx<5; dx++) {
+ int pixel=(*this)[y+dy][x+dx];
+ val+=pixel*blur[dy][dx];
+ }
+ }
+ (*result)[y][x]=val/273;
+ }
+ }
+ return [ImageWrapper imageWithCPPImage:result];
+}
+
+// Histogram equalisation
+// http://en.wikipedia.org/wiki/Histogram_equalisation
+void Image::HistogramEqualisation() {
+ std::vector<int> pdf(256);
+ std::vector<int> cdf(256);
+ // compute the pdf
+ for(int i=0; i<m_height*m_width; i++) {
+ pdf[m_imageData[i]]++;
+ }
+ // compute the cdf
+ cdf[0]=pdf[0];
+ for(int i=1; i<256; i++) {
+ cdf[i]=cdf[i-1]+pdf[i];
+ }
+ // now map the pixels to the new values
+ for(int i=0; i<m_height*m_width; i++) {
+ m_imageData[i]=255*cdf[m_imageData[i]]/cdf[255];
+ }
+}
+
+// convert from a gray scale image back into a UIImage
+UIImage *Image::toUIImage() {
+ // generate space for the result
+ uint8_t *result=(uint8_t *) calloc(m_width*m_height*sizeof(uint32_t),1);
+ // process the image back to rgb
+ for(int i=0; i<m_height*m_width; i++) {
+ result[i*4]=0;
+ int val=m_imageData[i];
+ result[i*4+1]=val;
+ result[i*4+2]=val;
+ result[i*4+3]=val;
+ }
+ // create a UIImage
+ CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();
+ CGContextRef context=CGBitmapContextCreate(result, m_width, m_height, 8, m_width*sizeof(uint32_t), colorSpace, kCGBitmapByteOrder32Little|kCGImageAlphaNoneSkipLast);
+ CGImageRef image=CGBitmapContextCreateImage(context);
+ CGContextRelease(context);
+ CGColorSpaceRelease(colorSpace);
+ UIImage *resultUIImage=[UIImage imageWithCGImage:image];
+ CGImageRelease(image);
+ // make sure the data will be released by giving it to an autoreleased NSData
+ [NSData dataWithBytesNoCopy:result length:m_width*m_height];
+ return resultUIImage;
+}
+
+// helper functions for resizing
+float Image::Interpolate1(float a, float b, float c) {
+ float mu=c-floor(c);
+ return(a*(1-mu)+b*mu);
+}
+
+float Image::Interpolate2(float a, float b, float c, float d, float x, float y)
+{
+ float ab = Interpolate1(a,b,x);
+ float cd = Interpolate1(c,d,x);
+ return Interpolate1(ab,cd,y);
+}
+
+// shrink or stretch an image
+ImageWrapper *Image::resize(int newX, int newY) {
+ Image *result=new Image(newX, newY);
+ for(float y=0; y<newY; y++) {
+ for(float x=0; x<newX; x++) {
+ float srcX0=x*(float)(m_width-1)/(float)newX;
+ float srcY0=y*(float)(m_height-1)/(float)newY;
+ float srcX1=(x+1)*(float)(m_width-1)/(float)newX;
+ float srcY1=(y+1)*(float)(m_height-1)/(float)newY;
+ float val=0,count=0;
+ for(float srcY=srcY0; srcY<srcY1; srcY++) {
+ for(float srcX=srcX0; srcX<srcX1; srcX++) {
+ val+=Interpolate2((*this)[(int)srcY][(int) srcX], (*this)[(int)srcY][(int) srcX+1],
+ (*this)[(int)srcY+1][(int) srcX], (*this)[(int)srcY+1][(int) srcX+1],
+ srcX, srcY);
+ count++;
+ }
+ }
+ (*result)[(int) y][(int) x]=val/count;
+ }
+ }
+ return [ImageWrapper imageWithCPPImage:result];
+}
+
View
21 Classes/ImageProcessingAppDelegate.h
@@ -0,0 +1,21 @@
+//
+// ImageProcessingAppDelegate.h
+// ImageProcessing
+//
+// Created by Chris Greening on 14/03/2009.
+// Copyright CMG Research 2009. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface ImageProcessingAppDelegate : NSObject <UIApplicationDelegate> {
+
+ UIWindow *window;
+ UINavigationController *navigationController;
+}
+
+@property (nonatomic, retain) IBOutlet UIWindow *window;
+@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
+
+@end
+
View
38 Classes/ImageProcessingAppDelegate.m
@@ -0,0 +1,38 @@
+//
+// ImageProcessingAppDelegate.m
+// ImageProcessing
+//
+// Created by Chris Greening on 14/03/2009.
+// Copyright CMG Research 2009. All rights reserved.
+//
+
+#import "ImageProcessingAppDelegate.h"
+#import "RootViewController.h"
+
+
+@implementation ImageProcessingAppDelegate
+
+@synthesize window;
+@synthesize navigationController;
+
+
+- (void)applicationDidFinishLaunching:(UIApplication *)application {
+
+ // Configure and show the window
+ [window addSubview:[navigationController view]];
+ [window makeKeyAndVisible];
+}
+
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+ // Save data if appropriate
+}
+
+
+- (void)dealloc {
+ [navigationController release];
+ [window release];
+ [super dealloc];
+}
+
+@end
View
16 Classes/ListBundleImagesViewController.h
@@ -0,0 +1,16 @@
+//
+// ListBundleImagesViewController.h
+// SudokuScraper
+//
+// Created by Chris Greening on 19/01/2009.
+// Copyright 2009 CMG Research. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface ListBundleImagesViewController : UITableViewController {
+ NSMutableArray *bundleFiles;
+ NSString *imagesPath;
+}
+
+@end
View
158 Classes/ListBundleImagesViewController.mm
@@ -0,0 +1,158 @@
+//
+// ListBundleImagesViewController.m
+// SudokuScraper
+//
+// Created by Chris Greening on 19/01/2009.
+// Copyright 2009 CMG Research. All rights reserved.
+//
+
+#import "ListBundleImagesViewController.h"
+#import "ResultViewController.h"
+
+@implementation ListBundleImagesViewController
+
+/*
+- (id)initWithStyle:(UITableViewStyle)style {
+ // Override initWithStyle: if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
+ if (self = [super initWithStyle:style]) {
+ }
+ return self;
+}
+*/
+
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ bundleFiles = [[NSMutableArray alloc] init];
+ imagesPath=[[[NSBundle mainBundle] resourcePath] retain];
+ NSFileManager *fmanager=[NSFileManager defaultManager];
+ NSArray *directoryContents=[fmanager directoryContentsAtPath:imagesPath];
+ for(NSString *fileName in directoryContents) {
+ if([[fileName pathExtension] caseInsensitiveCompare:@"jpg"]==NSOrderedSame) {
+ [bundleFiles addObject:fileName];
+ }
+ }
+}
+
+
+/*
+- (void)viewWillAppear:(BOOL)animated {
+ [super viewWillAppear:animated];
+}
+*/
+/*
+- (void)viewDidAppear:(BOOL)animated {
+ [super viewDidAppear:animated];
+}
+*/
+/*
+- (void)viewWillDisappear:(BOOL)animated {
+ [super viewWillDisappear:animated];
+}
+*/
+/*
+- (void)viewDidDisappear:(BOOL)animated {
+ [super viewDidDisappear:animated];
+}
+*/
+
+/*
+// Override to allow orientations other than the default portrait orientation.
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
+ // Return YES for supported orientations
+ return (interfaceOrientation == UIInterfaceOrientationPortrait);
+}
+*/
+
+- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
+ // Release anything that's not essential, such as cached data
+}
+
+#pragma mark Table view methods
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
+ return 1;
+}
+
+
+// Customize the number of rows in the table view.
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+ return bundleFiles.count;
+}
+
+
+// Customize the appearance of table view cells.
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+
+ static NSString *CellIdentifier = @"Cell";
+
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+ if (cell == nil) {
+ cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
+ }
+
+ // Set up the cell...
+ cell.text=[bundleFiles objectAtIndex:indexPath.row];
+ cell.selectionStyle=UITableViewCellSelectionStyleGray;
+ return cell;
+}
+
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+ ResultViewController *resultViewController=[[[ResultViewController alloc] initWithNibName:@"ResultViewController" bundle:nil] autorelease];
+ resultViewController.view;
+ [resultViewController setImage:[UIImage imageWithContentsOfFile:[imagesPath stringByAppendingPathComponent:[bundleFiles objectAtIndex:indexPath.row]]]];
+ [self.navigationController pushViewController:resultViewController animated:YES];
+}
+
+
+/*
+// Override to support conditional editing of the table view.
+- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
+ // Return NO if you do not want the specified item to be editable.
+ return YES;
+}
+*/
+
+
+/*
+// Override to support editing the table view.
+- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
+
+ if (editingStyle == UITableViewCellEditingStyleDelete) {
+ // Delete the row from the data source
+ [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
+ }
+ else if (editingStyle == UITableViewCellEditingStyleInsert) {
+ // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
+ }
+}
+*/
+
+
+/*
+// Override to support rearranging the table view.
+- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
+}
+*/
+
+
+/*
+// Override to support conditional rearranging of the table view.
+- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
+ // Return NO if you do not want the item to be re-orderable.
+ return YES;
+}
+*/
+
+
+- (void)dealloc {
+ [bundleFiles release];
+ [imagesPath release];
+ [super dealloc];
+}
+
+
+@end
+
View
22 Classes/ResultViewController.h
@@ -0,0 +1,22 @@
+//
+// ResultViewController.h
+// ImageProcessing
+//
+// Created by Chris Greening on 08/03/2009.
+// Copyright 2009 CMG Research. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface ResultViewController : UIViewController {
+ IBOutlet UIImageView *originalImage;
+ IBOutlet UIImageView *resultImage;
+}
+
+@property(retain, nonatomic) UIImageView *resultImage;
+@property(retain, nonatomic) UIImageView *originalImage;
+
+
+-(void) setImage:(UIImage *) srcImage;
+
+@end
View
72 Classes/ResultViewController.mm
@@ -0,0 +1,72 @@
+//
+// ResultViewController.m
+// WhiteBoardGrab
+//
+// Created by Chris Greening on 08/03/2009.
+// Copyright 2009 CMG Research. All rights reserved.
+//
+
+#import "ResultViewController.h"
+#import "Image.h"
+
+@implementation ResultViewController
+
+@synthesize originalImage;
+@synthesize resultImage;
+
+/*
+// The designated initializer. Override to perform setup that is required before the view is loaded.
+- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
+ if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
+ // Custom initialization
+ }
+ return self;
+}
+*/
+
+/*
+// Implement loadView to create a view hierarchy programmatically, without using a nib.
+- (void)loadView {
+}
+*/
+
+/*
+// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
+- (void)viewDidLoad {
+ [super viewDidLoad];
+}
+*/
+
+/*
+// Override to allow orientations other than the default portrait orientation.
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
+ // Return YES for supported orientations
+ return (interfaceOrientation == UIInterfaceOrientationPortrait);
+}
+*/
+
+- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
+ // Release anything that's not essential, such as cached data
+}
+
+-(void) setImage:(UIImage *) srcImage {
+ originalImage.image=srcImage;
+ // convert to grey scale and shrink the image by 4 - this makes processing a lot faster!
+ ImageWrapper *greyScale=Image::createImage(srcImage, srcImage.size.width/4, srcImage.size.height/4);
+ // you can play around with the numbers to see how it effects the edge extraction
+ // typical numbers are tlow 0.20-0.50, thigh 0.60-0.90
+ ImageWrapper *edges=greyScale.image->gaussianBlur().image->cannyEdgeExtract(0.3,0.7);
+ // show the results
+ resultImage.image=edges.image->toUIImage();
+}
+
+
+- (void)dealloc {
+ [originalImage release];
+ [resultImage release];
+ [super dealloc];
+}
+
+
+@end
View
14 Classes/RootViewController.h
@@ -0,0 +1,14 @@
+//
+// RootViewController.h
+// ImageProcessing
+//
+// Created by Chris Greening on 14/03/2009.
+// Copyright CMG Research 2009. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface RootViewController : UITableViewController {
+}
+
+@end
View
141 Classes/RootViewController.m
@@ -0,0 +1,141 @@
+//
+// RootViewController.m
+// ImageProcessing
+//
+// Created by Chris Greening on 14/03/2009.
+// Copyright CMG Research 2009. All rights reserved.
+//
+
+#import "RootViewController.h"
+#import "ImageProcessingAppDelegate.h"
+
+
+@implementation RootViewController
+
+/*
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
+ // self.navigationItem.rightBarButtonItem = self.editButtonItem;
+}
+*/
+
+/*
+- (void)viewWillAppear:(BOOL)animated {
+ [super viewWillAppear:animated];
+}
+*/
+/*
+- (void)viewDidAppear:(BOOL)animated {
+ [super viewDidAppear:animated];
+}
+*/
+/*
+- (void)viewWillDisappear:(BOOL)animated {
+ [super viewWillDisappear:animated];
+}
+*/
+/*
+- (void)viewDidDisappear:(BOOL)animated {
+ [super viewDidDisappear:animated];
+}
+*/
+
+/*
+// Override to allow orientations other than the default portrait orientation.
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
+ // Return YES for supported orientations
+ return (interfaceOrientation == UIInterfaceOrientationPortrait);
+}
+*/
+
+- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
+ // Release anything that's not essential, such as cached data
+}
+
+#pragma mark Table view methods
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
+ return 1;
+}
+
+
+// Customize the number of rows in the table view.
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+ return 0;
+}
+
+
+// Customize the appearance of table view cells.
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+
+ static NSString *CellIdentifier = @"Cell";
+
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+ if (cell == nil) {
+ cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
+ }
+
+ // Set up the cell...
+
+ return cell;
+}
+
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+ // Navigation logic may go here. Create and push another view controller.
+ // AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:@"AnotherView" bundle:nil];
+ // [self.navigationController pushViewController:anotherViewController];
+ // [anotherViewController release];
+}
+
+
+/*
+// Override to support conditional editing of the table view.
+- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
+ // Return NO if you do not want the specified item to be editable.
+ return YES;
+}
+*/
+
+
+/*
+// Override to support editing the table view.
+- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
+
+ if (editingStyle == UITableViewCellEditingStyleDelete) {
+ // Delete the row from the data source
+ [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
+ }
+ else if (editingStyle == UITableViewCellEditingStyleInsert) {
+ // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
+ }
+}
+*/
+
+
+/*
+// Override to support rearranging the table view.
+- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
+}
+*/
+
+
+/*
+// Override to support conditional rearranging of the table view.
+- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
+ // Return NO if you do not want the item to be re-orderable.
+ return YES;
+}
+*/
+
+
+- (void)dealloc {
+ [super dealloc];
+}
+
+
+@end
+
View
1,415 ImageProcessing.xcodeproj/chris.mode1v3
@@ -0,0 +1,1415 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>ActivePerspectiveName</key>
+ <string>Project</string>
+ <key>AllowedModules</key>
+ <array>
+ <dict>
+ <key>BundleLoadPath</key>
+ <string></string>
+ <key>MaxInstances</key>
+ <string>n</string>
+ <key>Module</key>
+ <string>PBXSmartGroupTreeModule</string>
+ <key>Name</key>
+ <string>Groups and Files Outline View</string>
+ </dict>
+ <dict>
+ <key>BundleLoadPath</key>
+ <string></string>
+ <key>MaxInstances</key>
+ <string>n</string>
+ <key>Module</key>
+ <string>PBXNavigatorGroup</string>
+ <key>Name</key>
+ <string>Editor</string>
+ </dict>
+ <dict>
+ <key>BundleLoadPath</key>
+ <string></string>
+ <key>MaxInstances</key>
+ <string>n</string>
+ <key>Module</key>
+ <string>XCTaskListModule</string>
+ <key>Name</key>
+ <string>Task List</string>
+ </dict>
+ <dict>
+ <key>BundleLoadPath</key>
+ <string></string>
+ <key>MaxInstances</key>
+ <string>n</string>
+ <key>Module</key>
+ <string>XCDetailModule</string>
+ <key>Name</key>
+ <string>File and Smart Group Detail Viewer</string>
+ </dict>
+ <dict>
+ <key>BundleLoadPath</key>
+ <string></string>
+ <key>MaxInstances</key>
+ <string>1</string>
+ <key>Module</key>
+ <string>PBXBuildResultsModule</string>
+ <key>Name</key>
+ <string>Detailed Build Results Viewer</string>
+ </dict>
+ <dict>
+ <key>BundleLoadPath</key>
+ <string></string>
+ <key>MaxInstances</key>
+ <string>1</string>
+ <key>Module</key>
+ <string>PBXProjectFindModule</string>
+ <key>Name</key>
+ <string>Project Batch Find Tool</string>
+ </dict>
+ <dict>
+ <key>BundleLoadPath</key>
+ <string></string>
+ <key>MaxInstances</key>
+ <string>n</string>
+ <key>Module</key>
+ <string>XCProjectFormatConflictsModule</string>
+ <key>Name</key>
+ <string>Project Format Conflicts List</string>
+ </dict>
+ <dict>
+ <key>BundleLoadPath</key>
+ <string></string>
+ <key>MaxInstances</key>
+ <string>n</string>
+ <key>Module</key>
+ <string>PBXBookmarksModule</string>
+ <key>Name</key>
+ <string>Bookmarks Tool</string>
+ </dict>
+ <dict>
+ <key>BundleLoadPath</key>
+ <string></string>
+ <key>MaxInstances</key>
+ <string>n</string>
+ <key>Module</key>
+ <string>PBXClassBrowserModule</string>
+ <key>Name</key>
+ <string>Class Browser</string>
+ </dict>
+ <dict>
+ <key>BundleLoadPath</key>
+ <string></string>
+ <key>MaxInstances</key>
+ <string>n</string>
+ <key>Module</key>
+ <string>PBXCVSModule</string>
+ <key>Name</key>
+ <string>Source Code Control Tool</string>
+ </dict>
+ <dict>
+ <key>BundleLoadPath</key>
+ <string></string>
+ <key>MaxInstances</key>
+ <string>n</string>
+ <key>Module</key>
+ <string>PBXDebugBreakpointsModule</string>
+ <key>Name</key>
+ <string>Debug Breakpoints Tool</string>
+ </dict>
+ <dict>
+ <key>BundleLoadPath</key>
+ <string></string>
+ <key>MaxInstances</key>
+ <string>n</string>
+ <key>Module</key>
+ <string>XCDockableInspector</string>
+ <key>Name</key>
+ <string>Inspector</string>
+ </dict>
+ <dict>
+ <key>BundleLoadPath</key>
+ <string></string>
+ <key>MaxInstances</key>
+ <string>n</string>
+ <key>Module</key>
+ <string>PBXOpenQuicklyModule</string>
+ <key>Name</key>
+ <string>Open Quickly Tool</string>
+ </dict>
+ <dict>
+ <key>BundleLoadPath</key>
+ <string></string>
+ <key>MaxInstances</key>
+ <string>1</string>
+ <key>Module</key>
+ <string>PBXDebugSessionModule</string>
+ <key>Name</key>
+ <string>Debugger</string>
+ </dict>
+ <dict>
+ <key>BundleLoadPath</key>
+ <string></string>
+ <key>MaxInstances</key>
+ <string>1</string>
+ <key>Module</key>
+ <string>PBXDebugCLIModule</string>
+ <key>Name</key>
+ <string>Debug Console</string>
+ </dict>
+ <dict>
+ <key>BundleLoadPath</key>
+ <string></string>
+ <key>MaxInstances</key>
+ <string>n</string>
+ <key>Module</key>
+ <string>XCSnapshotModule</string>
+ <key>Name</key>
+ <string>Snapshots Tool</string>
+ </dict>
+ </array>
+ <key>BundlePath</key>
+ <string>/Developer/Library/PrivateFrameworks/DevToolsInterface.framework/Resources</string>
+ <key>Description</key>
+ <string>DefaultDescriptionKey</string>
+ <key>DockingSystemVisible</key>
+ <false/>
+ <key>Extension</key>
+ <string>mode1v3</string>
+ <key>FavBarConfig</key>
+ <dict>
+ <key>PBXProjectModuleGUID</key>
+ <string>839D2F730F6BC9B9000E4323</string>
+ <key>XCBarModuleItemNames</key>
+ <dict/>
+ <key>XCBarModuleItems</key>
+ <array/>
+ </dict>
+ <key>FirstTimeWindowDisplayed</key>
+ <false/>
+ <key>Identifier</key>
+ <string>com.apple.perspectives.project.mode1v3</string>
+ <key>MajorVersion</key>
+ <integer>33</integer>
+ <key>MinorVersion</key>
+ <integer>0</integer>
+ <key>Name</key>
+ <string>Default</string>
+ <key>Notifications</key>
+ <array/>
+ <key>OpenEditors</key>
+ <array/>
+ <key>PerspectiveWidths</key>
+ <array>
+ <integer>-1</integer>
+ <integer>-1</integer>
+ </array>
+ <key>Perspectives</key>
+ <array>
+ <dict>
+ <key>ChosenToolbarItems</key>
+ <array>
+ <string>active-combo-popup</string>
+ <string>action</string>
+ <string>NSToolbarFlexibleSpaceItem</string>
+ <string>buildOrClean</string>
+ <string>build-and-go</string>
+ <string>com.apple.ide.PBXToolbarStopButton</string>
+ <string>get-info</string>
+ <string>NSToolbarFlexibleSpaceItem</string>
+ <string>com.apple.pbx.toolbar.searchfield</string>
+ </array>
+ <key>ControllerClassBaseName</key>
+ <string></string>
+ <key>IconName</key>
+ <string>WindowOfProjectWithEditor</string>
+ <key>Identifier</key>
+ <string>perspective.project</string>
+ <key>IsVertical</key>
+ <false/>
+ <key>Layout</key>
+ <array>
+ <dict>
+ <key>BecomeActive</key>
+ <true/>
+ <key>ContentConfiguration</key>
+ <dict>
+ <key>PBXBottomSmartGroupGIDs</key>
+ <array>
+ <string>1C37FBAC04509CD000000102</string>
+ <string>1C37FAAC04509CD000000102</string>
+ <string>1C08E77C0454961000C914BD</string>
+ <string>1C37FABC05509CD000000102</string>
+ <string>1C37FABC05539CD112110102</string>
+ <string>E2644B35053B69B200211256</string>
+ <string>1C37FABC04509CD000100104</string>
+ <string>1CC0EA4004350EF90044410B</string>
+ <string>1CC0EA4004350EF90041110B</string>
+ </array>
+ <key>PBXProjectModuleGUID</key>
+ <string>1CE0B1FE06471DED0097A5F4</string>
+ <key>PBXProjectModuleLabel</key>
+ <string>Files</string>
+ <key>PBXProjectStructureProvided</key>
+ <string>yes</string>
+ <key>PBXSmartGroupTreeModuleColumnData</key>
+ <dict>
+ <key>PBXSmartGroupTreeModuleColumnWidthsKey</key>
+ <array>
+ <real>280</real>
+ </array>
+ <key>PBXSmartGroupTreeModuleColumnsKey_v4</key>
+ <array>
+ <string>MainColumn</string>
+ </array>
+ </dict>
+ <key>PBXSmartGroupTreeModuleOutlineStateKey_v7</key>
+ <dict>
+ <key>PBXSmartGroupTreeModuleOutlineStateExpansionKey</key>
+ <array>
+ <string>29B97314FDCFA39411CA2CEA</string>
+ <string>080E96DDFE201D6D7F000001</string>
+ <string>29B97317FDCFA39411CA2CEA</string>
+ <string>1C37FABC05509CD000000102</string>
+ </array>
+ <key>PBXSmartGroupTreeModuleOutlineStateSelectionKey</key>
+ <array>
+ <array>
+ <integer>2</integer>
+ <integer>1</integer>
+ <integer>0</integer>
+ </array>
+ </array>
+ <key>PBXSmartGroupTreeModuleOutlineStateVisibleRectKey</key>
+ <string>{{0, 0}, {280, 660}}</string>
+ </dict>
+ <key>PBXTopSmartGroupGIDs</key>
+ <array/>
+ <key>XCIncludePerspectivesSwitch</key>
+ <true/>
+ <key>XCSharingToken</key>
+ <string>com.apple.Xcode.GFSharingToken</string>
+ </dict>
+ <key>GeometryConfiguration</key>
+ <dict>
+ <key>Frame</key>
+ <string>{{0, 0}, {297, 678}}</string>
+ <key>GroupTreeTableConfiguration</key>
+ <array>
+ <string>MainColumn</string>
+ <real>280</real>
+ </array>
+ <key>RubberWindowFrame</key>
+ <string>-1 59 1280 719 0 0 1280 778 </string>
+ </dict>
+ <key>Module</key>
+ <string>PBXSmartGroupTreeModule</string>
+ <key>Proportion</key>
+ <string>297pt</string>
+ </dict>
+ <dict>
+ <key>Dock</key>
+ <array>
+ <dict>
+ <key>ContentConfiguration</key>
+ <dict>
+ <key>PBXProjectModuleGUID</key>
+ <string>1CE0B20306471E060097A5F4</string>
+ <key>PBXProjectModuleLabel</key>
+ <string>Image.h</string>
+ <key>PBXSplitModuleInNavigatorKey</key>
+ <dict>
+ <key>Split0</key>
+ <dict>
+ <key>PBXProjectModuleGUID</key>
+ <string>1CE0B20406471E060097A5F4</string>
+ <key>PBXProjectModuleLabel</key>
+ <string>Image.h</string>
+ <key>_historyCapacity</key>
+ <integer>0</integer>
+ <key>bookmark</key>
+ <string>839D30640F6BD440000E4323</string>
+ <key>history</key>
+ <array>
+ <string>839D30100F6BCD7E000E4323</string>
+ <string>839D30120F6BCD7E000E4323</string>
+ <string>839D302A0F6BCE61000E4323</string>
+ <string>839D304F0F6BD3A8000E4323</string>
+ <string>839D305E0F6BD440000E4323</string>
+ <string>839D305F0F6BD440000E4323</string>
+ <string>839D30600F6BD440000E4323</string>
+ </array>
+ <key>prevStack</key>
+ <array>
+ <string>839D2F8A0F6BCA31000E4323</string>
+ <string>839D2F8B0F6BCA31000E4323</string>
+ <string>839D2FCC0F6BCB22000E4323</string>
+ <string>839D2FCE0F6BCB22000E4323</string>
+ <string>839D2FD00F6BCB22000E4323</string>
+ <string>839D2FDD0F6BCC3A000E4323</string>
+ <string>839D2FEC0F6BCCCD000E4323</string>
+ <string>839D2FED0F6BCCCD000E4323</string>
+ <string>839D30150F6BCD7E000E4323</string>
+ <string>839D30170F6BCD7E000E4323</string>
+ <string>839D30180F6BCD7E000E4323</string>
+ <string>839D30190F6BCD7E000E4323</string>
+ <string>839D302C0F6BCE61000E4323</string>
+ <string>839D302D0F6BCE61000E4323</string>
+ <string>839D30530F6BD3A8000E4323</string>
+ <string>839D30550F6BD3A8000E4323</string>
+ <string>839D30610F6BD440000E4323</string>
+ <string>839D30620F6BD440000E4323</string>
+ <string>839D30630F6BD440000E4323</string>
+ </array>
+ </dict>
+ <key>SplitCount</key>
+ <string>1</string>
+ </dict>
+ <key>StatusBarVisibility</key>
+ <true/>
+ </dict>
+ <key>GeometryConfiguration</key>
+ <dict>
+ <key>Frame</key>
+ <string>{{0, 0}, {978, 500}}</string>
+ <key>RubberWindowFrame</key>
+ <string>-1 59 1280 719 0 0 1280 778 </string>
+ </dict>
+ <key>Module</key>
+ <string>PBXNavigatorGroup</string>
+ <key>Proportion</key>
+ <string>500pt</string>
+ </dict>
+ <dict>
+ <key>ContentConfiguration</key>
+ <dict>
+ <key>PBXProjectModuleGUID</key>
+ <string>1CE0B20506471E060097A5F4</string>
+ <key>PBXProjectModuleLabel</key>
+ <string>Detail</string>
+ </dict>
+ <key>GeometryConfiguration</key>
+ <dict>
+ <key>Frame</key>
+ <string>{{0, 505}, {978, 173}}</string>
+ <key>RubberWindowFrame</key>
+ <string>-1 59 1280 719 0 0 1280 778 </string>
+ </dict>
+ <key>Module</key>
+ <string>XCDetailModule</string>
+ <key>Proportion</key>
+ <string>173pt</string>
+ </dict>
+ </array>
+ <key>Proportion</key>
+ <string>978pt</string>
+ </dict>
+ </array>
+ <key>Name</key>
+ <string>Project</string>
+ <key>ServiceClasses</key>
+ <array>
+ <string>XCModuleDock</string>
+ <string>PBXSmartGroupTreeModule</string>
+ <string>XCModuleDock</string>
+ <string>PBXNavigatorGroup</string>
+ <string>XCDetailModule</string>
+ </array>
+ <key>TableOfContents</key>
+ <array>
+ <string>839D2F710F6BC9B9000E4323</string>
+ <string>1CE0B1FE06471DED0097A5F4</string>
+ <string>839D2F720F6BC9B9000E4323</string>
+ <string>1CE0B20306471E060097A5F4</string>
+ <string>1CE0B20506471E060097A5F4</string>
+ </array>
+ <key>ToolbarConfiguration</key>
+ <string>xcode.toolbar.config.defaultV3</string>
+ </dict>
+ <dict>
+ <key>ControllerClassBaseName</key>
+ <string></string>
+ <key>IconName</key>
+ <string>WindowOfProject</string>
+ <key>Identifier</key>
+ <string>perspective.morph</string>
+ <key>IsVertical</key>
+ <integer>0</integer>
+ <key>Layout</key>
+ <array>
+ <dict>
+ <key>BecomeActive</key>
+ <integer>1</integer>
+ <key>ContentConfiguration</key>
+ <dict>
+ <key>PBXBottomSmartGroupGIDs</key>
+ <array>
+ <string>1C37FBAC04509CD000000102</string>
+ <string>1C37FAAC04509CD000000102</string>
+ <string>1C08E77C0454961000C914BD</string>
+ <string>1C37FABC05509CD000000102</string>
+ <string>1C37FABC05539CD112110102</string>
+ <string>E2644B35053B69B200211256</string>
+ <string>1C37FABC04509CD000100104</string>
+ <string>1CC0EA4004350EF90044410B</string>
+ <string>1CC0EA4004350EF90041110B</string>
+ </array>
+ <key>PBXProjectModuleGUID</key>
+ <string>11E0B1FE06471DED0097A5F4</string>
+ <key>PBXProjectModuleLabel</key>
+ <string>Files</string>
+ <key>PBXProjectStructureProvided</key>
+ <string>yes</string>
+ <key>PBXSmartGroupTreeModuleColumnData</key>
+ <dict>
+ <key>PBXSmartGroupTreeModuleColumnWidthsKey</key>
+ <array>
+ <real>186</real>
+ </array>
+ <key>PBXSmartGroupTreeModuleColumnsKey_v4</key>
+ <array>
+ <string>MainColumn</string>
+ </array>
+ </dict>
+ <key>PBXSmartGroupTreeModuleOutlineStateKey_v7</key>
+ <dict>
+ <key>PBXSmartGroupTreeModuleOutlineStateExpansionKey</key>
+ <array>
+ <string>29B97314FDCFA39411CA2CEA</string>
+ <string>1C37FABC05509CD000000102</string>
+ </array>
+ <key>PBXSmartGroupTreeModuleOutlineStateSelectionKey</key>
+ <array>
+ <array>
+ <integer>0</integer>
+ </array>
+ </array>
+ <key>PBXSmartGroupTreeModuleOutlineStateVisibleRectKey</key>
+ <string>{{0, 0}, {186, 337}}</string>
+ </dict>
+ <key>PBXTopSmartGroupGIDs</key>
+ <array/>
+ <key>XCIncludePerspectivesSwitch</key>
+ <integer>1</integer>
+ <key>XCSharingToken</key>
+ <string>com.apple.Xcode.GFSharingToken</string>
+ </dict>
+ <key>GeometryConfiguration</key>
+ <dict>
+ <key>Frame</key>
+ <string>{{0, 0}, {203, 355}}</string>
+ <key>GroupTreeTableConfiguration</key>
+ <array>
+ <string>MainColumn</string>
+ <real>186</real>
+ </array>
+ <key>RubberWindowFrame</key>
+ <string>373 269 690 397 0 0 1440 878 </string>
+ </dict>
+ <key>Module</key>
+ <string>PBXSmartGroupTreeModule</string>
+ <key>Proportion</key>
+ <string>100%</string>
+ </dict>
+ </array>
+ <key>Name</key>
+ <string>Morph</string>
+ <key>PreferredWidth</key>
+ <integer>300</integer>
+ <key>ServiceClasses</key>
+ <array>
+ <string>XCModuleDock</string>
+ <string>PBXSmartGroupTreeModule</string>
+ </array>
+ <key>TableOfContents</key>
+ <array>
+ <string>11E0B1FE06471DED0097A5F4</string>
+ </array>
+ <key>ToolbarConfiguration</key>
+ <string>xcode.toolbar.config.default.shortV3</string>
+ </dict>
+ </array>
+ <key>PerspectivesBarVisible</key>
+ <false/>
+ <key>ShelfIsVisible</key>
+ <false/>
+ <key>SourceDescription</key>
+ <string>file at '/Developer/Library/PrivateFrameworks/DevToolsInterface.framework/Resources/XCPerspectivesSpecificationMode1.xcperspec'</string>
+ <key>StatusbarIsVisible</key>
+ <true/>
+ <key>TimeStamp</key>
+ <real>0.0</real>
+ <key>ToolbarDisplayMode</key>
+ <integer>1</integer>
+ <key>ToolbarIsVisible</key>
+ <true/>
+ <key>ToolbarSizeMode</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Perspectives</string>
+ <key>UpdateMessage</key>
+ <string>The Default Workspace in this version of Xcode now includes support to hide and show the detail view (what has been referred to as the "Metro-Morph" feature). You must discard your current Default Workspace settings and update to the latest Default Workspace in order to gain this feature. Do you wish to update to the latest Workspace defaults for project '%@'?</string>
+ <key>WindowJustification</key>
+ <integer>5</integer>
+ <key>WindowOrderList</key>
+ <array>
+ <string>839D2FE30F6BCC3A000E4323</string>
+ <string>839D2FE40F6BCC3A000E4323</string>
+ <string>1C530D57069F1CE1000CFCEE</string>
+ <string>839D2F740F6BC9B9000E4323</string>
+ <string>1C78EAAD065D492600B07095</string>
+ <string>1CD10A99069EF8BA00B06720</string>
+ <string>/Users/chris/dropbox/Dropbox/Work/ImageProcessing/ImageProcessing.xcodeproj</string>
+ </array>
+ <key>WindowString</key>
+ <string>-1 59 1280 719 0 0 1280 778 </string>
+ <key>WindowToolsV3</key>
+ <array>
+ <dict>
+ <key>FirstTimeWindowDisplayed</key>
+ <false/>
+ <key>Identifier</key>
+ <string>windowTool.build</string>
+ <key>IsVertical</key>
+ <true/>
+ <key>Layout</key>
+ <array>
+ <dict>
+ <key>Dock</key>
+ <array>
+ <dict>
+ <key>ContentConfiguration</key>
+ <dict>
+ <key>PBXProjectModuleGUID</key>
+ <string>1CD0528F0623707200166675</string>
+ <key>PBXProjectModuleLabel</key>
+ <string></string>
+ <key>StatusBarVisibility</key>
+ <true/>
+ </dict>
+ <key>GeometryConfiguration</key>
+ <dict>
+ <key>Frame</key>
+ <string>{{0, 0}, {500, 218}}</string>
+ <key>RubberWindowFrame</key>
+ <string>194 107 500 500 0 0 1280 778 </string>
+ </dict>
+ <key>Module</key>
+ <string>PBXNavigatorGroup</string>
+ <key>Proportion</key>
+ <string>218pt</string>
+ </dict>
+ <dict>
+ <key>ContentConfiguration</key>
+ <dict>
+ <key>PBXProjectModuleGUID</key>
+ <string>XCMainBuildResultsModuleGUID</string>
+ <key>PBXProjectModuleLabel</key>
+ <string>Build</string>
+ <key>XCBuildResultsTrigger_Collapse</key>
+ <integer>1021</integer>
+ <key>XCBuildResultsTrigger_Open</key>
+ <integer>1011</integer>
+ </dict>
+ <key>GeometryConfiguration</key>
+ <dict>
+ <key>Frame</key>
+ <string>{{0, 223}, {500, 236}}</string>
+ <key>RubberWindowFrame</key>
+ <string>194 107 500 500 0 0 1280 778 </string>
+ </dict>
+ <key>Module</key>
+ <string>PBXBuildResultsModule</string>
+ <key>Proportion</key>
+ <string>236pt</string>
+ </dict>
+ </array>
+ <key>Proportion</key>
+ <string>459pt</string>
+ </dict>
+ </array>
+ <key>Name</key>
+ <string>Build Results</string>
+ <key>ServiceClasses</key>
+ <array>
+ <string>PBXBuildResultsModule</string>
+ </array>
+ <key>StatusbarIsVisible</key>
+ <true/>
+ <key>TableOfContents</key>
+ <array>
+ <string>839D2F740F6BC9B9000E4323</string>
+ <string>839D2F750F6BC9B9000E4323</string>
+ <string>1CD0528F0623707200166675</string>
+ <string>XCMainBuildResultsModuleGUID</string>
+ </array>
+ <key>ToolbarConfiguration</key>
+ <string>xcode.toolbar.config.buildV3</string>
+ <key>WindowString</key>
+ <string>194 107 500 500 0 0 1280 778 </string>
+ <key>WindowToolGUID</key>
+ <string>839D2F740F6BC9B9000E4323</string>
+ <key>WindowToolIsVisible</key>
+ <false/>
+ </dict>
+ <dict>
+ <key>FirstTimeWindowDisplayed</key>
+ <false/>
+ <key>Identifier</key>
+ <string>windowTool.debugger</string>
+ <key>IsVertical</key>
+ <true/>
+ <key>Layout</key>
+ <array>
+ <dict>
+ <key>Dock</key>
+ <array>
+ <dict>
+ <key>ContentConfiguration</key>
+ <dict>
+ <key>Debugger</key>
+ <dict>
+ <key>HorizontalSplitView</key>
+ <dict>
+ <key>_collapsingFrameDimension</key>
+ <real>0.0</real>
+ <key>_indexOfCollapsedView</key>
+ <integer>0</integer>
+ <key>_percentageOfCollapsedView</key>
+ <real>0.0</real>
+ <key>isCollapsed</key>
+ <string>yes</string>
+ <key>sizes</key>
+ <array>
+ <string>{{0, 0}, {316, 185}}</string>
+ <string>{{316, 0}, {378, 185}}</string>
+ </array>
+ </dict>
+ <key>VerticalSplitView</key>
+ <dict>
+ <key>_collapsingFrameDimension</key>
+ <real>0.0</real>
+ <key>_indexOfCollapsedView</key>
+ <integer>0</integer>
+ <key>_percentageOfCollapsedView</key>
+ <real>0.0</real>
+ <key>isCollapsed</key>
+ <string>yes</string>
+ <key>sizes</key>
+ <array>
+ <string>{{0, 0}, {694, 185}}</string>
+ <string>{{0, 185}, {694, 196}}</string>
+ </array>
+ </dict>
+ </dict>
+ <key>LauncherConfigVersion</key>
+ <string>8</string>
+ <key>PBXProjectModuleGUID</key>
+ <string>1C162984064C10D400B95A72</string>
+ <key>PBXProjectModuleLabel</key>
+ <string>Debug - GLUTExamples (Underwater)</string>
+ </dict>
+ <key>GeometryConfiguration</key>
+ <dict>
+ <key>DebugConsoleVisible</key>
+ <string>None</string>
+ <key>DebugConsoleWindowFrame</key>
+ <string>{{200, 200}, {500, 300}}</string>
+ <key>DebugSTDIOWindowFrame</key>
+ <string>{{200, 200}, {500, 300}}</string>
+ <key>Frame</key>
+ <string>{{0, 0}, {694, 381}}</string>
+ <key>PBXDebugSessionStackFrameViewKey</key>
+ <dict>
+ <key>DebugVariablesTableConfiguration</key>
+ <array>
+ <string>Name</string>
+ <real>120</real>
+ <string>Value</string>
+ <real>85</real>
+ <string>Summary</string>
+ <real>148</real>
+ </array>
+ <key>Frame</key>
+ <string>{{316, 0}, {378, 185}}</string>
+ <key>RubberWindowFrame</key>
+ <string>20 333 694 422 0 0 1280 778 </string>
+ </dict>
+ <key>RubberWindowFrame</key>
+ <string>20 333 694 422 0 0 1280 778 </string>
+ </dict>
+ <key>Module</key>
+ <string>PBXDebugSessionModule</string>
+ <key>Proportion</key>
+ <string>381pt</string>
+ </dict>
+ </array>
+ <key>Proportion</key>
+ <string>381pt</string>
+ </dict>
+ </array>
+ <key>Name</key>
+ <string>Debugger</string>
+ <key>ServiceClasses</key>
+ <array>
+ <string>PBXDebugSessionModule</string>
+ </array>
+ <key>StatusbarIsVisible</key>
+ <true/>
+ <key>TableOfContents</key>
+ <array>
+ <string>1CD10A99069EF8BA00B06720</string>
+ <string>839D2F900F6BCAB5000E4323</string>
+ <string>1C162984064C10D400B95A72</string>
+ <string>839D2F910F6BCAB5000E4323</string>
+ <string>839D2F920F6BCAB5000E4323</string>
+ <string>839D2F930F6BCAB5000E4323</string>
+ <string>839D2F940F6BCAB5000E4323</string>
+ <string>839D2F950F6BCAB5000E4323</string>
+ </array>
+ <key>ToolbarConfiguration</key>
+ <string>xcode.toolbar.config.debugV3</string>
+ <key>WindowString</key>
+ <string>20 333 694 422 0 0 1280 778 </string>
+ <key>WindowToolGUID</key>
+ <string>1CD10A99069EF8BA00B06720</string>
+ <key>WindowToolIsVisible</key>
+ <true/>
+ </dict>
+ <dict>
+ <key>FirstTimeWindowDisplayed</key>
+ <false/>
+ <key>Identifier</key>
+ <string>windowTool.find</string>
+ <key>IsVertical</key>
+ <true/>
+ <key>Layout</key>
+ <array>
+ <dict>
+ <key>Dock</key>
+ <array>
+ <dict>
+ <key>Dock</key>
+ <array>
+ <dict>
+ <key>BecomeActive</key>
+ <true/>
+ <key>ContentConfiguration</key>
+ <dict>
+ <key>PBXProjectModuleGUID</key>
+ <string>1CDD528C0622207200134675</string>
+ <key>PBXProjectModuleLabel</key>
+ <string>ResultViewController.h</string>
+ <key>StatusBarVisibility</key>
+ <true/>
+ </dict>
+ <key>GeometryConfiguration</key>
+ <dict>
+ <key>Frame</key>
+ <string>{{0, 0}, {781, 212}}</string>
+ <key>RubberWindowFrame</key>
+ <string>21 285 781 470 0 0 1280 778 </string>
+ </dict>
+ <key>Module</key>
+ <string>PBXNavigatorGroup</string>
+ <key>Proportion</key>
+ <string>781pt</string>
+ </dict>
+ </array>
+ <key>Proportion</key>
+ <string>212pt</string>
+ </dict>
+ <dict>
+ <key>ContentConfiguration</key>
+ <dict>
+ <key>PBXProjectModuleGUID</key>
+ <string>1CD0528E0623707200166675</string>
+ <key>PBXProjectModuleLabel</key>
+ <string>Project Find</string>
+ </dict>
+ <key>GeometryConfiguration</key>
+ <dict>
+ <key>Frame</key>
+ <string>{{0, 217}, {781, 212}}</string>
+ <key>RubberWindowFrame</key>
+ <string>21 285 781 470 0 0 1280 778 </string>
+ </dict>
+ <key>Module</key>
+ <string>PBXProjectFindModule</string>
+ <key>Proportion</key>
+ <string>212pt</string>
+ </dict>
+ </array>
+ <key>Proportion</key>
+ <string>429pt</string>
+ </dict>
+ </array>
+ <key>Name</key>
+ <string>Project Find</string>
+ <key>ServiceClasses</key>
+ <array>
+ <string>PBXProjectFindModule</string>
+ </array>
+ <key>StatusbarIsVisible</key>
+ <true/>
+ <key>TableOfContents</key>
+ <array>
+ <string>1C530D57069F1CE1000CFCEE</string>
+ <string>839D2F9A0F6BCAB5000E4323</string>
+ <string>839D2F9B0F6BCAB5000E4323</string>
+ <string>1CDD528C0622207200134675</string>
+ <string>1CD0528E0623707200166675</string>
+ </array>
+ <key>WindowString</key>
+ <string>21 285 781 470 0 0 1280 778 </string>
+ <key>WindowToolGUID</key>
+ <string>1C530D57069F1CE1000CFCEE</string>
+ <key>WindowToolIsVisible</key>
+ <false/>
+ </dict>
+ <dict>
+ <key>Identifier</key>
+ <string>MENUSEPARATOR</string>
+ </dict>
+ <dict>
+ <key>FirstTimeWindowDisplayed</key>
+ <false/>
+ <key>Identifier</key>
+ <string>windowTool.debuggerConsole</string>
+ <key>IsVertical</key>
+ <true/>
+ <key>Layout</key>
+ <array>
+ <dict>
+ <key>Dock</key>
+ <array>
+ <dict>
+ <key>BecomeActive</key>
+ <true/>
+ <key>ContentConfiguration</key>
+ <dict>
+ <key>PBXProjectModuleGUID</key>
+ <string>1C78EAAC065D492600B07095</string>
+ <key>PBXProjectModuleLabel</key>
+ <string>Debugger Console</string>
+ </dict>
+ <key>GeometryConfiguration</key>
+ <dict>
+ <key>Frame</key>
+ <string>{{0, 0}, {650, 209}}</string>
+ <key>RubberWindowFrame</key>
+ <string>21 505 650 250 0 0 1280 778 </string>
+ </dict>
+ <key>Module</key>
+ <string>PBXDebugCLIModule</string>
+ <key>Proportion</key>
+ <string>209pt</string>
+ </dict>
+ </array>
+ <key>Proportion</key>
+ <string>209pt</string>
+ </dict>
+ </array>
+ <key>Name</key>
+ <string>Debugger Console</string>
+ <key>ServiceClasses</key>
+ <array>
+ <string>PBXDebugCLIModule</string>
+ </array>
+ <key>StatusbarIsVisible</key>
+ <true/>
+ <key>TableOfContents</key>
+ <array>
+ <string>1C78EAAD065D492600B07095</string>
+ <string>839D2FE00F6BCC3A000E4323</string>
+ <string>1C78EAAC065D492600B07095</string>
+ </array>
+ <key>ToolbarConfiguration</key>
+ <string>xcode.toolbar.config.consoleV3</string>
+ <key>WindowString</key>
+ <string>21 505 650 250 0 0 1280 778 </string>
+ <key>WindowToolGUID</key>
+ <string>1C78EAAD065D492600B07095</string>
+ <key>WindowToolIsVisible</key>
+ <true/>
+ </dict>
+ <dict>
+ <key>Identifier</key>
+ <string>windowTool.snapshots</string>
+ <key>Layout</key>
+ <array>
+ <dict>
+ <key>Dock</key>
+ <array>
+ <dict>
+ <key>Module</key>
+ <string>XCSnapshotModule</string>
+ <key>Proportion</key>
+ <string>100%</string>
+ </dict>
+ </array>
+ <key>Proportion</key>
+ <string>100%</string>
+ </dict>
+ </array>
+ <key>Name</key>
+ <string>Snapshots</string>
+ <key>ServiceClasses</key>
+ <array>
+ <string>XCSnapshotModule</string>
+ </array>
+ <key>StatusbarIsVisible</key>
+ <string>Yes</string>
+ <key>ToolbarConfiguration</key>
+ <string>xcode.toolbar.config.snapshots</string>
+ <key>WindowString</key>
+ <string>315 824 300 550 0 0 1440 878 </string>
+ <key>WindowToolIsVisible</key>
+ <string>Yes</string>
+ </dict>
+ <dict>
+ <key>Identifier</key>
+ <string>windowTool.scm</string>
+ <key>Layout</key>
+ <array>
+ <dict>
+ <key>Dock</key>
+ <array>
+ <dict>
+ <key>ContentConfiguration</key>
+ <dict>
+ <key>PBXProjectModuleGUID</key>
+ <string>1C78EAB2065D492600B07095</string>
+ <key>PBXProjectModuleLabel</key>
+ <string>&lt;No Editor&gt;</string>
+ <key>PBXSplitModuleInNavigatorKey</key>
+ <dict>
+ <key>Split0</key>
+ <dict>
+ <key>PBXProjectModuleGUID</key>
+ <string>1C78EAB3065D492600B07095</string>
+ </dict>
+ <key>SplitCount</key>
+ <string>1</string>
+ </dict>
+ <key>StatusBarVisibility</key>
+ <integer>1</integer>
+ </dict>
+ <key>GeometryConfiguration</key>
+ <dict>
+ <key>Frame</key>
+ <string>{{0, 0}, {452, 0}}</string>
+ <key>RubberWindowFrame</key>
+ <string>743 379 452 308 0 0 1280 1002 </string>
+ </dict>
+ <key>Module</key>
+ <string>PBXNavigatorGroup</string>
+ <key>Proportion</key>
+ <string>0pt</string>
+ </dict>
+ <dict>
+ <key>BecomeActive</key>
+ <integer>1</integer>
+ <key>ContentConfiguration</key>
+ <dict>
+ <key>PBXProjectModuleGUID</key>
+ <string>1CD052920623707200166675</string>
+ <key>PBXProjectModuleLabel</key>
+ <string>SCM</string>
+ </dict>
+ <key>GeometryConfiguration</key>
+ <dict>
+ <key>ConsoleFrame</key>
+ <string>{{0, 259}, {452, 0}}</string>
+ <key>Frame</key>
+ <string>{{0, 7}, {452, 259}}</string>
+ <key>RubberWindowFrame</key>
+ <string>743 379 452 308 0 0 1280 1002 </string>
+ <key>TableConfiguration</key>
+ <array>
+ <string>Status</string>
+ <real>30</real>
+ <string>FileName</string>
+ <real>199</real>
+ <string>Path</string>
+ <real>197.0950012207031</real>
+ </array>
+ <key>TableFrame</key>
+ <string>{{0, 0}, {452, 250}}</string>
+ </dict>
+ <key>Module</key>
+ <string>PBXCVSModule</string>
+ <key>Proportion</key>
+ <string>262pt</string>
+ </dict>
+ </array>
+ <key>Proportion</key>
+ <string>266pt</string>
+ </dict>
+ </array>
+ <key>Name</key>
+ <string>SCM</string>
+ <key>ServiceClasses</key>
+ <array>
+ <string>PBXCVSModule</string>
+ </array>
+ <key>StatusbarIsVisible</key>
+ <integer>1</integer>
+ <key>TableOfContents</key>
+ <array>
+ <string>1C78EAB4065D492600B07095</string>
+ <string>1C78EAB5065D492600B07095</string>
+ <string>1C78EAB2065D492600B07095</string>
+ <string>1CD052920623707200166675</string>
+ </array>
+ <key>ToolbarConfiguration</key>
+ <string>xcode.toolbar.config.scm</string>
+ <key>WindowString</key>
+ <string>743 379 452 308 0 0 1280 1002 </string>
+ </dict>
+ <dict>
+ <key>Identifier</key>
+ <string>windowTool.breakpoints</string>
+ <key>IsVertical</key>
+ <integer>0</integer>
+ <key>Layout</key>
+ <array>
+ <dict>
+ <key>Dock</key>
+ <array>
+ <dict>
+ <key>BecomeActive</key>
+ <integer>1</integer>
+ <key>ContentConfiguration</key>
+ <dict>
+ <key>PBXBottomSmartGroupGIDs</key>
+ <array>
+ <string>1C77FABC04509CD000000102</string>
+ </array>
+ <key>PBXProjectModuleGUID</key>
+ <string>1CE0B1FE06471DED0097A5F4</string>
+ <key>PBXProjectModuleLabel</key>
+ <string>Files</string>
+ <key>PBXProjectStructureProvided</key>
+ <string>no</string>
+ <key>PBXSmartGroupTreeModuleColumnData</key>
+ <dict>
+ <key>PBXSmartGroupTreeModuleColumnWidthsKey</key>
+ <array>
+ <real>168</real>
+ </array>
+ <key>PBXSmartGroupTreeModuleColumnsKey_v4</key>
+ <array>
+ <string>MainColumn</string>
+ </array>
+ </dict>
+ <key>PBXSmartGroupTreeModuleOutlineStateKey_v7</key>
+ <dict>
+ <key>PBXSmartGroupTreeModuleOutlineStateExpansionKey</key>
+ <array>
+ <string>1C77FABC04509CD000000102</string>
+ </array>
+ <key>PBXSmartGroupTreeModuleOutlineStateSelectionKey</key>
+ <array>
+ <array>
+ <integer>0</integer>
+ </array>
+ </array>
+ <key>PBXSmartGroupTreeModuleOutlineStateVisibleRectKey</key>
+ <string>{{0, 0}, {168, 350}}</string>
+ </dict>
+ <key>PBXTopSmartGroupGIDs</key>
+ <array/>
+ <key>XCIncludePerspectivesSwitch</key>
+ <integer>0</integer>
+ </dict>
+ <key>GeometryConfiguration</key>
+ <dict>
+ <key>Frame</key>
+ <string>{{0, 0}, {185, 368}}</string>
+ <key>GroupTreeTableConfiguration</key>
+ <array>
+ <string>MainColumn</string>
+ <real>168</real>
+ </array>
+ <key>RubberWindowFrame</key>
+ <string>315 424 744 409 0 0 1440 878 </string>
+ </dict>
+ <key>Module</key>
+ <string>PBXSmartGroupTreeModule</string>
+ <key>Proportion</key>
+ <string>185pt</string>
+ </dict>
+ <dict>
+ <key>ContentConfiguration</key>
+ <dict>
+ <key>PBXProjectModuleGUID</key>
+ <string>1CA1AED706398EBD00589147</string>
+ <key>PBXProjectModuleLabel</key>
+ <string>Detail</string>
+ </dict>
+ <key>GeometryConfiguration</key>
+ <dict>
+ <key>Frame</key>
+ <string>{{190, 0}, {554, 368}}</string>
+ <key>RubberWindowFrame</key>
+ <string>315 424 744 409 0 0 1440 878 </string>
+ </dict>
+ <key>Module</key>
+ <string>XCDetailModule</string>
+ <key>Proportion</key>
+ <string>554pt</string>
+ </dict>
+ </array>
+ <key>Proportion</key>