Related
I'm trying to make a cocos2d/box2d game work on iPad, iPhone and iPhone retina.
My problem is, that the fixture and body don't line up on the retina simulator, please click on screenshots below for illustration (as a new stackoverflow member, it won't allow me to post the screenshot here).
screenshot
(please disregard the different shapes, I want the 4 corners to line up)
I've done quite a bit of research on this over the last couple of days, and the closest I found was this:
link
But the solution offered there with PTM_RATIO and CC_CONTENT_SCALE_FACTOR() doesn't seem to work in my case. I think it has to do with the fact that I don't load an image from file into my sprite. Most solutions to this problem are based on loading -hd image files for the retina display, but I don't want to use files in my game at all. I basically want to draw the polygons myself at runtime,
My code looks as follows:
-(CCSprite*)addSprite
{
CGSize contextsize = CGSizeMake(200, 200);
UIGraphicsBeginImageContext(contextsize);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextFlush(context);
CGContextSetAllowsAntialiasing(context, true);
CGContextTranslateCTM(context, 0, contextsize.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGFloat components[] = {0.0, 0.0, 1.0, 1.0};
CGColorRef color = CGColorCreate(colorspace, components);
CGContextSetStrokeColorWithColor(context, color);
UIBezierPath* aPath;
aPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 100)
radius:100
startAngle:0
endAngle:1.57
clockwise:YES];
[aPath addArcWithCenter:CGPointMake(100, 100)
radius:50
startAngle:1.57
endAngle:0
clockwise:NO];
[aPath stroke];
CGContextStrokePath(context);
CGColorSpaceRelease(colorspace);
CGColorRelease(color);
UIImage *graphImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CCTexture2D *tex = [[[CCTexture2D alloc] initWithImage:graphImage] autorelease];
CCSprite *sprite = [CCSprite spriteWithTexture:tex];
return sprite;
}
-(void) addFixture:(CCSprite *)fixsprite
{
b2Vec2 arcdots[] = {
b2Vec2(50.0f / PTM_RATIO, 0.0f / PTM_RATIO),
b2Vec2(100.0f / PTM_RATIO, 0.0f / PTM_RATIO),
b2Vec2(0.0f / PTM_RATIO, 100.0f / PTM_RATIO),
b2Vec2(0.0f / PTM_RATIO, 50.0f / PTM_RATIO)
};
b2PolygonShape p_shape;
b2FixtureDef fixtureDef;
b2BodyDef bodyDef;
bodyDef.type = b2_kinematicBody;
bodyDef.position.Set(100/PTM_RATIO, 100/PTM_RATIO);
bodyDef.userData = fixsprite;
b2Body *body = world->CreateBody(&bodyDef);
p_shape.Set(arcdots, 4);
fixtureDef.shape = &p_shape;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.3f;
body->CreateFixture(&fixtureDef);
}
And I call these functions from the main routine as follows:
CCSprite *sprite2 = [self addSprite];
sprite2.position = ccp(0, 0);
[self addChild:sprite2 z:0];
[self addFixture:sprite2];
I have these lines uncommented in the delegate file:
if( ! [director enableRetinaDisplay:YES] )
CCLOG(#"Retina Display Not supported");
Please let me know if further information is required. And please be gentle, I'm only starting to learn this. Thanks for your time.
Unless otherwise mentioned, all coordinates in cocos2d (and most of UIKit) are given in points, not pixels. On a Retina display device you still have a point resolution of 480x320 points (960x640 pixels).
From that follows: when you calculate in actual pixels, multiply or divide by CC_CONTENT_SCALE_FACTOR. If you deal with point coordinates, do nothing. Since you're rendering your own polys I assume you know whether you use actual pixel coordinates or not. If you use OpenGL directly, then you'll be working with pixel coordinates.
I'm not sure if enabling Retina display mode does anything for you if you don't use cocos2d to render your content.
Lastly, a common misunderstanding is that the Box2D world is using point coordinates and must be transformed to pixels or vice versa. Neither is the case. The Box2D world is completely oblivious to a specific coordinate system. The use of PTM_RATIO is done only to ensure that Box2D coordinates are within reasonable ranges for the Box2D engine, since it works best with objects that are 1 meter in size/diameter, and most objects should range from 0.1 to 10 meters in diameter.
To define what I'm trying to do: I want to be able to take an arbitrary 'sprite' image from a ^2x^2 sized PNG, and display just the pixels of interest to a given x/y position on screen.
My results are the problem - major distortion - it looks awful! (Note these SS's are in iPhone sim but on real retina device they appear the same.. junky). Here is a screenshot of the source PNG in 'preview' - which looks wonderful (any variations on rendering that I describe in this question look almost exactly like the junky one)
Previously, I've asked a question about displaying a non-power-of-2 texture as a sprite using OpenGL ES 2.0 (although this applies to any OpenGL). I'm close, but I have some issues that I can't resolve. I think there are probably multiple bugs - I think there's some bug where I'm basically aliasing what I'm displaying by rendering large then squashing x2 or vice versa but I can't see it. Additionally, there are off by one errors and I cannot get a handle on them. I can't visually identify them occurring but I know for sure they're there.
I'm working in 960 x 640 landscape (on iPhone4 retina display). So I expect 0->959 moves left to right, 0->639 moves bottom to top. (And I think I'm seeing opposite of this - but that's not what this question is about)
To make things easy what I'm trying to achieve in this test case is a FULL SCREEN 960x640 display of a PNG file. Just one of them. I display a red background first so that it's obvious if I'm covering the screen or not.
Update: I realized the 'glViewport' inside of the setFramebuffer call was setting my width and height backwards. I noticed this because when I would set my geometry to draw from 0,0 to 100,100 it drew in a rectangle not a square. When I swapped these, that call does draw a square. However, using that same call, my entire screen fills with vertex range of 0,0 -> 480,320 (half 'resolution').. don't understand that. However no matter where I push on from this, I'm still not getting a good looking result
Here's my vertex shader:
attribute vec4 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
// Gives 'landscape' full screen..
mat4 projectionMatrix = mat4( 2.0/640.0, 0.0, 0.0, -1.0,
0.0, 2.0/960.0, 0.0, -1.0,
0.0, 0.0, -1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
// Gives a 1/4 of screen.. (not doing 2.0/.. was suggested in previous SO Q)
/*mat4 projectionMatrix = mat4( 1.0/640.0, 0.0, 0.0, -1.0,
0.0, 1.0/960.0, 0.0, -1.0,
0.0, 0.0, -1.0, 0.0,
0.0, 0.0, 0.0, 1.0); */
// Apply the projection matrix to the position and pass the texCoord
void main()
{
gl_Position = a_position;
gl_Position *= projectionMatrix;
v_texCoord = a_texCoord;
}
Here's my fragment shader:
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D s_texture;
void main()
{
gl_FragColor = texture2D(s_texture, v_texCoord);
}
Here's my draw code:
#define MYWIDTH 960.0f
#define MYHEIGHT 640.0f
// I have to refer to 'X' as height although I'd assume I use 'Y' here..
// I think my X and Y throughout this whole block of code is screwed up
// But, I have experimented flipping them all and verifying that if they
// Are taken from the way they're set now to swapping X and Y that things
// end up being turned the wrong way. So this is a mess, but unlikely my problem
#define BG_X_ORIGIN 0.0f
// ALSO NOTE HERE: I have to put my 'dest' at 640.0f.. --- see note [1] below
#define BG_X_DEST 640.0f
#define BG_Y_ORIGIN 0.0f
// --- see note [1] below
#define BG_Y_DEST 960.0f
// These are texturing coordinates, I texture starting at '0' px and then
// I calculate a percentage of the texture to use based on how many pixels I use
// divided by the actual size of the image (1024x1024)
#define BG_X_ZERO 0.0f
#define BG_Y_USEPERCENTAGE BG_X_DEST / 1023.0f
#define BG_Y_ZERO 0.0f
#define BG_X_USEPERCENTAGE BG_Y_DEST / 1023.0f
// glViewport(0, 0, MYWIDTH, MYHEIGHT);
// See note 2.. it sets glViewport basically, provided by Xcode project template
[(EAGLView *)self.view setFramebuffer];
// Big hack just to get things going - like I said before, these could be backwards
// w/respect to X and Y
static const GLfloat backgroundVertices[] = {
BG_X_ORIGIN, BG_Y_ORIGIN,
BG_X_DEST, BG_Y_ORIGIN,
BG_X_ORIGIN, BG_Y_DEST,
BG_X_DEST, BG_Y_DEST
};
static const GLfloat backgroundTexCoords[] = {
BG_X_ZERO, BG_Y_USEPERCENTAGE,
BG_X_USEPERCENTAGE, BG_Y_USEPERCENTAGE,
BG_X_ZERO, BG_Y_ZERO,
BG_X_USEPERCENTAGE, BG_Y_ZERO
};
// Turn on texturing
glEnable(GL_TEXTURE_2D);
// Clear to RED so that it's obvious when I'm not drawing my sprite on screen
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Texturing parameters - these make sense.. don't think they are the issue
glActiveTexture(GL_TEXTURE0);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);//GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);//GL_LINEAR);
// Update attribute values.
glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, backgroundVertices);
glEnableVertexAttribArray(ATTRIB_VERTEX);
glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, 0, 0, backgroundTexCoords);
glEnableVertexAttribArray(ATTRIB_TEXCOORD);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, background->textureId);
// I don't understand what this uniform does in the texture2D call in shader.
glUniform1f(uniforms[UNIFORM_SAMPLERLOC], 0);
// Draw the geometry...
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// present the framebuffer see note [3]
[(EAGLView *)self.view presentFramebuffer];
Note [1]:
If I set BG_X_DEST to 639.0f I do not get full coverage of the 640 pixels, I get red showing through on the right hand side. But this doesn't make sense to me - I'm aiming for pixel perfect and I have to draw my sprite geometry from 0 to 640 which is 641 pixels when I only have 640!!! red line appearing with 639f instead of 640f
And if I set BG_Y_DEST to 959.0f I do not get the red line show throug.
red line top bug appearing with 958f instead of 960 or 959f
This may be a good clue as to what bug(s) I have going on.
Note: [2] - included in the OpenGL ES 2 framework by Xcode
- (void)setFramebuffer
{
if (context)
{
[EAGLContext setCurrentContext:context];
if (!defaultFramebuffer)
[self createFramebuffer];
glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);
glViewport(0, 0, framebufferWidth, framebufferHeight);
}
}
Note [3]: - included in the OpenGL ES 2 framework by Xcode
- (BOOL)presentFramebuffer
{
BOOL success = FALSE;
if (context)
{
[EAGLContext setCurrentContext:context];
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
success = [context presentRenderbuffer:GL_RENDERBUFFER];
}
return success;
}
Note [4] - relevant image loading code (I have used PNG with and without alpha channel and actually it doesn't seem to make any difference... I also have tried to change my code up to be ARGB instead of RGBA and that's wrong - since A = 1.0 everywhere, I get a very RED image, which makes me think the RGBA is in fact valid and this code is right.): update: I have switched this texture loading to a completely different setup using CG/ImageIO calls and it looks identical to this so I assume it's not some kind of aliasing or color compression done by the image libraries (unless they both go to the same fundamental calls, which is possible..)
// Otherwise it isn't already loaded
glEnable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);//GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);//GL_LINEAR);
// TODO Next 2 can prob go later on..
glGenTextures(1, &(newTexture->textureId)); // generate Texture
// Use this before 'drawing' the texture to the memory...
glBindTexture(GL_TEXTURE_2D, newTexture->textureId);
NSString *path = [[NSBundle mainBundle]
pathForResource:[NSString stringWithUTF8String:newTexture->filename.c_str()] ofType:#"png"];
NSData *texData = [[NSData alloc] initWithContentsOfFile:path];
UIImage *image = [[UIImage alloc] initWithData:texData];
if (image == nil)
NSLog(#"Do real error checking here");
newTexture->width = CGImageGetWidth(image.CGImage);
newTexture->height = CGImageGetHeight(image.CGImage);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
void *imageData = malloc(newTexture->height * newTexture->width * 4 );
CGContextRef myContext = CGBitmapContextCreate
(imageData, newTexture->width, newTexture->height, 8, 4 * newTexture->width, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big );
CGColorSpaceRelease(colorSpace);
CGContextClearRect(myContext, CGRectMake(0, 0, newTexture->width, newTexture->height));
CGContextDrawImage(myContext, CGRectMake(0, 0, newTexture->width, newTexture->height), image.CGImage);
// Texture is created!
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, newTexture->width, newTexture->height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, imageData);
CGContextRelease(myContext);
free(imageData);
[image release];
[texData release];
[(EAGLView *)self.view setContentScaleFactor:2.0f];
By default, iPhone windows do scaling to reach their high resolution modes. Which was destroying my image quality ..
Thanks for all the help folks
I am trying to draw point sprites with OpenGL ES on iPhone. It's possible there could be very many of them (1000) and up to 64 pixels wide (maybe that's my problem right there - is there a limit or could I be using too much memory?)
I am using CADisplayLink to time the frames. What happens is that the first gl drawing function tends to delay or stall when either the point count is too high or when the point size is too big. In my example below, glClear() is the first drawing function, and it can take anywhere from 0.02 seconds to 0.2 seconds to run. If I simply comment out glClear, glDrawArrays becomes the slow function (it runs very fast otherwise).
This example is what I've stripped my code down to in order to isolate the problem. It simply draws a bunch of point sprites, with no texture, all in the same spot. I am using VBOs to store all the sprite data (position, color, size). It may seem like overkill for the example but of course I have intentions to modify this data later.
This is the view's init function (minus the boilerplate gl setup):
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glDisable(GL_LIGHTING);
glDisable(GL_FOG);
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendEquationOES(GL_FUNC_ADD_OES);
glClearColor(0.0, 0.0, 0.0, 0.0);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glTexEnvi(GL_POINT_SPRITE_OES, GL_COORD_REPLACE_OES, GL_TRUE);
glEnable(GL_POINT_SPRITE_OES);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_POINT_SIZE_ARRAY_OES);
glEnableClientState(GL_COLOR_ARRAY);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glEnable(GL_POINT_SMOOTH);
glGenBuffers(1, &vbo); // vbo is an instance variable
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glMatrixMode(GL_PROJECTION);
glOrthof(0.0, [self frame].size.width, 0.0, [self frame].size.height, 1.0f, -1.0f);
glViewport(0, 0, [self frame].size.width, [self frame].size.height);
glMatrixMode(GL_MODELVIEW);
glTranslatef(0.0f, [self frame].size.height, 0.0f);
glScalef(1.0f, -1.0f, 1.0f);
And this is the rendering function:
- (void)render
{
glClear(GL_COLOR_BUFFER_BIT); // This function runs slowly!
int pointCount = 1000;
// fyi...
// typedef struct {
// CGPoint point;
// CFTimeInterval time;
// GLubyte r, g, b, a;
// GLfloat size;
// } MyPoint;
glBufferData(GL_ARRAY_BUFFER, sizeof(MyPoint)*pointCount, NULL, GL_DYNAMIC_DRAW);
MyPoint * vboBuffer = (MyPoint *)glMapBufferOES(GL_ARRAY_BUFFER, GL_WRITE_ONLY_OES);
for (int i = 0; i < pointCount; i++) {
vboBuffer[i].a = (GLubyte)0xFF;
vboBuffer[i].r = (GLubyte)0xFF;
vboBuffer[i].g = (GLubyte)0xFF;
vboBuffer[i].b = (GLubyte)0xFF;
vboBuffer[i].size = 64.0;
vboBuffer[i].point = CGPointMake(200.0, 200.0);
}
glUnmapBufferOES(GL_ARRAY_BUFFER);
glPointSizePointerOES(GL_FLOAT, sizeof(MyPoint), (void *)offsetof(MyPoint, size));
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(MyPoint), (void *)offsetof(MyPoint, r));
glVertexPointer(2, GL_FLOAT, sizeof(MyPoint), (void *)offsetof(MyPoint, point));
glDrawArrays(GL_POINTS, 0, pointCount);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
}
Why is the glClear function stalling? It doesn't just delay in random amounts - depending on the point count or size, it tends to randomly delay in the same intevals (eg. 0.015 sec, 0.030 sec, 0.045 sec, etc). Also something strange I noticed is that if I switch to glBlendMode(GL_ZERO, GL_ONE), it runs just fine (although this is will not be the visual effect I'm after). Other glBlendMode values change the speed as well - usually for the better. That makes me think it is not a memory issue because that has nothing to do with the VBO (right?).
I admit I am a bit new at OpenGL and may be misunderstanding basic concepts about VBOs or other things. Any help or guidance is greatly appreciated!
If glClear() is slow you might try drawing a large blank quad that completely covers the viewport area.
Are you using sync (or is it enabled?). The delay you're seeing might be related to the fact that CPU and GPU run in parallel, so measuring time of individual GL calls has no meaning.
If you're using VSync (or the GPU is heavily loaded), there might be some latency in the SwapBuffers call, since some drivers make busy loops to wait for VBlank.
But first consider that you should NOT time individual GL calls, since most GL calls just set some state of the GPU or write to a command buffer, the command execution happens asynchronously.
I want to draw an image that would effectively be a circular progress indicator on a UIButton. Because the image is supposed to represent progress of a task, I do not think I should handle the drawing code in the view's drawrect method.
I have a thread that is performing some tasks. After each task, it calls a method on the main thread. The called method is supposed to update the image on the button.
In the button update method, I create a CGContextRef by using CGBitmapContextCreate.
Then I use the button's frame to create a CGRect.
Then I attempt to draw into using the context I created.
Lastly I set NeedsDisplay and clean up.
But none of this is inside the view's drawrect method.
I would like to know if anyone has used CGContext to draw on / in a view on-demand in a view while the view is being displayed.
I would like to get some ideas regarding an approach to doing this.
Here is an encapsulated version of what I am doing now:
CGContextRef xContext = nil;
CGColorSpaceRef xColorSpace;
CGRect xRect;
void* xBitmapData;
int iBMPByteCount;
int iBMPBytesPerRow;
float fBMPWidth = 20.0f;
float fBMPHeight = 20.0f;
float fPI = 3.14159;
float fRadius = 25.0f;
iBMPBytesPerRow = (fBMPWidth * 4);
iBMPByteCount = (iBMPBytesPerRow * fBMPHeight);
xColorSpace = CGColorSpaceCreateDeviceRGB();
xBitmapData = malloc(iBMPByteCount);
xContext = CGBitmapContextCreate(xBitmapData, fBMPWidth, fBMPHeight, 8, iBMPBytesPerRow, xColorSpace, kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(xColorSpace);
UIGraphicsPushContext(xContext);
xRect = CGRectMake(30.0f, 400.0f, 50.0f, 50.0f);
float fWidth = xRect.size.width;
float fHeight = xRect.size.height;
CGContextClearRect(xContext, xRect);
CGContextSetRGBStrokeColor(xContext, 0.5f, 0.6f, 0.7f, 1.0f);
CGContextSetLineWidth(xContext, 1.0f);
float fArcBegin = 45.0f * fPI / 180.0f;
float fArcEnd = 90.0f * fPI / 180.0f;
CGContextSetFillColor(xContext, CGColorGetComponents( [[UIColor greenColor] CGColor]));
CGContextMoveToPoint(xContext, fWidth, fHeight);
CGContextAddArc(xContext, fWidth, fHeight, fRadius, fArcBegin, fArcEnd, 0);
CGContextClosePath(xContext);
CGContextFillPath(xContext);
UIGraphicsPopContext;
CGContextRelease(xContext);
[self.view setNeedsDisplay];
// [self.view setNeedsDisplayInRect: xRect];
The above is a little bit wonky because I've tried different tweaks. However, I think it communicates what I am trying to do.
Alternative approach:
You could create a series of images that represent the progress updates and then replace the UIButton currentImage property with the setImage:forState: method at each step of the process. This doesn't require drawing in the existing view and this approach has worked well for me to show simple "animation" of images (buttons or other).
Would this approach work for you? If not, why not?
Bart
This was really bugging me so after dealing with a series of silly, but necessary issues regarding the project I want this functionality for, I played around with it.
The end result is that I can now arbitrarily draw an arc representing the progress of a particular background task to a button.
The goal was to draw something like the little indicator in the lower right hand corner of the XCode windows while a project is being cleaned or compiled.
I created a function that will draw and fill an arc and return it as a UIImage.
The worker thread calls method (PerformSelectorOnMainThread) with the current values and a button identifier. In the called method, I call the arc image function with the percentage filled and such.
example call:
oImg = [self ArcImageCreate:100.0f fWidth:100.0f
fPercentFilled: 0.45f fAngleStart: 0.0f xFillColor:[UIColor blueColor]];
Then set the background image of the button:
[oBtn setBackgroundImage: oImg forState: UIControlStateNormal];
Here is the function:
It is not finished, but it works well enough to illustrate how I am doing this.
/**
ArcImageCreate
#ingroup UngroupedFunctions
#brief Create a filled or unfilled solid arc and return it as a UIImage.
Allows for dynamic / arbitrary update of an object that allows a UIImage to be drawn on it. \
This can be used for some sort of pie chart or progress indicator by Image Flipping.
#param fHeight The height of the created UIImage.
#param fWidth The width of the created UIImage.
#param fPercentFilled A percentage of the circle to be filled by the arc. 0.0 to 1.0.
#param AngleStart The angle where the arc should start. 0 to 360. Clock Reference.
#param xFillColor The color of the filled area.
#return Pointer to a UIImage.
#todo [] Validate object creation at each step.
#todo [] Convert PercentFilled (0.0 to 1.0) to appropriate radian(?) (-3.15 to +3.15)
#todo [] Background Image Support. Allow for the arc to be drawn on top of an image \
and the whole thing returned.
#todo [] Background Image Reduction. Background images will have to be resized to fit the specfied size. \
Do not want to return a 65KB object because the background is 60K or whatever.
#todo [] UIColor RGBA Components. Determine a decent method of extracting RGVA values \
from a UIColor*. Check out arstechnica.com/apple/guides/2009/02/iphone-development-accessing-uicolor-components.ars \
for an idea.
*/
- (UIImage*) ArcImageCreate: (float)fHeight fWidth:(float)fWidth fPercentFilled:(float)fPercentFilled fAngleStart:(float)fAngleStart xFillColor:(UIColor*)xFillColor
{
UIImage* fnRez = nil;
float fArcBegin = 0.0f;
float fArcEnd = 0.0f;
float fArcPercent = 0.0f;
UIColor* xArcColor = nil;
float fArcImageWidth = 0.0f;
float fArcImageHeight = 0.0f;
CGRect xArcImageRect;
CGContextRef xContext = nil;
CGColorSpaceRef xColorSpace;
void* xBitmapData;
int iBMPByteCount;
int iBMPBytesPerRow;
float fPI = 3.14159;
float fRadius = 25.0f;
// #todo Force default of 100x100 px if out of bounds. \
// Check max image dimensions for iPhone. \
// If negative, flip values *if* values are 'reasonable'. \
// Determine minimum useable pixel dimensions. 10x10 px is too small. Or is it?
fArcImageWidth = fHeight;
fArcImageHeight = fWidth;
// Get the passed target percentage and clip it between 0.0 and 1.0
fArcPercent = (fPercentFilled 1.0f) ? 1.0f : fPercentFilled;
fArcPercent = (fArcPercent > 1.0f) ? 1.0f : fArcPercent;
// Get the passed start angle and clip it between 0.0 to 360.0
fArcBegin = (fAngleStart 359.0f) ? 0.0f : fAngleStart;
fArcBegin = (fArcBegin > 359.0f) ? 0.0f : fArcBegin;
fArcBegin = (fArcBegin * fPI) / 180.0f;
fArcEnd = ((360.0f * fArcPercent) * fPI) / 180.0f;
//
if (xFillColor == nil) {
// random color
} else {
xArcColor = xFillColor;
}
// Calculate memory required for image.
iBMPBytesPerRow = (fArcImageWidth * 4);
iBMPByteCount = (iBMPBytesPerRow * fArcImageHeight);
xBitmapData = malloc(iBMPByteCount);
// Create a color space. Behavior changes at OSXv10.4. Do not rely on it for consistency across devices.
xColorSpace = CGColorSpaceCreateDeviceRGB();
// Set the system to draw. Behavior changes at OSXv10.3.
// Both of these work. Not sure which is better.
// xContext = CGBitmapContextCreate(xBitmapData, fArcImageWidth, fArcImageHeight, 8, iBMPBytesPerRow, xColorSpace, kCGImageAlphaPremultipliedFirst);
xContext = CGBitmapContextCreate(NULL, fArcImageWidth, fArcImageHeight, 8, iBMPBytesPerRow, xColorSpace, kCGImageAlphaPremultipliedFirst);
// Let the system know the colorspace reference is no longer required.
CGColorSpaceRelease(xColorSpace);
// Set the created context as the current context.
// UIGraphicsPushContext(xContext);
// Define the image's box.
xArcImageRect = CGRectMake(0.0f, 0.0f, fArcImageWidth, fArcImageHeight);
// Clear the image's box.
// CGContextClearRect(xContext, xRect);
// Draw the ArcImage's background image.
// CGContextDrawImage(xContext, xArcImageRect, [oBackgroundImage CGImage]);
// Set Us Up The Transparent Drawing Area.
CGContextBeginTransparencyLayer(xContext, nil);
// Set the fill and stroke colors
// #todo [] Determine why SetFilColor does not. Use alternative method.
// CGContextSetFillColor(xContext, CGColorGetComponents([xArcColor CGColor]));
// CGContextSetFillColorWithColor(xContext, CGColorGetComponents([xArcColor CGColor]));
// Test Colors
CGContextSetRGBFillColor(xContext, 0.3f, 0.4f, 0.5f, 1.0f);
CGContextSetRGBStrokeColor(xContext, 0.5f, 0.6f, 0.7f, 1.0f);
CGContextSetLineWidth(xContext, 1.0f);
// Something like this to reverse drawing?
// CGContextTranslateCTM(xContext, TranslateXValue, TranslateYValue);
// CGContextScaleCTM(xContext, -1.0f, 1.0f); or CGContextScaleCTM(xContext, 1.0f, -1.0f);
// Test Vals
// fArcBegin = 45.0f * fPI / 180.0f; // 0.785397
// fArcEnd = 90.0f * fPI / 180.0f; // 1.570795
// Move to the start point and draw the arc.
CGContextMoveToPoint(xContext, fArcImageWidth/2.0f, fArcImageHeight/2.0f);
CGContextAddArc(xContext, fArcImageWidth/2.0f, fArcImageHeight/2.0f, fRadius, fArcBegin, fArcEnd, 0);
// Ask the OS to close the arc (current point to starting point).
CGContextClosePath(xContext);
// Fill 'er up. Implicit path closure.
CGContextFillPath(xContext);
// CGContextEOFillPath(context);
// Close Transparency drawing area.
CGContextEndTransparencyLayer(xContext);
// Create an ImageReference and create a UIImage from it.
CGImageRef xCGImageTemp = CGBitmapContextCreateImage(xContext);
CGContextRelease(xContext);
fnRez = [UIImage imageWithCGImage: xCGImageTemp];
CGImageRelease(xCGImageTemp);
// UIGraphicsPopContext;
return fnRez;
}
I have a view with UIImageView and an UIImage set to it. How do I make image sharp or blur using coregraphics?
Apple has a great sample program called GLImageProcessing that includes a very fast blur/sharpen effect using OpenGL ES 1.1 (meaning it works on all iPhones, not just the 3gs.)
If you're not fairly experienced with OpenGL, the code may make your head hurt.
Going down the OpenGL route felt like insane overkill for my needs (blurring a touched point on an image). Instead I implemented a simple blurring process that takes a touch point, creates a rect containing that touch point, samples the image in that point and then redraws the sample image upside down on top of the source rect several times slightly offset with slightly different opacity. This produces a pretty nice poor man's blur effect without an insane amount of code and complexity. Code follows:
- (UIImage*)imageWithBlurAroundPoint:(CGPoint)point {
CGRect bnds = CGRectZero;
UIImage* copy = nil;
CGContextRef ctxt = nil;
CGImageRef imag = self.CGImage;
CGRect rect = CGRectZero;
CGAffineTransform tran = CGAffineTransformIdentity;
int indx = 0;
rect.size.width = CGImageGetWidth(imag);
rect.size.height = CGImageGetHeight(imag);
bnds = rect;
UIGraphicsBeginImageContext(bnds.size);
ctxt = UIGraphicsGetCurrentContext();
// Cut out a sample out the image
CGRect fillRect = CGRectMake(point.x - 10, point.y - 10, 20, 20);
CGImageRef sampleImageRef = CGImageCreateWithImageInRect(self.CGImage, fillRect);
// Flip the image right side up & draw
CGContextSaveGState(ctxt);
CGContextScaleCTM(ctxt, 1.0, -1.0);
CGContextTranslateCTM(ctxt, 0.0, -rect.size.height);
CGContextConcatCTM(ctxt, tran);
CGContextDrawImage(UIGraphicsGetCurrentContext(), rect, imag);
// Restore the context so that the coordinate system is restored
CGContextRestoreGState(ctxt);
// Cut out a sample image and redraw it over the source rect
// several times, shifting the opacity and the positioning slightly
// to produce a blurred effect
for (indx = 0; indx < 5; indx++) {
CGRect myRect = CGRectOffset(fillRect, 0.5 * indx, 0.5 * indx);
CGContextSetAlpha(ctxt, 0.2 * indx);
CGContextScaleCTM(ctxt, 1.0, -1.0);
CGContextDrawImage(ctxt, myRect, sampleImageRef);
}
copy = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return copy;
}
What you really need are in the image filters in the CoreImage API. Unfortunately CoreImage is not supported on the iPhone (unless that changed recently and I missed it). Be careful here, as, IIRC, they are available in the SIM - but not on the device.
AFAIK there is no other way to do it properly with the native libraries, although I've sort of faked a blur before by creating an extra layer over the top which is a copy of what's below, offset by a pixel or two and with a low alpha value. For a proper blur effect, tho, the only way I've been able to do it is offline in Photoshop or similar.
Would be keen to hear if there is a better way too, but to my knowledge that is the situation currently.
Have a look at the following libraries:
https://github.com/coryleach/UIImageAdjust
https://github.com/esilverberg/ios-image-filters
https://github.com/cmkilger/CKImageAdditions