- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { CGRect bounds = [self bounds]; UITouch* touch = [[event touchesForView:self] anyObject]; firstTouch = YES; // Convert touch point from UIView referential to OpenGL one (upside- down flip) location = [touch locationInView:self]; location.y = bounds.size.height - location.y; } // Handles the continuation of a touch. - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { CGRect bounds = [self bounds]; UITouch* touch = [[event touchesForView:self] anyObject]; // Convert touch point from UIView referential to OpenGL one (upside- down flip) if (firstTouch) { firstTouch = NO; previousLocation = [touch previousLocationInView:self]; previousLocation.y = bounds.size.height - previousLocation.y; } else { location = [touch locationInView:self]; location.y = bounds.size.height - location.y; previousLocation = [touch previousLocationInView:self]; previousLocation.y = bounds.size.height - previousLocation.y; } // Render the stroke [self renderLineFromPoint:previousLocation toPoint:location]; } // Handles the end of a touch event when the touch is a tap. - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { CGRect bounds = [self bounds]; UITouch* touch = [[event touchesForView:self] anyObject]; if (firstTouch) { firstTouch = NO; previousLocation = [touch previousLocationInView:self]; previousLocation.y = bounds.size.height - previousLocation.y; [self renderLineFromPoint:previousLocation toPoint:location]; } }// Drawings a line onscreen based on where the user touches - (void) renderLineFromPoint:(CGPoint)start toPoint:(CGPoint)end { static GLfloat* vertexBuffer = NULL; static NSUInteger vertexMax = 64; NSUInteger vertexCount = 0, count, i; [EAGLContext setCurrentContext:context]; glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); // Convert locations from Points to Pixels CGFloat scale = self.contentScaleFactor; start.x *= scale;PaintingView.m 12-08-07 9:34 AM start.y *= scale; end.x *= scale; end.y *= scale; // Allocate vertex array buffer if(vertexBuffer == NULL) vertexBuffer = malloc(vertexMax * 2 * sizeof(GLfloat)); // Add points to the buffer so there are drawing points every X pixels count = MAX(ceilf(sqrtf((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y)) / kBrushPixelStep), 1); for(i = 0; i < count; ++i) { if(vertexCount == vertexMax) { vertexMax = 2 * vertexMax; vertexBuffer = realloc(vertexBuffer, vertexMax * 2 * sizeof (GLfloat)); } vertexBuffer[2 * vertexCount + 0] = start.x + (end.x - start.x) * ((GLfloat)i / (GLfloat)count); vertexBuffer[2 * vertexCount + 1] = start.y + (end.y - start.y) * ((GLfloat)i / (GLfloat)count); vertexCount += 1; } // Render the vertex array glVertexPointer(2, GL_FLOAT, 0, vertexBuffer); glDrawArrays(GL_POINTS, 0, vertexCount); // Display the buffer glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES]; }
Solved! Go to Solution.
#import <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
#import <ArcGIS/ArcGIS.h>
//CONSTANTS:
#define kBrushOpacity (1.0 / 3.0)
#define kBrushPixelStep 1
#define kBrushScale 1
#define kLuminosity 0.75
#define kSaturation 1.0
//CLASS INTERFACES:
@interface PaintingView : UIView
{
AGSMapView* _mapView;
AGSGraphicsLayer* _graphicsLayer;
@private
// The pixel dimensions of the backbuffer
GLint backingWidth;
GLint backingHeight;
EAGLContext *context;
// OpenGL names for the renderbuffer and framebuffers used to render to this view
GLuint viewRenderbuffer, viewFramebuffer;
// OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist)
GLuint depthRenderbuffer;
GLuint brushTexture;
CGPoint location;
CGPoint previousLocation;
AGSPoint *currentPoint;
AGSPoint *previousPoint;
Boolean firstTouch;
Boolean needsErase;
AGSMutableMultipoint *lines;
}
@property(nonatomic, readwrite) CGPoint location;
@property(nonatomic, readwrite) CGPoint previousLocation;
@property(nonatomic, readwrite) AGSMutableMultipoint *lines;
//@property(nonatomic, retain) AGSMapView mapView;
- (void)erase;
- (void)setBrushColorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue;
- (void)update;
@end
// PaintingView.m
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>
#import "PaintingView.h"
//CLASS IMPLEMENTATIONS:
// A class extension to declare private methods
@interface PaintingView (private)
- (BOOL)createFramebuffer;
- (void)destroyFramebuffer;
@end
@implementation PaintingView
@synthesize location;
@synthesize previousLocation;
@synthesize lines;
+ (Class) layerClass
{
return [CAEAGLLayer class];
}
// The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
- (id)initWithCoder:(NSCoder*)coder mapView:(AGSMapView*) mapView graphicsLayer:(AGSGraphicsLayer*)graphicsLayer {
_mapView = mapView;
_graphicsLayer = graphicsLayer;
lines = [[AGSMutableMultipoint alloc] initWithSpatialReference:_mapView];
NSMutableArray* recordedPaths;
CGImageRef brushImage;
CGContextRef brushContext;
GLubyte *brushData;
size_t width, height;
if ((self = [super initWithCoder:coder])) {
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
eaglLayer.opaque = YES;
// In this application, we want to retain the EAGLDrawable contents after a call to presentRenderbuffer.
eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
if (!context || ![EAGLContext setCurrentContext:context]) {
[self release];
return nil;
}
// Create a texture from an image
// First create a UIImage object from the data in a image file, and then extract the Core Graphics image
brushImage = [UIImage imageNamed:@"Particle1.png"].CGImage;
// Get the width and height of the image
width = 5;
height= 10;
// Make sure the image exists
if(brushImage) {
// Allocate memory needed for the bitmap context
brushData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
// Use the bitmatp creation function provided by the Core Graphics framework.
brushContext = CGBitmapContextCreate(brushData, width, height, 15, width * 8, CGImageGetColorSpace(brushImage), kCGImageAlphaPremultipliedLast);
// After you create the context, you can draw the image to the context.
CGContextDrawImage(brushContext, CGRectMake(0.0, 0.0, (CGFloat)width, (CGFloat)height), brushImage);
// You don't need the context at this point, so you need to release it to avoid memory leaks.
CGContextRelease(brushContext);
// Use OpenGL ES to generate a name for the texture.
glGenTextures(1, &brushTexture);
// Bind the texture name.
glBindTexture(GL_TEXTURE_2D, brushTexture);
// Set the texture parameters to use a minifying filter and a linear filer (weighted average)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// Specify a 2D texture image, providing the a pointer to the image data in memory
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, brushData);
// Release the image data; it's no longer needed
free(brushData);
}
// Set the view's scale factor
self.contentScaleFactor = 1.0;
// Setup OpenGL states
glMatrixMode(GL_PROJECTION);
CGRect frame = self.bounds;
CGFloat scale = self.contentScaleFactor;
// Setup the view port in Pixels
glOrthof(0, frame.size.width * scale, 0, frame.size.height * scale, -1, 1);
glViewport(0, 0, frame.size.width * scale, frame.size.height * scale);
glMatrixMode(GL_MODELVIEW);
glDisable(GL_DITHER);
glEnable(GL_TEXTURE_2D);
glEnableClientState(GL_VERTEX_ARRAY);
glEnable(GL_BLEND);
// Set a blending function appropriate for premultiplied alpha pixel data
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_POINT_SPRITE_OES);
glTexEnvf(GL_POINT_SPRITE_OES, GL_COORD_REPLACE_OES, GL_TRUE);
glPointSize(width / kBrushScale);
glColor4f(0.5f,0.1f,1.0f,1.0f);
// Make sure to start with a cleared buffer
needsErase = YES;
// Playback recorded path, which is "Shake Me"
recordedPaths = [NSMutableArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Recording" ofType:@"data"]];
if([recordedPaths count])
[self performSelector:@selector(playback:) withObject:recordedPaths afterDelay:0.2];
}
[self setBackgroundColor:[UIColor clearColor]];
//self.userInteractionEnabled = NO;
return self;
}
-(void)layoutSubviews
{
[EAGLContext setCurrentContext:context];
[self destroyFramebuffer];
[self createFramebuffer];
// Clear the framebuffer the first time it is allocated
if (needsErase) {
[self erase];
needsErase = NO;
}
}
- (BOOL)createFramebuffer
{
// Generate IDs for a framebuffer object and a color renderbuffer
glGenFramebuffersOES(1, &viewFramebuffer);
glGenRenderbuffersOES(1, &viewRenderbuffer);
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self.layer];
glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
glGenRenderbuffersOES(1, &depthRenderbuffer);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES)
{
NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
return NO;
}
return YES;
}
// Clean up any buffers we have allocated.
- (void)destroyFramebuffer
{
glDeleteFramebuffersOES(1, &viewFramebuffer);
viewFramebuffer = 0;
glDeleteRenderbuffersOES(1, &viewRenderbuffer);
viewRenderbuffer = 0;
if(depthRenderbuffer)
{
glDeleteRenderbuffersOES(1, &depthRenderbuffer);
depthRenderbuffer = 0;
}
}
// Releases resources when they are not longer needed.
- (void) dealloc
{
if (brushTexture)
{
glDeleteTextures(1, &brushTexture);
brushTexture = 0;
}
if([EAGLContext currentContext] == context)
{
[EAGLContext setCurrentContext:nil];
}
[context release];
[super dealloc];
}
// Erases the screen
- (void) erase
{
[EAGLContext setCurrentContext:context];
// Clear the buffer
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
// Display the buffer
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
}
// Drawings a line onscreen based on where the user touches
- (void) renderLineFromPoint:(AGSPoint*)astart toPoint:(AGSPoint*)aend//(CGPoint)start toPoint:(CGPoint)end
{
static GLfloat* vertexBuffer = NULL;
static NSUInteger vertexMax = 64;
NSUInteger vertexCount = 0, count, i;
CGPoint start = [_mapView toScreenPoint:astart];
CGPoint end = [_mapView toScreenPoint:aend];
[EAGLContext setCurrentContext:context];
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
// Convert locations from Points to Pixels
CGFloat scale = self.contentScaleFactor;
start.x *= scale;
start.y *= scale;
end.x *= scale;
end.y *= scale;
// Allocate vertex array buffer
if(vertexBuffer == NULL)
vertexBuffer = malloc(vertexMax * 2 * sizeof(GLfloat));
// Add points to the buffer so there are drawing points every X pixels
count = MAX(ceilf(sqrtf((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y)) / kBrushPixelStep), 1);
for(i = 0; i < count; ++i) {
if(vertexCount == vertexMax) {
vertexMax = 2 * vertexMax;
vertexBuffer = realloc(vertexBuffer, vertexMax * 2 * sizeof(GLfloat));
}
vertexBuffer[2 * vertexCount + 0] = start.x + (end.x - start.x) * ((GLfloat)i / (GLfloat)count);
vertexBuffer[2 * vertexCount + 1] = start.y + (end.y - start.y) * ((GLfloat)i / (GLfloat)count);
vertexCount += 1;
}
// Render the vertex array
glVertexPointer(2, GL_FLOAT, 0, vertexBuffer);
glDrawArrays(GL_POINTS, 0, vertexCount);
// Display the buffer
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
}
//Returns a map point based on user touch on screen.
-(AGSPoint*)getMapPointFromTouch:(NSSet *)touches
{
UITouch *myTouch = [[touches allObjects] objectAtIndex:0];
CGPoint touchPoint = [myTouch locationInView:self];
AGSPoint *mapPoint = [_mapView toMapPoint:touchPoint];
return mapPoint;
}
// Reads previously recorded points and draws them onscreen. This is the Shake Me message that appears when the application launches.
- (void) playback:(NSMutableArray*)recordedPaths
{
NSData* data = [recordedPaths objectAtIndex:0];
AGSPoint* point = (AGSPoint*)[data bytes];
NSUInteger count = [data length] / sizeof(CGPoint),
i;
// Render the current path
for(i = 0; i < count - 1; ++i)//, ++point)
[self renderLineFromPoint:[lines pointAtIndex:i] toPoint:[lines pointAtIndex:i+1]];
//[self renderLineFromPoint:*point toPoint:*(point + 1)];
// Render the next path after a short delay
[recordedPaths removeObjectAtIndex:0];
if([recordedPaths count])
[self performSelector:@selector(playback:) withObject:recordedPaths afterDelay:0.0];
}
// Handles the start of a touch
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
AGSPoint* coord = [[event touchesForView:_mapView] anyObject];
CGRect bounds = [self bounds];
UITouch* touch = [[event touchesForView:self] anyObject];
firstTouch = YES;
// get touch point and store are current point
currentPoint = [self getMapPointFromTouch:touches];
[lines addPoint: currentPoint];
}
// Handles the continuation of a touch.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
AGSPoint* coord = [[event touchesForView:_mapView] anyObject];
CGRect bounds = [self bounds];
UITouch* touch = [[event touchesForView:self] anyObject];
// Convert touch point from UIView referential to OpenGL one (upside-down flip)
if (firstTouch) {
firstTouch = NO;
previousPoint = [currentPoint copy];
} else {
previousPoint = [currentPoint copy];
currentPoint = [self getMapPointFromTouch:touches];
[lines addPoint:currentPoint];
}
// Render the stroke
[self renderLineFromPoint:previousPoint toPoint:currentPoint];
}
// Handles the end of a touch event when the touch is a tap.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
CGRect bounds = [self bounds];
UITouch* touch = [[event touchesForView:self] anyObject];
if (firstTouch) {
firstTouch = NO;
previousPoint = [currentPoint copy];
[self renderLineFromPoint:previousPoint toPoint:currentPoint];
}
}
// Handles the end of a touch event.
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
}
- (void)setBrushColorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue
{
// Set the brush color using premultiplied alpha values
glColor4f(100.0f,50.0f,200.0f,1.0f);
}
@end